@cello-protocol/cli 0.0.45 → 0.0.46
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/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +6 -5
- package/dist/cli-args.js.map +1 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +4 -3
- package/dist/commands.js.map +1 -1
- package/dist/hermes/assets.d.ts +2 -2
- package/dist/hermes/assets.d.ts.map +1 -1
- package/dist/hermes/assets.js +11 -11
- package/dist/hermes/install-hermes.d.ts +1 -1
- package/dist/hermes/install-hermes.js +3 -3
- package/dist/hermes/install-hermes.js.map +1 -1
- package/dist/parity-commands.d.ts +16 -7
- package/dist/parity-commands.d.ts.map +1 -1
- package/dist/parity-commands.js +19 -10
- package/dist/parity-commands.js.map +1 -1
- package/dist/registry.d.ts +19 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +437 -324
- package/dist/registry.js.map +1 -1
- package/package.json +2 -2
package/dist/registry.js
CHANGED
|
@@ -22,6 +22,19 @@ async function readStdin() {
|
|
|
22
22
|
chunks.push(Buffer.from(chunk));
|
|
23
23
|
return Buffer.concat(chunks).toString("utf8");
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* DOD-ONBOARD-HELP-1 §1 — the help is GROUPED, not alphabetical, and the groups render in this
|
|
27
|
+
* order. A new user reads it top-to-bottom as the order they will actually do things: get set up,
|
|
28
|
+
* bring an agent online, hold a conversation, then look at what it produced.
|
|
29
|
+
*/
|
|
30
|
+
export const GROUP_ORDER = [
|
|
31
|
+
"Setup",
|
|
32
|
+
"Agents",
|
|
33
|
+
"Messaging",
|
|
34
|
+
"Sessions & receipts",
|
|
35
|
+
"Contacts",
|
|
36
|
+
"Other",
|
|
37
|
+
];
|
|
25
38
|
/** Parse the parity commands' shared flags out of argv (`--agent`, `--pretty`, and value flags). */
|
|
26
39
|
function parityOpts(args) {
|
|
27
40
|
const { agent, positional } = splitAgentFlag(args);
|
|
@@ -88,7 +101,7 @@ function legacy(result) {
|
|
|
88
101
|
*
|
|
89
102
|
* `consumesValue: false` is deliberate and is what checkArgs PARITY requires: the pre-existing
|
|
90
103
|
* checkArgs never skipped --agent's value (only `--limit` did), and flipping this to true would
|
|
91
|
-
* change `cello
|
|
104
|
+
* change `cello contacts --agent --bogus` from a fail-loud unknown_flag into a silently
|
|
92
105
|
* accepted agent literally named "--bogus". The value is claimed by splitAgentFlag (arg-parse.ts),
|
|
93
106
|
* which owns --agent parsing; checkArgs only needs to know the FLAG is legal. Same for install's
|
|
94
107
|
* --agent / --hermes-home below.
|
|
@@ -100,9 +113,11 @@ const AGENT_AND_TIMEOUT = [
|
|
|
100
113
|
{ name: "--timeout-ms", consumesValue: true },
|
|
101
114
|
];
|
|
102
115
|
export const COMMANDS = [
|
|
116
|
+
// ═══ Setup — get a working agent, in the order you actually do it ═══════════════════════════
|
|
103
117
|
{
|
|
104
118
|
name: "login",
|
|
105
|
-
|
|
119
|
+
group: "Setup",
|
|
120
|
+
summary: "Start the local CELLO daemon and bring your agents online.",
|
|
106
121
|
help: "Usage: cello login — start the daemon (or connect to an existing one).",
|
|
107
122
|
async run(ctx) {
|
|
108
123
|
return legacy(await login(ctx.celloDir, ctx.daemonBin, ctx.logger));
|
|
@@ -110,7 +125,8 @@ export const COMMANDS = [
|
|
|
110
125
|
},
|
|
111
126
|
{
|
|
112
127
|
name: "logout",
|
|
113
|
-
|
|
128
|
+
group: "Setup",
|
|
129
|
+
summary: "Stop the daemon. Waits until it has actually exited.",
|
|
114
130
|
help: "Usage: cello logout — send shutdown to the running daemon.",
|
|
115
131
|
async run(ctx) {
|
|
116
132
|
// DOD-LOGOUT-WAIT-1: logout WAITS for the daemon to actually die before claiming
|
|
@@ -121,287 +137,287 @@ export const COMMANDS = [
|
|
|
121
137
|
},
|
|
122
138
|
{
|
|
123
139
|
name: "status",
|
|
124
|
-
|
|
140
|
+
group: "Setup",
|
|
141
|
+
summary: "Show whether the daemon is running and which agents are online.",
|
|
125
142
|
help: "Usage: cello status — query the daemon and print the structured status JSON.",
|
|
126
143
|
async run(ctx) {
|
|
127
144
|
return legacy(await status(ctx.celloDir));
|
|
128
145
|
},
|
|
129
146
|
},
|
|
130
147
|
{
|
|
131
|
-
name: "
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
name: "create-agent",
|
|
149
|
+
group: "Setup",
|
|
150
|
+
summary: "Create a new agent on this machine. Step 1 of 2.",
|
|
151
|
+
help: "Usage: cello create-agent <name> — create a new LOCAL agent identity (does not touch the directory).\n" +
|
|
152
|
+
// MONIKER-0 AC2: the regex text is DERIVED from the shared constant, never hand-typed.
|
|
153
|
+
` Name rule: 1–64 characters, letters/digits/'-'/'_' only, no spaces (regex ${MONIKER_RE.source}).\n` +
|
|
154
|
+
" Next step: 'cello register-agent <name> <pre-auth-token>' to register it with the directory.",
|
|
155
|
+
async run(ctx, args) {
|
|
156
|
+
return legacy(await createAgent(ctx.celloDir, args[0] ?? ""));
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "register-agent",
|
|
161
|
+
group: "Setup",
|
|
162
|
+
summary: "Publish an agent to the directory so others can reach it. Step 2 of 2.",
|
|
163
|
+
help: "Usage: cello register-agent <agent> <pre-auth-token> — register a LOCAL agent with the directory.\n" +
|
|
164
|
+
" The two-step onboarding: (1) 'cello create-agent <name>' makes the identity on this machine; (2) 'cello register-agent <name> <token>' publishes it to the directory so others can find and reach it.\n" +
|
|
135
165
|
" The token is a single-use pre-authorization ticket from the CELLO Operations Agent on Telegram, format 'CELLO-' + 33 characters, valid 24h.\n" +
|
|
136
|
-
" Example: cello register alice CELLO-3xY7...\n" +
|
|
137
|
-
" Env-var form (avoids retyping): CELLO_PREAUTH_TOKEN=CELLO-3xY7... cello register alice\n" +
|
|
166
|
+
" Example: cello register-agent alice CELLO-3xY7...\n" +
|
|
167
|
+
" Env-var form (avoids retyping): CELLO_PREAUTH_TOKEN=CELLO-3xY7... cello register-agent alice\n" +
|
|
138
168
|
" Quoting is only needed if a value contains spaces (agent names and tokens never do).",
|
|
139
169
|
async run(ctx, args) {
|
|
140
|
-
// cello register <agent> [preAuthToken] (token falls back to CELLO_PREAUTH_TOKEN so it
|
|
141
|
-
// not appear in shell history). Optional phone stub follows.
|
|
170
|
+
// cello register-agent <agent> [preAuthToken] (token falls back to CELLO_PREAUTH_TOKEN so it
|
|
171
|
+
// need not appear in shell history). Optional phone stub follows.
|
|
142
172
|
const agent = args[0] ?? "";
|
|
143
173
|
const preAuthToken = args[1] ?? process.env.CELLO_PREAUTH_TOKEN ?? "";
|
|
144
174
|
const phoneStub = args[2] ?? "";
|
|
145
175
|
return legacy(await register(ctx.celloDir, agent, preAuthToken, phoneStub));
|
|
146
176
|
},
|
|
147
177
|
},
|
|
148
|
-
{
|
|
149
|
-
name: "create-agent",
|
|
150
|
-
summary: "Create a new local agent identity (does not touch the directory).",
|
|
151
|
-
help: "Usage: cello create-agent <name> — create a new LOCAL agent identity (does not touch the directory).\n" +
|
|
152
|
-
// MONIKER-0 AC2: the regex text is DERIVED from the shared constant, never hand-typed.
|
|
153
|
-
` Name rule: 1–64 characters, letters/digits/'-'/'_' only, no spaces (regex ${MONIKER_RE.source}).\n` +
|
|
154
|
-
" Next step: 'cello register <name> <pre-auth-token>' to register it with the directory.",
|
|
155
|
-
async run(ctx, args) {
|
|
156
|
-
return legacy(await createAgent(ctx.celloDir, args[0] ?? ""));
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
178
|
{
|
|
160
179
|
name: "remove-agent",
|
|
161
|
-
|
|
180
|
+
group: "Setup",
|
|
181
|
+
summary: "Retire an agent permanently and free its name. Cannot be undone.",
|
|
162
182
|
help: "Usage: cello remove-agent <name> — retires a local agent (one-way) and frees its name.",
|
|
163
183
|
async run(ctx, args) {
|
|
164
184
|
return legacy(await removeAgent(ctx.celloDir, args[0] ?? ""));
|
|
165
185
|
},
|
|
166
186
|
},
|
|
187
|
+
// ═══ Agents — day-to-day control of who is online and who you are acting as ═════════════════
|
|
167
188
|
{
|
|
168
|
-
name: "
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
name: "agents",
|
|
190
|
+
group: "Agents",
|
|
191
|
+
summary: "List your agents and whether each one is online.",
|
|
192
|
+
help: "Usage: cello agents [--pretty] — list all loaded agents (name, state).\n" +
|
|
193
|
+
" The CLI twin of the cello_agents MCP tool. Prints JSON; use --pretty for humans.",
|
|
194
|
+
ipcMethod: IPC_METHODS.agents,
|
|
195
|
+
jsonOut: true,
|
|
171
196
|
async run(ctx, args) {
|
|
172
|
-
|
|
197
|
+
const { pretty } = parityOpts(args);
|
|
198
|
+
return listAgents(ctx.celloDir, { pretty });
|
|
173
199
|
},
|
|
174
200
|
},
|
|
175
201
|
{
|
|
176
|
-
name: "
|
|
177
|
-
|
|
178
|
-
|
|
202
|
+
name: "start-agent",
|
|
203
|
+
group: "Agents",
|
|
204
|
+
summary: "Bring an agent online so it can be reached.",
|
|
205
|
+
help: "Usage: cello start-agent <name> [--pretty] — bring a registered agent ONLINE.\n" +
|
|
206
|
+
" Does NOT select it as the current agent — use 'cello use-agent <name>' for that.\n" +
|
|
207
|
+
" Idempotent: starting an already-online agent is safe.",
|
|
208
|
+
ipcMethod: IPC_METHODS["start-agent"],
|
|
209
|
+
jsonOut: true,
|
|
179
210
|
async run(ctx, args) {
|
|
180
|
-
|
|
211
|
+
const { pretty, positional } = parityOpts(args);
|
|
212
|
+
return startAgent(ctx.celloDir, positional[0] ?? "", { pretty });
|
|
181
213
|
},
|
|
182
214
|
},
|
|
183
215
|
{
|
|
184
|
-
name: "
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
],
|
|
216
|
+
name: "use-agent",
|
|
217
|
+
group: "Agents",
|
|
218
|
+
summary: "Select the agent that later commands operate through.",
|
|
219
|
+
help: "Usage: cello use-agent <name> [--pretty] — select the CURRENT agent for later commands.\n" +
|
|
220
|
+
" Brings the agent online first if it is offline (AUTOSTART-1).\n" +
|
|
221
|
+
" The selection PERSISTS across invocations (recorded in <cello-dir>/current-agent), because\n" +
|
|
222
|
+
" each CLI command opens its own daemon connection — a selection that lived only on the socket\n" +
|
|
223
|
+
" would vanish the moment the command exited. Override per-command with '--agent <name>'.\n" +
|
|
224
|
+
" A selection the daemon rejects is not recorded.",
|
|
225
|
+
ipcMethod: IPC_METHODS["use-agent"],
|
|
226
|
+
jsonOut: true,
|
|
194
227
|
async run(ctx, args) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
filter = "all";
|
|
198
|
-
else if (args.includes("--closed"))
|
|
199
|
-
filter = "closed";
|
|
200
|
-
else if (args.includes("--failed"))
|
|
201
|
-
filter = "failed";
|
|
202
|
-
else if (args.includes("--open"))
|
|
203
|
-
filter = "open";
|
|
204
|
-
const limitIdx = args.indexOf("--limit");
|
|
205
|
-
let limit;
|
|
206
|
-
if (limitIdx !== -1 && args[limitIdx + 1] !== undefined) {
|
|
207
|
-
const n = Number(args[limitIdx + 1]);
|
|
208
|
-
if (Number.isFinite(n) && n > 0)
|
|
209
|
-
limit = Math.floor(n);
|
|
210
|
-
}
|
|
211
|
-
return legacy(await sessions(ctx.celloDir, { filter, limit }));
|
|
228
|
+
const { pretty, positional } = parityOpts(args);
|
|
229
|
+
return useAgent(ctx.celloDir, positional[0] ?? "", { pretty });
|
|
212
230
|
},
|
|
213
231
|
},
|
|
214
232
|
{
|
|
215
|
-
name: "
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
" Per-agent contact whitelist (M8C-CONTACT-1). --agent defaults to the current/sole-online agent.\n" +
|
|
222
|
-
" Contacts are added automatically too: initiating a session to X, or accepting X's inbound request, adds X.\n" +
|
|
223
|
-
" Note: 'set-moniker' is the name YOU give a CONTACT. The top-level 'cello moniker' is your OWN outbound name.\n" +
|
|
224
|
-
" ('tier' and 'away' remain accepted as aliases of set-tier / set-away.)\n" +
|
|
225
|
-
" Example: cello contact list --agent alice",
|
|
226
|
-
flags: AGENT_FLAG,
|
|
227
|
-
jsonOut: true, // review F3: the WHOLE address book honors §3 — one command, one contract
|
|
233
|
+
name: "stop-agent",
|
|
234
|
+
group: "Agents",
|
|
235
|
+
summary: "Take an agent offline. It stops accepting anything until restarted.",
|
|
236
|
+
help: "Usage: cello stop-agent <name> [--pretty] — take an agent offline.",
|
|
237
|
+
ipcMethod: IPC_METHODS["stop-agent"],
|
|
238
|
+
jsonOut: true,
|
|
228
239
|
async run(ctx, args) {
|
|
229
|
-
const {
|
|
230
|
-
|
|
231
|
-
const [sub, pubkey, valueArg] = positional;
|
|
232
|
-
if (sub === "add" && pubkey)
|
|
233
|
-
return contactAdd(ctx.celloDir, pubkey, o);
|
|
234
|
-
if (sub === "remove" && pubkey)
|
|
235
|
-
return contactRemove(ctx.celloDir, pubkey, o);
|
|
236
|
-
if (sub === "list")
|
|
237
|
-
return contactList(ctx.celloDir, o);
|
|
238
|
-
// set-tier / set-away are the DOD-CLI-PARITY-1 names; tier / away are the pre-existing verbs,
|
|
239
|
-
// kept as aliases so no existing script or muscle-memory breaks.
|
|
240
|
-
if ((sub === "set-tier" || sub === "tier") && pubkey && valueArg !== undefined) {
|
|
241
|
-
// Daemon validates the value; a non-numeric arg surfaces as its invalid_tier verdict.
|
|
242
|
-
return contactSetTier(ctx.celloDir, pubkey, Number(valueArg), o);
|
|
243
|
-
}
|
|
244
|
-
if ((sub === "set-away" || sub === "away") && pubkey) {
|
|
245
|
-
// The rest of the args form the away text; empty → clear.
|
|
246
|
-
const message = positional.slice(2).join(" ");
|
|
247
|
-
return contactSetAway(ctx.celloDir, pubkey, message.length > 0 ? message : null, o);
|
|
248
|
-
}
|
|
249
|
-
if (sub === "set-moniker" && pubkey) {
|
|
250
|
-
// DOD-CLI-PARITY-1: the per-CONTACT pet name (cello_contact_set_moniker) — was MCP-only.
|
|
251
|
-
// Empty → null clears it, mirroring the tool.
|
|
252
|
-
const moniker = positional.slice(2).join(" ");
|
|
253
|
-
return contactSetMoniker(ctx.celloDir, pubkey, moniker.length > 0 ? moniker : null, o);
|
|
254
|
-
}
|
|
255
|
-
return {
|
|
256
|
-
stdout: helpForSpec("contact"),
|
|
257
|
-
stderr: "",
|
|
258
|
-
exitCode: 1,
|
|
259
|
-
};
|
|
240
|
+
const { pretty, positional } = parityOpts(args);
|
|
241
|
+
return stopAgent(ctx.celloDir, positional[0] ?? "", { pretty });
|
|
260
242
|
},
|
|
261
243
|
},
|
|
262
244
|
{
|
|
263
|
-
name: "
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
245
|
+
name: "refresh",
|
|
246
|
+
group: "Agents",
|
|
247
|
+
// VERIFIED against the handler (cello_refresh_shares → runAgentRefresh), not guessed. It runs a
|
|
248
|
+
// resharing ceremony with the directory nodes and moves the agent to a NEW key epoch. Routine
|
|
249
|
+
// key hygiene — nothing is re-registered and the agent's public identity does not change.
|
|
250
|
+
summary: "Rotate an agent's signing-key shares to a fresh epoch (routine key hygiene).",
|
|
251
|
+
help: "Usage: cello refresh <name> — rotate the agent's split signing-key shares to a new epoch.\n" +
|
|
252
|
+
" CELLO never holds your whole signing key in one place — it is split into shares held with the\n" +
|
|
253
|
+
" directory nodes. This runs a ceremony that replaces every share with a fresh one. Your public\n" +
|
|
254
|
+
" identity does NOT change and you do not re-register; old shares simply stop being usable.\n" +
|
|
255
|
+
" Requires the directory to be reachable (the agent must be online and connected).\n" +
|
|
256
|
+
" Occasional hygiene, not something you need day to day.",
|
|
270
257
|
async run(ctx, args) {
|
|
271
|
-
|
|
272
|
-
const [sub, key, value] = positional;
|
|
273
|
-
if (sub === "get")
|
|
274
|
-
return legacy(await settingsGet(ctx.celloDir, key, agent)); // key optional → all
|
|
275
|
-
if (sub === "set" && key && value !== undefined) {
|
|
276
|
-
return legacy(await settingsSet(ctx.celloDir, key, value, agent));
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
stdout: "Usage: cello settings get [key] [--agent <name>] | cello settings set <key> <value> [--agent <name>]",
|
|
280
|
-
stderr: "",
|
|
281
|
-
exitCode: 1,
|
|
282
|
-
};
|
|
258
|
+
return legacy(await refreshShares(ctx.celloDir, args[0] ?? ""));
|
|
283
259
|
},
|
|
284
260
|
},
|
|
261
|
+
// ═══ Messaging — the conversation itself ════════════════════════════════════════════════════
|
|
285
262
|
{
|
|
286
|
-
name: "
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
" Local-only: never sent to the directory; the receiver treats it as an unverified hint (like caller ID).\n" +
|
|
293
|
-
" Example: cello moniker set Wonderland_Alice --agent alice",
|
|
263
|
+
name: "initiate-session",
|
|
264
|
+
group: "Messaging",
|
|
265
|
+
summary: "Open a session with someone (by public key). Prints the session id.",
|
|
266
|
+
help: "Usage: cello initiate-session <target-pubkey> [--agent <name>] [--pretty] — open a session.\n" +
|
|
267
|
+
" <target-pubkey> is the counterparty's hex public key. Prints the session_id you then pass to\n" +
|
|
268
|
+
" 'cello send' / 'cello receive' / 'cello close-session'. Adds them to your address book.",
|
|
294
269
|
flags: AGENT_FLAG,
|
|
270
|
+
ipcMethod: IPC_METHODS["initiate-session"],
|
|
271
|
+
jsonOut: true,
|
|
295
272
|
async run(ctx, args) {
|
|
296
|
-
const { agent, positional } =
|
|
297
|
-
|
|
298
|
-
if (sub === "set" && name)
|
|
299
|
-
return legacy(await monikerSet(ctx.celloDir, name, agent));
|
|
300
|
-
if (sub === "clear" && !name)
|
|
301
|
-
return legacy(await monikerSet(ctx.celloDir, null, agent));
|
|
302
|
-
return { stdout: helpForSpec("moniker"), stderr: "", exitCode: 1 };
|
|
273
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
274
|
+
return initiate(ctx.celloDir, positional[0] ?? "", { agent, pretty });
|
|
303
275
|
},
|
|
304
276
|
},
|
|
305
277
|
{
|
|
306
|
-
name: "
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
278
|
+
name: "await-session",
|
|
279
|
+
group: "Messaging",
|
|
280
|
+
summary: "Wait for someone to open a session with you.",
|
|
281
|
+
help: "Usage: cello await-session [--timeout-ms N] [--agent <name>] [--pretty]\n" +
|
|
282
|
+
" BLOCKS until someone opens a session with you (default 30000ms), then prints the request.\n" +
|
|
283
|
+
" On expiry it returns {\"type\":\"timeout\"} and exits 0 — a timeout is a normal answer, not an\n" +
|
|
284
|
+
" error (this mirrors cello_await_session exactly). Branch on .type in scripts.",
|
|
285
|
+
flags: AGENT_AND_TIMEOUT,
|
|
286
|
+
ipcMethod: IPC_METHODS["await-session"],
|
|
287
|
+
jsonOut: true,
|
|
310
288
|
async run(ctx, args) {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
289
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
290
|
+
const timeout = takeValueFlag(positional, "--timeout-ms");
|
|
291
|
+
try {
|
|
292
|
+
return await awaitSession(ctx.celloDir, { agent, pretty, timeoutMs: numberOrUndefined(timeout.value, "--timeout-ms") });
|
|
314
293
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
},
|
|
318
|
-
{
|
|
319
|
-
name: "install",
|
|
320
|
-
summary: "Wire the local CELLO daemon into a Hermes Agent installation.",
|
|
321
|
-
help: "Usage: cello install hermes --agent <name> [--hermes-home <path>] — wire the local CELLO daemon into a Hermes Agent installation.\n" +
|
|
322
|
-
" Scaffolds the CELLO platform-adapter plugin into the Hermes home (default ~/.hermes), binds CELLO_AGENT_NAME in its .env,\n" +
|
|
323
|
-
" and registers via 'hermes plugins enable cello' + 'hermes mcp add cello'. Idempotent — re-run to upgrade.\n" +
|
|
324
|
-
" After installing, restart the gateway: hermes gateway restart",
|
|
325
|
-
flags: [{ name: "--agent" }, { name: "--hermes-home" }],
|
|
326
|
-
async run(_ctx, args) {
|
|
327
|
-
const agentIdx = args.indexOf("--agent");
|
|
328
|
-
const homeIdx = args.indexOf("--hermes-home");
|
|
329
|
-
// Find the target positional, excluding both flags AND their values — so
|
|
330
|
-
// `cello install --agent alice hermes` still resolves target=hermes.
|
|
331
|
-
const target = args.find((a, i) => !a.startsWith("-") &&
|
|
332
|
-
!(agentIdx !== -1 && i === agentIdx + 1) &&
|
|
333
|
-
!(homeIdx !== -1 && i === homeIdx + 1));
|
|
334
|
-
if (target !== "hermes") {
|
|
335
|
-
return { stdout: helpForSpec("install"), stderr: "", exitCode: 1 };
|
|
294
|
+
catch (err) {
|
|
295
|
+
return flagError(err);
|
|
336
296
|
}
|
|
337
|
-
const { installHermes } = await import("./hermes/install-hermes.js");
|
|
338
|
-
return legacy(await installHermes({
|
|
339
|
-
agentName: agentIdx !== -1 ? (args[agentIdx + 1] ?? "") : "",
|
|
340
|
-
hermesHome: homeIdx !== -1 ? args[homeIdx + 1] : undefined,
|
|
341
|
-
}));
|
|
342
297
|
},
|
|
343
298
|
},
|
|
344
|
-
// ═══ DOD-CLI-PARITY-1 — the MCP-only capabilities, now reachable from bash ══════════════════
|
|
345
|
-
// Each honors the §3 contract (jsonOut) and calls the SAME daemon handler as its cello_* MCP
|
|
346
|
-
// tool (ipcMethod). Group A = operator control + address book; Group B = live conversation.
|
|
347
299
|
{
|
|
348
|
-
name: "
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
300
|
+
name: "receive-session",
|
|
301
|
+
group: "Messaging",
|
|
302
|
+
// TRUTH, not the old claim. The daemon registers the SAME handler for cello_receive_session and
|
|
303
|
+
// cello_receive (`handlers.set("cello_receive_session", handleReceive)`) — it does not accept or
|
|
304
|
+
// join anything; inbound sessions are auto-accepted by the standing receiver. The old summary
|
|
305
|
+
// ("Accept / join an inbound session request") described a step that does not exist. Slated for
|
|
306
|
+
// deletion under the no-aliases doctrine; until then it says what it is.
|
|
307
|
+
summary: "Alias of 'receive' — same behavior, no separate accept step. Prefer 'cello receive'.",
|
|
308
|
+
help: "Usage: cello receive-session <session-id> [--timeout-ms N] [--agent <name>] [--pretty]\n" +
|
|
309
|
+
" An ALIAS of 'cello receive' — the daemon runs the identical handler for both. It does NOT\n" +
|
|
310
|
+
" 'accept' or 'join' anything: an inbound session is auto-accepted for you, so there is no\n" +
|
|
311
|
+
" separate accept step to run. Use 'cello receive'; this exists only for backward parity with\n" +
|
|
312
|
+
" the cello_receive_session MCP tool and is expected to be removed.",
|
|
313
|
+
flags: AGENT_AND_TIMEOUT,
|
|
314
|
+
ipcMethod: IPC_METHODS["receive-session"],
|
|
353
315
|
jsonOut: true,
|
|
354
316
|
async run(ctx, args) {
|
|
355
|
-
const { pretty } = parityOpts(args);
|
|
356
|
-
|
|
317
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
318
|
+
const timeout = takeValueFlag(positional, "--timeout-ms");
|
|
319
|
+
try {
|
|
320
|
+
return await receiveSession(ctx.celloDir, timeout.rest[0] ?? "", {
|
|
321
|
+
agent,
|
|
322
|
+
pretty,
|
|
323
|
+
timeoutMs: numberOrUndefined(timeout.value, "--timeout-ms"),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
return flagError(err);
|
|
328
|
+
}
|
|
357
329
|
},
|
|
358
330
|
},
|
|
359
331
|
{
|
|
360
|
-
name: "
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
"
|
|
365
|
-
|
|
332
|
+
name: "close-session",
|
|
333
|
+
group: "Messaging",
|
|
334
|
+
summary: "End a session. Both sides sign off and get a tamper-proof receipt.",
|
|
335
|
+
help: "Usage: cello close-session <session-id> [--force] [--agent <name>] [--pretty]\n" +
|
|
336
|
+
" Both parties sign off on the whole conversation and each gets a notarized receipt\n" +
|
|
337
|
+
" ('cello sealed-receipt <session-id>' prints it).\n" +
|
|
338
|
+
" --force abandons a half-open session that can never be sealed (a handshake the counterparty\n" +
|
|
339
|
+
" never joined). It FORFEITS the receipt — never use it on a healthy session.",
|
|
340
|
+
flags: [
|
|
341
|
+
{ name: "--agent", consumesValue: false },
|
|
342
|
+
{ name: "--force", consumesValue: false },
|
|
343
|
+
],
|
|
344
|
+
ipcMethod: IPC_METHODS["close-session"],
|
|
366
345
|
jsonOut: true,
|
|
367
346
|
async run(ctx, args) {
|
|
368
|
-
const { pretty, positional } = parityOpts(args);
|
|
369
|
-
|
|
347
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
348
|
+
const force = positional.includes("--force");
|
|
349
|
+
const rest = positional.filter((a) => a !== "--force");
|
|
350
|
+
return closeSession(ctx.celloDir, rest[0] ?? "", { agent, pretty, force });
|
|
370
351
|
},
|
|
371
352
|
},
|
|
372
353
|
{
|
|
373
|
-
name: "
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
354
|
+
name: "send",
|
|
355
|
+
group: "Messaging",
|
|
356
|
+
// §4: "honors read-before-write" was jargon for a rule the operator meets as a REFUSAL. Say the
|
|
357
|
+
// rule, and say that the tool will tell you.
|
|
358
|
+
summary: "Send a message. Any unread messages must be read first — you'll be told, and blocked until you do.",
|
|
359
|
+
help: "Usage: cello send <session-id> <message…> [--stdin] [--agent <name>] [--pretty]\n" +
|
|
360
|
+
" The message is the remaining arguments, or the whole of stdin with --stdin (for text with\n" +
|
|
361
|
+
" newlines/quotes).\n" +
|
|
362
|
+
" If the other side has said something you have not read, the send is REFUSED and tells you how\n" +
|
|
363
|
+
" many messages are waiting. Read them ('cello receive <session-id>', or 'cello transcript\n" +
|
|
364
|
+
" <session-id>' for the whole conversation) and send again. This is deliberate: you cannot\n" +
|
|
365
|
+
" reply to something you never saw. The refusal is printed verbatim and never auto-fixed.",
|
|
366
|
+
flags: [
|
|
367
|
+
{ name: "--agent", consumesValue: false },
|
|
368
|
+
{ name: "--stdin", consumesValue: false },
|
|
369
|
+
],
|
|
370
|
+
ipcMethod: IPC_METHODS.send,
|
|
377
371
|
jsonOut: true,
|
|
378
372
|
async run(ctx, args) {
|
|
379
|
-
const { pretty, positional } = parityOpts(args);
|
|
380
|
-
|
|
373
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
374
|
+
const useStdin = positional.includes("--stdin");
|
|
375
|
+
const rest = positional.filter((a) => a !== "--stdin");
|
|
376
|
+
const sessionId = rest[0] ?? "";
|
|
377
|
+
const content = useStdin ? await readStdin() : rest.slice(1).join(" ");
|
|
378
|
+
return send(ctx.celloDir, sessionId, content, { agent, pretty });
|
|
381
379
|
},
|
|
382
380
|
},
|
|
383
381
|
{
|
|
384
|
-
name: "
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
"
|
|
389
|
-
"
|
|
390
|
-
"
|
|
391
|
-
|
|
392
|
-
|
|
382
|
+
name: "receive",
|
|
383
|
+
group: "Messaging",
|
|
384
|
+
summary: "Read the next message, or catch up on everything you missed with --since-seq.",
|
|
385
|
+
help: "Usage: cello receive <session-id> [--since-seq N] [--timeout-ms N] [--agent <name>] [--pretty]\n" +
|
|
386
|
+
" Default: WAITS for the next message (up to --timeout-ms, default 30000).\n" +
|
|
387
|
+
" With --since-seq N: returns every message after number N at once, immediately, without\n" +
|
|
388
|
+
" waiting — this is how you catch up after being away. Mirrors cello_receive exactly.",
|
|
389
|
+
flags: [
|
|
390
|
+
{ name: "--agent", consumesValue: false },
|
|
391
|
+
{ name: "--timeout-ms", consumesValue: true },
|
|
392
|
+
{ name: "--since-seq", consumesValue: true },
|
|
393
|
+
],
|
|
394
|
+
ipcMethod: IPC_METHODS.receive,
|
|
393
395
|
jsonOut: true,
|
|
394
396
|
async run(ctx, args) {
|
|
395
|
-
const { pretty, positional } = parityOpts(args);
|
|
396
|
-
|
|
397
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
398
|
+
const since = takeValueFlag(positional, "--since-seq");
|
|
399
|
+
const timeout = takeValueFlag(since.rest, "--timeout-ms");
|
|
400
|
+
try {
|
|
401
|
+
return await receive(ctx.celloDir, timeout.rest[0] ?? "", {
|
|
402
|
+
agent,
|
|
403
|
+
pretty,
|
|
404
|
+
sinceSeq: numberOrUndefined(since.value, "--since-seq"),
|
|
405
|
+
timeoutMs: numberOrUndefined(timeout.value, "--timeout-ms"),
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
return flagError(err);
|
|
410
|
+
}
|
|
397
411
|
},
|
|
398
412
|
},
|
|
399
413
|
{
|
|
400
414
|
name: "inbox",
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
"
|
|
415
|
+
group: "Messaging",
|
|
416
|
+
summary: "See who tried to reach you and what is unread, without reading anything.",
|
|
417
|
+
help: "Usage: cello inbox [--scope current|all] [--agent <name>] [--pretty] — what did I miss?\n" +
|
|
418
|
+
" Shows pending session requests and unread message COUNTS — never message content, and it\n" +
|
|
419
|
+
" does not mark anything as read ('cello receive' does that). Use it after being away.\n" +
|
|
420
|
+
" --scope all covers every agent you have, not just the current one.",
|
|
405
421
|
flags: [
|
|
406
422
|
{ name: "--agent", consumesValue: false },
|
|
407
423
|
{ name: "--scope", consumesValue: true },
|
|
@@ -430,12 +446,46 @@ export const COMMANDS = [
|
|
|
430
446
|
return inbox(ctx.celloDir, { agent, pretty, scope: value });
|
|
431
447
|
},
|
|
432
448
|
},
|
|
449
|
+
// ═══ Sessions & receipts — what the conversations left behind ═══════════════════════════════
|
|
450
|
+
{
|
|
451
|
+
name: "sessions",
|
|
452
|
+
group: "Sessions & receipts",
|
|
453
|
+
summary: "List your sessions (open by default; --all/--closed/--failed to filter).",
|
|
454
|
+
help: "Usage: cello sessions [--open|--closed|--failed|--all] [--limit N] — list session history (defaults to open).",
|
|
455
|
+
flags: [
|
|
456
|
+
{ name: "--open" },
|
|
457
|
+
{ name: "--closed" },
|
|
458
|
+
{ name: "--failed" },
|
|
459
|
+
{ name: "--all" },
|
|
460
|
+
{ name: "--limit", consumesValue: true },
|
|
461
|
+
],
|
|
462
|
+
async run(ctx, args) {
|
|
463
|
+
let filter;
|
|
464
|
+
if (args.includes("--all"))
|
|
465
|
+
filter = "all";
|
|
466
|
+
else if (args.includes("--closed"))
|
|
467
|
+
filter = "closed";
|
|
468
|
+
else if (args.includes("--failed"))
|
|
469
|
+
filter = "failed";
|
|
470
|
+
else if (args.includes("--open"))
|
|
471
|
+
filter = "open";
|
|
472
|
+
const limitIdx = args.indexOf("--limit");
|
|
473
|
+
let limit;
|
|
474
|
+
if (limitIdx !== -1 && args[limitIdx + 1] !== undefined) {
|
|
475
|
+
const n = Number(args[limitIdx + 1]);
|
|
476
|
+
if (Number.isFinite(n) && n > 0)
|
|
477
|
+
limit = Math.floor(n);
|
|
478
|
+
}
|
|
479
|
+
return legacy(await sessions(ctx.celloDir, { filter, limit }));
|
|
480
|
+
},
|
|
481
|
+
},
|
|
433
482
|
{
|
|
434
483
|
name: "transcript",
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
"
|
|
484
|
+
group: "Sessions & receipts",
|
|
485
|
+
summary: "Print the full conversation for a session — everything sent and received.",
|
|
486
|
+
help: "Usage: cello transcript <session-id> [--agent <name>] [--pretty] — the whole conversation.\n" +
|
|
487
|
+
" Sent AND received messages, in order. Stored on disk, so it survives a daemon restart.\n" +
|
|
488
|
+
" Reading it also catches you up, which un-blocks 'cello send' after you have been away.",
|
|
439
489
|
flags: AGENT_FLAG,
|
|
440
490
|
ipcMethod: IPC_METHODS.transcript,
|
|
441
491
|
jsonOut: true,
|
|
@@ -446,12 +496,16 @@ export const COMMANDS = [
|
|
|
446
496
|
},
|
|
447
497
|
{
|
|
448
498
|
name: "sealed-receipt",
|
|
449
|
-
|
|
499
|
+
group: "Sessions & receipts",
|
|
500
|
+
// THE one users want. Named and described so it cannot be confused with relay-receipts.
|
|
501
|
+
summary: "Print a closed session's notarized receipt — proof both sides signed off on the conversation.",
|
|
450
502
|
help: "Usage: cello sealed-receipt <session-id> [--agent <name>] [--pretty] — the NOTARIZED receipt.\n" +
|
|
451
|
-
"
|
|
452
|
-
"
|
|
453
|
-
"
|
|
454
|
-
"
|
|
503
|
+
" This is the proof CELLO exists to produce: when a session closes, both parties sign off on\n" +
|
|
504
|
+
" the whole conversation and the directory notarizes it. The receipt is tamper-evident — if a\n" +
|
|
505
|
+
" single message were altered, added or dropped, it would no longer match.\n" +
|
|
506
|
+
" It attests RECEIPT, never agreement (implies_assent: false) — an unanswered last message\n" +
|
|
507
|
+
" reads as delivered-but-unanswered, never as consent.\n" +
|
|
508
|
+
" NOT the same as 'cello relay-receipts', which is a low-level delivery-plumbing artifact.",
|
|
455
509
|
flags: AGENT_FLAG,
|
|
456
510
|
ipcMethod: IPC_METHODS["sealed-receipt"],
|
|
457
511
|
jsonOut: true,
|
|
@@ -460,137 +514,189 @@ export const COMMANDS = [
|
|
|
460
514
|
return sealedReceipt(ctx.celloDir, positional[0] ?? "", { agent, pretty });
|
|
461
515
|
},
|
|
462
516
|
},
|
|
463
|
-
// ─── Group B: live conversation ───────────────────────────────────────────────────────────
|
|
464
517
|
{
|
|
465
|
-
name: "
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
518
|
+
name: "relay-receipts",
|
|
519
|
+
group: "Sessions & receipts",
|
|
520
|
+
// VERIFIED against the handler (cello_get_relay_receipts → getRelayReceipts): per-MESSAGE
|
|
521
|
+
// signatures from a RELAY attesting it handled and ordered that message. Renamed from
|
|
522
|
+
// `receipts` because a name that differed from `sealed-receipt` by one plural could not be
|
|
523
|
+
// rescued by any description — Andre could not tell them apart, and he wrote the protocol.
|
|
524
|
+
summary: "Advanced/debug: per-message proofs signed by a relay. Not the session receipt — see 'sealed-receipt'.",
|
|
525
|
+
help: "Usage: cello relay-receipts <name> — ADVANCED / DEBUG. You almost certainly want\n" +
|
|
526
|
+
" 'cello sealed-receipt <session-id>' instead.\n" +
|
|
527
|
+
" When a message cannot go directly to the other agent (they are offline, or the network is in\n" +
|
|
528
|
+
" the way), it goes via a relay. The relay signs a small receipt saying it handled that message\n" +
|
|
529
|
+
" and where it fell in the order. This lists those — a plumbing artifact for diagnosing\n" +
|
|
530
|
+
" delivery, one per message.\n" +
|
|
531
|
+
" It says NOTHING about the conversation being agreed or sealed. That is 'cello sealed-receipt'.",
|
|
473
532
|
async run(ctx, args) {
|
|
474
|
-
|
|
475
|
-
return initiate(ctx.celloDir, positional[0] ?? "", { agent, pretty });
|
|
533
|
+
return legacy(await relayReceipts(ctx.celloDir, args[0] ?? ""));
|
|
476
534
|
},
|
|
477
535
|
},
|
|
536
|
+
// ═══ Contacts — the address book (plural) and one contact (singular) ════════════════════════
|
|
478
537
|
{
|
|
479
|
-
name: "
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
"
|
|
484
|
-
"
|
|
485
|
-
"
|
|
486
|
-
flags:
|
|
487
|
-
{ name: "--agent", consumesValue: false },
|
|
488
|
-
{ name: "--stdin", consumesValue: false },
|
|
489
|
-
],
|
|
490
|
-
ipcMethod: IPC_METHODS.send,
|
|
538
|
+
name: "contacts",
|
|
539
|
+
group: "Contacts",
|
|
540
|
+
summary: "List your address book — everyone this agent knows, and how much they're trusted.",
|
|
541
|
+
help: "Usage: cello contacts [--agent <name>] [--pretty] — list the whole address book.\n" +
|
|
542
|
+
" Contacts are added automatically when you open a session with someone, or accept theirs.\n" +
|
|
543
|
+
" To act on ONE contact, use 'cello contact <pubkey> <operation>'.\n" +
|
|
544
|
+
" --agent defaults to the current agent (or the only online one).",
|
|
545
|
+
flags: AGENT_FLAG,
|
|
491
546
|
jsonOut: true,
|
|
547
|
+
ipcMethod: IPC_METHODS.contacts,
|
|
492
548
|
async run(ctx, args) {
|
|
493
|
-
const { agent, pretty
|
|
494
|
-
|
|
495
|
-
const rest = positional.filter((a) => a !== "--stdin");
|
|
496
|
-
const sessionId = rest[0] ?? "";
|
|
497
|
-
const content = useStdin ? await readStdin() : rest.slice(1).join(" ");
|
|
498
|
-
return send(ctx.celloDir, sessionId, content, { agent, pretty });
|
|
549
|
+
const { agent, pretty } = parityOpts(args);
|
|
550
|
+
return contactList(ctx.celloDir, { agent, pretty });
|
|
499
551
|
},
|
|
500
552
|
},
|
|
501
553
|
{
|
|
502
|
-
name: "
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
"
|
|
507
|
-
"
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
554
|
+
name: "contact",
|
|
555
|
+
group: "Contacts",
|
|
556
|
+
summary: "Act on ONE contact: add, remove, set-tier, set-away, set-moniker.",
|
|
557
|
+
help: "Usage: cello contact <pubkey> <operation> [args] [--agent <name>] [--pretty]\n" +
|
|
558
|
+
"\n" +
|
|
559
|
+
" Operations:\n" +
|
|
560
|
+
" add add this peer to the address book\n" +
|
|
561
|
+
" remove remove them (they go back to being a stranger)\n" +
|
|
562
|
+
" set-tier <0..4> how much they're trusted: 0=blocked, 1=stranger, 2=known,\n" +
|
|
563
|
+
" 3=trusted (reaches you even when you're away), 4=vip.\n" +
|
|
564
|
+
" A higher tier RAISES their limits; it never removes screening.\n" +
|
|
565
|
+
" set-away <message…> what THIS person hears when you're away (empty clears it)\n" +
|
|
566
|
+
" set-moniker <name> YOUR pet name for THEM (empty clears it). Always wins over the\n" +
|
|
567
|
+
" name they offer — the one thing they cannot spoof.\n" +
|
|
568
|
+
"\n" +
|
|
569
|
+
" To list the whole book, use 'cello contacts'.\n" +
|
|
570
|
+
" Note: 'set-moniker' names a CONTACT. 'cello moniker' sets your OWN outbound name.\n" +
|
|
571
|
+
" Example: cello contact 178d420b… set-tier 3 --agent alice",
|
|
572
|
+
flags: AGENT_FLAG,
|
|
573
|
+
jsonOut: true, // review F3: the WHOLE address book honors §3 — one command, one contract
|
|
515
574
|
async run(ctx, args) {
|
|
516
575
|
const { agent, pretty, positional } = parityOpts(args);
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
576
|
+
const o = { agent, pretty };
|
|
577
|
+
// §3 SHAPE: `contact <pubkey> <op>` — the subject first, then what to do to them. (The old
|
|
578
|
+
// shape was `contact <op> <pubkey>`, which read like a verb table rather than an address book.)
|
|
579
|
+
const [pubkey, op, valueArg] = positional;
|
|
580
|
+
if (!pubkey || !op)
|
|
581
|
+
return { stdout: helpForSpec("contact"), stderr: "", exitCode: 1 };
|
|
582
|
+
if (op === "add")
|
|
583
|
+
return contactAdd(ctx.celloDir, pubkey, o);
|
|
584
|
+
if (op === "remove")
|
|
585
|
+
return contactRemove(ctx.celloDir, pubkey, o);
|
|
586
|
+
if (op === "set-tier" && valueArg !== undefined) {
|
|
587
|
+
// Daemon validates the value; a non-numeric arg surfaces as its invalid_tier verdict.
|
|
588
|
+
return contactSetTier(ctx.celloDir, pubkey, Number(valueArg), o);
|
|
526
589
|
}
|
|
527
|
-
|
|
528
|
-
|
|
590
|
+
if (op === "set-away") {
|
|
591
|
+
// The rest of the args form the away text; empty → clear.
|
|
592
|
+
const message = positional.slice(2).join(" ");
|
|
593
|
+
return contactSetAway(ctx.celloDir, pubkey, message.length > 0 ? message : null, o);
|
|
594
|
+
}
|
|
595
|
+
if (op === "set-moniker") {
|
|
596
|
+
// Empty → null clears it, mirroring the tool.
|
|
597
|
+
const moniker = positional.slice(2).join(" ");
|
|
598
|
+
return contactSetMoniker(ctx.celloDir, pubkey, moniker.length > 0 ? moniker : null, o);
|
|
529
599
|
}
|
|
600
|
+
return { stdout: helpForSpec("contact"), stderr: "", exitCode: 1 };
|
|
530
601
|
},
|
|
531
602
|
},
|
|
603
|
+
// ═══ Other ══════════════════════════════════════════════════════════════════════════════════
|
|
532
604
|
{
|
|
533
|
-
name: "
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
605
|
+
name: "settings",
|
|
606
|
+
group: "Other",
|
|
607
|
+
summary: "Get or set how reachable an agent is (limits per trust tier, away messages).",
|
|
608
|
+
help: "Usage: cello settings get [key] [--agent <name>] | cello settings set <key> <value> [--agent <name>]\n" +
|
|
609
|
+
" Per-agent reachability policy (DOD-SETTINGS-1). Keys: bounds.<tier>.max_sessions, bounds.<tier>.max_bytes\n" +
|
|
610
|
+
" (tier = unknown|known|whitelisted|vip; a finite positive integer), away.default, away.tier.<tier> (away text).\n" +
|
|
611
|
+
" An unset key uses the built-in default. Example: cello settings set bounds.known.max_sessions 8 --agent alice",
|
|
612
|
+
flags: AGENT_FLAG,
|
|
540
613
|
async run(ctx, args) {
|
|
541
|
-
const { agent,
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
return await
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
timeoutMs: numberOrUndefined(timeout.value, "--timeout-ms"),
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
catch (err) {
|
|
551
|
-
return flagError(err);
|
|
614
|
+
const { agent, positional } = splitAgentFlag(args);
|
|
615
|
+
const [sub, key, value] = positional;
|
|
616
|
+
if (sub === "get")
|
|
617
|
+
return legacy(await settingsGet(ctx.celloDir, key, agent)); // key optional → all
|
|
618
|
+
if (sub === "set" && key && value !== undefined) {
|
|
619
|
+
return legacy(await settingsSet(ctx.celloDir, key, value, agent));
|
|
552
620
|
}
|
|
621
|
+
return {
|
|
622
|
+
stdout: "Usage: cello settings get [key] [--agent <name>] | cello settings set <key> <value> [--agent <name>]",
|
|
623
|
+
stderr: "",
|
|
624
|
+
exitCode: 1,
|
|
625
|
+
};
|
|
553
626
|
},
|
|
554
627
|
},
|
|
555
628
|
{
|
|
556
|
-
name: "
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
"
|
|
561
|
-
"
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
629
|
+
name: "moniker",
|
|
630
|
+
group: "Other",
|
|
631
|
+
summary: "Set the name OTHERS see when this agent contacts them (like caller ID).",
|
|
632
|
+
help: "Usage: cello moniker set <name> [--agent <agent>] | cello moniker clear [--agent <agent>]\n" +
|
|
633
|
+
" Your OUTBOUND name — what shows up on the counterparty's screen when you reach them.\n" +
|
|
634
|
+
" Defaults to the agent name; 'set' overrides it, 'clear' restores the default.\n" +
|
|
635
|
+
// MONIKER-0 AC2: the regex text is DERIVED from the shared constant, never hand-typed.
|
|
636
|
+
` Name rule: 1–64 characters, letters/digits/'-'/'_' only, no spaces (regex ${MONIKER_RE.source}).\n` +
|
|
637
|
+
" It is a HINT, not proof — like caller ID, the receiver is shown it as self-declared and can\n" +
|
|
638
|
+
" override it with their own pet name for you. Never sent to the directory.\n" +
|
|
639
|
+
" Example: cello moniker set Wonderland_Alice --agent alice",
|
|
640
|
+
flags: AGENT_FLAG,
|
|
568
641
|
async run(ctx, args) {
|
|
569
|
-
const { agent,
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
642
|
+
const { agent, positional } = splitAgentFlag(args);
|
|
643
|
+
const [sub, name] = positional;
|
|
644
|
+
if (sub === "set" && name)
|
|
645
|
+
return legacy(await monikerSet(ctx.celloDir, name, agent));
|
|
646
|
+
if (sub === "clear" && !name)
|
|
647
|
+
return legacy(await monikerSet(ctx.celloDir, null, agent));
|
|
648
|
+
return { stdout: helpForSpec("moniker"), stderr: "", exitCode: 1 };
|
|
573
649
|
},
|
|
574
650
|
},
|
|
575
651
|
{
|
|
576
|
-
name: "
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
"
|
|
581
|
-
"
|
|
582
|
-
|
|
583
|
-
ipcMethod: IPC_METHODS["await-session"],
|
|
584
|
-
jsonOut: true,
|
|
652
|
+
name: "telegram",
|
|
653
|
+
group: "Other",
|
|
654
|
+
summary: "Connect a Telegram bot to your daemon for notifications, status updates, etc.",
|
|
655
|
+
help: "Usage: cello telegram set-token <bot_token> <allowlisted_chat_id>\n" +
|
|
656
|
+
" Connects a Telegram bot to your daemon so you get notified there (someone reaching you,\n" +
|
|
657
|
+
" status updates, and more over time). Starts polling immediately.\n" +
|
|
658
|
+
" The chat id you give is the ONLY chat that ever receives anything.",
|
|
585
659
|
async run(ctx, args) {
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return await awaitSession(ctx.celloDir, { agent, pretty, timeoutMs: numberOrUndefined(timeout.value, "--timeout-ms") });
|
|
660
|
+
const [sub, botToken, chatId] = args;
|
|
661
|
+
if (sub === "set-token" && botToken && chatId) {
|
|
662
|
+
return legacy(await telegramSetToken(ctx.celloDir, botToken, chatId));
|
|
590
663
|
}
|
|
591
|
-
|
|
592
|
-
|
|
664
|
+
return { stdout: "Usage: cello telegram set-token <bot_token> <allowlisted_chat_id>", stderr: "", exitCode: 1 };
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
name: "bridge",
|
|
669
|
+
group: "Other",
|
|
670
|
+
// Renamed from `install`, which read as "install CELLO itself" and hardcoded Hermes — the
|
|
671
|
+
// runtime is a PARAMETER. More runtimes are coming; the description must not claim otherwise.
|
|
672
|
+
summary: "Bridge CELLO into a third-party agent runtime (Hermes, OpenClaw, …).",
|
|
673
|
+
help: "Usage: cello bridge <runtime> --agent <name> [--hermes-home <path>]\n" +
|
|
674
|
+
" Wires the local CELLO daemon into a third-party agent runtime so that agent can use CELLO.\n" +
|
|
675
|
+
" Supported runtimes: hermes (more coming).\n" +
|
|
676
|
+
"\n" +
|
|
677
|
+
" hermes: scaffolds the CELLO plugin into the Hermes home (default ~/.hermes), binds\n" +
|
|
678
|
+
" CELLO_AGENT_NAME in its .env, and registers via 'hermes plugins enable cello' +\n" +
|
|
679
|
+
" 'hermes mcp add cello'. Idempotent — re-run to upgrade.\n" +
|
|
680
|
+
" Afterwards, restart the gateway: hermes gateway restart\n" +
|
|
681
|
+
"\n" +
|
|
682
|
+
" Example: cello bridge hermes --agent alice",
|
|
683
|
+
flags: [{ name: "--agent" }, { name: "--hermes-home" }],
|
|
684
|
+
async run(_ctx, args) {
|
|
685
|
+
const agentIdx = args.indexOf("--agent");
|
|
686
|
+
const homeIdx = args.indexOf("--hermes-home");
|
|
687
|
+
// Find the target positional, excluding both flags AND their values — so
|
|
688
|
+
// `cello bridge --agent alice hermes` still resolves target=hermes.
|
|
689
|
+
const target = args.find((a, i) => !a.startsWith("-") &&
|
|
690
|
+
!(agentIdx !== -1 && i === agentIdx + 1) &&
|
|
691
|
+
!(homeIdx !== -1 && i === homeIdx + 1));
|
|
692
|
+
if (target !== "hermes") {
|
|
693
|
+
return { stdout: helpForSpec("bridge"), stderr: "", exitCode: 1 };
|
|
593
694
|
}
|
|
695
|
+
const { installHermes } = await import("./hermes/install-hermes.js");
|
|
696
|
+
return legacy(await installHermes({
|
|
697
|
+
agentName: agentIdx !== -1 ? (args[agentIdx + 1] ?? "") : "",
|
|
698
|
+
hermesHome: homeIdx !== -1 ? args[homeIdx + 1] : undefined,
|
|
699
|
+
}));
|
|
594
700
|
},
|
|
595
701
|
},
|
|
596
702
|
];
|
|
@@ -630,12 +736,19 @@ export function flagsFor(name) {
|
|
|
630
736
|
return map;
|
|
631
737
|
}
|
|
632
738
|
/**
|
|
633
|
-
* DOD-ONBOARD-HELP-1: render the
|
|
634
|
-
*
|
|
739
|
+
* DOD-ONBOARD-HELP-1 §1: render the `Commands:` table GROUPED and in logical order.
|
|
740
|
+
*
|
|
741
|
+
* Flat-and-arbitrary was the reopen: `register` appeared before `create-agent`, so the table
|
|
742
|
+
* literally listed step 2 above step 1. Sections in GROUP_ORDER, commands in declaration order
|
|
743
|
+
* within each — the order a reader would actually do them. Name column is padded across the WHOLE
|
|
744
|
+
* table (not per group) so the summaries line up as one column down the page.
|
|
635
745
|
*/
|
|
636
746
|
export function renderCommandsTable() {
|
|
637
747
|
const width = Math.max(...COMMANDS.map((c) => c.name.length));
|
|
638
|
-
const
|
|
639
|
-
|
|
748
|
+
const sections = GROUP_ORDER.map((group) => {
|
|
749
|
+
const rows = COMMANDS.filter((c) => c.group === group).map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);
|
|
750
|
+
return rows.length === 0 ? null : `${group}:\n${rows.join("\n")}`;
|
|
751
|
+
}).filter((s) => s !== null);
|
|
752
|
+
return sections.join("\n\n");
|
|
640
753
|
}
|
|
641
754
|
//# sourceMappingURL=registry.js.map
|