@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.
@@ -1,27 +1,37 @@
1
- // `pane surface create` — create a surface via POST /v1/surfaces.
2
- import { createSessionSchema } from "@paneui/core";
1
+ // `pane create` — create a pane via POST /v1/panes.
2
+ import { createPaneSchema } from "@paneui/core";
3
3
  import { assertKnownFlags } from "../argv.js";
4
4
  import { makeClient } from "../config.js";
5
5
  import { resolveJson, resolveText } from "../input.js";
6
6
  import { printJson, fail, failFromError } from "../output.js";
7
+ import { formatPaneCreated } from "../format.js";
7
8
  const KNOWN_FLAGS = [
8
9
  "template",
9
10
  "template-id",
10
11
  "template-type",
12
+ "name",
13
+ "slug",
11
14
  "version",
12
15
  "event-schema",
13
16
  "input-schema",
14
17
  "title",
18
+ "preamble",
15
19
  "input-data",
16
20
  "ttl",
17
21
  "participants",
18
22
  "metadata",
19
23
  "callback",
24
+ "context-key",
25
+ "icon-emoji",
26
+ "icon-attachment-id",
20
27
  ];
21
- const KNOWN_BOOLS = [];
28
+ // `--json` forces machine-readable output even on a TTY. Without it, an
29
+ // interactive terminal gets the human-readable form (title + URLs + QR +
30
+ // countdown); pipes and `--json` callers still get the legacy JSON shape.
31
+ const KNOWN_BOOLS = ["json"];
22
32
  // Translate a Zod schema path (e.g. ["participants","humans"]) back to the
23
33
  // public CLI flag the user actually typed. Without this, a `--participants 0`
24
- // rejection surfaces as `participants.humans: ...` — which leaks the wire
34
+ // rejection panes as `participants.humans: ...` — which leaks the wire
25
35
  // shape and refers to no flag the user could fix.
26
36
  //
27
37
  // Match strategy: longest prefix wins. Schema paths whose top segment isn't
@@ -37,9 +47,15 @@ const SCHEMA_PATH_TO_FLAG = {
37
47
  callback: "--callback",
38
48
  input_data: "--input-data",
39
49
  title: "--title",
50
+ preamble: "--preamble",
51
+ context_key: "--context-key",
52
+ icon_emoji: "--icon-emoji",
53
+ icon_attachment_id: "--icon-attachment-id",
40
54
  "template.id": "--template-id",
41
55
  "template.version": "--version",
42
56
  "template.type": "--template-type",
57
+ "template.name": "--name",
58
+ "template.slug": "--slug",
43
59
  "template.source": "--template",
44
60
  "template.event_schema": "--event-schema",
45
61
  "template.input_schema": "--input-schema",
@@ -57,16 +73,16 @@ function schemaPathToFlag(path) {
57
73
  }
58
74
  return dotted;
59
75
  }
60
- export const createHelp = `pane surface create — create a Pane surface
76
+ export const createHelp = `pane create — create a Pane pane
61
77
 
62
- A surface is one use of an template. Supply the template in ONE of two ways:
78
+ A pane is one use of an template. Supply the template in ONE of two ways:
63
79
 
64
80
  Reference form — instance an existing reusable template (the cheap path,
65
81
  no HTML re-sent):
66
- pane surface create --template-id <id|slug> [--version <n>] [--input-data <v>]
82
+ pane create --template-id <id|slug> [--version <n>] [--input-data <v>]
67
83
 
68
84
  Inline form — a one-off template, defined on this call:
69
- pane surface create --template <path|inline> [--event-schema <path|json>] [options]
85
+ pane create --template <path|inline> [--event-schema <path|json>] [options]
70
86
 
71
87
  Exactly one of --template-id / --template must be given.
72
88
 
@@ -79,6 +95,14 @@ Template (choose one):
79
95
  the template's latest version.
80
96
  --template <v> Inline HTML template. Either a file path / URL, or inline
81
97
  HTML. Combine with --template-type to control reading.
98
+ --name <text> Inline-form template name. REQUIRED with --template so
99
+ the auto-created template carries a readable label in the
100
+ owner-shell UI (and so --title can fall back to it).
101
+ Rejected with --template-id (the reference form inherits
102
+ the existing template's name).
103
+ --slug <text> Inline-form template slug (optional). Must be unique among
104
+ your templates; a collision is rejected. Rejected with
105
+ --template-id.
82
106
  --event-schema <v> Inline-form event schema. A .json file, or inline JSON.
83
107
  Optional with --template. Omit for a view-only template
84
108
  (a report/dashboard the human only views — no page/agent
@@ -105,7 +129,7 @@ Template (choose one):
105
129
  --input-schema <v> Inline-form input schema. A .json file, or inline JSON.
106
130
  Optional with --template, rejected with --template-id
107
131
  (the schema comes from the pinned template version
108
- there). When present, the surface's --input-data is
132
+ there). When present, the pane's --input-data is
109
133
  validated against it AND any attachment ids declared at a
110
134
  "format": "pane-attachment-id" site become reachable from the
111
135
  page via window.pane.downloadBlob. Without it, attachment
@@ -114,10 +138,16 @@ Template (choose one):
114
138
 
115
139
  Options:
116
140
  --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.
141
+ line). Optional: when omitted, the relay falls back to
142
+ the template's name the existing Template.name for the
143
+ reference form, or the --name you pass for the inline
144
+ form.
145
+ --preamble <text> Optional one- or two-line context message rendered in
146
+ the shell band above the iframe — "who is asking, why".
147
+ Max 280 chars after trim; a single \\n is allowed for a
148
+ two-line message; other control chars are rejected. Pass
149
+ this whenever the artifact itself doesn't make the
150
+ request self-explanatory.
121
151
  --input-data <v> This instance's seed data — a JSON object (file path or
122
152
  inline JSON), validated by the relay against the template
123
153
  version's input_schema. The page reads it as
@@ -125,24 +155,43 @@ Options:
125
155
  --template-type <t> "html-inline" (default) or "html-ref". With "html-ref"
126
156
  the --template value is treated as a URL. Note: the relay
127
157
  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
158
+ will reject the pane — use "html-inline".
159
+ --ttl <seconds> Pane time-to-live in seconds. The relay clamps this
130
160
  to its configured MAX_TTL_SECONDS (defaults: 1 h
131
161
  requested, 24 h max for self-host; hosted may differ).
132
162
  The returned \`expires_at\` is the authoritative value.
133
163
  --participants <n> Number of human participants (default 1).
134
164
  --metadata <path|json> Arbitrary metadata object (file path or inline JSON).
135
165
  --callback <path|json> Webhook callback config: { url, events[], secret }.
166
+ --context-key <key> Natural key for "the same logical thing" — the relay
167
+ dedups repeated creates with the same
168
+ (template, owner, context_key) into one pane row,
169
+ returning {created:false, pane_id:<existing>} on
170
+ subsequent calls. Use this to make scripted creates
171
+ idempotent (e.g. "pr-42", "deal-1138", "home"). Only
172
+ meaningful when the calling agent is claimed by a
173
+ human; omit otherwise. Allowed chars: A-Za-z0-9_:.-,
174
+ max 256.
175
+ --icon-emoji <e> Per-pane icon override — a single emoji grapheme. NULL/
176
+ omitted = inherit the template's icon.
177
+ --icon-attachment-id <id>
178
+ Per-pane icon override — a ready raster-image attachment
179
+ (png/jpeg/webp/gif) accessible to this agent. No SVG, no
180
+ URLs. Omitted = inherit the template's icon.
136
181
  --url <url> Relay base URL (overrides PANE_URL).
137
182
  --api-key <key> Agent API key (overrides PANE_API_KEY).
183
+ --json Force JSON output even on a TTY. Default: JSON when
184
+ stdout is piped; a human-readable summary (title, URL,
185
+ QR code, expiry countdown) when stdout is a terminal.
138
186
  -h, --help Show this help.
139
187
 
140
- Output (stdout, JSON):
141
- { surface_id, urls, tokens, expires_at }
188
+ Output:
189
+ - Piped (or --json): { pane_id, urls, tokens, expires_at } as JSON
190
+ - TTY: title + each human URL + a scannable QR code + expiry countdown
142
191
 
143
192
  Deliver urls.humans to the human(s); keep tokens.agent for the WS stream.`;
144
193
  export async function runCreate(args) {
145
- assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane surface create");
194
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane create");
146
195
  const artifactIdVal = args.flags.get("template-id");
147
196
  const artifactVal = args.flags.get("template");
148
197
  // Exactly one of the two template forms must be present.
@@ -164,6 +213,15 @@ export async function runCreate(args) {
164
213
  if (args.flags.get("input-schema") !== undefined) {
165
214
  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
215
  }
216
+ // --name / --slug name the inline form's auto-created template. The
217
+ // reference form inherits the existing Template.name/slug, so they have
218
+ // no meaning here — reject rather than silently ignore.
219
+ if (args.flags.get("name") !== undefined) {
220
+ fail("--name is incompatible with --template-id — the reference form inherits the existing template's name. Use --name only with --template (inline form).", "invalid_args");
221
+ }
222
+ if (args.flags.get("slug") !== undefined) {
223
+ fail("--slug is incompatible with --template-id — the reference form inherits the existing template's slug. Use --slug only with --template (inline form).", "invalid_args");
224
+ }
167
225
  const ref = { id: artifactIdVal };
168
226
  const versionRaw = args.flags.get("version");
169
227
  if (versionRaw !== undefined) {
@@ -201,14 +259,28 @@ export async function runCreate(args) {
201
259
  catch (e) {
202
260
  fail(e instanceof Error ? e.message : String(e), "invalid_args");
203
261
  }
262
+ // --name is REQUIRED for the inline form: the relay names the
263
+ // auto-created template with it so the owner-shell UI has a readable
264
+ // label (the reference form inherits the existing Template.name instead).
265
+ // --slug is optional. Both are inline-only — they are rejected with
266
+ // --template-id below.
267
+ const nameVal = args.flags.get("name");
268
+ if (nameVal === undefined || nameVal === "") {
269
+ fail("--name is required with --template (inline form); it names the auto-created template so the UI shows a readable label", "invalid_args");
270
+ }
271
+ const slugVal = args.flags.get("slug");
204
272
  // Build the inline template object. event_schema / input_schema are
205
273
  // OMITTED entirely (not set to undefined) when their flags are absent —
206
274
  // omission is meaningful at the relay (view-only template / no input
207
275
  // contract).
208
276
  const inlineArtifact = {
277
+ name: nameVal,
209
278
  type: templateType,
210
279
  source,
211
280
  };
281
+ if (slugVal !== undefined) {
282
+ inlineArtifact["slug"] = slugVal;
283
+ }
212
284
  if (schemaVal !== undefined) {
213
285
  try {
214
286
  inlineArtifact["event_schema"] = resolveJson(schemaVal, "--event-schema");
@@ -240,6 +312,13 @@ export async function runCreate(args) {
240
312
  if (titleRaw !== undefined) {
241
313
  candidate["title"] = titleRaw;
242
314
  }
315
+ // --preamble — passthrough. The relay trims, normalises CRLF, enforces
316
+ // ≤280 chars and rejects non-newline control chars; mirroring those
317
+ // checks here would just create drift on edge cases.
318
+ const preambleRaw = args.flags.get("preamble");
319
+ if (preambleRaw !== undefined) {
320
+ candidate["preamble"] = preambleRaw;
321
+ }
243
322
  // --input-data — per-instance seed data, applies to either form (the relay
244
323
  // validates it against the pinned version's input_schema).
245
324
  const inputDataRaw = args.flags.get("input-data");
@@ -283,7 +362,26 @@ export async function runCreate(args) {
283
362
  fail(e instanceof Error ? e.message : String(e), "invalid_args");
284
363
  }
285
364
  }
286
- const parsed = createSessionSchema.safeParse(candidate);
365
+ // --context-key natural-key dedup. Passthrough; the relay enforces
366
+ // length + charset (createPaneSchema in @paneui/core), so an invalid
367
+ // value panes as a schema rejection on the call below rather than
368
+ // a duplicated client-side guard that could drift.
369
+ const contextKey = args.flags.get("context-key");
370
+ if (contextKey !== undefined) {
371
+ candidate["context_key"] = contextKey;
372
+ }
373
+ // Per-pane icon override. Passthrough — the relay validates the emoji
374
+ // (single grapheme) and the attachment (ready raster image, accessible to
375
+ // the agent), so a bad value panes as a schema/route rejection rather than
376
+ // a duplicated client guard.
377
+ const iconEmoji = args.flags.get("icon-emoji");
378
+ if (iconEmoji !== undefined)
379
+ candidate["icon_emoji"] = iconEmoji;
380
+ const iconAttachmentId = args.flags.get("icon-attachment-id");
381
+ if (iconAttachmentId !== undefined) {
382
+ candidate["icon_attachment_id"] = iconAttachmentId;
383
+ }
384
+ const parsed = createPaneSchema.safeParse(candidate);
287
385
  if (!parsed.success) {
288
386
  const issue = parsed.error.issues[0];
289
387
  const where = issue && issue.path.length > 0 ? schemaPathToFlag(issue.path) : "request";
@@ -292,8 +390,21 @@ export async function runCreate(args) {
292
390
  const req = parsed.data;
293
391
  const client = makeClient(args);
294
392
  try {
295
- const res = await client.createSession(req);
296
- printJson(res);
393
+ const res = await client.createPane(req);
394
+ // Output mode:
395
+ // --json explicit → JSON
396
+ // stdout NOT a TTY (pipe) → JSON (so scripts / agents stay parseable)
397
+ // stdout IS a TTY → human-readable (URL + QR + countdown)
398
+ // The TTY check matches the existing `pane taste` / `pane feedback`
399
+ // pattern: agents are non-interactive, humans are.
400
+ const forceJson = args.bools.has("json");
401
+ const isTty = Boolean(process.stdout.isTTY);
402
+ if (forceJson || !isTty) {
403
+ printJson(res);
404
+ }
405
+ else {
406
+ process.stdout.write(formatPaneCreated(res, { color: isTty }));
407
+ }
297
408
  }
298
409
  catch (e) {
299
410
  failFromError(e);
@@ -1,16 +1,16 @@
1
- // `pane surface delete <id>` — close/delete a surface.
1
+ // `pane delete <id>` — close/delete a pane.
2
2
  import { assertKnownFlags } from "../argv.js";
3
3
  import { makeClient } from "../config.js";
4
4
  import { printJson, fail, failFromError } from "../output.js";
5
5
  const KNOWN_FLAGS = [];
6
6
  const KNOWN_BOOLS = [];
7
- export const deleteHelp = `pane surface delete — close/delete a surface
7
+ export const deleteHelp = `pane delete — close/delete a pane
8
8
 
9
9
  Usage:
10
- pane surface delete <surface-id> [options]
10
+ pane delete <pane-id> [options]
11
11
 
12
- Closes and deletes the surface (DELETE /v1/surfaces/:id). Idempotent on the
13
- relay side — deleting an already-closed surface still succeeds.
12
+ Closes and deletes the pane (DELETE /v1/panes/:id). Idempotent on the
13
+ relay side — deleting an already-closed pane still succeeds.
14
14
 
15
15
  Options:
16
16
  --url <url> Relay base URL (overrides PANE_URL).
@@ -18,16 +18,16 @@ Options:
18
18
  -h, --help Show this help.
19
19
 
20
20
  Output (stdout, JSON):
21
- { surface_id, deleted: true }`;
21
+ { pane_id, deleted: true }`;
22
22
  export async function runDelete(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
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane delete");
24
+ const paneId = args.positionals[0];
25
+ if (!paneId)
26
+ fail("missing <pane-id>", "invalid_args");
27
27
  const client = makeClient(args);
28
28
  try {
29
- await client.deleteSession(surfaceId);
30
- printJson({ surface_id: surfaceId, deleted: true });
29
+ await client.deletePane(paneId);
30
+ printJson({ pane_id: paneId, deleted: true });
31
31
  }
32
32
  catch (e) {
33
33
  failFromError(e);
@@ -1,7 +1,7 @@
1
1
  import { assertKnownFlags } from "../argv.js";
2
2
  import { makeClient } from "../config.js";
3
3
  import { printJson, fail, failFromError } from "../output.js";
4
- const CREATE_FLAGS = ["type", "message", "surface-id"];
4
+ const CREATE_FLAGS = ["type", "message", "pane-id"];
5
5
  const LIST_FLAGS = ["limit", "before"];
6
6
  const NO_BOOLS = [];
7
7
  export const feedbackHelp = `pane feedback — submit / list feedback to the relay operator
@@ -25,7 +25,7 @@ Options for 'create':
25
25
  --type <bug|feature|note> Feedback category. Required.
26
26
  --message <text|-> Message body. Pass '-' to read from stdin.
27
27
  1..4000 chars after trim.
28
- --surface-id <id> Optional surface this feedback relates to;
28
+ --pane-id <id> Optional pane this feedback relates to;
29
29
  must belong to YOUR agent.
30
30
 
31
31
  Options for 'list':
@@ -38,7 +38,7 @@ Global:
38
38
  -h, --help Show this help.
39
39
 
40
40
  Examples:
41
- pane feedback create --type bug --message "watch hangs on empty surface"
41
+ pane feedback create --type bug --message "watch hangs on empty pane"
42
42
  echo "long-form note..." | pane feedback create --type note --message -
43
43
  pane feedback list --limit 20
44
44
 
@@ -55,7 +55,7 @@ async function runFeedbackCreate(args) {
55
55
  assertKnownFlags(args, CREATE_FLAGS, NO_BOOLS, "pane feedback create");
56
56
  const type = args.flags.get("type");
57
57
  const rawMessage = args.flags.get("message");
58
- const surfaceId = args.flags.get("surface-id");
58
+ const paneId = args.flags.get("pane-id");
59
59
  if (type === undefined) {
60
60
  fail("'pane feedback create' requires --type <bug|feature|note>", "invalid_args");
61
61
  }
@@ -83,7 +83,7 @@ async function runFeedbackCreate(args) {
83
83
  const res = await client.submitFeedback({
84
84
  type: type,
85
85
  message,
86
- ...(surfaceId !== undefined ? { surfaceId } : {}),
86
+ ...(paneId !== undefined ? { paneId } : {}),
87
87
  });
88
88
  printJson(res);
89
89
  }
@@ -1,23 +1,23 @@
1
- // `pane surface list` — enumerate YOUR agent's surfaces.
1
+ // `pane list` — enumerate YOUR agent's panes.
2
2
  //
3
3
  // The recovery primitive when the create-response was dropped: the URL itself
4
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
5
+ // field of the pane is intact and listable here. Pair with
6
+ // `pane participant new` to mint a fresh URL on a pane whose
7
7
  // original was lost.
8
8
  import { assertKnownFlags } from "../argv.js";
9
9
  import { makeClient } from "../config.js";
10
10
  import { printJson, fail, failFromError } from "../output.js";
11
11
  const KNOWN_FLAGS = ["status", "limit", "cursor", "template-id"];
12
12
  const KNOWN_BOOLS = [];
13
- export const listHelp = `pane surface list — list YOUR agent's surfaces
13
+ export const listHelp = `pane list — list YOUR agent's panes
14
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>'.
15
+ Prints panes (newest first) with no secrets in the response. Participant
16
+ tokens are stored hashed and CANNOT be recovered — if you lost a pane URL,
17
+ mint a fresh one with 'pane participant new <pane-id>'.
18
18
 
19
19
  Usage:
20
- pane surface list [options]
20
+ pane list [options]
21
21
 
22
22
  Options:
23
23
  --status <s> open | closed | all. Default: open. The status reported
@@ -25,7 +25,7 @@ Options:
25
25
  is reported as 'closed' even if not yet swept.
26
26
  --limit <N> Page size (default 50, max 200).
27
27
  --cursor <c> Opaque cursor from a previous page's next_cursor.
28
- --template-id <i> Filter to surfaces instantiated from a specific named
28
+ --template-id <i> Filter to panes instantiated from a specific named
29
29
  template (head id; not version id). Inline (anonymous)
30
30
  templates cannot be filtered this way — they have no
31
31
  stable handle.
@@ -33,20 +33,20 @@ Options:
33
33
  --api-key <key> Agent API key (overrides PANE_API_KEY).
34
34
  -h, --help Show this help.
35
35
 
36
- Recovery recipe (lost the URL but the surface is still alive):
37
- pane surface list # find the
38
- # surface_id +
36
+ Recovery recipe (lost the URL but the pane is still alive):
37
+ pane list # find the
38
+ # pane_id +
39
39
  # participant_id
40
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
41
+ pane participant new <pane-id> # mint a fresh URL
42
+ pane participant revoke <pane-id> <p-id> # invalidate the
43
43
  # old URL
44
44
 
45
45
  Output (stdout, JSON):
46
46
  {
47
47
  items: [
48
48
  {
49
- surface_id, title, status, template_id, template_version_id,
49
+ pane_id, title, status, template_id, template_version_id,
50
50
  template_version, participants: [...], created_at, expires_at,
51
51
  has_callback
52
52
  },
@@ -56,7 +56,7 @@ Output (stdout, JSON):
56
56
  }`;
57
57
  const STATUSES = ["open", "closed", "all"];
58
58
  export async function runList(args) {
59
- assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane surface list");
59
+ assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane list");
60
60
  const opts = {};
61
61
  const status = args.flags.get("status");
62
62
  if (status !== undefined) {
@@ -82,7 +82,7 @@ export async function runList(args) {
82
82
  }
83
83
  const client = makeClient(args);
84
84
  try {
85
- const page = await client.listSessions(opts);
85
+ const page = await client.listPanes(opts);
86
86
  printJson(page);
87
87
  }
88
88
  catch (e) {
@@ -1,29 +1,59 @@
1
- // `pane agent logout` — clear the locally-saved relay URL + API key.
1
+ // `pane agent logout` — clear one (or all) saved profile(s).
2
2
  import { assertKnownFlags } from "../argv.js";
3
- import { clearStore } from "../store.js";
4
- import { printJson } from "../output.js";
3
+ import { clearStore, readStore, removeProfile, resolveProfile, } from "../store.js";
4
+ import { printJson, fail } from "../output.js";
5
5
  const NO_FLAGS = [];
6
- const NO_BOOLS = [];
7
- export const logoutHelp = `pane agent logout — clear the saved relay URL + API key
6
+ const KNOWN_BOOLS = ["all"];
7
+ export const logoutHelp = `pane agent logout — clear a saved profile (or all of them)
8
8
 
9
9
  Usage:
10
10
  pane agent logout [options]
11
11
 
12
- Deletes the CLI config file (\${XDG_CONFIG_HOME:-~/.config}/pane/config.json),
13
- which holds the relay URL and the agent API key saved by 'pane agent register'.
14
- Idempotent no error if there is nothing to clear.
12
+ By default this clears the ACTIVE profile only (the one selected by --profile
13
+ / PANE_PROFILE / the store's current_profile). The on-disk file keeps the
14
+ other profiles, and 'current_profile' is unset so the next command falls back
15
+ to env / default URL until another profile is selected.
16
+
17
+ Pass --all to delete the whole config file (the pre-profile behaviour) — this
18
+ wipes every profile, not just the active one. Idempotent — no error if there
19
+ is nothing to clear.
15
20
 
16
21
  This only clears the LOCAL config. It does NOT revoke the key on the relay —
17
- the key keeps working until it is revoked. To revoke it on the relay, use
22
+ keys keep working until revoked. To revoke a key server-side, use
18
23
  'pane key revoke'.
19
24
 
20
25
  Options:
26
+ --profile <name> Target this profile instead of the active one.
27
+ --all Delete every profile (the whole config file).
21
28
  -h, --help Show this help.
22
29
 
23
30
  Output (stdout, JSON):
24
- { cleared: true, path }`;
31
+ { cleared: true, profile, path } (profile=null when --all)`;
25
32
  export async function runLogout(args) {
26
- assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane agent logout");
27
- const path = clearStore();
28
- printJson({ cleared: true, path });
33
+ assertKnownFlags(args, NO_FLAGS, KNOWN_BOOLS, "pane agent logout");
34
+ if (args.bools.has("all")) {
35
+ // Nuke everything — file gone, both legacy and new shape covered.
36
+ const path = clearStore();
37
+ printJson({ cleared: true, profile: null, path });
38
+ return;
39
+ }
40
+ const store = readStore();
41
+ const selector = args.flags.get("profile") ?? process.env.PANE_PROFILE;
42
+ let target;
43
+ try {
44
+ target = resolveProfile(store, selector);
45
+ }
46
+ catch (e) {
47
+ fail(e instanceof Error ? e.message : String(e), "config_error");
48
+ }
49
+ // Nothing to clear: empty store or legacy file with no migrate yet.
50
+ if (!target) {
51
+ // If there's literally nothing saved, mirror the legacy idempotent
52
+ // behaviour — delete the file (no-op if absent) and report cleared.
53
+ const path = clearStore();
54
+ printJson({ cleared: true, profile: null, path });
55
+ return;
56
+ }
57
+ const { path } = removeProfile(target.name);
58
+ printJson({ cleared: true, profile: target.name, path });
29
59
  }