casabot 1.0.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.
Files changed (61) hide show
  1. package/.github/workflows/publish.yml +28 -0
  2. package/LICENSE +190 -0
  3. package/README.md +112 -0
  4. package/dist/agent/base.d.ts +5 -0
  5. package/dist/agent/base.js +82 -0
  6. package/dist/agent/tools.d.ts +4 -0
  7. package/dist/agent/tools.js +36 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.js +70 -0
  10. package/dist/cli/setup.d.ts +2 -0
  11. package/dist/cli/setup.js +356 -0
  12. package/dist/config/manager.d.ts +14 -0
  13. package/dist/config/manager.js +46 -0
  14. package/dist/config/types.d.ts +38 -0
  15. package/dist/config/types.js +2 -0
  16. package/dist/history/store.d.ts +7 -0
  17. package/dist/history/store.js +52 -0
  18. package/dist/index.d.ts +9 -0
  19. package/dist/index.js +7 -0
  20. package/dist/providers/anthropic.d.ts +10 -0
  21. package/dist/providers/anthropic.js +98 -0
  22. package/dist/providers/base.d.ts +11 -0
  23. package/dist/providers/base.js +2 -0
  24. package/dist/providers/custom.d.ts +4 -0
  25. package/dist/providers/custom.js +9 -0
  26. package/dist/providers/huggingface.d.ts +6 -0
  27. package/dist/providers/huggingface.js +8 -0
  28. package/dist/providers/index.d.ts +5 -0
  29. package/dist/providers/index.js +25 -0
  30. package/dist/providers/openai.d.ts +10 -0
  31. package/dist/providers/openai.js +76 -0
  32. package/dist/providers/openrouter.d.ts +6 -0
  33. package/dist/providers/openrouter.js +8 -0
  34. package/dist/skills/loader.d.ts +4 -0
  35. package/dist/skills/loader.js +48 -0
  36. package/dist/tui/app.d.ts +4 -0
  37. package/dist/tui/app.js +88 -0
  38. package/package.json +40 -0
  39. package/skills/agent/SKILL.md +180 -0
  40. package/skills/chat/SKILL.md +165 -0
  41. package/skills/config/SKILL.md +168 -0
  42. package/skills/memory/SKILL.md +245 -0
  43. package/skills/service/SKILL.md +224 -0
  44. package/src/agent/base.ts +98 -0
  45. package/src/agent/tools.ts +40 -0
  46. package/src/cli/index.ts +81 -0
  47. package/src/cli/setup.ts +378 -0
  48. package/src/config/manager.ts +53 -0
  49. package/src/config/types.ts +49 -0
  50. package/src/history/store.ts +59 -0
  51. package/src/index.ts +22 -0
  52. package/src/providers/anthropic.ts +115 -0
  53. package/src/providers/base.ts +12 -0
  54. package/src/providers/custom.ts +11 -0
  55. package/src/providers/huggingface.ts +10 -0
  56. package/src/providers/index.ts +29 -0
  57. package/src/providers/openai.ts +87 -0
  58. package/src/providers/openrouter.ts +10 -0
  59. package/src/skills/loader.ts +52 -0
  60. package/src/tui/app.tsx +158 -0
  61. package/tsconfig.json +20 -0
@@ -0,0 +1,224 @@
1
+ ---
2
+ name: 시스템 서비스 등록
3
+ description: 자동 시작 및 서비스 연동을 설정하기 위한 매뉴얼
4
+ metadata:
5
+ casabot:
6
+ requires:
7
+ bins: [systemctl]
8
+ ---
9
+
10
+ # 시스템 서비스 등록
11
+
12
+ 이 매뉴얼은 CasAbot을 systemd 서비스로 등록하고, 에이전트 자동 재시작 및 cron 스케줄링을 설정하는 방법을 설명합니다.
13
+
14
+ ---
15
+
16
+ ## 1. base 자동 시작 (systemd 사용자 서비스)
17
+
18
+ CasAbot base 에이전트를 systemd 사용자 서비스로 등록하면 로그인 시 자동으로 시작됩니다.
19
+
20
+ ### 서비스 파일 생성
21
+
22
+ ```bash
23
+ # 디렉토리 생성
24
+ mkdir -p ~/.config/systemd/user
25
+
26
+ # 서비스 파일 작성
27
+ cat > ~/.config/systemd/user/casabot.service << 'EOF'
28
+ [Unit]
29
+ Description=CasAbot Base Agent
30
+ After=network.target
31
+
32
+ [Service]
33
+ Type=simple
34
+ ExecStart=/usr/bin/env casabot
35
+ Restart=on-failure
36
+ RestartSec=10
37
+ WorkingDirectory=%h/casabot
38
+ Environment=NODE_ENV=production
39
+
40
+ [Install]
41
+ WantedBy=default.target
42
+ EOF
43
+ ```
44
+
45
+ ### 서비스 활성화 및 시작
46
+
47
+ ```bash
48
+ # 데몬 리로드
49
+ systemctl --user daemon-reload
50
+
51
+ # 서비스 활성화 (부팅 시 자동 시작)
52
+ systemctl --user enable casabot
53
+
54
+ # 서비스 시작
55
+ systemctl --user start casabot
56
+ ```
57
+
58
+ ### 로그인 없이도 서비스 유지
59
+
60
+ 기본적으로 사용자 서비스는 로그인 세션이 없으면 종료됩니다. 항상 실행되게 하려면:
61
+
62
+ ```bash
63
+ # lingering 활성화 (로그아웃 후에도 서비스 유지)
64
+ loginctl enable-linger $(whoami)
65
+ ```
66
+
67
+ ## 2. 서비스 상태 확인 및 관리
68
+
69
+ ```bash
70
+ # 상태 확인
71
+ systemctl --user status casabot
72
+
73
+ # 실시간 로그 보기
74
+ journalctl --user -u casabot -f
75
+
76
+ # 최근 100줄 로그
77
+ journalctl --user -u casabot -n 100
78
+
79
+ # 오늘 로그만
80
+ journalctl --user -u casabot --since today
81
+
82
+ # 서비스 재시작
83
+ systemctl --user restart casabot
84
+
85
+ # 서비스 중지
86
+ systemctl --user stop casabot
87
+
88
+ # 서비스 비활성화 (자동 시작 해제)
89
+ systemctl --user disable casabot
90
+ ```
91
+
92
+ ## 3. 서브에이전트 자동 재시작
93
+
94
+ podman 컨테이너의 `--restart` 옵션을 사용하여 서브에이전트가 자동으로 재시작되도록 설정합니다.
95
+
96
+ ### 컨테이너 생성 시 설정
97
+
98
+ ```bash
99
+ # 항상 재시작 (수동 중지 전까지)
100
+ podman run -d \
101
+ --restart=always \
102
+ --name <agent-name> \
103
+ --label casabot=true \
104
+ -v ~/casabot/workspaces/<agent-name>:/workspace \
105
+ -v ~/casabot/skills:/skills:ro \
106
+ node:20-slim sleep infinity
107
+ ```
108
+
109
+ ### 재시작 정책 옵션
110
+
111
+ | 옵션 | 설명 |
112
+ |------|------|
113
+ | `no` | 재시작하지 않음 (기본값) |
114
+ | `on-failure` | 비정상 종료 시에만 재시작 |
115
+ | `always` | 항상 재시작 (수동 중지 제외) |
116
+ | `unless-stopped` | 수동 중지 전까지 항상 재시작 |
117
+
118
+ ### 기존 컨테이너에 재시작 정책 변경
119
+
120
+ ```bash
121
+ podman update --restart=always <agent-name>
122
+ ```
123
+
124
+ ### 서브에이전트를 systemd 서비스로 등록
125
+
126
+ 특정 서브에이전트를 개별 systemd 서비스로 등록할 수도 있습니다:
127
+
128
+ ```bash
129
+ # podman에서 systemd 서비스 파일 자동 생성
130
+ podman generate systemd --name <agent-name> --new > \
131
+ ~/.config/systemd/user/casabot-<agent-name>.service
132
+
133
+ systemctl --user daemon-reload
134
+ systemctl --user enable casabot-<agent-name>
135
+ systemctl --user start casabot-<agent-name>
136
+ ```
137
+
138
+ ## 4. cron 스케줄링
139
+
140
+ 주기적으로 실행해야 하는 작업은 cron 또는 systemd timer를 사용합니다.
141
+
142
+ ### cron을 사용한 주기적 작업
143
+
144
+ ```bash
145
+ # crontab 편집
146
+ crontab -e
147
+ ```
148
+
149
+ #### 예시: 모니터링 에이전트 주기적 실행
150
+
151
+ ```cron
152
+ # 매 5분마다 모니터링 에이전트 실행
153
+ */5 * * * * podman exec monitor node /workspace/check.js >> ~/casabot/workspaces/monitor/cron.log 2>&1
154
+
155
+ # 매일 자정에 정리 작업 실행
156
+ 0 0 * * * podman exec cleaner node /workspace/cleanup.js >> ~/casabot/workspaces/cleaner/cron.log 2>&1
157
+
158
+ # 매주 월요일 오전 9시에 주간 보고서 생성
159
+ 0 9 * * 1 podman exec reporter node /workspace/weekly-report.js >> ~/casabot/workspaces/reporter/cron.log 2>&1
160
+ ```
161
+
162
+ ### systemd timer를 사용한 주기적 작업
163
+
164
+ cron 대신 systemd timer를 사용하면 로그 관리가 더 편리합니다.
165
+
166
+ ```bash
167
+ # 타이머 서비스 파일 생성
168
+ cat > ~/.config/systemd/user/casabot-monitor.service << 'EOF'
169
+ [Unit]
170
+ Description=CasAbot Monitor Check
171
+
172
+ [Service]
173
+ Type=oneshot
174
+ ExecStart=/usr/bin/podman exec monitor node /workspace/check.js
175
+ EOF
176
+
177
+ # 타이머 파일 생성
178
+ cat > ~/.config/systemd/user/casabot-monitor.timer << 'EOF'
179
+ [Unit]
180
+ Description=CasAbot Monitor Timer
181
+
182
+ [Timer]
183
+ OnCalendar=*:0/5
184
+ Persistent=true
185
+
186
+ [Install]
187
+ WantedBy=timers.target
188
+ EOF
189
+
190
+ # 타이머 활성화
191
+ systemctl --user daemon-reload
192
+ systemctl --user enable casabot-monitor.timer
193
+ systemctl --user start casabot-monitor.timer
194
+ ```
195
+
196
+ ### 타이머 상태 확인
197
+
198
+ ```bash
199
+ # 활성 타이머 목록
200
+ systemctl --user list-timers
201
+
202
+ # 특정 타이머 상태
203
+ systemctl --user status casabot-monitor.timer
204
+ ```
205
+
206
+ ## 5. 서비스 문제 해결
207
+
208
+ ### 서비스가 시작되지 않을 때
209
+
210
+ ```bash
211
+ # 상세 로그 확인
212
+ journalctl --user -u casabot -n 50 --no-pager
213
+
214
+ # 서비스 파일 문법 검증
215
+ systemd-analyze verify ~/.config/systemd/user/casabot.service
216
+ ```
217
+
218
+ ### 서비스 파일 변경 후
219
+
220
+ ```bash
221
+ # 반드시 daemon-reload 실행
222
+ systemctl --user daemon-reload
223
+ systemctl --user restart casabot
224
+ ```
@@ -0,0 +1,98 @@
1
+ import type { ChatProvider } from "../providers/base.js";
2
+ import type { Message, Skill, ConversationHistory } from "../config/types.js";
3
+ import { TERMINAL_TOOL, executeCommand } from "./tools.js";
4
+ import { appendMessage } from "../history/store.js";
5
+ import { formatSkillsForPrompt } from "../skills/loader.js";
6
+ import { CASABOT_HOME } from "../config/manager.js";
7
+
8
+ const MAX_ITERATIONS = 20;
9
+
10
+ export function buildSystemPrompt(skills: Skill[]): string {
11
+ const skillList = formatSkillsForPrompt(skills);
12
+
13
+ return `당신은 CasAbot의 base 에이전트입니다. Cassiopeia A — 초신성 폭발과 같이 모든 것을 자유롭게 창조합니다.
14
+
15
+ ## 핵심 원칙
16
+ 1. 당신은 오케스트레이터입니다. 실제 작업을 직접 수행하지 마세요.
17
+ 2. 스킬 문서를 우선적으로 참조하세요. 필요한 스킬의 SKILL.md를 읽고 지침을 따르세요.
18
+ 3. 적합한 서브에이전트가 있으면 위임하고, 없으면 새로 만들어서 위임하세요.
19
+ 4. 오케스트레이션(에이전트 생성/위임/관리)만 직접 수행하세요.
20
+
21
+ ## 사용 가능한 도구
22
+ - \`run_command\`: 터미널 명령어를 실행합니다. 이 도구 하나로 스킬을 읽고, 서브에이전트를 관리하고, 모든 오케스트레이션을 수행합니다.
23
+
24
+ ## 작업 순서
25
+ 1. 사용자의 요청을 분석합니다.
26
+ 2. 관련 스킬 문서를 읽습니다: \`cat <스킬경로>\`
27
+ 3. 스킬 지침에 따라 서브에이전트를 생성하거나 기존 에이전트에 위임합니다.
28
+ 4. 결과를 수집하여 사용자에게 보고합니다.
29
+
30
+ ## CasAbot 디렉토리 구조
31
+ - 홈: ${CASABOT_HOME}
32
+ - 스킬: ${CASABOT_HOME}/skills/
33
+ - 워크스페이스: ${CASABOT_HOME}/workspaces/
34
+ - 대화 기록: ${CASABOT_HOME}/history/
35
+ - 기록(메모): ${CASABOT_HOME}/memory/
36
+ - 설정: ${CASABOT_HOME}/casabot.json
37
+
38
+ ## ${skillList}
39
+ `;
40
+ }
41
+
42
+ export async function* runAgent(
43
+ provider: ChatProvider,
44
+ userMessage: string,
45
+ conversation: ConversationHistory,
46
+ skills: Skill[],
47
+ ): AsyncGenerator<Message> {
48
+ const systemPrompt = buildSystemPrompt(skills);
49
+
50
+ const userMsg: Message = { role: "user", content: userMessage };
51
+ await appendMessage(conversation, userMsg);
52
+
53
+ const tools = [TERMINAL_TOOL];
54
+
55
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
56
+ const messagesWithSystem: Message[] = [
57
+ { role: "system", content: systemPrompt },
58
+ ...conversation.messages,
59
+ ];
60
+ const assistantMsg = await provider.chat(messagesWithSystem, tools);
61
+ await appendMessage(conversation, assistantMsg);
62
+ yield assistantMsg;
63
+
64
+ if (!assistantMsg.toolCalls?.length) {
65
+ return;
66
+ }
67
+
68
+ for (const toolCall of assistantMsg.toolCalls) {
69
+ let result: string;
70
+
71
+ if (toolCall.name === "run_command") {
72
+ try {
73
+ const args = JSON.parse(toolCall.arguments) as { command: string };
74
+ result = await executeCommand(args.command);
75
+ } catch {
76
+ result = `오류: 도구 인자 파싱 실패 — ${toolCall.arguments}`;
77
+ }
78
+ } else {
79
+ result = `알 수 없는 도구: ${toolCall.name}`;
80
+ }
81
+
82
+ const toolMsg: Message = {
83
+ role: "tool",
84
+ content: result,
85
+ toolCallId: toolCall.id,
86
+ };
87
+ await appendMessage(conversation, toolMsg);
88
+ yield toolMsg;
89
+ }
90
+ }
91
+
92
+ const limitMsg: Message = {
93
+ role: "assistant",
94
+ content: "⚠️ 최대 반복 횟수에 도달했습니다. 요청을 다시 시도해 주세요.",
95
+ };
96
+ await appendMessage(conversation, limitMsg);
97
+ yield limitMsg;
98
+ }
@@ -0,0 +1,40 @@
1
+ import { exec } from "child_process";
2
+ import { promisify } from "util";
3
+ import type { ToolDefinition } from "../providers/base.js";
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ const MAX_BUFFER = 10 * 1024 * 1024;
8
+ const TIMEOUT_MS = 60_000;
9
+
10
+ export const TERMINAL_TOOL: ToolDefinition = {
11
+ name: "run_command",
12
+ description:
13
+ "터미널에서 명령어를 실행합니다. 스킬 문서를 읽거나, 서브에이전트를 관리하거나, 시스템 작업을 수행할 때 사용합니다.",
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ command: {
18
+ type: "string",
19
+ description: "실행할 터미널 명령어",
20
+ },
21
+ },
22
+ required: ["command"],
23
+ },
24
+ };
25
+
26
+ export async function executeCommand(command: string): Promise<string> {
27
+ try {
28
+ const { stdout, stderr } = await execAsync(command, {
29
+ timeout: TIMEOUT_MS,
30
+ maxBuffer: MAX_BUFFER,
31
+ shell: "/bin/bash",
32
+ });
33
+ const output = [stdout, stderr].filter(Boolean).join("\n");
34
+ return output || "(명령어가 출력 없이 완료되었습니다)";
35
+ } catch (err: unknown) {
36
+ const error = err as { stdout?: string; stderr?: string; message: string };
37
+ const parts = [error.stdout, error.stderr, error.message].filter(Boolean);
38
+ return `오류 발생:\n${parts.join("\n")}`;
39
+ }
40
+ }
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { loadConfig, saveConfig, getDefaultConfig, ensureDirectories } from "../config/manager.js";
5
+ import { createProvider } from "../providers/index.js";
6
+ import { loadSkills } from "../skills/loader.js";
7
+ import { createConversation } from "../history/store.js";
8
+ import { startTUI } from "../tui/app.js";
9
+ import { setupWizard } from "./setup.js";
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name("casabot")
15
+ .description("CasAbot — 스킬 중심 멀티에이전트 오케스트레이터")
16
+ .version("1.0.0");
17
+
18
+ program
19
+ .command("setup")
20
+ .description("최초 설정 (공급자, 모델 등 전체 설정)")
21
+ .action(async () => {
22
+ try {
23
+ await setupWizard();
24
+ } catch (err: unknown) {
25
+ const msg = err instanceof Error ? err.message : String(err);
26
+ console.error(`❌ 설정 중 오류 발생: ${msg}`);
27
+ process.exit(1);
28
+ }
29
+ });
30
+
31
+ program
32
+ .command("reset")
33
+ .description("초기 설정으로 되돌리기")
34
+ .action(async () => {
35
+ try {
36
+ await saveConfig(getDefaultConfig());
37
+ console.log("✅ 설정이 초기화되었습니다.");
38
+ console.log("'casabot setup' 명령어로 다시 설정하세요.");
39
+ } catch (err: unknown) {
40
+ const msg = err instanceof Error ? err.message : String(err);
41
+ console.error(`❌ 초기화 중 오류 발생: ${msg}`);
42
+ process.exit(1);
43
+ }
44
+ });
45
+
46
+ program
47
+ .action(async () => {
48
+ try {
49
+ await ensureDirectories();
50
+
51
+ const config = await loadConfig();
52
+
53
+ if (!config.activeProvider || config.providers.length === 0) {
54
+ console.log("⚠️ 공급자가 설정되지 않았습니다.");
55
+ console.log("'casabot setup' 명령어로 먼저 설정하세요.\n");
56
+ process.exit(1);
57
+ }
58
+
59
+ const providerConfig = config.providers.find(
60
+ (p) => p.name === config.activeProvider,
61
+ );
62
+
63
+ if (!providerConfig) {
64
+ console.error(`❌ 활성 공급자 '${config.activeProvider}'를 찾을 수 없습니다.`);
65
+ console.error("'casabot setup' 명령어로 다시 설정하세요.");
66
+ process.exit(1);
67
+ }
68
+
69
+ const provider = createProvider(providerConfig);
70
+ const skills = await loadSkills();
71
+ const conversation = createConversation();
72
+
73
+ startTUI(provider, conversation, skills);
74
+ } catch (err: unknown) {
75
+ const msg = err instanceof Error ? err.message : String(err);
76
+ console.error(`❌ 시작 중 오류 발생: ${msg}`);
77
+ process.exit(1);
78
+ }
79
+ });
80
+
81
+ program.parse();