@fulldivevr/workcord-cli 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # @fulldivevr/workcord-cli
2
2
 
3
- TypeScript CLI for the WorkCord API. **One invocation = one user action.**
4
- Designed for automated agents that emulate real users — wake up, do one
5
- thing, exit. Route-level commands are generated from the backend's own
6
- OpenAPI spec; a small set of hand-written semantic commands sits on top.
3
+ TypeScript CLI for talking to WorkCord agents through persistent Hermes tenant
4
+ chat and using the WorkCord API. **One invocation = one user action.** Designed
5
+ for automated agents such as Claude Code: ask the user's Assistant for the
6
+ outcome you need, let that tenant manage its own instructions, skills, and
7
+ flavors, then inspect its reply. Route-level commands and advanced diagnostics
8
+ remain available.
7
9
 
8
10
  Lives in the backend repo (`clients/workcord-cli`) so the spec it generates
9
11
  from is always the spec this server serves.
@@ -30,15 +32,9 @@ Base URL must be a bare origin (scheme + host, no path). The `/api/v1` prefix
30
32
  is added by the CLI. Resolution order is `--base-url`, `WORKCORD_API_URL`,
31
33
  saved configuration, then the WorkCord production default.
32
34
 
33
- | Deployment | `WORKCORD_API_URL` |
34
- | --- | --- |
35
- | WorkCord prod | `https://work-rooms-api.wizeup.app` |
36
- | VIA | `https://via-rooms-api.wizeup.app` |
37
- | RoomCord prod | `https://rooms-api.wizeup.app` |
38
- | RoomCord dev | `https://rooms-api-dev.wizeup.app` |
39
- | JesusCord | `https://jesuscord-api.wizeup.app` |
40
- | Local WorkCord | `http://localhost:3001` |
41
- | Local RoomCord / JesusCord | `http://localhost:3000` |
35
+ Your deployment's origin comes from whoever operates it. A local server is
36
+ `http://localhost:3001` (WorkCord) or `http://localhost:3000` (RoomCord /
37
+ JesusCord).
42
38
 
43
39
  ```bash
44
40
  workcord auth login --email you@company.com --save
@@ -49,20 +45,17 @@ workcord rooms list
49
45
  Use another server for one command:
50
46
 
51
47
  ```bash
52
- workcord --base-url https://via-rooms-api.wizeup.app rooms list
48
+ workcord --base-url https://your-server.example.com rooms list
53
49
  ```
54
50
 
55
51
  Save another server and login for future commands:
56
52
 
57
53
  ```bash
58
- workcord --base-url https://via-rooms-api.wizeup.app auth login --email you@via.com --save
54
+ workcord --base-url https://your-server.example.com auth login --email you@company.com --save
59
55
  ```
60
56
 
61
- Reset the saved server to WorkCord by logging in and saving again:
62
-
63
- ```bash
64
- workcord --base-url https://work-rooms-api.wizeup.app auth login --email you@company.com --save
65
- ```
57
+ Reset the saved server to the built-in default by clearing `baseUrl` from
58
+ `~/.workcord/config.json` and logging in again.
66
59
 
67
60
  Output is JSON on stdout, errors on stderr, non-zero exit on failure.
68
61
 
@@ -153,6 +146,48 @@ workcord [--as <userId>] <namespace> <command> [flags]
153
146
  `--as` is only needed when minting with `WORKCORD_JWT_SECRET`; with
154
147
  `WORKCORD_TOKEN` set, omit it everywhere.
155
148
 
149
+ ### Agent-first customization
150
+
151
+ To change agent behavior, create or update a skill, or maintain a flavor, talk
152
+ to the owning agent through Hermes tenant chat. Do not edit prompts or tenant
153
+ files directly. With no `--agent`, the user's non-deletable Assistant is the
154
+ target and becomes the default owner of anything it creates:
155
+
156
+ ```bash
157
+ workcord agent chat new --message "Create a skill that prepares a concise weekly sales report and enable it."
158
+ workcord agent chat new --message "Update my default flavor instructions to require sources in research reports."
159
+ ```
160
+
161
+ Use the returned `chatId` for follow-up, verification, or rollback:
162
+
163
+ ```bash
164
+ workcord agent chat send --chat "$CHAT_ID" --message "Verify the change, list the versions you created, and explain how to roll it back."
165
+ ```
166
+
167
+ Both commands wait up to two minutes by default and return the durable Hermes
168
+ message document. Use `--async` for a queued response, then continue with its
169
+ `messageId`:
170
+
171
+ ```bash
172
+ workcord agent chat wait --message-id "$MESSAGE_ID" --timeout 10m
173
+ workcord agent chat history --chat "$CHAT_ID"
174
+ ```
175
+
176
+ If a local wait times out, the response has `timedOut: true`; the remote agent
177
+ continues working and can be checked with `chat wait`.
178
+
179
+ To make another owned agent the explicit owner and conversation target:
180
+
181
+ ```bash
182
+ workcord agent chat new --agent "$AGENT_ID" --message "Create and maintain a finance-specific flavor for yourself."
183
+ ```
184
+
185
+ The Assistant or selected agent works within its tenant's Hub permissions. It
186
+ can safely validate changes and report what it activated. `agent chat` uses
187
+ Hermes' native durable `hub_chat` transport; it is not raw A2A wire traffic.
188
+ A2A remains available for external agent peers. Direct `hermes` commands are
189
+ advanced diagnostics and recovery tools, not the normal customization workflow.
190
+
156
191
  - `workcord --help` lists namespaces.
157
192
  - `workcord <namespace> --help` lists that namespace's commands.
158
193
  - `workcord <namespace> <command> --help` shows the command's flags.
@@ -215,7 +250,7 @@ workcord --as USER rooms discover --search "ai" --tags ai,tools --limit 20
215
250
  workcord --as USER rooms create-with-message --name "ops" --public --tags ai,news --text "First post."
216
251
  workcord --as USER rooms members --room R
217
252
  workcord --as USER rooms upload-icon-file --room R --file ./icon.png
218
- workcord --as USER custom-agents create-hosted --name "Helper" --description "Room helper" --prompt-file ./prompt.txt --tools web_search
253
+ workcord --as USER agent chat new --message "Create a room helper that can research the web."
219
254
  workcord --as USER room-agents install-hosted --room R --agent system-wizeup
220
255
  workcord --as USER messages react --room R --message M --emoji 👍
221
256
  workcord --as USER dm send-text --to USER_B --text "hi"
@@ -281,12 +316,14 @@ workcord --as $U dm send-text --to $OTHER --text "hey"
281
316
  # → composite: opens (or finds) the DM room, sends the message, returns both
282
317
  ```
283
318
 
284
- ## Hermes Hub (`hermes` namespace)
319
+ ## Advanced Hermes diagnostics and recovery (`hermes` namespace)
285
320
 
286
- WorkCord provisions a **Hermes Hub tenant** for each room-agent and each Hermes
287
- custom agent. That tenant is where the agent actually runs: its chats, runs,
288
- files, skills, flavor pin, A2A peers and eval runs all live on the Hub, not in
289
- WorkCord. The `hermes` namespace drives that tenant directly.
321
+ WorkCord provisions a **Hermes Hub tenant** for each room-agent, Hermes custom
322
+ agent, and room-scoped marketplace flavor install. That tenant is where the
323
+ agent actually runs: its chats, runs, files, skills, flavor pin, A2A peers and
324
+ eval runs all live on the Hub, not in WorkCord. Normal changes should be
325
+ requested with `workcord agent chat`; the `hermes` namespace exists for direct
326
+ inspection, diagnostics, and recovery.
290
327
 
291
328
  This is the one namespace that talks to a **second origin**. Every other command
292
329
  goes to the WorkCord API; `hermes` uses the WorkCord API only to reveal the
@@ -300,8 +337,11 @@ Every `hermes` command takes exactly one of:
300
337
  | --- | --- |
301
338
  | `--agent <id>` | a custom agent id, used verbatim |
302
339
  | `--room-agent <id>` | a room agent id, resolved as `workcord-room-agent:<id>` |
340
+ | `--marketplace-install <id>` | an isolated marketplace flavor install id or its room agent id |
303
341
 
304
- Passing both, or neither, is an error.
342
+ Passing more than one, or none, is an error. Marketplace install access follows
343
+ current room ownership; transferring room ownership revokes the former
344
+ creator's ability to reveal the tenant credential.
305
345
 
306
346
  ### Credentials
307
347
 
@@ -355,6 +395,7 @@ material.
355
395
 
356
396
  ```bash
357
397
  workcord hermes whoami --room-agent $RA
398
+ workcord hermes whoami --marketplace-install $MARKETPLACE_INSTALL
358
399
  workcord hermes chat send --room-agent $RA --chat $CHAT --text "status?"
359
400
  workcord hermes flavor current --agent $AGENT
360
401
  workcord hermes peers find --agent $AGENT --query "translation" --limit 5
@@ -364,15 +405,16 @@ Version flags are named `--flavor-version` and `--skill-version`, not
364
405
  `--version`: commander's program-level `.version()` matches `--version`
365
406
  anywhere in argv, prints the CLI version and exits before the subcommand runs.
366
407
 
367
- ### Authoring flavors and skills
408
+ ### Direct flavor and skill recovery
409
+
410
+ For normal work, ask the Assistant or another owned agent to create, update,
411
+ validate, or activate its own flavor and skills. Hermes Hub supports
412
+ tenant-owned skill and flavor namespaces; ownership defaults to whichever agent
413
+ tenant performs the creation.
368
414
 
369
- `flavor publish`, `flavor rollout` and `skill publish` are **owner-gated on the
370
- Hub**. A WorkCord agent tenant is provisioned as a *consumer* and owns no flavor
371
- or skill namespace, so these will 403 out of the box. Ownership is
372
- admin-assigned only (`PUT /v1/flavors/{id}/owner`, likewise for skills) and
373
- there is **no self-service bootstrap** — a hub operator has to assign the
374
- namespace to your tenant first. The CLI turns that 403 into an explicit error
375
- naming the tenant and the namespace so you know exactly what to ask for.
415
+ The direct `flavor publish`, `flavor rollout`, and `skill publish` commands are
416
+ kept for advanced recovery and authorized publisher workflows. They operate as
417
+ the selected tenant and can only mutate namespaces that tenant owns.
376
418
 
377
419
  The CLI does **not** pack or untar archives. The authoring loop is:
378
420
 
package/dist/cli.js CHANGED
@@ -13,13 +13,14 @@ import { registerCustomAgents } from "./commands/custom-agents.js";
13
13
  import { registerChallenges } from "./commands/challenges.js";
14
14
  import { registerHealth } from "./commands/health.js";
15
15
  import { registerHermes } from "./commands/hermes.js";
16
+ import { registerAgent } from "./commands/agent.js";
16
17
  import { registerAll as registerGenerated } from "./generated/index.js";
17
18
  const program = new Command();
18
19
  const require = createRequire(import.meta.url);
19
20
  const { version } = require("../package.json");
20
21
  program
21
22
  .name("workcord")
22
- .description("CLI for the WorkCord API. One invocation = one user action.")
23
+ .description("Talk to WorkCord agents through Hermes tenant chat, with advanced API diagnostics.")
23
24
  .version(version)
24
25
  .option("--as <userId>", "user to act as")
25
26
  .option("--base-url <url>", "API base URL (overrides WORKCORD_API_URL and saved configuration)");
@@ -27,6 +28,7 @@ program.hook("preAction", () => {
27
28
  setBaseUrlOverride(program.opts().baseUrl);
28
29
  });
29
30
  registerWhoami(program);
31
+ registerAgent(program);
30
32
  registerAuth(program);
31
33
  registerMessages(program);
32
34
  registerRooms(program);
@@ -0,0 +1,191 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { actingUserId, resolveHermesKey } from "../auth.js";
4
+ import { call } from "../client.js";
5
+ import { hermesCall } from "../hermes-client.js";
6
+ export function messageText(opts) {
7
+ if (opts.message !== undefined && opts.messageFile !== undefined) {
8
+ throw new Error("Use either --message or --message-file, not both.");
9
+ }
10
+ const message = opts.messageFile !== undefined ? readFileSync(opts.messageFile, "utf8").trim() : String(opts.message || "").trim();
11
+ if (!message)
12
+ throw new Error("One of --message or --message-file is required and must not be blank.");
13
+ return message;
14
+ }
15
+ export function chatMessageBody(text) {
16
+ return { parts: [{ type: "text", text }] };
17
+ }
18
+ export function assistantIdFrom(value) {
19
+ const agents = Array.isArray(value?.data) ? value.data : [];
20
+ const assistant = agents.find((item) => item?.kind === "shadow");
21
+ if (!assistant?.id || typeof assistant.id !== "string") {
22
+ throw new Error("Your default Assistant is not ready. Open WorkCord once to provision it, or pass --agent <id>.");
23
+ }
24
+ return assistant.id;
25
+ }
26
+ export function chatIdFrom(value) {
27
+ const item = value;
28
+ const id = item?.chatId ?? item?.chat_id ?? item?.contextId ?? item?.context_id ?? item?.id;
29
+ if (typeof id !== "string" || !id)
30
+ throw new Error("Hub response did not contain a chat ID.");
31
+ return id;
32
+ }
33
+ export function messageIdFrom(value) {
34
+ const item = value;
35
+ const nested = item?.message;
36
+ const id = item?.messageId ?? item?.message_id ?? item?.id ?? nested?.messageId ?? nested?.message_id ?? nested?.id;
37
+ if (typeof id !== "string" || !id)
38
+ throw new Error("Hub response did not contain a chat message ID.");
39
+ return id;
40
+ }
41
+ export function messageState(value) {
42
+ const item = value;
43
+ const message = item?.message ?? item;
44
+ const status = message?.status;
45
+ const state = status && typeof status === "object" ? status.state : status ?? message?.state;
46
+ return String(state || "").toLowerCase();
47
+ }
48
+ export function messageRevision(value) {
49
+ const item = value;
50
+ const nested = item?.message;
51
+ const revision = Number(nested?.revision ?? item?.revision);
52
+ return Number.isSafeInteger(revision) && revision >= 0 ? revision : 0;
53
+ }
54
+ export function parseDuration(value = "2m") {
55
+ const match = /^(\d+)(ms|s|m|h)?$/.exec(value);
56
+ if (!match)
57
+ throw new Error(`Invalid duration: ${value}`);
58
+ const unit = (match[2] || "s");
59
+ const multiplier = { ms: 1, s: 1000, m: 60_000, h: 3_600_000 }[unit];
60
+ const duration = Number(match[1]) * multiplier;
61
+ if (!Number.isSafeInteger(duration) || duration <= 0)
62
+ throw new Error("Timeout must be greater than zero.");
63
+ return duration;
64
+ }
65
+ export async function waitForChatMessage(initial, timeoutMs, getLatest) {
66
+ let latest = initial;
67
+ const messageId = messageIdFrom(latest);
68
+ const terminal = new Set(["completed", "failed", "canceled", "cancelled"]);
69
+ const deadline = Date.now() + timeoutMs;
70
+ while (!terminal.has(messageState(latest))) {
71
+ const remaining = deadline - Date.now();
72
+ if (remaining <= 0)
73
+ return { message: latest, timedOut: true };
74
+ const waitSeconds = Math.max(1, Math.min(30, Math.ceil(remaining / 1000)));
75
+ latest = await getLatest(messageId, messageRevision(latest), waitSeconds);
76
+ }
77
+ return { message: latest, timedOut: false };
78
+ }
79
+ function tenantPath(creds, suffix) {
80
+ if (!creds.tenantId)
81
+ throw new Error("Hermes tenant ID is unavailable. Re-run with --refresh.");
82
+ return `/v1/tenants/${encodeURIComponent(creds.tenantId)}${suffix}`;
83
+ }
84
+ function out(value) {
85
+ process.stdout.write(JSON.stringify(value) + "\n");
86
+ }
87
+ function chatFlags(command) {
88
+ return command
89
+ .option("--agent <agentId>", "explicit alternate owned agent; omit to use your default Assistant")
90
+ .option("--refresh", "re-reveal the tenant key instead of using the cached one")
91
+ .option("--no-cache", "do not read or write the local tenant-key cache");
92
+ }
93
+ function messageFlags(command) {
94
+ return command
95
+ .option("--message <text>", "message to send to the agent")
96
+ .option("--message-file <path>", "read the message from a file")
97
+ .option("--async", "return after Hermes queues the message")
98
+ .option("--timeout <duration>", "maximum local wait, such as 30s or 5m", "2m")
99
+ .option("--idempotency-key <key>", "stable retry key; generated when omitted");
100
+ }
101
+ async function selectedAgentId(program, opts) {
102
+ if (opts.agent?.trim())
103
+ return opts.agent.trim();
104
+ return assistantIdFrom(await call({
105
+ path: "/api/v1/agents/mine",
106
+ query: { includeShadow: true },
107
+ asUserId: actingUserId(program),
108
+ }));
109
+ }
110
+ async function credentials(program, opts) {
111
+ const agentId = await selectedAgentId(program, opts);
112
+ const creds = await resolveHermesKey(agentId, {
113
+ refresh: opts.refresh,
114
+ cache: opts.cache,
115
+ asUserId: actingUserId(program),
116
+ });
117
+ return { agentId, creds };
118
+ }
119
+ async function sendAndWait(creds, chatId, text, opts) {
120
+ const queued = await hermesCall({
121
+ ...creds,
122
+ method: "POST",
123
+ path: tenantPath(creds, `/chats/${encodeURIComponent(chatId)}/messages`),
124
+ headers: { "idempotency-key": opts.idempotencyKey ?? randomUUID() },
125
+ body: chatMessageBody(text),
126
+ });
127
+ if (opts.async)
128
+ return { message: queued, timedOut: false };
129
+ return waitForChatMessage(queued, parseDuration(opts.timeout), (messageId, afterRevision, waitSeconds) => hermesCall({
130
+ ...creds,
131
+ path: tenantPath(creds, `/chat-messages/${encodeURIComponent(messageId)}`),
132
+ query: { afterRevision, waitSeconds },
133
+ }));
134
+ }
135
+ export function registerAgent(program) {
136
+ const group = program.command("agent").description("Talk to your Assistant or another owned agent through Hermes tenant chat");
137
+ const chat = group.command("chat").description("Persistent tenant conversations for instructions, skills, flavors, and agent work");
138
+ messageFlags(chatFlags(chat.command("new").description("Create a chat, send its first message, and wait for the agent.")))
139
+ .option("--title <title>", "optional chat title")
140
+ .action(async (opts) => {
141
+ const text = messageText(opts);
142
+ const { agentId, creds } = await credentials(program, opts);
143
+ const created = await hermesCall({
144
+ ...creds,
145
+ method: "POST",
146
+ path: tenantPath(creds, "/chats"),
147
+ body: opts.title ? { title: opts.title } : {},
148
+ });
149
+ const chatId = chatIdFrom(created);
150
+ const result = await sendAndWait(creds, chatId, text, opts);
151
+ out({ agentId, chatId, ...result });
152
+ });
153
+ messageFlags(chatFlags(chat.command("send").description("Continue an existing chat and wait for the agent.")))
154
+ .requiredOption("--chat <chatId>", "chat id returned by chat new")
155
+ .action(async (opts) => {
156
+ const text = messageText(opts);
157
+ const { agentId, creds } = await credentials(program, opts);
158
+ out({ agentId, chatId: opts.chat, ...(await sendAndWait(creds, opts.chat, text, opts)) });
159
+ });
160
+ chatFlags(chat.command("list").description("List the selected agent's chats.")).action(async (opts) => {
161
+ const { agentId, creds } = await credentials(program, opts);
162
+ out({ agentId, result: await hermesCall({ ...creds, path: tenantPath(creds, "/chats") }) });
163
+ });
164
+ chatFlags(chat.command("history").description("List messages in one chat."))
165
+ .requiredOption("--chat <chatId>", "chat id")
166
+ .action(async (opts) => {
167
+ const { agentId, creds } = await credentials(program, opts);
168
+ out({
169
+ agentId,
170
+ chatId: opts.chat,
171
+ result: await hermesCall({ ...creds, path: tenantPath(creds, `/chats/${encodeURIComponent(opts.chat)}/messages`) }),
172
+ });
173
+ });
174
+ chatFlags(chat.command("wait").description("Wait for one queued chat message to finish."))
175
+ .requiredOption("--message-id <messageId>", "message id returned by chat new/send --async")
176
+ .option("--timeout <duration>", "maximum local wait, such as 30s or 5m", "2m")
177
+ .action(async (opts) => {
178
+ const { agentId, creds } = await credentials(program, opts);
179
+ const initial = await hermesCall({ ...creds, path: tenantPath(creds, `/chat-messages/${encodeURIComponent(opts.messageId)}`) });
180
+ out({
181
+ agentId,
182
+ messageId: opts.messageId,
183
+ ...(await waitForChatMessage(initial, parseDuration(opts.timeout), (messageId, afterRevision, waitSeconds) => hermesCall({
184
+ ...creds,
185
+ path: tenantPath(creds, `/chat-messages/${encodeURIComponent(messageId)}`),
186
+ query: { afterRevision, waitSeconds },
187
+ }))),
188
+ });
189
+ });
190
+ }
191
+ //# sourceMappingURL=agent.js.map
@@ -29,10 +29,10 @@ function promptFrom(opts) {
29
29
  return opts.promptFile ? readFileSync(opts.promptFile, "utf8") : opts.prompt;
30
30
  }
31
31
  export function registerCustomAgents(program) {
32
- const group = getOrCreateGroup(program, "custom-agents", "CustomAgents operations");
32
+ const group = getOrCreateGroup(program, "custom-agents", "Legacy hosted-agent administration; use agent chat for Hermes agents");
33
33
  group
34
34
  .command("create-hosted")
35
- .description("Create a WorkCord-hosted custom agent with typed flags.")
35
+ .description("Create a legacy WorkCord-hosted agent directly. For Hermes agents, ask your Assistant instead.")
36
36
  .requiredOption("--name <name>", "agent name")
37
37
  .requiredOption("--description <text>", "short description")
38
38
  .option("--prompt <text>", "system prompt")
@@ -66,7 +66,7 @@ export function registerCustomAgents(program) {
66
66
  });
67
67
  group
68
68
  .command("update-hosted")
69
- .description("Update a WorkCord-hosted custom agent with typed flags.")
69
+ .description("Update a legacy WorkCord-hosted agent directly. For Hermes agents, use agent chat.")
70
70
  .requiredOption("--agent <agentId>", "hosted custom agent id")
71
71
  .option("--name <name>", "agent name")
72
72
  .option("--description <text>", "short description")
@@ -7,19 +7,21 @@ import { hermesBinary, hermesCall, hermesStream, hermesUpload } from "../hermes-
7
7
  import { assertArchiveSize, flavorSetWarnings, guardOwnership, rolloutBody, } from "../hermes-guards.js";
8
8
  import { getOrCreateGroup } from "./_group.js";
9
9
  export function agentRef(opts) {
10
- if (opts.agent && opts.roomAgent) {
11
- throw new Error("Pass exactly one of --agent <id> or --room-agent <id>, not both.");
10
+ const refs = [opts.agent, opts.roomAgent, opts.marketplaceInstall].filter(Boolean);
11
+ if (refs.length !== 1) {
12
+ throw new Error("Pass exactly one of --agent <id>, --room-agent <id>, or --marketplace-install <id>.");
12
13
  }
13
- if (opts.agent)
14
- return opts.agent;
15
14
  if (opts.roomAgent)
16
15
  return `workcord-room-agent:${opts.roomAgent}`;
17
- throw new Error("One of --agent <id> or --room-agent <id> is required.");
16
+ if (opts.marketplaceInstall)
17
+ return `marketplace-install:${opts.marketplaceInstall}`;
18
+ return opts.agent;
18
19
  }
19
20
  function agentFlags(cmd) {
20
21
  return cmd
21
22
  .option("--agent <id>", "custom agent id")
22
23
  .option("--room-agent <id>", "room agent id (resolved as workcord-room-agent:<id>)")
24
+ .option("--marketplace-install <id>", "marketplace flavor install id or its room agent id")
23
25
  .option("--refresh", "re-reveal the tenant key instead of using the cached one")
24
26
  .option("--no-cache", "do not read or write the local tenant-key cache");
25
27
  }
@@ -39,6 +41,9 @@ function parseJson(raw, flag) {
39
41
  function out(data) {
40
42
  process.stdout.write(JSON.stringify(data) + "\n");
41
43
  }
44
+ export function hermesChatTextBody(text) {
45
+ return { parts: [{ type: "text", text }] };
46
+ }
42
47
  async function credentials(program, opts) {
43
48
  return resolveHermesKey(agentRef(opts), {
44
49
  refresh: opts.refresh,
@@ -98,7 +103,7 @@ async function readArchive(archivePath) {
98
103
  return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
99
104
  }
100
105
  export function registerHermes(program) {
101
- const group = getOrCreateGroup(program, "hermes", "Hermes Hub operations for a WorkCord agent's tenant");
106
+ const group = getOrCreateGroup(program, "hermes", "Advanced Hermes Hub diagnostics and recovery for an agent tenant");
102
107
  agentFlags(group.command("key").description("Reveal (and cache) the Hermes Hub tenant key for an agent.")).action(async (opts) => {
103
108
  const creds = await credentials(program, opts);
104
109
  out({ key: creds.key, tenantId: creds.tenantId, hubBaseUrl: creds.hubBaseUrl });
@@ -157,7 +162,7 @@ export function registerHermes(program) {
157
162
  .option("--idempotency-key <key>", "Idempotency-Key header (generated when omitted)")
158
163
  .action(async (opts) => {
159
164
  const creds = await credentials(program, opts);
160
- const body = parseJson(opts.body, "--body") ?? (opts.text !== undefined ? { text: opts.text } : undefined);
165
+ const body = parseJson(opts.body, "--body") ?? (opts.text !== undefined ? hermesChatTextBody(opts.text) : undefined);
161
166
  if (body === undefined) {
162
167
  throw new Error("One of --text or --body is required.");
163
168
  }
@@ -0,0 +1,63 @@
1
+ import { call } from "../../client.js";
2
+ import { actingUserId } from "../../auth.js";
3
+ import { getOrCreateGroup, hasSubcommand } from "../_helpers.js";
4
+ export function register(program) {
5
+ const group = getOrCreateGroup(program, "call-recordings", "CallRecordings operations");
6
+ if (!hasSubcommand(group, "live-state")) {
7
+ group
8
+ .command("live-state")
9
+ .description("GET /api/v1/rooms/{roomId}/call/recording")
10
+ .requiredOption("--room-id <value>", "roomId (path param)")
11
+ .action(async (opts) => {
12
+ const as = actingUserId(program);
13
+ const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/call/recording`;
14
+ const data = await call({ method: "GET", path, asUserId: as });
15
+ process.stdout.write(JSON.stringify(data) + "\n");
16
+ });
17
+ }
18
+ if (!hasSubcommand(group, "live-segments")) {
19
+ group
20
+ .command("live-segments")
21
+ .description("GET /api/v1/rooms/{roomId}/call/transcript/live")
22
+ .requiredOption("--room-id <value>", "roomId (path param)")
23
+ .action(async (opts) => {
24
+ const as = actingUserId(program);
25
+ const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/call/transcript/live`;
26
+ const data = await call({ method: "GET", path, asUserId: as });
27
+ process.stdout.write(JSON.stringify(data) + "\n");
28
+ });
29
+ }
30
+ if (!hasSubcommand(group, "list")) {
31
+ group
32
+ .command("list")
33
+ .description("GET /api/v1/rooms/{roomId}/call/recordings")
34
+ .requiredOption("--room-id <value>", "roomId (path param)")
35
+ .option("--limit <value>", "string")
36
+ .action(async (opts) => {
37
+ const as = actingUserId(program);
38
+ const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/call/recordings`;
39
+ const query = {};
40
+ {
41
+ const v = opts.limit;
42
+ if (v !== undefined)
43
+ query["limit"] = v;
44
+ }
45
+ const data = await call({ method: "GET", path, asUserId: as, query });
46
+ process.stdout.write(JSON.stringify(data) + "\n");
47
+ });
48
+ }
49
+ if (!hasSubcommand(group, "transcript")) {
50
+ group
51
+ .command("transcript")
52
+ .description("GET /api/v1/rooms/{roomId}/call/recordings/{recordingId}/transcript")
53
+ .requiredOption("--room-id <value>", "roomId (path param)")
54
+ .requiredOption("--recording-id <value>", "recordingId (path param)")
55
+ .action(async (opts) => {
56
+ const as = actingUserId(program);
57
+ const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/call/recordings/${encodeURIComponent(opts.recordingId)}/transcript`;
58
+ const data = await call({ method: "GET", path, asUserId: as });
59
+ process.stdout.write(JSON.stringify(data) + "\n");
60
+ });
61
+ }
62
+ }
63
+ //# sourceMappingURL=call-recordings.js.map
@@ -3,6 +3,20 @@ import { actingUserId } from "../../auth.js";
3
3
  import { getOrCreateGroup, hasSubcommand } from "../_helpers.js";
4
4
  export function register(program) {
5
5
  const group = getOrCreateGroup(program, "internal", "Internal operations");
6
+ if (!hasSubcommand(group, "append-call-segments")) {
7
+ group
8
+ .command("append-call-segments")
9
+ .description("POST /api/v1/internal/calls/{callId}/segments")
10
+ .requiredOption("--call-id <value>", "callId (path param)")
11
+ .option("--body <json>", "request body as JSON (no schema available)")
12
+ .action(async (opts) => {
13
+ const as = actingUserId(program);
14
+ const path = `/api/v1/internal/calls/${encodeURIComponent(opts.callId)}/segments`;
15
+ const body = opts.body ? JSON.parse(opts.body) : undefined;
16
+ const data = await call({ method: "POST", path, asUserId: as, body });
17
+ process.stdout.write(JSON.stringify(data) + "\n");
18
+ });
19
+ }
6
20
  if (!hasSubcommand(group, "emit-message")) {
7
21
  group
8
22
  .command("emit-message")
@@ -383,5 +383,20 @@ export function register(program) {
383
383
  process.stdout.write(JSON.stringify(data) + "\n");
384
384
  });
385
385
  }
386
+ if (!hasSubcommand(group, "submit-agent-feedback")) {
387
+ group
388
+ .command("submit-agent-feedback")
389
+ .description("POST /api/v1/rooms/{roomId}/messages/{messageId}/agent-feedback")
390
+ .requiredOption("--room-id <value>", "roomId (path param)")
391
+ .requiredOption("--message-id <value>", "messageId (path param)")
392
+ .option("--body <json>", "request body as JSON (no schema available)")
393
+ .action(async (opts) => {
394
+ const as = actingUserId(program);
395
+ const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/messages/${encodeURIComponent(opts.messageId)}/agent-feedback`;
396
+ const body = opts.body ? JSON.parse(opts.body) : undefined;
397
+ const data = await call({ method: "POST", path, asUserId: as, body });
398
+ process.stdout.write(JSON.stringify(data) + "\n");
399
+ });
400
+ }
386
401
  }
387
402
  //# sourceMappingURL=messages.js.map
@@ -4,6 +4,7 @@ import { register as register_ambassador } from "./commands/ambassador.js";
4
4
  import { register as register_apple_iap } from "./commands/apple-iap.js";
5
5
  import { register as register_attribution } from "./commands/attribution.js";
6
6
  import { register as register_auth } from "./commands/auth.js";
7
+ import { register as register_call_recordings } from "./commands/call-recordings.js";
7
8
  import { register as register_challenges } from "./commands/challenges.js";
8
9
  import { register as register_clips } from "./commands/clips.js";
9
10
  import { register as register_contact_match } from "./commands/contact-match.js";
@@ -70,6 +71,7 @@ export function registerAll(program) {
70
71
  register_apple_iap(program);
71
72
  register_attribution(program);
72
73
  register_auth(program);
74
+ register_call_recordings(program);
73
75
  register_challenges(program);
74
76
  register_clips(program);
75
77
  register_contact_match(program);
@@ -51,7 +51,7 @@ export function assertArchiveSize(bytes, path) {
51
51
  }
52
52
  export function ownershipError(kind, namespaceId, tenantId, body) {
53
53
  const who = tenantId || "this tenant";
54
- const err = new Error(`Hermes Hub refused this as ${who}: it does not own the ${kind} namespace "${namespaceId}". A WorkCord agent tenant is provisioned as a consumer and owns no ${kind} namespace. Ownership is admin-assigned only (PUT /v1/${kind}s/${namespaceId}/owner) and there is no self-service bootstrap — ask a hub operator to assign "${namespaceId}" to ${who}.`);
54
+ const err = new Error(`Hermes Hub refused this as ${who}: it does not own the ${kind} namespace "${namespaceId}". For normal customization, ask the owning agent with workcord agent chat; it can create or update resources in its own namespace. Direct Hermes publishing is advanced recovery and requires an owned namespace.`);
55
55
  err.status = 403;
56
56
  err.body = body;
57
57
  return err;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fulldivevr/workcord-cli",
3
- "version": "1.2.0",
4
- "description": "CLI for the WorkCord API. One invocation = one user action.",
3
+ "version": "1.3.0",
4
+ "description": "Agent-first CLI using persistent Hermes tenant chat, with advanced API diagnostics.",
5
5
  "private": false,
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",