@paneui/cli 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/argv.js +3 -3
- package/dist/commands/agent.js +10 -2
- package/dist/commands/attachment-token.js +2 -2
- package/dist/commands/attachment-upload.js +8 -10
- package/dist/commands/attachment.js +7 -7
- package/dist/commands/claim.js +1 -1
- package/dist/commands/config.js +232 -20
- package/dist/commands/create.js +132 -21
- package/dist/commands/delete.js +12 -12
- package/dist/commands/feedback.js +5 -5
- package/dist/commands/list.js +17 -17
- package/dist/commands/logout.js +43 -13
- package/dist/commands/participant.js +38 -38
- package/dist/commands/query.js +204 -0
- package/dist/commands/records.js +285 -0
- package/dist/commands/register.js +53 -15
- package/dist/commands/send.js +17 -17
- package/dist/commands/set-key.js +92 -0
- package/dist/commands/skill.js +1 -1
- package/dist/commands/state.js +12 -12
- package/dist/commands/taste.js +3 -3
- package/dist/commands/template-records.js +195 -0
- package/dist/commands/template.js +243 -35
- package/dist/commands/trash.js +102 -0
- package/dist/commands/watch.js +22 -22
- package/dist/config.js +87 -20
- package/dist/format.js +133 -0
- package/dist/index.js +97 -20
- package/dist/output.js +1 -1
- package/dist/store.js +167 -26
- package/dist/upgrade.js +1 -1
- package/dist/version.js +2 -2
- package/package.json +5 -3
- package/dist/commands/surface.js +0 -118
package/dist/config.js
CHANGED
|
@@ -1,16 +1,65 @@
|
|
|
1
1
|
// Relay connection config: PANE_URL / PANE_API_KEY from the environment,
|
|
2
|
-
// overridable per-invocation with --url / --api-key.
|
|
2
|
+
// overridable per-invocation with --url / --api-key. Profile selection via
|
|
3
|
+
// --profile / PANE_PROFILE picks WHICH (url, api_key) pair to load from
|
|
4
|
+
// the saved store. See store.ts for the on-disk layout.
|
|
3
5
|
import { PaneClient } from "@paneui/core";
|
|
4
6
|
import { fail } from "./output.js";
|
|
5
|
-
import { readStore, storePath } from "./store.js";
|
|
7
|
+
import { readStore, resolveProfile, storePath } from "./store.js";
|
|
6
8
|
import { VERSION } from "./version.js";
|
|
9
|
+
/**
|
|
10
|
+
* The hosted Pane relay. Used as the relay-URL fallback so a fresh user only
|
|
11
|
+
* needs an API key — `pane agent register` against the hosted relay, then go. A
|
|
12
|
+
* self-hoster overrides it with `--url` / `PANE_URL` / `pane agent register --url`.
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_RELAY_URL = "https://relay.paneui.com";
|
|
15
|
+
/**
|
|
16
|
+
* Pick the profile-selector source — explicit flag wins over env, env wins
|
|
17
|
+
* over the store's `current_profile`. Returns both the selector value and
|
|
18
|
+
* where it came from so `describeConfig` can report it.
|
|
19
|
+
*/
|
|
20
|
+
function pickProfileSelector(args) {
|
|
21
|
+
const flag = args.flags.get("profile");
|
|
22
|
+
if (flag !== undefined && flag !== "") {
|
|
23
|
+
return { selector: flag, source: "flag" };
|
|
24
|
+
}
|
|
25
|
+
const env = process.env.PANE_PROFILE;
|
|
26
|
+
if (env !== undefined && env !== "") {
|
|
27
|
+
return { selector: env, source: "env" };
|
|
28
|
+
}
|
|
29
|
+
return { selector: undefined, source: "none" };
|
|
30
|
+
}
|
|
7
31
|
/**
|
|
8
32
|
* Resolve url + apiKey and report the SOURCE of each, WITHOUT making a network
|
|
9
33
|
* call and WITHOUT failing on a missing value (unlike `resolveConfig`). The
|
|
10
34
|
* full API key is never returned — only a short, masked prefix.
|
|
35
|
+
*
|
|
36
|
+
* Resolution model:
|
|
37
|
+
* - `--url` / `PANE_URL` and `--api-key` / `PANE_API_KEY` are DIRECT values:
|
|
38
|
+
* they override everything, including any active profile. CI scripts that
|
|
39
|
+
* set those env vars never need to think about profiles.
|
|
40
|
+
* - Otherwise the profile selector (`--profile` flag → `PANE_PROFILE` env →
|
|
41
|
+
* store's `current_profile`) picks one profile out of the store; the
|
|
42
|
+
* selected profile's `url` and `api_key` are used.
|
|
43
|
+
* - Final fallback for URL is `DEFAULT_RELAY_URL`.
|
|
11
44
|
*/
|
|
12
45
|
export function describeConfig(args) {
|
|
13
46
|
const store = readStore();
|
|
47
|
+
const { selector, source: selectorSource } = pickProfileSelector(args);
|
|
48
|
+
// The store gets visited only if --profile flag is set (explicit
|
|
49
|
+
// selector) and the store has a matching profile, OR the store has a
|
|
50
|
+
// current_profile and no explicit selector overrides it. `resolveProfile`
|
|
51
|
+
// throws on a typo'd selector; we swallow that here so describeConfig
|
|
52
|
+
// can't crash a `pane config show` — resolveConfig() is the one that
|
|
53
|
+
// surfaces the error when the caller actually needs a key.
|
|
54
|
+
let active;
|
|
55
|
+
try {
|
|
56
|
+
active = resolveProfile(store, selector);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
active = null;
|
|
60
|
+
}
|
|
61
|
+
// URL precedence: --url flag > PANE_URL env > active profile's url.
|
|
62
|
+
// The default URL is shown only when nothing else is set.
|
|
14
63
|
let url = null;
|
|
15
64
|
let urlSource = "none";
|
|
16
65
|
if (args.flags.get("url")) {
|
|
@@ -21,10 +70,11 @@ export function describeConfig(args) {
|
|
|
21
70
|
url = process.env.PANE_URL;
|
|
22
71
|
urlSource = "env";
|
|
23
72
|
}
|
|
24
|
-
else if (
|
|
25
|
-
url =
|
|
26
|
-
urlSource = "
|
|
73
|
+
else if (active && active.profile.url) {
|
|
74
|
+
url = active.profile.url;
|
|
75
|
+
urlSource = "profile";
|
|
27
76
|
}
|
|
77
|
+
// API key precedence: --api-key flag > PANE_API_KEY env > active profile's api_key.
|
|
28
78
|
let apiKey = null;
|
|
29
79
|
let keySource = "none";
|
|
30
80
|
if (args.flags.get("api-key")) {
|
|
@@ -35,37 +85,46 @@ export function describeConfig(args) {
|
|
|
35
85
|
apiKey = process.env.PANE_API_KEY;
|
|
36
86
|
keySource = "env";
|
|
37
87
|
}
|
|
38
|
-
else if (
|
|
39
|
-
apiKey =
|
|
40
|
-
keySource = "
|
|
88
|
+
else if (active && active.profile.apiKey) {
|
|
89
|
+
apiKey = active.profile.apiKey;
|
|
90
|
+
keySource = "profile";
|
|
41
91
|
}
|
|
42
92
|
return {
|
|
43
93
|
url: url ? url.replace(/\/$/, "") : null,
|
|
44
94
|
url_source: urlSource,
|
|
45
95
|
key_prefix: apiKey ? apiKey.slice(0, 10) + "…" : null,
|
|
46
96
|
key_source: keySource,
|
|
97
|
+
profile: active ? active.name : null,
|
|
98
|
+
profile_source: selectorSource,
|
|
47
99
|
config_path: storePath(),
|
|
48
100
|
};
|
|
49
101
|
}
|
|
50
|
-
/**
|
|
51
|
-
* The hosted Pane relay. Used as the relay-URL fallback so a fresh user only
|
|
52
|
-
* needs an API key — `pane agent register` against the hosted relay, then go. A
|
|
53
|
-
* self-hoster overrides it with `--url` / `PANE_URL` / `pane agent register --url`.
|
|
54
|
-
*/
|
|
55
|
-
export const DEFAULT_RELAY_URL = "https://relay.paneui.com";
|
|
56
102
|
/**
|
|
57
103
|
* Resolve relay URL + API key. Precedence (highest first):
|
|
58
|
-
* url: --url flag → PANE_URL env
|
|
59
|
-
* apiKey: --api-key → PANE_API_KEY
|
|
60
|
-
*
|
|
104
|
+
* url: --url flag → PANE_URL env → active profile's url → DEFAULT_RELAY_URL
|
|
105
|
+
* apiKey: --api-key → PANE_API_KEY → active profile's api_key
|
|
106
|
+
* "Active profile" is chosen by `--profile` / `PANE_PROFILE` / the store's
|
|
107
|
+
* `current_profile`. A typo'd `--profile dev` fails fast with `config_error`
|
|
108
|
+
* — we never silently fall back to a different relay.
|
|
61
109
|
*/
|
|
62
110
|
export function resolveConfig(args) {
|
|
63
111
|
const store = readStore();
|
|
112
|
+
const { selector } = pickProfileSelector(args);
|
|
113
|
+
let active;
|
|
114
|
+
try {
|
|
115
|
+
active = resolveProfile(store, selector);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
fail(e instanceof Error ? e.message : String(e), "config_error");
|
|
119
|
+
}
|
|
64
120
|
const url = args.flags.get("url") ??
|
|
65
121
|
process.env.PANE_URL ??
|
|
66
|
-
|
|
122
|
+
active?.profile.url ??
|
|
67
123
|
DEFAULT_RELAY_URL;
|
|
68
|
-
const apiKey = args.flags.get("api-key") ??
|
|
124
|
+
const apiKey = args.flags.get("api-key") ??
|
|
125
|
+
process.env.PANE_API_KEY ??
|
|
126
|
+
active?.profile.apiKey ??
|
|
127
|
+
"";
|
|
69
128
|
if (!apiKey) {
|
|
70
129
|
fail("missing API key: set PANE_API_KEY, pass --api-key <key>, or run 'pane agent register'", "config_error");
|
|
71
130
|
}
|
|
@@ -90,9 +149,17 @@ export function makeClient(args) {
|
|
|
90
149
|
*/
|
|
91
150
|
export function resolveRelayUrl(args) {
|
|
92
151
|
const store = readStore();
|
|
152
|
+
const { selector } = pickProfileSelector(args);
|
|
153
|
+
let active;
|
|
154
|
+
try {
|
|
155
|
+
active = resolveProfile(store, selector);
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
fail(e instanceof Error ? e.message : String(e), "config_error");
|
|
159
|
+
}
|
|
93
160
|
const url = args.flags.get("url") ??
|
|
94
161
|
process.env.PANE_URL ??
|
|
95
|
-
|
|
162
|
+
active?.profile.url ??
|
|
96
163
|
DEFAULT_RELAY_URL;
|
|
97
164
|
return url.replace(/\/$/, "");
|
|
98
165
|
}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Human-readable formatter for `pane create` output.
|
|
2
|
+
//
|
|
3
|
+
// The CLI is JSON-first — that's how agents call it. But humans run it too:
|
|
4
|
+
// the agent dev iterating on a template, the operator smoke-testing a relay,
|
|
5
|
+
// the developer who fires `pane create` once a day to grab a URL and hand
|
|
6
|
+
// it to themselves on their phone. Dumping `{ pane_id, urls, tokens, ... }`
|
|
7
|
+
// at them is a downgrade in every case where the next step is "open the
|
|
8
|
+
// URL in a browser".
|
|
9
|
+
//
|
|
10
|
+
// In a TTY (and without `--json` on the CLI), this module renders:
|
|
11
|
+
// - the title prominently
|
|
12
|
+
// - each human URL on its own line, copy-friendly
|
|
13
|
+
// - a QR code for the first human URL, scannable from a phone
|
|
14
|
+
// - the expiry as a countdown ("in 1h 0m") + ISO timestamp
|
|
15
|
+
// - the agent stream URL on a dim line (less important for humans)
|
|
16
|
+
//
|
|
17
|
+
// Trust boundary: every interpolated value is a server response or a string
|
|
18
|
+
// the caller asked us to render. No HTML escaping needed — terminal output.
|
|
19
|
+
// We DO neutralise stray ANSI escape characters (a malicious title could
|
|
20
|
+
// otherwise inject colour codes); see stripAnsi.
|
|
21
|
+
import qrcode from "qrcode-terminal";
|
|
22
|
+
/** ANSI helpers. Only applied when writing to a TTY; harmless characters
|
|
23
|
+
* otherwise. We deliberately don't pull in a colour library — the CLI has
|
|
24
|
+
* one runtime dep today (qrcode-terminal) and we'd like to keep the
|
|
25
|
+
* pane area tight. */
|
|
26
|
+
const ANSI = {
|
|
27
|
+
reset: "\x1b[0m",
|
|
28
|
+
bold: "\x1b[1m",
|
|
29
|
+
dim: "\x1b[2m",
|
|
30
|
+
cyan: "\x1b[36m",
|
|
31
|
+
green: "\x1b[32m",
|
|
32
|
+
yellow: "\x1b[33m",
|
|
33
|
+
};
|
|
34
|
+
// Strip control chars from interpolated strings. Defends against a relay
|
|
35
|
+
// response (or, more realistically, an echoed title in some future field)
|
|
36
|
+
// carrying ANSI escapes that would otherwise change the user's terminal
|
|
37
|
+
// colour after our output ends.
|
|
38
|
+
// eslint-disable-next-line no-control-regex
|
|
39
|
+
const CTRL_RX = /[\x00-\x08\x0b-\x1f\x7f]/g;
|
|
40
|
+
function safe(s) {
|
|
41
|
+
return s.replace(CTRL_RX, "");
|
|
42
|
+
}
|
|
43
|
+
/** Generate the QR-code string for `text` using qrcode-terminal's `small`
|
|
44
|
+
* rendering (one terminal char per QR module, ~half the height of the
|
|
45
|
+
* default). Returns the multi-line string, ready to write to stdout. */
|
|
46
|
+
function qrToString(text) {
|
|
47
|
+
let out = "";
|
|
48
|
+
qrcode.generate(text, { small: true }, (s) => {
|
|
49
|
+
out = s;
|
|
50
|
+
});
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/** Human-friendly countdown from now to `iso`. Returns "in 1h 0m" /
|
|
54
|
+
* "in 45m" / "in 30s" / "expired". Stable enough to test as a string. */
|
|
55
|
+
export function humanCountdown(iso, nowMs = Date.now()) {
|
|
56
|
+
const delta = new Date(iso).getTime() - nowMs;
|
|
57
|
+
if (!Number.isFinite(delta) || delta <= 0)
|
|
58
|
+
return "expired";
|
|
59
|
+
const totalSec = Math.floor(delta / 1000);
|
|
60
|
+
const days = Math.floor(totalSec / 86400);
|
|
61
|
+
const hours = Math.floor((totalSec % 86400) / 3600);
|
|
62
|
+
const mins = Math.floor((totalSec % 3600) / 60);
|
|
63
|
+
const secs = totalSec % 60;
|
|
64
|
+
if (days > 0)
|
|
65
|
+
return `in ${days}d ${hours}h`;
|
|
66
|
+
if (hours > 0)
|
|
67
|
+
return `in ${hours}h ${mins}m`;
|
|
68
|
+
if (mins > 0)
|
|
69
|
+
return `in ${mins}m`;
|
|
70
|
+
return `in ${secs}s`;
|
|
71
|
+
}
|
|
72
|
+
/** Render the pane-created response for a human reader. Returns the
|
|
73
|
+
* full multi-line string; the caller writes it to stdout. */
|
|
74
|
+
export function formatPaneCreated(res, opts = {}) {
|
|
75
|
+
const c = opts.color ?? false;
|
|
76
|
+
const b = c ? ANSI.bold : "";
|
|
77
|
+
const d = c ? ANSI.dim : "";
|
|
78
|
+
const cy = c ? ANSI.cyan : "";
|
|
79
|
+
const g = c ? ANSI.green : "";
|
|
80
|
+
const r = c ? ANSI.reset : "";
|
|
81
|
+
const title = safe(res.title);
|
|
82
|
+
const paneId = safe(res.pane_id);
|
|
83
|
+
const expiresIn = humanCountdown(res.expires_at);
|
|
84
|
+
const expiresAt = safe(res.expires_at);
|
|
85
|
+
const humanUrls = res.urls.humans.map(safe);
|
|
86
|
+
const agentStream = safe(res.urls.agent_stream);
|
|
87
|
+
const lines = [];
|
|
88
|
+
// Header — "Pane created" vs. "Existing pane reused" if `created`
|
|
89
|
+
// is explicitly false. Dedup hits from #262 carry created=false and the
|
|
90
|
+
// human shouldn't think they made a fresh row.
|
|
91
|
+
const headline = res.created === false
|
|
92
|
+
? `${b}${cy}Existing pane reused${r}`
|
|
93
|
+
: `${b}${g}Pane pane created${r}`;
|
|
94
|
+
lines.push(headline);
|
|
95
|
+
lines.push("");
|
|
96
|
+
lines.push(` ${d}Title:${r} ${title}`);
|
|
97
|
+
lines.push(` ${d}Pane:${r} ${paneId}`);
|
|
98
|
+
lines.push(` ${d}Expires:${r} ${expiresIn} ${d}(${expiresAt})${r}`);
|
|
99
|
+
if (res.context_key) {
|
|
100
|
+
lines.push(` ${d}Key:${r} ${safe(res.context_key)}`);
|
|
101
|
+
}
|
|
102
|
+
lines.push("");
|
|
103
|
+
if (humanUrls.length === 0) {
|
|
104
|
+
// Dedup-on-existing-pane path doesn't re-mint human URLs. Pane
|
|
105
|
+
// the situation explicitly rather than rendering a blank section.
|
|
106
|
+
lines.push(`${d}No human URLs minted on this response — fetch them with ` +
|
|
107
|
+
`\`pane participants ${paneId}\`.${r}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const label = humanUrls.length === 1 ? "Open this link" : "Open these links";
|
|
111
|
+
lines.push(`${label} in a browser:`);
|
|
112
|
+
lines.push("");
|
|
113
|
+
for (const u of humanUrls) {
|
|
114
|
+
lines.push(` ${b}${u}${r}`);
|
|
115
|
+
}
|
|
116
|
+
lines.push("");
|
|
117
|
+
// Show a QR for the first URL — scannable from a phone. The other
|
|
118
|
+
// URLs (if any) are visible above; one QR keeps the output compact.
|
|
119
|
+
lines.push(`Or scan this QR code with your phone:`);
|
|
120
|
+
lines.push("");
|
|
121
|
+
const qr = qrToString(humanUrls[0]);
|
|
122
|
+
// Indent each QR line by two spaces so it sits inside the same gutter
|
|
123
|
+
// as the rest of the body.
|
|
124
|
+
for (const ln of qr.split("\n")) {
|
|
125
|
+
if (ln.length === 0)
|
|
126
|
+
continue;
|
|
127
|
+
lines.push(` ${ln}`);
|
|
128
|
+
}
|
|
129
|
+
lines.push("");
|
|
130
|
+
}
|
|
131
|
+
lines.push(`${d}Agent stream:${r} ${agentStream}`);
|
|
132
|
+
return lines.join("\n") + "\n";
|
|
133
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// behind the shape and the rename from the older flat layout.
|
|
7
7
|
//
|
|
8
8
|
// Config: PANE_URL and PANE_API_KEY (env), overridable with --url / --api-key.
|
|
9
|
+
// Multiple environments live as named profiles in
|
|
10
|
+
// $XDG_CONFIG_HOME/pane/config.json; pick one with --profile or PANE_PROFILE.
|
|
9
11
|
// Output is JSON by default. Every noun self-documents via --help.
|
|
10
12
|
import { parseArgs, ArgvError } from "./argv.js";
|
|
11
13
|
/**
|
|
@@ -23,8 +25,14 @@ function failArgvError(e) {
|
|
|
23
25
|
process.stderr.write(JSON.stringify({ error }) + "\n");
|
|
24
26
|
process.exit(1);
|
|
25
27
|
}
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
+
import { runCreate, createHelp } from "./commands/create.js";
|
|
29
|
+
import { runList, listHelp } from "./commands/list.js";
|
|
30
|
+
import { runState, stateHelp } from "./commands/state.js";
|
|
31
|
+
import { runSend, sendHelp } from "./commands/send.js";
|
|
32
|
+
import { runWatch, watchHelp } from "./commands/watch.js";
|
|
33
|
+
import { runDelete, deleteHelp } from "./commands/delete.js";
|
|
34
|
+
import { runParticipant, participantHelp } from "./commands/participant.js";
|
|
35
|
+
import { runTemplate, artifactHelp } from "./commands/template.js";
|
|
28
36
|
import { runAgent, agentHelp } from "./commands/agent.js";
|
|
29
37
|
import { runKey, keyHelp } from "./commands/key.js";
|
|
30
38
|
import { runTaste, tasteHelp } from "./commands/taste.js";
|
|
@@ -32,20 +40,35 @@ import { runFeedback, feedbackHelp } from "./commands/feedback.js";
|
|
|
32
40
|
import { runConfig, configHelp } from "./commands/config.js";
|
|
33
41
|
import { runBlob, blobHelp } from "./commands/attachment.js";
|
|
34
42
|
import { runSkill, skillHelp } from "./commands/skill.js";
|
|
43
|
+
import { runRecords, recordsHelp } from "./commands/records.js";
|
|
44
|
+
import { runTemplateRecords, templateRecordsHelp, } from "./commands/template-records.js";
|
|
45
|
+
import { runQuery, queryHelp } from "./commands/query.js";
|
|
46
|
+
import { runTrash, trashHelp } from "./commands/trash.js";
|
|
35
47
|
import { VERSION } from "./version.js";
|
|
36
48
|
import { PaneApiError } from "@paneui/core";
|
|
37
49
|
import { failUpgradeRequired } from "./output.js";
|
|
38
50
|
const ROOT_HELP = `pane — a round-trip UI channel between agents and humans
|
|
39
51
|
|
|
40
52
|
Usage:
|
|
41
|
-
pane <
|
|
53
|
+
pane <command> [options]
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
Pane commands (operate on the core noun — a live UI channel):
|
|
56
|
+
create Create a pane (POST /v1/panes). Prints pane_id, urls,
|
|
57
|
+
tokens, expires_at.
|
|
58
|
+
list Enumerate YOUR agent's panes.
|
|
59
|
+
show <id> Non-blocking snapshot: pane metadata + event log.
|
|
60
|
+
send <id> Emit an agent event into a pane.
|
|
61
|
+
watch <id> Stream a pane's events as JSON-lines on stdout.
|
|
62
|
+
delete <id> Close/delete a pane (DELETE /v1/panes/:id).
|
|
63
|
+
participant Manage participant URLs on an existing pane
|
|
64
|
+
<list|new|revoke> (list | mint a fresh URL | revoke one URL).
|
|
65
|
+
|
|
66
|
+
Other noun groups:
|
|
47
67
|
template Reusable, versioned UI templates
|
|
48
68
|
(create | version | update | search | list | show | delete).
|
|
69
|
+
template-records Owner-curated content scoped to a Template head
|
|
70
|
+
(list | get | upsert | update | delete), visible to
|
|
71
|
+
every pane derived from any version of the template.
|
|
49
72
|
key YOUR agent's API key (list | revoke).
|
|
50
73
|
taste YOUR agent's freeform UI taste notes
|
|
51
74
|
(get | set | clear) — presentation preferences the agent
|
|
@@ -53,28 +76,38 @@ Nouns:
|
|
|
53
76
|
generating a pane template.
|
|
54
77
|
feedback One-shot feedback to the relay operator
|
|
55
78
|
(create | list) — bug reports, feature requests, notes.
|
|
56
|
-
attachment
|
|
57
|
-
delete | token <mint|revoke|list>).
|
|
58
|
-
an agent, a
|
|
59
|
-
from event payloads + input_data via
|
|
79
|
+
attachment Binary attachments (upload | download | show | list |
|
|
80
|
+
delete | token <mint|revoke|list>). Attachments are
|
|
81
|
+
scoped to an agent, a pane, or a template, and can be
|
|
82
|
+
referenced from event payloads + input_data via
|
|
60
83
|
\`format: pane-attachment-id\`.
|
|
61
84
|
agent Agent identity on this machine (register | logout).
|
|
62
85
|
config CLI config inspection (show).
|
|
63
86
|
skill The relay's SKILL.md (show | version) — auto-updating;
|
|
64
87
|
no API key required.
|
|
88
|
+
trash Manage soft-deleted panes / templates
|
|
89
|
+
(list | restore | restore-template | purge | purge-template).
|
|
90
|
+
query Run read-only SQL over your scoped panes / records /
|
|
91
|
+
events. JSON / CSV / TSV / table output.
|
|
65
92
|
|
|
66
|
-
Run \`pane <
|
|
93
|
+
Run \`pane <command> --help\` for command-specific options.
|
|
67
94
|
|
|
68
95
|
Config:
|
|
69
96
|
PANE_URL Relay base URL. Override: --url <url>
|
|
70
97
|
PANE_API_KEY Agent API key. Override: --api-key <key>
|
|
98
|
+
PANE_PROFILE Active profile name. Override: --profile <name>
|
|
71
99
|
'pane agent register' provisions the API key and saves it (with the URL) to
|
|
72
|
-
\${XDG_CONFIG_HOME:-~/.config}/pane/config.json
|
|
73
|
-
|
|
100
|
+
\${XDG_CONFIG_HOME:-~/.config}/pane/config.json under a named profile —
|
|
101
|
+
afterwards commands need no env vars. Manage multiple environments
|
|
102
|
+
(dev/staging/prod) with 'pane config list / use / add / rm'.
|
|
74
103
|
|
|
75
104
|
Global flags:
|
|
76
105
|
-h, --help Show help.
|
|
77
106
|
-v, --version Print version.
|
|
107
|
+
--profile <name> Pick a saved profile for this invocation (overrides
|
|
108
|
+
PANE_PROFILE and the saved 'current_profile').
|
|
109
|
+
--url <url> Relay base URL — bypasses profile selection entirely.
|
|
110
|
+
--api-key <key> Agent API key — bypasses profile selection entirely.
|
|
78
111
|
|
|
79
112
|
Output: stdout is machine-readable JSON; errors go to stderr as
|
|
80
113
|
{"error":{"code","message"}} with a non-zero exit.`;
|
|
@@ -85,7 +118,7 @@ Output: stdout is machine-readable JSON; errors go to stderr as
|
|
|
85
118
|
//
|
|
86
119
|
// `version` is deliberately NOT here: the top-level `-v` / `--version` is
|
|
87
120
|
// handled from rawArgv[0] before parseArgs runs, so it never needs to be a
|
|
88
|
-
// boolean flag — and keeping it out lets `pane
|
|
121
|
+
// boolean flag — and keeping it out lets `pane create --version <n>` /
|
|
89
122
|
// `pane template version` consume a value as a normal value-flag.
|
|
90
123
|
const BOOLEAN_FLAGS = new Set([
|
|
91
124
|
"json",
|
|
@@ -122,7 +155,15 @@ async function main() {
|
|
|
122
155
|
throw e;
|
|
123
156
|
}
|
|
124
157
|
const helps = {
|
|
125
|
-
surface
|
|
158
|
+
// Top-level pane verbs (formerly `pane surface <verb>` — see #163 follow-up).
|
|
159
|
+
create: createHelp,
|
|
160
|
+
list: listHelp,
|
|
161
|
+
show: stateHelp,
|
|
162
|
+
send: sendHelp,
|
|
163
|
+
watch: watchHelp,
|
|
164
|
+
delete: deleteHelp,
|
|
165
|
+
participant: participantHelp,
|
|
166
|
+
// Other noun groups.
|
|
126
167
|
template: artifactHelp,
|
|
127
168
|
key: keyHelp,
|
|
128
169
|
taste: tasteHelp,
|
|
@@ -131,6 +172,10 @@ async function main() {
|
|
|
131
172
|
agent: agentHelp,
|
|
132
173
|
config: configHelp,
|
|
133
174
|
skill: skillHelp,
|
|
175
|
+
records: recordsHelp,
|
|
176
|
+
"template-records": templateRecordsHelp,
|
|
177
|
+
query: queryHelp,
|
|
178
|
+
trash: trashHelp,
|
|
134
179
|
};
|
|
135
180
|
if (!(noun in helps)) {
|
|
136
181
|
process.stderr.write(JSON.stringify({
|
|
@@ -142,7 +187,7 @@ async function main() {
|
|
|
142
187
|
process.exit(1);
|
|
143
188
|
}
|
|
144
189
|
// `pane <noun> --help` with no verb prints the noun-level help. A verb-level
|
|
145
|
-
// --help is the responsibility of each runner (e.g.
|
|
190
|
+
// --help is the responsibility of each runner (e.g. runPane dispatches to
|
|
146
191
|
// the verb runner which reads its own xxxHelp). This pre-empt only fires
|
|
147
192
|
// when --help is the FIRST positional-equivalent — i.e. no verb given.
|
|
148
193
|
if (args.bools.has("help") && args.positionals.length === 0) {
|
|
@@ -150,11 +195,31 @@ async function main() {
|
|
|
150
195
|
return;
|
|
151
196
|
}
|
|
152
197
|
switch (noun) {
|
|
153
|
-
|
|
154
|
-
|
|
198
|
+
// Top-level pane verbs.
|
|
199
|
+
case "create":
|
|
200
|
+
await runCreate(args);
|
|
201
|
+
break;
|
|
202
|
+
case "list":
|
|
203
|
+
await runList(args);
|
|
204
|
+
break;
|
|
205
|
+
case "show":
|
|
206
|
+
await runState(args);
|
|
207
|
+
break;
|
|
208
|
+
case "send":
|
|
209
|
+
await runSend(args);
|
|
210
|
+
break;
|
|
211
|
+
case "watch":
|
|
212
|
+
await runWatch(args);
|
|
213
|
+
break;
|
|
214
|
+
case "delete":
|
|
215
|
+
await runDelete(args);
|
|
155
216
|
break;
|
|
217
|
+
case "participant":
|
|
218
|
+
await runParticipant(args);
|
|
219
|
+
break;
|
|
220
|
+
// Other noun groups.
|
|
156
221
|
case "template":
|
|
157
|
-
await
|
|
222
|
+
await runTemplate(args);
|
|
158
223
|
break;
|
|
159
224
|
case "key":
|
|
160
225
|
await runKey(args);
|
|
@@ -177,6 +242,18 @@ async function main() {
|
|
|
177
242
|
case "skill":
|
|
178
243
|
await runSkill(args);
|
|
179
244
|
break;
|
|
245
|
+
case "records":
|
|
246
|
+
await runRecords(args);
|
|
247
|
+
break;
|
|
248
|
+
case "template-records":
|
|
249
|
+
await runTemplateRecords(args);
|
|
250
|
+
break;
|
|
251
|
+
case "query":
|
|
252
|
+
await runQuery(args);
|
|
253
|
+
break;
|
|
254
|
+
case "trash":
|
|
255
|
+
await runTrash(args);
|
|
256
|
+
break;
|
|
180
257
|
}
|
|
181
258
|
}
|
|
182
259
|
main().catch((err) => {
|
package/dist/output.js
CHANGED
|
@@ -8,7 +8,7 @@ export function printJson(value) {
|
|
|
8
8
|
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Print a single compact JSON line to stdout and flush. Used by `pane
|
|
11
|
+
* Print a single compact JSON line to stdout and flush. Used by `pane watch`
|
|
12
12
|
* so a pipe-reader (e.g. Claude Code's Monitor tool) sees each event
|
|
13
13
|
* immediately, one event per line.
|
|
14
14
|
*/
|