claude-octopus 1.1.2 → 1.1.4

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.
@@ -52,6 +52,24 @@ code { font-family: var(--mono); background: var(--surface); padding: 0.15em 0.4
52
52
 
53
53
  .toast { position: fixed; bottom: 1.5rem; right: 1.5rem; background: var(--surface); border: 1px solid var(--accent); border-radius: 8px; padding: 0.75rem 1rem; font-size: 0.85rem; color: var(--accent); opacity: 0; transition: opacity 0.3s; pointer-events: none; z-index: 100; }
54
54
  .toast.show { opacity: 1; }
55
+
56
+ code { overflow-wrap: break-word; word-break: break-all; }
57
+ .entry-meta { flex-wrap: wrap; }
58
+ .entry-prompt { overflow-wrap: break-word; word-break: break-word; }
59
+ .run-table tbody tr:active { background: var(--surface); }
60
+
61
+ @media (max-width: 600px) {
62
+ body { padding: 1rem 0.75rem; }
63
+ .connection { float: none; display: flex; margin-top: 0.5rem; }
64
+ .stats { gap: 0.75rem; }
65
+ .stat-card { min-width: 0; flex: 1 1 calc(50% - 0.75rem); padding: 0.75rem; }
66
+ .run-table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
67
+ .entry-badge, .entry-seq { font-size: 0.75rem; }
68
+ .toast { left: 0.75rem; right: 0.75rem; bottom: 0.75rem; text-align: center; }
69
+ }
70
+ @media (max-width: 360px) {
71
+ .stat-card { flex: 1 1 100%; }
72
+ }
55
73
  `;
56
74
  // ── Client-side JS ───────────────────────────────────────────────
57
75
  const DASHBOARD_JS = `
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * CSS for the HTML report — extracted from report.ts.
3
3
  */
4
- export declare const REPORT_CSS = "\n:root {\n --bg: #0d1117; --surface: #161b22; --border: #30363d;\n --text: #e6edf3; --text-dim: #8b949e; --text-bright: #f0f6fc;\n --accent: #58a6ff; --green: #3fb950; --red: #f85149; --orange: #d29922;\n --font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n}\n* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { font-family: var(--font); background: var(--bg); color: var(--text); padding: 2rem; max-width: 1200px; margin: 0 auto; line-height: 1.5; }\nh1 { color: var(--text-bright); margin-bottom: 0.5rem; }\nh2 { color: var(--text-bright); font-size: 1.3rem; }\nh3 { color: var(--text); font-size: 1rem; margin: 1rem 0 0.5rem; }\ncode { font-family: var(--mono); background: var(--surface); padding: 0.15em 0.4em; border-radius: 4px; font-size: 0.9em; }\npre { font-family: var(--mono); font-size: 0.8em; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }\na { color: var(--accent); text-decoration: none; }\n.subtitle { color: var(--text-dim); margin-bottom: 2rem; }\n\n/* Run list table */\n.run-table { width: 100%; border-collapse: collapse; margin: 1rem 0; }\n.run-table th, .run-table td { padding: 0.5rem 0.75rem; text-align: left; border-bottom: 1px solid var(--border); font-size: 0.85rem; }\n.run-table th { color: var(--text-dim); font-weight: 600; }\n.run-table tbody tr:hover { background: var(--surface); }\n\n/* Run detail */\n.run { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }\n.run-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }\n.run-badge { font-size: 0.75rem; padding: 0.2em 0.6em; border-radius: 12px; font-weight: 600; }\n.run-badge-ok { background: rgba(63,185,80,0.15); color: var(--green); }\n.run-badge-error { background: rgba(248,81,73,0.15); color: var(--red); }\n\n.run-stats { display: flex; gap: 2rem; margin-bottom: 1.5rem; flex-wrap: wrap; }\n.stat { text-align: center; }\n.stat-val { font-size: 1.3rem; font-weight: 700; color: var(--text-bright); }\n.stat-label { font-size: 0.75rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }\n\n/* Timeline bar */\n.timeline-bar { display: flex; align-items: center; gap: 0; margin-bottom: 1rem; padding: 0.5rem 0; }\n.timeline-dot { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 700; color: var(--bg); flex-shrink: 0; }\n.timeline-dot + .timeline-dot { margin-left: -1px; }\n.timeline-dot::before { content: \"\"; position: absolute; }\n.dot-ok { background: var(--green); }\n.dot-error { background: var(--red); }\n.timeline-dot + .timeline-dot { margin-left: 4px; }\n\n/* Agent cards */\n.agent-card { border: 1px solid var(--border); border-radius: 6px; padding: 1rem; margin-bottom: 0.75rem; background: var(--bg); }\n.agent-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }\n.agent-seq { width: 24px; height: 24px; border-radius: 50%; background: var(--accent); color: var(--bg); display: flex; align-items: center; justify-content: center; font-size: 0.7rem; font-weight: 700; flex-shrink: 0; }\n.agent-name { font-weight: 700; font-size: 1rem; color: var(--text-bright); }\n.agent-badge { font-size: 0.7rem; padding: 0.15em 0.5em; border-radius: 10px; font-weight: 600; }\n.status-ok { background: rgba(63,185,80,0.15); color: var(--green); }\n.status-error { background: rgba(248,81,73,0.15); color: var(--red); }\n.agent-meta { display: flex; gap: 1rem; font-size: 0.8rem; color: var(--text-dim); margin-bottom: 0.5rem; flex-wrap: wrap; }\n.session-id { font-family: var(--mono); font-size: 0.75rem; }\n.agent-prompt { font-size: 0.85rem; color: var(--text-dim); font-style: italic; margin-bottom: 0.75rem; }\n\n/* Transcript */\n.transcript { margin-top: 0.5rem; }\n.transcript > summary { cursor: pointer; font-size: 0.85rem; color: var(--accent); font-weight: 600; padding: 0.3rem 0; }\n.transcript-body { margin-top: 0.5rem; max-height: 600px; overflow-y: auto; border: 1px solid var(--border); border-radius: 4px; padding: 0.5rem; }\n.msg { padding: 0.5rem; border-bottom: 1px solid var(--border); }\n.msg:last-child { border-bottom: none; }\n.msg-role { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.25rem; }\n.msg-user .msg-role { color: var(--accent); }\n.msg-assistant .msg-role { color: var(--green); }\n.msg-system .msg-role { color: var(--orange); }\n.msg-content { font-size: 0.8rem; }\n.text-block { margin-bottom: 0.5rem; white-space: pre-wrap; }\n.tool-call { background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.2); border-radius: 4px; padding: 0.5rem; margin: 0.25rem 0; }\n.tool-name { font-family: var(--mono); font-weight: 700; color: var(--accent); font-size: 0.8rem; }\n.tool-input { margin-top: 0.25rem; color: var(--text-dim); font-size: 0.75rem; max-height: 200px; overflow-y: auto; }\n.tool-result { background: rgba(63,185,80,0.05); border: 1px solid rgba(63,185,80,0.15); border-radius: 4px; padding: 0.5rem; margin: 0.25rem 0; }\n.tool-result pre { color: var(--text-dim); font-size: 0.75rem; max-height: 200px; overflow-y: auto; }\n.no-transcript { font-size: 0.8rem; color: var(--text-dim); font-style: italic; }\n.empty { color: var(--text-dim); font-style: italic; }\n";
4
+ export declare const REPORT_CSS = "\n:root {\n --bg: #0d1117; --surface: #161b22; --border: #30363d;\n --text: #e6edf3; --text-dim: #8b949e; --text-bright: #f0f6fc;\n --accent: #58a6ff; --green: #3fb950; --red: #f85149; --orange: #d29922;\n --font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif;\n --mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n}\n* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { font-family: var(--font); background: var(--bg); color: var(--text); padding: 2rem; max-width: 1200px; margin: 0 auto; line-height: 1.5; }\nh1 { color: var(--text-bright); margin-bottom: 0.5rem; }\nh2 { color: var(--text-bright); font-size: 1.3rem; }\nh3 { color: var(--text); font-size: 1rem; margin: 1rem 0 0.5rem; }\ncode { font-family: var(--mono); background: var(--surface); padding: 0.15em 0.4em; border-radius: 4px; font-size: 0.9em; }\npre { font-family: var(--mono); font-size: 0.8em; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }\na { color: var(--accent); text-decoration: none; }\n.subtitle { color: var(--text-dim); margin-bottom: 2rem; }\n\n/* Run list table */\n.run-table { width: 100%; border-collapse: collapse; margin: 1rem 0; }\n.run-table th, .run-table td { padding: 0.5rem 0.75rem; text-align: left; border-bottom: 1px solid var(--border); font-size: 0.85rem; }\n.run-table th { color: var(--text-dim); font-weight: 600; }\n.run-table tbody tr:hover { background: var(--surface); }\n\n/* Run detail */\n.run { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }\n.run-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }\n.run-badge { font-size: 0.75rem; padding: 0.2em 0.6em; border-radius: 12px; font-weight: 600; }\n.run-badge-ok { background: rgba(63,185,80,0.15); color: var(--green); }\n.run-badge-error { background: rgba(248,81,73,0.15); color: var(--red); }\n\n.run-stats { display: flex; gap: 2rem; margin-bottom: 1.5rem; flex-wrap: wrap; }\n.stat { text-align: center; }\n.stat-val { font-size: 1.3rem; font-weight: 700; color: var(--text-bright); }\n.stat-label { font-size: 0.75rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }\n\n/* Timeline bar */\n.timeline-bar { display: flex; align-items: center; gap: 0; margin-bottom: 1rem; padding: 0.5rem 0; }\n.timeline-dot { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 700; color: var(--bg); flex-shrink: 0; }\n.timeline-dot + .timeline-dot { margin-left: -1px; }\n.timeline-dot::before { content: \"\"; position: absolute; }\n.dot-ok { background: var(--green); }\n.dot-error { background: var(--red); }\n.timeline-dot + .timeline-dot { margin-left: 4px; }\n\n/* Agent cards */\n.agent-card { border: 1px solid var(--border); border-radius: 6px; padding: 1rem; margin-bottom: 0.75rem; background: var(--bg); }\n.agent-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }\n.agent-seq { width: 24px; height: 24px; border-radius: 50%; background: var(--accent); color: var(--bg); display: flex; align-items: center; justify-content: center; font-size: 0.7rem; font-weight: 700; flex-shrink: 0; }\n.agent-name { font-weight: 700; font-size: 1rem; color: var(--text-bright); }\n.agent-badge { font-size: 0.7rem; padding: 0.15em 0.5em; border-radius: 10px; font-weight: 600; }\n.status-ok { background: rgba(63,185,80,0.15); color: var(--green); }\n.status-error { background: rgba(248,81,73,0.15); color: var(--red); }\n.agent-meta { display: flex; gap: 1rem; font-size: 0.8rem; color: var(--text-dim); margin-bottom: 0.5rem; flex-wrap: wrap; }\n.session-id { font-family: var(--mono); font-size: 0.75rem; }\n.agent-prompt { font-size: 0.85rem; color: var(--text-dim); font-style: italic; margin-bottom: 0.75rem; }\n\n/* Transcript */\n.transcript { margin-top: 0.5rem; }\n.transcript > summary { cursor: pointer; font-size: 0.85rem; color: var(--accent); font-weight: 600; padding: 0.3rem 0; }\n.transcript-body { margin-top: 0.5rem; max-height: 600px; overflow-y: auto; border: 1px solid var(--border); border-radius: 4px; padding: 0.5rem; }\n.msg { padding: 0.5rem; border-bottom: 1px solid var(--border); }\n.msg:last-child { border-bottom: none; }\n.msg-role { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.25rem; }\n.msg-user .msg-role { color: var(--accent); }\n.msg-assistant .msg-role { color: var(--green); }\n.msg-system .msg-role { color: var(--orange); }\n.msg-content { font-size: 0.8rem; }\n.text-block { margin-bottom: 0.5rem; white-space: pre-wrap; }\n.tool-call { background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.2); border-radius: 4px; padding: 0.5rem; margin: 0.25rem 0; }\n.tool-name { font-family: var(--mono); font-weight: 700; color: var(--accent); font-size: 0.8rem; }\n.tool-input { margin-top: 0.25rem; color: var(--text-dim); font-size: 0.75rem; max-height: 200px; overflow-y: auto; }\n.tool-result { background: rgba(63,185,80,0.05); border: 1px solid rgba(63,185,80,0.15); border-radius: 4px; padding: 0.5rem; margin: 0.25rem 0; }\n.tool-result pre { color: var(--text-dim); font-size: 0.75rem; max-height: 200px; overflow-y: auto; }\n.no-transcript { font-size: 0.8rem; color: var(--text-dim); font-style: italic; }\n.empty { color: var(--text-dim); font-style: italic; }\n\ncode { overflow-wrap: break-word; word-break: break-all; }\n.agent-prompt { overflow-wrap: break-word; word-break: break-word; }\n.run-header { flex-wrap: wrap; }\n.run-header h2 { word-break: break-all; }\n.run-table tbody tr:active { background: var(--surface); }\n\n@media (max-width: 600px) {\n body { padding: 1rem 0.75rem; }\n .run-table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }\n .run { padding: 0.75rem; }\n .run-stats { gap: 1rem; }\n .stat { flex: 1 1 calc(50% - 1rem); min-width: 0; }\n .timeline-bar { flex-wrap: wrap; gap: 4px; }\n .timeline-dot + .timeline-dot { margin-left: 0; }\n .timeline-dot { width: 36px; height: 36px; font-size: 0.8rem; }\n .agent-card { padding: 0.75rem; }\n .agent-meta { gap: 0.5rem; }\n .agent-seq { width: 32px; height: 32px; font-size: 0.75rem; }\n .agent-badge, .msg-role { font-size: 0.75rem; }\n .transcript > summary { padding: 0.75rem 0; min-height: 44px; display: flex; align-items: center; }\n .transcript-body { max-height: 50vh; }\n .tool-input { max-height: none; overflow-y: visible; }\n .tool-result pre { max-height: none; overflow-y: visible; }\n}\n@media (max-width: 360px) {\n .stat { flex: 1 1 100%; }\n}\n";
@@ -77,4 +77,32 @@ a { color: var(--accent); text-decoration: none; }
77
77
  .tool-result pre { color: var(--text-dim); font-size: 0.75rem; max-height: 200px; overflow-y: auto; }
78
78
  .no-transcript { font-size: 0.8rem; color: var(--text-dim); font-style: italic; }
79
79
  .empty { color: var(--text-dim); font-style: italic; }
80
+
81
+ code { overflow-wrap: break-word; word-break: break-all; }
82
+ .agent-prompt { overflow-wrap: break-word; word-break: break-word; }
83
+ .run-header { flex-wrap: wrap; }
84
+ .run-header h2 { word-break: break-all; }
85
+ .run-table tbody tr:active { background: var(--surface); }
86
+
87
+ @media (max-width: 600px) {
88
+ body { padding: 1rem 0.75rem; }
89
+ .run-table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
90
+ .run { padding: 0.75rem; }
91
+ .run-stats { gap: 1rem; }
92
+ .stat { flex: 1 1 calc(50% - 1rem); min-width: 0; }
93
+ .timeline-bar { flex-wrap: wrap; gap: 4px; }
94
+ .timeline-dot + .timeline-dot { margin-left: 0; }
95
+ .timeline-dot { width: 36px; height: 36px; font-size: 0.8rem; }
96
+ .agent-card { padding: 0.75rem; }
97
+ .agent-meta { gap: 0.5rem; }
98
+ .agent-seq { width: 32px; height: 32px; font-size: 0.75rem; }
99
+ .agent-badge, .msg-role { font-size: 0.75rem; }
100
+ .transcript > summary { padding: 0.75rem 0; min-height: 44px; display: flex; align-items: center; }
101
+ .transcript-body { max-height: 50vh; }
102
+ .tool-input { max-height: none; overflow-y: visible; }
103
+ .tool-result pre { max-height: none; overflow-y: visible; }
104
+ }
105
+ @media (max-width: 360px) {
106
+ .stat { flex: 1 1 100%; }
107
+ }
80
108
  `;
package/package.json CHANGED
@@ -1,12 +1,20 @@
1
1
  {
2
2
  "name": "claude-octopus",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "One brain, many arms — spawn multiple specialized Claude Code agents as MCP servers",
5
5
  "mcpName": "io.github.xiaolai/claude-octopus",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "claude-octopus": "dist/index.js"
9
9
  },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsc --watch",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "test:coverage": "vitest run --coverage"
17
+ },
10
18
  "dependencies": {
11
19
  "@anthropic-ai/claude-agent-sdk": "^0.2.98",
12
20
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -18,16 +26,15 @@
18
26
  "typescript": "^5.8.0",
19
27
  "vitest": "^4.1.2"
20
28
  },
29
+ "files": [
30
+ "dist/",
31
+ "assets/",
32
+ "server.json",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
21
36
  "license": "ISC",
22
37
  "engines": {
23
38
  "node": ">=18.0.0"
24
- },
25
- "scripts": {
26
- "build": "tsc",
27
- "start": "node dist/index.js",
28
- "dev": "tsc --watch",
29
- "test": "vitest run",
30
- "test:watch": "vitest",
31
- "test:coverage": "vitest run --coverage"
32
39
  }
33
- }
40
+ }
@@ -1,29 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- permissions:
10
- contents: read
11
-
12
- jobs:
13
- build-and-test:
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@v4
17
-
18
- - uses: pnpm/action-setup@v4
19
- with:
20
- version: 9
21
-
22
- - uses: actions/setup-node@v4
23
- with:
24
- node-version: "22"
25
- cache: pnpm
26
-
27
- - run: pnpm install --frozen-lockfile
28
- - run: pnpm build
29
- - run: pnpm test
@@ -1,86 +0,0 @@
1
- name: SDK watch — auto-bump Agent SDK
2
-
3
- on:
4
- schedule:
5
- # Every 6 hours
6
- - cron: "0 */6 * * *"
7
- workflow_dispatch:
8
-
9
- permissions:
10
- contents: write
11
-
12
- jobs:
13
- check-and-bump:
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@v4
17
-
18
- - uses: pnpm/action-setup@v4
19
- with:
20
- version: 9
21
-
22
- - uses: actions/setup-node@v4
23
- with:
24
- node-version: "22"
25
- registry-url: "https://registry.npmjs.org"
26
- cache: pnpm
27
-
28
- - run: pnpm install --frozen-lockfile
29
-
30
- # ── Detect new SDK version ─────────────────────────────────
31
- - name: Check for SDK update
32
- id: check
33
- run: |
34
- CURRENT=$(node -e "console.log(require('./node_modules/@anthropic-ai/claude-agent-sdk/package.json').version)")
35
- LATEST=$(npm view @anthropic-ai/claude-agent-sdk version)
36
- echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
37
- echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
38
- if [ "$CURRENT" = "$LATEST" ]; then
39
- echo "skip=true" >> "$GITHUB_OUTPUT"
40
- else
41
- echo "skip=false" >> "$GITHUB_OUTPUT"
42
- fi
43
-
44
- # ── Bump SDK ───────────────────────────────────────────────
45
- - name: Update SDK dependency
46
- if: steps.check.outputs.skip == 'false'
47
- run: pnpm add @anthropic-ai/claude-agent-sdk@latest
48
-
49
- # ── Build + test ───────────────────────────────────────────
50
- - name: Build
51
- if: steps.check.outputs.skip == 'false'
52
- run: pnpm build
53
-
54
- - name: Test
55
- if: steps.check.outputs.skip == 'false'
56
- run: pnpm test
57
-
58
- # ── Bump octopus patch version ─────────────────────────────
59
- - name: Bump patch version
60
- if: steps.check.outputs.skip == 'false'
61
- id: version
62
- run: |
63
- NEW_VERSION=$(node -e "
64
- const pkg = require('./package.json');
65
- const [maj, min, pat] = pkg.version.split('.').map(Number);
66
- console.log(maj + '.' + min + '.' + (pat + 1));
67
- ")
68
- pnpm pkg set version="$NEW_VERSION"
69
- echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
70
-
71
- # ── Commit + push ──────────────────────────────────────────
72
- - name: Commit and push
73
- if: steps.check.outputs.skip == 'false'
74
- run: |
75
- git config user.name "github-actions[bot]"
76
- git config user.email "github-actions[bot]@users.noreply.github.com"
77
- git add package.json pnpm-lock.yaml
78
- git commit -m "chore: bump agent-sdk ${{ steps.check.outputs.current }} -> ${{ steps.check.outputs.latest }}, release v${{ steps.version.outputs.version }}"
79
- git push
80
-
81
- # ── Publish to npm ─────────────────────────────────────────
82
- - name: Publish
83
- if: steps.check.outputs.skip == 'false'
84
- run: pnpm publish --no-git-checks --access public
85
- env:
86
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -1,160 +0,0 @@
1
- /**
2
- * CLI prompt helpers and MCP client detection — extracted from init.ts.
3
- */
4
-
5
- import { createInterface } from "node:readline/promises";
6
- import { readFile, writeFile, access } from "node:fs/promises";
7
- import { join } from "node:path";
8
- import { homedir } from "node:os";
9
- import type { AgentConfig } from "./templates.js";
10
-
11
- // ── Types ─────────────────────────────────────────────────────────
12
-
13
- export interface McpClient {
14
- name: string;
15
- configPath: string;
16
- }
17
-
18
- export interface McpConfig {
19
- mcpServers?: Record<string, unknown>;
20
- [key: string]: unknown;
21
- }
22
-
23
- // ── File helpers ─────────────────────────────────────────────────
24
-
25
- async function fileExists(path: string): Promise<boolean> {
26
- try {
27
- await access(path);
28
- return true;
29
- } catch {
30
- return false;
31
- }
32
- }
33
-
34
- // ── MCP Client Detection ─────────────────────────────────────────
35
-
36
- export async function detectMcpClients(): Promise<McpClient[]> {
37
- const home = homedir();
38
- const cwd = process.cwd();
39
-
40
- const candidates: McpClient[] = [
41
- { name: "Claude Code (project)", configPath: join(cwd, ".mcp.json") },
42
- { name: "Claude Desktop", configPath: join(home, ".claude", "mcp.json") },
43
- { name: "Cursor", configPath: join(cwd, ".cursor", "mcp.json") },
44
- { name: "Windsurf", configPath: join(cwd, ".windsurf", "mcp.json") },
45
- { name: "Claude Code (user)", configPath: join(home, ".claude", "mcp.json") },
46
- ];
47
-
48
- const seen = new Set<string>();
49
- const unique: McpClient[] = [];
50
- for (const c of candidates) {
51
- if (!seen.has(c.configPath)) {
52
- seen.add(c.configPath);
53
- unique.push(c);
54
- }
55
- }
56
-
57
- const detected: McpClient[] = [];
58
- for (const client of unique) {
59
- if (await fileExists(client.configPath)) {
60
- detected.push(client);
61
- }
62
- }
63
- return detected;
64
- }
65
-
66
- // ── Config I/O ───────────────────────────────────────────────────
67
-
68
- export async function readMcpConfig(path: string): Promise<McpConfig> {
69
- let raw: string;
70
- try {
71
- raw = await readFile(path, "utf-8");
72
- } catch (err) {
73
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return {};
74
- throw new Error(`Cannot read ${path}: ${(err as Error).message}`);
75
- }
76
- try {
77
- return JSON.parse(raw) as McpConfig;
78
- } catch {
79
- throw new Error(`${path} contains invalid JSON \u2014 fix it manually before running init`);
80
- }
81
- }
82
-
83
- export async function writeMcpConfig(path: string, config: McpConfig): Promise<void> {
84
- await writeFile(path, JSON.stringify(config, null, 2) + "\n", "utf-8");
85
- }
86
-
87
- // ── Interactive Prompts ──────────────────────────────────────────
88
-
89
- export async function choose(
90
- rl: ReturnType<typeof createInterface>,
91
- prompt: string,
92
- options: { label: string; value: string }[],
93
- ): Promise<string> {
94
- console.log(`\n${prompt}\n`);
95
- for (let i = 0; i < options.length; i++) {
96
- console.log(` ${i + 1}. ${options[i].label}`);
97
- }
98
- while (true) {
99
- const answer = await rl.question(`\nChoice [1-${options.length}]: `);
100
- const idx = parseInt(answer.trim(), 10) - 1;
101
- if (idx >= 0 && idx < options.length) {
102
- return options[idx].value;
103
- }
104
- console.log(`Please enter a number between 1 and ${options.length}.`);
105
- }
106
- }
107
-
108
- export async function confirm(
109
- rl: ReturnType<typeof createInterface>,
110
- prompt: string,
111
- ): Promise<boolean> {
112
- const answer = await rl.question(`${prompt} [Y/n]: `);
113
- return answer.trim().toLowerCase() !== "n";
114
- }
115
-
116
- // ── Custom Agent Builder ─────────────────────────────────────────
117
-
118
- export async function buildCustomAgent(
119
- rl: ReturnType<typeof createInterface>,
120
- ): Promise<AgentConfig> {
121
- console.log("\nDescribe your custom agent:\n");
122
-
123
- const description = await rl.question(" Description (what should it do?): ");
124
- let name: string;
125
- while (true) {
126
- const raw = (await rl.question(" Name (kebab-case, e.g. my-agent): ")).trim();
127
- if (/^[a-z0-9][a-z0-9-]*$/.test(raw) && raw.length <= 30) {
128
- name = raw;
129
- break;
130
- }
131
- console.log(" Name must be lowercase alphanumeric with hyphens, 1-30 chars.");
132
- }
133
-
134
- const toolName = name
135
- .replace(/[^a-zA-Z0-9]+/g, "_")
136
- .replace(/^_|_$/g, "")
137
- .slice(0, 53) || "custom_agent";
138
-
139
- const model = await choose(rl, " Model:", [
140
- { label: "Sonnet (balanced)", value: "sonnet" },
141
- { label: "Opus (most capable)", value: "opus" },
142
- { label: "Haiku (fastest, cheapest)", value: "haiku" },
143
- { label: "Default (inherit from SDK)", value: "" },
144
- ]);
145
-
146
- const readOnly = await confirm(rl, " Read-only (no file writes)?");
147
-
148
- const agent: AgentConfig = {
149
- serverName: name || "custom-agent",
150
- toolName,
151
- description: description || "Custom Claude Code agent",
152
- };
153
- if (model) agent.model = model;
154
- if (readOnly) agent.allowedTools = "Read,Grep,Glob";
155
- if (description) {
156
- agent.appendPrompt = description;
157
- }
158
-
159
- return agent;
160
- }
package/src/cli.ts DELETED
@@ -1,70 +0,0 @@
1
- /**
2
- * CLI report command.
3
- *
4
- * Usage:
5
- * claude-octopus report [run_id] [--out file.html] [--no-transcripts]
6
- * claude-octopus report --list
7
- */
8
-
9
- import { writeFile } from "node:fs/promises";
10
- import { buildTimelineConfig } from "./config.js";
11
- import { generateReport } from "./report.js";
12
-
13
- function usage(): never {
14
- console.error(`Usage:
15
- claude-octopus report [run_id] [--out file.html] [--no-transcripts]
16
-
17
- Options:
18
- run_id Generate detailed report for this run
19
- (no run_id) List all runs (default)
20
- --out <file> Write to file instead of stdout
21
- --no-transcripts Omit session transcripts from the report
22
- --help Show this help
23
- `);
24
- process.exit(1);
25
- }
26
-
27
- export function runReportCli(args: string[]): void {
28
- let runId: string | undefined;
29
- let outFile: string | undefined;
30
- let includeTranscripts = true;
31
-
32
- for (let i = 0; i < args.length; i++) {
33
- const arg = args[i];
34
- if (arg === "--out" || arg === "-o") {
35
- outFile = args[++i];
36
- if (!outFile) usage();
37
- } else if (arg === "--no-transcripts") {
38
- includeTranscripts = false;
39
- } else if (arg === "--list") {
40
- runId = undefined;
41
- } else if (arg === "--help" || arg === "-h") {
42
- usage();
43
- } else if (!arg.startsWith("-")) {
44
- runId = arg;
45
- } else {
46
- console.error(`Unknown option: ${arg}`);
47
- usage();
48
- }
49
- }
50
-
51
- const timeline = buildTimelineConfig();
52
-
53
- generateReport({
54
- timelineDir: timeline.dir,
55
- runId,
56
- includeTranscripts,
57
- })
58
- .then((html) => {
59
- if (outFile) {
60
- return writeFile(outFile, html, "utf-8").then(() => {
61
- console.error(`Report written to ${outFile}`);
62
- });
63
- }
64
- process.stdout.write(html);
65
- })
66
- .catch((error) => {
67
- console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
68
- process.exit(1);
69
- });
70
- }
package/src/config.ts DELETED
@@ -1,117 +0,0 @@
1
- import { join } from "node:path";
2
- import { homedir } from "node:os";
3
- import type { Options, OctopusConfig, TimelineConfig } from "./types.js";
4
- import {
5
- envStr,
6
- envList,
7
- envNum,
8
- envBool,
9
- envJson,
10
- validatePermissionMode,
11
- } from "./lib.js";
12
-
13
- export function buildTimelineConfig(): TimelineConfig {
14
- const raw = envStr("CLAUDE_TIMELINE_DIR");
15
- let dir: string;
16
- if (raw) {
17
- dir = raw.startsWith("~/") ? join(homedir(), raw.slice(2)) : raw === "~" ? homedir() : raw;
18
- } else {
19
- dir = join(homedir(), ".claude-octopus", "timelines");
20
- }
21
- return { dir };
22
- }
23
-
24
- export function buildOctopusConfig(): OctopusConfig {
25
- return {
26
- sdkOptions: buildBaseOptions(),
27
- timeline: buildTimelineConfig(),
28
- };
29
- }
30
-
31
- export function buildBaseOptions(): Options {
32
- const opts: Options = {
33
- cwd: envStr("CLAUDE_CWD") || process.cwd(),
34
- persistSession: envBool("CLAUDE_PERSIST_SESSION", true),
35
- };
36
-
37
- const rawPerm = envStr("CLAUDE_PERMISSION_MODE") || "default";
38
- const permMode = validatePermissionMode(rawPerm);
39
- if (rawPerm !== permMode) {
40
- console.error(
41
- `claude-octopus: invalid CLAUDE_PERMISSION_MODE "${rawPerm}", using "default"`
42
- );
43
- }
44
- opts.permissionMode = permMode as Options["permissionMode"];
45
- if (permMode === "bypassPermissions") {
46
- opts.allowDangerouslySkipPermissions = true;
47
- }
48
-
49
- const model = envStr("CLAUDE_MODEL");
50
- if (model) opts.model = model;
51
-
52
- const tools = envList("CLAUDE_ALLOWED_TOOLS");
53
- if (tools) opts.tools = tools;
54
- const disallowed = envList("CLAUDE_DISALLOWED_TOOLS");
55
- if (disallowed) opts.disallowedTools = disallowed;
56
-
57
- const maxTurns = envNum("CLAUDE_MAX_TURNS");
58
- if (maxTurns !== undefined && maxTurns > 0 && Number.isInteger(maxTurns)) {
59
- opts.maxTurns = maxTurns;
60
- }
61
- const maxBudget = envNum("CLAUDE_MAX_BUDGET_USD");
62
- if (maxBudget !== undefined && maxBudget > 0) {
63
- opts.maxBudgetUsd = maxBudget;
64
- }
65
-
66
- const sysPrompt = envStr("CLAUDE_SYSTEM_PROMPT");
67
- const appendPrompt = envStr("CLAUDE_APPEND_PROMPT");
68
- if (sysPrompt) {
69
- opts.systemPrompt = sysPrompt;
70
- } else if (appendPrompt) {
71
- opts.systemPrompt = {
72
- type: "preset",
73
- preset: "claude_code",
74
- append: appendPrompt,
75
- };
76
- }
77
-
78
- const dirs = envList("CLAUDE_ADDITIONAL_DIRS");
79
- if (dirs) opts.additionalDirectories = dirs;
80
-
81
- const plugins = envList("CLAUDE_PLUGINS");
82
- if (plugins) {
83
- opts.plugins = plugins.map((p) => ({ type: "local" as const, path: p }));
84
- }
85
-
86
- const mcpServers = envJson<Record<string, unknown>>("CLAUDE_MCP_SERVERS");
87
- if (mcpServers) {
88
- opts.mcpServers = mcpServers as Options["mcpServers"];
89
- }
90
-
91
- const effort = envStr("CLAUDE_EFFORT");
92
- if (effort) opts.effort = effort as Options["effort"];
93
-
94
- const sources = envList("CLAUDE_SETTING_SOURCES");
95
- if (sources) {
96
- opts.settingSources = sources as Options["settingSources"];
97
- }
98
-
99
- const settings = envStr("CLAUDE_SETTINGS");
100
- if (settings) {
101
- const trimmed = settings.trim();
102
- if (trimmed.startsWith("{")) {
103
- try {
104
- opts.settings = JSON.parse(trimmed);
105
- } catch (e) {
106
- console.error(`claude-octopus: invalid CLAUDE_SETTINGS JSON: ${e instanceof Error ? e.message : e}`);
107
- }
108
- } else {
109
- opts.settings = trimmed;
110
- }
111
- }
112
-
113
- const betas = envList("CLAUDE_BETAS");
114
- if (betas) opts.betas = betas as Options["betas"];
115
-
116
- return opts;
117
- }