@fulldivevr/workcord-cli 1.2.1 → 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.
@@ -144,6 +146,48 @@ workcord [--as <userId>] <namespace> <command> [flags]
144
146
  `--as` is only needed when minting with `WORKCORD_JWT_SECRET`; with
145
147
  `WORKCORD_TOKEN` set, omit it everywhere.
146
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
+
147
191
  - `workcord --help` lists namespaces.
148
192
  - `workcord <namespace> --help` lists that namespace's commands.
149
193
  - `workcord <namespace> <command> --help` shows the command's flags.
@@ -206,7 +250,7 @@ workcord --as USER rooms discover --search "ai" --tags ai,tools --limit 20
206
250
  workcord --as USER rooms create-with-message --name "ops" --public --tags ai,news --text "First post."
207
251
  workcord --as USER rooms members --room R
208
252
  workcord --as USER rooms upload-icon-file --room R --file ./icon.png
209
- 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."
210
254
  workcord --as USER room-agents install-hosted --room R --agent system-wizeup
211
255
  workcord --as USER messages react --room R --message M --emoji 👍
212
256
  workcord --as USER dm send-text --to USER_B --text "hi"
@@ -272,12 +316,14 @@ workcord --as $U dm send-text --to $OTHER --text "hey"
272
316
  # → composite: opens (or finds) the DM room, sends the message, returns both
273
317
  ```
274
318
 
275
- ## Hermes Hub (`hermes` namespace)
319
+ ## Advanced Hermes diagnostics and recovery (`hermes` namespace)
276
320
 
277
- WorkCord provisions a **Hermes Hub tenant** for each room-agent and each Hermes
278
- custom agent. That tenant is where the agent actually runs: its chats, runs,
279
- files, skills, flavor pin, A2A peers and eval runs all live on the Hub, not in
280
- 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.
281
327
 
282
328
  This is the one namespace that talks to a **second origin**. Every other command
283
329
  goes to the WorkCord API; `hermes` uses the WorkCord API only to reveal the
@@ -291,8 +337,11 @@ Every `hermes` command takes exactly one of:
291
337
  | --- | --- |
292
338
  | `--agent <id>` | a custom agent id, used verbatim |
293
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 |
294
341
 
295
- 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.
296
345
 
297
346
  ### Credentials
298
347
 
@@ -346,6 +395,7 @@ material.
346
395
 
347
396
  ```bash
348
397
  workcord hermes whoami --room-agent $RA
398
+ workcord hermes whoami --marketplace-install $MARKETPLACE_INSTALL
349
399
  workcord hermes chat send --room-agent $RA --chat $CHAT --text "status?"
350
400
  workcord hermes flavor current --agent $AGENT
351
401
  workcord hermes peers find --agent $AGENT --query "translation" --limit 5
@@ -355,15 +405,16 @@ Version flags are named `--flavor-version` and `--skill-version`, not
355
405
  `--version`: commander's program-level `.version()` matches `--version`
356
406
  anywhere in argv, prints the CLI version and exits before the subcommand runs.
357
407
 
358
- ### 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.
359
414
 
360
- `flavor publish`, `flavor rollout` and `skill publish` are **owner-gated on the
361
- Hub**. A WorkCord agent tenant is provisioned as a *consumer* and owns no flavor
362
- or skill namespace, so these will 403 out of the box. Ownership is
363
- admin-assigned only (`PUT /v1/flavors/{id}/owner`, likewise for skills) and
364
- there is **no self-service bootstrap** — a hub operator has to assign the
365
- namespace to your tenant first. The CLI turns that 403 into an explicit error
366
- 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.
367
418
 
368
419
  The CLI does **not** pack or untar archives. The authoring loop is:
369
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.1",
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",