@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.
@@ -0,0 +1,135 @@
1
+ // `pane attachment` — manage binary attachments (attachments) on the relay.
2
+ //
3
+ // A attachment is a typed binary file (image, PDF, audio, video, etc.) owned by an
4
+ // agent and optionally bound to a surface or template. Pages reference attachments
5
+ // by id with `format: pane-attachment-id`; participants can fetch a attachment through a
6
+ // minted capability URL (/b/<token>) without needing the agent's API key.
7
+ //
8
+ // This file is a thin dispatcher — each verb's actual logic lives in its own
9
+ // file (attachment-upload.ts, attachment-download.ts, attachment-show.ts, attachment-delete.ts) and
10
+ // the token sub-noun is dispatched via attachment-token.ts.
11
+ //
12
+ // Most attachment verbs read their primary positional (the attachment_id) at
13
+ // positionals[0]; we slice off our own verb before delegating so each verb
14
+ // runner doesn't need to know it was reached through `pane attachment`.
15
+ import { runBlobUpload, blobUploadHelp } from "./attachment-upload.js";
16
+ import { runBlobDownload, blobDownloadHelp } from "./attachment-download.js";
17
+ import { runBlobShow, blobShowHelp } from "./attachment-show.js";
18
+ import { runBlobList, blobListHelp } from "./attachment-list.js";
19
+ import { runBlobDelete, blobDeleteHelp } from "./attachment-delete.js";
20
+ import { runBlobToken, blobTokenHelp } from "./attachment-token.js";
21
+ import { fail } from "../output.js";
22
+ export const blobHelp = `pane attachment — manage attachments (binary attachments) on the relay
23
+
24
+ A attachment is a typed binary file (image, PDF, audio, video, ...) the agent has
25
+ uploaded to the relay. Blobs are scoped:
26
+
27
+ agent — reusable across the agent's surfaces (default)
28
+ surface — bound to one surface; deleted with it
29
+ template — bound to a reusable template; deleted with it
30
+
31
+ Pages reference attachments by id (the relay's schema validates the id with
32
+ \`format: pane-attachment-id\`). For a participant-facing URL that bypasses the
33
+ agent's API key, mint a token with 'pane attachment token mint'.
34
+
35
+ Usage:
36
+ pane attachment <verb> [options]
37
+
38
+ Verbs:
39
+ upload Upload a local file. Required: --file. Optional:
40
+ --scope, --surface-id, --template-id, --filename,
41
+ --mime. Prints { attachment_id, scope, mime, size, sha256,
42
+ ... }.
43
+
44
+ download <attachment-id> Download a attachment by id. Use --out <path> to write a
45
+ file (default: writes to stdout — useful for piping).
46
+
47
+ show <attachment-id> Print a attachment's metadata (HEAD-based — doesn't
48
+ download the bytes).
49
+
50
+ list Enumerate YOUR agent's non-deleted attachments (newest
51
+ first). Supports --cursor + --limit for pagination.
52
+
53
+ delete <attachment-id> Soft-delete a attachment. Idempotent.
54
+
55
+ token <verb> Capability URLs for a attachment (mint | revoke | list).
56
+ 'mint' returns a /b/<token> URL anyone can GET, with
57
+ optional --ttl and --once. 'revoke' invalidates one
58
+ token. 'list' enumerates a attachment's tokens (without
59
+ the token plaintext, which is unrecoverable).
60
+
61
+ Run \`pane attachment <verb> --help\` for verb-specific options.
62
+
63
+ Output: stdout is machine-readable JSON. Errors go to stderr as
64
+ {"error":{"code","message"}} with a non-zero exit.`;
65
+ /**
66
+ * Build a new ParsedArgs with the leading positional (the verb) stripped.
67
+ * The downstream verb runners read their primary positional (the attachment_id)
68
+ * at positionals[0], so we hand them an args object that looks exactly like
69
+ * they were called directly — mirrors surface.ts's shiftPositionals.
70
+ */
71
+ function shiftPositionals(args) {
72
+ // Propagate danglingValueFlags so the leaf runner's assertKnownFlags
73
+ // can still distinguish "unknown flag" from "missing value" — see the
74
+ // matching note in surface.ts's shiftPositionals.
75
+ const out = {
76
+ positionals: args.positionals.slice(1),
77
+ flags: args.flags,
78
+ bools: args.bools,
79
+ };
80
+ if (args.danglingValueFlags !== undefined) {
81
+ out.danglingValueFlags = args.danglingValueFlags;
82
+ }
83
+ return out;
84
+ }
85
+ export async function runBlob(args) {
86
+ const verb = args.positionals[0];
87
+ // `pane attachment token --help` (verb-level help on the token sub-noun, with no
88
+ // further sub-verb). The general --help pre-empt in index.ts only fires
89
+ // when no positional follows the noun; here a positional ("token") is
90
+ // present, so the sub-noun must own its own --help routing.
91
+ if (verb === "token" &&
92
+ args.bools.has("help") &&
93
+ args.positionals.length === 1) {
94
+ process.stdout.write(blobTokenHelp + "\n");
95
+ return;
96
+ }
97
+ // `pane attachment list --help` — same pattern (list takes no required positional
98
+ // so the general pre-empt would already fire, but for parity with surface.ts
99
+ // we route through here when args carry the "list" positional explicitly).
100
+ if (verb === "list" &&
101
+ args.bools.has("help") &&
102
+ args.positionals.length === 1) {
103
+ process.stdout.write(blobListHelp + "\n");
104
+ return;
105
+ }
106
+ const inner = shiftPositionals(args);
107
+ switch (verb) {
108
+ case "upload":
109
+ await runBlobUpload(inner);
110
+ break;
111
+ case "download":
112
+ await runBlobDownload(inner);
113
+ break;
114
+ case "show":
115
+ await runBlobShow(inner);
116
+ break;
117
+ case "list":
118
+ await runBlobList(inner);
119
+ break;
120
+ case "delete":
121
+ await runBlobDelete(inner);
122
+ break;
123
+ case "token":
124
+ await runBlobToken(inner);
125
+ break;
126
+ case undefined:
127
+ fail("missing verb — usage: pane attachment <upload|download|show|list|delete|token> (run 'pane attachment --help')", "invalid_args");
128
+ break;
129
+ default:
130
+ fail(`unknown attachment verb '${verb}' — expected upload|download|show|list|delete|token (run 'pane attachment --help')`, "invalid_args");
131
+ }
132
+ }
133
+ // Re-export per-verb helps so tests / docs can import them by canonical name
134
+ // without knowing which file owns each verb.
135
+ export { blobUploadHelp, blobDownloadHelp, blobShowHelp, blobListHelp, blobDeleteHelp, blobTokenHelp, };
@@ -0,0 +1,68 @@
1
+ // `pane agent claim <code>` — bind this agent to a human via a one-shot
2
+ // claim code the human generated in their settings UI.
3
+ //
4
+ // Flow (§6.1):
5
+ // 1. Alice opens Settings → "Claim an agent" → relay mints a one-shot code,
6
+ // shows it to her once, 15-min TTL.
7
+ // 2. Alice hands the code to the agent out-of-band (this CLI invocation
8
+ // is exactly that handoff).
9
+ // 3. CLI calls POST /v1/agents/claim with the calling agent's API key.
10
+ // 4. Relay binds Agent.ownerHumanId = alice.id, migrates surface ownership.
11
+ //
12
+ // The CLI does NOT print the human's email or id — only the relay's response,
13
+ // which is { ok, owner_human_id, claimed_at }. The agent's existing API key
14
+ // keeps working.
15
+ import { PaneClient, PaneApiError } from "@paneui/core";
16
+ import { assertKnownFlags } from "../argv.js";
17
+ import { resolveConfig } from "../config.js";
18
+ import { printJson, fail } from "../output.js";
19
+ const KNOWN_FLAGS = ["url", "api-key"];
20
+ const KNOWN_BOOLS = [];
21
+ export const claimHelp = `pane agent claim — claim this agent for a human
22
+
23
+ Usage:
24
+ pane agent claim <code>
25
+
26
+ Binds the calling agent to the human whose one-shot claim code is provided.
27
+ The human generates the code in their settings UI (or via the relay's
28
+ POST /v1/self/claim-codes endpoint) and hands it to the agent out-of-band.
29
+
30
+ Arguments:
31
+ <code> The one-shot claim code (begins with cc_). Required.
32
+
33
+ Options:
34
+ --url <url> Relay base URL. Falls back to PANE_URL / config file.
35
+ --api-key <key> Agent API key. Falls back to PANE_API_KEY / config file.
36
+ -h, --help Show this help.
37
+
38
+ Output (stdout, JSON):
39
+ { ok: true, owner_human_id, claimed_at }
40
+
41
+ Errors:
42
+ invalid_code code is unknown, expired, or already consumed
43
+ agent_already_claimed this agent already has an owning human
44
+
45
+ Notes:
46
+ This is a one-way operation. To rotate the owner, revoke this agent
47
+ (\`pane key revoke\`) and register a new one.`;
48
+ export async function runClaim(args) {
49
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane agent claim");
50
+ const code = args.positionals[0];
51
+ if (!code) {
52
+ fail("missing required argument: <code> — run 'pane agent claim --help'", "invalid_args");
53
+ return;
54
+ }
55
+ const creds = resolveConfig(args);
56
+ const client = new PaneClient({ url: creds.url, apiKey: creds.apiKey });
57
+ try {
58
+ const result = await client.claimAgent(code);
59
+ printJson(result);
60
+ }
61
+ catch (err) {
62
+ if (err instanceof PaneApiError) {
63
+ fail(err.message, err.code);
64
+ return;
65
+ }
66
+ throw err;
67
+ }
68
+ }
@@ -1,14 +1,19 @@
1
- // `pane config` — show the resolved relay config without a network call.
1
+ // `pane config show` — show the resolved relay config without a network call.
2
+ import { assertKnownFlags } from "../argv.js";
2
3
  import { describeConfig } from "../config.js";
3
- import { printJson } from "../output.js";
4
+ import { printJson, fail } from "../output.js";
5
+ const NO_FLAGS = [];
6
+ const NO_BOOLS = [];
4
7
  export const configHelp = `pane config — show the resolved relay config
5
8
 
6
9
  Usage:
7
- pane config [options]
10
+ pane config show [options]
8
11
 
9
- Prints the relay URL and API-key info the CLI would use, and where each value
10
- came from. Makes NO network call purely inspects flags, env vars, and the
11
- saved config file.
12
+ Verbs:
13
+ show Print the relay URL and API-key info the CLI would use,
14
+ and where each value came from. Makes NO network call —
15
+ purely inspects flags, env vars, and the saved config
16
+ file.
12
17
 
13
18
  The API key is never printed in full: only a short masked prefix.
14
19
 
@@ -26,6 +31,20 @@ Output (stdout, JSON):
26
31
  key_source, "flag" | "env" | "store" | "none"
27
32
  config_path absolute path to the CLI config file
28
33
  }`;
29
- export async function runConfig(args) {
34
+ async function runConfigShow(args) {
35
+ assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane config show");
30
36
  printJson(describeConfig(args));
31
37
  }
38
+ export async function runConfig(args) {
39
+ const verb = args.positionals[0];
40
+ switch (verb) {
41
+ case "show":
42
+ await runConfigShow(args);
43
+ break;
44
+ case undefined:
45
+ fail("missing verb — usage: pane config show (run 'pane config --help')", "invalid_args");
46
+ break;
47
+ default:
48
+ fail(`unknown config verb '${verb}' — expected show (run 'pane config --help')`, "invalid_args");
49
+ }
50
+ }
@@ -1,8 +1,24 @@
1
- // `pane create` — create a session via POST /v1/sessions.
1
+ // `pane surface create` — create a surface via POST /v1/surfaces.
2
2
  import { createSessionSchema } from "@paneui/core";
3
+ import { assertKnownFlags } from "../argv.js";
3
4
  import { makeClient } from "../config.js";
4
5
  import { resolveJson, resolveText } from "../input.js";
5
6
  import { printJson, fail, failFromError } from "../output.js";
7
+ const KNOWN_FLAGS = [
8
+ "template",
9
+ "template-id",
10
+ "template-type",
11
+ "version",
12
+ "event-schema",
13
+ "input-schema",
14
+ "title",
15
+ "input-data",
16
+ "ttl",
17
+ "participants",
18
+ "metadata",
19
+ "callback",
20
+ ];
21
+ const KNOWN_BOOLS = [];
6
22
  // Translate a Zod schema path (e.g. ["participants","humans"]) back to the
7
23
  // public CLI flag the user actually typed. Without this, a `--participants 0`
8
24
  // rejection surfaces as `participants.humans: ...` — which leaks the wire
@@ -10,9 +26,9 @@ import { printJson, fail, failFromError } from "../output.js";
10
26
  //
11
27
  // Match strategy: longest prefix wins. Schema paths whose top segment isn't
12
28
  // in the table fall back to dotted notation so we degrade gracefully on
13
- // fields that don't have a single corresponding flag (e.g. `artifact.source`
14
- // — there's no single --artifact-source flag for the inline form, just
15
- // --artifact pointing at the whole blob).
29
+ // fields that don't have a single corresponding flag (e.g. `template.source`
30
+ // — there's no single --template-source flag for the inline form, just
31
+ // --template pointing at the whole attachment).
16
32
  const SCHEMA_PATH_TO_FLAG = {
17
33
  participants: "--participants",
18
34
  "participants.humans": "--participants",
@@ -20,11 +36,13 @@ const SCHEMA_PATH_TO_FLAG = {
20
36
  metadata: "--metadata",
21
37
  callback: "--callback",
22
38
  input_data: "--input-data",
23
- "artifact.id": "--artifact-id",
24
- "artifact.version": "--version",
25
- "artifact.type": "--artifact-type",
26
- "artifact.source": "--artifact",
27
- "artifact.event_schema": "--event-schema",
39
+ title: "--title",
40
+ "template.id": "--template-id",
41
+ "template.version": "--version",
42
+ "template.type": "--template-type",
43
+ "template.source": "--template",
44
+ "template.event_schema": "--event-schema",
45
+ "template.input_schema": "--input-schema",
28
46
  };
29
47
  function schemaPathToFlag(path) {
30
48
  const dotted = path.map(String).join(".");
@@ -39,32 +57,32 @@ function schemaPathToFlag(path) {
39
57
  }
40
58
  return dotted;
41
59
  }
42
- export const createHelp = `pane create — create a Pane session
60
+ export const createHelp = `pane surface create — create a Pane surface
43
61
 
44
- A session is one use of an artifact. Supply the artifact in ONE of two ways:
62
+ A surface is one use of an template. Supply the template in ONE of two ways:
45
63
 
46
- Reference form — instance an existing reusable artifact (the cheap path,
64
+ Reference form — instance an existing reusable template (the cheap path,
47
65
  no HTML re-sent):
48
- pane create --artifact-id <id|slug> [--version <n>] [--input-data <v>]
66
+ pane surface create --template-id <id|slug> [--version <n>] [--input-data <v>]
49
67
 
50
- Inline form — a one-off artifact, defined on this call:
51
- pane create --artifact <path|inline> [--event-schema <path|json>] [options]
68
+ Inline form — a one-off template, defined on this call:
69
+ pane surface create --template <path|inline> [--event-schema <path|json>] [options]
52
70
 
53
- Exactly one of --artifact-id / --artifact must be given.
71
+ Exactly one of --template-id / --template must be given.
54
72
 
55
- Artifact (choose one):
56
- --artifact-id <v> Reference an existing named artifact by id or slug.
57
- Tip: run 'pane artifact search <keywords>' first — a
58
- suitable artifact may already exist; reuse it instead of
73
+ Template (choose one):
74
+ --template-id <v> Reference an existing named template by id or slug.
75
+ Tip: run 'pane template search <keywords>' first — a
76
+ suitable template may already exist; reuse it instead of
59
77
  regenerating HTML.
60
- --version <n> With --artifact-id: pin a specific version. Defaults to
61
- the artifact's latest version.
62
- --artifact <v> Inline HTML artifact. Either a file path / URL, or inline
63
- HTML. Combine with --artifact-type to control reading.
78
+ --version <n> With --template-id: pin a specific version. Defaults to
79
+ the template's latest version.
80
+ --template <v> Inline HTML template. Either a file path / URL, or inline
81
+ HTML. Combine with --template-type to control reading.
64
82
  --event-schema <v> Inline-form event schema. A .json file, or inline JSON.
65
- Optional with --artifact. Omit for a view-only artifact
83
+ Optional with --template. Omit for a view-only template
66
84
  (a report/dashboard the human only views — no page/agent
67
- events). Ignored with --artifact-id.
85
+ events). Ignored with --template-id.
68
86
 
69
87
  Shape — an object with an "events" map, keyed by event
70
88
  type. Each entry declares who may emit it and the JSON
@@ -84,17 +102,31 @@ Artifact (choose one):
84
102
  emittedBy is any non-empty subset of ["page", "agent"].
85
103
  payload is a JSON Schema; the relay validates every
86
104
  emit against it. See docs/SPEC.md for the full grammar.
105
+ --input-schema <v> Inline-form input schema. A .json file, or inline JSON.
106
+ Optional with --template, rejected with --template-id
107
+ (the schema comes from the pinned template version
108
+ there). When present, the surface's --input-data is
109
+ validated against it AND any attachment ids declared at a
110
+ "format": "pane-attachment-id" site become reachable from the
111
+ page via window.pane.downloadBlob. Without it, attachment
112
+ refs in --input-data are silently unreachable. See
113
+ docs/SPEC.md and #208.
87
114
 
88
115
  Options:
116
+ --title <text> Tab title shown to the human (max 80 chars, single
117
+ line). Required, with one ergonomic exception: when
118
+ --template-id references a named template, the relay
119
+ falls back to Template.name. Inline (--template …) form
120
+ always needs --title.
89
121
  --input-data <v> This instance's seed data — a JSON object (file path or
90
- inline JSON), validated by the relay against the artifact
122
+ inline JSON), validated by the relay against the template
91
123
  version's input_schema. The page reads it as
92
124
  window.pane.inputData.
93
- --artifact-type <t> "html-inline" (default) or "html-ref". With "html-ref"
94
- the --artifact value is treated as a URL. Note: the relay
95
- does not serve "html-ref" artifacts in this release and
96
- will reject the session — use "html-inline".
97
- --ttl <seconds> Session time-to-live in seconds. The relay clamps this
125
+ --template-type <t> "html-inline" (default) or "html-ref". With "html-ref"
126
+ the --template value is treated as a URL. Note: the relay
127
+ does not serve "html-ref" templates in this release and
128
+ will reject the surface — use "html-inline".
129
+ --ttl <seconds> Surface time-to-live in seconds. The relay clamps this
98
130
  to its configured MAX_TTL_SECONDS (defaults: 1 h
99
131
  requested, 24 h max for self-host; hosted may differ).
100
132
  The returned \`expires_at\` is the authoritative value.
@@ -106,18 +138,19 @@ Options:
106
138
  -h, --help Show this help.
107
139
 
108
140
  Output (stdout, JSON):
109
- { session_id, urls, tokens, expires_at }
141
+ { surface_id, urls, tokens, expires_at }
110
142
 
111
143
  Deliver urls.humans to the human(s); keep tokens.agent for the WS stream.`;
112
144
  export async function runCreate(args) {
113
- const artifactIdVal = args.flags.get("artifact-id");
114
- const artifactVal = args.flags.get("artifact");
115
- // Exactly one of the two artifact forms must be present.
145
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane surface create");
146
+ const artifactIdVal = args.flags.get("template-id");
147
+ const artifactVal = args.flags.get("template");
148
+ // Exactly one of the two template forms must be present.
116
149
  if (artifactIdVal !== undefined && artifactVal !== undefined) {
117
- fail("pass only one of --artifact-id (reference an existing artifact) or --artifact (inline a one-off)", "invalid_args");
150
+ fail("pass only one of --template-id (reference an existing template) or --template (inline a one-off)", "invalid_args");
118
151
  }
119
152
  if (artifactIdVal === undefined && artifactVal === undefined) {
120
- fail("missing artifact — pass --artifact-id <id|slug> to reference an existing artifact, or --artifact <path|inline> to inline one", "invalid_args");
153
+ fail("missing template — pass --template-id <id|slug> to reference an existing template, or --template <path|inline> to inline one", "invalid_args");
121
154
  }
122
155
  // Assemble a candidate request object, then validate the whole thing with
123
156
  // the shared Zod schema (single source of truth, matches what the relay
@@ -125,8 +158,12 @@ export async function runCreate(args) {
125
158
  // flag-specific message; the schema then enforces shape and bounds.
126
159
  const candidate = {};
127
160
  if (artifactIdVal !== undefined) {
128
- // Reference form — instance an existing named artifact. --artifact /
129
- // --event-schema are not needed here.
161
+ // Reference form — instance an existing named template. --template /
162
+ // --event-schema / --input-schema are not used here: the template's
163
+ // pinned version carries them already.
164
+ if (args.flags.get("input-schema") !== undefined) {
165
+ fail("--input-schema is incompatible with --template-id — the input schema comes from the pinned template version. Author the schema on the template (`pane template create --input-schema …`) instead.", "invalid_args");
166
+ }
130
167
  const ref = { id: artifactIdVal };
131
168
  const versionRaw = args.flags.get("version");
132
169
  if (versionRaw !== undefined) {
@@ -136,32 +173,40 @@ export async function runCreate(args) {
136
173
  }
137
174
  ref["version"] = version;
138
175
  }
139
- candidate["artifact"] = ref;
176
+ candidate["template"] = ref;
140
177
  }
141
178
  else {
142
- // Inline form — the event schema rides inside the `artifact` object; the
143
- // relay transparently creates an anonymous artifact behind it.
144
- // --event-schema is optional: omitting it makes a view-only one-off (a
145
- // report/dashboard the human only views), and the relay then rejects every
146
- // page/agent emit.
179
+ // Inline form — the event + input schemas ride inside the `template`
180
+ // object; the relay transparently creates an anonymous template behind
181
+ // it. Both schemas are optional:
182
+ // - --event-schema absent view-only one-off (no page/agent emits)
183
+ // - --input-schema absent → no input contract; --input-data passes
184
+ // through unvalidated AND any attachment ids in it are unreachable from
185
+ // the page (the participant attachment-download bridge walks input_data
186
+ // against the template version's inputSchema). Pass --input-schema
187
+ // when --input-data carries attachment refs the page needs to render.
188
+ // See #208.
147
189
  const schemaVal = args.flags.get("event-schema");
148
- const artifactType = (args.flags.get("artifact-type") ?? "html-inline");
149
- if (artifactType !== "html-inline" && artifactType !== "html-ref") {
150
- fail("--artifact-type must be 'html-inline' or 'html-ref'", "invalid_args");
190
+ const inputSchemaVal = args.flags.get("input-schema");
191
+ const templateType = (args.flags.get("template-type") ?? "html-inline");
192
+ if (templateType !== "html-inline" && templateType !== "html-ref") {
193
+ fail("--template-type must be 'html-inline' or 'html-ref'", "invalid_args");
151
194
  }
152
195
  // html-ref: the value is a URL, used verbatim. html-inline: file or literal.
153
196
  let source;
154
197
  try {
155
198
  source =
156
- artifactType === "html-ref" ? artifactVal : resolveText(artifactVal);
199
+ templateType === "html-ref" ? artifactVal : resolveText(artifactVal);
157
200
  }
158
201
  catch (e) {
159
202
  fail(e instanceof Error ? e.message : String(e), "invalid_args");
160
203
  }
161
- // Build the inline artifact object. event_schema is OMITTED entirely (not
162
- // set to undefined) when --event-schema is absent — a view-only artifact.
204
+ // Build the inline template object. event_schema / input_schema are
205
+ // OMITTED entirely (not set to undefined) when their flags are absent —
206
+ // omission is meaningful at the relay (view-only template / no input
207
+ // contract).
163
208
  const inlineArtifact = {
164
- type: artifactType,
209
+ type: templateType,
165
210
  source,
166
211
  };
167
212
  if (schemaVal !== undefined) {
@@ -172,7 +217,28 @@ export async function runCreate(args) {
172
217
  fail(e instanceof Error ? e.message : String(e), "invalid_args");
173
218
  }
174
219
  }
175
- candidate["artifact"] = inlineArtifact;
220
+ if (inputSchemaVal !== undefined) {
221
+ try {
222
+ const v = resolveJson(inputSchemaVal, "--input-schema");
223
+ if (v === null || typeof v !== "object" || Array.isArray(v)) {
224
+ fail("--input-schema must be a JSON object", "invalid_args");
225
+ }
226
+ inlineArtifact["input_schema"] = v;
227
+ }
228
+ catch (e) {
229
+ fail(e instanceof Error ? e.message : String(e), "invalid_args");
230
+ }
231
+ }
232
+ candidate["template"] = inlineArtifact;
233
+ }
234
+ // --title — passthrough, no client-side requiredness. The relay is the
235
+ // single source of truth: it enforces "required, with --template-id +
236
+ // Template.name as the only fallback" and the shape rules (length, control
237
+ // chars). Keeping all that server-side avoids drift between the CLI's
238
+ // pre-checks and the relay's actual rules.
239
+ const titleRaw = args.flags.get("title");
240
+ if (titleRaw !== undefined) {
241
+ candidate["title"] = titleRaw;
176
242
  }
177
243
  // --input-data — per-instance seed data, applies to either form (the relay
178
244
  // validates it against the pinned version's input_schema).
@@ -1,13 +1,16 @@
1
- // `pane delete <id>` — close/delete a session.
1
+ // `pane surface delete <id>` — close/delete a surface.
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 deleteHelp = `pane delete — close/delete a session
5
+ const KNOWN_FLAGS = [];
6
+ const KNOWN_BOOLS = [];
7
+ export const deleteHelp = `pane surface delete — close/delete a surface
5
8
 
6
9
  Usage:
7
- pane delete <session-id> [options]
10
+ pane surface delete <surface-id> [options]
8
11
 
9
- Closes and deletes the session (DELETE /v1/sessions/:id). Idempotent on the
10
- relay side — deleting an already-closed session still succeeds.
12
+ Closes and deletes the surface (DELETE /v1/surfaces/:id). Idempotent on the
13
+ relay side — deleting an already-closed surface still succeeds.
11
14
 
12
15
  Options:
13
16
  --url <url> Relay base URL (overrides PANE_URL).
@@ -15,15 +18,16 @@ Options:
15
18
  -h, --help Show this help.
16
19
 
17
20
  Output (stdout, JSON):
18
- { session_id, deleted: true }`;
21
+ { surface_id, deleted: true }`;
19
22
  export async function runDelete(args) {
20
- const sessionId = args.positionals[0];
21
- if (!sessionId)
22
- fail("missing <session-id>", "invalid_args");
23
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane surface delete");
24
+ const surfaceId = args.positionals[0];
25
+ if (!surfaceId)
26
+ fail("missing <surface-id>", "invalid_args");
23
27
  const client = makeClient(args);
24
28
  try {
25
- await client.deleteSession(sessionId);
26
- printJson({ session_id: sessionId, deleted: true });
29
+ await client.deleteSession(surfaceId);
30
+ printJson({ surface_id: surfaceId, deleted: true });
27
31
  }
28
32
  catch (e) {
29
33
  failFromError(e);
@@ -1,5 +1,9 @@
1
+ import { assertKnownFlags } from "../argv.js";
1
2
  import { makeClient } from "../config.js";
2
3
  import { printJson, fail, failFromError } from "../output.js";
4
+ const CREATE_FLAGS = ["type", "message", "surface-id"];
5
+ const LIST_FLAGS = ["limit", "before"];
6
+ const NO_BOOLS = [];
3
7
  export const feedbackHelp = `pane feedback — submit / list feedback to the relay operator
4
8
 
5
9
  Feedback is a one-shot bug report, feature request, or note from YOUR agent
@@ -21,7 +25,7 @@ Options for 'create':
21
25
  --type <bug|feature|note> Feedback category. Required.
22
26
  --message <text|-> Message body. Pass '-' to read from stdin.
23
27
  1..4000 chars after trim.
24
- --session-id <id> Optional session this feedback relates to;
28
+ --surface-id <id> Optional surface this feedback relates to;
25
29
  must belong to YOUR agent.
26
30
 
27
31
  Options for 'list':
@@ -34,7 +38,7 @@ Global:
34
38
  -h, --help Show this help.
35
39
 
36
40
  Examples:
37
- pane feedback create --type bug --message "watch hangs on empty session"
41
+ pane feedback create --type bug --message "watch hangs on empty surface"
38
42
  echo "long-form note..." | pane feedback create --type note --message -
39
43
  pane feedback list --limit 20
40
44
 
@@ -48,9 +52,10 @@ async function readStdin() {
48
52
  return Buffer.concat(chunks).toString("utf8");
49
53
  }
50
54
  async function runFeedbackCreate(args) {
55
+ assertKnownFlags(args, CREATE_FLAGS, NO_BOOLS, "pane feedback create");
51
56
  const type = args.flags.get("type");
52
57
  const rawMessage = args.flags.get("message");
53
- const sessionId = args.flags.get("session-id");
58
+ const surfaceId = args.flags.get("surface-id");
54
59
  if (type === undefined) {
55
60
  fail("'pane feedback create' requires --type <bug|feature|note>", "invalid_args");
56
61
  }
@@ -78,7 +83,7 @@ async function runFeedbackCreate(args) {
78
83
  const res = await client.submitFeedback({
79
84
  type: type,
80
85
  message,
81
- ...(sessionId !== undefined ? { sessionId } : {}),
86
+ ...(surfaceId !== undefined ? { surfaceId } : {}),
82
87
  });
83
88
  printJson(res);
84
89
  }
@@ -87,6 +92,7 @@ async function runFeedbackCreate(args) {
87
92
  }
88
93
  }
89
94
  async function runFeedbackList(args) {
95
+ assertKnownFlags(args, LIST_FLAGS, NO_BOOLS, "pane feedback list");
90
96
  const limitRaw = args.flags.get("limit");
91
97
  const before = args.flags.get("before");
92
98
  let limit;