@paneui/cli 0.0.5 → 0.0.7

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.
@@ -1,17 +1,21 @@
1
- // `pane keys` — inspect or revoke the calling agent's API key.
1
+ // `pane key` — inspect or revoke the calling agent's API key.
2
2
  //
3
- // Flat command namespace: `keys` is one top-level command that branches on a
4
- // positional subcommand (list / revoke). The relay scopes /v1/keys to the
3
+ // Flat command namespace: `key` is one top-level noun that branches on a
4
+ // positional verb (list / revoke). The relay scopes /v1/keys to the
5
5
  // authenticated agent, so there is exactly one key — the caller's own. Both
6
- // subcommands therefore act ONLY on the caller's own key.
6
+ // verbs therefore act ONLY on the caller's own key.
7
+ import { assertKnownFlags } from "../argv.js";
7
8
  import { makeClient } from "../config.js";
8
9
  import { printJson, fail, failFromError } from "../output.js";
9
- export const keysHelp = `pane keys — inspect or revoke YOUR agent's API key
10
+ const NO_FLAGS = [];
11
+ const NO_BOOLS = [];
12
+ const REVOKE_BOOLS = ["yes"];
13
+ export const keyHelp = `pane key — inspect or revoke YOUR agent's API key
10
14
 
11
15
  Usage:
12
- pane keys <subcommand> [options]
16
+ pane key <verb> [options]
13
17
 
14
- Subcommands:
18
+ Verbs:
15
19
  list Show YOUR agent's key info. The relay scopes keys to the
16
20
  authenticated agent — there is exactly one key per agent, your
17
21
  own. Prints { agent_id, name, key_prefix, created_at,
@@ -19,18 +23,19 @@ Subcommands:
19
23
 
20
24
  revoke Revoke YOUR OWN API key — a self-destruct. The key stops working
21
25
  IMMEDIATELY; every subsequent command fails until you run
22
- 'pane register' again to provision a new key. The relay only
26
+ 'pane agent register' again to provision a new key. The relay only
23
27
  allows revoking your own key. Requires --yes to confirm.
24
28
  Prints { revoked: true, agent_id }.
25
29
 
26
30
  Options:
27
- --yes Confirm 'keys revoke' (required — it is irreversible).
31
+ --yes Confirm 'key revoke' (required — it is irreversible).
28
32
  --url <url> Relay base URL (overrides PANE_URL).
29
33
  --api-key <key> Agent API key (overrides PANE_API_KEY).
30
34
  -h, --help Show this help.
31
35
 
32
36
  Output: stdout is machine-readable JSON.`;
33
- async function runKeysList(args) {
37
+ async function runKeyList(args) {
38
+ assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane key list");
34
39
  const client = makeClient(args);
35
40
  try {
36
41
  const info = await client.listKeys();
@@ -40,9 +45,10 @@ async function runKeysList(args) {
40
45
  failFromError(e);
41
46
  }
42
47
  }
43
- async function runKeysRevoke(args) {
48
+ async function runKeyRevoke(args) {
49
+ assertKnownFlags(args, NO_FLAGS, REVOKE_BOOLS, "pane key revoke");
44
50
  if (!args.bools.has("yes")) {
45
- fail("'pane keys revoke' revokes YOUR OWN API key — it stops working " +
51
+ fail("'pane key revoke' revokes YOUR OWN API key — it stops working " +
46
52
  "immediately and is irreversible. Pass --yes to confirm.", "confirmation_required");
47
53
  }
48
54
  const client = makeClient(args);
@@ -58,19 +64,19 @@ async function runKeysRevoke(args) {
58
64
  failFromError(e);
59
65
  }
60
66
  }
61
- export async function runKeys(args) {
67
+ export async function runKey(args) {
62
68
  const sub = args.positionals[0];
63
69
  switch (sub) {
64
70
  case "list":
65
- await runKeysList(args);
71
+ await runKeyList(args);
66
72
  break;
67
73
  case "revoke":
68
- await runKeysRevoke(args);
74
+ await runKeyRevoke(args);
69
75
  break;
70
76
  case undefined:
71
- fail("missing subcommand — usage: pane keys <list|revoke> (run 'pane keys --help')", "invalid_args");
77
+ fail("missing verb — usage: pane key <list|revoke> (run 'pane key --help')", "invalid_args");
72
78
  break;
73
79
  default:
74
- fail(`unknown keys subcommand '${sub}' — expected list|revoke (run 'pane keys --help')`, "invalid_args");
80
+ fail(`unknown key verb '${sub}' — expected list|revoke (run 'pane key --help')`, "invalid_args");
75
81
  }
76
82
  }
@@ -0,0 +1,91 @@
1
+ // `pane surface list` — enumerate YOUR agent's surfaces.
2
+ //
3
+ // The recovery primitive when the create-response was dropped: the URL itself
4
+ // is unrecoverable (the relay stores only the token hash), but every other
5
+ // field of the surface is intact and listable here. Pair with
6
+ // `pane surface participant new` to mint a fresh URL on a surface whose
7
+ // original was lost.
8
+ import { assertKnownFlags } from "../argv.js";
9
+ import { makeClient } from "../config.js";
10
+ import { printJson, fail, failFromError } from "../output.js";
11
+ const KNOWN_FLAGS = ["status", "limit", "cursor", "template-id"];
12
+ const KNOWN_BOOLS = [];
13
+ export const listHelp = `pane surface list — list YOUR agent's surfaces
14
+
15
+ Prints surfaces (newest first) with no secrets in the response. Participant
16
+ tokens are stored hashed and CANNOT be recovered — if you lost a surface URL,
17
+ mint a fresh one with 'pane surface participant new <surface-id>'.
18
+
19
+ Usage:
20
+ pane surface list [options]
21
+
22
+ Options:
23
+ --status <s> open | closed | all. Default: open. The status reported
24
+ is the EFFECTIVE status — a row whose ttl is in the past
25
+ is reported as 'closed' even if not yet swept.
26
+ --limit <N> Page size (default 50, max 200).
27
+ --cursor <c> Opaque cursor from a previous page's next_cursor.
28
+ --template-id <i> Filter to surfaces instantiated from a specific named
29
+ template (head id; not version id). Inline (anonymous)
30
+ templates cannot be filtered this way — they have no
31
+ stable handle.
32
+ --url <url> Relay base URL (overrides PANE_URL).
33
+ --api-key <key> Agent API key (overrides PANE_API_KEY).
34
+ -h, --help Show this help.
35
+
36
+ Recovery recipe (lost the URL but the surface is still alive):
37
+ pane surface list # find the
38
+ # surface_id +
39
+ # participant_id
40
+ # you lost
41
+ pane surface participant new <surface-id> # mint a fresh URL
42
+ pane surface participant revoke <surface-id> <p-id> # invalidate the
43
+ # old URL
44
+
45
+ Output (stdout, JSON):
46
+ {
47
+ items: [
48
+ {
49
+ surface_id, title, status, template_id, template_version_id,
50
+ template_version, participants: [...], created_at, expires_at,
51
+ has_callback
52
+ },
53
+ ...
54
+ ],
55
+ next_cursor: <opaque|null>
56
+ }`;
57
+ const STATUSES = ["open", "closed", "all"];
58
+ export async function runList(args) {
59
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane surface list");
60
+ const opts = {};
61
+ const status = args.flags.get("status");
62
+ if (status !== undefined) {
63
+ if (!STATUSES.includes(status)) {
64
+ fail(`--status must be one of: ${STATUSES.join(", ")} (got '${status}')`, "invalid_args");
65
+ }
66
+ opts.status = status;
67
+ }
68
+ const limitRaw = args.flags.get("limit");
69
+ if (limitRaw !== undefined) {
70
+ const n = Number(limitRaw);
71
+ if (!Number.isInteger(n) || n <= 0 || n > 200) {
72
+ fail(`--limit must be an integer in 1..200 (got '${limitRaw}')`, "invalid_args");
73
+ }
74
+ opts.limit = n;
75
+ }
76
+ const cursor = args.flags.get("cursor");
77
+ if (cursor !== undefined && cursor !== "")
78
+ opts.cursor = cursor;
79
+ const templateId = args.flags.get("template-id");
80
+ if (templateId !== undefined && templateId !== "") {
81
+ opts.template_id = templateId;
82
+ }
83
+ const client = makeClient(args);
84
+ try {
85
+ const page = await client.listSessions(opts);
86
+ printJson(page);
87
+ }
88
+ catch (e) {
89
+ failFromError(e);
90
+ }
91
+ }
@@ -1,25 +1,29 @@
1
- // `pane logout` — clear the locally-saved relay URL + API key.
1
+ // `pane agent logout` — clear the locally-saved relay URL + API key.
2
+ import { assertKnownFlags } from "../argv.js";
2
3
  import { clearStore } from "../store.js";
3
4
  import { printJson } from "../output.js";
4
- export const logoutHelp = `pane logout — clear the saved relay URL + API key
5
+ const NO_FLAGS = [];
6
+ const NO_BOOLS = [];
7
+ export const logoutHelp = `pane agent logout — clear the saved relay URL + API key
5
8
 
6
9
  Usage:
7
- pane logout [options]
10
+ pane agent logout [options]
8
11
 
9
12
  Deletes the CLI config file (\${XDG_CONFIG_HOME:-~/.config}/pane/config.json),
10
- which holds the relay URL and the agent API key saved by 'pane register'.
13
+ which holds the relay URL and the agent API key saved by 'pane agent register'.
11
14
  Idempotent — no error if there is nothing to clear.
12
15
 
13
16
  This only clears the LOCAL config. It does NOT revoke the key on the relay —
14
17
  the key keeps working until it is revoked. To revoke it on the relay, use
15
- 'pane keys revoke'.
18
+ 'pane key revoke'.
16
19
 
17
20
  Options:
18
21
  -h, --help Show this help.
19
22
 
20
23
  Output (stdout, JSON):
21
24
  { cleared: true, path }`;
22
- export async function runLogout() {
25
+ export async function runLogout(args) {
26
+ assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane agent logout");
23
27
  const path = clearStore();
24
28
  printJson({ cleared: true, path });
25
29
  }
@@ -0,0 +1,137 @@
1
+ // `pane surface participant <new|revoke>` — mint or invalidate one
2
+ // participant URL on an existing surface. Recovery + leak-containment
3
+ // primitives that together replace the destructive `pane surface delete +
4
+ // pane surface create` workaround for the lost-URL case.
5
+ //
6
+ // This file is a sub-noun dispatcher under `pane surface`. The surface
7
+ // dispatcher hands us a ParsedArgs whose positionals[0] is "participant"
8
+ // (our sub-noun marker), so we read the verb from positionals[1] and the
9
+ // args from positionals[2..]. This mirrors the way every other sub-verb
10
+ // runner (runState, runDelete, ...) reads positionals[0] as its first arg
11
+ // after a single shift.
12
+ import { assertKnownFlags } from "../argv.js";
13
+ import { makeClient } from "../config.js";
14
+ import { printJson, fail, failFromError } from "../output.js";
15
+ const NO_FLAGS = [];
16
+ const NO_BOOLS = [];
17
+ export const participantHelp = `pane surface participant — manage one surface's participant URLs
18
+
19
+ Participant tokens are stored hashed on the relay and CANNOT be recovered.
20
+ If you lost the create-response (and the URL with it), use 'new' to mint a
21
+ fresh URL — the surface keeps its event log, template pin, and created_at.
22
+ Use 'revoke' to invalidate a single URL while keeping the surface alive.
23
+
24
+ Usage:
25
+ pane surface participant <verb> <args>
26
+
27
+ Verbs:
28
+ list <surface-id> List the participants on one surface, including
29
+ revoked rows (for audit). Returns
30
+ { surface_id, items: [...] } where each item
31
+ carries { participant_id, kind, token_prefix,
32
+ joined_at, revoked_at }. Use this to find the
33
+ participant_id you need to pass to 'revoke'.
34
+
35
+ new <surface-id> Mint a fresh human URL on an existing surface.
36
+ Returns { participant_id, kind, token, url,
37
+ created_at } — ONCE. The plaintext token is
38
+ never recoverable; save the response (pipe to
39
+ a JSONL log) before delivering the URL.
40
+
41
+ revoke <surface-id> <participant-id>
42
+ Invalidate one participant URL. The surface's
43
+ other participants (and the agent's own
44
+ websocket) are untouched. Idempotent: running
45
+ revoke twice still returns success.
46
+ Note: existing WebSocket connections held
47
+ under the revoked token are NOT actively
48
+ kicked in v1; new HTTP and WS connections
49
+ under that token will fail with 404.
50
+
51
+ Options:
52
+ --url <url> Relay base URL (overrides PANE_URL).
53
+ --api-key <key> Agent API key (overrides PANE_API_KEY).
54
+ -h, --help Show this help.
55
+
56
+ Recovery recipe:
57
+ pane surface list # find surface_id
58
+ pane surface participant list <surface-id> # find participant
59
+ # ids on that
60
+ # surface
61
+ pane surface participant new <surface-id> # mint a new URL
62
+ pane surface participant revoke <surface-id> <p-id> # invalidate the
63
+ # old URL
64
+
65
+ Output: stdout is machine-readable JSON.`;
66
+ async function runParticipantList(args) {
67
+ assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane surface participant list");
68
+ const surfaceId = args.positionals[1];
69
+ if (!surfaceId) {
70
+ fail("missing <surface-id> — usage: pane surface participant list <surface-id>", "invalid_args");
71
+ }
72
+ const client = makeClient(args);
73
+ try {
74
+ const res = await client.listParticipants(surfaceId);
75
+ printJson(res);
76
+ }
77
+ catch (e) {
78
+ failFromError(e);
79
+ }
80
+ }
81
+ async function runParticipantNew(args) {
82
+ assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane surface participant new");
83
+ const surfaceId = args.positionals[1];
84
+ if (!surfaceId) {
85
+ fail("missing <surface-id> — usage: pane surface participant new <surface-id>", "invalid_args");
86
+ }
87
+ const client = makeClient(args);
88
+ try {
89
+ const res = await client.mintParticipant(surfaceId);
90
+ printJson(res);
91
+ }
92
+ catch (e) {
93
+ failFromError(e);
94
+ }
95
+ }
96
+ async function runParticipantRevoke(args) {
97
+ assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane surface participant revoke");
98
+ const surfaceId = args.positionals[1];
99
+ const participantId = args.positionals[2];
100
+ if (!surfaceId || !participantId) {
101
+ fail("missing arguments — usage: pane surface participant revoke <surface-id> <participant-id>", "invalid_args");
102
+ }
103
+ const client = makeClient(args);
104
+ try {
105
+ await client.revokeParticipant(surfaceId, participantId);
106
+ printJson({
107
+ surface_id: surfaceId,
108
+ participant_id: participantId,
109
+ revoked: true,
110
+ });
111
+ }
112
+ catch (e) {
113
+ failFromError(e);
114
+ }
115
+ }
116
+ export async function runParticipant(args) {
117
+ // positionals[0] is the verb (list | new | revoke), positionals[1..] are
118
+ // the verb's args. (The surface.ts dispatcher already shifted off the
119
+ // "participant" marker before calling us.)
120
+ const verb = args.positionals[0];
121
+ switch (verb) {
122
+ case "list":
123
+ await runParticipantList(args);
124
+ break;
125
+ case "new":
126
+ await runParticipantNew(args);
127
+ break;
128
+ case "revoke":
129
+ await runParticipantRevoke(args);
130
+ break;
131
+ case undefined:
132
+ fail("missing verb — usage: pane surface participant <list|new|revoke> (run 'pane surface participant --help')", "invalid_args");
133
+ break;
134
+ default:
135
+ fail(`unknown participant verb '${verb}' — expected list|new|revoke (run 'pane surface participant --help')`, "invalid_args");
136
+ }
137
+ }
@@ -1,4 +1,4 @@
1
- // `pane register` — self-provision an agent API key from the relay.
1
+ // `pane agent register` — self-provision an agent API key from the relay.
2
2
  //
3
3
  // This is the one command that needs no API key: it is the call that obtains
4
4
  // one. If the relay runs REGISTRATION_MODE=secret, pass the shared
@@ -6,14 +6,17 @@
6
6
  // (and relay URL) are persisted to the CLI config file, so every later command
7
7
  // works with only PANE_URL (or nothing) set.
8
8
  import { registerAgent, PaneApiError } from "@paneui/core";
9
+ import { assertKnownFlags } from "../argv.js";
9
10
  import { DEFAULT_RELAY_URL } from "../config.js";
10
11
  import { printJson, fail, failUpgradeRequired } from "../output.js";
11
12
  import { readStore, writeStore } from "../store.js";
12
13
  import { VERSION } from "../version.js";
13
- export const registerHelp = `pane register — register this agent with the relay and save the key locally
14
+ const KNOWN_FLAGS = ["name", "secret"];
15
+ const KNOWN_BOOLS = ["print-key"];
16
+ export const registerHelp = `pane agent register — register this agent with the relay and save the key locally
14
17
 
15
18
  Usage:
16
- pane register [options]
19
+ pane agent register [options]
17
20
 
18
21
  Calls POST /v1/register, then saves the returned API key (and relay URL) to the
19
22
  CLI config file — so afterwards every other command works with only PANE_URL
@@ -36,6 +39,7 @@ Output (stdout, JSON):
36
39
  The API key is saved to the CLI config file (mode 0600); it is not printed
37
40
  unless --print-key is passed.`;
38
41
  export async function runRegister(args) {
42
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane agent register");
39
43
  const store = readStore();
40
44
  const url = args.flags.get("url") ??
41
45
  process.env.PANE_URL ??
@@ -56,7 +60,7 @@ export async function runRegister(args) {
56
60
  if (e instanceof PaneApiError) {
57
61
  // 426 cli_upgrade_required goes through the shared upgrade-message
58
62
  // path (stderr block + exit 75) so the SKILL.md's instructions to the
59
- // agent's harness fire on `pane register` too.
63
+ // agent's harness fire on `pane agent register` too.
60
64
  if (e.status === 426 && e.code === "cli_upgrade_required") {
61
65
  failUpgradeRequired(e);
62
66
  }
@@ -1,21 +1,39 @@
1
- // `pane send <id>` — append an agent event to a session.
1
+ // `pane surface send <id>` — append an agent event to a surface.
2
+ import { readFileSync } from "node:fs";
3
+ import { basename } from "node:path";
4
+ import { assertKnownFlags } from "../argv.js";
2
5
  import { makeClient } from "../config.js";
3
6
  import { resolveJson } from "../input.js";
4
7
  import { printJson, fail, failFromError } from "../output.js";
5
- export const sendHelp = `pane send — emit an agent event into a session
8
+ const KNOWN_FLAGS = [
9
+ "type",
10
+ "data",
11
+ "attachment",
12
+ "causation-id",
13
+ "idempotency-key",
14
+ ];
15
+ const KNOWN_BOOLS = [];
16
+ export const sendHelp = `pane surface send — emit an agent event into a surface
6
17
 
7
18
  Usage:
8
- pane send <session-id> --type <event-type> --data <path|json> [options]
19
+ pane surface send <surface-id> --type <event-type> --data <path|json> [options]
20
+ pane surface send <surface-id> --type <event-type> --attachment <file-path> [options]
9
21
 
10
- POSTs an event to /v1/sessions/:id/events. The event is stamped as authored by
22
+ POSTs an event to /v1/surfaces/:id/events. The event is stamped as authored by
11
23
  the agent (the relay derives identity from the API key — it cannot be spoofed).
12
24
 
13
25
  Required:
14
- --type <t> Event type. Must exist in the session's event schema
26
+ --type <t> Event type. Must exist in the surface's event schema
15
27
  with the agent in its emittedBy list.
16
28
  --data <v> Event payload: a file path to a .json file, or inline
17
29
  JSON. Use --data 'null' or --data '{}' for no payload.
18
30
 
31
+ ALTERNATIVE to --data:
32
+ --attachment <path> One-shot: upload <path> as a surface-scope attachment, then
33
+ send an event whose payload is the AttachmentRef. The event
34
+ data is { attachment: <AttachmentRef> }; declare it in your event
35
+ schema with \`format: pane-attachment-id\` on \`attachment.attachment_id\`.
36
+
19
37
  Options:
20
38
  --causation-id <id> Opaque causation id stored verbatim on the event.
21
39
  --idempotency-key <k> Dedup key — a repeat send with the same key is a no-op.
@@ -26,15 +44,52 @@ Options:
26
44
  Output (stdout, JSON):
27
45
  { event, deduped }`;
28
46
  export async function runSend(args) {
29
- const sessionId = args.positionals[0];
30
- if (!sessionId)
31
- fail("missing <session-id>", "invalid_args");
47
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane surface send");
48
+ const surfaceId = args.positionals[0];
49
+ if (!surfaceId)
50
+ fail("missing <surface-id>", "invalid_args");
32
51
  const type = args.flags.get("type");
33
52
  if (!type)
34
53
  fail("missing --type", "invalid_args");
35
54
  const dataRaw = args.flags.get("data");
36
- if (dataRaw === undefined)
37
- fail("missing --data", "invalid_args");
55
+ const blobPath = args.flags.get("attachment");
56
+ if (dataRaw !== undefined && blobPath !== undefined) {
57
+ fail("--data and --attachment are mutually exclusive", "invalid_args");
58
+ }
59
+ if (dataRaw === undefined && blobPath === undefined) {
60
+ fail("missing --data or --attachment", "invalid_args");
61
+ }
62
+ const client = makeClient(args);
63
+ // --attachment path: upload the file as a surface-scope attachment, then send an
64
+ // event whose data is { attachment: <AttachmentRef> }. The surface's event schema
65
+ // is expected to declare a attachment field with format: pane-attachment-id.
66
+ if (blobPath !== undefined) {
67
+ let bytes;
68
+ try {
69
+ bytes = readFileSync(blobPath);
70
+ }
71
+ catch (e) {
72
+ fail(`failed to read --attachment '${blobPath}': ${e instanceof Error ? e.message : String(e)}`, "invalid_args");
73
+ }
74
+ try {
75
+ const ref = await client.uploadBlob(bytes, {
76
+ scope: "surface",
77
+ surfaceId: surfaceId,
78
+ filename: basename(blobPath),
79
+ });
80
+ const res = await client.sendEvent(surfaceId, {
81
+ type: type,
82
+ data: { attachment: ref },
83
+ causationId: args.flags.get("causation-id"),
84
+ idempotencyKey: args.flags.get("idempotency-key"),
85
+ });
86
+ printJson(res);
87
+ }
88
+ catch (e) {
89
+ failFromError(e);
90
+ }
91
+ return;
92
+ }
38
93
  let data;
39
94
  try {
40
95
  data = resolveJson(dataRaw, "--data");
@@ -42,9 +97,8 @@ export async function runSend(args) {
42
97
  catch (e) {
43
98
  fail(e instanceof Error ? e.message : String(e), "invalid_args");
44
99
  }
45
- const client = makeClient(args);
46
100
  try {
47
- const res = await client.sendEvent(sessionId, {
101
+ const res = await client.sendEvent(surfaceId, {
48
102
  type: type,
49
103
  data,
50
104
  causationId: args.flags.get("causation-id"),
@@ -7,37 +7,41 @@
7
7
  // agent always reads what the relay it's actually talking to wants it
8
8
  // to read.
9
9
  //
10
- // Two subcommands:
11
- // `pane skill` — print the full markdown to stdout (the
10
+ // Two verbs:
11
+ // `pane skill show` — print the full markdown to stdout (the
12
12
  // install / refresh path; pipe to a file).
13
13
  // `pane skill version` — print just the relay's skill version (the
14
14
  // "is my local copy stale?" probe). The agent
15
15
  // compares this to the `<!-- pane skill v… -->`
16
16
  // comment in its local skill file and re-runs
17
- // `pane skill > <path>` when they differ.
17
+ // `pane skill show > <path>` when they differ.
18
18
  //
19
19
  // Both are unauthenticated — the skill route is public on the relay and
20
20
  // an agent on a too-old CLI must be able to read the upgrade instructions
21
21
  // even before it has registered (or before its key was minted).
22
+ import { assertKnownFlags } from "../argv.js";
22
23
  import { resolveRelayUrl } from "../config.js";
23
24
  import { fail } from "../output.js";
25
+ const NO_FLAGS = [];
26
+ const NO_BOOLS = [];
27
+ const VERSION_BOOLS = ["plain"];
24
28
  import { VERSION } from "../version.js";
25
29
  export const skillHelp = `pane skill — fetch the relay's SKILL.md (or its version)
26
30
 
27
31
  Usage:
28
- pane skill Print the full skill to stdout.
32
+ pane skill show Print the full skill to stdout.
29
33
  pane skill version [--plain] Print just the relay's skill version.
30
34
 
31
35
  The skill is auto-updating: the relay's deployed image owns the version,
32
36
  so this is always the skill that matches the relay you are talking to.
33
37
 
34
38
  Unauthenticated — no API key needed. An agent can call either form
35
- before 'pane register' to bootstrap or refresh its local skill copy.
39
+ before 'pane agent register' to bootstrap or refresh its local skill copy.
36
40
 
37
- Subcommands:
38
- (bare) Fetch GET /skills/pane/SKILL.md and write the raw
41
+ Verbs:
42
+ show Fetch GET /skills/pane/SKILL.md and write the raw
39
43
  markdown to stdout. Pipe to your local skill path:
40
- pane skill > ~/.claude/skills/pane/SKILL.md
44
+ pane skill show > ~/.claude/skills/pane/SKILL.md
41
45
  version Fetch GET /skills/pane/SKILL.md/version and print
42
46
  the relay's skill version. Default output is the
43
47
  JSON envelope; --plain prints just the version
@@ -78,8 +82,9 @@ async function failOnNon2xx(res, target) {
78
82
  const body = await res.text().catch(() => "");
79
83
  fail(`relay returned ${res.status} for ${target}${body ? ": " + body.slice(0, 200) : ""}`, "relay_error");
80
84
  }
81
- // `pane skill` (no positional) — print the full skill.
85
+ // `pane skill show` — print the full skill.
82
86
  async function runSkillFetch(args) {
87
+ assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane skill show");
83
88
  const url = resolveRelayUrl(args);
84
89
  const target = url + "/skills/pane/SKILL.md";
85
90
  const res = await fetchOrFail(target);
@@ -94,6 +99,7 @@ async function runSkillFetch(args) {
94
99
  }
95
100
  // `pane skill version [--plain]` — print just the version.
96
101
  async function runSkillVersion(args) {
102
+ assertKnownFlags(args, NO_FLAGS, VERSION_BOOLS, "pane skill version");
97
103
  const url = resolveRelayUrl(args);
98
104
  const target = url + "/skills/pane/SKILL.md/version";
99
105
  const res = await fetchOrFail(target);
@@ -124,13 +130,16 @@ async function runSkillVersion(args) {
124
130
  export async function runSkill(args) {
125
131
  const sub = args.positionals[0];
126
132
  switch (sub) {
127
- case undefined:
133
+ case "show":
128
134
  await runSkillFetch(args);
129
135
  break;
130
136
  case "version":
131
137
  await runSkillVersion(args);
132
138
  break;
139
+ case undefined:
140
+ fail("missing verb — usage: pane skill <show|version> (run 'pane skill --help')", "invalid_args");
141
+ break;
133
142
  default:
134
- fail(`unknown skill subcommand '${sub}' — expected 'version' or no subcommand (run 'pane skill --help')`, "invalid_args");
143
+ fail(`unknown skill verb '${sub}' — expected show|version (run 'pane skill --help')`, "invalid_args");
135
144
  }
136
145
  }
@@ -1,20 +1,23 @@
1
- // `pane state <id>` — snapshot of a session, optionally long-polled.
1
+ // `pane surface show <id>` — snapshot of a surface, optionally long-polled.
2
+ import { assertKnownFlags } from "../argv.js";
2
3
  import { makeClient } from "../config.js";
3
4
  import { printJson, fail, failFromError } from "../output.js";
4
- export const stateHelp = `pane state — show a session's metadata and event log
5
+ const KNOWN_FLAGS = ["since", "wait"];
6
+ const KNOWN_BOOLS = [];
7
+ export const stateHelp = `pane surface show — show a surface's metadata and event log
5
8
 
6
9
  Usage:
7
- pane state <session-id> [options]
10
+ pane surface show <surface-id> [options]
8
11
 
9
- By default non-blocking: fetches session metadata (GET /v1/sessions/:id) plus
10
- the event log (GET /v1/sessions/:id/events) and prints them together.
12
+ By default non-blocking: fetches surface metadata (GET /v1/surfaces/:id) plus
13
+ the event log (GET /v1/surfaces/:id/events) and prints them together.
11
14
 
12
15
  With --wait, blocks at the relay for up to <secs> if no new events are
13
16
  available since the cursor — returns as soon as something lands. Use this
14
17
  for headless polling agents that can't keep a WebSocket open (cron,
15
18
  FaaS, slow links): poll, then re-poll using next_cursor as --since on the
16
- next call. Compared to 'pane watch', it's higher latency per round-trip
17
- but no long-lived connection.
19
+ next call. Compared to 'pane surface watch', it's higher latency per
20
+ round-trip but no long-lived connection.
18
21
 
19
22
  Options:
20
23
  --since <cursor> Only return events after this opaque cursor. Pass
@@ -31,9 +34,10 @@ Options:
31
34
  Output (stdout, JSON):
32
35
  { meta, events, next_cursor }`;
33
36
  export async function runState(args) {
34
- const sessionId = args.positionals[0];
35
- if (!sessionId)
36
- fail("missing <session-id>", "invalid_args");
37
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane surface show");
38
+ const surfaceId = args.positionals[0];
39
+ if (!surfaceId)
40
+ fail("missing <surface-id>", "invalid_args");
37
41
  const since = args.flags.get("since") ?? null;
38
42
  // --wait <secs>: hand the server the long-poll window. The relay caps
39
43
  // this at 30s; we pass the raw value and let the relay clamp (sending
@@ -50,8 +54,8 @@ export async function runState(args) {
50
54
  }
51
55
  const client = makeClient(args);
52
56
  try {
53
- const meta = await client.getSession(sessionId);
54
- const page = await client.getEvents(sessionId, {
57
+ const meta = await client.getSession(surfaceId);
58
+ const page = await client.getEvents(surfaceId, {
55
59
  since,
56
60
  ...(waitSeconds !== undefined ? { waitSeconds } : {}),
57
61
  });