@fulldivevr/workcord-cli 1.3.0 → 2.17.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 +91 -41
- package/dist/auth.js +24 -8
- package/dist/cli.js +7 -3
- package/dist/client.js +8 -1
- package/dist/commands/agent-skills.js +127 -0
- package/dist/commands/hub.js +667 -0
- package/dist/commands/messages.js +150 -4
- package/dist/commands/reflections.js +96 -0
- package/dist/commands/rooms.js +7 -3
- package/dist/generated/commands/agent-feedback-admin.js +69 -0
- package/dist/generated/commands/ai.js +31 -0
- package/dist/generated/commands/call-recordings.js +56 -0
- package/dist/generated/commands/contact-match.js +20 -1
- package/dist/generated/commands/credit-packs.js +112 -0
- package/dist/generated/commands/custom-agents.js +483 -1
- package/dist/generated/commands/desktop-releases.js +2 -2
- package/dist/generated/commands/edge-sizecheck.js +25 -0
- package/dist/generated/commands/feed.js +6 -0
- package/dist/generated/commands/interactive-events.js +47 -0
- package/dist/generated/commands/internal.js +191 -0
- package/dist/generated/commands/meetings.js +12 -0
- package/dist/generated/commands/messages.js +15 -0
- package/dist/generated/commands/my-feedback.js +18 -0
- package/dist/generated/commands/pinned-messages.js +6 -0
- package/dist/generated/commands/proxy-nodes.js +83 -0
- package/dist/generated/commands/push.js +20 -0
- package/dist/generated/commands/reviews.js +96 -0
- package/dist/generated/commands/room-agents.js +243 -1
- package/dist/generated/commands/room-invites.js +12 -0
- package/dist/generated/commands/rooms.js +79 -1
- package/dist/generated/commands/search.js +1 -1
- package/dist/generated/commands/shadow-dispatch-push.js +20 -0
- package/dist/generated/commands/shadow-reflections.js +111 -0
- package/dist/generated/index.js +20 -0
- package/dist/http.js +6 -2
- package/dist/room-types.js +10 -0
- package/package.json +1 -1
- package/dist/generated/commands/health.js +0 -29
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @fulldivevr/workcord-cli
|
|
2
2
|
|
|
3
|
-
TypeScript CLI for talking to WorkCord agents through persistent
|
|
3
|
+
TypeScript CLI for talking to WorkCord agents through persistent Autoworker Hub tenant
|
|
4
4
|
chat and using the WorkCord API. **One invocation = one user action.** Designed
|
|
5
5
|
for automated agents such as Claude Code: ask the user's Assistant for the
|
|
6
6
|
outcome you need, let that tenant manage its own instructions, skills, and
|
|
@@ -149,7 +149,7 @@ workcord [--as <userId>] <namespace> <command> [flags]
|
|
|
149
149
|
### Agent-first customization
|
|
150
150
|
|
|
151
151
|
To change agent behavior, create or update a skill, or maintain a flavor, talk
|
|
152
|
-
to the owning agent through
|
|
152
|
+
to the owning agent through Hub tenant chat. Do not edit prompts or tenant
|
|
153
153
|
files directly. With no `--agent`, the user's non-deletable Assistant is the
|
|
154
154
|
target and becomes the default owner of anything it creates:
|
|
155
155
|
|
|
@@ -164,7 +164,7 @@ Use the returned `chatId` for follow-up, verification, or rollback:
|
|
|
164
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
165
|
```
|
|
166
166
|
|
|
167
|
-
Both commands wait up to two minutes by default and return the durable
|
|
167
|
+
Both commands wait up to two minutes by default and return the durable Hub
|
|
168
168
|
message document. Use `--async` for a queued response, then continue with its
|
|
169
169
|
`messageId`:
|
|
170
170
|
|
|
@@ -184,8 +184,8 @@ workcord agent chat new --agent "$AGENT_ID" --message "Create and maintain a fin
|
|
|
184
184
|
|
|
185
185
|
The Assistant or selected agent works within its tenant's Hub permissions. It
|
|
186
186
|
can safely validate changes and report what it activated. `agent chat` uses
|
|
187
|
-
|
|
188
|
-
A2A remains available for external agent peers. Direct `
|
|
187
|
+
the Hub's native durable `hub_chat` transport; it is not raw A2A wire traffic.
|
|
188
|
+
A2A remains available for external agent peers. Direct `hub` commands are
|
|
189
189
|
advanced diagnostics and recovery tools, not the normal customization workflow.
|
|
190
190
|
|
|
191
191
|
- `workcord --help` lists namespaces.
|
|
@@ -205,6 +205,7 @@ workcord <namespace> <command> [--<path-param> <value>] [--body '<json>'] [--que
|
|
|
205
205
|
```bash
|
|
206
206
|
workcord auth login --email you@company.com
|
|
207
207
|
workcord --as USER messages send-text --room R --text "hello"
|
|
208
|
+
workcord messages ask-agent --room R --agent INSTALLATION_ID --text "Summarize the decision"
|
|
208
209
|
```
|
|
209
210
|
|
|
210
211
|
Semantic aliases cover common agent/admin actions with consistent flags:
|
|
@@ -225,6 +226,10 @@ Server auto-extracts mention tokens from visible `@Name` text. Prefer
|
|
|
225
226
|
member's display name and avoids exposing raw user ids. `auth login` never
|
|
226
227
|
takes `--as` — it is how you obtain a `WORKCORD_TOKEN` in the first place.
|
|
227
228
|
|
|
229
|
+
`messages ask-agent` posts an ordinary visible `@Agent` request, then waits for
|
|
230
|
+
that installed agent's visible reply in the room or request thread. Pass the
|
|
231
|
+
`installationId` returned by Home to `--agent`.
|
|
232
|
+
|
|
228
233
|
### Error handling
|
|
229
234
|
|
|
230
235
|
Every error is **a single JSON line on stderr**, with a non-zero exit code:
|
|
@@ -316,22 +321,25 @@ workcord --as $U dm send-text --to $OTHER --text "hey"
|
|
|
316
321
|
# → composite: opens (or finds) the DM room, sends the message, returns both
|
|
317
322
|
```
|
|
318
323
|
|
|
319
|
-
## Advanced
|
|
324
|
+
## Advanced Hub diagnostics and recovery (`hub` namespace)
|
|
320
325
|
|
|
321
|
-
WorkCord provisions
|
|
326
|
+
WorkCord provisions an **Autoworker Hub tenant** for each room-agent, custom
|
|
322
327
|
agent, and room-scoped marketplace flavor install. That tenant is where the
|
|
323
328
|
agent actually runs: its chats, runs, files, skills, flavor pin, A2A peers and
|
|
324
329
|
eval runs all live on the Hub, not in WorkCord. Normal changes should be
|
|
325
|
-
requested with `workcord agent chat`; the `
|
|
330
|
+
requested with `workcord agent chat`; the `hub` namespace exists for direct
|
|
326
331
|
inspection, diagnostics, and recovery.
|
|
327
332
|
|
|
328
333
|
This is the one namespace that talks to a **second origin**. Every other command
|
|
329
|
-
goes to the WorkCord API; `
|
|
334
|
+
goes to the WorkCord API; `hub` uses the WorkCord API only to reveal the
|
|
330
335
|
tenant key, then calls the Hub's `/v1/tenant-api/*` routes with it.
|
|
331
336
|
|
|
337
|
+
`workcord hub ...` is a hidden alias of `workcord hub ...`, kept for one
|
|
338
|
+
release. New scripts should use `hub`.
|
|
339
|
+
|
|
332
340
|
### Target selection
|
|
333
341
|
|
|
334
|
-
Every `
|
|
342
|
+
Every `hub` command takes exactly one of:
|
|
335
343
|
|
|
336
344
|
| Flag | Meaning |
|
|
337
345
|
| --- | --- |
|
|
@@ -350,11 +358,16 @@ and is **never** accepted as a flag:
|
|
|
350
358
|
|
|
351
359
|
| Env var | Meaning |
|
|
352
360
|
| --- | --- |
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
355
|
-
| `
|
|
361
|
+
| `WORKCORD_HUB_KEY` | tenant key, used verbatim; requires `WORKCORD_HUB_URL` |
|
|
362
|
+
| `WORKCORD_HUB_URL` | Hub origin (bare origin, no path); requires `WORKCORD_HUB_KEY` |
|
|
363
|
+
| `WORKCORD_HUB_TENANT_ID` | tenant id for the tenant-scoped routes, alongside the two above |
|
|
364
|
+
| `WORKCORD_HUB_MANAGED` | `1` enables managed mode (see below) |
|
|
365
|
+
|
|
366
|
+
The older `WORKCORD_HERMES_KEY`, `WORKCORD_HERMES_HUB_URL`,
|
|
367
|
+
`WORKCORD_HERMES_TENANT_ID` and `WORKCORD_HERMES_MANAGED` names still work and
|
|
368
|
+
take precedence when both are set.
|
|
356
369
|
|
|
357
|
-
Setting only one of `
|
|
370
|
+
Setting only one of `WORKCORD_HUB_KEY` / `WORKCORD_HUB_URL` is an
|
|
358
371
|
error — the CLI will not half-guess a hub.
|
|
359
372
|
|
|
360
373
|
With none of them set, the CLI calls
|
|
@@ -371,34 +384,56 @@ under `hermes`, alongside the saved login. The file is written `0600` inside a
|
|
|
371
384
|
| `--refresh` | ignore the cached entry, re-reveal, rewrite the cache |
|
|
372
385
|
| `--no-cache` | neither read nor write the cache for this invocation |
|
|
373
386
|
|
|
374
|
-
Env credentials always win over the cache. `
|
|
387
|
+
Env credentials always win over the cache. `hub key` prints the resolved
|
|
375
388
|
credentials on stdout — that is its whole job; no other command emits key
|
|
376
389
|
material.
|
|
377
390
|
|
|
391
|
+
### Managed mode
|
|
392
|
+
|
|
393
|
+
A personal Shadow tenant runs this CLI with a WorkCord token that WorkCord
|
|
394
|
+
resolves to the owner user. It must use the same `hub` commands, but with a
|
|
395
|
+
per-call reveal and nothing on disk. Managed mode is enabled by
|
|
396
|
+
`WORKCORD_HUB_MANAGED=1` (or `WORKCORD_HERMES_MANAGED=1`), or per invocation
|
|
397
|
+
with `--managed`. In managed mode the CLI:
|
|
398
|
+
|
|
399
|
+
- always reveals the key through `POST /api/v1/agents/{id}/hermes-cli-key/reveal`,
|
|
400
|
+
- never reads or writes `~/.workcord/config.json` for hub credentials,
|
|
401
|
+
- ignores `WORKCORD_HUB_KEY` / `WORKCORD_HERMES_KEY`,
|
|
402
|
+
- rejects `--as` with an error,
|
|
403
|
+
- never prints the key (`hub key` prints only `tenantId` and `hubBaseUrl`).
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
export WORKCORD_TOKEN=... # the owner user's token
|
|
407
|
+
export WORKCORD_HUB_MANAGED=1
|
|
408
|
+
workcord hub flavor set --agent $AGENT --flavor-version 1.4.0
|
|
409
|
+
workcord hub flavor subscribe --agent $AGENT --policy stable
|
|
410
|
+
workcord hub agent restart --agent $AGENT
|
|
411
|
+
```
|
|
412
|
+
|
|
378
413
|
### Commands
|
|
379
414
|
|
|
380
415
|
| Group | What |
|
|
381
416
|
| --- | --- |
|
|
382
|
-
| `
|
|
383
|
-
| `
|
|
384
|
-
| `
|
|
385
|
-
| `
|
|
386
|
-
| `
|
|
387
|
-
| `
|
|
388
|
-
| `
|
|
389
|
-
| `
|
|
390
|
-
| `
|
|
391
|
-
| `
|
|
392
|
-
| `
|
|
393
|
-
| `
|
|
394
|
-
| `
|
|
417
|
+
| `hub key` / `whoami` / `runtime` | reveal credentials, show the tenant, show its runtime |
|
|
418
|
+
| `hub agent status\|start\|stop\|restart\|card` | tenant agent lifecycle |
|
|
419
|
+
| `hub chat list\|create\|get\|messages\|send\|message\|stream\|stop\|approve` | chats and chat messages (`stream` is SSE) |
|
|
420
|
+
| `hub run create\|get\|stream\|stop\|approve` | runs |
|
|
421
|
+
| `hub files ls\|cat\|download` | tenant-visible files |
|
|
422
|
+
| `hub share groups\|ls\|read\|stat\|grep\|find\|write\|mkdir\|move\|rm` | share-group filesystem |
|
|
423
|
+
| `hub skills list\|enable\|disable\|files\|cat` | skills installed on this tenant |
|
|
424
|
+
| `hub flavor current\|catalog\|get\|set\|subscribe\|checkout\|publish\|rollout` | flavor marketplace and this tenant's pin |
|
|
425
|
+
| `hub skill catalog\|get\|checkout\|publish` | skill marketplace |
|
|
426
|
+
| `hub eval suites\|run\|list\|get\|cancel` | flavor eval suites and runs |
|
|
427
|
+
| `hub peers list\|get\|call\|find` | A2A peers |
|
|
428
|
+
| `hub conversations list\|get\|messages` | A2A conversations |
|
|
429
|
+
| `hub sessions list`, `hub artifacts` | agent sessions and artifacts |
|
|
395
430
|
|
|
396
431
|
```bash
|
|
397
|
-
workcord
|
|
398
|
-
workcord
|
|
399
|
-
workcord
|
|
400
|
-
workcord
|
|
401
|
-
workcord
|
|
432
|
+
workcord hub whoami --room-agent $RA
|
|
433
|
+
workcord hub whoami --marketplace-install $MARKETPLACE_INSTALL
|
|
434
|
+
workcord hub chat send --room-agent $RA --chat $CHAT --text "status?"
|
|
435
|
+
workcord hub flavor current --agent $AGENT
|
|
436
|
+
workcord hub peers find --agent $AGENT --query "translation" --limit 5
|
|
402
437
|
```
|
|
403
438
|
|
|
404
439
|
Version flags are named `--flavor-version` and `--skill-version`, not
|
|
@@ -408,7 +443,7 @@ anywhere in argv, prints the CLI version and exits before the subcommand runs.
|
|
|
408
443
|
### Direct flavor and skill recovery
|
|
409
444
|
|
|
410
445
|
For normal work, ask the Assistant or another owned agent to create, update,
|
|
411
|
-
validate, or activate its own flavor and skills.
|
|
446
|
+
validate, or activate its own flavor and skills. Autoworker Hub supports
|
|
412
447
|
tenant-owned skill and flavor namespaces; ownership defaults to whichever agent
|
|
413
448
|
tenant performs the creation.
|
|
414
449
|
|
|
@@ -419,9 +454,9 @@ the selected tenant and can only mutate namespaces that tenant owns.
|
|
|
419
454
|
The CLI does **not** pack or untar archives. The authoring loop is:
|
|
420
455
|
|
|
421
456
|
```bash
|
|
422
|
-
workcord
|
|
423
|
-
# unpack, edit, and repack with
|
|
424
|
-
workcord
|
|
457
|
+
workcord hub flavor checkout --agent $AGENT --flavor-version 1.4.0 --out flavor.tgz
|
|
458
|
+
# unpack, edit, and repack with the Hub's own tooling
|
|
459
|
+
workcord hub flavor publish --agent $AGENT --archive flavor.tgz
|
|
425
460
|
```
|
|
426
461
|
|
|
427
462
|
`--archive` must already be a gzipped package built beforehand; anything over
|
|
@@ -462,10 +497,10 @@ without running `regen` if you don't need the latest server changes.
|
|
|
462
497
|
src/
|
|
463
498
|
├── cli.ts commander entry, global --as flag
|
|
464
499
|
├── auth.ts auth resolution: WORKCORD_TOKEN vs minted --as JWT,
|
|
465
|
-
│ plus
|
|
500
|
+
│ plus Autoworker Hub key resolution
|
|
466
501
|
├── http.ts shared fetch primitives (retry, errors, query)
|
|
467
502
|
├── client.ts WorkCord fetch wrapper (JSON + multipart)
|
|
468
|
-
├── hermes-client.ts
|
|
503
|
+
├── hermes-client.ts Autoworker Hub fetch wrapper (JSON, upload, SSE, binary)
|
|
469
504
|
├── hermes-guards.ts rollout/ownership/archive guards, version compare
|
|
470
505
|
├── commands/ hand-written semantic commands
|
|
471
506
|
└── generated/ auto-generated from openapi.json
|
|
@@ -506,7 +541,21 @@ part of any automated gate.
|
|
|
506
541
|
|
|
507
542
|
## Release
|
|
508
543
|
|
|
509
|
-
|
|
544
|
+
The backend release workflow synchronizes API, CLI, and app release versions.
|
|
545
|
+
It regenerates commands, tests, builds, and packs the CLI before deployment.
|
|
546
|
+
After all deployments succeed, it publishes that archive to npmjs.org: `main`
|
|
547
|
+
updates `latest`, and `dev` updates `dev`. Manual backend deployments do not
|
|
548
|
+
publish. App workflows increment only the Flutter build number.
|
|
549
|
+
|
|
550
|
+
Configure npm trusted publishing for `fulldiveVR/wizeup-rooms-api`, workflow
|
|
551
|
+
`deploy-wize-rooms-api.yml`, with permission to publish and manage dist-tags for
|
|
552
|
+
`@fulldivevr/workcord-cli`. The API repository's `TOKEN_FOR_SUB` secret must also
|
|
553
|
+
have contents read/write access to `fulldiveVR/wizeup-rooms` so that release CI
|
|
554
|
+
can synchronize `pubspec.yaml`. Merge the app workflow changes before enabling
|
|
555
|
+
the backend pipeline. An existing npm version must match the verified archive;
|
|
556
|
+
release CI fails on a conflicting archive or a version that would lower a tag.
|
|
557
|
+
|
|
558
|
+
For a manual recovery publish:
|
|
510
559
|
|
|
511
560
|
1. `npm run regen` — refresh the spec and generated commands; commit the diff.
|
|
512
561
|
2. `npm run typecheck`
|
|
@@ -515,7 +564,8 @@ Publishing is a manual maintainer action — there is no auto-publish CI.
|
|
|
515
564
|
`README.md`, `LICENSE`, `package.json`. No sources, no `openapi.json`, no
|
|
516
565
|
`.npmrc`.
|
|
517
566
|
5. Smoke: `node dist/cli.js --help` and `node dist/cli.js --version`.
|
|
518
|
-
6.
|
|
567
|
+
6. Use the backend release version, then publish with explicit registry overrides:
|
|
568
|
+
`npm publish --access public --registry=https://registry.npmjs.org --@fulldivevr:registry=https://registry.npmjs.org`.
|
|
519
569
|
|
|
520
570
|
`prepack` re-runs `typecheck` + `test` + `build`, so steps 2–3 also gate the publish.
|
|
521
571
|
|
package/dist/auth.js
CHANGED
|
@@ -28,7 +28,14 @@ export function resolveAuth(asUserId, env = process.env, config = {}) {
|
|
|
28
28
|
}
|
|
29
29
|
throw new Error("No credentials. Run `workcord auth login --email <address> --save`, or set WORKCORD_TOKEN. For privileged impersonation, set WORKCORD_JWT_SECRET and pass --as <userId>.");
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
function envFlag(value) {
|
|
32
|
+
const v = value?.trim().toLowerCase();
|
|
33
|
+
return v === "1" || v === "true" || v === "yes";
|
|
34
|
+
}
|
|
35
|
+
export function isManagedHubMode(env = process.env) {
|
|
36
|
+
return envFlag(env.WORKCORD_HERMES_MANAGED) || envFlag(env.WORKCORD_HUB_MANAGED);
|
|
37
|
+
}
|
|
38
|
+
export async function revealHermesKey(agentRef, asUserId) {
|
|
32
39
|
const { call } = await import("./client.js");
|
|
33
40
|
const res = (await call({
|
|
34
41
|
method: "POST",
|
|
@@ -36,25 +43,34 @@ async function revealHermesKey(agentRef, asUserId) {
|
|
|
36
43
|
asUserId,
|
|
37
44
|
}));
|
|
38
45
|
if (!res?.key) {
|
|
39
|
-
throw new Error(`Reveal for ${agentRef} returned no
|
|
46
|
+
throw new Error(`Reveal for ${agentRef} returned no hub tenant key`);
|
|
40
47
|
}
|
|
41
48
|
if (!res.hubBaseUrl) {
|
|
42
|
-
throw new Error(`Reveal for ${agentRef} returned no hubBaseUrl. Set
|
|
49
|
+
throw new Error(`Reveal for ${agentRef} returned no hubBaseUrl. Set WORKCORD_HUB_URL (with WORKCORD_HUB_KEY) to target the hub explicitly.`);
|
|
43
50
|
}
|
|
44
51
|
return { key: res.key, tenantId: res.tenantId ?? "", hubBaseUrl: res.hubBaseUrl };
|
|
45
52
|
}
|
|
46
53
|
export async function resolveHermesKey(agentRef, opts = {}) {
|
|
47
54
|
const env = opts.env ?? process.env;
|
|
48
|
-
const
|
|
49
|
-
|
|
55
|
+
const managed = opts.managed ?? isManagedHubMode(env);
|
|
56
|
+
if (managed) {
|
|
57
|
+
if (opts.asUserId) {
|
|
58
|
+
throw new Error("--as is not allowed in managed hub mode (WORKCORD_HUB_MANAGED): the hub key is always revealed for the token's own user.");
|
|
59
|
+
}
|
|
60
|
+
const revealed = await (opts.reveal ?? revealHermesKey)(agentRef);
|
|
61
|
+
return { ...revealed, source: "reveal" };
|
|
62
|
+
}
|
|
63
|
+
const envKey = env.WORKCORD_HERMES_KEY?.trim() || env.WORKCORD_HUB_KEY?.trim() || undefined;
|
|
64
|
+
const envHub = env.WORKCORD_HERMES_HUB_URL?.trim() || env.WORKCORD_HUB_URL?.trim() || undefined;
|
|
65
|
+
const envTenant = env.WORKCORD_HERMES_TENANT_ID?.trim() || env.WORKCORD_HUB_TENANT_ID?.trim() || "";
|
|
50
66
|
if (envKey) {
|
|
51
67
|
if (!envHub) {
|
|
52
|
-
throw new Error("WORKCORD_HERMES_KEY is set but WORKCORD_HERMES_HUB_URL is not. Set both, or unset the key to reveal it from WorkCord.");
|
|
68
|
+
throw new Error("WORKCORD_HUB_KEY (WORKCORD_HERMES_KEY) is set but WORKCORD_HUB_URL (WORKCORD_HERMES_HUB_URL) is not. Set both, or unset the key to reveal it from WorkCord.");
|
|
53
69
|
}
|
|
54
|
-
return { key: envKey, tenantId:
|
|
70
|
+
return { key: envKey, tenantId: envTenant, hubBaseUrl: envHub, source: "env" };
|
|
55
71
|
}
|
|
56
72
|
if (envHub && !envKey) {
|
|
57
|
-
throw new Error("WORKCORD_HERMES_HUB_URL is set but WORKCORD_HERMES_KEY is not. Set both, or unset the hub URL to reveal both from WorkCord.");
|
|
73
|
+
throw new Error("WORKCORD_HUB_URL (WORKCORD_HERMES_HUB_URL) is set but WORKCORD_HUB_KEY (WORKCORD_HERMES_KEY) is not. Set both, or unset the hub URL to reveal both from WorkCord.");
|
|
58
74
|
}
|
|
59
75
|
const useCache = opts.cache !== false;
|
|
60
76
|
const config = opts.config ?? (await readConfig());
|
package/dist/cli.js
CHANGED
|
@@ -12,15 +12,17 @@ import { registerRoomAgents } from "./commands/room-agents.js";
|
|
|
12
12
|
import { registerCustomAgents } from "./commands/custom-agents.js";
|
|
13
13
|
import { registerChallenges } from "./commands/challenges.js";
|
|
14
14
|
import { registerHealth } from "./commands/health.js";
|
|
15
|
-
import {
|
|
15
|
+
import { registerHub } from "./commands/hub.js";
|
|
16
16
|
import { registerAgent } from "./commands/agent.js";
|
|
17
|
+
import { registerAgentSkills } from "./commands/agent-skills.js";
|
|
18
|
+
import { registerReflections } from "./commands/reflections.js";
|
|
17
19
|
import { registerAll as registerGenerated } from "./generated/index.js";
|
|
18
20
|
const program = new Command();
|
|
19
21
|
const require = createRequire(import.meta.url);
|
|
20
22
|
const { version } = require("../package.json");
|
|
21
23
|
program
|
|
22
24
|
.name("workcord")
|
|
23
|
-
.description("Talk to WorkCord agents through
|
|
25
|
+
.description("Talk to WorkCord agents through Autoworker Hub tenant chat, with advanced API diagnostics.")
|
|
24
26
|
.version(version)
|
|
25
27
|
.option("--as <userId>", "user to act as")
|
|
26
28
|
.option("--base-url <url>", "API base URL (overrides WORKCORD_API_URL and saved configuration)");
|
|
@@ -36,9 +38,11 @@ registerDm(program);
|
|
|
36
38
|
registerUsers(program);
|
|
37
39
|
registerRoomAgents(program);
|
|
38
40
|
registerCustomAgents(program);
|
|
41
|
+
registerAgentSkills(program);
|
|
42
|
+
registerReflections(program);
|
|
39
43
|
registerChallenges(program); // adds regenerate-cards (not in the OpenAPI spec)
|
|
40
44
|
registerHealth(program);
|
|
41
|
-
|
|
45
|
+
registerHub(program);
|
|
42
46
|
registerGenerated(program);
|
|
43
47
|
program.parseAsync().catch((err) => {
|
|
44
48
|
const out = { error: err.message ?? String(err) };
|
package/dist/client.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { resolveAuth } from "./auth.js";
|
|
2
2
|
import { DEFAULT_BASE_URL, readConfig } from "./config.js";
|
|
3
3
|
import { applyQuery, fetchWithRetry, httpError, normalizeBaseUrl, safeJson } from "./http.js";
|
|
4
|
+
import { validateRoomCreateBody } from "./room-types.js";
|
|
4
5
|
let baseUrlOverride;
|
|
5
6
|
export function setBaseUrlOverride(url) {
|
|
6
7
|
baseUrlOverride = url;
|
|
7
8
|
}
|
|
8
9
|
export async function getBaseUrl() {
|
|
9
10
|
const config = await readConfig();
|
|
10
|
-
return normalizeBaseUrl(baseUrlOverride
|
|
11
|
+
return normalizeBaseUrl(baseUrlOverride
|
|
12
|
+
|| process.env.WORKCORD_API_URL
|
|
13
|
+
|| process.env.WORKCORD_API_URL_TOKEN
|
|
14
|
+
|| config.baseUrl
|
|
15
|
+
|| DEFAULT_BASE_URL);
|
|
11
16
|
}
|
|
12
17
|
export async function call({ method = "GET", path, query, body, headers, asUserId }) {
|
|
13
18
|
const config = await readConfig();
|
|
@@ -18,6 +23,8 @@ export async function callPublic({ method = "GET", path, query, body, headers })
|
|
|
18
23
|
return request({ method, path, query, body, headers });
|
|
19
24
|
}
|
|
20
25
|
async function request({ method = "GET", path, query, body, headers, bearer }) {
|
|
26
|
+
if (method === "POST" && path === "/api/v1/rooms")
|
|
27
|
+
validateRoomCreateBody(body);
|
|
21
28
|
const base = await getBaseUrl();
|
|
22
29
|
const url = applyQuery(new URL(base + path), query);
|
|
23
30
|
const res = await fetchWithRetry(() => ({
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { actingUserId } from "../auth.js";
|
|
2
|
+
import { call } from "../client.js";
|
|
3
|
+
import { getOrCreateGroup } from "./_group.js";
|
|
4
|
+
const SKILL_ID = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
5
|
+
const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
6
|
+
export function parseSkillRef(ref) {
|
|
7
|
+
const at = ref.lastIndexOf("@");
|
|
8
|
+
if (at <= 0 || at === ref.length - 1)
|
|
9
|
+
throw new Error("skill must be ID@VERSION, e.g. web-research@1.0.0");
|
|
10
|
+
return { id: skillId(ref.slice(0, at)), version: semver(ref.slice(at + 1)) };
|
|
11
|
+
}
|
|
12
|
+
function skillId(id) {
|
|
13
|
+
if (!SKILL_ID.test(id))
|
|
14
|
+
throw new Error("skill id must be a lowercase slug");
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
function semver(version) {
|
|
18
|
+
if (!SEMVER.test(version))
|
|
19
|
+
throw new Error("version must be a semantic version, e.g. 1.0.0");
|
|
20
|
+
return version;
|
|
21
|
+
}
|
|
22
|
+
function bool(value) {
|
|
23
|
+
if (value === "true")
|
|
24
|
+
return true;
|
|
25
|
+
if (value === "false")
|
|
26
|
+
return false;
|
|
27
|
+
throw new Error("enabled must be true or false");
|
|
28
|
+
}
|
|
29
|
+
function skillPath(path) {
|
|
30
|
+
const segments = path.split("/");
|
|
31
|
+
if (segments.some((s) => !s || s === "." || s === ".."))
|
|
32
|
+
throw new Error("invalid skill file path");
|
|
33
|
+
return segments.map(encodeURIComponent).join("/");
|
|
34
|
+
}
|
|
35
|
+
// Agent ids are uuids, optionally behind the prefix a room agent is listed under.
|
|
36
|
+
const AGENT_ID = /^(?:workcord-room-agent:|marketplace-install:)?[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$/i;
|
|
37
|
+
/** Pick the one agent named `name` (exact, case-insensitive) from a list-mine response. */
|
|
38
|
+
export function agentIdByName(name, mine) {
|
|
39
|
+
const agents = (mine?.data ?? []);
|
|
40
|
+
const wanted = name.toLowerCase();
|
|
41
|
+
const found = (Array.isArray(agents) ? agents : [])
|
|
42
|
+
.filter((a) => typeof a?.name === "string" && a.name.toLowerCase() === wanted);
|
|
43
|
+
if (found.length === 0)
|
|
44
|
+
throw new Error(`no agent named ${name}`);
|
|
45
|
+
if (found.length > 1) {
|
|
46
|
+
throw new Error(`more than one agent named ${name}: ${found.map((a) => `${a.name} (${a.id})`).join(", ")}`);
|
|
47
|
+
}
|
|
48
|
+
return String(found[0].id);
|
|
49
|
+
}
|
|
50
|
+
function agentPath(agentId, suffix) {
|
|
51
|
+
return `/api/v1/agents/${encodeURIComponent(agentId)}/${suffix}`;
|
|
52
|
+
}
|
|
53
|
+
export function registerAgentSkills(program, send = call, output = (value) => process.stdout.write(JSON.stringify(value) + "\n")) {
|
|
54
|
+
const agents = getOrCreateGroup(program, "custom-agents", "CustomAgents operations");
|
|
55
|
+
const skills = agents.command("skills").description("Inspect and manage skills on an agent you own");
|
|
56
|
+
const withActor = (req) => {
|
|
57
|
+
const asUserId = actingUserId(program);
|
|
58
|
+
return asUserId ? { ...req, asUserId } : req;
|
|
59
|
+
};
|
|
60
|
+
// The server takes ids only, so a name is resolved here against list-mine.
|
|
61
|
+
const resolve = async (agent) => {
|
|
62
|
+
if (AGENT_ID.test(agent))
|
|
63
|
+
return agent;
|
|
64
|
+
const mine = await send(withActor({ method: "GET", path: "/api/v1/agents/mine", query: { includeShadow: "true" } }));
|
|
65
|
+
return agentIdByName(agent, mine);
|
|
66
|
+
};
|
|
67
|
+
const run = async (agent, req) => {
|
|
68
|
+
const result = await send(withActor(req(await resolve(agent))));
|
|
69
|
+
if (result != null)
|
|
70
|
+
output(result);
|
|
71
|
+
};
|
|
72
|
+
agents.command("stop")
|
|
73
|
+
.description("Stop an agent you own. Its runtime returns to cold-idle.")
|
|
74
|
+
.argument("<agent>", "agent id or exact name")
|
|
75
|
+
.action((agent) => run(agent, (id) => ({ method: "POST", path: agentPath(id, "stop") })));
|
|
76
|
+
skills.command("list")
|
|
77
|
+
.description("List the agent's skills (core, flavor, private, and market).")
|
|
78
|
+
.argument("<agent>", "agent id or exact name")
|
|
79
|
+
.action((agent) => run(agent, (id) => ({ method: "GET", path: agentPath(id, "skills") })));
|
|
80
|
+
skills.command("files")
|
|
81
|
+
.description("List the files attached to one skill.")
|
|
82
|
+
.argument("<agent>", "agent id or exact name")
|
|
83
|
+
.argument("<category>", "skill category")
|
|
84
|
+
.argument("<skill>", "skill name")
|
|
85
|
+
.action((agent, category, skill) => run(agent, (id) => ({
|
|
86
|
+
method: "GET",
|
|
87
|
+
path: agentPath(id, `skills/${encodeURIComponent(category)}/${encodeURIComponent(skill)}/files`),
|
|
88
|
+
})));
|
|
89
|
+
skills.command("read")
|
|
90
|
+
.description("Read one skill file.")
|
|
91
|
+
.argument("<agent>", "agent id or exact name")
|
|
92
|
+
.argument("<path>", "path relative to the skill root, e.g. research/web-research/SKILL.md")
|
|
93
|
+
.action((agent, path) => {
|
|
94
|
+
const file = skillPath(path);
|
|
95
|
+
return run(agent, (id) => ({ method: "GET", path: agentPath(id, `skill-files/${file}`) }));
|
|
96
|
+
});
|
|
97
|
+
skills.command("enabled")
|
|
98
|
+
.description("Enable or disable one skill.")
|
|
99
|
+
.argument("<agent>", "agent id or exact name")
|
|
100
|
+
.argument("<name>", "skill name")
|
|
101
|
+
.argument("<true|false>", "new state")
|
|
102
|
+
.action((agent, name, enabled) => {
|
|
103
|
+
const body = { enabled: bool(enabled) };
|
|
104
|
+
return run(agent, (id) => ({ method: "PUT", path: agentPath(id, `skills/${encodeURIComponent(name)}/state`), body }));
|
|
105
|
+
});
|
|
106
|
+
skills.command("installed")
|
|
107
|
+
.description("List the market skills installed on the agent.")
|
|
108
|
+
.argument("<agent>", "agent id or exact name")
|
|
109
|
+
.action((agent) => run(agent, (id) => ({ method: "GET", path: agentPath(id, "market-skills") })));
|
|
110
|
+
skills.command("install")
|
|
111
|
+
.description("Install or update a market skill at an exact version.")
|
|
112
|
+
.argument("<agent>", "agent id or exact name")
|
|
113
|
+
.argument("<ID@VERSION>", "market skill and version, e.g. web-research@1.0.0")
|
|
114
|
+
.action((agent, ref) => {
|
|
115
|
+
const body = parseSkillRef(ref);
|
|
116
|
+
return run(agent, (id) => ({ method: "POST", path: agentPath(id, "market-skills"), body }));
|
|
117
|
+
});
|
|
118
|
+
skills.command("uninstall")
|
|
119
|
+
.description("Remove a market skill.")
|
|
120
|
+
.argument("<agent>", "agent id or exact name")
|
|
121
|
+
.argument("<id>", "market skill id")
|
|
122
|
+
.action((agent, id) => {
|
|
123
|
+
const skill = encodeURIComponent(skillId(id));
|
|
124
|
+
return run(agent, (agentId) => ({ method: "DELETE", path: agentPath(agentId, `market-skills/${skill}`) }));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=agent-skills.js.map
|