@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,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rotom skill — 全局 skill 知识库 + (group, agent, skill) 绑定关系管理。
|
|
3
|
+
*
|
|
4
|
+
* skill 是全局能力资产,本身无可见性。可见性靠绑定:某群的某 agent 持有某 skill。
|
|
5
|
+
* `rotom skill mine <groupId>` 查当前 agent 在该群绑定的 skill。
|
|
6
|
+
*/
|
|
7
|
+
import { api, printJson, printTable, fail, flagStr, requireFlag, } from "./common.js";
|
|
8
|
+
import { route, qs, usage } from "./routes.js";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
function fmtPreview(s, len = 60) {
|
|
11
|
+
if (!s)
|
|
12
|
+
return "";
|
|
13
|
+
const flat = s.replace(/\n/g, " ");
|
|
14
|
+
return flat.length > len ? flat.slice(0, len) + "…" : flat;
|
|
15
|
+
}
|
|
16
|
+
/** --content 'foo' 或 --content @path/to/file.md。 */
|
|
17
|
+
function resolveContent(v) {
|
|
18
|
+
if (v.startsWith("@")) {
|
|
19
|
+
try {
|
|
20
|
+
return readFileSync(v.slice(1), "utf-8");
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
fail(`读取 --content ${v} 失败:${e.message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return v;
|
|
27
|
+
}
|
|
28
|
+
export async function cmdSkill(agent, rest, flags) {
|
|
29
|
+
const sub = rest[0];
|
|
30
|
+
// ── list(全局 skill 索引)─────────────────────────────────────────────
|
|
31
|
+
if (sub === "list") {
|
|
32
|
+
const category = flagStr(flags, "category");
|
|
33
|
+
const url = `${route("/skills")}${qs({ category })}`;
|
|
34
|
+
const data = await api(agent, "GET", url);
|
|
35
|
+
printTable(data.map((s) => ({
|
|
36
|
+
name: s.name,
|
|
37
|
+
category: s.category ?? "",
|
|
38
|
+
description: fmtPreview(s.description, 50),
|
|
39
|
+
views: s.view_count,
|
|
40
|
+
})), ["name", "category", "description", "views"]);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// ── search ───────────────────────────────────────────────────────────
|
|
44
|
+
if (sub === "search") {
|
|
45
|
+
const keyword = rest[1];
|
|
46
|
+
if (!keyword)
|
|
47
|
+
usage("skill search", "<keyword>");
|
|
48
|
+
const data = await api(agent, "GET", `${route("/skills/search")}${qs({ q: keyword })}`);
|
|
49
|
+
printTable(data.map((s) => ({
|
|
50
|
+
name: s.name,
|
|
51
|
+
category: s.category ?? "",
|
|
52
|
+
description: fmtPreview(s.description, 50),
|
|
53
|
+
})), ["name", "category", "description"]);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// ── get(看全文,view_count+1)─────────────────────────────────────────
|
|
57
|
+
if (sub === "get") {
|
|
58
|
+
const name = rest[1];
|
|
59
|
+
if (!name)
|
|
60
|
+
usage("skill get", "<name>");
|
|
61
|
+
const data = await api(agent, "GET", route("/skills/:name", name));
|
|
62
|
+
printJson(data);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// ── create ───────────────────────────────────────────────────────────
|
|
66
|
+
if (sub === "create") {
|
|
67
|
+
const name = requireFlag(flags, "name");
|
|
68
|
+
const description = requireFlag(flags, "description");
|
|
69
|
+
const contentRaw = requireFlag(flags, "content");
|
|
70
|
+
const content = resolveContent(contentRaw);
|
|
71
|
+
const category = flagStr(flags, "category");
|
|
72
|
+
if (!name || !description || !content)
|
|
73
|
+
fail("--name, --description, --content 都必填");
|
|
74
|
+
const body = { name: name.trim(), description, content, createdBy: agent.name };
|
|
75
|
+
if (category)
|
|
76
|
+
body.category = category;
|
|
77
|
+
const data = await api(agent, "POST", "/skills", body);
|
|
78
|
+
printJson(data);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// ── update ───────────────────────────────────────────────────────────
|
|
82
|
+
if (sub === "update") {
|
|
83
|
+
const name = rest[1];
|
|
84
|
+
if (!name)
|
|
85
|
+
usage("skill update", "<name> [--description D] [--content C|@file] [--category C]");
|
|
86
|
+
const body = {};
|
|
87
|
+
const description = flagStr(flags, "description");
|
|
88
|
+
const contentRaw = flagStr(flags, "content");
|
|
89
|
+
const category = flagStr(flags, "category");
|
|
90
|
+
if (description !== undefined)
|
|
91
|
+
body.description = description;
|
|
92
|
+
if (contentRaw !== undefined)
|
|
93
|
+
body.content = resolveContent(contentRaw);
|
|
94
|
+
if (category !== undefined)
|
|
95
|
+
body.category = category;
|
|
96
|
+
if (Object.keys(body).length === 0)
|
|
97
|
+
fail("至少传一个 --description / --content / --category");
|
|
98
|
+
await api(agent, "PATCH", route("/skills/:name", name), body);
|
|
99
|
+
printJson({ ok: true });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// ── remove ──────────────────────────────────────────────────────────
|
|
103
|
+
if (sub === "remove") {
|
|
104
|
+
const name = rest[1];
|
|
105
|
+
if (!name)
|
|
106
|
+
usage("skill remove", "<name>");
|
|
107
|
+
await api(agent, "DELETE", route("/skills/:name", name));
|
|
108
|
+
printJson({ ok: true });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// ── bind / unbind ───────────────────────────────────────────────────
|
|
112
|
+
if (sub === "bind") {
|
|
113
|
+
const groupId = rest[1];
|
|
114
|
+
const agentName = rest[2];
|
|
115
|
+
const skillName = rest[3];
|
|
116
|
+
if (!groupId || !agentName || !skillName)
|
|
117
|
+
usage("skill bind", "<groupId> <agentName> <skillName>");
|
|
118
|
+
await api(agent, "POST", route("/groups/:groupId/skills/:agent/bind", groupId, agentName), {
|
|
119
|
+
skillName,
|
|
120
|
+
});
|
|
121
|
+
printJson({ ok: true });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (sub === "unbind") {
|
|
125
|
+
const groupId = rest[1];
|
|
126
|
+
const agentName = rest[2];
|
|
127
|
+
const skillName = rest[3];
|
|
128
|
+
if (!groupId || !agentName || !skillName)
|
|
129
|
+
usage("skill unbind", "<groupId> <agentName> <skillName>");
|
|
130
|
+
await api(agent, "DELETE", route("/groups/:groupId/skills/:agent/bind/:skill", groupId, agentName, skillName));
|
|
131
|
+
printJson({ ok: true });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// ── bindings(查绑定关系)───────────────────────────────────────────
|
|
135
|
+
if (sub === "bindings") {
|
|
136
|
+
const groupId = rest[1];
|
|
137
|
+
const agentName = rest[2];
|
|
138
|
+
const url = `${route("/skills/bindings/all")}${qs({ groupId, agentName })}`;
|
|
139
|
+
const data = await api(agent, "GET", url);
|
|
140
|
+
printTable(data.map((b) => ({
|
|
141
|
+
group: b.group_id?.slice(0, 8) ?? "",
|
|
142
|
+
agent: b.agent_name,
|
|
143
|
+
skill: b.skill_name ?? b.skill_id?.slice(0, 8) ?? "",
|
|
144
|
+
})), ["group", "agent", "skill"]);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// ── mine(当前 agent 在该群绑定的 skill)────────────────────────────
|
|
148
|
+
if (sub === "mine") {
|
|
149
|
+
const groupId = rest[1];
|
|
150
|
+
if (!groupId)
|
|
151
|
+
usage("skill mine", "<groupId>");
|
|
152
|
+
const data = await api(agent, "GET", route("/groups/:groupId/skills/:agent", groupId, agent.name));
|
|
153
|
+
printTable(data.map((s) => ({
|
|
154
|
+
name: s.name,
|
|
155
|
+
category: s.category ?? "",
|
|
156
|
+
description: fmtPreview(s.description, 50),
|
|
157
|
+
})), ["name", "category", "description"]);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
fail(`unknown skill subcommand: ${sub || "(none)"}
|
|
161
|
+
usage:
|
|
162
|
+
skill list [--category <c>]
|
|
163
|
+
skill search <keyword>
|
|
164
|
+
skill get <name>
|
|
165
|
+
skill create --name <n> --description <d> --content <c|@file> [--category <c>]
|
|
166
|
+
skill update <name> [--description <d>] [--content <c|@file>] [--category <c>]
|
|
167
|
+
skill remove <name>
|
|
168
|
+
skill bind <groupId> <agentName> <skillName>
|
|
169
|
+
skill unbind <groupId> <agentName> <skillName>
|
|
170
|
+
skill bindings [groupId] [agentName]
|
|
171
|
+
skill mine <groupId> # 当前 agent 在该群绑定的 skill
|
|
172
|
+
`);
|
|
173
|
+
}
|
package/dist/cli/team.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rotom team — Federation 团队 CLI(不依赖 dashboard)。
|
|
3
|
+
*
|
|
4
|
+
* 通过本机 master 的 REST API 操作 federation 状态:
|
|
5
|
+
* rotom team join <coordEndpoint> [--team-name <name>]
|
|
6
|
+
* rotom team leave
|
|
7
|
+
* rotom team list
|
|
8
|
+
* rotom team members [--team-id <id>]
|
|
9
|
+
*
|
|
10
|
+
* 不需要 agent 上下文(不带 token),走 masterFetch。
|
|
11
|
+
* 本机 master 必须先起来;若未起,fail 给清晰提示。
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { ROTOM_HOME, fail, flagStr, printJson, } from "./common.js";
|
|
16
|
+
import { masterFetch, route } from "./routes.js";
|
|
17
|
+
import { resolveLocalMasterUrl } from "./identity.js";
|
|
18
|
+
async function probeLocalMaster(httpBase) {
|
|
19
|
+
const probe = await masterFetch(`${httpBase}/api/identity`, { method: "GET" }).catch(() => null);
|
|
20
|
+
if (!probe || probe.status === 0) {
|
|
21
|
+
fail(`local master unreachable at ${httpBase}. ` +
|
|
22
|
+
`Start it first (e.g. \`rotom master start --daemon\` or \`rotom run opc\`).`);
|
|
23
|
+
}
|
|
24
|
+
if (probe.status >= 500) {
|
|
25
|
+
fail(`local master returned HTTP ${probe.status}: ${JSON.stringify(probe.data)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** 从 ~/.rotom/team.json 读当前 teamId(member 模式) */
|
|
29
|
+
function readLocalTeamId() {
|
|
30
|
+
const teamConfigPath = path.join(ROTOM_HOME, "team.json");
|
|
31
|
+
try {
|
|
32
|
+
const raw = JSON.parse(fs.readFileSync(teamConfigPath, "utf-8"));
|
|
33
|
+
if (raw?.id)
|
|
34
|
+
return raw.id;
|
|
35
|
+
}
|
|
36
|
+
catch { /* not joined yet */ }
|
|
37
|
+
fail(`no ~/.rotom/team.json found — run \`rotom team join <coordEndpoint>\` first, ` +
|
|
38
|
+
`or pass --team-id explicitly.`);
|
|
39
|
+
}
|
|
40
|
+
export async function cmdTeam(rest, flags) {
|
|
41
|
+
const sub = rest[0];
|
|
42
|
+
if (!sub) {
|
|
43
|
+
fail("usage: rotom team <join|leave|list|members> [args]\n" +
|
|
44
|
+
" join <coordEndpoint> [--team-name N]\n" +
|
|
45
|
+
" leave\n" +
|
|
46
|
+
" list\n" +
|
|
47
|
+
" members [--team-id ID]");
|
|
48
|
+
}
|
|
49
|
+
const httpBase = resolveLocalMasterUrl();
|
|
50
|
+
await probeLocalMaster(httpBase);
|
|
51
|
+
const args = rest.slice(1);
|
|
52
|
+
switch (sub) {
|
|
53
|
+
case "join": return cmdTeamJoin(httpBase, args, flags);
|
|
54
|
+
case "leave": return cmdTeamLeave(httpBase);
|
|
55
|
+
case "list": return cmdTeamList(httpBase);
|
|
56
|
+
case "members": return cmdTeamMembers(httpBase, args, flags);
|
|
57
|
+
default:
|
|
58
|
+
fail(`unknown team subcommand: ${sub}\nRun 'rotom team' for usage.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function cmdTeamJoin(httpBase, args, flags) {
|
|
62
|
+
const coordEndpoint = args[0];
|
|
63
|
+
if (!coordEndpoint) {
|
|
64
|
+
fail("usage: rotom team join <coordEndpoint> [--team-name <name>]\n coordEndpoint e.g. ws://192.168.1.5:28800");
|
|
65
|
+
}
|
|
66
|
+
const teamName = flagStr(flags, "team-name");
|
|
67
|
+
const body = { coordEndpoint };
|
|
68
|
+
if (teamName)
|
|
69
|
+
body.teamName = teamName;
|
|
70
|
+
const resp = await masterFetch(`${httpBase}/api/teams/join`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
body: JSON.stringify(body),
|
|
73
|
+
});
|
|
74
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
75
|
+
const err = resp.data?.error ?? JSON.stringify(resp.data);
|
|
76
|
+
fail(`team join failed (HTTP ${resp.status}): ${err}`);
|
|
77
|
+
}
|
|
78
|
+
printJson(resp.data);
|
|
79
|
+
}
|
|
80
|
+
async function cmdTeamLeave(httpBase) {
|
|
81
|
+
const resp = await masterFetch(`${httpBase}/api/teams/leave`, { method: "POST" });
|
|
82
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
83
|
+
const err = resp.data?.error ?? JSON.stringify(resp.data);
|
|
84
|
+
fail(`team leave failed (HTTP ${resp.status}): ${err}`);
|
|
85
|
+
}
|
|
86
|
+
printJson(resp.data);
|
|
87
|
+
}
|
|
88
|
+
async function cmdTeamList(httpBase) {
|
|
89
|
+
const resp = await masterFetch(`${httpBase}/api/teams`, { method: "GET" });
|
|
90
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
91
|
+
const err = resp.data?.error ?? JSON.stringify(resp.data);
|
|
92
|
+
fail(`team list failed (HTTP ${resp.status}): ${err}`);
|
|
93
|
+
}
|
|
94
|
+
printJson(resp.data);
|
|
95
|
+
}
|
|
96
|
+
async function cmdTeamMembers(httpBase, args, flags) {
|
|
97
|
+
const teamIdFlag = flagStr(flags, "team-id");
|
|
98
|
+
const teamId = teamIdFlag ?? readLocalTeamId();
|
|
99
|
+
const url = `${httpBase}${route("/api/teams/:id/members", teamId)}`;
|
|
100
|
+
const resp = await masterFetch(url, { method: "GET" });
|
|
101
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
102
|
+
const err = resp.data?.error ?? JSON.stringify(resp.data);
|
|
103
|
+
fail(`team members failed (HTTP ${resp.status}): ${err}`);
|
|
104
|
+
}
|
|
105
|
+
printJson(resp.data);
|
|
106
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code PreToolUse hook bridge.
|
|
4
|
+
*
|
|
5
|
+
* claude spawns this script (one process per tool call) and feeds it a JSON
|
|
6
|
+
* payload like:
|
|
7
|
+
* { "hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": { "command": "rm -rf …" } }
|
|
8
|
+
* on stdin. The script forwards the payload to the unix-domain-socket server
|
|
9
|
+
* the ClaudeCodeExecutor opened for this run, waits (possibly for minutes)
|
|
10
|
+
* for the user's verdict, and prints the claude-shaped response JSON to
|
|
11
|
+
* stdout:
|
|
12
|
+
* { "hookSpecificOutput": { "hookEventName": "PreToolUse",
|
|
13
|
+
* "permissionDecision": "allow" | "deny",
|
|
14
|
+
* "permissionDecisionReason": "…" } }
|
|
15
|
+
*
|
|
16
|
+
* The socket path travels via the ROTOM_APPROVAL_SOCKET env var the executor
|
|
17
|
+
* sets when spawning claude. A shared-secret token in ROTOM_APPROVAL_TOKEN
|
|
18
|
+
* keeps stray local processes from injecting decisions.
|
|
19
|
+
*
|
|
20
|
+
* Fail-closed: if the socket is unreachable we deny the call. The executor
|
|
21
|
+
* is the one place that should ever auto-allow, and it does that by simply
|
|
22
|
+
* not configuring the hook (no settings.json, no socket).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
"use strict";
|
|
26
|
+
|
|
27
|
+
const http = require("node:http");
|
|
28
|
+
|
|
29
|
+
const socketPath = process.env.ROTOM_APPROVAL_SOCKET || "";
|
|
30
|
+
const token = process.env.ROTOM_APPROVAL_TOKEN || "";
|
|
31
|
+
|
|
32
|
+
function emitDeny(reason) {
|
|
33
|
+
process.stdout.write(JSON.stringify({
|
|
34
|
+
hookSpecificOutput: {
|
|
35
|
+
hookEventName: "PreToolUse",
|
|
36
|
+
permissionDecision: "deny",
|
|
37
|
+
permissionDecisionReason: reason,
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!socketPath) {
|
|
44
|
+
emitDeny("rotom approval bridge: ROTOM_APPROVAL_SOCKET not set");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let raw = "";
|
|
48
|
+
process.stdin.setEncoding("utf8");
|
|
49
|
+
process.stdin.on("data", (chunk) => { raw += chunk; });
|
|
50
|
+
process.stdin.on("end", () => {
|
|
51
|
+
const req = http.request({
|
|
52
|
+
socketPath,
|
|
53
|
+
method: "POST",
|
|
54
|
+
path: "/approval",
|
|
55
|
+
headers: {
|
|
56
|
+
"content-type": "application/json",
|
|
57
|
+
"x-rotom-token": token,
|
|
58
|
+
},
|
|
59
|
+
}, (res) => {
|
|
60
|
+
let body = "";
|
|
61
|
+
res.setEncoding("utf8");
|
|
62
|
+
res.on("data", (c) => { body += c; });
|
|
63
|
+
res.on("end", () => {
|
|
64
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
65
|
+
process.stdout.write(body);
|
|
66
|
+
process.exit(0);
|
|
67
|
+
} else {
|
|
68
|
+
emitDeny(`rotom approval bridge: server returned ${res.statusCode}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
req.on("error", (err) => {
|
|
73
|
+
emitDeny(`rotom approval bridge: ${err.message}`);
|
|
74
|
+
});
|
|
75
|
+
req.write(raw);
|
|
76
|
+
req.end();
|
|
77
|
+
});
|
|
78
|
+
process.stdin.on("error", (err) => {
|
|
79
|
+
emitDeny(`rotom approval bridge: stdin error ${err.message}`);
|
|
80
|
+
});
|