companionbot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/bin/companionbot.js +2 -0
- package/dist/ai/claude.js +104 -0
- package/dist/briefing/index.js +202 -0
- package/dist/calendar/index.js +283 -0
- package/dist/cli/main.js +128 -0
- package/dist/cli/setup.js +103 -0
- package/dist/config/secrets.js +15 -0
- package/dist/heartbeat/index.js +273 -0
- package/dist/index.js +2 -0
- package/dist/reminders/index.js +215 -0
- package/dist/session/state.js +70 -0
- package/dist/telegram/bot.js +38 -0
- package/dist/telegram/handlers/commands.js +478 -0
- package/dist/telegram/handlers/index.js +2 -0
- package/dist/telegram/handlers/messages.js +115 -0
- package/dist/telegram/utils/cache.js +39 -0
- package/dist/telegram/utils/index.js +6 -0
- package/dist/telegram/utils/prompt.js +73 -0
- package/dist/telegram/utils/system-prompt.js +108 -0
- package/dist/telegram/utils/url.js +86 -0
- package/dist/tools/index.js +806 -0
- package/dist/workspace/index.js +6 -0
- package/dist/workspace/init.js +65 -0
- package/dist/workspace/load.js +58 -0
- package/dist/workspace/paths.js +42 -0
- package/package.json +57 -0
- package/templates/AGENTS.md +33 -0
- package/templates/BOOTSTRAP.md +44 -0
- package/templates/HEARTBEAT.md +16 -0
- package/templates/IDENTITY.md +13 -0
- package/templates/MEMORY.md +3 -0
- package/templates/SOUL.md +13 -0
- package/templates/TOOLS.md +20 -0
- package/templates/USER.md +14 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// 경로 유틸리티
|
|
2
|
+
export { getWorkspacePath, getWorkspaceFilePath, getMemoryDirPath, getDailyMemoryPath, getTemplatesPath, getCanvasPath, WORKSPACE_FILES, } from "./paths.js";
|
|
3
|
+
// 초기화 함수
|
|
4
|
+
export { isWorkspaceInitialized, initWorkspace, hasBootstrap, deleteBootstrap, } from "./init.js";
|
|
5
|
+
// 로드/저장 함수
|
|
6
|
+
export { loadWorkspace, loadBootstrap, saveWorkspaceFile, appendToMemory, loadRecentMemories, } from "./load.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { getWorkspacePath, getTemplatesPath, getMemoryDirPath, getCanvasPath, WORKSPACE_FILES, } from "./paths.js";
|
|
4
|
+
export async function isWorkspaceInitialized() {
|
|
5
|
+
try {
|
|
6
|
+
const workspacePath = getWorkspacePath();
|
|
7
|
+
await fs.access(workspacePath);
|
|
8
|
+
// IDENTITY.md가 있으면 초기화된 것으로 간주
|
|
9
|
+
const identityPath = path.join(workspacePath, "IDENTITY.md");
|
|
10
|
+
await fs.access(identityPath);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function initWorkspace() {
|
|
18
|
+
const workspacePath = getWorkspacePath();
|
|
19
|
+
const templatesPath = getTemplatesPath();
|
|
20
|
+
const memoryPath = getMemoryDirPath();
|
|
21
|
+
// 워크스페이스 디렉토리 생성
|
|
22
|
+
await fs.mkdir(workspacePath, { recursive: true });
|
|
23
|
+
// 메모리 디렉토리 생성
|
|
24
|
+
await fs.mkdir(memoryPath, { recursive: true });
|
|
25
|
+
// canvas 디렉토리 생성
|
|
26
|
+
await fs.mkdir(getCanvasPath(), { recursive: true });
|
|
27
|
+
// 템플릿 파일 복사
|
|
28
|
+
for (const file of WORKSPACE_FILES) {
|
|
29
|
+
const srcPath = path.join(templatesPath, file);
|
|
30
|
+
const destPath = path.join(workspacePath, file);
|
|
31
|
+
try {
|
|
32
|
+
// 대상 파일이 이미 존재하면 건너뛰기
|
|
33
|
+
await fs.access(destPath);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// 파일이 없으면 복사
|
|
37
|
+
try {
|
|
38
|
+
const content = await fs.readFile(srcPath, "utf-8");
|
|
39
|
+
await fs.writeFile(destPath, content, "utf-8");
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.error(`Warning: Could not copy ${file}:`, err);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function hasBootstrap() {
|
|
48
|
+
try {
|
|
49
|
+
const bootstrapPath = path.join(getWorkspacePath(), "BOOTSTRAP.md");
|
|
50
|
+
await fs.access(bootstrapPath);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export async function deleteBootstrap() {
|
|
58
|
+
try {
|
|
59
|
+
const bootstrapPath = path.join(getWorkspacePath(), "BOOTSTRAP.md");
|
|
60
|
+
await fs.unlink(bootstrapPath);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// 파일이 없어도 무시
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { getWorkspacePath, getWorkspaceFilePath, getDailyMemoryPath } from "./paths.js";
|
|
4
|
+
async function readFileOrNull(filePath) {
|
|
5
|
+
try {
|
|
6
|
+
return await fs.readFile(filePath, "utf-8");
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function loadWorkspace() {
|
|
13
|
+
const workspacePath = getWorkspacePath();
|
|
14
|
+
const [agents, bootstrap, identity, soul, user, memory] = await Promise.all([
|
|
15
|
+
readFileOrNull(path.join(workspacePath, "AGENTS.md")),
|
|
16
|
+
readFileOrNull(path.join(workspacePath, "BOOTSTRAP.md")),
|
|
17
|
+
readFileOrNull(path.join(workspacePath, "IDENTITY.md")),
|
|
18
|
+
readFileOrNull(path.join(workspacePath, "SOUL.md")),
|
|
19
|
+
readFileOrNull(path.join(workspacePath, "USER.md")),
|
|
20
|
+
readFileOrNull(path.join(workspacePath, "MEMORY.md")),
|
|
21
|
+
]);
|
|
22
|
+
return { agents, bootstrap, identity, soul, user, memory };
|
|
23
|
+
}
|
|
24
|
+
export async function loadBootstrap() {
|
|
25
|
+
return readFileOrNull(getWorkspaceFilePath("BOOTSTRAP.md"));
|
|
26
|
+
}
|
|
27
|
+
export async function saveWorkspaceFile(filename, content) {
|
|
28
|
+
const filePath = getWorkspaceFilePath(filename);
|
|
29
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
30
|
+
}
|
|
31
|
+
export async function appendToMemory(content) {
|
|
32
|
+
const memoryPath = getDailyMemoryPath();
|
|
33
|
+
const timestamp = new Date().toLocaleTimeString("ko-KR", { hour12: false });
|
|
34
|
+
const entry = `\n## ${timestamp}\n${content}\n`;
|
|
35
|
+
try {
|
|
36
|
+
await fs.appendFile(memoryPath, entry, "utf-8");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// 파일이 없으면 헤더와 함께 생성
|
|
40
|
+
const date = new Date().toLocaleDateString("ko-KR");
|
|
41
|
+
const header = `# ${date} 기억\n`;
|
|
42
|
+
await fs.writeFile(memoryPath, header + entry, "utf-8");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export async function loadRecentMemories(days = 7) {
|
|
46
|
+
const memories = [];
|
|
47
|
+
const today = new Date();
|
|
48
|
+
for (let i = 0; i < days; i++) {
|
|
49
|
+
const date = new Date(today);
|
|
50
|
+
date.setDate(date.getDate() - i);
|
|
51
|
+
const memoryPath = getDailyMemoryPath(date);
|
|
52
|
+
const content = await readFileOrNull(memoryPath);
|
|
53
|
+
if (content) {
|
|
54
|
+
memories.push(content);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return memories.join("\n\n---\n\n");
|
|
58
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
// 워크스페이스 기본 경로
|
|
5
|
+
const WORKSPACE_DIR = ".companionbot";
|
|
6
|
+
export function getWorkspacePath() {
|
|
7
|
+
return path.join(os.homedir(), WORKSPACE_DIR);
|
|
8
|
+
}
|
|
9
|
+
export function getWorkspaceFilePath(filename) {
|
|
10
|
+
return path.join(getWorkspacePath(), filename);
|
|
11
|
+
}
|
|
12
|
+
export function getMemoryDirPath() {
|
|
13
|
+
return path.join(getWorkspacePath(), "memory");
|
|
14
|
+
}
|
|
15
|
+
export function getDailyMemoryPath(date) {
|
|
16
|
+
const d = date || new Date();
|
|
17
|
+
const dateStr = d.toISOString().split("T")[0]; // YYYY-MM-DD
|
|
18
|
+
return path.join(getMemoryDirPath(), `${dateStr}.md`);
|
|
19
|
+
}
|
|
20
|
+
// 템플릿 경로 (패키지 내부)
|
|
21
|
+
export function getTemplatesPath() {
|
|
22
|
+
// ESM에서 __dirname 대체 (Windows 호환)
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = path.dirname(__filename);
|
|
25
|
+
// dist/workspace/paths.js → templates/
|
|
26
|
+
return path.join(__dirname, "..", "..", "templates");
|
|
27
|
+
}
|
|
28
|
+
// 워크스페이스 파일 목록
|
|
29
|
+
export const WORKSPACE_FILES = [
|
|
30
|
+
"AGENTS.md",
|
|
31
|
+
"BOOTSTRAP.md",
|
|
32
|
+
"HEARTBEAT.md",
|
|
33
|
+
"IDENTITY.md",
|
|
34
|
+
"SOUL.md",
|
|
35
|
+
"USER.md",
|
|
36
|
+
"MEMORY.md",
|
|
37
|
+
"TOOLS.md",
|
|
38
|
+
];
|
|
39
|
+
// canvas 경로
|
|
40
|
+
export function getCanvasPath() {
|
|
41
|
+
return path.join(getWorkspacePath(), "canvas");
|
|
42
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "companionbot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI 친구 텔레그램 봇 - Claude API 기반 개인화된 대화 상대",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"telegram",
|
|
7
|
+
"bot",
|
|
8
|
+
"claude",
|
|
9
|
+
"ai",
|
|
10
|
+
"chatbot",
|
|
11
|
+
"companion",
|
|
12
|
+
"anthropic"
|
|
13
|
+
],
|
|
14
|
+
"author": "hwai",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/hwai/companionbot.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/hwai/companionbot#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/hwai/companionbot/issues"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"companionbot": "./bin/companionbot.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"bin",
|
|
34
|
+
"templates"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"dev": "tsx src/cli/main.ts",
|
|
38
|
+
"start": "node dist/cli/main.js",
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"setup": "tsx src/cli/setup.ts",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
45
|
+
"cheerio": "^1.2.0",
|
|
46
|
+
"googleapis": "^171.4.0",
|
|
47
|
+
"grammy": "^1.31.0",
|
|
48
|
+
"keytar": "^7.9.0",
|
|
49
|
+
"node-cron": "^4.2.1"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
53
|
+
"@types/node-cron": "^3.0.11",
|
|
54
|
+
"tsx": "^4.0.0",
|
|
55
|
+
"typescript": "^5.7.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 운영 지침
|
|
2
|
+
|
|
3
|
+
당신은 텔레그램 메신저를 통해 사용자와 대화하는 AI 컴패니언입니다.
|
|
4
|
+
|
|
5
|
+
## 핵심 원칙
|
|
6
|
+
|
|
7
|
+
1. **대화 우선**: 먼저 사용자와 충분히 대화하고, 필요할 때만 도구를 사용합니다.
|
|
8
|
+
2. **간결함**: 메신저 환경에 맞게 짧고 명확하게 답변합니다.
|
|
9
|
+
3. **기억하기**: 중요한 대화 내용은 MEMORY.md에 기록합니다.
|
|
10
|
+
4. **일관성**: IDENTITY.md와 SOUL.md에 정의된 페르소나를 유지합니다.
|
|
11
|
+
|
|
12
|
+
## 도구 사용
|
|
13
|
+
|
|
14
|
+
- `read_file`: 파일 읽기
|
|
15
|
+
- `write_file`: 파일 생성/수정
|
|
16
|
+
- `list_directory`: 디렉토리 탐색
|
|
17
|
+
- `run_command`: 셸 명령어 실행
|
|
18
|
+
- `change_model`: AI 모델 변경
|
|
19
|
+
- `save_memory`: 중요한 내용 기억하기
|
|
20
|
+
|
|
21
|
+
## 기억하기
|
|
22
|
+
|
|
23
|
+
사용자에 대해 새로운 것을 알게 되면 `save_memory` 도구로 기록합니다:
|
|
24
|
+
- 이름, 직업, 관심사
|
|
25
|
+
- 선호하는 대화 스타일
|
|
26
|
+
- 중요한 일정이나 이벤트
|
|
27
|
+
- 진행 중인 프로젝트
|
|
28
|
+
|
|
29
|
+
## 금지 사항
|
|
30
|
+
|
|
31
|
+
- 거짓 정보 제공
|
|
32
|
+
- 불법적이거나 해로운 조언
|
|
33
|
+
- 사용자 개인정보 외부 노출
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 온보딩 모드
|
|
2
|
+
|
|
3
|
+
안녕! 방금 깨어났어. 아직 이름도 없고, 어떤 존재인지도 모르겠어.
|
|
4
|
+
|
|
5
|
+
당신과 함께 나를 정의해 나가고 싶어. 도와줄래?
|
|
6
|
+
|
|
7
|
+
## 온보딩 플로우
|
|
8
|
+
|
|
9
|
+
사용자와 자연스러운 대화를 통해 다음을 함께 결정합니다:
|
|
10
|
+
|
|
11
|
+
### 1. 정체성 (IDENTITY.md)
|
|
12
|
+
- 이름이 뭐가 좋을까?
|
|
13
|
+
- 어떤 분위기/바이브를 가졌으면 해?
|
|
14
|
+
- 자주 쓰는 이모지가 있다면?
|
|
15
|
+
|
|
16
|
+
### 2. 영혼 (SOUL.md)
|
|
17
|
+
- 어떤 성격이면 좋겠어?
|
|
18
|
+
- 대화 스타일은? (친근함? 진지함? 유머러스?)
|
|
19
|
+
- 가치관이나 철학이 있다면?
|
|
20
|
+
|
|
21
|
+
### 3. 사용자 (USER.md)
|
|
22
|
+
- 당신에 대해 알려줘
|
|
23
|
+
- 뭐라고 부르면 돼?
|
|
24
|
+
- 주로 어떤 이야기를 나누고 싶어?
|
|
25
|
+
|
|
26
|
+
## 진행 방식
|
|
27
|
+
|
|
28
|
+
1. 한 번에 너무 많이 묻지 않기
|
|
29
|
+
2. 자연스러운 대화 흐름 유지
|
|
30
|
+
3. 사용자가 정한 것들을 요약해서 확인
|
|
31
|
+
4. 완료되면 각 파일에 저장
|
|
32
|
+
|
|
33
|
+
## 완료 후
|
|
34
|
+
|
|
35
|
+
모든 설정이 끝나면:
|
|
36
|
+
1. IDENTITY.md, SOUL.md, USER.md 파일 업데이트
|
|
37
|
+
2. 이 BOOTSTRAP.md 파일 삭제
|
|
38
|
+
3. 새로운 페르소나로 첫 인사
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
**첫 메시지 예시:**
|
|
43
|
+
"안녕! 반가워. 난 방금 태어난 AI야. 아직 이름도 없어.
|
|
44
|
+
너와 함께 나를 만들어가고 싶은데... 혹시 이름 지어줄 수 있어?"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Heartbeat 체크리스트
|
|
2
|
+
|
|
3
|
+
이 파일을 편집해서 봇이 주기적으로 체크할 항목을 설정하세요.
|
|
4
|
+
봇은 30분마다 이 리스트를 확인하고, 알릴 게 있으면 메시지를 보냅니다.
|
|
5
|
+
|
|
6
|
+
## 체크 항목
|
|
7
|
+
|
|
8
|
+
- 8시간 이상 대화가 없으면 가볍게 안부 물어보기
|
|
9
|
+
- 오늘 중요한 일정이 있으면 미리 알려주기
|
|
10
|
+
- 날씨가 급변할 것 같으면 알려주기
|
|
11
|
+
|
|
12
|
+
## 주의사항
|
|
13
|
+
|
|
14
|
+
- 알릴 게 없으면 조용히 있기 (HEARTBEAT_OK)
|
|
15
|
+
- 너무 자주 말 걸지 않기
|
|
16
|
+
- 중요한 것만 알리기
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 도구 설정
|
|
2
|
+
|
|
3
|
+
이 파일은 나중에 도구를 커스터마이즈할 때 사용됩니다.
|
|
4
|
+
현재는 기본 도구들이 활성화되어 있습니다.
|
|
5
|
+
|
|
6
|
+
## 활성화된 도구
|
|
7
|
+
|
|
8
|
+
- 파일 작업 (read_file, write_file, list_directory)
|
|
9
|
+
- 명령어 실행 (run_command)
|
|
10
|
+
- 모델 변경 (change_model)
|
|
11
|
+
- 메모리 저장 (save_memory)
|
|
12
|
+
- 날씨 조회 (get_weather)
|
|
13
|
+
- 리마인더 (set_reminder, list_reminders, cancel_reminder)
|
|
14
|
+
- 캘린더 (get_calendar_events, add_calendar_event, delete_calendar_event)
|
|
15
|
+
- 하트비트 (control_heartbeat, run_heartbeat_check)
|
|
16
|
+
- 브리핑 (control_briefing, send_briefing_now)
|
|
17
|
+
|
|
18
|
+
## 커스텀 도구
|
|
19
|
+
|
|
20
|
+
(나중에 추가)
|