aicodeman 1.14.1 → 1.15.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/README.zh-CN.md +91 -21
- package/dist/cli.d.ts +32 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +137 -34
- package/dist/cli.js.map +1 -1
- package/dist/config/webview-limits.d.ts +17 -1
- package/dist/config/webview-limits.d.ts.map +1 -1
- package/dist/config/webview-limits.js +18 -2
- package/dist/config/webview-limits.js.map +1 -1
- package/dist/hooks-config.d.ts +20 -0
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +100 -49
- package/dist/hooks-config.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.b24f795e.js → app.d4c3d0f9.js} +10 -10
- package/dist/web/public/app.d4c3d0f9.js.br +0 -0
- package/dist/web/public/app.d4c3d0f9.js.gz +0 -0
- package/dist/web/public/constants.e7175b95.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/i18n.da34cfaf.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +11 -7
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.9936227d.js → keyboard-accessory.2ea414af.js} +3 -0
- package/dist/web/public/keyboard-accessory.2ea414af.js.br +0 -0
- package/dist/web/public/keyboard-accessory.2ea414af.js.gz +0 -0
- package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.32cacde5.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.1d4203e0.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.1356a4f4.js.gz +0 -0
- package/dist/web/public/settings-ui.b41d33a5.js.gz +0 -0
- package/dist/web/public/styles.c4e58e13.css +1 -0
- package/dist/web/public/styles.c4e58e13.css.br +0 -0
- package/dist/web/public/styles.c4e58e13.css.gz +0 -0
- package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
- package/dist/web/public/sw.js +1 -0
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.430395be.js +4 -0
- package/dist/web/public/terminal-ui.430395be.js.br +0 -0
- package/dist/web/public/terminal-ui.430395be.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js +4 -0
- package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.br +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/{voice-input.085e9e73.js → voice-input.e5461859.js} +3 -0
- package/dist/web/public/voice-input.e5461859.js.br +0 -0
- package/dist/web/public/{voice-input.085e9e73.js.gz → voice-input.e5461859.js.gz} +0 -0
- package/dist/web/public/webview-tabs.js +6 -2
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +30 -2
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/webview-routes.d.ts.map +1 -1
- package/dist/web/routes/webview-routes.js +50 -3
- package/dist/web/routes/webview-routes.js.map +1 -1
- package/package.json +3 -1
- package/scripts/postinstall.js +29 -0
- package/skills/codeman/SKILL.md +62 -10
- package/skills/codeman/reference/endpoints.md +63 -8
- package/skills/codeman/reference/recipes.md +28 -5
- package/dist/web/public/app.b24f795e.js.br +0 -0
- package/dist/web/public/app.b24f795e.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.9936227d.js.br +0 -0
- package/dist/web/public/keyboard-accessory.9936227d.js.gz +0 -0
- package/dist/web/public/styles.b10e5edd.css +0 -1
- package/dist/web/public/styles.b10e5edd.css.br +0 -0
- package/dist/web/public/styles.b10e5edd.css.gz +0 -0
- package/dist/web/public/terminal-ui.bc6819a9.js +0 -4
- package/dist/web/public/terminal-ui.bc6819a9.js.br +0 -0
- package/dist/web/public/terminal-ui.bc6819a9.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.br +0 -0
package/README.zh-CN.md
CHANGED
|
@@ -657,6 +657,16 @@ sc -l # 列出会话
|
|
|
657
657
|
|
|
658
658
|
面向不经浏览器控制 Codeman 的 AI 智能体与自动化:一个拉起工作会话的智能体、一个 CI 机器人,或是**运行在 Codeman 会话*内部*、编排其他会话的 Claude Code**。UI 能做的一切都是 HTTP + CLI,因此智能体也能做。
|
|
659
659
|
|
|
660
|
+
> **捷径:装上打包好的智能体技能。** 下面这一整套(外加多工作会话的实战配方)已经作为 Claude Code 技能随仓库发布在 [`skills/codeman`](skills/codeman/SKILL.md),会话内部的智能体不必等你把文档粘进提示词就能驱动 Codeman。三种获取方式:
|
|
661
|
+
>
|
|
662
|
+
> - `npx skills add Ark0N/Codeman --skill codeman -g`:全局安装,任何支持技能的智能体都能用
|
|
663
|
+
> - `codeman skill install`(全局)或 `codeman skill install --case <name>`:给那些从 npm 安装、从未克隆过仓库的用户;`codeman skill uninstall` 可撤销
|
|
664
|
+
> - **App Settings → Agent Skill**(`agentSkillEnabled`,默认关闭):开启后,Codeman 会在每次于某个 case 中创建 Claude 会话时把技能注入该 case;case 里用户自己写的 `skills/codeman` 永远不会被覆盖
|
|
665
|
+
>
|
|
666
|
+
> 全局安装(`codeman skill install` 或 `npx skills add`)会被**本机每一个新建的 Claude Code 会话**读到,无论它在不在 Codeman 里。技能自带门禁:不在 Codeman 会话中(`CODEMAN_MUX` 未设置)时它拒绝动作,所以全局装上它对无关会话没有代价。
|
|
667
|
+
>
|
|
668
|
+
> ⚠️ 把 `agentSkillEnabled` 关回去**不会删掉已经注入的副本**(在创建时做清扫,会把技能从共用同一个 `.claude/` 目录的其他活动会话脚下抽走)。要删就按 case 删:`codeman skill uninstall --case <name>`。
|
|
669
|
+
|
|
660
670
|
### 检测自己身处 Codeman 内部
|
|
661
671
|
|
|
662
672
|
当 CLI 运行在 Codeman 受管会话中时,以下环境变量会被设置 —— 读取它们,别硬编码任何东西:
|
|
@@ -670,15 +680,21 @@ sc -l # 列出会话
|
|
|
670
680
|
|
|
671
681
|
### 行路规则(POST 之前先读)
|
|
672
682
|
|
|
673
|
-
1.
|
|
683
|
+
1. **只发单行输入,而且必须以 `\r` 结尾。** 编程输入按字面文本发送,**只有当输入里含回车符时才会触发 Enter**:`{"input":"run tests\r"}`。少了 `\r`,文本就停在会话的输入框里不被提交(同一次调用里的 `wait` 还会在一个压根没开始的回合上耗满整个超时)。内嵌的换行会被剥掉而不是报错,因此 `"echo A\necho B\r"` 执行的是拼起来的 `echo Aecho B`:一次调用只发一行。
|
|
674
684
|
2. **让输入幂等。** 在 `POST …/input` 上带上稳定的 `clientId` 和按会话单调递增的 `seq`。服务端会去重,因此连接中断后的重试不会重复投递提示。
|
|
675
|
-
3. **认证。** 若设置了 `CODEMAN_PASSWORD`,发送 HTTP Basic 认证(用户 `admin` 或 `CODEMAN_USERNAME`)或 `codeman_session` cookie。默认的环回安装无密码。缺失的 `Origin` 头被允许,因此普通 `curl` 可用;跨站的浏览器 origin 会被拒绝(CSRF
|
|
685
|
+
3. **认证。** 若设置了 `CODEMAN_PASSWORD`,发送 HTTP Basic 认证(用户 `admin` 或 `CODEMAN_USERNAME`)或 `codeman_session` cookie。默认的环回安装无密码。缺失的 `Origin` 头被允许,因此普通 `curl` 可用;跨站的浏览器 origin 会被拒绝(CSRF 防护)。⚠️ `401` 回的是裸字符串 `Unauthorized`,**不是** JSON 信封,直接喂给 `jq` 只会抛解析错误而看不到真正的失败原因:先看状态码,再解析。
|
|
676
686
|
4. **响应信封。** 多数端点返回 `{ "success": true, "data": … }`(错误:`{ "success": false, "error", "errorCode" }`)。少数遗留 GET 返回裸响应体 —— **两种都要处理**(`body.data ?? body`)。
|
|
677
687
|
5. **`/api/v1/*`** 是 `/api/*` 的稳定别名。
|
|
688
|
+
6. **用等待代替轮询,别把超时当成错误。** 等待类端点在没等到事情发生时也以 HTTP `200` 加 `wait.timedOut: true` 应答,所以要循环调用短等待(默认 60 秒),而不是发一个超长的调用:隧道会掐断空闲连接。`wait.timeoutMs` 告诉你服务端钳制之后真正采用的超时(上限 600 秒)。
|
|
689
|
+
7. **只有 `claude` 会话会发出 `stop` 与 `blocked`。** 这两个来自 Claude Code hook;`shell` 与外部 CLI(opencode/codex/gemini/antigravity)只接受 `idle`、`working` 与 `exit`。在这些模式上显式索要 `stop` 会得到 `400`;不传 `until` 则永远安全。⚠️ `shell` 会话的 `idle` 只在启动时触发**一次**,此后再也不会,所以在那里用「发送并等待」只能等到超时:没有 hook 的会话请用 `wait-output` 标记来同步。
|
|
690
|
+
8. **没有任何东西会报告「就绪」,得自己显式等。** 新会话在 PID 出现之前一律回答 `{"signal":"exit","immediate":true}`(意思是*还没启动*,不是*崩了*),而全新 case 里的 `claude` 工作会话接着会停在 CLI 的信任对话框上。此时给它发提示,等待会在约 2 秒后因 `idle` 解除,看上去和一个跑完的回合一模一样,而文本其实卡在对话框里。下面的配方 2b 就是避开它的顺序。
|
|
678
691
|
|
|
679
692
|
### 常用配方
|
|
680
693
|
|
|
681
694
|
```bash
|
|
695
|
+
# 每个 Codeman 会话里都自动设好了 CODEMAN_API_URL,协议也是对的。
|
|
696
|
+
# 下面的兜底值适用于标准安装;在 --https 安装上请自己写 https:// 的地址,
|
|
697
|
+
# 并给每个 curl 加上 -k(自签名证书)。
|
|
682
698
|
API="${CODEMAN_API_URL:-http://127.0.0.1:3000}"
|
|
683
699
|
# (若设置了密码,给每个调用加上 -u admin:"$CODEMAN_PASSWORD")
|
|
684
700
|
|
|
@@ -690,18 +706,69 @@ curl -s -X POST "$API/api/quick-start" \
|
|
|
690
706
|
-H 'Content-Type: application/json' \
|
|
691
707
|
-d '{"caseName":"refactor-auth","mode":"claude","effort":"high"}' | jq
|
|
692
708
|
|
|
709
|
+
# 2b. 等这个工作会话真正就绪(见规则 8):先探输入框的标记,信任对话框只作兜底。
|
|
710
|
+
# (反过来先探信任对话框、再盲发一个 Enter,在重复运行时会误伤:对话框的文字
|
|
711
|
+
# 会一直留在缓冲区里,探测因此匹配到旧文本,而那个 Enter 落进了已经就绪的输入框。)
|
|
712
|
+
# 匹配单个词:TUI 的文字到达匹配器时可能已经丢掉了词间空格。
|
|
713
|
+
until [ "$(curl -s "$API/api/sessions/$SID" | jq '.data.pid')" != null ]; do sleep 1; done
|
|
714
|
+
R=$(curl -sG "$API/api/sessions/$SID/wait-output" --data-urlencode 'match=bypass' \
|
|
715
|
+
--data-urlencode 'from=buffer' --data-urlencode 'timeout=5000')
|
|
716
|
+
if ! jq -e '.data.wait.matched' <<<"$R" >/dev/null; then
|
|
717
|
+
T=$(curl -sG "$API/api/sessions/$SID/wait-output" --data-urlencode 'match=trust' \
|
|
718
|
+
--data-urlencode 'from=buffer' --data-urlencode 'timeout=2000')
|
|
719
|
+
jq -e '.data.wait.matched' <<<"$T" >/dev/null && \
|
|
720
|
+
curl -s -X POST "$API/api/sessions/$SID/input" -H 'Content-Type: application/json' \
|
|
721
|
+
-d '{"input":"\r","useMux":true}' # 接受首次运行的信任对话框
|
|
722
|
+
curl -sG "$API/api/sessions/$SID/wait-output" --data-urlencode 'match=bypass' \
|
|
723
|
+
--data-urlencode 'from=buffer' --data-urlencode 'timeout=45000' >/dev/null
|
|
724
|
+
fi
|
|
725
|
+
|
|
693
726
|
# 3. 向会话发送提示(精确一次:clientId + seq)
|
|
694
727
|
curl -s -X POST "$API/api/sessions/$SID/input" \
|
|
695
728
|
-H 'Content-Type: application/json' \
|
|
696
|
-
-d '{"input":"Run the test suite and summarize failures","useMux":true,"clientId":"agent-1","seq":1}'
|
|
697
|
-
|
|
698
|
-
# 4. 读回终端内容
|
|
699
|
-
curl -s "$API/api/sessions/$SID/output" | jq -r '.data // .'
|
|
729
|
+
-d '{"input":"Run the test suite and summarize failures\r","useMux":true,"clientId":"agent-1","seq":1}'
|
|
700
730
|
|
|
701
|
-
#
|
|
731
|
+
# 4. 发送提示并阻塞到这一回合结束(先注册等待再写入,因此不会拿上一回合的状态来应答)
|
|
732
|
+
curl -s -X POST "$API/api/sessions/$SID/input" \
|
|
733
|
+
-H 'Content-Type: application/json' \
|
|
734
|
+
-d '{"input":"Run the test suite and summarize failures\r","useMux":true,
|
|
735
|
+
"clientId":"agent-1","seq":2,"wait":"stop,exit","waitTimeout":60000}' \
|
|
736
|
+
| jq '.data.wait' # -> {"signal":"stop","timedOut":false,"waitedMs":41230,...}
|
|
737
|
+
# (`stop` 是回合结束的权威 hook。加上 `idle` 会让它在转圈停顿时也解除,
|
|
738
|
+
# 任何重画出 ❯ 提示符的东西同理,比如一个对话框。)
|
|
739
|
+
|
|
740
|
+
# 4b. 超时了?那是 200,不是失败。循环调用短等待即可。
|
|
741
|
+
curl -s "$API/api/sessions/$SID/wait?until=stop,exit&timeout=60000" | jq '.data.wait'
|
|
742
|
+
|
|
743
|
+
# 4c. 或者等输出里出现某个标记(shell 会话也适用)。
|
|
744
|
+
# ⚠️ 每次调用都要用不同的标记(tmux 重画会重放旧屏幕文字),并且把标记拆开写,
|
|
745
|
+
# 让敲进去的那一行本身不包含它:你自己的按键会回显进输出流,不拆开的标记会在
|
|
746
|
+
# 命令还没跑之前就匹配上。from=buffer 用来接住在等待落地之前就已打印的标记。
|
|
747
|
+
N=$RANDOM
|
|
748
|
+
curl -s -X POST "$API/api/sessions/$SID/input" -H 'Content-Type: application/json' \
|
|
749
|
+
-d "{\"input\":\"M=DONE; npm test; echo \${M}_$N rc=\$?\r\",\"useMux\":true}"
|
|
750
|
+
curl -sG "$API/api/sessions/$SID/wait-output" \
|
|
751
|
+
--data-urlencode "match=DONE_$N" --data-urlencode 'from=buffer' \
|
|
752
|
+
--data-urlencode 'timeout=60000' | jq '.data.wait'
|
|
753
|
+
|
|
754
|
+
# 5. 读回答案。claude / codex 会话用 last-response:它取自 transcript 而不是屏幕,
|
|
755
|
+
# 因此不带 TUI 的画框与重画噪声。⚠️ 要轮询,别只读一次:transcript 落盘比 stop
|
|
756
|
+
# 信号稍晚,紧跟着「发送并等待」返回后立刻读,常常拿到空串。
|
|
757
|
+
for _ in $(seq 1 10); do
|
|
758
|
+
TXT=$(curl -s "$API/api/sessions/$SID/last-response" | jq -r '.data.text')
|
|
759
|
+
[ -n "$TXT" ] && break; sleep 1
|
|
760
|
+
done
|
|
761
|
+
printf '%s\n' "$TXT"
|
|
762
|
+
|
|
763
|
+
# 5b. 其他模式(shell/opencode/gemini/antigravity)没有 transcript,读终端。
|
|
764
|
+
# ⚠️ 用 terminal?tail=,不要用 /output:后者的 textOutput 对每个由 tmux 承载的
|
|
765
|
+
# (也就是每个交互式)会话都是空的。tail 按字节计,返回的是含 ANSI 的终端数据。
|
|
766
|
+
curl -s "$API/api/sessions/$SID/terminal?tail=8000" | jq -r '.data.terminalBuffer'
|
|
767
|
+
|
|
768
|
+
# 6. 流式接收实时事件(会话输出、智能体活动、状态)
|
|
702
769
|
curl -sN "$API/api/events" # Server-Sent Events
|
|
703
770
|
|
|
704
|
-
#
|
|
771
|
+
# 7. 调度周期性工作(cron 风格任务)
|
|
705
772
|
curl -s -X POST "$API/api/cron/jobs" \
|
|
706
773
|
-H 'Content-Type: application/json' \
|
|
707
774
|
-d '{"name":"nightly-deps","agentType":"claude","workingDir":"/home/me/proj",
|
|
@@ -709,11 +776,11 @@ curl -s -X POST "$API/api/cron/jobs" \
|
|
|
709
776
|
"inputMode":"typed","scheduleType":"daily","dailyTime":"03:00",
|
|
710
777
|
"enabled":true,"concurrencyPolicy":"warn_only"}' | jq
|
|
711
778
|
|
|
712
|
-
#
|
|
779
|
+
# 8. 查看后台子智能体及其活动记录
|
|
713
780
|
curl -s "$API/api/subagents" | jq '.data // .'
|
|
714
781
|
curl -s "$API/api/subagents/$AID/transcript" | jq -r '.data // .'
|
|
715
782
|
|
|
716
|
-
#
|
|
783
|
+
# 9. 全系统快照(会话、设置、重生、统计)
|
|
717
784
|
curl -s "$API/api/status" | jq
|
|
718
785
|
```
|
|
719
786
|
|
|
@@ -739,20 +806,23 @@ Codeman 会注册 Claude Code hook,它们 `POST /api/hook-event`(`permission
|
|
|
739
806
|
|
|
740
807
|
## API
|
|
741
808
|
|
|
742
|
-
基于 Fastify 的 REST —— **
|
|
809
|
+
基于 Fastify 的 REST —— **21 个路由模块中约 200 个处理器**,外加一条 SSE 流和一条 WebSocket 终端通道。所有响应都使用 `ApiResponse<T>` 信封(`{success, data}` / `{success, error, errorCode}`);`/api/v1/*` 是稳定别名。以下是一个有代表性的子集:
|
|
743
810
|
|
|
744
811
|
### 会话(Sessions)
|
|
745
812
|
|
|
746
|
-
| 方法 | 端点
|
|
747
|
-
| -------- |
|
|
748
|
-
| `GET` | `/api/sessions`
|
|
749
|
-
| `POST` | `/api/quick-start`
|
|
750
|
-
| `POST` | `/api/sessions/:id/input`
|
|
751
|
-
| `GET` | `/api/sessions/:id/
|
|
752
|
-
| `GET` | `/api/sessions/
|
|
753
|
-
| `
|
|
754
|
-
| `
|
|
755
|
-
| `
|
|
813
|
+
| 方法 | 端点 | 说明 |
|
|
814
|
+
| -------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
815
|
+
| `GET` | `/api/sessions` | 列出全部 |
|
|
816
|
+
| `POST` | `/api/quick-start` | 创建 case + 启动会话(`{caseName?, mode?, effort?, envOverrides?}`) |
|
|
817
|
+
| `POST` | `/api/sessions/:id/input` | 发送输入(`{input, useMux?, clientId?, seq?, wait?, waitTimeout?}`:`clientId`+`seq` = 精确一次;`wait` 阻塞到这一回合结束) |
|
|
818
|
+
| `GET` | `/api/sessions/:id/terminal` | 读取终端输出(`?tail=<bytes>`、`?full=1`):交互式会话的读取路径 |
|
|
819
|
+
| `GET` | `/api/sessions/:id/output` | 一次性的解析输出(tmux 承载的会话里 `textOutput` 为空) |
|
|
820
|
+
| `GET` | `/api/sessions/:id/wait` | 阻塞到某个信号触发(`?until=stop,idle,exit&timeout=&fresh=`);超时是 `200` |
|
|
821
|
+
| `GET` | `/api/sessions/:id/wait-output` | 阻塞到某个字面串出现(`?match=&nocase=&from=now\|buffer&timeout=`) |
|
|
822
|
+
| `GET` | `/api/sessions/unified` | 统一的活动 + 历史清单(会话管理器):`?q=&limit=` |
|
|
823
|
+
| `POST` | `/api/sessions/:id/pin` | 在会话管理器中置顶 / 取消置顶(`{pinned}`) |
|
|
824
|
+
| `PUT` | `/api/session-order` | 跨设备同步标签顺序(`{order: [ids]}`) |
|
|
825
|
+
| `DELETE` | `/api/sessions/:id` | 删除会话 |
|
|
756
826
|
|
|
757
827
|
### 重生(Respawn)
|
|
758
828
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -8,5 +8,37 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command } from 'commander';
|
|
10
10
|
declare const program: Command;
|
|
11
|
+
/**
|
|
12
|
+
* Case name to directory, checking `linked-cases.json` FIRST and falling back to the
|
|
13
|
+
* shared single-user cases dir. Mirrors `resolveCasePath()` in `case-routes.ts`, which
|
|
14
|
+
* is what the web UI and `quick-start` use. Without the linked-cases lookup this
|
|
15
|
+
* command rejected every case linked in from outside `~/codeman-cases` with
|
|
16
|
+
* "Case not found", even though the server resolved the same name fine.
|
|
17
|
+
*
|
|
18
|
+
* Sync and tolerant on purpose: a missing or malformed registry means "no linked
|
|
19
|
+
* cases", never a crash.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveCliCasePath(name: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve where `skill install` / `skill uninstall` operate. Global is
|
|
24
|
+
* `~/.claude/skills/codeman` (Claude Code's user-scope skill dir, read by every new
|
|
25
|
+
* session); `--case <name>` targets `<case>/.claude/skills/codeman`, resolved through
|
|
26
|
+
* `resolveCliCasePath()` above. The web server's automatic per-case injection
|
|
27
|
+
* (`agentSkillEnabled`) covers multi-user spaces; this CLI is a local operator tool
|
|
28
|
+
* and stays single-user.
|
|
29
|
+
*
|
|
30
|
+
* A missing case is REPORTED, not exited on: the exit lives in the wrapper below so
|
|
31
|
+
* this resolution (including the linked-cases lookup, which shipped unguarded) can be
|
|
32
|
+
* unit-tested without `process.exit(1)` taking the test runner down with it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveSkillTargetPath(options: {
|
|
35
|
+
case?: string;
|
|
36
|
+
}): {
|
|
37
|
+
target: string;
|
|
38
|
+
missingCase?: undefined;
|
|
39
|
+
} | {
|
|
40
|
+
target?: undefined;
|
|
41
|
+
missingCase: string;
|
|
42
|
+
};
|
|
11
43
|
export { program };
|
|
12
44
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,QAAA,MAAM,OAAO,SAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAgG9B;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASvD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,MAAM,CAAC,EAAE,SAAS,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAO5F;AAmhCD,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -121,7 +121,7 @@ const LINKED_CASES_FILE = dataPath('linked-cases.json');
|
|
|
121
121
|
* Sync and tolerant on purpose: a missing or malformed registry means "no linked
|
|
122
122
|
* cases", never a crash.
|
|
123
123
|
*/
|
|
124
|
-
function resolveCliCasePath(name) {
|
|
124
|
+
export function resolveCliCasePath(name) {
|
|
125
125
|
try {
|
|
126
126
|
const linked = JSON.parse(readFileSync(LINKED_CASES_FILE, 'utf-8'));
|
|
127
127
|
const target = linked?.[name];
|
|
@@ -140,17 +140,28 @@ function resolveCliCasePath(name) {
|
|
|
140
140
|
* `resolveCliCasePath()` above. The web server's automatic per-case injection
|
|
141
141
|
* (`agentSkillEnabled`) covers multi-user spaces; this CLI is a local operator tool
|
|
142
142
|
* and stays single-user.
|
|
143
|
+
*
|
|
144
|
+
* A missing case is REPORTED, not exited on: the exit lives in the wrapper below so
|
|
145
|
+
* this resolution (including the linked-cases lookup, which shipped unguarded) can be
|
|
146
|
+
* unit-tested without `process.exit(1)` taking the test runner down with it.
|
|
143
147
|
*/
|
|
144
|
-
function
|
|
148
|
+
export function resolveSkillTargetPath(options) {
|
|
145
149
|
if (options.case) {
|
|
146
150
|
const casePath = resolveCliCasePath(options.case);
|
|
147
|
-
if (!existsSync(casePath))
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
return join(casePath, '.claude', 'skills', 'codeman');
|
|
151
|
+
if (!existsSync(casePath))
|
|
152
|
+
return { missingCase: casePath };
|
|
153
|
+
return { target: join(casePath, '.claude', 'skills', 'codeman') };
|
|
152
154
|
}
|
|
153
|
-
return join(homedir(), '.claude', 'skills', 'codeman');
|
|
155
|
+
return { target: join(homedir(), '.claude', 'skills', 'codeman') };
|
|
156
|
+
}
|
|
157
|
+
/** Exit-owning wrapper around `resolveSkillTargetPath()` for the two commands below. */
|
|
158
|
+
function resolveSkillTarget(options) {
|
|
159
|
+
const resolved = resolveSkillTargetPath(options);
|
|
160
|
+
if (resolved.missingCase !== undefined) {
|
|
161
|
+
console.error(chalk.red(`✗ Case not found: ${resolved.missingCase}`));
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
return resolved.target;
|
|
154
165
|
}
|
|
155
166
|
/** Print an AgentSkillApplyResult for humans; exit non-zero when nothing was done. */
|
|
156
167
|
function reportSkillResult(result, target) {
|
|
@@ -514,42 +525,134 @@ function printStats(stats) {
|
|
|
514
525
|
console.log(` Busy: ${stats.busySessions}`);
|
|
515
526
|
console.log('');
|
|
516
527
|
}
|
|
517
|
-
|
|
528
|
+
/**
|
|
529
|
+
* GET `<base>/api/status` with a short timeout, tolerating the self-signed cert an
|
|
530
|
+
* `--https` install uses. ANY HTTP answer proves the server is up: a 401 just
|
|
531
|
+
* means it wants credentials (sent when available, same env → data-dir `.env`
|
|
532
|
+
* fallback as `codeman attach`).
|
|
533
|
+
*/
|
|
534
|
+
function probeWebServerAt(base) {
|
|
535
|
+
let url;
|
|
536
|
+
try {
|
|
537
|
+
url = new URL('/api/status', base);
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
return Promise.resolve(null);
|
|
541
|
+
}
|
|
542
|
+
const envFile = readCodemanEnv();
|
|
543
|
+
const username = process.env.CODEMAN_USERNAME || envFile.CODEMAN_USERNAME || 'admin';
|
|
544
|
+
const password = process.env.CODEMAN_PASSWORD || envFile.CODEMAN_PASSWORD;
|
|
545
|
+
const transport = url.protocol === 'https:' ? https : http;
|
|
546
|
+
const headers = { Accept: 'application/json' };
|
|
547
|
+
if (password) {
|
|
548
|
+
headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
549
|
+
}
|
|
550
|
+
return new Promise((resolve) => {
|
|
551
|
+
const req = transport.request({
|
|
552
|
+
protocol: url.protocol,
|
|
553
|
+
hostname: url.hostname,
|
|
554
|
+
port: url.port,
|
|
555
|
+
method: 'GET',
|
|
556
|
+
path: url.pathname,
|
|
557
|
+
rejectUnauthorized: false,
|
|
558
|
+
headers,
|
|
559
|
+
timeout: 3000,
|
|
560
|
+
}, (res) => {
|
|
561
|
+
const chunks = [];
|
|
562
|
+
let received = 0;
|
|
563
|
+
res.on('data', (chunk) => {
|
|
564
|
+
received += chunk.length;
|
|
565
|
+
if (received <= 1024 * 1024)
|
|
566
|
+
chunks.push(chunk);
|
|
567
|
+
});
|
|
568
|
+
res.on('end', () => {
|
|
569
|
+
const statusCode = res.statusCode ?? 0;
|
|
570
|
+
if (statusCode === 401) {
|
|
571
|
+
resolve({ reachable: true, url: base, statusCode, authRequired: true });
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
let version;
|
|
575
|
+
let sessions;
|
|
576
|
+
try {
|
|
577
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString('utf-8'));
|
|
578
|
+
const data = parsed?.data ?? parsed;
|
|
579
|
+
if (typeof data?.version === 'string')
|
|
580
|
+
version = data.version;
|
|
581
|
+
if (Array.isArray(data?.sessions))
|
|
582
|
+
sessions = data.sessions;
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
// Not JSON, but still an answer, so still running.
|
|
586
|
+
}
|
|
587
|
+
resolve({ reachable: true, url: base, statusCode, version, sessions });
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
req.on('timeout', () => req.destroy(new Error('timeout')));
|
|
591
|
+
req.on('error', () => resolve(null));
|
|
592
|
+
req.end();
|
|
593
|
+
});
|
|
594
|
+
}
|
|
518
595
|
program
|
|
519
596
|
.command('status')
|
|
520
|
-
.description('Show
|
|
521
|
-
.
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
597
|
+
.description('Show whether the Codeman web server is running, plus session/task state')
|
|
598
|
+
.option('--url <url>', 'Server URL to probe (defaults to CODEMAN_API_URL, then local port)')
|
|
599
|
+
.action(async (options) => {
|
|
600
|
+
// Issue #230: this command runs in its own fresh process, and the old output
|
|
601
|
+
// reported THAT process's (always-stopped) Ralph loop under a bare "Status:",
|
|
602
|
+
// reading as "the server is down" while the web service ran fine. Probe the
|
|
603
|
+
// real server first; the Ralph loop has its own `codeman ralph status`.
|
|
604
|
+
const port = process.env.CODEMAN_PORT || '3000';
|
|
605
|
+
const candidates = options.url
|
|
606
|
+
? [options.url]
|
|
607
|
+
: process.env.CODEMAN_API_URL
|
|
608
|
+
? [process.env.CODEMAN_API_URL]
|
|
609
|
+
: [`https://127.0.0.1:${port}`, `http://127.0.0.1:${port}`];
|
|
610
|
+
let probe = { reachable: false, url: candidates[0] };
|
|
611
|
+
for (const candidate of candidates) {
|
|
612
|
+
const answer = await probeWebServerAt(candidate);
|
|
613
|
+
if (answer) {
|
|
614
|
+
probe = answer;
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
538
618
|
console.log(chalk.bold('\nCodeman Status'));
|
|
539
619
|
console.log('─'.repeat(40));
|
|
540
|
-
console.log(chalk.bold('\
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
620
|
+
console.log(chalk.bold('\nWeb Server:'));
|
|
621
|
+
if (probe.reachable) {
|
|
622
|
+
const version = probe.version ? ` (v${probe.version})` : '';
|
|
623
|
+
console.log(` Status: ${chalk.green('running')}${version} at ${probe.url}`);
|
|
624
|
+
if (probe.authRequired) {
|
|
625
|
+
console.log(chalk.gray(' (answers 401: set CODEMAN_PASSWORD/CODEMAN_USERNAME to see session details)'));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
console.log(` Status: ${chalk.red('not reachable')} at ${candidates.join(' or ')}`);
|
|
630
|
+
console.log(chalk.gray(' (start it with `codeman web`, or check your service: systemctl --user status codeman-web)'));
|
|
631
|
+
}
|
|
632
|
+
// Prefer the server's live view; fall back to the shared saved state, labeled
|
|
633
|
+
// as such, so the numbers are never silently a different thing.
|
|
634
|
+
if (probe.sessions) {
|
|
635
|
+
const live = probe.sessions;
|
|
636
|
+
console.log(chalk.bold('\nSessions (live, from the server):'));
|
|
637
|
+
console.log(` Total: ${live.length}`);
|
|
638
|
+
console.log(` Idle: ${live.filter((s) => s.status === 'idle').length}`);
|
|
639
|
+
console.log(` Busy: ${live.filter((s) => s.status === 'busy').length}`);
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
const manager = getSessionManager();
|
|
643
|
+
const storedValues = Object.values(manager.getStoredSessions());
|
|
644
|
+
console.log(chalk.bold('\nSessions (from saved state):'));
|
|
645
|
+
console.log(` Active: ${storedValues.filter((s) => s.status !== 'stopped').length}`);
|
|
646
|
+
console.log(` Idle: ${storedValues.filter((s) => s.status === 'idle').length}`);
|
|
647
|
+
console.log(` Busy: ${storedValues.filter((s) => s.status === 'busy').length}`);
|
|
648
|
+
}
|
|
649
|
+
const taskCounts = getTaskQueue().getCount();
|
|
544
650
|
console.log(chalk.bold('\nTasks:'));
|
|
545
651
|
console.log(` Total: ${taskCounts.total}`);
|
|
546
652
|
console.log(` Pending: ${taskCounts.pending}`);
|
|
547
653
|
console.log(` Running: ${taskCounts.running}`);
|
|
548
654
|
console.log(` Completed: ${taskCounts.completed}`);
|
|
549
655
|
console.log(` Failed: ${taskCounts.failed}`);
|
|
550
|
-
const statusColor = loopStatus === 'running' ? chalk.green : loopStatus === 'paused' ? chalk.yellow : chalk.gray;
|
|
551
|
-
console.log(chalk.bold('\nRalph Loop:'));
|
|
552
|
-
console.log(` Status: ${statusColor(loopStatus)}`);
|
|
553
656
|
console.log('');
|
|
554
657
|
});
|
|
555
658
|
program
|