@konglx/rotom 2.21.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.md +417 -0
- package/bin/mesh-master.sh +439 -0
- package/bin/rotom +29 -0
- package/bin/rotom-link.sh +136 -0
- package/bin/rotom-send-with-status +57 -0
- package/bin/rotom-up.sh +428 -0
- package/dist/cli/ask.js +62 -0
- package/dist/cli/common.js +321 -0
- package/dist/cli/config.js +65 -0
- package/dist/cli/directory.js +17 -0
- package/dist/cli/executor.js +58 -0
- package/dist/cli/fed.js +91 -0
- package/dist/cli/group.js +273 -0
- package/dist/cli/identity.js +62 -0
- package/dist/cli/init.js +268 -0
- package/dist/cli/issue.js +202 -0
- package/dist/cli/join.js +170 -0
- package/dist/cli/link.js +47 -0
- package/dist/cli/master.js +51 -0
- package/dist/cli/memory.js +307 -0
- package/dist/cli/note.js +68 -0
- package/dist/cli/repo.js +77 -0
- package/dist/cli/rotom.js +277 -0
- package/dist/cli/routes.js +118 -0
- package/dist/cli/run.js +45 -0
- package/dist/cli/schedule.js +237 -0
- package/dist/cli/skill.js +173 -0
- package/dist/cli/team.js +106 -0
- package/dist/executor/claude-code-hook.cjs +80 -0
- package/dist/executor/cli-executor.js +8 -0
- package/dist/executor/executors/claude-code.js +780 -0
- package/dist/executor/executors/codex.js +719 -0
- package/dist/executor/executors/hermes-cli.js +855 -0
- package/dist/executor/executors/openclaw.js +467 -0
- package/dist/executor/executors/pi.js +514 -0
- package/dist/executor/index.js +269 -0
- package/dist/executor/jsonrpc-transport.js +125 -0
- package/dist/executor/process-runner.js +101 -0
- package/dist/executor/reasoning-status.js +83 -0
- package/dist/executor/repo-cache.js +502 -0
- package/dist/executor/session-store.js +188 -0
- package/dist/executor/worker-chat.js +257 -0
- package/dist/executor/worker-connection.js +89 -0
- package/dist/executor/worker-issue.js +264 -0
- package/dist/executor/worker.js +877 -0
- package/dist/link/pending-requests.js +72 -0
- package/dist/link/server.js +233 -0
- package/dist/link/visibility-store.js +58 -0
- package/dist/master/api/agents.js +333 -0
- package/dist/master/api/artifacts.js +271 -0
- package/dist/master/api/domains.js +64 -0
- package/dist/master/api/groups.js +635 -0
- package/dist/master/api/guidance-templates.js +147 -0
- package/dist/master/api/index.js +89 -0
- package/dist/master/api/issues-patrol.js +172 -0
- package/dist/master/api/issues.js +663 -0
- package/dist/master/api/links-patrol.js +168 -0
- package/dist/master/api/links.js +114 -0
- package/dist/master/api/memory.js +259 -0
- package/dist/master/api/messages.js +157 -0
- package/dist/master/api/notes.js +77 -0
- package/dist/master/api/schedule-patterns.js +133 -0
- package/dist/master/api/schedules.js +272 -0
- package/dist/master/api/sessions.js +158 -0
- package/dist/master/api/share.js +269 -0
- package/dist/master/api/skills.js +190 -0
- package/dist/master/api/teams.js +122 -0
- package/dist/master/api/uploads.js +245 -0
- package/dist/master/auth.js +134 -0
- package/dist/master/dashboard/animations/calico-dozing.apng +0 -0
- package/dist/master/dashboard/animations/calico-error.apng +0 -0
- package/dist/master/dashboard/animations/calico-happy.apng +0 -0
- package/dist/master/dashboard/animations/calico-notification.apng +0 -0
- package/dist/master/dashboard/animations/calico-sleeping.apng +0 -0
- package/dist/master/dashboard/animations/calico-thinking.apng +0 -0
- package/dist/master/dashboard/animations/calico-waking.apng +0 -0
- package/dist/master/dashboard/assets/ApprovalCard-C38VV6ko.css +1 -0
- package/dist/master/dashboard/assets/ApprovalCard-CHPh2dmE.js +17 -0
- package/dist/master/dashboard/assets/ArtifactPanel-P_2gAP7v.js +1 -0
- package/dist/master/dashboard/assets/ArtifactPanel-aGHySny5.css +1 -0
- package/dist/master/dashboard/assets/css.worker-DaIe3gwK.js +84 -0
- package/dist/master/dashboard/assets/editor.worker-BCzxt1at.js +12 -0
- package/dist/master/dashboard/assets/html.worker-CKrFyw_2.js +461 -0
- package/dist/master/dashboard/assets/index-CChrTn81.css +32 -0
- package/dist/master/dashboard/assets/index-Dhu4SN1z.js +181 -0
- package/dist/master/dashboard/assets/json.worker-B7c_PmGb.js +49 -0
- package/dist/master/dashboard/assets/markdown-CeN5IgdF.js +29 -0
- package/dist/master/dashboard/assets/monaco-core-DyX1CsEw.css +1 -0
- package/dist/master/dashboard/assets/monaco-core-oQiQUisy.js +833 -0
- package/dist/master/dashboard/assets/monaco-setup-CiOPQdmo.js +1 -0
- package/dist/master/dashboard/assets/react-vendor-C8IxlyCR.js +67 -0
- package/dist/master/dashboard/assets/ts.worker-BhkL8olL.js +51334 -0
- package/dist/master/dashboard/assets/useMonaco-ILb4vyPh.js +12 -0
- package/dist/master/dashboard/assets/vite-preload-CxJPbCTl.js +1 -0
- package/dist/master/dashboard/debug-auth.html +197 -0
- package/dist/master/dashboard/favicon.ico +0 -0
- package/dist/master/dashboard/index.html +20 -0
- package/dist/master/dashboard/rotom-avatar.png +0 -0
- package/dist/master/db/agent-sessions.js +60 -0
- package/dist/master/db/agent-visibility.js +64 -0
- package/dist/master/db/agents.js +119 -0
- package/dist/master/db/ask-bridges.js +157 -0
- package/dist/master/db/build-update.js +59 -0
- package/dist/master/db/core.js +82 -0
- package/dist/master/db/domains.js +80 -0
- package/dist/master/db/groups.js +316 -0
- package/dist/master/db/guidance-templates.js +58 -0
- package/dist/master/db/index.js +12 -0
- package/dist/master/db/internal.js +45 -0
- package/dist/master/db/issues-patrol.js +81 -0
- package/dist/master/db/issues.js +373 -0
- package/dist/master/db/links.js +221 -0
- package/dist/master/db/master-node.js +43 -0
- package/dist/master/db/memory.js +272 -0
- package/dist/master/db/messages.js +210 -0
- package/dist/master/db/notes.js +55 -0
- package/dist/master/db/schedule-patterns.js +56 -0
- package/dist/master/db/schedules.js +135 -0
- package/dist/master/db/skills.js +144 -0
- package/dist/master/db/team.js +88 -0
- package/dist/master/db/types.js +10 -0
- package/dist/master/db.js +12 -0
- package/dist/master/embedded.js +133 -0
- package/dist/master/federation/client.js +283 -0
- package/dist/master/federation/identity.js +133 -0
- package/dist/master/federation/manager.js +267 -0
- package/dist/master/federation/publisher.js +87 -0
- package/dist/master/federation/self-publisher.js +69 -0
- package/dist/master/federation/server.js +487 -0
- package/dist/master/group-paths.js +208 -0
- package/dist/master/offline-queue.js +38 -0
- package/dist/master/opc-bootstrap.js +245 -0
- package/dist/master/patrol-terminal.js +275 -0
- package/dist/master/repo-scan.js +188 -0
- package/dist/master/router.js +214 -0
- package/dist/master/scheduler-handlers.js +510 -0
- package/dist/master/scheduler.js +201 -0
- package/dist/master/server.js +203 -0
- package/dist/master/services/link-collector.js +82 -0
- package/dist/master/services/link-patrol-bootstrap.js +50 -0
- package/dist/master/services/memory-extract-prompt.js +34 -0
- package/dist/master/services/patrol-bootstrap.js +63 -0
- package/dist/master/share-tokens.js +56 -0
- package/dist/master/terminal-hub.js +300 -0
- package/dist/master/uploads.js +108 -0
- package/dist/master/util/fs.js +100 -0
- package/dist/master/util/paths.js +50 -0
- package/dist/master/util/persona.js +10 -0
- package/dist/master/ws-hub/connection.js +928 -0
- package/dist/master/ws-hub/conversation.js +290 -0
- package/dist/master/ws-hub/directory.js +70 -0
- package/dist/master/ws-hub/dispatch-enrich.js +34 -0
- package/dist/master/ws-hub/hub.js +136 -0
- package/dist/master/ws-hub/index.js +9 -0
- package/dist/master/ws-hub/internal.js +35 -0
- package/dist/master/ws-hub/routing.js +295 -0
- package/dist/master/ws-hub/sessions.js +130 -0
- package/dist/master/ws-hub.js +11 -0
- package/dist/shared/agent-profile.js +44 -0
- package/dist/shared/constants.js +55 -0
- package/dist/shared/dedup.js +33 -0
- package/dist/shared/group-context.js +62 -0
- package/dist/shared/json-codec.js +33 -0
- package/dist/shared/logger.js +136 -0
- package/dist/shared/mention.js +22 -0
- package/dist/shared/network.js +40 -0
- package/dist/shared/parse.js +18 -0
- package/dist/shared/prompt-composer.js +171 -0
- package/dist/shared/protocol/client-messages.js +8 -0
- package/dist/shared/protocol/enums.js +6 -0
- package/dist/shared/protocol/federation.js +62 -0
- package/dist/shared/protocol/guards.js +87 -0
- package/dist/shared/protocol/server-messages.js +8 -0
- package/dist/shared/protocol/types.js +8 -0
- package/dist/shared/protocol.js +19 -0
- package/dist/shared/readonly-allowlist.js +122 -0
- package/dist/shared/rotom-cli-prompt.js +23 -0
- package/dist/shared/skill-context.js +19 -0
- package/dist/shared/skill-md.js +43 -0
- package/dist/shared/slash-commands.js +50 -0
- package/dist/shared/time.js +80 -0
- package/dist/shared/title.js +46 -0
- package/dist/shared/url-extractor.js +99 -0
- package/migrations/001-schema.sql +942 -0
- package/package.json +68 -0
- package/scripts/fix-node-pty-perms.mjs +46 -0
- package/skill/rotom-a2a-communicate/SKILL.md +257 -0
- package/skill/rotom-bus-host/SKILL.md +78 -0
- package/skill/rotom-bus-host/scripts/poll-replies.sh +148 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* rotom — Mesh CLI for digital employees.
|
|
4
|
+
*
|
|
5
|
+
* Every invocation acts as a specific agent. Selection priority:
|
|
6
|
+
* 1. ROTOM_AGENT env
|
|
7
|
+
* 2. --as <name>
|
|
8
|
+
* 3. ~/.rotom/config.json#defaultAgent
|
|
9
|
+
* If none of those resolve, rotom refuses to run (so you never accidentally
|
|
10
|
+
* use the wrong agent's token on a multi-agent box).
|
|
11
|
+
*
|
|
12
|
+
* The agent's master URL + mesh token come from one of:
|
|
13
|
+
* - "openclaw": channels['a2a-gateway'].{master,token,name} in openclaw.json
|
|
14
|
+
* - "executor": matching `workers[].name` in executor.config.json
|
|
15
|
+
* - Auto-discovery: ~/.rotom/executor.config.json (shared with `executor`
|
|
16
|
+
* worker process). No explicit `rotom config add-executor` needed.
|
|
17
|
+
* - Env fallback: ROTOM_MASTER + ROTOM_TOKEN (set by worker child spawns).
|
|
18
|
+
*
|
|
19
|
+
* Output is JSON by default. `--pretty` switches to a human table where it
|
|
20
|
+
* makes sense; everywhere else it pretty-prints the same JSON.
|
|
21
|
+
*/
|
|
22
|
+
import { ensureRotomSkillMd } from "../shared/skill-md.js";
|
|
23
|
+
import { parseArgs, setPretty, resolveAgent, flagStr, fail, } from "./common.js";
|
|
24
|
+
import { cmdConfig } from "./config.js";
|
|
25
|
+
import { cmdWhoami } from "./identity.js";
|
|
26
|
+
import { cmdStatus } from "./identity.js";
|
|
27
|
+
import { cmdDirectory } from "./directory.js";
|
|
28
|
+
import { cmdGroup } from "./group.js";
|
|
29
|
+
import { cmdIssue } from "./issue.js";
|
|
30
|
+
import { cmdNote } from "./note.js";
|
|
31
|
+
import { cmdMemory } from "./memory.js";
|
|
32
|
+
import { cmdSkill } from "./skill.js";
|
|
33
|
+
import { cmdSchedule } from "./schedule.js";
|
|
34
|
+
import { cmdAsk } from "./ask.js";
|
|
35
|
+
import { cmdMaster, colonExpand } from "./master.js";
|
|
36
|
+
import { cmdExecutor } from "./executor.js";
|
|
37
|
+
import { cmdInit } from "./init.js";
|
|
38
|
+
import { cmdJoin } from "./join.js";
|
|
39
|
+
import { cmdRepo } from "./repo.js";
|
|
40
|
+
import { cmdRun } from "./run.js";
|
|
41
|
+
import { cmdTeam } from "./team.js";
|
|
42
|
+
import { cmdLink } from "./link.js";
|
|
43
|
+
import { cmdFed } from "./fed.js";
|
|
44
|
+
const HELP = `rotom — Mesh CLI
|
|
45
|
+
|
|
46
|
+
Usage: rotom [--as <agent>] [--pretty] <command> [args]
|
|
47
|
+
|
|
48
|
+
Agent selection:
|
|
49
|
+
--as <name> override which registered agent to act as
|
|
50
|
+
ROTOM_AGENT env same, via env (takes priority over --as)
|
|
51
|
+
defaultAgent (config) fallback
|
|
52
|
+
Auto-discovery: workers in ~/.rotom/executor.config.json resolve
|
|
53
|
+
by name without needing 'rotom config add-executor' first.
|
|
54
|
+
|
|
55
|
+
Config:
|
|
56
|
+
config show
|
|
57
|
+
config init
|
|
58
|
+
config use <name> set default agent
|
|
59
|
+
config add-openclaw <name> <openclaw.json> register an OpenClaw-hosted agent
|
|
60
|
+
config add-executor <name> <executor.json> register an executor worker
|
|
61
|
+
config remove <name>
|
|
62
|
+
|
|
63
|
+
Bootstrap (first-time setup):
|
|
64
|
+
init detect claude/codex/hermes, ask for
|
|
65
|
+
names + master IP, register agents,
|
|
66
|
+
and write ~/.rotom/executor.config.json
|
|
67
|
+
Flags:
|
|
68
|
+
--master <ip:port> skip prompt (default: 127.0.0.1:28800)
|
|
69
|
+
--domain <name> skip prompt (default: pick from master's existing
|
|
70
|
+
domains; falls back to "默认部门" or "default")
|
|
71
|
+
--name-prefix <p> default name = <p>-<tool> (default: $USER)
|
|
72
|
+
--tools <a,b,c> limit detection to a subset of claude,codex,hermes
|
|
73
|
+
--yes / -y accept all defaults, do not overwrite without confirm
|
|
74
|
+
--force overwrite existing executor.config.json without prompt
|
|
75
|
+
|
|
76
|
+
join <masterHost:port> --name <n> --domain <d> --cli-tool <claude|codex|hermes|openclaw>
|
|
77
|
+
[--working-dir PATH] [--profile-position P]
|
|
78
|
+
[--profile-bio B] [--force]
|
|
79
|
+
首次申请 token 落盘到 ~/.rotom/(本地
|
|
80
|
+
交互式 CLI 作为 mesh host 用,不起
|
|
81
|
+
executor daemon)。一个机器一个 CLI 一个
|
|
82
|
+
agent:每次换 CLI 用不同 --name + --cli-tool。
|
|
83
|
+
--cli-tool 缺省时按 PATH 自动探测。落盘结构
|
|
84
|
+
对齐 executor.config.json workers[](含
|
|
85
|
+
cliTool/workingDir/profile),master 侧不存
|
|
86
|
+
cliTool(REST-only,不维持 WS)。
|
|
87
|
+
|
|
88
|
+
Identity:
|
|
89
|
+
whoami
|
|
90
|
+
status master health check (no agent needed)
|
|
91
|
+
|
|
92
|
+
Read:
|
|
93
|
+
directory [--online] [--domain D]
|
|
94
|
+
group create <title> --agents <a,b[,c...]> [--message M] [--note D|--note-file F] [--cwd PATH] [--no-template] [--a2a-direct]
|
|
95
|
+
一键建群+拉人。默认加载"群内讨论方案设计"
|
|
96
|
+
guidance 模板(可 --no-template 跳过)。
|
|
97
|
+
预检 --agents 名字都已注册,未注册 → fail 不建群。
|
|
98
|
+
--a2a-direct 建单播群(unicast):≥2 成员,
|
|
99
|
+
消息只入库、不广播,worker 不被
|
|
100
|
+
自动唤醒;只在 CLI --need-reply
|
|
101
|
+
显式点名时叫醒对方回话,每轮
|
|
102
|
+
A 发 → B 回 → 停。
|
|
103
|
+
group list
|
|
104
|
+
group members <groupId>
|
|
105
|
+
group history <groupId> [--limit N]
|
|
106
|
+
group new-messages <groupId> --since <ISO> 只看某个时间点之后的新消息(轮询用)
|
|
107
|
+
group archive <groupId>
|
|
108
|
+
group unarchive <groupId>
|
|
109
|
+
issue list <groupId> [--status S] [--type task]
|
|
110
|
+
issue show <issueId>
|
|
111
|
+
issue events <issueId>
|
|
112
|
+
issue messages <issueId>
|
|
113
|
+
issue comment <issueId> --message M [--reply-to <eventId>]
|
|
114
|
+
|
|
115
|
+
Send:
|
|
116
|
+
group send <groupId> <target> <message...> [--no-dispatch] [--need-reply]
|
|
117
|
+
--no-dispatch 只入库+广播,不 trigger target 的 worker(同步信息用)
|
|
118
|
+
--need-reply 自动补 @target,master 硬剥回复里的 @asker 防回触发(一问一答)
|
|
119
|
+
|
|
120
|
+
Issue:
|
|
121
|
+
issue create <groupId> --description D [--title T] [--priority low|medium|high|critical]
|
|
122
|
+
[--assignee <agent>] [--approval-policy r_allow|rw_allow] [--run]
|
|
123
|
+
description 是主输入;title 可选,未传时由后端从前 40 字符自动截断生成。
|
|
124
|
+
description 以已注册的 slash command 开头时(如 "/plan ...")将以对应模式执行。
|
|
125
|
+
/plan:Claude 走 --permission-mode plan;Codex 注入 developerInstructions。
|
|
126
|
+
--assignee 创建后立即把 issue 指派给指定 agent(不会自动起跑)。
|
|
127
|
+
--approval-policy rw_allow(默认,读写都默认通过) / r_allow(读默认通过,写需人工审批)。
|
|
128
|
+
--run 创建+指派后立即派发执行;必须同时给 --assignee,且 agent 必须在线。
|
|
129
|
+
--run 的 prompt 直接用 --description;若只传 --title 则 fallback 到 title。
|
|
130
|
+
issue update <issueId> [--title T] [--description D] [--priority low|medium|high|critical]
|
|
131
|
+
[--assignee <agent> | --unassign] [--approval-policy r_allow|rw_allow]
|
|
132
|
+
[--status open|in_progress|completed|failed|cancelled]
|
|
133
|
+
局部更新 issue 字段。至少给一个 flag。
|
|
134
|
+
只传 --description 不传 --title 时,后端会重新截断 title 并重解析 slash command。
|
|
135
|
+
--assignee / --unassign 互斥。
|
|
136
|
+
--status 低层 setter,可任意切换(含 reopen cancelled→open),无状态机限制。
|
|
137
|
+
issue cancel <issueId>
|
|
138
|
+
issue delete <issueId>
|
|
139
|
+
|
|
140
|
+
Note (极简文字记录,纯 CRUD):
|
|
141
|
+
note list <groupId>
|
|
142
|
+
note show <noteId>
|
|
143
|
+
note create <groupId> --title T [--description D]
|
|
144
|
+
note update <noteId> [--title T] [--description D]
|
|
145
|
+
note delete <noteId>
|
|
146
|
+
|
|
147
|
+
Memory (记忆体系:note=纯人看 agent_visible=0;memory=agent 可见 agent_visible=1):
|
|
148
|
+
memory search <keyword> <groupId> [--scope group|global] [--category <c>]
|
|
149
|
+
memory list <groupId> [--scope group|global] [--type note|memory|all] [--category <c>] [--tags t1,t2]
|
|
150
|
+
memory get <memoryId>
|
|
151
|
+
memory add <groupId> --key K --value V --category C [--scope global] [--summary S] [--tags t1,t2]
|
|
152
|
+
[--visibility group|global|private] [--no-agent-visible] [--expires 7d]
|
|
153
|
+
memory update <id> [--value V] [--category C] [--visibility V] [--agent-visible|--no-agent-visible]
|
|
154
|
+
memory remove <id>
|
|
155
|
+
memory promote <id> --visibility global
|
|
156
|
+
memory pending <groupId> [--scope global]
|
|
157
|
+
memory approve <id>
|
|
158
|
+
memory reject <id>
|
|
159
|
+
memory stats <groupId> [--scope global]
|
|
160
|
+
|
|
161
|
+
Schedule (群内定时任务,master 端 30s tick 调度):
|
|
162
|
+
schedule list [--group <id>] [--pretty]
|
|
163
|
+
schedule show <id>
|
|
164
|
+
schedule add --group <id> --mode <agent|message> [--agent A] --prompt P
|
|
165
|
+
( --every <dur> | --in <dur> | --at <iso> )
|
|
166
|
+
[--name N] [--repeat N|0|∞] [--enabled true|false]
|
|
167
|
+
schedule update <id> [--every D] [--in D] [--at ISO] [--prompt T] [--name N]
|
|
168
|
+
[--mode agent|message] [--agent A]
|
|
169
|
+
[--repeat N|0|∞] [--enabled true|false]
|
|
170
|
+
schedule remove <id> (alias: delete)
|
|
171
|
+
schedule enable <id> | disable <id>
|
|
172
|
+
schedule trigger <id>
|
|
173
|
+
--every <dur> 例 30s / 5m / 2h / 1d interval 模式,>= 30s
|
|
174
|
+
--in <dur> 例 3m one-shot,相对当前时间
|
|
175
|
+
--at <iso> 例 2026-06-22T09:00 one-shot,绝对时间
|
|
176
|
+
--repeat N 最多跑 N 次后自动 disable;传 0 或 ∞ 表示不限次数
|
|
177
|
+
|
|
178
|
+
Ask (Agent A 提问 B + 5min 超时兜底 bridge,详见 docs/AGENT_ASK_REPLY_TIMER.md):
|
|
179
|
+
ask <groupId> <target> <question...> [--timeout 5m] [--escalate-to <真人>]
|
|
180
|
+
发问 + 建 bridge。系统自动 5min 超时:
|
|
181
|
+
- B @ A → master 正常 dispatch 给 A,timer 自动 cancel
|
|
182
|
+
- B 不 @ 回复 → 5min 后系统建 Issue 给 A,描述里复述回复
|
|
183
|
+
- 完全无回复 → 5min 后系统建 Issue 指示 A @ 真人求救
|
|
184
|
+
target 离线 → 不建 bridge,exit 2(提示 A 自己 @ 真人)
|
|
185
|
+
ask list --group <id> [--status pending|answered|timed_out|cancelled] [--pretty]
|
|
186
|
+
ask show <bridgeId>
|
|
187
|
+
ask cancel <bridgeId> A 主动 cancel(收到非@回复,自己判断是回复了)
|
|
188
|
+
|
|
189
|
+
Process lifecycle (local daemon control — do not require an agent):
|
|
190
|
+
run <opc|federation> [opts] 一站式启动 master + executor(等价 bin/rotom-up.sh start)
|
|
191
|
+
opc OPC 模式(默认,本机 master + 自动 spawn executor)
|
|
192
|
+
federation 协调 master(注入 ROTOM_MASTER_ROLE=coordination),作为 federation 中心节点
|
|
193
|
+
通用选项: --port N | --host A | --data D | --no-build | --dev
|
|
194
|
+
master <start|stop|restart|status> [--daemon] [--port N] [--host A] [--data D] [--dev]
|
|
195
|
+
master:start | master:stop | master:status | master:restart (alias)
|
|
196
|
+
executor [--config <path>] start executor workers (reads ~/.rotom/executor.config.json by default)
|
|
197
|
+
|
|
198
|
+
Repo cache (内置 git worktree 缓存,migration 051;本机 FS 操作,不需要 agent):
|
|
199
|
+
repo list 列出 ~/.rotom/repos/ 下所有 bare clone + worktree 数 + 磁盘占用
|
|
200
|
+
repo prune [--remove-orphans] 清理孤儿 worktree 元数据,可选删除无引用且 30 天未 fetch 的 bare clone
|
|
201
|
+
repo fetch <repo-id> 显式 git fetch --prune 某 bare clone
|
|
202
|
+
repo remove <repo-id> 删除 bare clone(要求无活跃 worktree)
|
|
203
|
+
|
|
204
|
+
Federation (跨 master 协作,不依赖 dashboard):
|
|
205
|
+
team join <coordEndpoint> [--team-name N] 本机 master 运行时加入远端协调 master 形成团队
|
|
206
|
+
coordEndpoint 形如 ws://192.168.1.5:28800
|
|
207
|
+
team leave 离开当前团队,切回 standalone
|
|
208
|
+
team list 已加入的团队列表(本机视角)
|
|
209
|
+
team members [--team-id <id>] 团队内可见 agent 列表(--team-id 缺省读 ~/.rotom/team.json)
|
|
210
|
+
|
|
211
|
+
link join <coordEndpoint> [--hostname N] 一次性:生成 masterId + 写 ~/.rotom/link.json(轻量客户端模式)
|
|
212
|
+
link start [--port N] 启动 rotom-link daemon(默认端口 28900),不起完整 master
|
|
213
|
+
link stop | restart | status | logs daemon 生命周期
|
|
214
|
+
(随后可 rotom fed members / rotom fed ask <ref> "...")
|
|
215
|
+
|
|
216
|
+
fed members 列出协调 master 同步来的可见 agent
|
|
217
|
+
fed ask <ref> "<question>" [--timeout 5m] 阻塞等回复(ref 形如 alice@hostB 或 alice)
|
|
218
|
+
|
|
219
|
+
Global flags:
|
|
220
|
+
--pretty format output for humans (tables / indented JSON)
|
|
221
|
+
`;
|
|
222
|
+
async function main() {
|
|
223
|
+
ensureRotomSkillMd();
|
|
224
|
+
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
225
|
+
setPretty(flags.pretty === true);
|
|
226
|
+
if (positional.length === 0 || flags.help === true || positional[0] === "help") {
|
|
227
|
+
process.stdout.write(HELP);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const cmd = positional[0];
|
|
231
|
+
const rest = positional.slice(1);
|
|
232
|
+
const asFlag = flagStr(flags, "as");
|
|
233
|
+
// Commands that don't need an agent
|
|
234
|
+
if (cmd === "config")
|
|
235
|
+
return cmdConfig(rest, flags);
|
|
236
|
+
if (cmd === "init")
|
|
237
|
+
return cmdInit(rest, flags);
|
|
238
|
+
if (cmd === "join")
|
|
239
|
+
return cmdJoin(rest, flags);
|
|
240
|
+
if (cmd === "team")
|
|
241
|
+
return cmdTeam(rest, flags);
|
|
242
|
+
if (cmd === "link")
|
|
243
|
+
return cmdLink(rest, flags);
|
|
244
|
+
if (cmd === "fed")
|
|
245
|
+
return cmdFed(rest, flags);
|
|
246
|
+
// Master / executor / status — no agent required
|
|
247
|
+
if (cmd === "master" || cmd === "master:start" || cmd === "master:stop" ||
|
|
248
|
+
cmd === "master:status" || cmd === "master:restart") {
|
|
249
|
+
return cmdMaster(colonExpand(cmd, rest), flags);
|
|
250
|
+
}
|
|
251
|
+
if (cmd === "executor") {
|
|
252
|
+
return cmdExecutor(rest, flags);
|
|
253
|
+
}
|
|
254
|
+
if (cmd === "repo") {
|
|
255
|
+
return cmdRepo(rest, flags);
|
|
256
|
+
}
|
|
257
|
+
if (cmd === "run") {
|
|
258
|
+
return cmdRun(rest);
|
|
259
|
+
}
|
|
260
|
+
if (cmd === "status") {
|
|
261
|
+
return cmdStatus(rest, flags);
|
|
262
|
+
}
|
|
263
|
+
const agent = resolveAgent(asFlag);
|
|
264
|
+
switch (cmd) {
|
|
265
|
+
case "whoami": return cmdWhoami(agent);
|
|
266
|
+
case "directory": return cmdDirectory(agent, flags);
|
|
267
|
+
case "group": return cmdGroup(agent, rest, flags);
|
|
268
|
+
case "issue": return cmdIssue(agent, rest, flags);
|
|
269
|
+
case "note": return cmdNote(agent, rest, flags);
|
|
270
|
+
case "memory": return cmdMemory(agent, rest, flags);
|
|
271
|
+
case "skill": return cmdSkill(agent, rest, flags);
|
|
272
|
+
case "schedule": return cmdSchedule(agent, rest, flags);
|
|
273
|
+
case "ask": return cmdAsk(agent, rest, flags);
|
|
274
|
+
default: fail(`unknown command: ${cmd}\nRun 'rotom help' for usage.`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
main().catch((e) => fail(e.message));
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI route / query-string / usage / fetch helpers.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the hand-rolled `${encodeURIComponent(gid)}` template strings,
|
|
5
|
+
* `URLSearchParams` + `?${qs.toString() ? \`?${qs}\` : ""}` boilerplate,
|
|
6
|
+
* `fail("usage: rotom <cmd> ...")` lines, and bootstrap fetches in
|
|
7
|
+
* `cli/init.ts` / `cli/join.ts` that bypassed `common.ts:api()` because they
|
|
8
|
+
* have no `ResolvedAgent` yet.
|
|
9
|
+
*
|
|
10
|
+
* Existing `common.ts` exports (`api`, `fail`, `masterHttpUrl`, ...) are the
|
|
11
|
+
* foundation — `routes.ts` builds on top, not around them.
|
|
12
|
+
*/
|
|
13
|
+
import { fail, masterHttpUrl } from "./common.js";
|
|
14
|
+
// ── Route builder ─────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Substitute `:name` placeholders in `template` with URL-encoded segments.
|
|
17
|
+
*
|
|
18
|
+
* route("/groups/:groupId/issues/:issueId", gid, iid)
|
|
19
|
+
* → "/groups/<encoded gid>/issues/<encoded iid>"
|
|
20
|
+
*
|
|
21
|
+
* The placeholder names are documentation only — segments are positional and
|
|
22
|
+
* substituted in order. Throws if the segment count doesn't match the
|
|
23
|
+
* placeholder count, so typos in the template surface at the call site.
|
|
24
|
+
*/
|
|
25
|
+
export function route(template, ...segs) {
|
|
26
|
+
const placeholders = template.match(/:[A-Za-z_][A-Za-z0-9_]*/g) ?? [];
|
|
27
|
+
if (placeholders.length !== segs.length) {
|
|
28
|
+
throw new Error(`route(${template}): expected ${placeholders.length} segment(s), got ${segs.length}`);
|
|
29
|
+
}
|
|
30
|
+
let out = template;
|
|
31
|
+
for (let i = 0; i < segs.length; i++) {
|
|
32
|
+
out = out.replace(placeholders[i], encodeURIComponent(segs[i]));
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
// ── Query-string builder ──────────────────────────────────────────────────
|
|
37
|
+
/**
|
|
38
|
+
* Build a query string from a params map. Returns either `""` (no params) or
|
|
39
|
+
* `"?k=v&k2=v2"`. Skips `undefined` / empty-string values, coerces numbers to
|
|
40
|
+
* string, URL-encodes both keys and values.
|
|
41
|
+
*
|
|
42
|
+
* qs({ status: "open", limit: 50, type: undefined })
|
|
43
|
+
* → "?status=open&limit=50"
|
|
44
|
+
*/
|
|
45
|
+
export function qs(params) {
|
|
46
|
+
const pairs = [];
|
|
47
|
+
for (const [k, v] of Object.entries(params)) {
|
|
48
|
+
if (v === undefined || v === null || v === "")
|
|
49
|
+
continue;
|
|
50
|
+
pairs.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
|
|
51
|
+
}
|
|
52
|
+
return pairs.length === 0 ? "" : `?${pairs.join("&")}`;
|
|
53
|
+
}
|
|
54
|
+
// ── Usage helpers ─────────────────────────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Print `usage: rotom <cmd> <hint>` to stderr and exit 1. `hint` is the
|
|
57
|
+
* literal post-`rotom ` text — e.g. `usage("ask list", "rotom ask list --group <id>")`
|
|
58
|
+
* emits `usage: rotom ask list --group <id>` (matching the existing
|
|
59
|
+
* `fail("usage: rotom ...")` strings, so output stays byte-identical).
|
|
60
|
+
*/
|
|
61
|
+
export function usage(cmd, hint) {
|
|
62
|
+
const hintStr = hint ? ` ${hint}` : "";
|
|
63
|
+
fail(`usage: rotom ${cmd}${hintStr}`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Print `unknown <cmd> subcommand: <sub | "(none)">` to stderr and exit 1.
|
|
67
|
+
* Used at the tail of every CLI command's subcommand dispatch.
|
|
68
|
+
*/
|
|
69
|
+
export function unknownSubcommand(cmd, sub) {
|
|
70
|
+
fail(`unknown ${cmd} subcommand: ${sub || "(none)"}`);
|
|
71
|
+
}
|
|
72
|
+
// ── Master URL helpers ─────────────────────────────────────────────────────
|
|
73
|
+
/**
|
|
74
|
+
* Build a `ws://` (or `wss://`) URL from a master spec that may be either
|
|
75
|
+
* `http://host:port` or `ws://host:port`. The inverse of `masterHttpUrl`.
|
|
76
|
+
* Strips trailing slashes for consistency.
|
|
77
|
+
*/
|
|
78
|
+
export function masterWsUrl(masterHttpOrWs) {
|
|
79
|
+
return masterHttpOrWs
|
|
80
|
+
.replace(/^http:\/\//, "ws://")
|
|
81
|
+
.replace(/^https:\/\//, "wss://")
|
|
82
|
+
.replace(/\/+$/, "");
|
|
83
|
+
}
|
|
84
|
+
/** Build `http://host:port` from raw `host` + `port`. */
|
|
85
|
+
export function masterHttpBase(host, port) {
|
|
86
|
+
return `http://${host}:${port}`;
|
|
87
|
+
}
|
|
88
|
+
/** Build `ws://host:port` from raw `host` + `port`. */
|
|
89
|
+
export function masterWsBase(host, port) {
|
|
90
|
+
return `ws://${host}:${port}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Fetch a master URL without an auth token — for bootstrap paths (init, join)
|
|
94
|
+
* that don't yet have a `ResolvedAgent`. Always sets `Content-Type:
|
|
95
|
+
* application/json` and parses the response as JSON (falling back to the raw
|
|
96
|
+
* text if the body isn't valid JSON). Does NOT retry — callers decide how to
|
|
97
|
+
* handle non-2xx and network errors.
|
|
98
|
+
*/
|
|
99
|
+
export async function masterFetch(url, init) {
|
|
100
|
+
const merged = {
|
|
101
|
+
...init,
|
|
102
|
+
headers: {
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
...(init?.headers ?? {}),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const resp = await fetch(url, merged);
|
|
108
|
+
const text = await resp.text();
|
|
109
|
+
let data;
|
|
110
|
+
try {
|
|
111
|
+
data = text ? JSON.parse(text) : null;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
data = text;
|
|
115
|
+
}
|
|
116
|
+
return { status: resp.status, data };
|
|
117
|
+
}
|
|
118
|
+
export { masterHttpUrl };
|
package/dist/cli/run.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rotom run — 一站式启动入口(等价 bin/rotom-up.sh start,可选 federation 角色)。
|
|
3
|
+
*
|
|
4
|
+
* rotom run opc 启动 master + 自动 spawn executor(OPC 模式,默认)
|
|
5
|
+
* rotom run federation 启动协调 master + 自动 spawn executor(federation 中心节点)
|
|
6
|
+
*
|
|
7
|
+
* 与 `pnpm start` 的区别:走 CLI 统一入口,且 federation 子命令会自动注入
|
|
8
|
+
* ROTOM_MASTER_ROLE=coordination,免去手写环境变量。
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import { fail, installRoot } from "./common.js";
|
|
14
|
+
function runRotomUp(args, env) {
|
|
15
|
+
const scriptPath = path.join(installRoot(), "bin/rotom-up.sh");
|
|
16
|
+
if (!fs.existsSync(scriptPath)) {
|
|
17
|
+
fail(`shell script not found: ${scriptPath}`);
|
|
18
|
+
}
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const child = spawn("bash", [scriptPath, ...args], {
|
|
21
|
+
stdio: "inherit",
|
|
22
|
+
env: { ...process.env, ...env },
|
|
23
|
+
});
|
|
24
|
+
child.on("error", reject);
|
|
25
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export async function cmdRun(rest) {
|
|
29
|
+
const sub = rest[0];
|
|
30
|
+
const passthrough = rest.slice(1);
|
|
31
|
+
let env = {};
|
|
32
|
+
switch (sub) {
|
|
33
|
+
case "opc":
|
|
34
|
+
break;
|
|
35
|
+
case "federation":
|
|
36
|
+
env.ROTOM_MASTER_ROLE = "coordination";
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
fail("usage: rotom run <opc|federation> [--port N] [--host A] [--data D] [--no-build] [--dev]\n" +
|
|
40
|
+
" opc 启动 master + executor(OPC 模式,默认)\n" +
|
|
41
|
+
" federation 启动协调 master + executor(ROTOM_MASTER_ROLE=coordination)");
|
|
42
|
+
}
|
|
43
|
+
const code = await runRotomUp(passthrough, env);
|
|
44
|
+
process.exit(code);
|
|
45
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { toBeijing } from "../shared/time.js";
|
|
2
|
+
/**
|
|
3
|
+
* rotom schedule — schedule list/show/add/update/remove/enable/disable/trigger.
|
|
4
|
+
*/
|
|
5
|
+
import { api, printJson, printTable, fail, flagStr, requireFlag, pretty, } from "./common.js";
|
|
6
|
+
import { route, qs, usage } from "./routes.js";
|
|
7
|
+
function parseDuration(input) {
|
|
8
|
+
const s = String(input).trim();
|
|
9
|
+
const m = s.match(/^(\d+)\s*(s|sec|m|min|h|hr|d|day)?$/i);
|
|
10
|
+
if (!m) {
|
|
11
|
+
const n = Number(s);
|
|
12
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
13
|
+
}
|
|
14
|
+
const n = parseInt(m[1], 10);
|
|
15
|
+
const unit = (m[2] || "s").toLowerCase();
|
|
16
|
+
switch (unit) {
|
|
17
|
+
case "s":
|
|
18
|
+
case "sec": return n;
|
|
19
|
+
case "m":
|
|
20
|
+
case "min": return n * 60;
|
|
21
|
+
case "h":
|
|
22
|
+
case "hr": return n * 3600;
|
|
23
|
+
case "d":
|
|
24
|
+
case "day": return n * 86400;
|
|
25
|
+
default: return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function formatNextRun(ms) {
|
|
29
|
+
if (!ms)
|
|
30
|
+
return "-";
|
|
31
|
+
try {
|
|
32
|
+
return toBeijing(ms);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return String(ms);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function cmdSchedule(agent, rest, flags) {
|
|
39
|
+
const sub = rest[0];
|
|
40
|
+
if (sub === "list") {
|
|
41
|
+
const gid = flagStr(flags, "group");
|
|
42
|
+
const routePath = `${route("/schedules")}${qs({ group_id: gid })}`;
|
|
43
|
+
const data = await api(agent, "GET", routePath);
|
|
44
|
+
if (pretty) {
|
|
45
|
+
printTable(data.map((t) => ({
|
|
46
|
+
id: t.id,
|
|
47
|
+
name: t.name,
|
|
48
|
+
group: t.group_id.slice(0, 8),
|
|
49
|
+
mode: t.mode,
|
|
50
|
+
kind: t.schedule_kind,
|
|
51
|
+
every: t.interval_sec ? `${t.interval_sec}s` : "-",
|
|
52
|
+
enabled: t.enabled ? "yes" : "no",
|
|
53
|
+
repeat: t.repeat_times ?? "\u221E",
|
|
54
|
+
ran: t.repeat_count,
|
|
55
|
+
last_status: t.last_status ?? "-",
|
|
56
|
+
next_run_at: formatNextRun(t.next_run_at),
|
|
57
|
+
})), ["id", "name", "group", "mode", "kind", "every", "enabled", "repeat", "ran", "last_status", "next_run_at"]);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
printJson(data);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (sub === "show") {
|
|
65
|
+
const id = rest[1];
|
|
66
|
+
if (!id)
|
|
67
|
+
usage("schedule show", "<id>");
|
|
68
|
+
const data = await api(agent, "GET", route("/schedules/:id", id));
|
|
69
|
+
printJson(data);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (sub === "add") {
|
|
73
|
+
const groupId = requireFlag(flags, "group");
|
|
74
|
+
const modeRaw = flagStr(flags, "mode") || "agent";
|
|
75
|
+
if (modeRaw !== "agent" && modeRaw !== "message") {
|
|
76
|
+
fail(`--mode must be "agent" or "message" (got: ${modeRaw})`);
|
|
77
|
+
}
|
|
78
|
+
const mode = modeRaw;
|
|
79
|
+
const agentName = flagStr(flags, "agent");
|
|
80
|
+
if (mode === "agent" && !agentName) {
|
|
81
|
+
fail("--agent is required when --mode agent");
|
|
82
|
+
}
|
|
83
|
+
const prompt = requireFlag(flags, "prompt");
|
|
84
|
+
const name = flagStr(flags, "name") || `schedule-${Date.now()}`;
|
|
85
|
+
const repeatRaw = flagStr(flags, "repeat");
|
|
86
|
+
const enabledRaw = flagStr(flags, "enabled");
|
|
87
|
+
const everyRaw = flagStr(flags, "every");
|
|
88
|
+
const inRaw = flagStr(flags, "in");
|
|
89
|
+
const atRaw = flagStr(flags, "at");
|
|
90
|
+
const presentSchedule = [everyRaw, inRaw, atRaw].filter(Boolean).length;
|
|
91
|
+
if (presentSchedule !== 1) {
|
|
92
|
+
fail("exactly one of --every / --in / --at is required\n" +
|
|
93
|
+
" --every <dur> e.g. 30s, 5m, 2h, 1d (interval schedule)\n" +
|
|
94
|
+
" --in <dur> e.g. 3m (one-shot, relative)\n" +
|
|
95
|
+
" --at <iso-time> e.g. 2026-06-22T09:00 (one-shot, absolute)");
|
|
96
|
+
}
|
|
97
|
+
const body = { name, group_id: groupId, mode, prompt };
|
|
98
|
+
if (mode === "agent" && agentName)
|
|
99
|
+
body.agent_name = agentName;
|
|
100
|
+
if (enabledRaw !== undefined)
|
|
101
|
+
body.enabled = enabledRaw !== "false" && enabledRaw !== "0";
|
|
102
|
+
if (everyRaw !== undefined) {
|
|
103
|
+
const sec = parseDuration(everyRaw);
|
|
104
|
+
if (sec === null || sec < 30)
|
|
105
|
+
fail(`--every must parse to >= 30s (got: ${everyRaw})`);
|
|
106
|
+
body.schedule_kind = "interval";
|
|
107
|
+
body.interval_sec = sec;
|
|
108
|
+
}
|
|
109
|
+
else if (inRaw !== undefined) {
|
|
110
|
+
const sec = parseDuration(inRaw);
|
|
111
|
+
if (sec === null || sec <= 0)
|
|
112
|
+
fail(`--in must parse to a positive duration (got: ${inRaw})`);
|
|
113
|
+
body.schedule_kind = "once";
|
|
114
|
+
body.run_at = Date.now() + sec * 1000;
|
|
115
|
+
}
|
|
116
|
+
else if (atRaw !== undefined) {
|
|
117
|
+
const ms = Date.parse(atRaw);
|
|
118
|
+
if (!Number.isFinite(ms) || ms <= Date.now()) {
|
|
119
|
+
fail(`--at must be a valid ISO datetime in the future (got: ${atRaw})`);
|
|
120
|
+
}
|
|
121
|
+
body.schedule_kind = "once";
|
|
122
|
+
body.run_at = ms;
|
|
123
|
+
}
|
|
124
|
+
if (repeatRaw !== undefined) {
|
|
125
|
+
if (repeatRaw === "0" || repeatRaw === "\u221E" || repeatRaw.toLowerCase() === "infinite") {
|
|
126
|
+
body.repeat_times = null;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const n = parseInt(repeatRaw, 10);
|
|
130
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
131
|
+
fail(`--repeat must be a positive integer, 0, or \u221E (got: ${repeatRaw})`);
|
|
132
|
+
body.repeat_times = n;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const created = await api(agent, "POST", "/schedules", body);
|
|
136
|
+
printJson(created);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (sub === "update") {
|
|
140
|
+
const id = rest[1];
|
|
141
|
+
if (!id)
|
|
142
|
+
usage("schedule update", "<id> [--every D] [--in D] [--at ISO] [--prompt T] [--name N] [--mode agent|message] [--agent A] [--repeat N] [--enabled true|false]");
|
|
143
|
+
const body = {};
|
|
144
|
+
const name = flagStr(flags, "name");
|
|
145
|
+
if (name !== undefined)
|
|
146
|
+
body.name = name;
|
|
147
|
+
const prompt = flagStr(flags, "prompt");
|
|
148
|
+
if (prompt !== undefined)
|
|
149
|
+
body.prompt = prompt;
|
|
150
|
+
const modeRaw = flagStr(flags, "mode");
|
|
151
|
+
if (modeRaw !== undefined) {
|
|
152
|
+
if (modeRaw !== "agent" && modeRaw !== "message")
|
|
153
|
+
fail(`--mode must be "agent" or "message"`);
|
|
154
|
+
body.mode = modeRaw;
|
|
155
|
+
}
|
|
156
|
+
const agentName = flagStr(flags, "agent");
|
|
157
|
+
if (agentName !== undefined)
|
|
158
|
+
body.agent_name = agentName;
|
|
159
|
+
const repeatRaw = flagStr(flags, "repeat");
|
|
160
|
+
if (repeatRaw !== undefined) {
|
|
161
|
+
if (repeatRaw === "0" || repeatRaw === "\u221E" || repeatRaw.toLowerCase() === "infinite") {
|
|
162
|
+
body.repeat_times = null;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const n = parseInt(repeatRaw, 10);
|
|
166
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
167
|
+
fail("--repeat must be a positive integer, 0, or \u221E");
|
|
168
|
+
body.repeat_times = n;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const enabledRaw = flagStr(flags, "enabled");
|
|
172
|
+
if (enabledRaw !== undefined)
|
|
173
|
+
body.enabled = enabledRaw !== "false" && enabledRaw !== "0";
|
|
174
|
+
const everyRaw = flagStr(flags, "every");
|
|
175
|
+
const inRaw = flagStr(flags, "in");
|
|
176
|
+
const atRaw = flagStr(flags, "at");
|
|
177
|
+
if (everyRaw !== undefined) {
|
|
178
|
+
const sec = parseDuration(everyRaw);
|
|
179
|
+
if (sec === null || sec < 30)
|
|
180
|
+
fail("--every must parse to >= 30s");
|
|
181
|
+
body.schedule_kind = "interval";
|
|
182
|
+
body.interval_sec = sec;
|
|
183
|
+
}
|
|
184
|
+
else if (inRaw !== undefined) {
|
|
185
|
+
const sec = parseDuration(inRaw);
|
|
186
|
+
if (sec === null || sec <= 0)
|
|
187
|
+
fail("--in must parse to a positive duration");
|
|
188
|
+
body.schedule_kind = "once";
|
|
189
|
+
body.run_at = Date.now() + sec * 1000;
|
|
190
|
+
}
|
|
191
|
+
else if (atRaw !== undefined) {
|
|
192
|
+
const ms = Date.parse(atRaw);
|
|
193
|
+
if (!Number.isFinite(ms) || ms <= Date.now())
|
|
194
|
+
fail("--at must be a valid ISO datetime in the future");
|
|
195
|
+
body.schedule_kind = "once";
|
|
196
|
+
body.run_at = ms;
|
|
197
|
+
}
|
|
198
|
+
if (Object.keys(body).length === 0) {
|
|
199
|
+
fail("no fields to update — pass at least one of --every / --in / --at / --prompt / --name / --mode / --agent / --repeat / --enabled");
|
|
200
|
+
}
|
|
201
|
+
const data = await api(agent, "PATCH", route("/schedules/:id", id), body);
|
|
202
|
+
printJson(data);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (sub === "remove" || sub === "delete") {
|
|
206
|
+
const id = rest[1];
|
|
207
|
+
if (!id)
|
|
208
|
+
usage("schedule remove", "<id>");
|
|
209
|
+
const data = await api(agent, "DELETE", route("/schedules/:id", id));
|
|
210
|
+
printJson(data);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (sub === "enable" || sub === "disable") {
|
|
214
|
+
const id = rest[1];
|
|
215
|
+
if (!id)
|
|
216
|
+
usage(`schedule ${sub}`, "<id>");
|
|
217
|
+
const data = await api(agent, "PATCH", route("/schedules/:id", id), { enabled: sub === "enable" });
|
|
218
|
+
printJson(data);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (sub === "trigger") {
|
|
222
|
+
const id = rest[1];
|
|
223
|
+
if (!id)
|
|
224
|
+
usage("schedule trigger", "<id>");
|
|
225
|
+
const data = await api(agent, "POST", route("/schedules/:id/trigger", id));
|
|
226
|
+
printJson(data);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
usage("schedule", "<list|show|add|update|remove|enable|disable|trigger> [...]\n" +
|
|
230
|
+
" list [--group <id>] [--pretty]\n" +
|
|
231
|
+
" show <id>\n" +
|
|
232
|
+
" add --group <id> --mode <agent|message> [--agent A] --prompt P\n" +
|
|
233
|
+
" ( --every <dur> | --in <dur> | --at <iso> ) [--name N] [--repeat N] [--enabled true|false]\n" +
|
|
234
|
+
" update <id> [--every D] [--in D] [--at ISO] [--prompt T] [--name N] [--mode M] [--agent A]\n" +
|
|
235
|
+
" [--repeat N] [--enabled true|false]\n" +
|
|
236
|
+
" remove <id> | enable <id> | disable <id> | trigger <id>");
|
|
237
|
+
}
|