@paneui/core 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ that the browser `WebSocket` does not, and the relay protocol relies on it.
16
16
  Because of this, `@paneui/core` is **not** intended to run in a browser or other
17
17
  non-Node runtime as-is.
18
18
 
19
- The HTTP surface (`PaneClient`, `registerAgent`) uses the standard `fetch` API
19
+ The HTTP pane (`PaneClient`, `registerAgent`) uses the standard `fetch` API
20
20
  and is runtime-agnostic; only `openStream` carries the Node constraint.
21
21
 
22
22
  If you need a browser client, treat that as separate future work — it would
@@ -28,4 +28,4 @@ need a `ws`-vs-global-`WebSocket` abstraction rather than the unconditional
28
28
  - `PaneClient` / `PaneApiError` — typed HTTP operations against a relay.
29
29
  - `openStream` — WebSocket stream (replay-on-connect, then live). **Node only.**
30
30
  - `registerAgent` — agent registration helper.
31
- - `artifactSchema`, `callbackSchema`, `createSessionSchema` — Zod schemas.
31
+ - `artifactSchema`, `callbackSchema`, `createPaneSchema` — Zod schemas.
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { TemplateRecord, TemplateSummary, TemplateType, TemplateVersion, CreateArtifactResponse, CreateSessionRequest, CreateSessionResponse, EventsPage, FeedbackPage, FeedbackSubmission, FeedbackType, KeyInfo, MintParticipantResponse, PaneEvent, ParticipantsList, SurfaceState, SurfacesPage, TasteInfo } from "./types.js";
2
- import type { ListSessionsQuery } from "./schemas.js";
1
+ import type { TemplateRecord, TemplateSummary, TemplateType, TemplateVersion, CreateArtifactResponse, CreatePaneRequest, CreatePaneResponse, EventsPage, FeedbackPage, FeedbackSubmission, FeedbackType, KeyInfo, MintParticipantResponse, PaneEvent, ParticipantsList, SerializedRecord, PaneState, PanesPage, TasteInfo, TrashListResponse } from "./types.js";
2
+ import type { ListPanesQuery } from "./schemas.js";
3
3
  export interface ClientOptions {
4
4
  /** Relay base URL, e.g. https://pane.example.com. Trailing slash is trimmed. */
5
5
  url: string;
@@ -27,6 +27,22 @@ export interface RelayResponse {
27
27
  * Request body for POST /v1/templates — create a named, reusable template plus
28
28
  * its v1 content. Mirrors `createArtifactSchema` from ./schemas.js.
29
29
  */
30
+ /** Response from POST /v1/query. */
31
+ export interface QueryResponse {
32
+ /** Ordered column names exactly as DuckDB returned them. */
33
+ columns: string[];
34
+ /** Result rows; each row is an array of values aligned to `columns`. */
35
+ rows: unknown[][];
36
+ /** True if the result was capped by the relay's per-query row cap. */
37
+ truncated: boolean;
38
+ /** Tells the caller which panes the query saw and how it was scoped. */
39
+ scope: {
40
+ kind: "human" | "agent";
41
+ pane_count: number;
42
+ };
43
+ /** Wall-clock milliseconds the relay spent serving the query. */
44
+ elapsed_ms: number;
45
+ }
30
46
  export interface CreateArtifactRequest {
31
47
  name: string;
32
48
  slug?: string;
@@ -36,6 +52,9 @@ export interface CreateArtifactRequest {
36
52
  type: TemplateType;
37
53
  event_schema?: unknown;
38
54
  input_schema?: Record<string, unknown>;
55
+ /** Optional template icon emoji (a single emoji grapheme). Image icons are
56
+ * set post-create via `updateArtifact({ icon_attachment_id })`. */
57
+ icon_emoji?: string;
39
58
  }
40
59
  /**
41
60
  * Request body for POST /v1/templates/:id/versions — append a new immutable
@@ -56,6 +75,11 @@ export interface PatchArtifactMetadataRequest {
56
75
  slug?: string;
57
76
  description?: string;
58
77
  tags?: string[];
78
+ /** Set a single-grapheme emoji icon, or `null` to clear it. */
79
+ icon_emoji?: string | null;
80
+ /** Set the icon to a ready, template-scoped raster image attachment, or
81
+ * `null` to clear it. */
82
+ icon_attachment_id?: string | null;
59
83
  }
60
84
  /**
61
85
  * An error thrown by the typed operations when the relay returns a non-2xx
@@ -98,21 +122,134 @@ export declare class PaneClient {
98
122
  private asObject;
99
123
  /** Throw a PaneApiError from a failed RelayResponse. */
100
124
  private fail;
101
- /** POST /v1/surfaces — create a surface. */
102
- createSession(req: CreateSessionRequest): Promise<CreateSessionResponse>;
103
- /** GET /v1/surfaces/:id — non-blocking surface metadata. */
104
- getSession(surfaceId: string): Promise<SurfaceState>;
125
+ /** POST /v1/panes — create a pane. */
126
+ createPane(req: CreatePaneRequest): Promise<CreatePaneResponse>;
127
+ /** GET /v1/panes/:id — non-blocking pane metadata. */
128
+ getPane(paneId: string): Promise<PaneState>;
105
129
  /**
106
- * GET /v1/surfaces/:id/events — fetch the event log.
130
+ * GET /v1/panes/:id/events — fetch the event log.
107
131
  * `since` is an opaque cursor; `waitSeconds` enables the relay long-poll
108
132
  * (0 = non-blocking, capped at 30 by the relay).
109
133
  */
110
- getEvents(surfaceId: string, opts?: {
134
+ getEvents(paneId: string, opts?: {
111
135
  since?: string | null;
112
136
  waitSeconds?: number;
113
137
  }): Promise<EventsPage>;
114
- /** POST /v1/surfaces/:id/events — append an agent event. */
115
- sendEvent(surfaceId: string, ev: {
138
+ /**
139
+ * POST /v1/query — run a read-only SQL query against the agent's own
140
+ * scoped data (panes, records, events). The relay scopes the result at
141
+ * the view layer; the agent can never see rows whose pane.owner_human_id
142
+ * does not match the caller's scope. Three generic views (panes, records,
143
+ * events) are always exposed; per-collection / per-event-type views are
144
+ * also materialized for any schemas declared on the caller's templates.
145
+ *
146
+ * `opts.paneId` narrows the scope to a single pane — handy when two of
147
+ * the caller's panes declare the same collection with incompatible types
148
+ * and the materializer would otherwise raise view_conflict.
149
+ *
150
+ * `data` is a JSON column — use Postgres-style operators (->>, ->) to
151
+ * project into it. Cap: 10,000 result rows (response.truncated=true
152
+ * signals the cap was hit); statement timeout: 10 seconds.
153
+ */
154
+ query(sql: string, opts?: {
155
+ paneId?: string;
156
+ }): Promise<QueryResponse>;
157
+ /**
158
+ * GET /v1/panes/:id/records/:collection — cursor-paginated list.
159
+ * Includes tombstones (`deleted_at` set) so reconnecting clients can
160
+ * observe deletions.
161
+ */
162
+ listRecords(paneId: string, collection: string, opts?: {
163
+ since?: number;
164
+ limit?: number;
165
+ }): Promise<{
166
+ records: SerializedRecord[];
167
+ next_since: number;
168
+ has_more: boolean;
169
+ }>;
170
+ /**
171
+ * Convenience: walk listRecords until the named recordKey is found OR
172
+ * the collection is exhausted. The relay has no dedicated single-get
173
+ * route today; this client-side scan trades round trips for not adding
174
+ * a route. Fine for typical CLI use; not appropriate for hot paths.
175
+ */
176
+ getRecord(paneId: string, collection: string, recordKey: string): Promise<SerializedRecord | null>;
177
+ /**
178
+ * POST /v1/panes/:id/records/:collection — create-or-return-existing.
179
+ * Duplicate `recordKey` returns the existing row with `deduped: true`.
180
+ */
181
+ upsertRecord(paneId: string, collection: string, body: {
182
+ record_key?: string;
183
+ data: unknown;
184
+ }): Promise<{
185
+ record: SerializedRecord;
186
+ deduped: boolean;
187
+ }>;
188
+ /**
189
+ * PATCH /v1/panes/:id/records/:collection/:recordKey — optimistic
190
+ * update. On 409 the relay returns the current row in `details.current`.
191
+ */
192
+ updateRecord(paneId: string, collection: string, recordKey: string, body: {
193
+ data: unknown;
194
+ if_match?: number;
195
+ }): Promise<{
196
+ record: SerializedRecord;
197
+ }>;
198
+ /**
199
+ * DELETE /v1/panes/:id/records/:collection/:recordKey — soft-delete.
200
+ * Optional `if_match` returns 409 + `details.current` on mismatch.
201
+ */
202
+ deleteRecord(paneId: string, collection: string, recordKey: string, opts?: {
203
+ ifMatch?: number;
204
+ }): Promise<void>;
205
+ /**
206
+ * GET /v1/templates/:id/template-records/:collection — owner-only list of
207
+ * a template's curated records. Same wire shape as listRecords, separate
208
+ * route so the relay can route owner-vs-page access independently.
209
+ */
210
+ listTemplateRecords(templateIdOrSlug: string, collection: string, opts?: {
211
+ since?: number;
212
+ limit?: number;
213
+ }): Promise<{
214
+ records: SerializedRecord[];
215
+ next_since: number;
216
+ has_more: boolean;
217
+ }>;
218
+ /**
219
+ * Client-side scan helper — same shape as `getRecord` but for template
220
+ * records. Walks listTemplateRecords until the key matches.
221
+ */
222
+ getTemplateRecord(templateIdOrSlug: string, collection: string, recordKey: string): Promise<SerializedRecord | null>;
223
+ /**
224
+ * POST /v1/templates/:id/template-records/:collection — owner-only
225
+ * create-or-return-existing.
226
+ */
227
+ upsertTemplateRecord(templateIdOrSlug: string, collection: string, body: {
228
+ record_key?: string;
229
+ data: unknown;
230
+ }): Promise<{
231
+ record: SerializedRecord;
232
+ deduped: boolean;
233
+ }>;
234
+ /**
235
+ * PATCH /v1/templates/:id/template-records/:collection/:recordKey —
236
+ * optimistic-locked update.
237
+ */
238
+ updateTemplateRecord(templateIdOrSlug: string, collection: string, recordKey: string, body: {
239
+ data: unknown;
240
+ if_match?: number;
241
+ }): Promise<{
242
+ record: SerializedRecord;
243
+ }>;
244
+ /**
245
+ * DELETE /v1/templates/:id/template-records/:collection/:recordKey —
246
+ * soft-delete.
247
+ */
248
+ deleteTemplateRecord(templateIdOrSlug: string, collection: string, recordKey: string, opts?: {
249
+ ifMatch?: number;
250
+ }): Promise<void>;
251
+ /** POST /v1/panes/:id/events — append an agent event. */
252
+ sendEvent(paneId: string, ev: {
116
253
  type: string;
117
254
  data: unknown;
118
255
  causationId?: string;
@@ -168,7 +305,7 @@ export declare class PaneClient {
168
305
  * POST /v1/agents/claim — bind this agent to a human via a one-shot
169
306
  * claim code the human generated in their settings UI. After a
170
307
  * successful claim the agent's existing API key continues to work,
171
- * but the agent (and its surfaces/templates) now belong to the
308
+ * but the agent (and its panes/templates) now belong to the
172
309
  * claiming human. One-way operation — there is no unclaim in v1.
173
310
  */
174
311
  claimAgent(code: string): Promise<{
@@ -205,7 +342,7 @@ export declare class PaneClient {
205
342
  submitFeedback(req: {
206
343
  type: FeedbackType;
207
344
  message: string;
208
- surfaceId?: string;
345
+ paneId?: string;
209
346
  }): Promise<FeedbackSubmission>;
210
347
  /**
211
348
  * GET /v1/feedback — the calling agent's own submissions, newest first.
@@ -216,67 +353,143 @@ export declare class PaneClient {
216
353
  before?: string;
217
354
  }): Promise<FeedbackPage>;
218
355
  /**
219
- * GET /v1/surfaces — list the calling agent's surfaces. Default filter is
356
+ * GET /v1/panes — list the calling agent's panes. Default filter is
220
357
  * `status=open` (effective status — respects expiresAt). Response items
221
358
  * carry NO secrets: no participant token plaintext, no callback URL, no
222
359
  * metadata or input_data. Use `participant_id` from the list as the handle
223
360
  * for {@link revokeParticipant}; use {@link mintParticipant} to issue a
224
361
  * fresh URL when the original was lost.
225
362
  */
226
- listSessions(opts?: ListSessionsQuery): Promise<SurfacesPage>;
363
+ listPanes(opts?: ListPanesQuery): Promise<PanesPage>;
227
364
  /**
228
- * GET /v1/surfaces/:id/participants — list every participant on one
229
- * surface (active and revoked). Bounded by MAX_PARTICIPANTS_PER_SESSION
365
+ * GET /v1/panes/:id/participants — list every participant on one
366
+ * pane (active and revoked). Bounded by MAX_PARTICIPANTS_PER_PANE
230
367
  * on the relay, so the full list is returned with no pagination.
231
368
  * Use this to find the `participant_id` you need to pass to
232
369
  * {@link revokeParticipant}, or to audit revoked rows.
233
370
  */
234
- listParticipants(surfaceId: string): Promise<ParticipantsList>;
371
+ listParticipants(paneId: string): Promise<ParticipantsList>;
235
372
  /**
236
- * POST /v1/surfaces/:id/participants — mint a fresh participant URL for an
237
- * existing surface. The one-shot recovery primitive when the original URL
238
- * was dropped: the surface keeps its event log, template pin, and created_at.
373
+ * POST /v1/panes/:id/participants — mint a fresh participant URL for an
374
+ * existing pane. The one-shot recovery primitive when the original URL
375
+ * was dropped: the pane keeps its event log, template pin, and created_at.
239
376
  * v1 supports `kind: "human"` only.
240
377
  *
241
378
  * The plaintext token is returned EXACTLY ONCE in the response — the relay
242
379
  * stores only the hash. Save the response (e.g. pipe to a JSONL log) before
243
380
  * delivering the URL to the human.
244
381
  */
245
- mintParticipant(surfaceId: string, opts?: {
382
+ mintParticipant(paneId: string, opts?: {
246
383
  kind?: "human";
247
384
  }): Promise<MintParticipantResponse>;
248
385
  /**
249
- * DELETE /v1/surfaces/:id/participants/:participant_id — revoke a single
250
- * participant URL. The surface's other participants (and the agent's own
386
+ * DELETE /v1/panes/:id/participants/:participant_id — revoke a single
387
+ * participant URL. The pane's other participants (and the agent's own
251
388
  * WebSocket) are untouched. Idempotent: revoking an unknown or already-
252
389
  * revoked participant returns 204. The agent participant cannot be revoked
253
- * via this endpoint — use {@link deleteSession} instead.
390
+ * via this endpoint — use {@link deletePane} instead.
254
391
  *
255
392
  * Existing WebSocket connections held under the revoked token are NOT
256
393
  * actively kicked in v1; new HTTP and WS connections are refused.
257
394
  */
258
- revokeParticipant(surfaceId: string, participantId: string): Promise<void>;
395
+ revokeParticipant(paneId: string, participantId: string): Promise<void>;
396
+ /**
397
+ * DELETE /v1/panes/:id — close/delete a pane. Idempotent on the relay
398
+ * side (an already-closed pane still returns 204 with no body).
399
+ */
400
+ deletePane(id: string): Promise<void>;
401
+ /**
402
+ * GET /v1/trash — list soft-deleted panes + templates in the caller's
403
+ * agent-scope. The trash UI / `pane trash list` lives off this. (#306)
404
+ */
405
+ listTrash(): Promise<TrashListResponse>;
406
+ /**
407
+ * POST /v1/trash/panes/:id/restore — un-trash a soft-deleted pane (clear
408
+ * deletedAt + audit row). (#306)
409
+ */
410
+ restorePane(id: string): Promise<void>;
259
411
  /**
260
- * DELETE /v1/surfaces/:id — close/delete a surface. Idempotent on the relay
261
- * side (an already-closed surface still returns 204 with no body).
412
+ * POST /v1/trash/templates/:id/restoreun-trash a soft-deleted template. (#306)
262
413
  */
263
- deleteSession(id: string): Promise<void>;
414
+ restoreTemplate(id: string): Promise<void>;
415
+ /**
416
+ * DELETE /v1/trash/panes/:id — permanently hard-delete a trashed pane
417
+ * (bypass retention window). (#306)
418
+ */
419
+ permanentDeletePane(id: string): Promise<void>;
420
+ /**
421
+ * DELETE /v1/trash/templates/:id — permanently hard-delete a trashed
422
+ * template. Refused 409 if a live pane still references one of its
423
+ * versions. (#306)
424
+ */
425
+ permanentDeleteTemplate(id: string): Promise<void>;
264
426
  /**
265
427
  * DELETE /v1/templates/:id — remove an template and (server-side) all its
266
428
  * versions. Strict cascade: the relay refuses with 409 conflict if any
267
- * surface in any state still references one of the template's versions —
268
- * surface that as a typed PaneApiError so the CLI can render a hint
429
+ * pane in any state still references one of the template's versions —
430
+ * pane that as a typed PaneApiError so the CLI can render a hint
269
431
  * instead of swallowing it.
270
432
  */
271
433
  deleteArtifact(idOrSlug: string): Promise<void>;
434
+ /**
435
+ * POST /v1/templates/:id/publish — enter the public catalog. The optional
436
+ * `scopes` list locks the permissions the template will request from
437
+ * installers at this version (see Phase F §4.5 / §8). Passing an empty
438
+ * array clears the stored scopes; omitting `scopes` keeps the existing
439
+ * ones.
440
+ */
441
+ publishTemplate(idOrSlug: string, body?: {
442
+ scopes?: string[];
443
+ }): Promise<{
444
+ id: string;
445
+ slug: string | null;
446
+ name: string | null;
447
+ published_at: string | null;
448
+ scopes: string[];
449
+ install_count: number;
450
+ }>;
451
+ /**
452
+ * POST /v1/templates/:id/unpublish — leave the public catalog. Existing
453
+ * installs are unaffected (humans keep their pinned version), but the
454
+ * template no longer appears in `searchPublicTemplates` results.
455
+ */
456
+ unpublishTemplate(idOrSlug: string): Promise<{
457
+ id: string;
458
+ published_at: string | null;
459
+ }>;
460
+ /**
461
+ * GET /v1/templates/catalog?q=... — agent-side public catalog search.
462
+ * Lets an agent discover already-published apps before authoring a
463
+ * duplicate. Sorted by install_count desc, then publish recency.
464
+ */
465
+ searchPublicTemplates(query?: string, opts?: {
466
+ limit?: number;
467
+ offset?: number;
468
+ }): Promise<{
469
+ items: Array<{
470
+ id: string;
471
+ slug: string | null;
472
+ name: string | null;
473
+ description: string | null;
474
+ tags: string[] | null;
475
+ shape: string;
476
+ scopes: string[];
477
+ published_at: string | null;
478
+ install_count: number;
479
+ latest_version: number;
480
+ }>;
481
+ total: number;
482
+ offset: number;
483
+ limit: number;
484
+ }>;
272
485
  /**
273
486
  * Upload a attachment to the relay. Returns a `AttachmentRef` that can be referenced
274
487
  * in event payloads (the relay's `format: pane-attachment-id` schema vocab
275
488
  * validates the id) or in `pane create --input-data`.
276
489
  *
277
- * Scope defaults to "agent" (reusable across the agent's surfaces). For
278
- * `scope: "surface"` pass `surfaceId`; for `scope: "template"` pass
279
- * `templateId`. The agent must own the referenced surface / template;
490
+ * Scope defaults to "agent" (reusable across the agent's panes). For
491
+ * `scope: "pane"` pass `paneId`; for `scope: "template"` pass
492
+ * `templateId`. The agent must own the referenced pane / template;
280
493
  * cross-tenant attempts return attachment_not_found.
281
494
  *
282
495
  * MIME is inferred from `mime` if supplied; otherwise the relay sniffs
@@ -307,7 +520,7 @@ export declare class PaneClient {
307
520
  }>;
308
521
  /**
309
522
  * Mint a `/b/<token>` capability URL for `attachmentId`. Default TTL is set by
310
- * the relay (24h agent, surface-TTL surface, 30d template). `once: true`
523
+ * the relay (24h agent, pane-TTL pane, 30d template). `once: true`
311
524
  * tokens self-delete on first GET.
312
525
  */
313
526
  mintBlobToken(attachmentId: string, opts?: {
@@ -354,7 +567,7 @@ export declare class PaneClient {
354
567
  /** Per-attachment metadata as returned by `POST /v1/attachments` and friends. */
355
568
  export interface AttachmentRef {
356
569
  attachment_id: string;
357
- scope: "agent" | "surface" | "template";
570
+ scope: "agent" | "pane" | "template";
358
571
  mime: string;
359
572
  size: number;
360
573
  sha256: string;
@@ -363,15 +576,15 @@ export interface AttachmentRef {
363
576
  height?: number | null;
364
577
  filename?: string | null;
365
578
  status?: string;
366
- surface_id?: string | null;
579
+ pane_id?: string | null;
367
580
  template_id?: string | null;
368
581
  created_at?: string;
369
582
  confirmed_at?: string | null;
370
583
  deleted_at?: string | null;
371
584
  }
372
585
  export interface UploadBlobOptions {
373
- scope?: "agent" | "surface" | "template";
374
- surfaceId?: string;
586
+ scope?: "agent" | "pane" | "template";
587
+ paneId?: string;
375
588
  templateId?: string;
376
589
  /** Declared Content-Type. Defaults to `application/octet-stream`. The
377
590
  * relay sniffs leading bytes and may reject with `mime_mismatch`. */
@@ -383,8 +596,8 @@ export interface PresignBlobOptions {
383
596
  mime: string;
384
597
  size: number;
385
598
  sha256: string;
386
- scope?: "agent" | "surface" | "template";
387
- surfaceId?: string;
599
+ scope?: "agent" | "pane" | "template";
600
+ paneId?: string;
388
601
  templateId?: string;
389
602
  filename?: string;
390
603
  }