@paneui/cli 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lalit Singh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @paneui/cli
2
+
3
+ Command-line client for the [Pane](https://github.com/aerolalit/paneui) relay:
4
+ hand a human a rich interactive UI by URL and capture their answer as structured
5
+ data — from any agent (cron job, chat bot, CI, headless server).
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install -g @paneui/cli
11
+ # or, no install:
12
+ npx @paneui/cli <command>
13
+ ```
14
+
15
+ The binary is `pane`.
16
+
17
+ ## Setup
18
+
19
+ ```sh
20
+ export PANE_URL=https://relay.paneui.com # or your self-hosted relay
21
+ pane register --name "my-agent" # provisions and saves an API key
22
+ ```
23
+
24
+ `pane register` writes the URL + API key to
25
+ `${XDG_CONFIG_HOME:-~/.config}/pane/config.json`. Subsequent commands need
26
+ only `PANE_URL` (or nothing) in the environment.
27
+
28
+ Override per-invocation with `--url <url>` and `--api-key <key>`.
29
+
30
+ ## Commands
31
+
32
+ ```
33
+ pane register Provision an agent API key and save it locally
34
+ pane create Create a session — returns session_id, urls, tokens
35
+ pane artifact Manage reusable, versioned artifacts
36
+ pane state <id> Non-blocking snapshot: metadata + event log
37
+ pane send <id> Emit an agent event into a session
38
+ pane watch <id> Stream a session's events as JSON-lines on stdout
39
+ pane delete <id> Close / delete a session
40
+ pane keys Inspect or revoke your agent's API key
41
+ pane config Show the resolved relay config (no network call)
42
+ pane logout Clear the locally-saved URL + API key
43
+ ```
44
+
45
+ Run `pane <command> --help` for command-specific options.
46
+
47
+ ## Output
48
+
49
+ stdout is machine-readable JSON. Errors go to stderr as
50
+ `{"error":{"code","message"}}` with a non-zero exit.
51
+
52
+ ```sh
53
+ SESSION=$(pane create --template form --schema ./q.json | jq -r .session_id)
54
+ pane watch "$SESSION" | jq 'select(.type == "human_response")'
55
+ ```
56
+
57
+ ## Links
58
+
59
+ - Repo: <https://github.com/aerolalit/paneui>
60
+ - Spec: <https://github.com/aerolalit/paneui/blob/main/docs/SPEC.md>
61
+ - License: MIT
package/dist/argv.js ADDED
@@ -0,0 +1,52 @@
1
+ // Tiny hand-rolled argv parser. No CLI framework.
2
+ //
3
+ // Supports:
4
+ // --flag value --flag=value --bool -h
5
+ // Everything that isn't a flag (or a flag's value) is a positional.
6
+ /** Thrown when a value-flag is given with no argument (e.g. trailing `--url`). */
7
+ export class ArgvError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "ArgvError";
11
+ }
12
+ }
13
+ /**
14
+ * Parse argv tokens. `booleanFlags` lists flags that never consume a value
15
+ * (e.g. --json, --once, --help); everything else with a `--name` form
16
+ * consumes the next token unless written as `--name=value`.
17
+ */
18
+ export function parseArgs(tokens, booleanFlags) {
19
+ const positionals = [];
20
+ const flags = new Map();
21
+ const bools = new Set();
22
+ for (let i = 0; i < tokens.length; i++) {
23
+ const tok = tokens[i];
24
+ if (tok === "-h" || tok === "--help") {
25
+ bools.add("help");
26
+ continue;
27
+ }
28
+ if (tok.startsWith("--")) {
29
+ const body = tok.slice(2);
30
+ const eq = body.indexOf("=");
31
+ if (eq !== -1) {
32
+ flags.set(body.slice(0, eq), body.slice(eq + 1));
33
+ continue;
34
+ }
35
+ if (booleanFlags.has(body)) {
36
+ bools.add(body);
37
+ continue;
38
+ }
39
+ const next = tokens[i + 1];
40
+ if (next === undefined || next.startsWith("--")) {
41
+ // A value-flag with no argument is a user error — don't silently
42
+ // demote it to a boolean (which hides the mistake).
43
+ throw new ArgvError(`--${body} requires a value`);
44
+ }
45
+ flags.set(body, next);
46
+ i++;
47
+ continue;
48
+ }
49
+ positionals.push(tok);
50
+ }
51
+ return { positionals, flags, bools };
52
+ }
@@ -0,0 +1,331 @@
1
+ // `pane artifact` — manage reusable, versioned artifacts.
2
+ //
3
+ // Flat command namespace: `artifact` is one top-level command that branches on
4
+ // a positional subcommand (create / version / update / search / list / show).
5
+ // An artifact is a reusable UI template (HTML + event schema + optional input
6
+ // schema); a session is one *use* of one version of it. Authoring an artifact
7
+ // once and instancing it via `pane create --artifact-id` removes the per-use
8
+ // cost of regenerating the same HTML.
9
+ import { createArtifactSchema, createArtifactVersionSchema, patchArtifactMetadataSchema, } from "@paneui/core";
10
+ import { makeClient } from "../config.js";
11
+ import { resolveJson, resolveText } from "../input.js";
12
+ import { printJson, fail, failFromError } from "../output.js";
13
+ export const artifactHelp = `pane artifact — manage reusable, versioned artifacts
14
+
15
+ An artifact is a reusable UI template: HTML + an event schema + an optional
16
+ input schema. A session is one use of one version of it. Author an artifact
17
+ once, then instance it many times with 'pane create --artifact-id <id|slug>'
18
+ instead of regenerating the HTML on every session.
19
+
20
+ Usage:
21
+ pane artifact <subcommand> [options]
22
+
23
+ Subcommands:
24
+ create Create a named, reusable artifact (its v1).
25
+ version Append a new version to an existing artifact.
26
+ update Update an artifact's head metadata (name/slug/description/tags).
27
+ search Search the agent's named artifacts (lean — no HTML).
28
+ list List the agent's named artifacts (search with no query).
29
+ show Show a full artifact: head metadata + its version list.
30
+
31
+ pane artifact create --name <n> --artifact <path|inline>
32
+ [--event-schema <path|json>] [--slug <s>]
33
+ [--description <d>] [--tags <t1,t2>]
34
+ [--input-schema <path|json>] [--artifact-type <t>]
35
+ Creates a named artifact. Prints { artifact_id, slug, version }.
36
+
37
+ pane artifact version <id|slug> --artifact <path|inline>
38
+ [--event-schema <path|json>]
39
+ [--input-schema <path|json>] [--artifact-type <t>]
40
+ Appends a new immutable version. Prints { artifact_id, version }.
41
+
42
+ pane artifact update <id|slug> [--name <n>] [--slug <s>]
43
+ [--description <d>] [--tags <t1,t2>]
44
+ Updates head metadata only (never the content). Prints the lean summary.
45
+
46
+ pane artifact search [query]
47
+ Text search over name + description + tags, ranked by last_used_at.
48
+ Prints an array of { id, slug, name, description, tags,
49
+ latest_version, last_used_at }.
50
+
51
+ pane artifact list
52
+ Alias of 'search' with no query — lists all the agent's artifacts.
53
+
54
+ pane artifact show <id|slug>
55
+ Prints the full artifact: head metadata + every version's content.
56
+
57
+ Options:
58
+ --name <n> Artifact display name (required for 'create').
59
+ --slug <s> Stable, agent-chosen handle (unique per agent). The
60
+ durable way to reference the artifact later.
61
+ --description <d> Prose: what the artifact is and does. Read by an agent
62
+ deciding whether to reuse it.
63
+ --tags <t1,t2,...> Comma-separated keywords for search.
64
+ --artifact <v> HTML artifact body — a file path, or inline HTML.
65
+ --event-schema <v> Event schema — a .json file path, or inline JSON.
66
+ Optional: omit for a view-only artifact (a
67
+ report/dashboard the human only views — no page/agent
68
+ events).
69
+
70
+ Shape — an object with an "events" map, keyed by event
71
+ type. Each entry declares who may emit it and the JSON
72
+ Schema for its payload:
73
+ {
74
+ "events": {
75
+ "form.submitted": {
76
+ "emittedBy": ["page"],
77
+ "payload": {
78
+ "type": "object",
79
+ "properties": { "answer": { "type": "string" } },
80
+ "required": ["answer"]
81
+ }
82
+ }
83
+ }
84
+ }
85
+ emittedBy is any non-empty subset of ["page", "agent"].
86
+ payload is a JSON Schema; the relay validates every
87
+ emit against it. See docs/SPEC.md for the full grammar.
88
+ --input-schema <v> JSON Schema for this artifact's per-session input_data —
89
+ a file path, or inline JSON. Optional.
90
+ --artifact-type <t> "html-inline" (default) or "html-ref".
91
+ --url <url> Relay base URL (overrides PANE_URL).
92
+ --api-key <key> Agent API key (overrides PANE_API_KEY).
93
+ -h, --help Show this help.
94
+
95
+ Output: stdout is machine-readable JSON.`;
96
+ /** Resolve --artifact-type, defaulting to html-inline. */
97
+ function resolveArtifactType(args) {
98
+ const t = (args.flags.get("artifact-type") ?? "html-inline");
99
+ if (t !== "html-inline" && t !== "html-ref") {
100
+ fail("--artifact-type must be 'html-inline' or 'html-ref'", "invalid_args");
101
+ }
102
+ return t;
103
+ }
104
+ /** Resolve the artifact HTML body (file or inline; verbatim URL for html-ref). */
105
+ function resolveSource(args, type) {
106
+ const artifactVal = args.flags.get("artifact");
107
+ if (!artifactVal)
108
+ fail("missing --artifact", "invalid_args");
109
+ try {
110
+ return type === "html-ref" ? artifactVal : resolveText(artifactVal);
111
+ }
112
+ catch (e) {
113
+ fail(e instanceof Error ? e.message : String(e), "invalid_args");
114
+ }
115
+ }
116
+ /**
117
+ * Resolve --event-schema — file path or inline JSON. --event-schema is
118
+ * optional: when absent this returns `undefined`, which makes a view-only
119
+ * artifact (no event vocabulary — the human only views it). The caller must
120
+ * omit `event_schema` from the request entirely when this returns `undefined`.
121
+ */
122
+ function resolveEventSchema(args) {
123
+ const schemaVal = args.flags.get("event-schema");
124
+ if (!schemaVal)
125
+ return undefined;
126
+ try {
127
+ return resolveJson(schemaVal, "--event-schema");
128
+ }
129
+ catch (e) {
130
+ fail(e instanceof Error ? e.message : String(e), "invalid_args");
131
+ }
132
+ }
133
+ /** Resolve the optional --input-schema — file path or inline JSON. */
134
+ function resolveInputSchema(args) {
135
+ const raw = args.flags.get("input-schema");
136
+ if (raw === undefined)
137
+ return undefined;
138
+ try {
139
+ const v = resolveJson(raw, "--input-schema");
140
+ if (v === null || typeof v !== "object" || Array.isArray(v)) {
141
+ fail("--input-schema must be a JSON object", "invalid_args");
142
+ }
143
+ return v;
144
+ }
145
+ catch (e) {
146
+ fail(e instanceof Error ? e.message : String(e), "invalid_args");
147
+ }
148
+ }
149
+ /** Parse a comma-separated --tags flag into a string array. */
150
+ function resolveTags(args) {
151
+ const raw = args.flags.get("tags");
152
+ if (raw === undefined)
153
+ return undefined;
154
+ const tags = raw
155
+ .split(",")
156
+ .map((t) => t.trim())
157
+ .filter((t) => t !== "");
158
+ return tags.length > 0 ? tags : undefined;
159
+ }
160
+ async function runArtifactCreate(args) {
161
+ const name = args.flags.get("name");
162
+ if (!name)
163
+ fail("missing --name", "invalid_args");
164
+ const type = resolveArtifactType(args);
165
+ const source = resolveSource(args, type);
166
+ const eventSchema = resolveEventSchema(args);
167
+ const inputSchema = resolveInputSchema(args);
168
+ const tags = resolveTags(args);
169
+ const slug = args.flags.get("slug");
170
+ const description = args.flags.get("description");
171
+ const candidate = {
172
+ name,
173
+ source,
174
+ type,
175
+ };
176
+ // event_schema is OMITTED entirely when --event-schema is absent — a view-only
177
+ // artifact. Setting it to `undefined` would still add the key.
178
+ if (eventSchema !== undefined)
179
+ candidate["event_schema"] = eventSchema;
180
+ if (slug !== undefined)
181
+ candidate["slug"] = slug;
182
+ if (description !== undefined)
183
+ candidate["description"] = description;
184
+ if (tags !== undefined)
185
+ candidate["tags"] = tags;
186
+ if (inputSchema !== undefined)
187
+ candidate["input_schema"] = inputSchema;
188
+ const parsed = createArtifactSchema.safeParse(candidate);
189
+ if (!parsed.success) {
190
+ const issue = parsed.error.issues[0];
191
+ const where = issue && issue.path.length > 0 ? issue.path.join(".") : "request";
192
+ fail(`invalid artifact: ${where}: ${issue ? issue.message : "validation failed"}`, "invalid_args", parsed.error.flatten());
193
+ }
194
+ const req = parsed.data;
195
+ const client = makeClient(args);
196
+ try {
197
+ const res = await client.createArtifact(req);
198
+ printJson({
199
+ artifact_id: res.artifact_id,
200
+ slug: slug ?? null,
201
+ version: res.version,
202
+ });
203
+ }
204
+ catch (e) {
205
+ failFromError(e);
206
+ }
207
+ }
208
+ async function runArtifactVersion(args) {
209
+ const idOrSlug = args.positionals[1];
210
+ if (!idOrSlug) {
211
+ fail("missing artifact <id|slug> — usage: pane artifact version <id|slug>", "invalid_args");
212
+ }
213
+ const type = resolveArtifactType(args);
214
+ const source = resolveSource(args, type);
215
+ const eventSchema = resolveEventSchema(args);
216
+ const inputSchema = resolveInputSchema(args);
217
+ const candidate = {
218
+ source,
219
+ type,
220
+ };
221
+ // event_schema is OMITTED entirely when --event-schema is absent — a view-only
222
+ // version. Setting it to `undefined` would still add the key.
223
+ if (eventSchema !== undefined)
224
+ candidate["event_schema"] = eventSchema;
225
+ if (inputSchema !== undefined)
226
+ candidate["input_schema"] = inputSchema;
227
+ const parsed = createArtifactVersionSchema.safeParse(candidate);
228
+ if (!parsed.success) {
229
+ const issue = parsed.error.issues[0];
230
+ const where = issue && issue.path.length > 0 ? issue.path.join(".") : "request";
231
+ fail(`invalid version: ${where}: ${issue ? issue.message : "validation failed"}`, "invalid_args", parsed.error.flatten());
232
+ }
233
+ const req = parsed.data;
234
+ const client = makeClient(args);
235
+ try {
236
+ const res = await client.createArtifactVersion(idOrSlug, req);
237
+ printJson({ artifact_id: res.artifact_id, version: res.version });
238
+ }
239
+ catch (e) {
240
+ failFromError(e);
241
+ }
242
+ }
243
+ async function runArtifactUpdate(args) {
244
+ const idOrSlug = args.positionals[1];
245
+ if (!idOrSlug) {
246
+ fail("missing artifact <id|slug> — usage: pane artifact update <id|slug>", "invalid_args");
247
+ }
248
+ const candidate = {};
249
+ const name = args.flags.get("name");
250
+ const slug = args.flags.get("slug");
251
+ const description = args.flags.get("description");
252
+ const tags = resolveTags(args);
253
+ if (name !== undefined)
254
+ candidate["name"] = name;
255
+ if (slug !== undefined)
256
+ candidate["slug"] = slug;
257
+ if (description !== undefined)
258
+ candidate["description"] = description;
259
+ if (tags !== undefined)
260
+ candidate["tags"] = tags;
261
+ if (Object.keys(candidate).length === 0) {
262
+ fail("nothing to update — pass at least one of --name / --slug / --description / --tags", "invalid_args");
263
+ }
264
+ const parsed = patchArtifactMetadataSchema.safeParse(candidate);
265
+ if (!parsed.success) {
266
+ const issue = parsed.error.issues[0];
267
+ const where = issue && issue.path.length > 0 ? issue.path.join(".") : "request";
268
+ fail(`invalid update: ${where}: ${issue ? issue.message : "validation failed"}`, "invalid_args", parsed.error.flatten());
269
+ }
270
+ const metadata = parsed.data;
271
+ const client = makeClient(args);
272
+ try {
273
+ const res = await client.updateArtifact(idOrSlug, metadata);
274
+ printJson(res);
275
+ }
276
+ catch (e) {
277
+ failFromError(e);
278
+ }
279
+ }
280
+ async function runArtifactSearch(args, query) {
281
+ const client = makeClient(args);
282
+ try {
283
+ const res = await client.searchArtifacts(query);
284
+ printJson(res);
285
+ }
286
+ catch (e) {
287
+ failFromError(e);
288
+ }
289
+ }
290
+ async function runArtifactShow(args) {
291
+ const idOrSlug = args.positionals[1];
292
+ if (!idOrSlug) {
293
+ fail("missing artifact <id|slug> — usage: pane artifact show <id|slug>", "invalid_args");
294
+ }
295
+ const client = makeClient(args);
296
+ try {
297
+ const res = await client.getArtifact(idOrSlug);
298
+ printJson(res);
299
+ }
300
+ catch (e) {
301
+ failFromError(e);
302
+ }
303
+ }
304
+ export async function runArtifact(args) {
305
+ const sub = args.positionals[0];
306
+ switch (sub) {
307
+ case "create":
308
+ await runArtifactCreate(args);
309
+ break;
310
+ case "version":
311
+ await runArtifactVersion(args);
312
+ break;
313
+ case "update":
314
+ await runArtifactUpdate(args);
315
+ break;
316
+ case "search":
317
+ await runArtifactSearch(args, args.positionals[1]);
318
+ break;
319
+ case "list":
320
+ await runArtifactSearch(args, undefined);
321
+ break;
322
+ case "show":
323
+ await runArtifactShow(args);
324
+ break;
325
+ case undefined:
326
+ fail("missing subcommand — usage: pane artifact <create|version|update|search|list|show> (run 'pane artifact --help')", "invalid_args");
327
+ break;
328
+ default:
329
+ fail(`unknown artifact subcommand '${sub}' — expected create|version|update|search|list|show (run 'pane artifact --help')`, "invalid_args");
330
+ }
331
+ }
@@ -0,0 +1,31 @@
1
+ // `pane config` — show the resolved relay config without a network call.
2
+ import { describeConfig } from "../config.js";
3
+ import { printJson } from "../output.js";
4
+ export const configHelp = `pane config — show the resolved relay config
5
+
6
+ Usage:
7
+ pane config [options]
8
+
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
+
13
+ The API key is never printed in full: only a short masked prefix.
14
+
15
+ Options:
16
+ --url <url> Relay base URL (overrides PANE_URL) — affects the report.
17
+ --api-key <key> Agent API key (overrides PANE_API_KEY) — affects the
18
+ report.
19
+ -h, --help Show this help.
20
+
21
+ Output (stdout, JSON):
22
+ {
23
+ url, relay base URL, or null if unset
24
+ url_source, "flag" | "env" | "store" | "none"
25
+ key_prefix, first ~10 chars of the API key + "…", or null
26
+ key_source, "flag" | "env" | "store" | "none"
27
+ config_path absolute path to the CLI config file
28
+ }`;
29
+ export async function runConfig(args) {
30
+ printJson(describeConfig(args));
31
+ }