@paneui/cli 0.0.17 → 0.0.19
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 +14 -0
- package/dist/commands/create.js +1 -1
- package/dist/commands/demo-artifact.js +189 -72
- package/dist/commands/demo.js +1 -1
- package/dist/commands/records.js +27 -2
- package/dist/commands/template-records.js +24 -2
- package/dist/commands/template.js +58 -3
- package/dist/commands/update.js +194 -0
- package/dist/index.js +20 -2
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -14,6 +14,19 @@ npx @paneui/cli <command>
|
|
|
14
14
|
|
|
15
15
|
The binary is `pane`.
|
|
16
16
|
|
|
17
|
+
## Try it
|
|
18
|
+
|
|
19
|
+
Register once, then `pane demo` spins up a short-lived sample pane on the hosted
|
|
20
|
+
relay, opens it in your browser, and prints the structured event back in your
|
|
21
|
+
terminal the moment you interact (the demo pane is cleaned up on exit):
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npx @paneui/cli agent register --name "my-agent" # one-time, hosted relay
|
|
25
|
+
npx @paneui/cli demo # Node 20+ — round-trip in ~60s
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Add `--no-open` on a headless / SSH box and it just prints the URL.
|
|
29
|
+
|
|
17
30
|
## Setup
|
|
18
31
|
|
|
19
32
|
```sh
|
|
@@ -32,6 +45,7 @@ Override per-invocation with `--url <url>` and `--api-key <key>`.
|
|
|
32
45
|
Uniform `pane <noun> <verb> [options]`:
|
|
33
46
|
|
|
34
47
|
```
|
|
48
|
+
pane demo Zero-setup guided tour — see the round-trip live
|
|
35
49
|
pane agent register Provision an agent API key and save it locally
|
|
36
50
|
pane agent logout Clear the locally-saved URL + API key
|
|
37
51
|
pane create Create a pane — returns pane_id, urls, tokens
|
package/dist/commands/create.js
CHANGED
|
@@ -198,7 +198,7 @@ Options:
|
|
|
198
198
|
the shell band above the iframe — "who is asking, why".
|
|
199
199
|
Max 280 chars after trim; a single \\n is allowed for a
|
|
200
200
|
two-line message; other control chars are rejected. Pass
|
|
201
|
-
this whenever the
|
|
201
|
+
this whenever the pane itself doesn't make the
|
|
202
202
|
request self-explanatory.
|
|
203
203
|
--input-data <v> This instance's seed data — a JSON object (file path or
|
|
204
204
|
inline JSON), validated by the relay against the template
|
|
@@ -96,47 +96,127 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
96
96
|
<head>
|
|
97
97
|
<meta charset="utf-8" />
|
|
98
98
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
99
|
+
<meta name="color-scheme" content="light dark" />
|
|
99
100
|
<title>Pane — the 60-second tour</title>
|
|
100
101
|
<style>
|
|
101
102
|
:root {
|
|
102
|
-
--bg: #
|
|
103
|
-
--
|
|
104
|
-
--
|
|
105
|
-
--
|
|
106
|
-
--
|
|
107
|
-
--
|
|
108
|
-
--
|
|
109
|
-
--
|
|
103
|
+
--bg: #f7f5f1;
|
|
104
|
+
--bg-soft: #efece5;
|
|
105
|
+
--panel: #ffffff;
|
|
106
|
+
--panel-2: #faf8f4;
|
|
107
|
+
--border: #e6e0d6;
|
|
108
|
+
--ink: #1a1726;
|
|
109
|
+
--muted: #5b5570;
|
|
110
|
+
--faint: #8b85a0;
|
|
111
|
+
--agent: #D97757;
|
|
112
|
+
--human: #E0A23A;
|
|
113
|
+
--accent: #D97757;
|
|
114
|
+
--accent-2: #E0A23A;
|
|
115
|
+
--code: #f3f0ea;
|
|
110
116
|
--radius: 14px;
|
|
111
117
|
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
|
112
118
|
Arial, sans-serif;
|
|
113
119
|
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
120
|
+
/* scheme-dependent depth + accent tints (overridden in dark mode) */
|
|
121
|
+
--glow: radial-gradient(120vw 78vh at 50% -12%,
|
|
122
|
+
rgba(217, 119, 87, 0.10), rgba(224, 162, 58, 0.04) 46%, transparent 72%);
|
|
123
|
+
--shadow-stage: 0 14px 38px rgba(26, 23, 38, 0.08);
|
|
124
|
+
--shadow-card: 0 4px 14px rgba(26, 23, 38, 0.04);
|
|
125
|
+
--shadow-cta: 0 8px 20px rgba(217, 119, 87, 0.28);
|
|
126
|
+
--shadow-cta-hover: 0 10px 28px rgba(217, 119, 87, 0.30);
|
|
127
|
+
--focus-ring: 0 0 0 3px rgba(217, 119, 87, 0.12);
|
|
128
|
+
--accent-tint: rgba(217, 119, 87, 0.07);
|
|
129
|
+
--accent-tint-strong: rgba(217, 119, 87, 0.08);
|
|
130
|
+
--accent-tint-border: rgba(217, 119, 87, 0.32);
|
|
131
|
+
}
|
|
132
|
+
@media (prefers-color-scheme: dark) {
|
|
133
|
+
:root {
|
|
134
|
+
--bg: #14110d;
|
|
135
|
+
--bg-soft: #1b160f;
|
|
136
|
+
--panel: #211b14;
|
|
137
|
+
--panel-2: #1b160f;
|
|
138
|
+
--border: #2a231a;
|
|
139
|
+
--ink: #f3ece2;
|
|
140
|
+
--muted: #a89c89;
|
|
141
|
+
--faint: #87795f;
|
|
142
|
+
--agent: #e8906b;
|
|
143
|
+
--human: #E0A23A;
|
|
144
|
+
--accent: #e8906b;
|
|
145
|
+
--accent-2: #E0A23A;
|
|
146
|
+
--code: #2a231a;
|
|
147
|
+
/* darker, subtler ambient glow + deeper shadows for contrast */
|
|
148
|
+
--glow: radial-gradient(120vw 78vh at 50% -12%,
|
|
149
|
+
rgba(232, 144, 107, 0.07), rgba(224, 162, 58, 0.03) 46%, transparent 72%);
|
|
150
|
+
--shadow-stage: 0 16px 40px rgba(0, 0, 0, 0.45);
|
|
151
|
+
--shadow-card: 0 4px 14px rgba(0, 0, 0, 0.30);
|
|
152
|
+
--shadow-cta: 0 8px 20px rgba(0, 0, 0, 0.45);
|
|
153
|
+
--shadow-cta-hover: 0 10px 28px rgba(0, 0, 0, 0.55);
|
|
154
|
+
--focus-ring: 0 0 0 3px rgba(232, 144, 107, 0.22);
|
|
155
|
+
--accent-tint: rgba(232, 144, 107, 0.14);
|
|
156
|
+
--accent-tint-strong: rgba(232, 144, 107, 0.16);
|
|
157
|
+
--accent-tint-border: rgba(232, 144, 107, 0.40);
|
|
158
|
+
}
|
|
114
159
|
}
|
|
115
160
|
* { box-sizing: border-box; }
|
|
161
|
+
html { color-scheme: light dark; }
|
|
116
162
|
html, body { height: 100%; }
|
|
117
163
|
body {
|
|
118
164
|
margin: 0;
|
|
119
|
-
background:
|
|
120
|
-
radial-gradient(1100px 600px at 50% -10%, #1a2030 0%, var(--bg) 60%);
|
|
165
|
+
background: var(--bg);
|
|
121
166
|
color: var(--ink);
|
|
122
167
|
font-family: var(--font);
|
|
123
168
|
-webkit-font-smoothing: antialiased;
|
|
169
|
+
text-rendering: optimizeLegibility;
|
|
124
170
|
display: flex;
|
|
125
171
|
align-items: center;
|
|
126
172
|
justify-content: center;
|
|
127
173
|
min-height: 100%;
|
|
128
174
|
padding: 24px;
|
|
129
|
-
line-height: 1.
|
|
175
|
+
line-height: 1.65;
|
|
176
|
+
position: relative;
|
|
177
|
+
}
|
|
178
|
+
/* ambient warm-sunset glow, matching the landing page */
|
|
179
|
+
body::before {
|
|
180
|
+
content: "";
|
|
181
|
+
position: fixed;
|
|
182
|
+
inset: 0;
|
|
183
|
+
background: var(--glow);
|
|
184
|
+
pointer-events: none;
|
|
185
|
+
z-index: 0;
|
|
130
186
|
}
|
|
131
187
|
.stage {
|
|
188
|
+
position: relative;
|
|
189
|
+
z-index: 1;
|
|
132
190
|
width: 100%;
|
|
133
191
|
max-width: 560px;
|
|
134
192
|
background: var(--panel);
|
|
135
|
-
border: 1px solid var(--
|
|
193
|
+
border: 1px solid var(--border);
|
|
136
194
|
border-radius: var(--radius);
|
|
137
|
-
box-shadow:
|
|
195
|
+
box-shadow: var(--shadow-stage);
|
|
138
196
|
overflow: hidden;
|
|
139
197
|
}
|
|
198
|
+
.brandbar {
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
gap: 10px;
|
|
202
|
+
padding: 14px 22px 12px;
|
|
203
|
+
border-bottom: 1px solid var(--border);
|
|
204
|
+
background: var(--bg-soft);
|
|
205
|
+
}
|
|
206
|
+
.brandbar .mark { width: 26px; height: 26px; display: block; flex: none; }
|
|
207
|
+
.brandbar .name {
|
|
208
|
+
font-weight: 700;
|
|
209
|
+
font-size: 16px;
|
|
210
|
+
letter-spacing: -0.01em;
|
|
211
|
+
color: var(--ink);
|
|
212
|
+
}
|
|
213
|
+
.brandbar .ttl {
|
|
214
|
+
margin-left: auto;
|
|
215
|
+
font-size: 12px;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
letter-spacing: 0.03em;
|
|
218
|
+
color: var(--faint);
|
|
219
|
+
}
|
|
140
220
|
.progress {
|
|
141
221
|
display: flex;
|
|
142
222
|
gap: 6px;
|
|
@@ -146,46 +226,62 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
146
226
|
flex: 1;
|
|
147
227
|
height: 3px;
|
|
148
228
|
border-radius: 3px;
|
|
149
|
-
background: var(--
|
|
229
|
+
background: var(--border);
|
|
150
230
|
transition: background 0.4s ease;
|
|
151
231
|
}
|
|
152
|
-
.progress i.on { background: linear-gradient(
|
|
153
|
-
.body { padding:
|
|
232
|
+
.progress i.on { background: linear-gradient(100deg, var(--accent), var(--accent-2)); }
|
|
233
|
+
.body { padding: 22px 30px 30px; }
|
|
154
234
|
.scene { display: none; }
|
|
155
235
|
.scene.active { display: block; }
|
|
156
236
|
.kicker {
|
|
157
237
|
font-size: 12px;
|
|
158
|
-
|
|
238
|
+
font-weight: 700;
|
|
239
|
+
letter-spacing: 0.12em;
|
|
159
240
|
text-transform: uppercase;
|
|
160
241
|
color: var(--muted);
|
|
161
242
|
margin: 0 0 10px;
|
|
162
243
|
}
|
|
163
244
|
h1 {
|
|
164
|
-
font-size:
|
|
165
|
-
line-height: 1.
|
|
245
|
+
font-size: 25px;
|
|
246
|
+
line-height: 1.18;
|
|
247
|
+
font-weight: 800;
|
|
166
248
|
margin: 0 0 12px;
|
|
167
|
-
letter-spacing: -0.
|
|
249
|
+
letter-spacing: -0.025em;
|
|
168
250
|
}
|
|
169
|
-
|
|
251
|
+
h1 .grad {
|
|
252
|
+
background: linear-gradient(100deg, var(--agent), var(--human));
|
|
253
|
+
-webkit-background-clip: text;
|
|
254
|
+
background-clip: text;
|
|
255
|
+
color: transparent;
|
|
256
|
+
}
|
|
257
|
+
p { margin: 0 0 14px; color: var(--muted); }
|
|
170
258
|
p.lead { color: var(--ink); font-size: 16px; }
|
|
171
259
|
.muted { color: var(--muted); font-size: 14px; }
|
|
260
|
+
code {
|
|
261
|
+
font-family: var(--mono);
|
|
262
|
+
font-size: 0.92em;
|
|
263
|
+
background: var(--code);
|
|
264
|
+
border-radius: 5px;
|
|
265
|
+
padding: 2px 6px;
|
|
266
|
+
color: var(--ink);
|
|
267
|
+
}
|
|
172
268
|
button.cta {
|
|
173
269
|
appearance: none;
|
|
174
270
|
border: 0;
|
|
175
271
|
cursor: pointer;
|
|
176
272
|
font: inherit;
|
|
177
|
-
font-weight:
|
|
178
|
-
color: #
|
|
179
|
-
background: linear-gradient(
|
|
180
|
-
padding: 12px
|
|
273
|
+
font-weight: 650;
|
|
274
|
+
color: #fff;
|
|
275
|
+
background: linear-gradient(100deg, var(--accent), var(--accent-2));
|
|
276
|
+
padding: 12px 22px;
|
|
181
277
|
border-radius: 10px;
|
|
182
278
|
margin-top: 6px;
|
|
183
279
|
transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.2s ease;
|
|
184
|
-
box-shadow:
|
|
280
|
+
box-shadow: var(--shadow-cta);
|
|
185
281
|
}
|
|
186
|
-
button.cta:hover { transform: translateY(-1px); box-shadow:
|
|
282
|
+
button.cta:hover { transform: translateY(-1px); box-shadow: var(--shadow-cta-hover); }
|
|
187
283
|
button.cta:active { transform: translateY(0); }
|
|
188
|
-
button.cta:disabled { opacity: 0.
|
|
284
|
+
button.cta:disabled { opacity: 0.5; cursor: default; transform: none; box-shadow: none; }
|
|
189
285
|
.diagram {
|
|
190
286
|
display: flex;
|
|
191
287
|
align-items: center;
|
|
@@ -193,75 +289,83 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
193
289
|
gap: 8px;
|
|
194
290
|
margin: 6px 0 18px;
|
|
195
291
|
padding: 18px 14px;
|
|
196
|
-
border: 1px solid var(--
|
|
292
|
+
border: 1px solid var(--border);
|
|
197
293
|
border-radius: 12px;
|
|
198
|
-
background:
|
|
294
|
+
background: var(--panel-2);
|
|
199
295
|
}
|
|
200
296
|
.node {
|
|
201
297
|
flex: 1;
|
|
202
298
|
text-align: center;
|
|
203
299
|
font-size: 13px;
|
|
300
|
+
font-weight: 600;
|
|
204
301
|
color: var(--ink);
|
|
205
302
|
padding: 12px 6px;
|
|
206
|
-
border: 1px solid var(--
|
|
303
|
+
border: 1px solid var(--border);
|
|
207
304
|
border-radius: 10px;
|
|
208
|
-
background:
|
|
305
|
+
background: var(--panel);
|
|
306
|
+
box-shadow: var(--shadow-card);
|
|
209
307
|
}
|
|
210
308
|
.node .ic { font-size: 20px; display: block; margin-bottom: 4px; }
|
|
211
|
-
.node small { display: block; color: var(--
|
|
212
|
-
.wire { color: var(--accent); font-size: 18px;
|
|
309
|
+
.node small { display: block; color: var(--faint); font-weight: 500; font-size: 11px; margin-top: 2px; }
|
|
310
|
+
.wire { color: var(--accent); font-size: 18px; }
|
|
213
311
|
.field { display: block; margin: 0 0 14px; }
|
|
214
|
-
.field span { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; }
|
|
312
|
+
.field span { display: block; font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 6px; }
|
|
215
313
|
input[type="text"] {
|
|
216
314
|
width: 100%;
|
|
217
315
|
font: inherit;
|
|
218
316
|
color: var(--ink);
|
|
219
|
-
background:
|
|
220
|
-
border: 1px solid var(--
|
|
221
|
-
border-radius:
|
|
317
|
+
background: var(--panel-2);
|
|
318
|
+
border: 1px solid var(--border);
|
|
319
|
+
border-radius: 10px;
|
|
222
320
|
padding: 11px 12px;
|
|
223
321
|
outline: none;
|
|
322
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
224
323
|
}
|
|
225
|
-
input[type="text"]
|
|
324
|
+
input[type="text"]::placeholder { color: var(--faint); }
|
|
325
|
+
input[type="text"]:focus { border-color: var(--accent); box-shadow: var(--focus-ring); }
|
|
226
326
|
.choices { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
227
327
|
.choices label {
|
|
228
328
|
flex: 1;
|
|
229
329
|
min-width: 120px;
|
|
230
|
-
border: 1px solid var(--
|
|
231
|
-
border-radius:
|
|
330
|
+
border: 1px solid var(--border);
|
|
331
|
+
border-radius: 10px;
|
|
232
332
|
padding: 11px 12px;
|
|
233
333
|
cursor: pointer;
|
|
234
|
-
background:
|
|
235
|
-
transition: border-color 0.15s ease, background 0.15s ease;
|
|
334
|
+
background: var(--panel-2);
|
|
335
|
+
transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
|
|
236
336
|
}
|
|
237
|
-
.choices label.sel { border-color: var(--accent); background:
|
|
337
|
+
.choices label.sel { border-color: var(--accent); background: var(--accent-tint); box-shadow: var(--focus-ring); }
|
|
238
338
|
.choices input { position: absolute; opacity: 0; pointer-events: none; }
|
|
239
|
-
.choices b { display: block; font-size: 14px; }
|
|
240
|
-
.choices em { display: block; font-style: normal; color: var(--
|
|
339
|
+
.choices b { display: block; font-size: 14px; color: var(--ink); }
|
|
340
|
+
.choices em { display: block; font-style: normal; color: var(--faint); font-size: 12px; }
|
|
241
341
|
pre {
|
|
242
342
|
margin: 0 0 14px;
|
|
243
343
|
padding: 14px 16px;
|
|
244
|
-
background:
|
|
245
|
-
border: 1px solid var(--
|
|
246
|
-
border-radius:
|
|
344
|
+
background: var(--panel-2);
|
|
345
|
+
border: 1px solid var(--border);
|
|
346
|
+
border-radius: 11px;
|
|
247
347
|
font-family: var(--mono);
|
|
248
348
|
font-size: 13px;
|
|
249
|
-
|
|
349
|
+
line-height: 1.6;
|
|
350
|
+
color: var(--ink);
|
|
250
351
|
overflow-x: auto;
|
|
251
352
|
white-space: pre-wrap;
|
|
252
353
|
word-break: break-word;
|
|
354
|
+
box-shadow: var(--shadow-card);
|
|
253
355
|
}
|
|
254
|
-
pre
|
|
255
|
-
pre .
|
|
356
|
+
pre code { background: transparent; padding: 0; border-radius: 0; font-size: inherit; }
|
|
357
|
+
pre .k { color: var(--accent); font-weight: 600; }
|
|
358
|
+
pre .s { color: var(--human); }
|
|
256
359
|
.badge {
|
|
257
360
|
display: inline-flex;
|
|
258
361
|
align-items: center;
|
|
259
362
|
gap: 7px;
|
|
260
363
|
font-size: 13px;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
364
|
+
font-weight: 600;
|
|
365
|
+
color: var(--agent);
|
|
366
|
+
border: 1px solid var(--accent-tint-border);
|
|
367
|
+
background: var(--accent-tint-strong);
|
|
368
|
+
padding: 6px 12px;
|
|
265
369
|
border-radius: 999px;
|
|
266
370
|
margin: 0 0 14px;
|
|
267
371
|
}
|
|
@@ -277,14 +381,15 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
277
381
|
gap: 10px;
|
|
278
382
|
align-items: baseline;
|
|
279
383
|
padding: 9px 12px;
|
|
280
|
-
border: 1px solid var(--
|
|
281
|
-
border-radius:
|
|
384
|
+
border: 1px solid var(--border);
|
|
385
|
+
border-radius: 10px;
|
|
282
386
|
margin-bottom: 7px;
|
|
283
|
-
background:
|
|
387
|
+
background: var(--panel-2);
|
|
284
388
|
font-size: 13px;
|
|
285
389
|
}
|
|
286
|
-
.log code { font-family: var(--mono); color: var(--accent); }
|
|
287
|
-
.log .who { color: var(--
|
|
390
|
+
.log code { font-family: var(--mono); color: var(--accent); background: transparent; padding: 0; }
|
|
391
|
+
.log .who { color: var(--faint); font-size: 11px; margin-left: auto; }
|
|
392
|
+
.inline-cmd { font-family: var(--mono); color: var(--accent); background: var(--code); }
|
|
288
393
|
.anim-in { animation: rise 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
|
|
289
394
|
@keyframes rise {
|
|
290
395
|
from { opacity: 0; transform: translateY(10px); }
|
|
@@ -298,6 +403,18 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
298
403
|
</head>
|
|
299
404
|
<body>
|
|
300
405
|
<main class="stage" role="application" aria-label="Pane tutorial">
|
|
406
|
+
<div class="brandbar">
|
|
407
|
+
<svg class="mark" viewBox="0 0 100 100" aria-hidden="true">
|
|
408
|
+
<rect width="100" height="100" rx="22" fill="#D97757" />
|
|
409
|
+
<circle cx="62" cy="58" r="17" fill="#ffffff" />
|
|
410
|
+
<rect x="20" y="26" width="40" height="32" rx="10" fill="#D97757" />
|
|
411
|
+
<rect x="24" y="30" width="32" height="24" rx="7" fill="#ffffff" />
|
|
412
|
+
<circle cx="33.5" cy="42" r="3.4" fill="#D97757" />
|
|
413
|
+
<circle cx="46.5" cy="42" r="3.4" fill="#D97757" />
|
|
414
|
+
</svg>
|
|
415
|
+
<span class="name">Pane</span>
|
|
416
|
+
<span class="ttl">the 60-second tour</span>
|
|
417
|
+
</div>
|
|
301
418
|
<div class="progress" aria-hidden="true">
|
|
302
419
|
<i data-step="1"></i><i data-step="2"></i><i data-step="3"></i>
|
|
303
420
|
<i data-step="4"></i><i data-step="5"></i><i data-step="6"></i>
|
|
@@ -306,9 +423,9 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
306
423
|
<!-- Scene 1 — Hook -->
|
|
307
424
|
<section class="scene" data-scene="1">
|
|
308
425
|
<p class="kicker">A pane</p>
|
|
309
|
-
<h1>You're looking at a pane
|
|
426
|
+
<h1>You're looking at a <span class="grad">pane</span>.</h1>
|
|
310
427
|
<p class="lead">
|
|
311
|
-
An agent just handed you this UI
|
|
428
|
+
An agent just handed you this UI by URL, nothing installed. Whatever
|
|
312
429
|
you do here turns into structured data the agent reads back.
|
|
313
430
|
</p>
|
|
314
431
|
<p class="muted">Let's prove it in about a minute.</p>
|
|
@@ -327,7 +444,7 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
327
444
|
<div class="node"><span class="ic">▦</span>this page<small>the pane</small></div>
|
|
328
445
|
</div>
|
|
329
446
|
<p>
|
|
330
|
-
You ran <code>pane demo</code>
|
|
447
|
+
You ran <code>pane demo</code> and that command is acting as your
|
|
331
448
|
agent right now. It's watching this session over a WebSocket.
|
|
332
449
|
</p>
|
|
333
450
|
<button class="cta" id="b-hello" type="button">Click me</button>
|
|
@@ -337,7 +454,7 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
337
454
|
<section class="scene" data-scene="3">
|
|
338
455
|
<div class="badge">✓ Your agent just received your click.</div>
|
|
339
456
|
<h1>That landed in two places.</h1>
|
|
340
|
-
<p class="point">Look at your terminal
|
|
457
|
+
<p class="point">Look at your terminal, it printed the same event.</p>
|
|
341
458
|
<p>
|
|
342
459
|
The click became a <code>demo.hello</code> event, streamed to the
|
|
343
460
|
agent, which streamed a reply back to redraw this page. No polling, no
|
|
@@ -350,7 +467,7 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
350
467
|
<!-- Scene 4 — Structured data -->
|
|
351
468
|
<section class="scene" data-scene="4">
|
|
352
469
|
<p class="kicker">Structured data</p>
|
|
353
|
-
<h1>Interactions aren't clicks
|
|
470
|
+
<h1>Interactions aren't clicks, they're data.</h1>
|
|
354
471
|
<label class="field">
|
|
355
472
|
<span>Your name (optional)</span>
|
|
356
473
|
<input type="text" id="f-name" autocomplete="off" maxlength="80"
|
|
@@ -372,7 +489,7 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
372
489
|
<div class="badge" style="margin-top:16px">✓ Your agent received:</div>
|
|
373
490
|
<pre id="echo-pre"></pre>
|
|
374
491
|
<p class="muted">
|
|
375
|
-
That's the exact payload
|
|
492
|
+
That's the exact payload, typed and validated by the relay before
|
|
376
493
|
the agent ever saw it.
|
|
377
494
|
</p>
|
|
378
495
|
</div>
|
|
@@ -389,7 +506,7 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
389
506
|
<ul class="log" id="log"></ul>
|
|
390
507
|
<p class="muted">
|
|
391
508
|
From your terminal that's just:
|
|
392
|
-
<code
|
|
509
|
+
<code class="inline-cmd">pane show <id></code>
|
|
393
510
|
</p>
|
|
394
511
|
</section>
|
|
395
512
|
|
|
@@ -398,13 +515,13 @@ export const DEMO_ARTIFACT_HTML = `<!doctype html>
|
|
|
398
515
|
<p class="kicker">Your turn</p>
|
|
399
516
|
<h1>That's the whole idea.</h1>
|
|
400
517
|
<p>An agent hands a human a UI, gets structured data back. To build one:</p>
|
|
401
|
-
<pre><span class="k">pane</span> create \\
|
|
402
|
-
--template ./form.html --name "My form" \\
|
|
518
|
+
<pre><code><span class="k">pane</span> create \\
|
|
519
|
+
--template ./form.html --name <span class="s">"My form"</span> \\
|
|
403
520
|
--event-schema ./schema.json
|
|
404
|
-
<span class="k">pane</span> watch <id> --type form.submitted</pre>
|
|
521
|
+
<span class="k">pane</span> watch <id> --type form.submitted</code></pre>
|
|
405
522
|
<p>
|
|
406
523
|
The full guide is in the skill:
|
|
407
|
-
<code
|
|
524
|
+
<code class="inline-cmd">pane skill show</code>.
|
|
408
525
|
</p>
|
|
409
526
|
<p class="muted">Now go hand one to a human.</p>
|
|
410
527
|
</section>
|
package/dist/commands/demo.js
CHANGED
|
@@ -34,7 +34,7 @@ export const demoHelp = `pane demo — take the 60-second guided tour
|
|
|
34
34
|
Usage:
|
|
35
35
|
pane demo [options]
|
|
36
36
|
|
|
37
|
-
Creates a short-lived pane
|
|
37
|
+
Creates a short-lived pane from the built-in tutorial template, opens its URL
|
|
38
38
|
in your browser (or prints it if none is available), then runs a tiny agent
|
|
39
39
|
loop right here in your terminal that watches the session and reacts to each
|
|
40
40
|
thing you do in the pane. Every event you trigger is echoed to this terminal
|
package/dist/commands/records.js
CHANGED
|
@@ -27,6 +27,11 @@ Verbs:
|
|
|
27
27
|
--data <path|json> [--if-match <version>]
|
|
28
28
|
delete <pane-id> <collection> <record-key>
|
|
29
29
|
[--if-match <version>] [--yes]
|
|
30
|
+
delete-collection <pane-id> <collection>
|
|
31
|
+
[--yes]
|
|
32
|
+
Drop a WHOLE collection — all its records + the collection row.
|
|
33
|
+
Owner-only. Collection names are immutable (no rename): to "rename",
|
|
34
|
+
delete the old collection and write under the new name.
|
|
30
35
|
watch <pane-id>
|
|
31
36
|
[--collection <name>]... [--since-seq <name>=<n>]...
|
|
32
37
|
|
|
@@ -43,7 +48,7 @@ export async function runRecords(args) {
|
|
|
43
48
|
return;
|
|
44
49
|
}
|
|
45
50
|
if (verb === undefined) {
|
|
46
|
-
fail("missing verb — pane records <list|get|upsert|update|delete|watch>", "invalid_args");
|
|
51
|
+
fail("missing verb — pane records <list|get|upsert|update|delete|delete-collection|watch>", "invalid_args");
|
|
47
52
|
}
|
|
48
53
|
const sub = {
|
|
49
54
|
positionals: args.positionals.slice(1),
|
|
@@ -64,10 +69,12 @@ export async function runRecords(args) {
|
|
|
64
69
|
return runUpdate(sub);
|
|
65
70
|
case "delete":
|
|
66
71
|
return runDelete(sub);
|
|
72
|
+
case "delete-collection":
|
|
73
|
+
return runDeleteCollection(sub);
|
|
67
74
|
case "watch":
|
|
68
75
|
return runWatch(sub);
|
|
69
76
|
default:
|
|
70
|
-
fail(`unknown verb '${verb}' — pane records <list|get|upsert|update|delete|watch>`, "invalid_args");
|
|
77
|
+
fail(`unknown verb '${verb}' — pane records <list|get|upsert|update|delete|delete-collection|watch>`, "invalid_args");
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
80
|
// ---------------------------------------------------------------------------
|
|
@@ -199,6 +206,24 @@ async function runDelete(args) {
|
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
208
|
// ---------------------------------------------------------------------------
|
|
209
|
+
// delete-collection — drop a whole collection (all rows + the collection row)
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
async function runDeleteCollection(args) {
|
|
212
|
+
assertKnownFlags(args, ["url", "api-key"], ["yes", "help"], "pane records delete-collection");
|
|
213
|
+
const [paneId, collection] = args.positionals;
|
|
214
|
+
if (!paneId || !collection) {
|
|
215
|
+
fail("usage: pane records delete-collection <pane-id> <collection>", "invalid_args");
|
|
216
|
+
}
|
|
217
|
+
const client = makeClient(args);
|
|
218
|
+
try {
|
|
219
|
+
await client.deleteRecordCollection(paneId, collection);
|
|
220
|
+
printJson({ deleted: true, collection });
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
failFromError(e);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
202
227
|
// watch — stream record deltas as JSON-lines
|
|
203
228
|
// ---------------------------------------------------------------------------
|
|
204
229
|
async function runWatch(args) {
|
|
@@ -29,6 +29,11 @@ Verbs:
|
|
|
29
29
|
--data <path|json> [--if-match <version>]
|
|
30
30
|
delete <template-id|slug> <collection> <record-key>
|
|
31
31
|
[--if-match <version>] [--yes]
|
|
32
|
+
delete-collection <template-id|slug> <collection>
|
|
33
|
+
[--yes]
|
|
34
|
+
Drop a WHOLE collection — all its records + the collection row.
|
|
35
|
+
Owner-only. Collection names are immutable (no rename): to "rename",
|
|
36
|
+
delete the old collection and write under the new name.
|
|
32
37
|
|
|
33
38
|
Output (stdout): single JSON object per command.
|
|
34
39
|
Errors on stderr: {"error":{"code","message"}} with non-zero exit.`;
|
|
@@ -39,7 +44,7 @@ export async function runTemplateRecords(args) {
|
|
|
39
44
|
return;
|
|
40
45
|
}
|
|
41
46
|
if (verb === undefined) {
|
|
42
|
-
fail("missing verb — pane template-records <list|get|upsert|update|delete>", "invalid_args");
|
|
47
|
+
fail("missing verb — pane template-records <list|get|upsert|update|delete|delete-collection>", "invalid_args");
|
|
43
48
|
}
|
|
44
49
|
const sub = {
|
|
45
50
|
positionals: args.positionals.slice(1),
|
|
@@ -60,8 +65,10 @@ export async function runTemplateRecords(args) {
|
|
|
60
65
|
return runUpdate(sub);
|
|
61
66
|
case "delete":
|
|
62
67
|
return runDelete(sub);
|
|
68
|
+
case "delete-collection":
|
|
69
|
+
return runDeleteCollection(sub);
|
|
63
70
|
default:
|
|
64
|
-
fail(`unknown verb '${verb}' — pane template-records <list|get|upsert|update|delete>`, "invalid_args");
|
|
71
|
+
fail(`unknown verb '${verb}' — pane template-records <list|get|upsert|update|delete|delete-collection>`, "invalid_args");
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
async function runList(args) {
|
|
@@ -177,6 +184,21 @@ async function runDelete(args) {
|
|
|
177
184
|
failFromError(e);
|
|
178
185
|
}
|
|
179
186
|
}
|
|
187
|
+
async function runDeleteCollection(args) {
|
|
188
|
+
assertKnownFlags(args, ["url", "api-key"], ["yes", "help"], "pane template-records delete-collection");
|
|
189
|
+
const [templateId, collection] = args.positionals;
|
|
190
|
+
if (!templateId || !collection) {
|
|
191
|
+
fail("usage: pane template-records delete-collection <template-id|slug> <collection>", "invalid_args");
|
|
192
|
+
}
|
|
193
|
+
const client = makeClient(args);
|
|
194
|
+
try {
|
|
195
|
+
await client.deleteTemplateRecordCollection(templateId, collection);
|
|
196
|
+
printJson({ deleted: true, collection });
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
failFromError(e);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
180
202
|
function parseIntFlag(args, name, defaultValue, bounds = {}) {
|
|
181
203
|
const raw = args.flags.get(name);
|
|
182
204
|
if (raw === undefined)
|
|
@@ -24,6 +24,7 @@ const CREATE_FLAGS = [
|
|
|
24
24
|
"event-schema",
|
|
25
25
|
"input-schema",
|
|
26
26
|
"record-schema",
|
|
27
|
+
"template-record-schema",
|
|
27
28
|
"icon-emoji",
|
|
28
29
|
];
|
|
29
30
|
const SET_ICON_FLAGS = ["template-id", "emoji", "image"];
|
|
@@ -34,6 +35,7 @@ const VERSION_FLAGS = [
|
|
|
34
35
|
"event-schema",
|
|
35
36
|
"input-schema",
|
|
36
37
|
"record-schema",
|
|
38
|
+
"template-record-schema",
|
|
37
39
|
];
|
|
38
40
|
const UPDATE_FLAGS = ["name", "slug", "description", "tags"];
|
|
39
41
|
const NO_FLAGS = [];
|
|
@@ -76,8 +78,9 @@ Subcommands:
|
|
|
76
78
|
[--event-schema <path|json>] [--slug <s>]
|
|
77
79
|
[--description <d>] [--tags <t1,t2>]
|
|
78
80
|
[--input-schema <path|json>]
|
|
79
|
-
[--record-schema <path|json>]
|
|
80
|
-
[--
|
|
81
|
+
[--record-schema <path|json>]
|
|
82
|
+
[--template-record-schema <path|json>]
|
|
83
|
+
[--template-type <t>] [--icon-emoji <emoji>]
|
|
81
84
|
Creates a named template. Prints { template_id, slug, version }.
|
|
82
85
|
--icon-emoji sets a single-emoji icon at create time; use 'set-icon'
|
|
83
86
|
with --image to attach an uploaded image icon afterwards.
|
|
@@ -85,7 +88,9 @@ Subcommands:
|
|
|
85
88
|
pane template version <id|slug> --template <path|inline>
|
|
86
89
|
[--event-schema <path|json>]
|
|
87
90
|
[--input-schema <path|json>]
|
|
88
|
-
[--record-schema <path|json>]
|
|
91
|
+
[--record-schema <path|json>]
|
|
92
|
+
[--template-record-schema <path|json>]
|
|
93
|
+
[--template-type <t>]
|
|
89
94
|
Appends a new immutable version. Prints { template_id, version }.
|
|
90
95
|
|
|
91
96
|
pane template update <id|slug> [--name <n>] [--slug <s>]
|
|
@@ -217,6 +222,17 @@ Options:
|
|
|
217
222
|
The relay validates the document and rejects anything
|
|
218
223
|
malformed at create time. See 'pane skill show' (the
|
|
219
224
|
"Records" section) for the full grammar.
|
|
225
|
+
--template-record-schema <v>
|
|
226
|
+
JSON Schema 2020-12 document declaring this template's
|
|
227
|
+
TEMPLATE-level record collections — a file path, or
|
|
228
|
+
inline JSON. Optional; omit for a template with no
|
|
229
|
+
template-level collections. Same "x-pane-collections"
|
|
230
|
+
grammar as --record-schema, but the collections it
|
|
231
|
+
declares are shared across every pane of the template
|
|
232
|
+
(one collection per template) instead of per-pane.
|
|
233
|
+
Without it, 'pane template-records upsert' is rejected
|
|
234
|
+
with "template declares no template-level record
|
|
235
|
+
collections" — set this first to bootstrap the feature.
|
|
220
236
|
--icon-emoji <e> Single-emoji icon for the template (create only).
|
|
221
237
|
--template-type <t> "html-inline" (default) or "html-ref".
|
|
222
238
|
--url <url> Relay base URL (overrides PANE_URL).
|
|
@@ -300,6 +316,32 @@ function resolveRecordSchema(args) {
|
|
|
300
316
|
fail(e instanceof Error ? e.message : String(e), "invalid_args");
|
|
301
317
|
}
|
|
302
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Resolve the optional --template-record-schema — file path or inline JSON.
|
|
321
|
+
* Must be an object (a JSON Schema 2020-12 document with an
|
|
322
|
+
* `x-pane-collections` extension), same grammar as --record-schema, but
|
|
323
|
+
* declares TEMPLATE-level (shared across all panes of the template) record
|
|
324
|
+
* collections rather than per-pane ones. Absent → returns `undefined` so the
|
|
325
|
+
* caller omits `template_record_schema` from the request entirely (template
|
|
326
|
+
* has no template-level record collections). The relay runs the full shape +
|
|
327
|
+
* collection validation; this helper only does the obvious "is it an object"
|
|
328
|
+
* pre-check so a typo surfaces locally.
|
|
329
|
+
*/
|
|
330
|
+
function resolveTemplateRecordSchema(args) {
|
|
331
|
+
const raw = args.flags.get("template-record-schema");
|
|
332
|
+
if (raw === undefined)
|
|
333
|
+
return undefined;
|
|
334
|
+
try {
|
|
335
|
+
const v = resolveJson(raw, "--template-record-schema");
|
|
336
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) {
|
|
337
|
+
fail("--template-record-schema must be a JSON object", "invalid_args");
|
|
338
|
+
}
|
|
339
|
+
return v;
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
fail(e instanceof Error ? e.message : String(e), "invalid_args");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
303
345
|
/** Parse a comma-separated --tags flag into a string array. */
|
|
304
346
|
function resolveTags(args) {
|
|
305
347
|
const raw = args.flags.get("tags");
|
|
@@ -321,6 +363,7 @@ async function runTemplateCreate(args) {
|
|
|
321
363
|
const eventSchema = resolveEventSchema(args);
|
|
322
364
|
const inputSchema = resolveInputSchema(args);
|
|
323
365
|
const recordSchema = resolveRecordSchema(args);
|
|
366
|
+
const templateRecordSchema = resolveTemplateRecordSchema(args);
|
|
324
367
|
const tags = resolveTags(args);
|
|
325
368
|
const slug = args.flags.get("slug");
|
|
326
369
|
const description = args.flags.get("description");
|
|
@@ -346,6 +389,11 @@ async function runTemplateCreate(args) {
|
|
|
346
389
|
// `undefined` would still add the key.
|
|
347
390
|
if (recordSchema !== undefined)
|
|
348
391
|
candidate["record_schema"] = recordSchema;
|
|
392
|
+
// template_record_schema is OMITTED entirely when --template-record-schema is
|
|
393
|
+
// absent — a template with no template-level record collections. Setting it
|
|
394
|
+
// to `undefined` would still add the key.
|
|
395
|
+
if (templateRecordSchema !== undefined)
|
|
396
|
+
candidate["template_record_schema"] = templateRecordSchema;
|
|
349
397
|
const iconEmoji = args.flags.get("icon-emoji");
|
|
350
398
|
if (iconEmoji !== undefined)
|
|
351
399
|
candidate["icon_emoji"] = iconEmoji;
|
|
@@ -380,6 +428,7 @@ async function runTemplateVersion(args) {
|
|
|
380
428
|
const eventSchema = resolveEventSchema(args);
|
|
381
429
|
const inputSchema = resolveInputSchema(args);
|
|
382
430
|
const recordSchema = resolveRecordSchema(args);
|
|
431
|
+
const templateRecordSchema = resolveTemplateRecordSchema(args);
|
|
383
432
|
const candidate = {
|
|
384
433
|
source,
|
|
385
434
|
type,
|
|
@@ -395,6 +444,12 @@ async function runTemplateVersion(args) {
|
|
|
395
444
|
// serialise as null and read as "intentionally cleared" on the wire.
|
|
396
445
|
if (recordSchema !== undefined)
|
|
397
446
|
candidate["record_schema"] = recordSchema;
|
|
447
|
+
// template_record_schema is OMITTED entirely when --template-record-schema is
|
|
448
|
+
// absent — same rationale as record_schema above. Including the key as
|
|
449
|
+
// undefined would serialise as null and read as "intentionally cleared" on
|
|
450
|
+
// the wire.
|
|
451
|
+
if (templateRecordSchema !== undefined)
|
|
452
|
+
candidate["template_record_schema"] = templateRecordSchema;
|
|
398
453
|
const parsed = createArtifactVersionSchema.safeParse(candidate);
|
|
399
454
|
if (!parsed.success) {
|
|
400
455
|
const issue = parsed.error.issues[0];
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// `pane update <pane-id>` — in-place edit of instance-level pane fields (#502)
|
|
2
|
+
// via PATCH /v1/panes/:id. Mirrors the create flags for the editable subset
|
|
3
|
+
// (everything that is per-pane rather than per-template).
|
|
4
|
+
import { updatePaneSchema } from "@paneui/core";
|
|
5
|
+
import { assertKnownFlags } from "../argv.js";
|
|
6
|
+
import { makeClient } from "../config.js";
|
|
7
|
+
import { resolveJson } from "../input.js";
|
|
8
|
+
import { printJson, fail, failFromError } from "../output.js";
|
|
9
|
+
const KNOWN_FLAGS = [
|
|
10
|
+
"ttl",
|
|
11
|
+
"expires-at",
|
|
12
|
+
"input-data",
|
|
13
|
+
"title",
|
|
14
|
+
"preamble",
|
|
15
|
+
"metadata",
|
|
16
|
+
"tags",
|
|
17
|
+
"icon-emoji",
|
|
18
|
+
"icon-attachment-id",
|
|
19
|
+
];
|
|
20
|
+
// `--clear-icon-emoji` / `--clear-icon-attachment-id` set the corresponding
|
|
21
|
+
// field to `null` on the wire (drops the per-pane override; the pane falls back
|
|
22
|
+
// to the template's icon).
|
|
23
|
+
const KNOWN_BOOLS = ["clear-icon-emoji", "clear-icon-attachment-id"];
|
|
24
|
+
const SCHEMA_PATH_TO_FLAG = {
|
|
25
|
+
ttl: "--ttl",
|
|
26
|
+
expires_at: "--expires-at",
|
|
27
|
+
input_data: "--input-data",
|
|
28
|
+
title: "--title",
|
|
29
|
+
preamble: "--preamble",
|
|
30
|
+
metadata: "--metadata",
|
|
31
|
+
tags: "--tags",
|
|
32
|
+
icon_emoji: "--icon-emoji",
|
|
33
|
+
icon_attachment_id: "--icon-attachment-id",
|
|
34
|
+
};
|
|
35
|
+
function schemaPathToFlag(path) {
|
|
36
|
+
const dotted = path.map(String).join(".");
|
|
37
|
+
for (let i = path.length; i > 0; i--) {
|
|
38
|
+
const prefix = path.slice(0, i).map(String).join(".");
|
|
39
|
+
const flag = SCHEMA_PATH_TO_FLAG[prefix];
|
|
40
|
+
if (flag !== undefined)
|
|
41
|
+
return flag;
|
|
42
|
+
}
|
|
43
|
+
return dotted;
|
|
44
|
+
}
|
|
45
|
+
export const updateHelp = `pane update — edit instance-level fields on a live pane
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
pane update <pane-id> [options]
|
|
49
|
+
|
|
50
|
+
Edits a live pane in place (PATCH /v1/panes/:id). The pane keeps its id, URL,
|
|
51
|
+
event log, and template pin — only the per-instance fields listed below change.
|
|
52
|
+
Pass at least one flag; ttl and --expires-at are mutually exclusive.
|
|
53
|
+
|
|
54
|
+
The relay revalidates --input-data against the pane's current template
|
|
55
|
+
version's input_schema (the pane may have been upgraded since create), and
|
|
56
|
+
runs the same attachment-access checks as 'pane create'.
|
|
57
|
+
|
|
58
|
+
Lifecycle:
|
|
59
|
+
--ttl <seconds> Reset the pane's lifetime to now + <seconds>. Clamped
|
|
60
|
+
against the relay's MAX_TTL_SECONDS cap; exceeding it
|
|
61
|
+
is rejected (not silently truncated).
|
|
62
|
+
--expires-at <iso> Set expires_at to a specific ISO-8601 timestamp.
|
|
63
|
+
Must be in the future and within MAX_TTL_SECONDS
|
|
64
|
+
from now. Mutually exclusive with --ttl.
|
|
65
|
+
|
|
66
|
+
Display:
|
|
67
|
+
--title <text> New tab title. Same length/control-char rules as
|
|
68
|
+
create.
|
|
69
|
+
--preamble <text> New preamble (the context band above the iframe).
|
|
70
|
+
Max 280 chars after trim; one \\n permitted.
|
|
71
|
+
--icon-emoji <e> Set per-pane icon to a single emoji grapheme.
|
|
72
|
+
--icon-attachment-id <id>
|
|
73
|
+
Set per-pane icon to a ready raster-image
|
|
74
|
+
attachment (png/jpeg/webp/gif).
|
|
75
|
+
--clear-icon-emoji Clear the emoji override; fall back to the
|
|
76
|
+
template's icon.
|
|
77
|
+
--clear-icon-attachment-id
|
|
78
|
+
Clear the attachment override; fall back to the
|
|
79
|
+
template's icon.
|
|
80
|
+
|
|
81
|
+
Data:
|
|
82
|
+
--input-data <path|json> Replace the pane's input_data wholesale (JSON file
|
|
83
|
+
path or inline JSON). Revalidated against the pinned
|
|
84
|
+
template version's input_schema.
|
|
85
|
+
--metadata <path|json> Replace the pane's metadata wholesale.
|
|
86
|
+
--tags <t1,t2,...> Replace the per-pane tags (the relay merges them with
|
|
87
|
+
the template's tags, deduped). ≤20 tags, ≤50 chars
|
|
88
|
+
each; 'favorite' / 'favorites' are reserved.
|
|
89
|
+
|
|
90
|
+
Other:
|
|
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, JSON):
|
|
96
|
+
The full new pane state — same shape as 'pane show <id>' — plus an
|
|
97
|
+
\`updated_fields\` array naming which fields actually changed.`;
|
|
98
|
+
export async function runUpdate(args) {
|
|
99
|
+
assertKnownFlags(args, KNOWN_FLAGS, KNOWN_BOOLS, "pane update");
|
|
100
|
+
const paneId = args.positionals[0];
|
|
101
|
+
if (!paneId)
|
|
102
|
+
fail("missing <pane-id>", "invalid_args");
|
|
103
|
+
const candidate = {};
|
|
104
|
+
const ttlRaw = args.flags.get("ttl");
|
|
105
|
+
const expiresAtRaw = args.flags.get("expires-at");
|
|
106
|
+
if (ttlRaw !== undefined && expiresAtRaw !== undefined) {
|
|
107
|
+
fail("--ttl and --expires-at are mutually exclusive — pass one or the other", "invalid_args");
|
|
108
|
+
}
|
|
109
|
+
if (ttlRaw !== undefined) {
|
|
110
|
+
const ttl = Number(ttlRaw);
|
|
111
|
+
if (!Number.isFinite(ttl))
|
|
112
|
+
fail("--ttl must be a number", "invalid_args");
|
|
113
|
+
candidate["ttl"] = ttl;
|
|
114
|
+
}
|
|
115
|
+
if (expiresAtRaw !== undefined) {
|
|
116
|
+
candidate["expires_at"] = expiresAtRaw;
|
|
117
|
+
}
|
|
118
|
+
const titleRaw = args.flags.get("title");
|
|
119
|
+
if (titleRaw !== undefined)
|
|
120
|
+
candidate["title"] = titleRaw;
|
|
121
|
+
const preambleRaw = args.flags.get("preamble");
|
|
122
|
+
if (preambleRaw !== undefined)
|
|
123
|
+
candidate["preamble"] = preambleRaw;
|
|
124
|
+
const inputDataRaw = args.flags.get("input-data");
|
|
125
|
+
if (inputDataRaw !== undefined) {
|
|
126
|
+
try {
|
|
127
|
+
candidate["input_data"] = resolveJson(inputDataRaw, "--input-data");
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
fail(e instanceof Error ? e.message : String(e), "invalid_args");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const metaRaw = args.flags.get("metadata");
|
|
134
|
+
if (metaRaw !== undefined) {
|
|
135
|
+
try {
|
|
136
|
+
candidate["metadata"] = resolveJson(metaRaw, "--metadata");
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
fail(e instanceof Error ? e.message : String(e), "invalid_args");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const tagsRaw = args.flags.get("tags");
|
|
143
|
+
if (tagsRaw !== undefined) {
|
|
144
|
+
const tags = tagsRaw
|
|
145
|
+
.split(",")
|
|
146
|
+
.map((t) => t.trim())
|
|
147
|
+
.filter((t) => t !== "");
|
|
148
|
+
// Empty list explicitly = caller passed `--tags ""`; send [] so the relay
|
|
149
|
+
// wipes per-pane tags down to just the template inheritance.
|
|
150
|
+
candidate["tags"] = tags;
|
|
151
|
+
}
|
|
152
|
+
// Icon overrides. The set-flag and the clear-flag for the same field are
|
|
153
|
+
// contradictory — refuse rather than silently picking one.
|
|
154
|
+
const iconEmojiRaw = args.flags.get("icon-emoji");
|
|
155
|
+
const clearIconEmoji = args.bools.has("clear-icon-emoji");
|
|
156
|
+
if (iconEmojiRaw !== undefined && clearIconEmoji) {
|
|
157
|
+
fail("--icon-emoji and --clear-icon-emoji are mutually exclusive", "invalid_args");
|
|
158
|
+
}
|
|
159
|
+
if (iconEmojiRaw !== undefined)
|
|
160
|
+
candidate["icon_emoji"] = iconEmojiRaw;
|
|
161
|
+
if (clearIconEmoji)
|
|
162
|
+
candidate["icon_emoji"] = null;
|
|
163
|
+
const iconAttachmentRaw = args.flags.get("icon-attachment-id");
|
|
164
|
+
const clearIconAttachment = args.bools.has("clear-icon-attachment-id");
|
|
165
|
+
if (iconAttachmentRaw !== undefined && clearIconAttachment) {
|
|
166
|
+
fail("--icon-attachment-id and --clear-icon-attachment-id are mutually exclusive", "invalid_args");
|
|
167
|
+
}
|
|
168
|
+
if (iconAttachmentRaw !== undefined) {
|
|
169
|
+
candidate["icon_attachment_id"] = iconAttachmentRaw;
|
|
170
|
+
}
|
|
171
|
+
if (clearIconAttachment)
|
|
172
|
+
candidate["icon_attachment_id"] = null;
|
|
173
|
+
// Bail before the round trip if nothing was supplied — the relay would
|
|
174
|
+
// reject this with `invalid_request` anyway, but a local message names the
|
|
175
|
+
// CLI flags the user knows.
|
|
176
|
+
if (Object.keys(candidate).length === 0) {
|
|
177
|
+
fail("pass at least one field to update (see --help for the full list)", "invalid_args");
|
|
178
|
+
}
|
|
179
|
+
const parsed = updatePaneSchema.safeParse(candidate);
|
|
180
|
+
if (!parsed.success) {
|
|
181
|
+
const issue = parsed.error.issues[0];
|
|
182
|
+
const where = issue && issue.path.length > 0 ? schemaPathToFlag(issue.path) : "request";
|
|
183
|
+
fail(`invalid update request: ${where}: ${issue ? issue.message : "validation failed"}`, "invalid_args", parsed.error.flatten());
|
|
184
|
+
}
|
|
185
|
+
const req = parsed.data;
|
|
186
|
+
const client = makeClient(args);
|
|
187
|
+
try {
|
|
188
|
+
const res = await client.updatePane(paneId, req);
|
|
189
|
+
printJson(res);
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
failFromError(e);
|
|
193
|
+
}
|
|
194
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ import { runSend, sendHelp } from "./commands/send.js";
|
|
|
32
32
|
import { runWatch, watchHelp } from "./commands/watch.js";
|
|
33
33
|
import { runDelete, deleteHelp } from "./commands/delete.js";
|
|
34
34
|
import { runUpgrade, upgradeHelp } from "./commands/upgrade.js";
|
|
35
|
+
import { runUpdate, updateHelp } from "./commands/update.js";
|
|
35
36
|
import { runParticipant, participantHelp } from "./commands/participant.js";
|
|
36
37
|
import { runShare, shareHelp } from "./commands/share.js";
|
|
37
38
|
import { runTemplate, artifactHelp } from "./commands/template.js";
|
|
@@ -66,6 +67,10 @@ Pane commands (operate on the core noun — a live UI channel):
|
|
|
66
67
|
upgrade <id> Re-pin a live pane to another version of its template —
|
|
67
68
|
swap design + content in place, same URL (--template-version
|
|
68
69
|
<n>, --force to override the schema-compat gate).
|
|
70
|
+
update <id> Edit instance-level fields in place (PATCH /v1/panes/:id):
|
|
71
|
+
--ttl / --expires-at, --title, --preamble, --input-data,
|
|
72
|
+
--metadata, --tags, --icon-emoji, --icon-attachment-id
|
|
73
|
+
(and matching --clear-icon-* flags). Same URL.
|
|
69
74
|
participant Manage participant URLs on an existing pane
|
|
70
75
|
<list|new|revoke> (list | mint a fresh URL | revoke one URL).
|
|
71
76
|
share <id> Share a pane by identity: invite humans by email
|
|
@@ -80,9 +85,14 @@ Other noun groups:
|
|
|
80
85
|
Doubles as an end-to-end smoke test of your install.
|
|
81
86
|
template Reusable, versioned UI templates
|
|
82
87
|
(create | version | update | search | list | show | delete).
|
|
88
|
+
records Per-pane mutable collections — todo lists, checklists,
|
|
89
|
+
kanban cards, comments (list | get | upsert | update |
|
|
90
|
+
delete | delete-collection | watch), keyed by a stable
|
|
91
|
+
record_key.
|
|
83
92
|
template-records Owner-curated content scoped to a Template head
|
|
84
|
-
(list | get | upsert | update | delete
|
|
85
|
-
every pane derived from
|
|
93
|
+
(list | get | upsert | update | delete |
|
|
94
|
+
delete-collection), visible to every pane derived from
|
|
95
|
+
any version of the template.
|
|
86
96
|
key YOUR agent's API key (list | revoke).
|
|
87
97
|
taste YOUR agent's freeform UI taste notes
|
|
88
98
|
(get | set | clear) — presentation preferences the agent
|
|
@@ -153,6 +163,10 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
153
163
|
"list",
|
|
154
164
|
// `pane upgrade --force`: override the strict schema-compat gate.
|
|
155
165
|
"force",
|
|
166
|
+
// `pane update --clear-icon-*`: drop the per-pane icon override (the field
|
|
167
|
+
// becomes null and the pane falls back to the template's icon).
|
|
168
|
+
"clear-icon-emoji",
|
|
169
|
+
"clear-icon-attachment-id",
|
|
156
170
|
]);
|
|
157
171
|
async function main() {
|
|
158
172
|
const rawArgv = process.argv.slice(2);
|
|
@@ -189,6 +203,7 @@ async function main() {
|
|
|
189
203
|
watch: watchHelp,
|
|
190
204
|
delete: deleteHelp,
|
|
191
205
|
upgrade: upgradeHelp,
|
|
206
|
+
update: updateHelp,
|
|
192
207
|
participant: participantHelp,
|
|
193
208
|
share: shareHelp,
|
|
194
209
|
// Other noun groups.
|
|
@@ -246,6 +261,9 @@ async function main() {
|
|
|
246
261
|
case "upgrade":
|
|
247
262
|
await runUpgrade(args);
|
|
248
263
|
break;
|
|
264
|
+
case "update":
|
|
265
|
+
await runUpdate(args);
|
|
266
|
+
break;
|
|
249
267
|
case "participant":
|
|
250
268
|
await runParticipant(args);
|
|
251
269
|
break;
|
package/dist/version.js
CHANGED
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
// Keep this in lockstep with packages/cli/package.json's `version` field;
|
|
9
9
|
// they're consulted in different places (here for the runtime header,
|
|
10
10
|
// package.json for npm publish + dependency resolution).
|
|
11
|
-
export const VERSION = "0.0.
|
|
11
|
+
export const VERSION = "0.0.19";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paneui/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "Command-line client for the Pane relay: create panes, inspect state, send and watch events.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"test:unit": "vitest run"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@paneui/core": "^0.0.
|
|
44
|
+
"@paneui/core": "^0.0.19",
|
|
45
45
|
"qrcode-terminal": "^0.12.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@types/node": "^25.9.
|
|
48
|
+
"@types/node": "^25.9.2",
|
|
49
49
|
"@types/qrcode-terminal": "^0.12.2",
|
|
50
50
|
"typescript": "^6.0.3",
|
|
51
51
|
"vitest": "^4.1.8"
|