@paneui/cli 0.0.1 → 0.0.3

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.
@@ -58,7 +58,10 @@ Options:
58
58
  the --artifact value is treated as a URL. Note: the relay
59
59
  does not serve "html-ref" artifacts in this release and
60
60
  will reject the session — use "html-inline".
61
- --ttl <seconds> Session time-to-live in seconds.
61
+ --ttl <seconds> Session time-to-live in seconds. The relay clamps this
62
+ to its configured MAX_TTL_SECONDS (defaults: 1 h
63
+ requested, 24 h max for self-host; hosted may differ).
64
+ The returned \`expires_at\` is the authoritative value.
62
65
  --participants <n> Number of human participants (default 1).
63
66
  --metadata <path|json> Arbitrary metadata object (file path or inline JSON).
64
67
  --callback <path|json> Webhook callback config: { url, events[], secret }.
@@ -0,0 +1,148 @@
1
+ // `pane taste` — read / write / clear the calling agent's freeform "taste
2
+ // notes" markdown blob.
3
+ //
4
+ // Taste notes are presentation preferences the agent has learned from human
5
+ // feedback ("denser layout", "no rounded corners", "use a dark header") — the
6
+ // kind of guidance that should outlive a single session. The intended loop:
7
+ //
8
+ // 1. Before generating a pane artifact, run `pane taste get` and feed the
9
+ // `taste` field into the prompt so prior preferences shape the output.
10
+ // 2. When the human gives new presentation feedback, run `pane taste get`,
11
+ // merge the feedback into the existing notes IN THE PROMPT, then call
12
+ // `pane taste set` with the WHOLE new blob (the relay does whole-blob
13
+ // replace, not append — that's deliberate, so the notes can't grow
14
+ // unbounded into noise).
15
+ //
16
+ // Keep taste notes about *presentation/UI taste only* — colours, density,
17
+ // component preferences. Project context, todos, and per-session state belong
18
+ // somewhere else. Today the blob is keyed by the agent's API key (per-agent);
19
+ // when pane gains first-class humans, this may move to per-human.
20
+ import { readFileSync } from "node:fs";
21
+ import { makeClient } from "../config.js";
22
+ import { printJson, fail, failFromError } from "../output.js";
23
+ export const tasteHelp = `pane taste — read / write / clear YOUR agent's UI taste notes
24
+
25
+ Taste notes are a small markdown blob storing presentation preferences your
26
+ agent has picked up from human feedback ("denser table", "no rounded corners",
27
+ "use a dark header"). Read them before generating a pane artifact so prior
28
+ feedback shapes the output; rewrite them whenever the human gives new
29
+ presentation feedback. Keep entries about UI/presentation taste only — not
30
+ project context, todos, or session state.
31
+
32
+ Usage:
33
+ pane taste <subcommand> [options]
34
+
35
+ Subcommands:
36
+ get Print the current notes blob:
37
+ { taste: string|null, updated_at: string|null, bytes: number }.
38
+ taste is null and bytes is 0 when notes have never been written.
39
+
40
+ set Whole-blob replace. Read markdown from stdin OR --file <path>
41
+ (exactly one). The relay rejects empty/whitespace-only payloads
42
+ and caps the blob at MAX_TASTE_BYTES (utf8). To clear the notes,
43
+ use 'pane taste clear', not 'set' with an empty body.
44
+
45
+ clear Delete the notes. Requires --yes (it is destructive). Prints
46
+ { cleared: true }.
47
+
48
+ Options:
49
+ --file <path> Read 'set' input from <path> (otherwise reads stdin).
50
+ --yes Confirm 'clear'.
51
+ --url <url> Relay base URL (overrides PANE_URL).
52
+ --api-key <key> Agent API key (overrides PANE_API_KEY).
53
+ -h, --help Show this help.
54
+
55
+ Examples:
56
+ pane taste get
57
+ echo "- denser layout\\n- no rounded corners" | pane taste set
58
+ pane taste set --file ./taste.md
59
+ pane taste clear --yes
60
+
61
+ Output: stdout is machine-readable JSON.`;
62
+ // Drain process.stdin to a utf8 string. Resolves to "" when stdin is a TTY
63
+ // (i.e. nothing was piped in), so the caller can detect "no input" and emit a
64
+ // proper error instead of hanging forever waiting for keystrokes.
65
+ async function readStdin() {
66
+ if (process.stdin.isTTY)
67
+ return "";
68
+ const chunks = [];
69
+ for await (const chunk of process.stdin) {
70
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
71
+ }
72
+ return Buffer.concat(chunks).toString("utf8");
73
+ }
74
+ async function runTasteGet(args) {
75
+ const client = makeClient(args);
76
+ try {
77
+ const info = await client.getTaste();
78
+ printJson(info);
79
+ }
80
+ catch (e) {
81
+ failFromError(e);
82
+ }
83
+ }
84
+ async function runTasteSet(args) {
85
+ const filePath = args.flags.get("file");
86
+ const hasStdin = !process.stdin.isTTY;
87
+ if (filePath !== undefined && hasStdin) {
88
+ fail("'pane taste set' takes input from EITHER stdin OR --file <path>, not both", "invalid_args");
89
+ }
90
+ if (filePath === undefined && !hasStdin) {
91
+ fail("'pane taste set' needs input — pipe markdown on stdin or pass --file <path>", "invalid_args");
92
+ }
93
+ let taste;
94
+ if (filePath !== undefined) {
95
+ try {
96
+ taste = readFileSync(filePath, "utf8");
97
+ }
98
+ catch (e) {
99
+ fail(`failed to read --file '${filePath}': ${e instanceof Error ? e.message : String(e)}`, "invalid_args");
100
+ }
101
+ }
102
+ else {
103
+ taste = await readStdin();
104
+ }
105
+ if (taste.trim().length === 0) {
106
+ fail("'pane taste set' refuses an empty or whitespace-only blob — use 'pane taste clear --yes' to delete the notes", "invalid_args");
107
+ }
108
+ const client = makeClient(args);
109
+ try {
110
+ const info = await client.setTaste(taste);
111
+ printJson(info);
112
+ }
113
+ catch (e) {
114
+ failFromError(e);
115
+ }
116
+ }
117
+ async function runTasteClear(args) {
118
+ if (!args.bools.has("yes")) {
119
+ fail("'pane taste clear' deletes YOUR agent's taste notes — it is destructive. Pass --yes to confirm.", "confirmation_required");
120
+ }
121
+ const client = makeClient(args);
122
+ try {
123
+ await client.clearTaste();
124
+ printJson({ cleared: true });
125
+ }
126
+ catch (e) {
127
+ failFromError(e);
128
+ }
129
+ }
130
+ export async function runTaste(args) {
131
+ const sub = args.positionals[0];
132
+ switch (sub) {
133
+ case "get":
134
+ await runTasteGet(args);
135
+ break;
136
+ case "set":
137
+ await runTasteSet(args);
138
+ break;
139
+ case "clear":
140
+ await runTasteClear(args);
141
+ break;
142
+ case undefined:
143
+ fail("missing subcommand — usage: pane taste <get|set|clear> (run 'pane taste --help')", "invalid_args");
144
+ break;
145
+ default:
146
+ fail(`unknown taste subcommand '${sub}' — expected get|set|clear (run 'pane taste --help')`, "invalid_args");
147
+ }
148
+ }
package/dist/index.js CHANGED
@@ -13,8 +13,9 @@ import { runArtifact, artifactHelp } from "./commands/artifact.js";
13
13
  import { runConfig, configHelp } from "./commands/config.js";
14
14
  import { runLogout, logoutHelp } from "./commands/logout.js";
15
15
  import { runKeys, keysHelp } from "./commands/keys.js";
16
+ import { runTaste, tasteHelp } from "./commands/taste.js";
16
17
  import { runDelete, deleteHelp } from "./commands/delete.js";
17
- const VERSION = "0.0.1";
18
+ const VERSION = "0.0.3";
18
19
  const ROOT_HELP = `pane — a round-trip UI channel between agents and humans
19
20
 
20
21
  Usage:
@@ -33,6 +34,10 @@ Commands:
33
34
  (long-lived; the building block for pipe-readers).
34
35
  delete <id> Close/delete a session (DELETE /v1/sessions/:id).
35
36
  keys Inspect or revoke YOUR agent's API key (list / revoke).
37
+ taste Read / write / clear YOUR agent's UI taste notes
38
+ (get / set / clear) — presentation preferences the agent
39
+ has learned from human feedback and reads before
40
+ generating a pane artifact.
36
41
  config Show the resolved relay config (no network call).
37
42
  logout Clear the locally-saved relay URL + API key.
38
43
 
@@ -99,6 +104,7 @@ async function main() {
99
104
  watch: watchHelp,
100
105
  delete: deleteHelp,
101
106
  keys: keysHelp,
107
+ taste: tasteHelp,
102
108
  config: configHelp,
103
109
  logout: logoutHelp,
104
110
  };
@@ -140,6 +146,9 @@ async function main() {
140
146
  case "keys":
141
147
  await runKeys(args);
142
148
  break;
149
+ case "taste":
150
+ await runTaste(args);
151
+ break;
143
152
  case "config":
144
153
  await runConfig(args);
145
154
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paneui/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Command-line client for the Pane relay: create sessions, inspect state, send and watch events.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  "test:unit": "vitest run"
42
42
  },
43
43
  "dependencies": {
44
- "@paneui/core": "^0.0.1"
44
+ "@paneui/core": "^0.0.3"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^22.7.0",