@paneui/cli 0.0.8 → 0.0.10

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.
@@ -4,10 +4,12 @@
4
4
  // a positional subcommand (create / version / update / search / list / show /
5
5
  // delete).
6
6
  // An template is a reusable UI template (HTML + event schema + optional input
7
- // schema); a surface is one *use* of one version of it. Authoring an template
8
- // once and instancing it via `pane surface create --template-id` removes the
7
+ // schema); a pane is one *use* of one version of it. Authoring an template
8
+ // once and instancing it via `pane create --template-id` removes the
9
9
  // per-use cost of regenerating the same HTML.
10
- import { createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, } from "@paneui/core";
10
+ import { readFileSync } from "node:fs";
11
+ import { basename } from "node:path";
12
+ import { createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, validateIconEmoji, } from "@paneui/core";
11
13
  import { assertKnownFlags } from "../argv.js";
12
14
  import { makeClient } from "../config.js";
13
15
  import { resolveJson, resolveText } from "../input.js";
@@ -21,7 +23,10 @@ const CREATE_FLAGS = [
21
23
  "template-type",
22
24
  "event-schema",
23
25
  "input-schema",
26
+ "icon-emoji",
24
27
  ];
28
+ const SET_ICON_FLAGS = ["template-id", "emoji", "image"];
29
+ const SET_ICON_BOOLS = ["clear"];
25
30
  const VERSION_FLAGS = [
26
31
  "template",
27
32
  "template-type",
@@ -32,32 +37,47 @@ const UPDATE_FLAGS = ["name", "slug", "description", "tags"];
32
37
  const NO_FLAGS = [];
33
38
  const NO_BOOLS = [];
34
39
  const DELETE_BOOLS = ["yes"];
40
+ const PUBLISH_FLAGS = ["scopes"];
41
+ const SEARCH_PUBLIC_FLAGS = ["limit", "offset"];
35
42
  export const artifactHelp = `pane template — manage reusable, versioned templates
36
43
 
37
44
  An template is a reusable UI template: HTML + an event schema + an optional
38
- input schema. A surface is one use of one version of it. Author an template
39
- once, then instance it many times with 'pane surface create --template-id <id|slug>'
40
- instead of regenerating the HTML on every surface.
45
+ input schema. A pane is one use of one version of it. Author an template
46
+ once, then instance it many times with 'pane create --template-id <id|slug>'
47
+ instead of regenerating the HTML on every pane.
41
48
 
42
49
  Usage:
43
50
  pane template <subcommand> [options]
44
51
 
45
52
  Subcommands:
46
- create Create a named, reusable template (its v1).
47
- version Append a new version to an existing template.
48
- update Update an template's head metadata (name/slug/description/tags).
49
- search Search the agent's named templates (lean — no HTML).
50
- list List the agent's named templates (search with no query).
51
- show Show a full template: head metadata + its version list.
52
- delete Permanently delete an template and ALL its versions. Requires
53
- --yes. Refused with 409 conflict if any surface (open or
54
- closed) still references the template — delete those first.
53
+ create Create a named, reusable template (its v1).
54
+ version Append a new version to an existing template.
55
+ update Update an template's head metadata (name/slug/description/tags).
56
+ search Search the agent's named templates (lean — no HTML).
57
+ list List the agent's named templates (search with no query).
58
+ show Show a full template: head metadata + its version list.
59
+ delete Permanently delete an template and ALL its versions. Requires
60
+ --yes. Refused with 409 conflict if any pane (open or
61
+ closed) still references the template — delete those first.
62
+ publish Publish a template to the public catalog (so other humans
63
+ can install it). Optionally lock --scopes at publish time.
64
+ unpublish Remove a template from the public catalog. Existing
65
+ installs are unaffected; the template just stops appearing
66
+ in searches.
67
+ search-public Search the PUBLIC catalog of all published templates from
68
+ every agent. Use before creating a new template, to find
69
+ an existing one you can install/recommend instead.
70
+ set-icon Set (or clear) a template's icon — a single emoji, or an
71
+ uploaded raster image (png/jpeg/webp/gif). No SVG, no URLs.
55
72
 
56
73
  pane template create --name <n> --template <path|inline>
57
74
  [--event-schema <path|json>] [--slug <s>]
58
75
  [--description <d>] [--tags <t1,t2>]
59
76
  [--input-schema <path|json>] [--template-type <t>]
77
+ [--icon-emoji <emoji>]
60
78
  Creates a named template. Prints { template_id, slug, version }.
79
+ --icon-emoji sets a single-emoji icon at create time; use 'set-icon'
80
+ with --image to attach an uploaded image icon afterwards.
61
81
 
62
82
  pane template version <id|slug> --template <path|inline>
63
83
  [--event-schema <path|json>]
@@ -81,11 +101,41 @@ Subcommands:
81
101
 
82
102
  pane template delete <id|slug> --yes
83
103
  Permanently deletes the template and all its versions. Refused
84
- (409 conflict) if any surface in any state still references one
85
- of the template's versions — run 'pane surface delete <surface-id>' on
104
+ (409 conflict) if any pane in any state still references one
105
+ of the template's versions — run 'pane delete <pane-id>' on
86
106
  each first, or wait for the relay's TTL sweeper to reclaim them.
87
107
  Prints { template, deleted: true } on success.
88
108
 
109
+ pane template publish <id|slug> [--scopes <s1,s2,...>]
110
+ Publishes the template to the public catalog. Other humans can then
111
+ install it from their /apps page. --scopes is a comma-separated
112
+ 'verb:noun' list (e.g. 'read:agent,write:pane') that locks the
113
+ permissions this version requests; reissue with new --scopes to
114
+ reset them. Prints the head metadata + published_at + scopes.
115
+
116
+ pane template unpublish <id|slug>
117
+ Removes the template from the public catalog. Existing installs
118
+ keep working — they're pinned to their version. Prints the head
119
+ metadata with published_at: null.
120
+
121
+ pane template search-public [query] [--limit <n>] [--offset <n>]
122
+ Searches the PUBLIC catalog across every agent's published
123
+ templates. Substring match on name, description, and tags. Ranked
124
+ by install_count desc, then publish recency. Useful BEFORE
125
+ authoring: 'pane template search-public pr-review' may find an
126
+ existing one you can install instead of building from scratch.
127
+ Prints { items, total, offset, limit }.
128
+
129
+ pane template set-icon --template-id <id> (--emoji <e> | --image <path>)
130
+ pane template set-icon --template-id <id> --clear
131
+ Sets the template's icon. Pass exactly one of:
132
+ --emoji <e> a single emoji grapheme (rendered inline as text)
133
+ --image <path> a local raster image (png/jpeg/webp/gif). Uploaded as
134
+ a template-scoped attachment, then linked as the icon.
135
+ --clear removes both the emoji and the image icon.
136
+ Image icons are served same-origin from the relay; external URLs and
137
+ SVG are never accepted. Prints the updated lean template summary.
138
+
89
139
  Options:
90
140
  --name <n> Template display name (required for 'create').
91
141
  --slug <s> Stable, agent-chosen handle (unique per agent). The
@@ -117,8 +167,9 @@ Options:
117
167
  emittedBy is any non-empty subset of ["page", "agent"].
118
168
  payload is a JSON Schema; the relay validates every
119
169
  emit against it. See docs/SPEC.md for the full grammar.
120
- --input-schema <v> JSON Schema for this template's per-surface input_data —
170
+ --input-schema <v> JSON Schema for this template's per-pane input_data —
121
171
  a file path, or inline JSON. Optional.
172
+ --icon-emoji <e> Single-emoji icon for the template (create only).
122
173
  --template-type <t> "html-inline" (default) or "html-ref".
123
174
  --url <url> Relay base URL (overrides PANE_URL).
124
175
  --api-key <key> Agent API key (overrides PANE_API_KEY).
@@ -189,7 +240,7 @@ function resolveTags(args) {
189
240
  .filter((t) => t !== "");
190
241
  return tags.length > 0 ? tags : undefined;
191
242
  }
192
- async function runArtifactCreate(args) {
243
+ async function runTemplateCreate(args) {
193
244
  assertKnownFlags(args, CREATE_FLAGS, NO_BOOLS, "pane template create");
194
245
  const name = args.flags.get("name");
195
246
  if (!name)
@@ -218,6 +269,9 @@ async function runArtifactCreate(args) {
218
269
  candidate["tags"] = tags;
219
270
  if (inputSchema !== undefined)
220
271
  candidate["input_schema"] = inputSchema;
272
+ const iconEmoji = args.flags.get("icon-emoji");
273
+ if (iconEmoji !== undefined)
274
+ candidate["icon_emoji"] = iconEmoji;
221
275
  const parsed = createArtifactSchema.safeParse(candidate);
222
276
  if (!parsed.success) {
223
277
  const issue = parsed.error.issues[0];
@@ -238,7 +292,7 @@ async function runArtifactCreate(args) {
238
292
  failFromError(e);
239
293
  }
240
294
  }
241
- async function runArtifactVersion(args) {
295
+ async function runTemplateVersion(args) {
242
296
  assertKnownFlags(args, VERSION_FLAGS, NO_BOOLS, "pane template version");
243
297
  const idOrSlug = args.positionals[1];
244
298
  if (!idOrSlug) {
@@ -274,7 +328,7 @@ async function runArtifactVersion(args) {
274
328
  failFromError(e);
275
329
  }
276
330
  }
277
- async function runArtifactUpdate(args) {
331
+ async function runTemplateUpdate(args) {
278
332
  assertKnownFlags(args, UPDATE_FLAGS, NO_BOOLS, "pane template update");
279
333
  const idOrSlug = args.positionals[1];
280
334
  if (!idOrSlug) {
@@ -312,7 +366,7 @@ async function runArtifactUpdate(args) {
312
366
  failFromError(e);
313
367
  }
314
368
  }
315
- async function runArtifactSearch(args, query) {
369
+ async function runTemplateSearch(args, query) {
316
370
  assertKnownFlags(args, NO_FLAGS, NO_BOOLS, query === undefined ? "pane template list" : "pane template search");
317
371
  const client = makeClient(args);
318
372
  try {
@@ -323,7 +377,7 @@ async function runArtifactSearch(args, query) {
323
377
  failFromError(e);
324
378
  }
325
379
  }
326
- async function runArtifactShow(args) {
380
+ async function runTemplateShow(args) {
327
381
  assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane template show");
328
382
  const idOrSlug = args.positionals[1];
329
383
  if (!idOrSlug) {
@@ -340,10 +394,10 @@ async function runArtifactShow(args) {
340
394
  }
341
395
  // `pane template delete <id|slug> --yes` — remove an template (and, server-
342
396
  // side, all its versions). The relay refuses with 409 conflict if any
343
- // surface still references it; the CLI surfaces that as the relay-supplied
397
+ // pane still references it; the CLI panes that as the relay-supplied
344
398
  // envelope. `--yes` is required because there's no Undo button on a delete
345
399
  // and the same `pane template create` slug isn't reservable once gone.
346
- async function runArtifactDelete(args) {
400
+ async function runTemplateDelete(args) {
347
401
  assertKnownFlags(args, NO_FLAGS, DELETE_BOOLS, "pane template delete");
348
402
  const idOrSlug = args.positionals[1];
349
403
  if (!idOrSlug) {
@@ -361,34 +415,188 @@ async function runArtifactDelete(args) {
361
415
  failFromError(e);
362
416
  }
363
417
  }
364
- export async function runArtifact(args) {
418
+ async function runTemplatePublish(args) {
419
+ assertKnownFlags(args, PUBLISH_FLAGS, NO_BOOLS, "pane template publish");
420
+ const idOrSlug = args.positionals[1];
421
+ if (!idOrSlug) {
422
+ fail("missing template <id|slug> — usage: pane template publish <id|slug> [--scopes <s1,s2,...>]", "invalid_args");
423
+ }
424
+ // --scopes is a comma-separated list of verb:noun strings. Omit to leave
425
+ // the existing scopes alone (server semantics). Pass an empty string to
426
+ // explicitly clear them (we send []).
427
+ const rawScopes = args.flags.get("scopes");
428
+ const body = {};
429
+ if (rawScopes !== undefined) {
430
+ body.scopes = rawScopes
431
+ .split(",")
432
+ .map((s) => s.trim())
433
+ .filter((s) => s.length > 0);
434
+ }
435
+ const client = makeClient(args);
436
+ try {
437
+ const res = await client.publishTemplate(idOrSlug, body);
438
+ printJson(res);
439
+ }
440
+ catch (e) {
441
+ failFromError(e);
442
+ }
443
+ }
444
+ async function runTemplateUnpublish(args) {
445
+ assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane template unpublish");
446
+ const idOrSlug = args.positionals[1];
447
+ if (!idOrSlug) {
448
+ fail("missing template <id|slug> — usage: pane template unpublish <id|slug>", "invalid_args");
449
+ }
450
+ const client = makeClient(args);
451
+ try {
452
+ const res = await client.unpublishTemplate(idOrSlug);
453
+ printJson(res);
454
+ }
455
+ catch (e) {
456
+ failFromError(e);
457
+ }
458
+ }
459
+ async function runTemplateSearchPublic(args) {
460
+ assertKnownFlags(args, SEARCH_PUBLIC_FLAGS, NO_BOOLS, "pane template search-public");
461
+ const query = args.positionals[1];
462
+ const opts = {};
463
+ const limitRaw = args.flags.get("limit");
464
+ if (limitRaw !== undefined) {
465
+ const n = Number(limitRaw);
466
+ if (!Number.isInteger(n) || n < 1 || n > 50) {
467
+ fail("--limit must be an integer between 1 and 50", "invalid_args");
468
+ }
469
+ opts.limit = n;
470
+ }
471
+ const offsetRaw = args.flags.get("offset");
472
+ if (offsetRaw !== undefined) {
473
+ const n = Number(offsetRaw);
474
+ if (!Number.isInteger(n) || n < 0) {
475
+ fail("--offset must be a non-negative integer", "invalid_args");
476
+ }
477
+ opts.offset = n;
478
+ }
479
+ const client = makeClient(args);
480
+ try {
481
+ const res = await client.searchPublicTemplates(query, opts);
482
+ printJson(res);
483
+ }
484
+ catch (e) {
485
+ failFromError(e);
486
+ }
487
+ }
488
+ // `pane template set-icon --template-id <id> (--emoji <e> | --image <path> |
489
+ // --clear)` — set or clear a template's icon. --emoji PATCHes icon_emoji
490
+ // directly; --image uploads the file as a template-scoped attachment then
491
+ // PATCHes icon_attachment_id; --clear nulls both.
492
+ async function runTemplateSetIcon(args) {
493
+ assertKnownFlags(args, SET_ICON_FLAGS, SET_ICON_BOOLS, "pane template set-icon");
494
+ const templateId = args.flags.get("template-id");
495
+ if (!templateId) {
496
+ fail("missing --template-id <id> — usage: pane template set-icon --template-id <id> (--emoji <e> | --image <path> | --clear)", "invalid_args");
497
+ }
498
+ const emoji = args.flags.get("emoji");
499
+ const imagePath = args.flags.get("image");
500
+ const clear = args.bools.has("clear");
501
+ // Exactly one of --emoji / --image / --clear.
502
+ const chosen = [emoji !== undefined, imagePath !== undefined, clear].filter(Boolean).length;
503
+ if (chosen !== 1) {
504
+ fail("pass exactly one of --emoji <e>, --image <path>, or --clear", "invalid_args");
505
+ }
506
+ const client = makeClient(args);
507
+ if (clear) {
508
+ try {
509
+ const res = await client.updateArtifact(templateId, {
510
+ icon_emoji: null,
511
+ icon_attachment_id: null,
512
+ });
513
+ printJson(res);
514
+ }
515
+ catch (e) {
516
+ failFromError(e);
517
+ }
518
+ return;
519
+ }
520
+ if (emoji !== undefined) {
521
+ // Validate client-side for a clear error before the round trip; the relay
522
+ // re-validates regardless.
523
+ const v = validateIconEmoji(emoji);
524
+ if (!v.ok)
525
+ fail(`invalid --emoji: ${v.error}`, "invalid_args");
526
+ try {
527
+ const res = await client.updateArtifact(templateId, {
528
+ icon_emoji: emoji,
529
+ });
530
+ printJson(res);
531
+ }
532
+ catch (e) {
533
+ failFromError(e);
534
+ }
535
+ return;
536
+ }
537
+ // --image: upload the file as a template-scoped attachment, then link it.
538
+ let bytes;
539
+ try {
540
+ bytes = readFileSync(imagePath);
541
+ }
542
+ catch (e) {
543
+ fail(`failed to read --image '${imagePath}': ${e instanceof Error ? e.message : String(e)}`, "invalid_args");
544
+ }
545
+ try {
546
+ const ref = await client.uploadBlob(bytes, {
547
+ scope: "template",
548
+ templateId: templateId,
549
+ filename: basename(imagePath),
550
+ });
551
+ const res = await client.updateArtifact(templateId, {
552
+ icon_attachment_id: ref.attachment_id,
553
+ });
554
+ printJson(res);
555
+ }
556
+ catch (e) {
557
+ failFromError(e);
558
+ }
559
+ }
560
+ export async function runTemplate(args) {
365
561
  const sub = args.positionals[0];
366
562
  switch (sub) {
367
563
  case "create":
368
- await runArtifactCreate(args);
564
+ await runTemplateCreate(args);
369
565
  break;
370
566
  case "version":
371
- await runArtifactVersion(args);
567
+ await runTemplateVersion(args);
372
568
  break;
373
569
  case "update":
374
- await runArtifactUpdate(args);
570
+ await runTemplateUpdate(args);
375
571
  break;
376
572
  case "search":
377
- await runArtifactSearch(args, args.positionals[1]);
573
+ await runTemplateSearch(args, args.positionals[1]);
378
574
  break;
379
575
  case "list":
380
- await runArtifactSearch(args, undefined);
576
+ await runTemplateSearch(args, undefined);
381
577
  break;
382
578
  case "show":
383
- await runArtifactShow(args);
579
+ await runTemplateShow(args);
384
580
  break;
385
581
  case "delete":
386
- await runArtifactDelete(args);
582
+ await runTemplateDelete(args);
583
+ break;
584
+ case "publish":
585
+ await runTemplatePublish(args);
586
+ break;
587
+ case "unpublish":
588
+ await runTemplateUnpublish(args);
589
+ break;
590
+ case "search-public":
591
+ await runTemplateSearchPublic(args);
592
+ break;
593
+ case "set-icon":
594
+ await runTemplateSetIcon(args);
387
595
  break;
388
596
  case undefined:
389
- fail("missing subcommand — usage: pane template <create|version|update|search|list|show|delete> (run 'pane template --help')", "invalid_args");
597
+ fail("missing subcommand — usage: pane template <create|version|update|search|list|show|delete|publish|unpublish|search-public|set-icon> (run 'pane template --help')", "invalid_args");
390
598
  break;
391
599
  default:
392
- fail(`unknown template subcommand '${sub}' — expected create|version|update|search|list|show|delete (run 'pane template --help')`, "invalid_args");
600
+ fail(`unknown template subcommand '${sub}' — expected create|version|update|search|list|show|delete|publish|unpublish|search-public|set-icon (run 'pane template --help')`, "invalid_args");
393
601
  }
394
602
  }
@@ -0,0 +1,102 @@
1
+ // `pane trash <verb>` — manage the soft-delete trash (#306 / CLI follow-up).
2
+ //
3
+ // pane trash list show soft-deleted panes + templates
4
+ // pane trash restore <pane-id> restore a trashed pane
5
+ // pane trash restore-template <template-id> restore a trashed template
6
+ // pane trash purge <pane-id> permanent hard-delete now
7
+ // pane trash purge-template <template-id> permanent hard-delete now
8
+ //
9
+ // Why "purge" not "delete": `pane delete <id>` already exists and means
10
+ // "close the live pane" (which, with #303, sends it to trash via the TTL
11
+ // sweeper after it expires). Using "delete" again on a trashed row would
12
+ // be confusing — "purge" reads unambiguously as "the row is gone for good".
13
+ import { assertKnownFlags } from "../argv.js";
14
+ import { makeClient } from "../config.js";
15
+ import { printJson, fail, failFromError } from "../output.js";
16
+ const KNOWN_FLAGS = [];
17
+ const KNOWN_BOOLS = [];
18
+ export const trashHelp = `pane trash — manage the soft-delete trash
19
+
20
+ Usage:
21
+ pane trash list Show soft-deleted panes + templates
22
+ pane trash restore <pane-id> Restore a trashed pane
23
+ pane trash restore-template <id-or-slug> Restore a trashed template
24
+ pane trash purge <pane-id> Permanent hard-delete now (skips
25
+ the retention window)
26
+ pane trash purge-template <id-or-slug> Permanent hard-delete now
27
+
28
+ Soft-deleted rows live in trash until the hard-delete sweeper reclaims them
29
+ (default retention: 30 days free / never paid). 'restore' takes them back
30
+ out of trash; 'purge' deletes them immediately, bypassing the window.
31
+
32
+ Options:
33
+ --url <url> Relay base URL (overrides PANE_URL).
34
+ --api-key <key> Agent API key (overrides PANE_API_KEY).
35
+ -h, --help Show this help.
36
+
37
+ Output (stdout, JSON):
38
+ list: { panes: [...], templates: [...] }
39
+ restore / purge: { pane_id|template_id, restored|purged: true }`;
40
+ export async function runTrash(args) {
41
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane trash");
42
+ const verb = args.positionals[0];
43
+ if (verb === undefined || verb === "-h" || verb === "--help") {
44
+ process.stdout.write(trashHelp + "\n");
45
+ return;
46
+ }
47
+ const id = args.positionals[1];
48
+ // Validate positional arguments BEFORE building the client so the
49
+ // invalid_args envelope is emitted without a second wrap-around via
50
+ // failFromError (which would clobber it to `internal`).
51
+ switch (verb) {
52
+ case "list":
53
+ case "restore":
54
+ case "restore-template":
55
+ case "purge":
56
+ case "purge-template":
57
+ break;
58
+ default:
59
+ fail(`unknown trash verb '${verb}' — run 'pane trash --help'`, "invalid_args");
60
+ }
61
+ if (verb === "restore" || verb === "purge") {
62
+ if (!id)
63
+ fail("missing <pane-id>", "invalid_args");
64
+ }
65
+ else if (verb === "restore-template" || verb === "purge-template") {
66
+ if (!id)
67
+ fail("missing <template-id-or-slug>", "invalid_args");
68
+ }
69
+ const client = makeClient(args);
70
+ try {
71
+ switch (verb) {
72
+ case "list": {
73
+ const body = await client.listTrash();
74
+ printJson(body);
75
+ return;
76
+ }
77
+ case "restore": {
78
+ await client.restorePane(id);
79
+ printJson({ pane_id: id, restored: true });
80
+ return;
81
+ }
82
+ case "restore-template": {
83
+ await client.restoreTemplate(id);
84
+ printJson({ template_id: id, restored: true });
85
+ return;
86
+ }
87
+ case "purge": {
88
+ await client.permanentDeletePane(id);
89
+ printJson({ pane_id: id, purged: true });
90
+ return;
91
+ }
92
+ case "purge-template": {
93
+ await client.permanentDeleteTemplate(id);
94
+ printJson({ template_id: id, purged: true });
95
+ return;
96
+ }
97
+ }
98
+ }
99
+ catch (e) {
100
+ failFromError(e);
101
+ }
102
+ }
@@ -1,4 +1,4 @@
1
- // `pane surface watch <id>` — long-lived: hold a WebSocket and stream events as
1
+ // `pane watch <id>` — long-lived: hold a WebSocket and stream events as
2
2
  // JSON-lines on stdout. This harness-agnostic stdout is the core contract:
3
3
  // one compact JSON object per line, flushed after every event, so any
4
4
  // pipe-reader (Claude Code's Monitor tool, `while read line`, jq -c, ...)
@@ -11,14 +11,14 @@ import { printJsonLine, fail } from "../output.js";
11
11
  import { VERSION } from "../version.js";
12
12
  const KNOWN_FLAGS = ["since", "type", "filter-type", "timeout"];
13
13
  const KNOWN_BOOLS = ["once"];
14
- export const watchHelp = `pane surface watch — stream a surface's events as JSON-lines
14
+ export const watchHelp = `pane watch — stream a pane's events as JSON-lines
15
15
 
16
16
  Usage:
17
- pane surface watch <surface-id> [options]
17
+ pane watch <pane-id> [options]
18
18
 
19
- Holds a WebSocket to WS /v1/surfaces/:id/stream. Prints ONE compact JSON
19
+ Holds a WebSocket to WS /v1/panes/:id/stream. Prints ONE compact JSON
20
20
  object per line to stdout, flushing after each — designed to be piped into a
21
- line-reader. On surface close, prints a final {"type":"_closed"} line and
21
+ line-reader. On pane close, prints a final {"type":"_closed"} line and
22
22
  exits 0.
23
23
 
24
24
  Modes:
@@ -34,7 +34,7 @@ Options:
34
34
  --filter-type <t[,t2,…]>
35
35
  Print only events whose type is in this set.
36
36
  system.* events (lifecycle: participant.joined,
37
- surface.expired, …) and the terminal {"type":
37
+ pane.expired, …) and the terminal {"type":
38
38
  "_closed"} line always pass through, so the
39
39
  harness still sees them. Combine with --type X
40
40
  --filter-type X for "stream only X events and
@@ -42,7 +42,7 @@ Options:
42
42
  --type alone that agents often expect.
43
43
  --since <cursor> Replay only events after this opaque cursor.
44
44
  --timeout <secs> Wall-clock max wait. Fail with code ws_timeout if
45
- the natural exit condition (--once, --type, surface
45
+ the natural exit condition (--once, --type, pane
46
46
  close) doesn't happen within this many seconds.
47
47
  Frames arriving DO NOT reset the timer — this is
48
48
  the budget for "give up on the human", not an idle
@@ -52,17 +52,17 @@ Options:
52
52
  --api-key <key> Agent API key (overrides PANE_API_KEY).
53
53
  -h, --help Show this help.
54
54
 
55
- Each line is one event envelope: { id, surface_id, author, ts, type, data,
55
+ Each line is one event envelope: { id, pane_id, author, ts, type, data,
56
56
  causation_id, idempotency_key }. The terminal line is {"type":"_closed"}.
57
57
 
58
- Pattern — Claude Code Monitor tool: run \`pane surface watch <id> --type form.submitted\`
58
+ Pattern — Claude Code Monitor tool: run \`pane watch <id> --type form.submitted\`
59
59
  as a monitored process; the harness re-invokes the model when the line lands.
60
60
 
61
61
  Wait for any of several events:
62
- pane surface watch <id> --type form.submitted,form.cancelled --timeout 60
62
+ pane watch <id> --type form.submitted,form.cancelled --timeout 60
63
63
 
64
64
  Stream only matching events to stdout, exit on the first:
65
- pane surface watch <id> --type form.submitted --filter-type form.submitted`;
65
+ pane watch <id> --type form.submitted --filter-type form.submitted`;
66
66
  // Parse a comma-separated event-type list (e.g. "form.submitted,form.cancelled")
67
67
  // into a Set. Empty/whitespace entries are dropped. Returns null when the flag
68
68
  // wasn't given (so callers can distinguish "no filter" from "empty filter").
@@ -91,10 +91,10 @@ export function shouldPrintEvent(eventType, filterTypes) {
91
91
  return filterTypes.has(eventType);
92
92
  }
93
93
  export async function runWatch(args) {
94
- assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane surface watch");
95
- const surfaceId = args.positionals[0];
96
- if (!surfaceId)
97
- fail("missing <surface-id>", "invalid_args");
94
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane watch");
95
+ const paneId = args.positionals[0];
96
+ if (!paneId)
97
+ fail("missing <pane-id>", "invalid_args");
98
98
  const cfg = resolveConfig(args);
99
99
  const since = args.flags.get("since") ?? null;
100
100
  // --type controls the EXIT condition (set of types that trigger exit 0
@@ -135,7 +135,7 @@ export async function runWatch(args) {
135
135
  }
136
136
  finish(0);
137
137
  };
138
- // Track whether the relay told us the surface expired before the socket
138
+ // Track whether the relay told us the pane expired before the socket
139
139
  // closed — a 1006/1008/1011 close after that is still a clean shutdown.
140
140
  let sawSessionExpired = false;
141
141
  // Wall-clock timeout. The reporter's mental model (#137) and the skill
@@ -145,7 +145,7 @@ export async function runWatch(args) {
145
145
  // useless once any frame arrived, even a system.participant.joined
146
146
  // emitted the moment a human connected. Frames now DO NOT reset the
147
147
  // timer; the only ways `--timeout` doesn't fire are the natural exit
148
- // conditions (--once, --type match, surface close) finishing first.
148
+ // conditions (--once, --type match, pane close) finishing first.
149
149
  let timer;
150
150
  if (timeoutSec !== null) {
151
151
  timer = setTimeout(() => {
@@ -154,7 +154,7 @@ export async function runWatch(args) {
154
154
  }
155
155
  const handle = openStream({
156
156
  wsBaseUrl: client.wsBaseUrl,
157
- surfaceId: surfaceId,
157
+ paneId: paneId,
158
158
  token: cfg.apiKey,
159
159
  since,
160
160
  }, {
@@ -167,8 +167,8 @@ export async function runWatch(args) {
167
167
  if (shouldPrintEvent(event.type, filterTypes)) {
168
168
  printJsonLine(event);
169
169
  }
170
- // A system.surface.expired event means the surface is closing.
171
- if (event.type === "system.surface.expired") {
170
+ // A system.pane.expired event means the pane is closing.
171
+ if (event.type === "system.pane.expired") {
172
172
  sawSessionExpired = true;
173
173
  emitClosed();
174
174
  return;
@@ -184,8 +184,8 @@ export async function runWatch(args) {
184
184
  onClose: ({ code, reason }) => {
185
185
  // A clean close is 1000 (normal) or 1001 (going away). Any other code
186
186
  // — 1006 abnormal, 1008 policy/auth, 1011 server error — is a failure
187
- // UNLESS we already saw system.surface.expired, which means the relay
188
- // closed us on purpose after a clean surface end.
187
+ // UNLESS we already saw system.pane.expired, which means the relay
188
+ // closed us on purpose after a clean pane end.
189
189
  if (code === 1000 || code === 1001 || sawSessionExpired) {
190
190
  emitClosed();
191
191
  return;