@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 CHANGED
@@ -34,11 +34,11 @@ Uniform `pane <noun> <verb> [options]`:
34
34
  ```
35
35
  pane agent register Provision an agent API key and save it locally
36
36
  pane agent logout Clear the locally-saved URL + API key
37
- pane surface create Create a surface — returns surface_id, urls, tokens
38
- pane surface show <id> Non-blocking snapshot: metadata + event log
39
- pane surface send <id> Emit an agent event into a surface
40
- pane surface watch <id> Stream a surface's events as JSON-lines on stdout
41
- pane surface delete <id> Close / delete a surface
37
+ pane create Create a pane — returns pane_id, urls, tokens
38
+ pane show <id> Non-blocking snapshot: metadata + event log
39
+ pane send <id> Emit an agent event into a pane
40
+ pane watch <id> Stream a pane's events as JSON-lines on stdout
41
+ pane delete <id> Close / delete a pane
42
42
  pane template <verb> Manage reusable, versioned templates
43
43
  pane key list | revoke Inspect or revoke your agent's API key
44
44
  pane taste get | set | clear Read / write / clear UI-taste notes
@@ -56,12 +56,12 @@ stdout is machine-readable JSON. Errors go to stderr as
56
56
  `{"error":{"code","message"}}` with a non-zero exit.
57
57
 
58
58
  ```sh
59
- SESSION=$(pane surface create --template form --schema ./q.json | jq -r .surface_id)
60
- pane surface watch "$SESSION" | jq 'select(.type == "human_response")'
59
+ SESSION=$(pane create --template ./form.html --name "Quick poll" --event-schema ./q.json | jq -r .pane_id)
60
+ pane watch "$SESSION" | jq 'select(.type == "human_response")'
61
61
  ```
62
62
 
63
63
  ## Links
64
64
 
65
65
  - Repo: <https://github.com/aerolalit/paneui>
66
- - Spec: <https://github.com/aerolalit/paneui/attachment/main/docs/SPEC.md>
66
+ - Spec: <https://github.com/aerolalit/paneui/blob/main/docs/SPEC.md>
67
67
  - License: MIT
package/dist/argv.js CHANGED
@@ -89,7 +89,7 @@ export function parseArgs(tokens, booleanFlags) {
89
89
  * so adding a new global flag updates one place. `url` / `api-key` are the
90
90
  * relay-target overrides; `help` / `json` are universal display modes.
91
91
  */
92
- const GLOBAL_FLAGS = ["url", "api-key"];
92
+ const GLOBAL_FLAGS = ["url", "api-key", "profile"];
93
93
  const GLOBAL_BOOLS = ["help", "json"];
94
94
  /**
95
95
  * Reject anything the per-command allow-list (plus the globals above) does
@@ -104,7 +104,7 @@ const GLOBAL_BOOLS = ["help", "json"];
104
104
  *
105
105
  * Also resolves the parser's `danglingValueFlags`: an unknown name there
106
106
  * is reported alongside other unknowns ("unknown flag(s): --bogus"); a
107
- * known name there surfaces as "--name requires a value". This is what
107
+ * known name there panes as "--name requires a value". This is what
108
108
  * keeps the error message uniform for a typo whether or not a value
109
109
  * follows it.
110
110
  */
@@ -129,7 +129,7 @@ export function assertKnownFlags(args, knownFlags, knownBools, helpCommand) {
129
129
  throw new ArgvError(`unknown flag(s): ${unknown.join(", ")}`, `run \`${helpCommand} --help\` for the supported flags`);
130
130
  }
131
131
  // No unknowns — but a known value-flag may still have been left without
132
- // a value. Surface the first such case with the pre-existing message
132
+ // a value. Pane the first such case with the pre-existing message
133
133
  // shape ("--name requires a value"). Reporting only the first keeps the
134
134
  // message simple; the user fixes that flag, re-runs, sees the next one.
135
135
  for (const k of dangling) {
@@ -10,6 +10,7 @@
10
10
  import { runRegister } from "./register.js";
11
11
  import { runLogout } from "./logout.js";
12
12
  import { runClaim } from "./claim.js";
13
+ import { runSetKey } from "./set-key.js";
13
14
  import { fail } from "../output.js";
14
15
  export const agentHelp = `pane agent — manage this agent's identity on the relay
15
16
 
@@ -22,6 +23,10 @@ Verbs:
22
23
  claim <code> Bind this agent to a human via a one-shot claim code the
23
24
  human generated in their Settings UI (POST /v1/agents/claim).
24
25
  One-way; no unclaim in v1.
26
+ set-key <key> Save a new API key into the CLI config file. Used after
27
+ regenerating the agent's key in the relay's My-agents UI:
28
+ the human pastes the new key here so subsequent commands
29
+ authenticate as the same agent.
25
30
  logout Clear the locally-saved relay URL + API key. Does NOT
26
31
  revoke the key on the relay — use 'pane key revoke' for
27
32
  that.
@@ -42,13 +47,16 @@ export async function runAgent(args) {
42
47
  case "claim":
43
48
  await runClaim(verbArgs);
44
49
  break;
50
+ case "set-key":
51
+ await runSetKey(verbArgs);
52
+ break;
45
53
  case "logout":
46
54
  await runLogout(verbArgs);
47
55
  break;
48
56
  case undefined:
49
- fail("missing verb — usage: pane agent <register|claim|logout> (run 'pane agent --help')", "invalid_args");
57
+ fail("missing verb — usage: pane agent <register|claim|set-key|logout> (run 'pane agent --help')", "invalid_args");
50
58
  break;
51
59
  default:
52
- fail(`unknown agent verb '${verb}' — expected register|claim|logout (run 'pane agent --help')`, "invalid_args");
60
+ fail(`unknown agent verb '${verb}' — expected register|claim|set-key|logout (run 'pane agent --help')`, "invalid_args");
53
61
  }
54
62
  }
@@ -10,7 +10,7 @@
10
10
  // hands us a ParsedArgs whose positionals[0] is "token" (our sub-noun
11
11
  // marker), so we read the verb from positionals[1] and the args from
12
12
  // positionals[2..]. Mirrors how participant.ts dispatches under `pane
13
- // surface participant`.
13
+ // pane participant`.
14
14
  import { assertKnownFlags } from "../argv.js";
15
15
  import { makeClient } from "../config.js";
16
16
  import { fail, failFromError, printJson } from "../output.js";
@@ -31,7 +31,7 @@ Usage:
31
31
  Verbs:
32
32
  mint <attachment-id> Mint a /b/<token> capability URL for one attachment.
33
33
  Optional: --ttl <seconds> (defaults by scope:
34
- 30d template / surface TTL / 24h agent; the caller
34
+ 30d template / pane TTL / 24h agent; the caller
35
35
  can only shorten), --once (token self-deletes on
36
36
  first successful GET). Returns { token, url,
37
37
  expires_at, ... } — ONCE.
@@ -7,7 +7,7 @@ import { fail, failFromError, printJson } from "../output.js";
7
7
  const KNOWN_FLAGS = [
8
8
  "file",
9
9
  "scope",
10
- "surface-id",
10
+ "pane-id",
11
11
  "template-id",
12
12
  "filename",
13
13
  "mime",
@@ -22,8 +22,8 @@ Required:
22
22
  --file <path> Local file to upload.
23
23
 
24
24
  Scope (default: agent):
25
- --scope <s> "agent" | "surface" | "template".
26
- --surface-id <id> Required when --scope=surface.
25
+ --scope <s> "agent" | "pane" | "template".
26
+ --pane-id <id> Required when --scope=pane.
27
27
  --template-id <id> Required when --scope=template.
28
28
 
29
29
  Optional:
@@ -50,14 +50,12 @@ export async function runBlobUpload(args) {
50
50
  fail(`failed to read --file '${filePath}': ${e instanceof Error ? e.message : String(e)}`, "invalid_args");
51
51
  }
52
52
  const scopeRaw = args.flags.get("scope") ?? "agent";
53
- if (scopeRaw !== "agent" &&
54
- scopeRaw !== "surface" &&
55
- scopeRaw !== "template") {
56
- fail(`unknown --scope '${scopeRaw}' — expected one of: agent, surface, template`, "invalid_args");
53
+ if (scopeRaw !== "agent" && scopeRaw !== "pane" && scopeRaw !== "template") {
54
+ fail(`unknown --scope '${scopeRaw}' expected one of: agent, pane, template`, "invalid_args");
57
55
  }
58
56
  const scope = scopeRaw;
59
- if (scope === "surface" && !args.flags.get("surface-id")) {
60
- fail("--scope=surface requires --surface-id <id>", "invalid_args");
57
+ if (scope === "pane" && !args.flags.get("pane-id")) {
58
+ fail("--scope=pane requires --pane-id <id>", "invalid_args");
61
59
  }
62
60
  if (scope === "template" && !args.flags.get("template-id")) {
63
61
  fail("--scope=template requires --template-id <id>", "invalid_args");
@@ -66,7 +64,7 @@ export async function runBlobUpload(args) {
66
64
  try {
67
65
  const ref = await client.uploadBlob(bytes, {
68
66
  scope,
69
- surfaceId: args.flags.get("surface-id"),
67
+ paneId: args.flags.get("pane-id"),
70
68
  templateId: args.flags.get("template-id"),
71
69
  filename: args.flags.get("filename") ?? basename(filePath),
72
70
  mime: args.flags.get("mime"),
@@ -1,7 +1,7 @@
1
1
  // `pane attachment` — manage binary attachments (attachments) on the relay.
2
2
  //
3
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
4
+ // agent and optionally bound to a pane or template. Pages reference attachments
5
5
  // by id with `format: pane-attachment-id`; participants can fetch a attachment through a
6
6
  // minted capability URL (/b/<token>) without needing the agent's API key.
7
7
  //
@@ -24,8 +24,8 @@ export const blobHelp = `pane attachment — manage attachments (binary attachme
24
24
  A attachment is a typed binary file (image, PDF, audio, video, ...) the agent has
25
25
  uploaded to the relay. Blobs are scoped:
26
26
 
27
- agent — reusable across the agent's surfaces (default)
28
- surface — bound to one surface; deleted with it
27
+ agent — reusable across the agent's panes (default)
28
+ pane — bound to one pane; deleted with it
29
29
  template — bound to a reusable template; deleted with it
30
30
 
31
31
  Pages reference attachments by id (the relay's schema validates the id with
@@ -37,7 +37,7 @@ Usage:
37
37
 
38
38
  Verbs:
39
39
  upload Upload a local file. Required: --file. Optional:
40
- --scope, --surface-id, --template-id, --filename,
40
+ --scope, --pane-id, --template-id, --filename,
41
41
  --mime. Prints { attachment_id, scope, mime, size, sha256,
42
42
  ... }.
43
43
 
@@ -66,12 +66,12 @@ Output: stdout is machine-readable JSON. Errors go to stderr as
66
66
  * Build a new ParsedArgs with the leading positional (the verb) stripped.
67
67
  * The downstream verb runners read their primary positional (the attachment_id)
68
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.
69
+ * they were called directly — mirrors pane.ts's shiftPositionals.
70
70
  */
71
71
  function shiftPositionals(args) {
72
72
  // Propagate danglingValueFlags so the leaf runner's assertKnownFlags
73
73
  // can still distinguish "unknown flag" from "missing value" — see the
74
- // matching note in surface.ts's shiftPositionals.
74
+ // matching note in pane.ts's shiftPositionals.
75
75
  const out = {
76
76
  positionals: args.positionals.slice(1),
77
77
  flags: args.flags,
@@ -95,7 +95,7 @@ export async function runBlob(args) {
95
95
  return;
96
96
  }
97
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
98
+ // so the general pre-empt would already fire, but for parity with pane.ts
99
99
  // we route through here when args carry the "list" positional explicitly).
100
100
  if (verb === "list" &&
101
101
  args.bools.has("help") &&
@@ -7,7 +7,7 @@
7
7
  // 2. Alice hands the code to the agent out-of-band (this CLI invocation
8
8
  // is exactly that handoff).
9
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.
10
+ // 4. Relay binds Agent.ownerHumanId = alice.id, migrates pane ownership.
11
11
  //
12
12
  // The CLI does NOT print the human's email or id — only the relay's response,
13
13
  // which is { ok, owner_human_id, claimed_at }. The agent's existing API key
@@ -1,50 +1,262 @@
1
- // `pane config show`show the resolved relay config without a network call.
1
+ // `pane config <verb>`inspect and manage the multi-profile CLI config.
2
+ //
3
+ // show describe the resolved (url, api_key) the CLI would use
4
+ // list list saved profiles (names + URLs + current marker)
5
+ // use <name> switch the active profile
6
+ // add <name> manually add a profile (for keys obtained out of band)
7
+ // rm <name> delete a profile
8
+ //
9
+ // All four mutating verbs operate on the multi-profile store at
10
+ // $XDG_CONFIG_HOME/pane/config.json. See store.ts for the layout. They make
11
+ // NO network calls — purely local config management.
2
12
  import { assertKnownFlags } from "../argv.js";
3
13
  import { describeConfig } from "../config.js";
14
+ import { isValidProfileName, readStore, removeProfile, setCurrentProfile, storePath, upsertProfile, } from "../store.js";
4
15
  import { printJson, fail } from "../output.js";
5
- const NO_FLAGS = [];
6
- const NO_BOOLS = [];
7
- export const configHelp = `pane config — show the resolved relay config
16
+ const SHOW_FLAGS = [];
17
+ const SHOW_BOOLS = [];
18
+ const LIST_FLAGS = [];
19
+ const LIST_BOOLS = [];
20
+ const USE_FLAGS = [];
21
+ const USE_BOOLS = [];
22
+ const ADD_FLAGS = ["api-key"];
23
+ const ADD_BOOLS = [];
24
+ const RM_FLAGS = [];
25
+ const RM_BOOLS = [];
26
+ export const configHelp = `pane config — show and manage the CLI config (multi-profile)
27
+
28
+ Usage:
29
+ pane config show Show the resolved relay config
30
+ pane config list List saved profiles
31
+ pane config use <profile> Switch the active profile
32
+ pane config add <profile> --url <u> --api-key <k>
33
+ Add a profile manually
34
+ pane config rm <profile> Delete a profile
35
+
36
+ A profile is one (url, api_key) pair under a short name (dev, staging, prod).
37
+ Switch via 'pane config use', '--profile <name>', or the PANE_PROFILE env var.
38
+ The active profile's (url, api_key) is what every other command sees unless
39
+ overridden by --url / --api-key or PANE_URL / PANE_API_KEY.
40
+
41
+ Run \`pane config <verb> --help\` for verb-specific help. The full API key is
42
+ never printed; only a short masked prefix.
43
+
44
+ The config file lives at \${XDG_CONFIG_HOME:-~/.config}/pane/config.json
45
+ (mode 0600).`;
46
+ const showHelp = `pane config show — show the resolved relay config
8
47
 
9
48
  Usage:
10
49
  pane config show [options]
11
50
 
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.
51
+ Reports the (url, api_key) the CLI would use right now, and where each value
52
+ came from (flag / env / profile / none). Purely inspects flags + env + the
53
+ saved config file; makes NO network call.
17
54
 
18
- The API key is never printed in full: only a short masked prefix.
55
+ The API key is never printed in full only a short masked prefix.
19
56
 
20
57
  Options:
21
58
  --url <url> Relay base URL (overrides PANE_URL) — affects the report.
22
- --api-key <key> Agent API key (overrides PANE_API_KEY) — affects the
23
- report.
59
+ --api-key <key> Agent API key (overrides PANE_API_KEY) — affects the report.
60
+ --profile <name> Profile to resolve against — affects the report.
61
+ -h, --help Show this help.
62
+
63
+ Output (stdout, JSON):
64
+ {
65
+ url, url_source, flag | env | profile | none
66
+ key_prefix, key_source, flag | env | profile | none
67
+ profile, profile_source, active profile name + how it was chosen
68
+ config_path
69
+ }`;
70
+ const listHelp = `pane config list — list saved profiles
71
+
72
+ Usage:
73
+ pane config list [options]
74
+
75
+ Prints every profile in the local config file, with its URL and a masked
76
+ key prefix. The active profile carries 'current: true'.
77
+
78
+ Options:
24
79
  -h, --help Show this help.
25
80
 
26
81
  Output (stdout, JSON):
27
82
  {
28
- url, relay base URL, or null if unset
29
- url_source, "flag" | "env" | "store" | "none"
30
- key_prefix, first ~10 chars of the API key + "…", or null
31
- key_source, "flag" | "env" | "store" | "none"
32
- config_path absolute path to the CLI config file
83
+ current: <name|null>,
84
+ profiles: [ { name, url, key_prefix, current }, ],
85
+ config_path
33
86
  }`;
87
+ const useHelp = `pane config use <profile> — switch the active profile
88
+
89
+ Usage:
90
+ pane config use <profile>
91
+
92
+ Sets 'current_profile' in the config file. The named profile must exist
93
+ (create it first with 'pane agent register --profile <name>' or
94
+ 'pane config add <name>').
95
+
96
+ Options:
97
+ -h, --help Show this help.
98
+
99
+ Output (stdout, JSON):
100
+ { profile, saved_to }`;
101
+ const addHelp = `pane config add <profile> — add a profile manually
102
+
103
+ Usage:
104
+ pane config add <profile> --url <url> --api-key <api-key>
105
+
106
+ Saves a (url, api_key) pair under <profile> without contacting the relay.
107
+ Use this when an operator handed you an API key out of band (e.g. a closed-
108
+ registration relay) — for self-register and secret-mode relays, prefer
109
+ 'pane agent register --profile <name>'.
110
+
111
+ If <profile> already exists, the existing values are overwritten.
112
+
113
+ Options:
114
+ --url <url> Relay base URL. REQUIRED.
115
+ --api-key <key> Agent API key. REQUIRED.
116
+ -h, --help Show this help.
117
+
118
+ Output (stdout, JSON):
119
+ { profile, saved_to }
120
+
121
+ Does NOT change 'current_profile' unless this is the first profile being
122
+ added. Use 'pane config use' afterwards to switch.`;
123
+ const rmHelp = `pane config rm <profile> — delete a profile
124
+
125
+ Usage:
126
+ pane config rm <profile>
127
+
128
+ Removes the named profile from the config file. If it was the active profile,
129
+ 'current_profile' is cleared (the next command falls back to env / default
130
+ URL until another profile is selected via --profile or 'pane config use').
131
+
132
+ Options:
133
+ -h, --help Show this help.
134
+
135
+ Output (stdout, JSON):
136
+ { profile, was_current, path }`;
34
137
  async function runConfigShow(args) {
35
- assertKnownFlags(args, NO_FLAGS, NO_BOOLS, "pane config show");
138
+ assertKnownFlags(args, SHOW_FLAGS, SHOW_BOOLS, "pane config show");
36
139
  printJson(describeConfig(args));
37
140
  }
141
+ function maskKey(key) {
142
+ if (!key)
143
+ return null;
144
+ if (key.startsWith("pane_") && key.length >= 11)
145
+ return key.slice(0, 11) + "…";
146
+ return key.slice(0, 8) + "…";
147
+ }
148
+ async function runConfigList(args) {
149
+ assertKnownFlags(args, LIST_FLAGS, LIST_BOOLS, "pane config list");
150
+ const store = readStore();
151
+ const profiles = Object.entries(store.profiles)
152
+ .map(([name, p]) => ({
153
+ name,
154
+ url: p.url ?? null,
155
+ key_prefix: maskKey(p.apiKey),
156
+ current: name === store.currentProfile,
157
+ }))
158
+ .sort((a, b) => a.name.localeCompare(b.name));
159
+ printJson({
160
+ current: store.currentProfile ?? null,
161
+ profiles,
162
+ config_path: storePath(),
163
+ });
164
+ }
165
+ async function runConfigUse(args) {
166
+ assertKnownFlags(args, USE_FLAGS, USE_BOOLS, "pane config use");
167
+ const name = args.positionals[1];
168
+ if (!name) {
169
+ fail("missing profile name — usage: pane config use <profile>", "invalid_args");
170
+ }
171
+ let savedTo;
172
+ try {
173
+ savedTo = setCurrentProfile(name);
174
+ }
175
+ catch (e) {
176
+ fail(e instanceof Error ? e.message : String(e), "config_error");
177
+ }
178
+ printJson({ profile: name, saved_to: savedTo });
179
+ }
180
+ async function runConfigAdd(args) {
181
+ assertKnownFlags(args, ADD_FLAGS, ADD_BOOLS, "pane config add");
182
+ const name = args.positionals[1];
183
+ if (!name) {
184
+ fail("missing profile name — usage: pane config add <profile> --url <url> --api-key <key>", "invalid_args");
185
+ }
186
+ if (!isValidProfileName(name)) {
187
+ fail(`invalid profile name '${name}' — letters, digits, _ and -, up to 32 chars`, "invalid_args");
188
+ }
189
+ const url = args.flags.get("url");
190
+ const apiKey = args.flags.get("api-key");
191
+ if (!url) {
192
+ fail("--url is required — usage: pane config add <profile> --url <url> --api-key <key>", "invalid_args");
193
+ }
194
+ if (!apiKey) {
195
+ fail("--api-key is required — usage: pane config add <profile> --url <url> --api-key <key>", "invalid_args");
196
+ }
197
+ // setCurrent=false: adding a profile shouldn't silently switch the user
198
+ // off whatever they were on. They use `pane config use` after to flip.
199
+ // EXCEPT: if there's no current profile yet (first add), upsertProfile
200
+ // sets it automatically — that's the correct fresh-install behaviour.
201
+ const savedTo = upsertProfile(name, { url: url.replace(/\/$/, ""), apiKey }, false);
202
+ printJson({ profile: name, saved_to: savedTo });
203
+ }
204
+ async function runConfigRm(args) {
205
+ assertKnownFlags(args, RM_FLAGS, RM_BOOLS, "pane config rm");
206
+ const name = args.positionals[1];
207
+ if (!name) {
208
+ fail("missing profile name — usage: pane config rm <profile>", "invalid_args");
209
+ }
210
+ let result;
211
+ try {
212
+ result = removeProfile(name);
213
+ }
214
+ catch (e) {
215
+ fail(e instanceof Error ? e.message : String(e), "config_error");
216
+ }
217
+ printJson({
218
+ profile: name,
219
+ was_current: result.was_current,
220
+ path: result.path,
221
+ });
222
+ }
38
223
  export async function runConfig(args) {
39
224
  const verb = args.positionals[0];
225
+ // Per-verb --help: 'pane config show --help' etc. Caught before dispatch
226
+ // so each runner doesn't need to repeat the check.
227
+ if (args.bools.has("help") && verb !== undefined) {
228
+ const helps = {
229
+ show: showHelp,
230
+ list: listHelp,
231
+ use: useHelp,
232
+ add: addHelp,
233
+ rm: rmHelp,
234
+ };
235
+ if (helps[verb] !== undefined) {
236
+ process.stdout.write(helps[verb] + "\n");
237
+ return;
238
+ }
239
+ }
40
240
  switch (verb) {
41
241
  case "show":
42
242
  await runConfigShow(args);
43
243
  break;
244
+ case "list":
245
+ await runConfigList(args);
246
+ break;
247
+ case "use":
248
+ await runConfigUse(args);
249
+ break;
250
+ case "add":
251
+ await runConfigAdd(args);
252
+ break;
253
+ case "rm":
254
+ await runConfigRm(args);
255
+ break;
44
256
  case undefined:
45
- fail("missing verb — usage: pane config show (run 'pane config --help')", "invalid_args");
257
+ fail("missing verb — usage: pane config <show|list|use|add|rm> (run 'pane config --help')", "invalid_args");
46
258
  break;
47
259
  default:
48
- fail(`unknown config verb '${verb}' — expected show (run 'pane config --help')`, "invalid_args");
260
+ fail(`unknown config verb '${verb}' — expected show|list|use|add|rm (run 'pane config --help')`, "invalid_args");
49
261
  }
50
262
  }