@paneui/cli 0.0.9 → 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.
- package/README.md +8 -8
- package/dist/argv.js +3 -3
- package/dist/commands/agent.js +10 -2
- package/dist/commands/attachment-token.js +2 -2
- package/dist/commands/attachment-upload.js +8 -10
- package/dist/commands/attachment.js +7 -7
- package/dist/commands/claim.js +1 -1
- package/dist/commands/config.js +232 -20
- package/dist/commands/create.js +132 -21
- package/dist/commands/delete.js +12 -12
- package/dist/commands/feedback.js +5 -5
- package/dist/commands/list.js +17 -17
- package/dist/commands/logout.js +43 -13
- package/dist/commands/participant.js +38 -38
- package/dist/commands/query.js +204 -0
- package/dist/commands/records.js +285 -0
- package/dist/commands/register.js +53 -15
- package/dist/commands/send.js +17 -17
- package/dist/commands/set-key.js +92 -0
- package/dist/commands/skill.js +1 -1
- package/dist/commands/state.js +12 -12
- package/dist/commands/taste.js +3 -3
- package/dist/commands/template-records.js +195 -0
- package/dist/commands/template.js +243 -35
- package/dist/commands/trash.js +102 -0
- package/dist/commands/watch.js +22 -22
- package/dist/config.js +87 -20
- package/dist/format.js +133 -0
- package/dist/index.js +97 -20
- package/dist/output.js +1 -1
- package/dist/store.js +167 -26
- package/dist/upgrade.js +1 -1
- package/dist/version.js +2 -2
- package/package.json +5 -3
- package/dist/commands/surface.js +0 -118
|
@@ -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
|
|
8
|
-
// once and instancing it via `pane
|
|
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 {
|
|
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
|
|
39
|
-
once, then instance it many times with 'pane
|
|
40
|
-
instead of regenerating the HTML on every
|
|
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
|
|
47
|
-
version
|
|
48
|
-
update
|
|
49
|
-
search
|
|
50
|
-
list
|
|
51
|
-
show
|
|
52
|
-
delete
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
85
|
-
of the template's versions — run 'pane
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
564
|
+
await runTemplateCreate(args);
|
|
369
565
|
break;
|
|
370
566
|
case "version":
|
|
371
|
-
await
|
|
567
|
+
await runTemplateVersion(args);
|
|
372
568
|
break;
|
|
373
569
|
case "update":
|
|
374
|
-
await
|
|
570
|
+
await runTemplateUpdate(args);
|
|
375
571
|
break;
|
|
376
572
|
case "search":
|
|
377
|
-
await
|
|
573
|
+
await runTemplateSearch(args, args.positionals[1]);
|
|
378
574
|
break;
|
|
379
575
|
case "list":
|
|
380
|
-
await
|
|
576
|
+
await runTemplateSearch(args, undefined);
|
|
381
577
|
break;
|
|
382
578
|
case "show":
|
|
383
|
-
await
|
|
579
|
+
await runTemplateShow(args);
|
|
384
580
|
break;
|
|
385
581
|
case "delete":
|
|
386
|
-
await
|
|
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
|
+
}
|
package/dist/commands/watch.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// `pane
|
|
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
|
|
14
|
+
export const watchHelp = `pane watch — stream a pane's events as JSON-lines
|
|
15
15
|
|
|
16
16
|
Usage:
|
|
17
|
-
pane
|
|
17
|
+
pane watch <pane-id> [options]
|
|
18
18
|
|
|
19
|
-
Holds a WebSocket to WS /v1/
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
95
|
-
const
|
|
96
|
-
if (!
|
|
97
|
-
fail("missing <
|
|
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
|
|
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,
|
|
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
|
-
|
|
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.
|
|
171
|
-
if (event.type === "system.
|
|
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.
|
|
188
|
-
// closed us on purpose after a clean
|
|
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;
|