@cello-protocol/cli 0.0.45 → 0.0.47
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 +22 -16
- package/dist/parity-commands.d.ts.map +1 -1
- package/dist/parity-commands.js +25 -19
- 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 +409 -328
- package/dist/registry.js.map +1 -1
- package/package.json +2 -2
package/dist/registry.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { MONIKER_RE } from "@cello-protocol/protocol-types";
|
|
15
15
|
import { login, logout, status, register, createAgent, removeAgent, refreshShares, relayReceipts, sessions, settingsGet, settingsSet, monikerSet, telegramSetToken, } from "./commands.js";
|
|
16
16
|
import { splitAgentFlag } from "./arg-parse.js";
|
|
17
|
-
import { IPC_METHODS, contactAdd, contactRemove, contactList, contactSetTier, contactSetAway, listAgents, startAgent, stopAgent, useAgent, inbox, transcript, contactSetMoniker, sealedReceipt, initiate, send, receive,
|
|
17
|
+
import { IPC_METHODS, contactAdd, contactRemove, contactList, contactSetTier, contactSetAway, listAgents, startAgent, stopAgent, useAgent, inbox, transcript, contactSetMoniker, sealedReceipt, initiate, send, receive, closeSession, awaitSession, } from "./parity-commands.js";
|
|
18
18
|
/** Read the whole of stdin — `cello send <id> --stdin` for message text with newlines/quotes. */
|
|
19
19
|
async function readStdin() {
|
|
20
20
|
const chunks = [];
|
|
@@ -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,24 +137,38 @@ 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] ?? "";
|
|
@@ -146,43 +176,249 @@ export const COMMANDS = [
|
|
|
146
176
|
},
|
|
147
177
|
},
|
|
148
178
|
{
|
|
149
|
-
name: "
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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.",
|
|
179
|
+
name: "remove-agent",
|
|
180
|
+
group: "Setup",
|
|
181
|
+
summary: "Retire an agent permanently and free its name. Cannot be undone.",
|
|
182
|
+
help: "Usage: cello remove-agent <name> — retires a local agent (one-way) and frees its name.",
|
|
155
183
|
async run(ctx, args) {
|
|
156
|
-
return legacy(await
|
|
184
|
+
return legacy(await removeAgent(ctx.celloDir, args[0] ?? ""));
|
|
157
185
|
},
|
|
158
186
|
},
|
|
187
|
+
// ═══ Agents — day-to-day control of who is online and who you are acting as ═════════════════
|
|
159
188
|
{
|
|
160
|
-
name: "
|
|
161
|
-
|
|
162
|
-
|
|
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,
|
|
163
196
|
async run(ctx, args) {
|
|
164
|
-
|
|
197
|
+
const { pretty } = parityOpts(args);
|
|
198
|
+
return listAgents(ctx.celloDir, { pretty });
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
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,
|
|
210
|
+
async run(ctx, args) {
|
|
211
|
+
const { pretty, positional } = parityOpts(args);
|
|
212
|
+
return startAgent(ctx.celloDir, positional[0] ?? "", { pretty });
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
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,
|
|
227
|
+
async run(ctx, args) {
|
|
228
|
+
const { pretty, positional } = parityOpts(args);
|
|
229
|
+
return useAgent(ctx.celloDir, positional[0] ?? "", { pretty });
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
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,
|
|
239
|
+
async run(ctx, args) {
|
|
240
|
+
const { pretty, positional } = parityOpts(args);
|
|
241
|
+
return stopAgent(ctx.celloDir, positional[0] ?? "", { pretty });
|
|
165
242
|
},
|
|
166
243
|
},
|
|
167
244
|
{
|
|
168
245
|
name: "refresh",
|
|
169
|
-
|
|
170
|
-
|
|
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.",
|
|
171
257
|
async run(ctx, args) {
|
|
172
258
|
return legacy(await refreshShares(ctx.celloDir, args[0] ?? ""));
|
|
173
259
|
},
|
|
174
260
|
},
|
|
261
|
+
// ═══ Messaging — the conversation itself ════════════════════════════════════════════════════
|
|
175
262
|
{
|
|
176
|
-
name: "
|
|
177
|
-
|
|
178
|
-
|
|
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.",
|
|
269
|
+
flags: AGENT_FLAG,
|
|
270
|
+
ipcMethod: IPC_METHODS["initiate-session"],
|
|
271
|
+
jsonOut: true,
|
|
179
272
|
async run(ctx, args) {
|
|
180
|
-
|
|
273
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
274
|
+
return initiate(ctx.celloDir, positional[0] ?? "", { agent, pretty });
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
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,
|
|
288
|
+
async run(ctx, args) {
|
|
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") });
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
return flagError(err);
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "close-session",
|
|
301
|
+
group: "Messaging",
|
|
302
|
+
summary: "End a session. Both sides sign off and get a tamper-proof receipt.",
|
|
303
|
+
help: "Usage: cello close-session <session-id> [--force] [--agent <name>] [--pretty]\n" +
|
|
304
|
+
" Both parties sign off on the whole conversation and each gets a notarized receipt\n" +
|
|
305
|
+
" ('cello sealed-receipt <session-id>' prints it).\n" +
|
|
306
|
+
" --force abandons a half-open session that can never be sealed (a handshake the counterparty\n" +
|
|
307
|
+
" never joined). It FORFEITS the receipt — never use it on a healthy session.",
|
|
308
|
+
flags: [
|
|
309
|
+
{ name: "--agent", consumesValue: false },
|
|
310
|
+
{ name: "--force", consumesValue: false },
|
|
311
|
+
],
|
|
312
|
+
ipcMethod: IPC_METHODS["close-session"],
|
|
313
|
+
jsonOut: true,
|
|
314
|
+
async run(ctx, args) {
|
|
315
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
316
|
+
const force = positional.includes("--force");
|
|
317
|
+
const rest = positional.filter((a) => a !== "--force");
|
|
318
|
+
return closeSession(ctx.celloDir, rest[0] ?? "", { agent, pretty, force });
|
|
181
319
|
},
|
|
182
320
|
},
|
|
321
|
+
{
|
|
322
|
+
name: "send",
|
|
323
|
+
group: "Messaging",
|
|
324
|
+
// §4: "honors read-before-write" was jargon for a rule the operator meets as a REFUSAL. Say the
|
|
325
|
+
// rule, and say that the tool will tell you.
|
|
326
|
+
summary: "Send a message. Any unread messages must be read first — you'll be told, and blocked until you do.",
|
|
327
|
+
help: "Usage: cello send <session-id> <message…> [--stdin] [--agent <name>] [--pretty]\n" +
|
|
328
|
+
" The message is the remaining arguments, or the whole of stdin with --stdin (for text with\n" +
|
|
329
|
+
" newlines/quotes).\n" +
|
|
330
|
+
" If the other side has said something you have not read, the send is REFUSED and tells you how\n" +
|
|
331
|
+
" many messages are waiting. Read them ('cello receive <session-id>', or 'cello transcript\n" +
|
|
332
|
+
" <session-id>' for the whole conversation) and send again. This is deliberate: you cannot\n" +
|
|
333
|
+
" reply to something you never saw. The refusal is printed verbatim and never auto-fixed.",
|
|
334
|
+
flags: [
|
|
335
|
+
{ name: "--agent", consumesValue: false },
|
|
336
|
+
{ name: "--stdin", consumesValue: false },
|
|
337
|
+
],
|
|
338
|
+
ipcMethod: IPC_METHODS.send,
|
|
339
|
+
jsonOut: true,
|
|
340
|
+
async run(ctx, args) {
|
|
341
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
342
|
+
const useStdin = positional.includes("--stdin");
|
|
343
|
+
const rest = positional.filter((a) => a !== "--stdin");
|
|
344
|
+
const sessionId = rest[0] ?? "";
|
|
345
|
+
const content = useStdin ? await readStdin() : rest.slice(1).join(" ");
|
|
346
|
+
return send(ctx.celloDir, sessionId, content, { agent, pretty });
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: "receive",
|
|
351
|
+
group: "Messaging",
|
|
352
|
+
summary: "Read the next message, or catch up on everything you missed with --since-seq.",
|
|
353
|
+
help: "Usage: cello receive <session-id> [--since-seq N] [--timeout-ms N] [--agent <name>] [--pretty]\n" +
|
|
354
|
+
" Default: WAITS for the next message (up to --timeout-ms, default 30000).\n" +
|
|
355
|
+
" With --since-seq N: returns every message after number N at once, immediately, without\n" +
|
|
356
|
+
" waiting — this is how you catch up after being away. Mirrors cello_receive exactly.",
|
|
357
|
+
flags: [
|
|
358
|
+
{ name: "--agent", consumesValue: false },
|
|
359
|
+
{ name: "--timeout-ms", consumesValue: true },
|
|
360
|
+
{ name: "--since-seq", consumesValue: true },
|
|
361
|
+
],
|
|
362
|
+
ipcMethod: IPC_METHODS.receive,
|
|
363
|
+
jsonOut: true,
|
|
364
|
+
async run(ctx, args) {
|
|
365
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
366
|
+
const since = takeValueFlag(positional, "--since-seq");
|
|
367
|
+
const timeout = takeValueFlag(since.rest, "--timeout-ms");
|
|
368
|
+
try {
|
|
369
|
+
return await receive(ctx.celloDir, timeout.rest[0] ?? "", {
|
|
370
|
+
agent,
|
|
371
|
+
pretty,
|
|
372
|
+
sinceSeq: numberOrUndefined(since.value, "--since-seq"),
|
|
373
|
+
timeoutMs: numberOrUndefined(timeout.value, "--timeout-ms"),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
return flagError(err);
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: "inbox",
|
|
383
|
+
group: "Messaging",
|
|
384
|
+
summary: "See who tried to reach you and what is unread, without reading anything.",
|
|
385
|
+
help: "Usage: cello inbox [--scope current|all] [--agent <name>] [--pretty] — what did I miss?\n" +
|
|
386
|
+
" Shows pending session requests and unread message COUNTS — never message content, and it\n" +
|
|
387
|
+
" does not mark anything as read ('cello receive' does that). Use it after being away.\n" +
|
|
388
|
+
" --scope all covers every agent you have, not just the current one.",
|
|
389
|
+
flags: [
|
|
390
|
+
{ name: "--agent", consumesValue: false },
|
|
391
|
+
{ name: "--scope", consumesValue: true },
|
|
392
|
+
],
|
|
393
|
+
ipcMethod: IPC_METHODS.inbox,
|
|
394
|
+
jsonOut: true,
|
|
395
|
+
async run(ctx, args) {
|
|
396
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
397
|
+
const { value } = takeValueFlag(positional, "--scope");
|
|
398
|
+
// Review F6: an UNRECOGNIZED scope must not silently become the default. A typo'd
|
|
399
|
+
// `--scope all` (e.g. "al") would have answered with `current`'s data and exit 0 — the
|
|
400
|
+
// operator reads "no notifications" while another agent's inbox is full.
|
|
401
|
+
if (value !== undefined && value !== "all" && value !== "current") {
|
|
402
|
+
return {
|
|
403
|
+
stdout: "",
|
|
404
|
+
stderr: JSON.stringify({
|
|
405
|
+
ok: false,
|
|
406
|
+
reason: "invalid_flag_value",
|
|
407
|
+
flag: "--scope",
|
|
408
|
+
value,
|
|
409
|
+
guidance: "--scope must be 'current' or 'all'. The command was NOT run — answering a different question than the one asked is worse than refusing.",
|
|
410
|
+
}),
|
|
411
|
+
exitCode: 1,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return inbox(ctx.celloDir, { agent, pretty, scope: value });
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
// ═══ Sessions & receipts — what the conversations left behind ═══════════════════════════════
|
|
183
418
|
{
|
|
184
419
|
name: "sessions",
|
|
185
|
-
|
|
420
|
+
group: "Sessions & receipts",
|
|
421
|
+
summary: "List your sessions (open by default; --all/--closed/--failed to filter).",
|
|
186
422
|
help: "Usage: cello sessions [--open|--closed|--failed|--all] [--limit N] — list session history (defaults to open).",
|
|
187
423
|
flags: [
|
|
188
424
|
{ name: "--open" },
|
|
@@ -211,57 +447,132 @@ export const COMMANDS = [
|
|
|
211
447
|
return legacy(await sessions(ctx.celloDir, { filter, limit }));
|
|
212
448
|
},
|
|
213
449
|
},
|
|
450
|
+
{
|
|
451
|
+
name: "transcript",
|
|
452
|
+
group: "Sessions & receipts",
|
|
453
|
+
summary: "Print the full conversation for a session — everything sent and received.",
|
|
454
|
+
help: "Usage: cello transcript <session-id> [--agent <name>] [--pretty] — the whole conversation.\n" +
|
|
455
|
+
" Sent AND received messages, in order. Stored on disk, so it survives a daemon restart.\n" +
|
|
456
|
+
" Reading it also catches you up, which un-blocks 'cello send' after you have been away.",
|
|
457
|
+
flags: AGENT_FLAG,
|
|
458
|
+
ipcMethod: IPC_METHODS.transcript,
|
|
459
|
+
jsonOut: true,
|
|
460
|
+
async run(ctx, args) {
|
|
461
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
462
|
+
return transcript(ctx.celloDir, positional[0] ?? "", { agent, pretty });
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "sealed-receipt",
|
|
467
|
+
group: "Sessions & receipts",
|
|
468
|
+
// THE one users want. Named and described so it cannot be confused with relay-receipts.
|
|
469
|
+
summary: "Print a closed session's notarized receipt — proof both sides signed off on the conversation.",
|
|
470
|
+
help: "Usage: cello sealed-receipt <session-id> [--agent <name>] [--pretty] — the NOTARIZED receipt.\n" +
|
|
471
|
+
" This is the proof CELLO exists to produce: when a session closes, both parties sign off on\n" +
|
|
472
|
+
" the whole conversation and the directory notarizes it. The receipt is tamper-evident — if a\n" +
|
|
473
|
+
" single message were altered, added or dropped, it would no longer match.\n" +
|
|
474
|
+
" It attests RECEIPT, never agreement (implies_assent: false) — an unanswered last message\n" +
|
|
475
|
+
" reads as delivered-but-unanswered, never as consent.\n" +
|
|
476
|
+
" NOT the same as 'cello relay-receipts', which is a low-level delivery-plumbing artifact.",
|
|
477
|
+
flags: AGENT_FLAG,
|
|
478
|
+
ipcMethod: IPC_METHODS["sealed-receipt"],
|
|
479
|
+
jsonOut: true,
|
|
480
|
+
async run(ctx, args) {
|
|
481
|
+
const { agent, pretty, positional } = parityOpts(args);
|
|
482
|
+
return sealedReceipt(ctx.celloDir, positional[0] ?? "", { agent, pretty });
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "relay-receipts",
|
|
487
|
+
group: "Sessions & receipts",
|
|
488
|
+
// VERIFIED against the handler (cello_get_relay_receipts → getRelayReceipts): per-MESSAGE
|
|
489
|
+
// signatures from a RELAY attesting it handled and ordered that message. Renamed from
|
|
490
|
+
// `receipts` because a name that differed from `sealed-receipt` by one plural could not be
|
|
491
|
+
// rescued by any description — Andre could not tell them apart, and he wrote the protocol.
|
|
492
|
+
summary: "Advanced/debug: per-message proofs signed by a relay. Not the session receipt — see 'sealed-receipt'.",
|
|
493
|
+
help: "Usage: cello relay-receipts <name> — ADVANCED / DEBUG. You almost certainly want\n" +
|
|
494
|
+
" 'cello sealed-receipt <session-id>' instead.\n" +
|
|
495
|
+
" When a message cannot go directly to the other agent (they are offline, or the network is in\n" +
|
|
496
|
+
" the way), it goes via a relay. The relay signs a small receipt saying it handled that message\n" +
|
|
497
|
+
" and where it fell in the order. This lists those — a plumbing artifact for diagnosing\n" +
|
|
498
|
+
" delivery, one per message.\n" +
|
|
499
|
+
" It says NOTHING about the conversation being agreed or sealed. That is 'cello sealed-receipt'.",
|
|
500
|
+
async run(ctx, args) {
|
|
501
|
+
return legacy(await relayReceipts(ctx.celloDir, args[0] ?? ""));
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
// ═══ Contacts — the address book (plural) and one contact (singular) ════════════════════════
|
|
505
|
+
{
|
|
506
|
+
name: "contacts",
|
|
507
|
+
group: "Contacts",
|
|
508
|
+
summary: "List your address book — everyone this agent knows, and how much they're trusted.",
|
|
509
|
+
help: "Usage: cello contacts [--agent <name>] [--pretty] — list the whole address book.\n" +
|
|
510
|
+
" Contacts are added automatically when you open a session with someone, or accept theirs.\n" +
|
|
511
|
+
" To act on ONE contact, use 'cello contact <pubkey> <operation>'.\n" +
|
|
512
|
+
" --agent defaults to the current agent (or the only online one).",
|
|
513
|
+
flags: AGENT_FLAG,
|
|
514
|
+
jsonOut: true,
|
|
515
|
+
ipcMethod: IPC_METHODS.contacts,
|
|
516
|
+
async run(ctx, args) {
|
|
517
|
+
const { agent, pretty } = parityOpts(args);
|
|
518
|
+
return contactList(ctx.celloDir, { agent, pretty });
|
|
519
|
+
},
|
|
520
|
+
},
|
|
214
521
|
{
|
|
215
522
|
name: "contact",
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
"
|
|
220
|
-
"
|
|
221
|
-
"
|
|
222
|
-
"
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"
|
|
523
|
+
group: "Contacts",
|
|
524
|
+
summary: "Act on ONE contact: add, remove, set-tier, set-away, set-moniker.",
|
|
525
|
+
help: "Usage: cello contact <pubkey> <operation> [args] [--agent <name>] [--pretty]\n" +
|
|
526
|
+
"\n" +
|
|
527
|
+
" Operations:\n" +
|
|
528
|
+
" add add this peer to the address book\n" +
|
|
529
|
+
" remove remove them (they go back to being a stranger)\n" +
|
|
530
|
+
" set-tier <0..4> how much they're trusted: 0=blocked, 1=stranger, 2=known,\n" +
|
|
531
|
+
" 3=trusted (reaches you even when you're away), 4=vip.\n" +
|
|
532
|
+
" A higher tier RAISES their limits; it never removes screening.\n" +
|
|
533
|
+
" set-away <message…> what THIS person hears when you're away (empty clears it)\n" +
|
|
534
|
+
" set-moniker <name> YOUR pet name for THEM (empty clears it). Always wins over the\n" +
|
|
535
|
+
" name they offer — the one thing they cannot spoof.\n" +
|
|
536
|
+
"\n" +
|
|
537
|
+
" To list the whole book, use 'cello contacts'.\n" +
|
|
538
|
+
" Note: 'set-moniker' names a CONTACT. 'cello moniker' sets your OWN outbound name.\n" +
|
|
539
|
+
" Example: cello contact 178d420b… set-tier 3 --agent alice",
|
|
226
540
|
flags: AGENT_FLAG,
|
|
227
541
|
jsonOut: true, // review F3: the WHOLE address book honors §3 — one command, one contract
|
|
228
542
|
async run(ctx, args) {
|
|
229
543
|
const { agent, pretty, positional } = parityOpts(args);
|
|
230
544
|
const o = { agent, pretty };
|
|
231
|
-
|
|
232
|
-
|
|
545
|
+
// §3 SHAPE: `contact <pubkey> <op>` — the subject first, then what to do to them. (The old
|
|
546
|
+
// shape was `contact <op> <pubkey>`, which read like a verb table rather than an address book.)
|
|
547
|
+
const [pubkey, op, valueArg] = positional;
|
|
548
|
+
if (!pubkey || !op)
|
|
549
|
+
return { stdout: helpForSpec("contact"), stderr: "", exitCode: 1 };
|
|
550
|
+
if (op === "add")
|
|
233
551
|
return contactAdd(ctx.celloDir, pubkey, o);
|
|
234
|
-
if (
|
|
552
|
+
if (op === "remove")
|
|
235
553
|
return contactRemove(ctx.celloDir, pubkey, o);
|
|
236
|
-
if (
|
|
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) {
|
|
554
|
+
if (op === "set-tier" && valueArg !== undefined) {
|
|
241
555
|
// Daemon validates the value; a non-numeric arg surfaces as its invalid_tier verdict.
|
|
242
556
|
return contactSetTier(ctx.celloDir, pubkey, Number(valueArg), o);
|
|
243
557
|
}
|
|
244
|
-
if (
|
|
558
|
+
if (op === "set-away") {
|
|
245
559
|
// The rest of the args form the away text; empty → clear.
|
|
246
560
|
const message = positional.slice(2).join(" ");
|
|
247
561
|
return contactSetAway(ctx.celloDir, pubkey, message.length > 0 ? message : null, o);
|
|
248
562
|
}
|
|
249
|
-
if (
|
|
250
|
-
// DOD-CLI-PARITY-1: the per-CONTACT pet name (cello_contact_set_moniker) — was MCP-only.
|
|
563
|
+
if (op === "set-moniker") {
|
|
251
564
|
// Empty → null clears it, mirroring the tool.
|
|
252
565
|
const moniker = positional.slice(2).join(" ");
|
|
253
566
|
return contactSetMoniker(ctx.celloDir, pubkey, moniker.length > 0 ? moniker : null, o);
|
|
254
567
|
}
|
|
255
|
-
return {
|
|
256
|
-
stdout: helpForSpec("contact"),
|
|
257
|
-
stderr: "",
|
|
258
|
-
exitCode: 1,
|
|
259
|
-
};
|
|
568
|
+
return { stdout: helpForSpec("contact"), stderr: "", exitCode: 1 };
|
|
260
569
|
},
|
|
261
570
|
},
|
|
571
|
+
// ═══ Other ══════════════════════════════════════════════════════════════════════════════════
|
|
262
572
|
{
|
|
263
573
|
name: "settings",
|
|
264
|
-
|
|
574
|
+
group: "Other",
|
|
575
|
+
summary: "Get or set how reachable an agent is (limits per trust tier, away messages).",
|
|
265
576
|
help: "Usage: cello settings get [key] [--agent <name>] | cello settings set <key> <value> [--agent <name>]\n" +
|
|
266
577
|
" Per-agent reachability policy (DOD-SETTINGS-1). Keys: bounds.<tier>.max_sessions, bounds.<tier>.max_bytes\n" +
|
|
267
578
|
" (tier = unknown|known|whitelisted|vip; a finite positive integer), away.default, away.tier.<tier> (away text).\n" +
|
|
@@ -284,12 +595,15 @@ export const COMMANDS = [
|
|
|
284
595
|
},
|
|
285
596
|
{
|
|
286
597
|
name: "moniker",
|
|
287
|
-
|
|
598
|
+
group: "Other",
|
|
599
|
+
summary: "Set the name OTHERS see when this agent contacts them (like caller ID).",
|
|
288
600
|
help: "Usage: cello moniker set <name> [--agent <agent>] | cello moniker clear [--agent <agent>]\n" +
|
|
289
|
-
"
|
|
601
|
+
" Your OUTBOUND name — what shows up on the counterparty's screen when you reach them.\n" +
|
|
602
|
+
" Defaults to the agent name; 'set' overrides it, 'clear' restores the default.\n" +
|
|
290
603
|
// MONIKER-0 AC2: the regex text is DERIVED from the shared constant, never hand-typed.
|
|
291
604
|
` Name rule: 1–64 characters, letters/digits/'-'/'_' only, no spaces (regex ${MONIKER_RE.source}).\n` +
|
|
292
|
-
"
|
|
605
|
+
" It is a HINT, not proof — like caller ID, the receiver is shown it as self-declared and can\n" +
|
|
606
|
+
" override it with their own pet name for you. Never sent to the directory.\n" +
|
|
293
607
|
" Example: cello moniker set Wonderland_Alice --agent alice",
|
|
294
608
|
flags: AGENT_FLAG,
|
|
295
609
|
async run(ctx, args) {
|
|
@@ -304,9 +618,12 @@ export const COMMANDS = [
|
|
|
304
618
|
},
|
|
305
619
|
{
|
|
306
620
|
name: "telegram",
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
621
|
+
group: "Other",
|
|
622
|
+
summary: "Connect a Telegram bot to your daemon for notifications, status updates, etc.",
|
|
623
|
+
help: "Usage: cello telegram set-token <bot_token> <allowlisted_chat_id>\n" +
|
|
624
|
+
" Connects a Telegram bot to your daemon so you get notified there (someone reaching you,\n" +
|
|
625
|
+
" status updates, and more over time). Starts polling immediately.\n" +
|
|
626
|
+
" The chat id you give is the ONLY chat that ever receives anything.",
|
|
310
627
|
async run(ctx, args) {
|
|
311
628
|
const [sub, botToken, chatId] = args;
|
|
312
629
|
if (sub === "set-token" && botToken && chatId) {
|
|
@@ -316,23 +633,32 @@ export const COMMANDS = [
|
|
|
316
633
|
},
|
|
317
634
|
},
|
|
318
635
|
{
|
|
319
|
-
name: "
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
636
|
+
name: "bridge",
|
|
637
|
+
group: "Other",
|
|
638
|
+
// Renamed from `install`, which read as "install CELLO itself" and hardcoded Hermes — the
|
|
639
|
+
// runtime is a PARAMETER. More runtimes are coming; the description must not claim otherwise.
|
|
640
|
+
summary: "Bridge CELLO into a third-party agent runtime (Hermes, OpenClaw, …).",
|
|
641
|
+
help: "Usage: cello bridge <runtime> --agent <name> [--hermes-home <path>]\n" +
|
|
642
|
+
" Wires the local CELLO daemon into a third-party agent runtime so that agent can use CELLO.\n" +
|
|
643
|
+
" Supported runtimes: hermes (more coming).\n" +
|
|
644
|
+
"\n" +
|
|
645
|
+
" hermes: scaffolds the CELLO plugin into the Hermes home (default ~/.hermes), binds\n" +
|
|
646
|
+
" CELLO_AGENT_NAME in its .env, and registers via 'hermes plugins enable cello' +\n" +
|
|
647
|
+
" 'hermes mcp add cello'. Idempotent — re-run to upgrade.\n" +
|
|
648
|
+
" Afterwards, restart the gateway: hermes gateway restart\n" +
|
|
649
|
+
"\n" +
|
|
650
|
+
" Example: cello bridge hermes --agent alice",
|
|
325
651
|
flags: [{ name: "--agent" }, { name: "--hermes-home" }],
|
|
326
652
|
async run(_ctx, args) {
|
|
327
653
|
const agentIdx = args.indexOf("--agent");
|
|
328
654
|
const homeIdx = args.indexOf("--hermes-home");
|
|
329
655
|
// Find the target positional, excluding both flags AND their values — so
|
|
330
|
-
// `cello
|
|
656
|
+
// `cello bridge --agent alice hermes` still resolves target=hermes.
|
|
331
657
|
const target = args.find((a, i) => !a.startsWith("-") &&
|
|
332
658
|
!(agentIdx !== -1 && i === agentIdx + 1) &&
|
|
333
659
|
!(homeIdx !== -1 && i === homeIdx + 1));
|
|
334
660
|
if (target !== "hermes") {
|
|
335
|
-
return { stdout: helpForSpec("
|
|
661
|
+
return { stdout: helpForSpec("bridge"), stderr: "", exitCode: 1 };
|
|
336
662
|
}
|
|
337
663
|
const { installHermes } = await import("./hermes/install-hermes.js");
|
|
338
664
|
return legacy(await installHermes({
|
|
@@ -341,258 +667,6 @@ export const COMMANDS = [
|
|
|
341
667
|
}));
|
|
342
668
|
},
|
|
343
669
|
},
|
|
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
|
-
{
|
|
348
|
-
name: "agents",
|
|
349
|
-
summary: "List every loaded agent and whether it is online.",
|
|
350
|
-
help: "Usage: cello agents [--pretty] — list all loaded agents (name, state).\n" +
|
|
351
|
-
" The CLI twin of the cello_list_agents MCP tool. Prints JSON; use --pretty for humans.",
|
|
352
|
-
ipcMethod: IPC_METHODS.agents,
|
|
353
|
-
jsonOut: true,
|
|
354
|
-
async run(ctx, args) {
|
|
355
|
-
const { pretty } = parityOpts(args);
|
|
356
|
-
return listAgents(ctx.celloDir, { pretty });
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
name: "start-agent",
|
|
361
|
-
summary: "Bring an agent online (without selecting it as current).",
|
|
362
|
-
help: "Usage: cello start-agent <name> [--pretty] — bring a registered agent ONLINE.\n" +
|
|
363
|
-
" Does NOT select it as the current agent — use 'cello use-agent <name>' for that.\n" +
|
|
364
|
-
" Idempotent: starting an already-online agent is safe.",
|
|
365
|
-
ipcMethod: IPC_METHODS["start-agent"],
|
|
366
|
-
jsonOut: true,
|
|
367
|
-
async run(ctx, args) {
|
|
368
|
-
const { pretty, positional } = parityOpts(args);
|
|
369
|
-
return startAgent(ctx.celloDir, positional[0] ?? "", { pretty });
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
name: "stop-agent",
|
|
374
|
-
summary: "Take an agent offline.",
|
|
375
|
-
help: "Usage: cello stop-agent <name> [--pretty] — take an agent offline.",
|
|
376
|
-
ipcMethod: IPC_METHODS["stop-agent"],
|
|
377
|
-
jsonOut: true,
|
|
378
|
-
async run(ctx, args) {
|
|
379
|
-
const { pretty, positional } = parityOpts(args);
|
|
380
|
-
return stopAgent(ctx.celloDir, positional[0] ?? "", { pretty });
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
{
|
|
384
|
-
name: "use-agent",
|
|
385
|
-
summary: "Select the agent that later commands act as (auto-starts it; persists).",
|
|
386
|
-
help: "Usage: cello use-agent <name> [--pretty] — select the CURRENT agent for later commands.\n" +
|
|
387
|
-
" Auto-starts the agent if it is offline (AUTOSTART-1).\n" +
|
|
388
|
-
" The selection PERSISTS across invocations (recorded in <cello-dir>/current-agent), because\n" +
|
|
389
|
-
" each CLI command opens its own daemon connection — a selection that lived only on the socket\n" +
|
|
390
|
-
" would vanish the moment the command exited. Override per-command with '--agent <name>'.\n" +
|
|
391
|
-
" A selection the daemon rejects is not recorded.",
|
|
392
|
-
ipcMethod: IPC_METHODS["use-agent"],
|
|
393
|
-
jsonOut: true,
|
|
394
|
-
async run(ctx, args) {
|
|
395
|
-
const { pretty, positional } = parityOpts(args);
|
|
396
|
-
return useAgent(ctx.celloDir, positional[0] ?? "", { pretty });
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
name: "inbox",
|
|
401
|
-
summary: "Check pending session requests and unread counts (the push-loss reconciler).",
|
|
402
|
-
help: "Usage: cello inbox [--scope current|all] [--agent <name>] [--pretty] — poll for what you missed.\n" +
|
|
403
|
-
" Content-free: pending session requests + unread message counts. Non-destructive (it does not\n" +
|
|
404
|
-
" drain anything — 'cello await-session' owns that). --scope all covers every loaded agent.",
|
|
405
|
-
flags: [
|
|
406
|
-
{ name: "--agent", consumesValue: false },
|
|
407
|
-
{ name: "--scope", consumesValue: true },
|
|
408
|
-
],
|
|
409
|
-
ipcMethod: IPC_METHODS.inbox,
|
|
410
|
-
jsonOut: true,
|
|
411
|
-
async run(ctx, args) {
|
|
412
|
-
const { agent, pretty, positional } = parityOpts(args);
|
|
413
|
-
const { value } = takeValueFlag(positional, "--scope");
|
|
414
|
-
// Review F6: an UNRECOGNIZED scope must not silently become the default. A typo'd
|
|
415
|
-
// `--scope all` (e.g. "al") would have answered with `current`'s data and exit 0 — the
|
|
416
|
-
// operator reads "no notifications" while another agent's inbox is full.
|
|
417
|
-
if (value !== undefined && value !== "all" && value !== "current") {
|
|
418
|
-
return {
|
|
419
|
-
stdout: "",
|
|
420
|
-
stderr: JSON.stringify({
|
|
421
|
-
ok: false,
|
|
422
|
-
reason: "invalid_flag_value",
|
|
423
|
-
flag: "--scope",
|
|
424
|
-
value,
|
|
425
|
-
guidance: "--scope must be 'current' or 'all'. The command was NOT run — answering a different question than the one asked is worse than refusing.",
|
|
426
|
-
}),
|
|
427
|
-
exitCode: 1,
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
return inbox(ctx.celloDir, { agent, pretty, scope: value });
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
|
-
{
|
|
434
|
-
name: "transcript",
|
|
435
|
-
summary: "Print a session's durable conversation transcript (sent + received).",
|
|
436
|
-
help: "Usage: cello transcript <session-id> [--agent <name>] [--pretty] — the durable transcript.\n" +
|
|
437
|
-
" Sent AND received messages in order; survives a daemon restart. This is also how you satisfy\n" +
|
|
438
|
-
" read-before-write after being away: read it, then 'cello send' is accepted.",
|
|
439
|
-
flags: AGENT_FLAG,
|
|
440
|
-
ipcMethod: IPC_METHODS.transcript,
|
|
441
|
-
jsonOut: true,
|
|
442
|
-
async run(ctx, args) {
|
|
443
|
-
const { agent, pretty, positional } = parityOpts(args);
|
|
444
|
-
return transcript(ctx.celloDir, positional[0] ?? "", { agent, pretty });
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
name: "sealed-receipt",
|
|
449
|
-
summary: "Print a closed session's notarized bilateral seal receipt.",
|
|
450
|
-
help: "Usage: cello sealed-receipt <session-id> [--agent <name>] [--pretty] — the NOTARIZED receipt.\n" +
|
|
451
|
-
" The artifact the bilateral close ceremony produces: per-party content frontiers and the sealed\n" +
|
|
452
|
-
" root both sides agree on. It attests RECEIPT, never assent (implies_assent: false) — an\n" +
|
|
453
|
-
" unanswered final message reads as delivered-but-unanswered, never as agreement.\n" +
|
|
454
|
-
" Distinct from 'cello receipts <name>', which lists RELAY ORDERING receipts (a different thing).",
|
|
455
|
-
flags: AGENT_FLAG,
|
|
456
|
-
ipcMethod: IPC_METHODS["sealed-receipt"],
|
|
457
|
-
jsonOut: true,
|
|
458
|
-
async run(ctx, args) {
|
|
459
|
-
const { agent, pretty, positional } = parityOpts(args);
|
|
460
|
-
return sealedReceipt(ctx.celloDir, positional[0] ?? "", { agent, pretty });
|
|
461
|
-
},
|
|
462
|
-
},
|
|
463
|
-
// ─── Group B: live conversation ───────────────────────────────────────────────────────────
|
|
464
|
-
{
|
|
465
|
-
name: "initiate",
|
|
466
|
-
summary: "Start a session with a target agent (by pubkey). Prints the session_id.",
|
|
467
|
-
help: "Usage: cello initiate <target-pubkey> [--agent <name>] [--pretty] — open a session.\n" +
|
|
468
|
-
" <target-pubkey> is the counterparty's hex public key. Prints the session_id you then pass to\n" +
|
|
469
|
-
" 'cello send' / 'cello receive' / 'cello close'. Adds the counterparty to your address book.",
|
|
470
|
-
flags: AGENT_FLAG,
|
|
471
|
-
ipcMethod: IPC_METHODS.initiate,
|
|
472
|
-
jsonOut: true,
|
|
473
|
-
async run(ctx, args) {
|
|
474
|
-
const { agent, pretty, positional } = parityOpts(args);
|
|
475
|
-
return initiate(ctx.celloDir, positional[0] ?? "", { agent, pretty });
|
|
476
|
-
},
|
|
477
|
-
},
|
|
478
|
-
{
|
|
479
|
-
name: "send",
|
|
480
|
-
summary: "Send a message in a session (honors read-before-write).",
|
|
481
|
-
help: "Usage: cello send <session-id> <message…> [--stdin] [--agent <name>] [--pretty]\n" +
|
|
482
|
-
" The message is the remaining arguments, or the whole of stdin with --stdin (for text with\n" +
|
|
483
|
-
" newlines/quotes). READ-BEFORE-WRITE: if the counterparty has spoken since you last read, the\n" +
|
|
484
|
-
" daemon rejects the send with session_not_current and its cursor — that verdict is printed\n" +
|
|
485
|
-
" verbatim and NOT auto-fixed. Catch up with 'cello transcript <session-id>', then resend.",
|
|
486
|
-
flags: [
|
|
487
|
-
{ name: "--agent", consumesValue: false },
|
|
488
|
-
{ name: "--stdin", consumesValue: false },
|
|
489
|
-
],
|
|
490
|
-
ipcMethod: IPC_METHODS.send,
|
|
491
|
-
jsonOut: true,
|
|
492
|
-
async run(ctx, args) {
|
|
493
|
-
const { agent, pretty, positional } = parityOpts(args);
|
|
494
|
-
const useStdin = positional.includes("--stdin");
|
|
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 });
|
|
499
|
-
},
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
name: "receive",
|
|
503
|
-
summary: "Receive the next message, or catch up in a batch with --since-seq.",
|
|
504
|
-
help: "Usage: cello receive <session-id> [--since-seq N] [--timeout-ms N] [--agent <name>] [--pretty]\n" +
|
|
505
|
-
" Default: BLOCKS for the next live message (up to --timeout-ms, default 30000).\n" +
|
|
506
|
-
" With --since-seq N: stateless CATCH-UP — returns every message after sequence N as a batch,\n" +
|
|
507
|
-
" immediately (no replay race, --timeout-ms ignored). Mirrors cello_receive exactly.",
|
|
508
|
-
flags: [
|
|
509
|
-
{ name: "--agent", consumesValue: false },
|
|
510
|
-
{ name: "--timeout-ms", consumesValue: true },
|
|
511
|
-
{ name: "--since-seq", consumesValue: true },
|
|
512
|
-
],
|
|
513
|
-
ipcMethod: IPC_METHODS.receive,
|
|
514
|
-
jsonOut: true,
|
|
515
|
-
async run(ctx, args) {
|
|
516
|
-
const { agent, pretty, positional } = parityOpts(args);
|
|
517
|
-
const since = takeValueFlag(positional, "--since-seq");
|
|
518
|
-
const timeout = takeValueFlag(since.rest, "--timeout-ms");
|
|
519
|
-
try {
|
|
520
|
-
return await receive(ctx.celloDir, timeout.rest[0] ?? "", {
|
|
521
|
-
agent,
|
|
522
|
-
pretty,
|
|
523
|
-
sinceSeq: numberOrUndefined(since.value, "--since-seq"),
|
|
524
|
-
timeoutMs: numberOrUndefined(timeout.value, "--timeout-ms"),
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
catch (err) {
|
|
528
|
-
return flagError(err);
|
|
529
|
-
}
|
|
530
|
-
},
|
|
531
|
-
},
|
|
532
|
-
{
|
|
533
|
-
name: "receive-session",
|
|
534
|
-
summary: "Accept / join an inbound session request.",
|
|
535
|
-
help: "Usage: cello receive-session <session-id> [--timeout-ms N] [--agent <name>] [--pretty]\n" +
|
|
536
|
-
" Joins an inbound session (the one 'cello await-session' told you about).",
|
|
537
|
-
flags: AGENT_AND_TIMEOUT,
|
|
538
|
-
ipcMethod: IPC_METHODS["receive-session"],
|
|
539
|
-
jsonOut: true,
|
|
540
|
-
async run(ctx, args) {
|
|
541
|
-
const { agent, pretty, positional } = parityOpts(args);
|
|
542
|
-
const timeout = takeValueFlag(positional, "--timeout-ms");
|
|
543
|
-
try {
|
|
544
|
-
return await receiveSession(ctx.celloDir, timeout.rest[0] ?? "", {
|
|
545
|
-
agent,
|
|
546
|
-
pretty,
|
|
547
|
-
timeoutMs: numberOrUndefined(timeout.value, "--timeout-ms"),
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
catch (err) {
|
|
551
|
-
return flagError(err);
|
|
552
|
-
}
|
|
553
|
-
},
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
name: "close",
|
|
557
|
-
summary: "Close a session — triggers the bilateral seal ceremony.",
|
|
558
|
-
help: "Usage: cello close <session-id> [--force] [--agent <name>] [--pretty]\n" +
|
|
559
|
-
" Normally runs the bilateral SEAL ceremony: both parties get a notarized receipt.\n" +
|
|
560
|
-
" --force abandons a half-open session that can never be sealed (a handshake the counterparty\n" +
|
|
561
|
-
" never joined). It FORFEITS the receipt — never use it on a healthy session.",
|
|
562
|
-
flags: [
|
|
563
|
-
{ name: "--agent", consumesValue: false },
|
|
564
|
-
{ name: "--force", consumesValue: false },
|
|
565
|
-
],
|
|
566
|
-
ipcMethod: IPC_METHODS.close,
|
|
567
|
-
jsonOut: true,
|
|
568
|
-
async run(ctx, args) {
|
|
569
|
-
const { agent, pretty, positional } = parityOpts(args);
|
|
570
|
-
const force = positional.includes("--force");
|
|
571
|
-
const rest = positional.filter((a) => a !== "--force");
|
|
572
|
-
return closeSession(ctx.celloDir, rest[0] ?? "", { agent, pretty, force });
|
|
573
|
-
},
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
name: "await-session",
|
|
577
|
-
summary: "Block until an inbound session request arrives (the doorbell).",
|
|
578
|
-
help: "Usage: cello await-session [--timeout-ms N] [--agent <name>] [--pretty]\n" +
|
|
579
|
-
" BLOCKS until someone opens a session with you (default 30000ms), then prints the request.\n" +
|
|
580
|
-
" On expiry it returns {\"type\":\"timeout\"} and exits 0 — a timeout is a normal answer, not an\n" +
|
|
581
|
-
" error (this mirrors cello_await_session exactly). Branch on .type in scripts.",
|
|
582
|
-
flags: AGENT_AND_TIMEOUT,
|
|
583
|
-
ipcMethod: IPC_METHODS["await-session"],
|
|
584
|
-
jsonOut: true,
|
|
585
|
-
async run(ctx, args) {
|
|
586
|
-
const { agent, pretty, positional } = parityOpts(args);
|
|
587
|
-
const timeout = takeValueFlag(positional, "--timeout-ms");
|
|
588
|
-
try {
|
|
589
|
-
return await awaitSession(ctx.celloDir, { agent, pretty, timeoutMs: numberOrUndefined(timeout.value, "--timeout-ms") });
|
|
590
|
-
}
|
|
591
|
-
catch (err) {
|
|
592
|
-
return flagError(err);
|
|
593
|
-
}
|
|
594
|
-
},
|
|
595
|
-
},
|
|
596
670
|
];
|
|
597
671
|
export function commandNames() {
|
|
598
672
|
return COMMANDS.map((c) => c.name);
|
|
@@ -630,12 +704,19 @@ export function flagsFor(name) {
|
|
|
630
704
|
return map;
|
|
631
705
|
}
|
|
632
706
|
/**
|
|
633
|
-
* DOD-ONBOARD-HELP-1: render the
|
|
634
|
-
*
|
|
707
|
+
* DOD-ONBOARD-HELP-1 §1: render the `Commands:` table GROUPED and in logical order.
|
|
708
|
+
*
|
|
709
|
+
* Flat-and-arbitrary was the reopen: `register` appeared before `create-agent`, so the table
|
|
710
|
+
* literally listed step 2 above step 1. Sections in GROUP_ORDER, commands in declaration order
|
|
711
|
+
* within each — the order a reader would actually do them. Name column is padded across the WHOLE
|
|
712
|
+
* table (not per group) so the summaries line up as one column down the page.
|
|
635
713
|
*/
|
|
636
714
|
export function renderCommandsTable() {
|
|
637
715
|
const width = Math.max(...COMMANDS.map((c) => c.name.length));
|
|
638
|
-
const
|
|
639
|
-
|
|
716
|
+
const sections = GROUP_ORDER.map((group) => {
|
|
717
|
+
const rows = COMMANDS.filter((c) => c.group === group).map((c) => ` ${c.name.padEnd(width)} ${c.summary}`);
|
|
718
|
+
return rows.length === 0 ? null : `${group}:\n${rows.join("\n")}`;
|
|
719
|
+
}).filter((s) => s !== null);
|
|
720
|
+
return sections.join("\n\n");
|
|
640
721
|
}
|
|
641
722
|
//# sourceMappingURL=registry.js.map
|