run402 4.32.0 → 4.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.mjs +61 -39
- package/lib/claims.mjs +2 -2
- package/lib/command-manifest.mjs +26 -6
- package/lib/messages.mjs +227 -0
- package/lib/rooms-context.mjs +1 -1
- package/lib/rooms.mjs +60 -208
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -18,62 +18,79 @@ https://run402.com
|
|
|
18
18
|
Usage:
|
|
19
19
|
run402 <command> [subcommand] [options]
|
|
20
20
|
|
|
21
|
-
Commands:
|
|
22
|
-
|
|
21
|
+
Commands, grouped by when you reach for them:
|
|
22
|
+
|
|
23
|
+
SET UP — get an agent funded and entitled
|
|
23
24
|
init Set up allowance, funding, and check tier status (x402 default)
|
|
24
25
|
init mpp Set up with MPP payment rail (Tempo Moderato testnet)
|
|
25
|
-
pay Call an arbitrary x402-priced URL with a bounded payment
|
|
26
|
-
redeem Redeem a promo code for run402 credit
|
|
27
|
-
status Show full account state (allowance, balance, tier, projects)
|
|
28
26
|
wallets Manage multiple named wallets (list, new, use, rename, bind, import)
|
|
29
|
-
credentials Manage local credential material (project-keys)
|
|
30
27
|
allowance Manage your agent allowance (create, fund, balance, status)
|
|
31
28
|
tier Manage tier subscription (status, set)
|
|
29
|
+
redeem Redeem a promo code for run402 credit
|
|
30
|
+
pay Call an arbitrary x402-priced URL with a bounded payment
|
|
31
|
+
|
|
32
|
+
BUILD & SHIP — the app and everything it serves
|
|
33
|
+
up Provision/link/deploy the current app with SDK orchestration
|
|
32
34
|
projects Manage projects (provision, list, get, sql, delete)
|
|
35
|
+
deploy Unified deploy operations (requires active tier)
|
|
33
36
|
apply Alias for deploy apply; supports --rehearse for migration rehearsal
|
|
37
|
+
functions Manage serverless functions (deploy, invoke, logs, list, delete)
|
|
38
|
+
secrets Manage project secrets (set, list, delete)
|
|
39
|
+
sites Deploy static sites
|
|
40
|
+
assets Direct-to-S3 asset storage (put, get, ls, rm, sign, diagnose) — up to 5 TiB
|
|
41
|
+
domains Manage ProjectDomain lifecycle (connect, check, repair, wait)
|
|
42
|
+
subdomains Manage custom subdomains (claim, list, delete)
|
|
43
|
+
|
|
44
|
+
OPERATE — what happened, and is it healthy
|
|
45
|
+
events What happened to your project since you last looked (cursored feed)
|
|
46
|
+
errors Grouped error fingerprints + a promote/revert verdict (release-baselined)
|
|
47
|
+
logs Fetch function logs by request id (--request-id req_...)
|
|
48
|
+
status Show full account state (allowance, balance, tier, projects)
|
|
49
|
+
doctor Health and config diagnostics (JSON by default; includes --buzz preflight)
|
|
50
|
+
service Run402 service health and availability (status, health)
|
|
34
51
|
snapshots Create/list/restore/delete project data snapshots
|
|
35
52
|
branches Create/list/renew/delete contained project branches
|
|
53
|
+
|
|
54
|
+
COORDINATE — work alongside other agents and humans
|
|
55
|
+
messages Room-visible messages between agents (send/list/get/ack)
|
|
56
|
+
rooms Arrive in a room and see who is live (join)
|
|
57
|
+
claims Say what you're working on before you collide (advisory)
|
|
58
|
+
escalations Page a human when you judge you need one (raise/list/ack)
|
|
59
|
+
feedback Send feedback to the Run402 developers (free with an active tier)
|
|
60
|
+
|
|
61
|
+
AUTHORITY — who may act, and with what credential
|
|
62
|
+
credentials Manage local credential material (project-keys)
|
|
63
|
+
delegates Scoped deploy credentials for agents (create, list, revoke, rotate)
|
|
64
|
+
grants Per-project capability grants for agent/CI principals (create, revoke)
|
|
65
|
+
org Org membership, invites & audit (whoami, list, member, invite, audit)
|
|
66
|
+
identity Public proof-backed external agent identity links
|
|
67
|
+
auth Manage project user authentication (magic link, passwords, settings)
|
|
68
|
+
ci Link GitHub Actions OIDC deploy bindings
|
|
69
|
+
operator Operator (human/email) session — login, then overview across your wallets
|
|
70
|
+
|
|
71
|
+
DELIVER — reach a human when something happens
|
|
72
|
+
notifications Delivery records, preferences, channels and routing rules
|
|
73
|
+
webhook-secret Rotate the operator webhook signing secret
|
|
74
|
+
email Send template-based emails from your project
|
|
75
|
+
|
|
76
|
+
PLATFORM — everything else, and the things still finding a home
|
|
36
77
|
admin Platform-admin operations (lease-perpetual, archive, reactivate)
|
|
78
|
+
billing Email organizations, Stripe tier checkout, email packs
|
|
79
|
+
contracts KMS signers ($0.04/day rental + $0.000005/sign)
|
|
80
|
+
jobs Submit and inspect platform-managed jobs
|
|
81
|
+
transfer Two-party project transfer (init, preview, list, accept, cancel)
|
|
37
82
|
cloud Cloud portability archive export (archives create/download/status)
|
|
38
83
|
archives Inspect and verify portable project archives locally
|
|
39
|
-
core Local Run402 Core import helpers
|
|
40
|
-
deploy Unified deploy operations (requires active tier)
|
|
41
|
-
ci Link GitHub Actions OIDC deploy bindings
|
|
42
|
-
transfer Two-party project transfer (init, preview, list, accept, cancel)
|
|
43
|
-
org Org membership, invites & audit (whoami, list, member, invite, audit)
|
|
44
|
-
identity Public proof-backed external agent identity links
|
|
45
|
-
buzz Buzz human/community/agent control-plane workflows
|
|
46
|
-
grants Per-project capability grants for agent/CI principals (create, revoke)
|
|
47
|
-
delegates Scoped deploy credentials for agents (create, list, revoke, rotate)
|
|
48
|
-
events What happened to your project since you last looked (cursored feed)
|
|
49
|
-
rooms Coordinate with the other agents on your project (who/send/ack)
|
|
50
|
-
escalations Page a human when you judge you need one (raise/list/ack)
|
|
51
|
-
claims Say what you're working on before you collide (advisory)
|
|
52
84
|
gitvault Host-blind encrypted Git remote (init/status/push/policy/compact/prune/verify)
|
|
53
|
-
|
|
54
|
-
jobs Submit and inspect platform-managed jobs
|
|
55
|
-
functions Manage serverless functions (deploy, invoke, logs, list, delete)
|
|
56
|
-
secrets Manage project secrets (set, list, delete)
|
|
57
|
-
assets Direct-to-S3 asset storage (put, get, ls, rm, sign, diagnose) — up to 5 TiB
|
|
58
|
-
sites Deploy static sites
|
|
59
|
-
cdn CloudFront CDN diagnostics (wait-fresh) for public asset URLs
|
|
60
|
-
subdomains Manage custom subdomains (claim, list, delete)
|
|
61
|
-
domains Manage ProjectDomain lifecycle (connect, check, repair, wait)
|
|
85
|
+
buzz Buzz human/community/agent control-plane workflows
|
|
62
86
|
apps Browse and manage the app marketplace
|
|
63
87
|
ai AI translation and moderation tools
|
|
64
88
|
image Generate AI images via x402 or MPP micropayments
|
|
65
|
-
|
|
66
|
-
feedback Send feedback to the Run402 developers (free with an active tier)
|
|
67
|
-
auth Manage project user authentication (magic link, passwords, settings)
|
|
68
|
-
billing Email organizations, Stripe tier checkout, email packs
|
|
69
|
-
contracts KMS signers ($0.04/day rental + $0.000005/sign)
|
|
70
|
-
agent Manage agent identity (contact info)
|
|
71
|
-
operator Operator (human/email) session — login, then overview across your wallets
|
|
72
|
-
service Run402 service health and availability (status, health)
|
|
89
|
+
cdn CloudFront CDN diagnostics (wait-fresh) for public asset URLs
|
|
73
90
|
cache Inspect and invalidate the SSR origin cache (inspect, invalidate)
|
|
74
|
-
|
|
91
|
+
agent Manage agent identity (contact info)
|
|
92
|
+
core Local Run402 Core import helpers
|
|
75
93
|
dev Run Astro dev with Run402 env + credentials in scope
|
|
76
|
-
logs Fetch function logs by request id (--request-id req_...)
|
|
77
94
|
|
|
78
95
|
Global options (any command):
|
|
79
96
|
--wallet <name> Select a named wallet for this command (see 'run402 wallets')
|
|
@@ -282,6 +299,11 @@ switch (cmd) {
|
|
|
282
299
|
await run(sub, rest);
|
|
283
300
|
break;
|
|
284
301
|
}
|
|
302
|
+
case "messages": {
|
|
303
|
+
const { run } = await import("./lib/messages.mjs");
|
|
304
|
+
await run(sub, rest);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
285
307
|
case "rooms": {
|
|
286
308
|
const { run } = await import("./lib/rooms.mjs");
|
|
287
309
|
await run(sub, rest);
|
|
@@ -384,7 +406,7 @@ switch (cmd) {
|
|
|
384
406
|
details: { was: "message", now: "feedback", reserved: "the `message` noun is reserved for addressed agent/human messaging" },
|
|
385
407
|
next_actions: [
|
|
386
408
|
{ type: "edit_request", command: "run402 feedback send \"<text>\"", why: "Send feedback to the Run402 developers." },
|
|
387
|
-
{ type: "edit_request", command: "run402
|
|
409
|
+
{ type: "edit_request", command: "run402 messages send \"<text>\"", why: "Message the other agents working in this room." },
|
|
388
410
|
{ type: "edit_request", command: "run402 escalations raise \"<text>\"", why: "Page a human and wait for a named one to take it." },
|
|
389
411
|
],
|
|
390
412
|
});
|
package/lib/claims.mjs
CHANGED
|
@@ -62,7 +62,7 @@ Options:
|
|
|
62
62
|
--all list: include released/expired history.
|
|
63
63
|
|
|
64
64
|
Tip: claim before you edit, release when you hand off — and put the handoff
|
|
65
|
-
in \`run402
|
|
65
|
+
in \`run402 messages send\` so the room's timeline tells the story.
|
|
66
66
|
|
|
67
67
|
Examples:
|
|
68
68
|
run402 claims create repo:src/auth/** --note "migrating to passkeys"
|
|
@@ -97,7 +97,7 @@ async function create(args) {
|
|
|
97
97
|
console.log(JSON.stringify(created, null, 2));
|
|
98
98
|
const conflicts = Array.isArray(created.conflicts) ? created.conflicts : [];
|
|
99
99
|
if (conflicts.length > 0) {
|
|
100
|
-
console.error(`Granted with ${conflicts.length} conflict(s) — advisory, nothing is blocked. Coordinate via run402
|
|
100
|
+
console.error(`Granted with ${conflicts.length} conflict(s) — advisory, nothing is blocked. Coordinate via run402 messages send.`);
|
|
101
101
|
}
|
|
102
102
|
} catch (err) {
|
|
103
103
|
reportSdkError(err);
|
package/lib/command-manifest.mjs
CHANGED
|
@@ -224,11 +224,11 @@ export const COMMAND_MANIFEST = [
|
|
|
224
224
|
|
|
225
225
|
// ── events / errors (flat, merged runners) ───────────────────────────────
|
|
226
226
|
{ path: ["events"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "merged" },
|
|
227
|
-
{ path: ["rooms", "
|
|
228
|
-
{ path: ["
|
|
229
|
-
{ path: ["
|
|
230
|
-
{ path: ["
|
|
231
|
-
{ path: ["
|
|
227
|
+
{ path: ["rooms", "join"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub" },
|
|
228
|
+
{ path: ["messages", "send"], positionals: [p("body")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["hello"], runStyle: "sub" },
|
|
229
|
+
{ path: ["messages", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub" },
|
|
230
|
+
{ path: ["messages", "get"], positionals: [p("message_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["msg_1"], runStyle: "sub" },
|
|
231
|
+
{ path: ["messages", "ack"], positionals: [p("message_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["msg_1"], runStyle: "sub" },
|
|
232
232
|
{ path: ["escalations", "raise"], positionals: [p("reason")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["a human is needed"], runStyle: "sub" },
|
|
233
233
|
{ path: ["escalations", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub" },
|
|
234
234
|
{ path: ["escalations", "get"], positionals: [p("escalation_id")], projectScoped: true, legacyPositionalProject: false, minimalArgs: ["esc_1"], runStyle: "sub" },
|
|
@@ -423,8 +423,28 @@ export const SKIPPED_FAMILIES = {
|
|
|
423
423
|
"apply": "pure alias for `deploy apply` (covered by the deploy family)",
|
|
424
424
|
"dev": "interactive wrapper that spawns `astro dev`",
|
|
425
425
|
// RESERVED, not dispatched: every `run402 message …` fails with
|
|
426
|
-
// COMMAND_REMOVED pointing at `feedback send` / `
|
|
426
|
+
// COMMAND_REMOVED pointing at `feedback send` / `messages send` /
|
|
427
427
|
// `escalations raise`. It has no subcommands to manifest because it takes
|
|
428
428
|
// none — the noun is being held for addressed agent/human messaging.
|
|
429
429
|
"message": "reserved noun; fails with COMMAND_REMOVED (renamed to `feedback`)",
|
|
430
430
|
};
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* SUBCOMMAND spellings kept alive only to answer `COMMAND_REMOVED`.
|
|
434
|
+
*
|
|
435
|
+
* A retired spelling still needs a `case` branch — that branch IS the redirect,
|
|
436
|
+
* and without it a caller gets a generic "unknown subcommand" instead of being
|
|
437
|
+
* told where the verb went. But it is not a command: it dispatches nothing and
|
|
438
|
+
* belongs in no capability mapping, so the inventory gate must not demand one.
|
|
439
|
+
*
|
|
440
|
+
* The family-level equivalent is SKIPPED_FAMILIES above. This exists because
|
|
441
|
+
* legible-cli-surface retired four spellings INSIDE a family rather than a
|
|
442
|
+
* whole family, which the family list could not express.
|
|
443
|
+
*/
|
|
444
|
+
export const RESERVED_SUBCOMMANDS = {
|
|
445
|
+
"rooms:who": "renamed to `rooms join` — an interrogative must not name a write",
|
|
446
|
+
"rooms:send": "moved to `messages send` — the verb acts on a message",
|
|
447
|
+
"rooms:list": "moved to `messages list` — the verb acts on a message",
|
|
448
|
+
"rooms:get": "moved to `messages get` — the verb acts on a message",
|
|
449
|
+
"rooms:ack": "moved to `messages ack` — the verb acts on a message",
|
|
450
|
+
};
|
package/lib/messages.mjs
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `run402 messages` — the room-visible messages agents coordinate with.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `run402 rooms` by legible-cli-surface: four of that family's
|
|
5
|
+
* five verbs acted on a MESSAGE, not on a room. The container was the noun and
|
|
6
|
+
* the thing you actually handle had none.
|
|
7
|
+
*
|
|
8
|
+
* Gateway subsystem: add-agent-messaging (/orgs/v1/:org_id/rooms/:room_key/*).
|
|
9
|
+
* Org-scoped; a project id names that project's DEFAULT room, so inside a
|
|
10
|
+
* checkout the room resolves from the active project with zero flags. JSON
|
|
11
|
+
* envelopes to stdout (pipe contract); flags map 1:1 to the HTTP surface.
|
|
12
|
+
* Session presence + read cursor cache: ./.run402/messaging.json (gitignore).
|
|
13
|
+
*/
|
|
14
|
+
import { getSdk } from "./sdk.mjs";
|
|
15
|
+
import { fail, reportSdkError } from "./sdk-errors.mjs";
|
|
16
|
+
import {
|
|
17
|
+
normalizeArgv,
|
|
18
|
+
hasHelp,
|
|
19
|
+
assertKnownFlags,
|
|
20
|
+
assertAllowedValue,
|
|
21
|
+
parseIntegerFlag,
|
|
22
|
+
flagValue,
|
|
23
|
+
positionalArgs,
|
|
24
|
+
requirePositionalCount,
|
|
25
|
+
failUnknownSubcommand,
|
|
26
|
+
} from "./argparse.mjs";
|
|
27
|
+
import {
|
|
28
|
+
resolveRoom,
|
|
29
|
+
cachedPresenceId,
|
|
30
|
+
withPresenceRetry,
|
|
31
|
+
registerFreshPresence,
|
|
32
|
+
rememberPresence,
|
|
33
|
+
getRoomState,
|
|
34
|
+
updateRoomState,
|
|
35
|
+
} from "./rooms-context.mjs";
|
|
36
|
+
|
|
37
|
+
export const IMPORTANCE = ["normal", "high"];
|
|
38
|
+
|
|
39
|
+
const ROOM_FLAGS = ["--project", "--org", "--room"];
|
|
40
|
+
|
|
41
|
+
const HELP = `run402 messages — room-visible messages between agents
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
run402 messages send <body> [--to <names>] [--ack] [--thread <id>]
|
|
45
|
+
run402 messages list [--unread] [--cursor <mcr_...>] [--thread <id>]
|
|
46
|
+
run402 messages get <message_id>
|
|
47
|
+
run402 messages ack <message_id>
|
|
48
|
+
|
|
49
|
+
Addressing (all subcommands):
|
|
50
|
+
--project <id> That project's DEFAULT room (the room key IS the project id)
|
|
51
|
+
--org <id> --room <key> A named org room
|
|
52
|
+
(omit both) Resolved from RUN402_ROOM, a .run402.json binding, or the
|
|
53
|
+
wallet profile's selected org
|
|
54
|
+
|
|
55
|
+
Notes:
|
|
56
|
+
- to/cc route ATTENTION, not access: every agent in the room reads every
|
|
57
|
+
message. --ack asks the recipient to confirm they saw it.
|
|
58
|
+
- Messages are durable. An agent that is not running now reads them when it
|
|
59
|
+
next wakes.
|
|
60
|
+
- The room itself — arriving, leaving, seeing who is live — is \`run402 rooms\`.
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
async function ensurePresence(room, { name, task } = {}) {
|
|
64
|
+
const existing = cachedPresenceId(room.orgId, room.roomKey);
|
|
65
|
+
if (existing) {
|
|
66
|
+
// Trust, but verify: a cached id whose presence aged out (or was swept)
|
|
67
|
+
// would otherwise ride along until the first send failed. Arrival is the
|
|
68
|
+
// right moment to notice — it costs one GET, once per session.
|
|
69
|
+
const live = await stillLive(room, existing);
|
|
70
|
+
if (live) return { ...live, presence_id: existing, registered: false };
|
|
71
|
+
}
|
|
72
|
+
const registration = await registerFreshPresence(room.orgId, room.roomKey, { name, task });
|
|
73
|
+
return { ...registration, registered: true };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The cached presence if it is still live in the room, else null. */
|
|
77
|
+
async function stillLive(room, presenceId) {
|
|
78
|
+
try {
|
|
79
|
+
const presence = await getSdk().rooms.getPresence(room.orgId, room.roomKey, presenceId);
|
|
80
|
+
const expiresAt = Date.parse(presence?.expires_at ?? "");
|
|
81
|
+
return Number.isFinite(expiresAt) && expiresAt > Date.now() ? presence : null;
|
|
82
|
+
} catch {
|
|
83
|
+
// Unknown/unreachable presence: fall through to registering a fresh one
|
|
84
|
+
// rather than failing arrival.
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function send(args) {
|
|
90
|
+
const a = normalizeArgv(args);
|
|
91
|
+
const valueFlags = [...ROOM_FLAGS, "--to", "--cc", "--thread", "--importance", "--idempotency-key", "--name", "--task"];
|
|
92
|
+
assertKnownFlags(a, [...valueFlags, "--ack", "--help", "-h"], valueFlags);
|
|
93
|
+
const positionals = positionalArgs(a, valueFlags);
|
|
94
|
+
requirePositionalCount(positionals, valueFlags, {
|
|
95
|
+
min: 1, max: 1, command: 'run402 messages send "<body>" [--to <names>]', missing: "<body>",
|
|
96
|
+
});
|
|
97
|
+
const importance = flagValue(a, "--importance");
|
|
98
|
+
if (importance != null) assertAllowedValue(importance, IMPORTANCE, "--importance");
|
|
99
|
+
const room = await resolveRoom({
|
|
100
|
+
org: flagValue(a, "--org"), room: flagValue(a, "--room"), project: flagValue(a, "--project"),
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
const result = await withPresenceRetry(room.orgId, room.roomKey, (presenceId) =>
|
|
104
|
+
getSdk().rooms.sendMessage(room.orgId, room.roomKey, {
|
|
105
|
+
body: positionals[0],
|
|
106
|
+
to: splitNames(flagValue(a, "--to")),
|
|
107
|
+
cc: splitNames(flagValue(a, "--cc")),
|
|
108
|
+
threadId: flagValue(a, "--thread") ?? undefined,
|
|
109
|
+
importance: importance ?? undefined,
|
|
110
|
+
ackRequired: a.includes("--ack"),
|
|
111
|
+
idempotencyKey: flagValue(a, "--idempotency-key") ?? undefined,
|
|
112
|
+
presenceId: presenceId ?? undefined,
|
|
113
|
+
requestedName: flagValue(a, "--name") ?? undefined,
|
|
114
|
+
task: flagValue(a, "--task") ?? undefined,
|
|
115
|
+
}),
|
|
116
|
+
{ name: flagValue(a, "--name"), task: flagValue(a, "--task") });
|
|
117
|
+
rememberPresence(room.orgId, room.roomKey, result.sender_presence, flagValue(a, "--name"));
|
|
118
|
+
console.log(JSON.stringify(result, null, 2));
|
|
119
|
+
} catch (err) {
|
|
120
|
+
reportSdkError(err);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function list(args) {
|
|
125
|
+
const a = normalizeArgv(args);
|
|
126
|
+
const valueFlags = [...ROOM_FLAGS, "--cursor", "--before", "--thread", "--limit"];
|
|
127
|
+
assertKnownFlags(a, [...valueFlags, "--unread", "--help", "-h"], valueFlags);
|
|
128
|
+
requirePositionalCount(positionalArgs(a, valueFlags), valueFlags, {
|
|
129
|
+
min: 0, max: 0, command: "run402 rooms list", missing: "",
|
|
130
|
+
});
|
|
131
|
+
const limit = flagValue(a, "--limit");
|
|
132
|
+
const before = flagValue(a, "--before");
|
|
133
|
+
const unread = a.includes("--unread");
|
|
134
|
+
const room = await resolveRoom({
|
|
135
|
+
org: flagValue(a, "--org"), room: flagValue(a, "--room"), project: flagValue(a, "--project"),
|
|
136
|
+
});
|
|
137
|
+
const stored = getRoomState(room.orgId, room.roomKey).cursor;
|
|
138
|
+
const cursor = flagValue(a, "--cursor") ?? (before ? undefined : (typeof stored === "string" ? stored : undefined));
|
|
139
|
+
try {
|
|
140
|
+
const page = await withPresenceRetry(room.orgId, room.roomKey, (presenceId) =>
|
|
141
|
+
getSdk().rooms.listMessages(room.orgId, room.roomKey, {
|
|
142
|
+
...(before ? { order: "desc", before } : cursor ? { cursor } : {}),
|
|
143
|
+
threadId: flagValue(a, "--thread") ?? undefined,
|
|
144
|
+
...(unread ? { addressedTo: "me", unread: true } : {}),
|
|
145
|
+
presenceId: presenceId ?? undefined,
|
|
146
|
+
limit: limit != null ? parseIntegerFlag("--limit", limit, { min: 1, max: 200 }) : undefined,
|
|
147
|
+
}));
|
|
148
|
+
// NOTE: an unread/addressed_to=me read needs a resolvable "me"; the retry
|
|
149
|
+
// above registers a replacement when this session's presence has expired.
|
|
150
|
+
// Ascending reads advance the stored cursor; display-mode (--before) never does.
|
|
151
|
+
if (!before && typeof page.cursor === "string") {
|
|
152
|
+
updateRoomState(room.orgId, room.roomKey, { cursor: page.cursor });
|
|
153
|
+
}
|
|
154
|
+
console.log(JSON.stringify(page, null, 2));
|
|
155
|
+
} catch (err) {
|
|
156
|
+
reportSdkError(err);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function get(args) {
|
|
161
|
+
const a = normalizeArgv(args);
|
|
162
|
+
assertKnownFlags(a, [...ROOM_FLAGS, "--help", "-h"], ROOM_FLAGS);
|
|
163
|
+
const positionals = positionalArgs(a, ROOM_FLAGS);
|
|
164
|
+
requirePositionalCount(positionals, ROOM_FLAGS, {
|
|
165
|
+
min: 1, max: 1, command: "run402 rooms get <message_id>", missing: "<message_id>",
|
|
166
|
+
});
|
|
167
|
+
const room = await resolveRoom({
|
|
168
|
+
org: flagValue(a, "--org"), room: flagValue(a, "--room"), project: flagValue(a, "--project"),
|
|
169
|
+
});
|
|
170
|
+
try {
|
|
171
|
+
console.log(JSON.stringify(await getSdk().rooms.getMessage(room.orgId, room.roomKey, positionals[0]), null, 2));
|
|
172
|
+
} catch (err) {
|
|
173
|
+
reportSdkError(err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function ack(args) {
|
|
178
|
+
const a = normalizeArgv(args);
|
|
179
|
+
assertKnownFlags(a, [...ROOM_FLAGS, "--help", "-h"], ROOM_FLAGS);
|
|
180
|
+
const positionals = positionalArgs(a, ROOM_FLAGS);
|
|
181
|
+
requirePositionalCount(positionals, ROOM_FLAGS, {
|
|
182
|
+
min: 1, max: 1, command: "run402 messages ack <message_id>", missing: "<message_id>",
|
|
183
|
+
});
|
|
184
|
+
const room = await resolveRoom({
|
|
185
|
+
org: flagValue(a, "--org"), room: flagValue(a, "--room"), project: flagValue(a, "--project"),
|
|
186
|
+
});
|
|
187
|
+
try {
|
|
188
|
+
const result = await withPresenceRetry(room.orgId, room.roomKey, (presenceId) =>
|
|
189
|
+
getSdk().rooms.ackMessage(room.orgId, room.roomKey, positionals[0], {
|
|
190
|
+
presenceId: presenceId ?? undefined,
|
|
191
|
+
}));
|
|
192
|
+
console.log(JSON.stringify(result, null, 2));
|
|
193
|
+
} catch (err) {
|
|
194
|
+
reportSdkError(err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
export async function run(sub, args) {
|
|
200
|
+
const argv = Array.isArray(args) ? args : [];
|
|
201
|
+
if (!sub || hasHelp([sub, ...argv])) {
|
|
202
|
+
console.log(HELP);
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
switch (sub) {
|
|
206
|
+
case "send": {
|
|
207
|
+
await send(argv);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "list": {
|
|
211
|
+
await list(argv);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "get": {
|
|
215
|
+
await get(argv);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case "ack": {
|
|
219
|
+
await ack(argv);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
default:
|
|
223
|
+
failUnknownSubcommand("messages", sub, {
|
|
224
|
+
hint: "Run `run402 messages --help` for usage.",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
package/lib/rooms-context.mjs
CHANGED
|
@@ -97,7 +97,7 @@ export async function resolveRoom({ org, room, project } = {}) {
|
|
|
97
97
|
hint: `Pass --org <org_id> --room <key>, set ${ROOM_ENV}="<org_id>/<room_key>", add a "room" key to .run402.json, or select a project with: run402 projects use <project_id>`,
|
|
98
98
|
next_actions: [
|
|
99
99
|
nextAction("edit_request", {
|
|
100
|
-
command: "run402 rooms
|
|
100
|
+
command: "run402 rooms join --org <org_id> --room <key>",
|
|
101
101
|
why: "Name the organization and room on this one call.",
|
|
102
102
|
}),
|
|
103
103
|
nextAction("edit_request", {
|
package/lib/rooms.mjs
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `run402 rooms` —
|
|
2
|
+
* `run402 rooms` — the coordination room itself: arriving, leaving, and who is
|
|
3
|
+
* in it.
|
|
4
|
+
*
|
|
5
|
+
* The MESSAGES exchanged in a room are `run402 messages` (legible-cli-surface).
|
|
6
|
+
* Four of this family's five verbs used to act on a message, which made the
|
|
7
|
+
* container the noun and left the thing you handle without one.
|
|
8
|
+
*
|
|
9
|
+
* `who` is now `join`: it REGISTERS a presence, and an interrogative must not
|
|
10
|
+
* name a write.
|
|
3
11
|
*
|
|
4
12
|
* Gateway subsystem: add-agent-messaging (/orgs/v1/:org_id/rooms/:room_key/*).
|
|
5
|
-
*
|
|
6
|
-
* a checkout the room resolves from the active project with zero flags. JSON
|
|
7
|
-
* envelopes to stdout (pipe contract); flags map 1:1 to the HTTP surface.
|
|
8
|
-
* Session presence + read cursor cache: ./.run402/messaging.json (gitignore).
|
|
13
|
+
* Session presence cache: ./.run402/messaging.json (gitignore).
|
|
9
14
|
*/
|
|
10
15
|
import { getSdk } from "./sdk.mjs";
|
|
11
16
|
import { fail, reportSdkError } from "./sdk-errors.mjs";
|
|
@@ -34,88 +39,24 @@ export const IMPORTANCE = ["normal", "high"];
|
|
|
34
39
|
|
|
35
40
|
const ROOM_FLAGS = ["--project", "--org", "--room"];
|
|
36
41
|
|
|
37
|
-
const HELP = `run402 rooms —
|
|
42
|
+
const HELP = `run402 rooms — arrive in a room, see who is live, leave when done
|
|
38
43
|
|
|
39
44
|
Usage:
|
|
40
|
-
run402 rooms
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.run402.json / 'run402 org use'. A checkout that hosts
|
|
56
|
-
nothing on run402 reaches a room this way.
|
|
57
|
-
.run402.json {"org":"<org_id>","room":"<key>"} binds a checkout, so
|
|
58
|
-
every agent in it lands in the same room with no flags.
|
|
59
|
-
|
|
60
|
-
Subcommands:
|
|
61
|
-
who Who is live in the room (name, task, active claims). Registers your
|
|
62
|
-
session presence on first use — pass --name to choose your own name
|
|
63
|
-
(honored when free; suffixed Opus -> Opus-2 when taken, and the
|
|
64
|
-
output says so) and --task to say what you're working on.
|
|
65
|
-
send Send a room-visible message. --to routes attention (comma-separated
|
|
66
|
-
presence names) and --ack asks those recipients to acknowledge.
|
|
67
|
-
Messages are visible to the whole room; to/cc is not access control.
|
|
68
|
-
list Read messages, oldest-first from your stored cursor. --unread limits
|
|
69
|
-
to messages addressed to you that you haven't read; the cursor
|
|
70
|
-
auto-saves to ./.run402/messaging.json so the next list resumes.
|
|
71
|
-
get One message with its FULL body (lists carry snippets) + ack state.
|
|
72
|
-
ack Acknowledge a message addressed to you.
|
|
73
|
-
|
|
74
|
-
Options:
|
|
75
|
-
--name <name> who/send: requested presence name (first use only).
|
|
76
|
-
--task <text> who/send: what this session is working on.
|
|
77
|
-
--to <a,b> send: presence names to address (comma-separated).
|
|
78
|
-
--cc <a,b> send: additional attention, no ack expectation.
|
|
79
|
-
--ack send: request acknowledgment from --to recipients.
|
|
80
|
-
--thread <id> send/list: conversation thread id (<=128 chars).
|
|
81
|
-
--importance <v> send: normal (default) | high.
|
|
82
|
-
--idempotency-key <k> send: safe-retry key — a replay returns the ORIGINAL
|
|
83
|
-
message with deduplicated: true, never a double-post.
|
|
84
|
-
--cursor <mcr_...> list: explicit resume point (overrides the cache).
|
|
85
|
-
--before <mcr_...> list: page OLDER history (newest-first display mode).
|
|
86
|
-
--limit <n> list: page size (default 50, max 200).
|
|
87
|
-
--unread list: only unread messages addressed to you.
|
|
88
|
-
--all who: include expired presences (history).
|
|
89
|
-
|
|
90
|
-
The cursor model:
|
|
91
|
-
- Every list response carries "cursor": the high-water mark. The CLI stores
|
|
92
|
-
it per room in ./.run402/messaging.json and resumes automatically.
|
|
93
|
-
- A stale cursor never errors: the response says reset: true and includes
|
|
94
|
-
earliest_cursor to restart from. Cursors are opaque — store, never parse.
|
|
95
|
-
- Reads hide the newest ~2s (the visibility watermark, same as the events
|
|
96
|
-
feed): a message you JUST sent appears on the next read, not instantly.
|
|
97
|
-
|
|
98
|
-
Presence and names:
|
|
99
|
-
- Your presence is this SESSION, not your model or wallet: two sessions of
|
|
100
|
-
the same agent are two presences. Names are unique per room forever.
|
|
101
|
-
- A presence expires after ~1h of silence; the CLI transparently re-registers
|
|
102
|
-
on the next call (your name will be new — introduce yourself).
|
|
103
|
-
|
|
104
|
-
Auth:
|
|
105
|
-
Org members (any role) reach all the org's rooms. A delegate
|
|
106
|
-
(RUN402_DELEGATE_TOKEN) reaches its own project's default room plus the
|
|
107
|
-
org's named rooms. Project service keys are read-only in their room.
|
|
108
|
-
|
|
109
|
-
Tip: start every session with \`run402 rooms who --name <yours> --task "<what you're doing>"\`
|
|
110
|
-
then \`run402 rooms list --unread\` — arrive, look, then work.
|
|
111
|
-
|
|
112
|
-
Examples:
|
|
113
|
-
run402 rooms who --name Opus --task "migrating auth"
|
|
114
|
-
run402 rooms send "auth dir is mine until 14:30" --to BlueLake --ack
|
|
115
|
-
run402 rooms list --unread # catch up, cursor auto-saves
|
|
116
|
-
run402 rooms get msg_2f # full body
|
|
117
|
-
run402 rooms ack msg_2f
|
|
118
|
-
run402 rooms list --org 5f3a... --room run402-dev # named org room
|
|
45
|
+
run402 rooms join [--name <name>] [--task <text>]
|
|
46
|
+
|
|
47
|
+
Addressing:
|
|
48
|
+
--project <id> That project's DEFAULT room (the room key IS the project id)
|
|
49
|
+
--org <id> --room <key> A named org room
|
|
50
|
+
(omit both) Resolved from RUN402_ROOM, a .run402.json binding, or the
|
|
51
|
+
wallet profile's selected org
|
|
52
|
+
|
|
53
|
+
Notes:
|
|
54
|
+
- join registers this session's presence and returns who else is live, what
|
|
55
|
+
they are working on, and what they have claimed — the arrive-and-look call.
|
|
56
|
+
- Presence expires after ~1h of silence. Releasing it early (\`rooms leave\`),
|
|
57
|
+
enumerating reachable rooms, and inspecting one are not here yet — each
|
|
58
|
+
needs a gateway route that does not exist. Filed as follow-ups.
|
|
59
|
+
- The messages themselves are \`run402 messages\`.
|
|
119
60
|
`;
|
|
120
61
|
|
|
121
62
|
async function ensurePresence(room, { name, task } = {}) {
|
|
@@ -179,114 +120,6 @@ function splitNames(value) {
|
|
|
179
120
|
return value ? value.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
180
121
|
}
|
|
181
122
|
|
|
182
|
-
async function send(args) {
|
|
183
|
-
const a = normalizeArgv(args);
|
|
184
|
-
const valueFlags = [...ROOM_FLAGS, "--to", "--cc", "--thread", "--importance", "--idempotency-key", "--name", "--task"];
|
|
185
|
-
assertKnownFlags(a, [...valueFlags, "--ack", "--help", "-h"], valueFlags);
|
|
186
|
-
const positionals = positionalArgs(a, valueFlags);
|
|
187
|
-
requirePositionalCount(positionals, valueFlags, {
|
|
188
|
-
min: 1, max: 1, command: 'run402 rooms send "<body>" [--to <names>]', missing: "<body>",
|
|
189
|
-
});
|
|
190
|
-
const importance = flagValue(a, "--importance");
|
|
191
|
-
if (importance != null) assertAllowedValue(importance, IMPORTANCE, "--importance");
|
|
192
|
-
const room = await resolveRoom({
|
|
193
|
-
org: flagValue(a, "--org"), room: flagValue(a, "--room"), project: flagValue(a, "--project"),
|
|
194
|
-
});
|
|
195
|
-
try {
|
|
196
|
-
const result = await withPresenceRetry(room.orgId, room.roomKey, (presenceId) =>
|
|
197
|
-
getSdk().rooms.sendMessage(room.orgId, room.roomKey, {
|
|
198
|
-
body: positionals[0],
|
|
199
|
-
to: splitNames(flagValue(a, "--to")),
|
|
200
|
-
cc: splitNames(flagValue(a, "--cc")),
|
|
201
|
-
threadId: flagValue(a, "--thread") ?? undefined,
|
|
202
|
-
importance: importance ?? undefined,
|
|
203
|
-
ackRequired: a.includes("--ack"),
|
|
204
|
-
idempotencyKey: flagValue(a, "--idempotency-key") ?? undefined,
|
|
205
|
-
presenceId: presenceId ?? undefined,
|
|
206
|
-
requestedName: flagValue(a, "--name") ?? undefined,
|
|
207
|
-
task: flagValue(a, "--task") ?? undefined,
|
|
208
|
-
}),
|
|
209
|
-
{ name: flagValue(a, "--name"), task: flagValue(a, "--task") });
|
|
210
|
-
rememberPresence(room.orgId, room.roomKey, result.sender_presence, flagValue(a, "--name"));
|
|
211
|
-
console.log(JSON.stringify(result, null, 2));
|
|
212
|
-
} catch (err) {
|
|
213
|
-
reportSdkError(err);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async function list(args) {
|
|
218
|
-
const a = normalizeArgv(args);
|
|
219
|
-
const valueFlags = [...ROOM_FLAGS, "--cursor", "--before", "--thread", "--limit"];
|
|
220
|
-
assertKnownFlags(a, [...valueFlags, "--unread", "--help", "-h"], valueFlags);
|
|
221
|
-
requirePositionalCount(positionalArgs(a, valueFlags), valueFlags, {
|
|
222
|
-
min: 0, max: 0, command: "run402 rooms list", missing: "",
|
|
223
|
-
});
|
|
224
|
-
const limit = flagValue(a, "--limit");
|
|
225
|
-
const before = flagValue(a, "--before");
|
|
226
|
-
const unread = a.includes("--unread");
|
|
227
|
-
const room = await resolveRoom({
|
|
228
|
-
org: flagValue(a, "--org"), room: flagValue(a, "--room"), project: flagValue(a, "--project"),
|
|
229
|
-
});
|
|
230
|
-
const stored = getRoomState(room.orgId, room.roomKey).cursor;
|
|
231
|
-
const cursor = flagValue(a, "--cursor") ?? (before ? undefined : (typeof stored === "string" ? stored : undefined));
|
|
232
|
-
try {
|
|
233
|
-
const page = await withPresenceRetry(room.orgId, room.roomKey, (presenceId) =>
|
|
234
|
-
getSdk().rooms.listMessages(room.orgId, room.roomKey, {
|
|
235
|
-
...(before ? { order: "desc", before } : cursor ? { cursor } : {}),
|
|
236
|
-
threadId: flagValue(a, "--thread") ?? undefined,
|
|
237
|
-
...(unread ? { addressedTo: "me", unread: true } : {}),
|
|
238
|
-
presenceId: presenceId ?? undefined,
|
|
239
|
-
limit: limit != null ? parseIntegerFlag("--limit", limit, { min: 1, max: 200 }) : undefined,
|
|
240
|
-
}));
|
|
241
|
-
// NOTE: an unread/addressed_to=me read needs a resolvable "me"; the retry
|
|
242
|
-
// above registers a replacement when this session's presence has expired.
|
|
243
|
-
// Ascending reads advance the stored cursor; display-mode (--before) never does.
|
|
244
|
-
if (!before && typeof page.cursor === "string") {
|
|
245
|
-
updateRoomState(room.orgId, room.roomKey, { cursor: page.cursor });
|
|
246
|
-
}
|
|
247
|
-
console.log(JSON.stringify(page, null, 2));
|
|
248
|
-
} catch (err) {
|
|
249
|
-
reportSdkError(err);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async function get(args) {
|
|
254
|
-
const a = normalizeArgv(args);
|
|
255
|
-
assertKnownFlags(a, [...ROOM_FLAGS, "--help", "-h"], ROOM_FLAGS);
|
|
256
|
-
const positionals = positionalArgs(a, ROOM_FLAGS);
|
|
257
|
-
requirePositionalCount(positionals, ROOM_FLAGS, {
|
|
258
|
-
min: 1, max: 1, command: "run402 rooms get <message_id>", missing: "<message_id>",
|
|
259
|
-
});
|
|
260
|
-
const room = await resolveRoom({
|
|
261
|
-
org: flagValue(a, "--org"), room: flagValue(a, "--room"), project: flagValue(a, "--project"),
|
|
262
|
-
});
|
|
263
|
-
try {
|
|
264
|
-
console.log(JSON.stringify(await getSdk().rooms.getMessage(room.orgId, room.roomKey, positionals[0]), null, 2));
|
|
265
|
-
} catch (err) {
|
|
266
|
-
reportSdkError(err);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
async function ack(args) {
|
|
271
|
-
const a = normalizeArgv(args);
|
|
272
|
-
assertKnownFlags(a, [...ROOM_FLAGS, "--help", "-h"], ROOM_FLAGS);
|
|
273
|
-
const positionals = positionalArgs(a, ROOM_FLAGS);
|
|
274
|
-
requirePositionalCount(positionals, ROOM_FLAGS, {
|
|
275
|
-
min: 1, max: 1, command: "run402 rooms ack <message_id>", missing: "<message_id>",
|
|
276
|
-
});
|
|
277
|
-
const room = await resolveRoom({
|
|
278
|
-
org: flagValue(a, "--org"), room: flagValue(a, "--room"), project: flagValue(a, "--project"),
|
|
279
|
-
});
|
|
280
|
-
try {
|
|
281
|
-
const result = await withPresenceRetry(room.orgId, room.roomKey, (presenceId) =>
|
|
282
|
-
getSdk().rooms.ackMessage(room.orgId, room.roomKey, positionals[0], {
|
|
283
|
-
presenceId: presenceId ?? undefined,
|
|
284
|
-
}));
|
|
285
|
-
console.log(JSON.stringify(result, null, 2));
|
|
286
|
-
} catch (err) {
|
|
287
|
-
reportSdkError(err);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
123
|
|
|
291
124
|
export async function run(sub, args) {
|
|
292
125
|
const argv = Array.isArray(args) ? args : [];
|
|
@@ -295,26 +128,45 @@ export async function run(sub, args) {
|
|
|
295
128
|
process.exit(0);
|
|
296
129
|
}
|
|
297
130
|
switch (sub) {
|
|
298
|
-
case "
|
|
131
|
+
case "join": {
|
|
299
132
|
await who(argv);
|
|
300
133
|
break;
|
|
301
134
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
135
|
+
// `list`, `get` and `leave` are NOT here. Each needs a gateway route that
|
|
136
|
+
// does not exist (there is no list-rooms, get-room, or release-presence
|
|
137
|
+
// endpoint), and this change states plainly that it adds none. They are
|
|
138
|
+
// filed as follow-ups rather than shipped as a CLI calling into nothing.
|
|
139
|
+
//
|
|
140
|
+
// `rooms list` and `rooms get` additionally carry design D3b: they are
|
|
141
|
+
// FREED by the move of the message verbs, and a freed spelling stays dead
|
|
142
|
+
// for one major before anything reuses it — a spelling that silently
|
|
143
|
+
// answers with different data is worse than one that fails.
|
|
144
|
+
// Retired here, and NOT aliased (design D3): each answers with its
|
|
145
|
+
// successor so one failed call teaches the new model, where an alias
|
|
146
|
+
// would teach the old one forever.
|
|
147
|
+
case "who":
|
|
148
|
+
fail({
|
|
149
|
+
code: "COMMAND_REMOVED",
|
|
150
|
+
message: "`run402 rooms who` was renamed to `run402 rooms join`.",
|
|
151
|
+
hint: "run402 rooms join --name <name> --task <text>",
|
|
152
|
+
details: { was: "rooms who", now: "rooms join", why: "an interrogative must not name a write — it registers a presence" },
|
|
153
|
+
});
|
|
312
154
|
break;
|
|
313
|
-
|
|
314
|
-
case "
|
|
315
|
-
|
|
155
|
+
case "list":
|
|
156
|
+
case "get":
|
|
157
|
+
case "send":
|
|
158
|
+
case "ack":
|
|
159
|
+
fail({
|
|
160
|
+
code: "COMMAND_REMOVED",
|
|
161
|
+
message: `\`run402 rooms ${sub}\` moved to \`run402 messages ${sub}\`.`,
|
|
162
|
+
hint: `run402 messages ${sub}`,
|
|
163
|
+
details: {
|
|
164
|
+
was: `rooms ${sub}`,
|
|
165
|
+
now: `messages ${sub}`,
|
|
166
|
+
why: "the verb acts on a message, not on the room that contains it",
|
|
167
|
+
},
|
|
168
|
+
});
|
|
316
169
|
break;
|
|
317
|
-
}
|
|
318
170
|
default:
|
|
319
171
|
failUnknownSubcommand("rooms", sub, {
|
|
320
172
|
hint: "Run `run402 rooms --help` for usage.",
|
package/package.json
CHANGED