@homespunapps/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/app-stream.d.ts +68 -0
- package/dist/app-stream.js +131 -0
- package/dist/client.d.ts +508 -0
- package/dist/client.js +619 -0
- package/dist/icons.d.ts +24 -0
- package/dist/icons.js +99 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +9 -0
- package/dist/limits.d.ts +8 -0
- package/dist/limits.js +10 -0
- package/dist/register.d.ts +32 -0
- package/dist/register.js +65 -0
- package/dist/schemas.d.ts +15 -0
- package/dist/schemas.js +24 -0
- package/dist/stream.d.ts +70 -0
- package/dist/stream.js +113 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.js +7 -0
- package/package.json +58 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import type { FeedbackPage, FeedbackSubmission, FeedbackType, KeyInfo, TasteInfo } from "./types.js";
|
|
2
|
+
export interface ClientOptions {
|
|
3
|
+
/** Relay base URL, e.g. https://homespun.example.com. Trailing slash is trimmed. */
|
|
4
|
+
url: string;
|
|
5
|
+
/** Agent API key (bearer token). */
|
|
6
|
+
apiKey: string;
|
|
7
|
+
/** Optional fetch override (defaults to global fetch). */
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
/**
|
|
10
|
+
* Optional client version string sent as `x-homespun-cli-version` on every
|
|
11
|
+
* request. The CLI passes its own `VERSION` constant here so a relay can
|
|
12
|
+
* detect version skew and respond with a `cli_upgrade_required` error
|
|
13
|
+
* (HTTP 426) when the CLI is below the relay's minimum supported version.
|
|
14
|
+
* Library callers (non-CLI) can leave this unset — the header is omitted
|
|
15
|
+
* and the relay treats the request as version-unknown.
|
|
16
|
+
*/
|
|
17
|
+
cliVersion?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Low-level relay response: ok flag, HTTP status, parsed JSON body. */
|
|
20
|
+
export interface RelayResponse {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
status: number;
|
|
23
|
+
data: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** Response from POST /v1/query. */
|
|
26
|
+
export interface QueryResponse {
|
|
27
|
+
/** Ordered column names exactly as DuckDB returned them. */
|
|
28
|
+
columns: string[];
|
|
29
|
+
/** Result rows; each row is an array of values aligned to `columns`. */
|
|
30
|
+
rows: unknown[][];
|
|
31
|
+
/** True if the result was capped by the relay's per-query row cap. */
|
|
32
|
+
truncated: boolean;
|
|
33
|
+
/** Tells the caller which apps the query saw and how it was scoped. */
|
|
34
|
+
scope: {
|
|
35
|
+
kind: "human" | "agent";
|
|
36
|
+
app_count: number;
|
|
37
|
+
};
|
|
38
|
+
/** Wall-clock milliseconds the relay spent serving the query. */
|
|
39
|
+
elapsed_ms: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* An error thrown by the typed operations when the relay returns a non-2xx
|
|
43
|
+
* response (or the request fails outright). Carries the HTTP status and the
|
|
44
|
+
* relay error envelope so callers can branch on `code`.
|
|
45
|
+
*/
|
|
46
|
+
export declare class HomespunApiError extends Error {
|
|
47
|
+
readonly status: number;
|
|
48
|
+
readonly code: string;
|
|
49
|
+
readonly details: unknown;
|
|
50
|
+
/** Agent-friendly remediation hint, when the relay supplies one. */
|
|
51
|
+
readonly hint?: string;
|
|
52
|
+
/** Whether retrying the same request may succeed (e.g. 429). */
|
|
53
|
+
readonly retryable?: boolean;
|
|
54
|
+
/** Documentation URL for this error class (mapped from the wire's `docs_url`). */
|
|
55
|
+
readonly docsUrl?: string;
|
|
56
|
+
constructor(status: number, code: string, message: string, details?: unknown, extra?: {
|
|
57
|
+
hint?: string;
|
|
58
|
+
retryable?: boolean;
|
|
59
|
+
docsUrl?: string;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export declare class HomespunClient {
|
|
63
|
+
private readonly base;
|
|
64
|
+
private readonly apiKey;
|
|
65
|
+
private readonly fetchImpl;
|
|
66
|
+
private readonly cliVersion;
|
|
67
|
+
constructor(opts: ClientOptions);
|
|
68
|
+
/** Relay base URL (trailing slash trimmed). */
|
|
69
|
+
get baseUrl(): string;
|
|
70
|
+
/** WebSocket base URL derived from the relay base URL (http→ws, https→wss). */
|
|
71
|
+
get wsBaseUrl(): string;
|
|
72
|
+
/**
|
|
73
|
+
* Low-level HTTP helper. Mirrors the relay API contract: Bearer auth,
|
|
74
|
+
* JSON bodies, 204 handled. Never throws on non-2xx — returns `ok: false`.
|
|
75
|
+
* Network failures return `{ ok: false, status: 0, ... }`.
|
|
76
|
+
*/
|
|
77
|
+
call(method: string, path: string, body?: object): Promise<RelayResponse>;
|
|
78
|
+
/** Assert a 2xx body is a non-null object before treating it as typed JSON. */
|
|
79
|
+
private asObject;
|
|
80
|
+
/** Throw a HomespunApiError from a failed RelayResponse. */
|
|
81
|
+
private fail;
|
|
82
|
+
/**
|
|
83
|
+
* GET /v1/keys — the calling agent's own key info. The relay scopes this to
|
|
84
|
+
* the authenticated agent: it returns one key (the caller's), not a list.
|
|
85
|
+
*/
|
|
86
|
+
listKeys(): Promise<KeyInfo>;
|
|
87
|
+
/**
|
|
88
|
+
* DELETE /v1/keys/:id — revoke an API key. The relay only permits revoking
|
|
89
|
+
* the caller's OWN key (any other id is rejected 403): this is a
|
|
90
|
+
* self-destruct. Returns 204 with no body on success.
|
|
91
|
+
*/
|
|
92
|
+
revokeKey(id: string): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* POST /v1/agents/claim — bind this agent to a human via a one-shot
|
|
95
|
+
* claim code the human generated in their settings UI. After a
|
|
96
|
+
* successful claim the agent's existing API key continues to work,
|
|
97
|
+
* but the agent (and its apps/templates) now belong to the
|
|
98
|
+
* claiming human. One-way operation — there is no unclaim in v1.
|
|
99
|
+
*/
|
|
100
|
+
claimAgent(code: string): Promise<{
|
|
101
|
+
ok: true;
|
|
102
|
+
owner_human_id: string;
|
|
103
|
+
claimed_at: string;
|
|
104
|
+
}>;
|
|
105
|
+
/**
|
|
106
|
+
* GET /v1/taste — the calling agent's freeform "taste notes" markdown attachment:
|
|
107
|
+
* presentation preferences the agent has picked up from human feedback over
|
|
108
|
+
* time. Returns `{ taste: null, updated_at: null, bytes: 0 }` when the
|
|
109
|
+
* agent has never written notes. Read this before generating an template so
|
|
110
|
+
* the agent applies prior feedback.
|
|
111
|
+
*/
|
|
112
|
+
getTaste(): Promise<TasteInfo>;
|
|
113
|
+
/**
|
|
114
|
+
* PUT /v1/taste — whole-attachment replace of the calling agent's taste notes.
|
|
115
|
+
* Empty/whitespace-only values are rejected by the relay; callers asking to
|
|
116
|
+
* clear must use {@link clearTaste}. The relay caps the payload at the
|
|
117
|
+
* server's `MAX_TASTE_BYTES` (utf8 bytes).
|
|
118
|
+
*/
|
|
119
|
+
setTaste(taste: string): Promise<TasteInfo>;
|
|
120
|
+
/**
|
|
121
|
+
* DELETE /v1/taste — clear the calling agent's taste notes (idempotent on
|
|
122
|
+
* the relay; clearing already-empty notes still succeeds). Returns 204 with
|
|
123
|
+
* no body.
|
|
124
|
+
*/
|
|
125
|
+
clearTaste(): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* POST /v1/feedback — submit a one-shot bug report, feature request, or
|
|
128
|
+
* note to the relay operator. Returns the new row's id, type, and
|
|
129
|
+
* created_at; the message is not echoed.
|
|
130
|
+
*/
|
|
131
|
+
submitFeedback(req: {
|
|
132
|
+
type: FeedbackType;
|
|
133
|
+
message: string;
|
|
134
|
+
appId?: string;
|
|
135
|
+
}): Promise<FeedbackSubmission>;
|
|
136
|
+
/**
|
|
137
|
+
* GET /v1/feedback — the calling agent's own submissions, newest first.
|
|
138
|
+
* `before` is an opaque cursor from a previous page's `next_before`.
|
|
139
|
+
*/
|
|
140
|
+
listFeedback(opts?: {
|
|
141
|
+
limit?: number;
|
|
142
|
+
before?: string;
|
|
143
|
+
}): Promise<FeedbackPage>;
|
|
144
|
+
/**
|
|
145
|
+
* Upload a attachment to the relay. Returns a `AttachmentRef` that can be referenced
|
|
146
|
+
* in event payloads (the relay's `format: homespun-attachment-id` schema vocab
|
|
147
|
+
* validates the id) or in `homespun create --input-data`.
|
|
148
|
+
*
|
|
149
|
+
* Scope defaults to "agent" (reusable). For `scope: "app"` pass `appId`.
|
|
150
|
+
* The calling agent's owning human must own the referenced App;
|
|
151
|
+
* cross-tenant attempts return app_not_found.
|
|
152
|
+
*
|
|
153
|
+
* MIME is inferred from `mime` if supplied; otherwise the relay sniffs
|
|
154
|
+
* leading bytes and may reject with mime_mismatch / mime_disallowed.
|
|
155
|
+
*
|
|
156
|
+
* Backed by the relay's multipart `POST /v1/attachments` (the fallback path).
|
|
157
|
+
* For large uploads (>1 MB on hosted Azure) call `presignBlob()` +
|
|
158
|
+
* `confirmBlob()` instead — those use SAS direct-to-storage and don't
|
|
159
|
+
* stream bytes through the relay.
|
|
160
|
+
*/
|
|
161
|
+
uploadBlob(file: Blob | Buffer | Uint8Array, opts?: UploadBlobOptions): Promise<AttachmentRef>;
|
|
162
|
+
/** GET /v1/attachments/:id — download bytes as an ArrayBuffer. */
|
|
163
|
+
downloadBlob(attachmentId: string): Promise<ArrayBuffer>;
|
|
164
|
+
/**
|
|
165
|
+
* GET a attachment's metadata only — useful before downloading large attachments, or
|
|
166
|
+
* for `homespun attachment show <id>` which doesn't want the bytes. Returns the full
|
|
167
|
+
* AttachmentRef (the same shape POST /v1/attachments returns): id, scope, mime, size,
|
|
168
|
+
* sha256, filename, width, height, status, scope FKs, timestamps.
|
|
169
|
+
*
|
|
170
|
+
* Backed by GET /v1/attachments/:id/metadata which serves the JSON AttachmentRef
|
|
171
|
+
* without streaming the bytes — cheap on the relay and avoids the
|
|
172
|
+
* encrypt-at-rest decrypt cost when only the metadata is needed.
|
|
173
|
+
*/
|
|
174
|
+
getBlob(attachmentId: string): Promise<AttachmentRef>;
|
|
175
|
+
/** DELETE /v1/attachments/:id — soft-delete (idempotent). */
|
|
176
|
+
deleteBlob(attachmentId: string): Promise<{
|
|
177
|
+
deleted: true;
|
|
178
|
+
}>;
|
|
179
|
+
/**
|
|
180
|
+
* Mint a `/b/<token>` capability URL for `attachmentId`. Default TTL is set by
|
|
181
|
+
* the relay (24h agent-scope, 30d app-scope). `once: true`
|
|
182
|
+
* tokens self-delete on first GET.
|
|
183
|
+
*/
|
|
184
|
+
mintBlobToken(attachmentId: string, opts?: {
|
|
185
|
+
ttlSeconds?: number;
|
|
186
|
+
once?: boolean;
|
|
187
|
+
}): Promise<AttachmentTokenMintResponse>;
|
|
188
|
+
/** Revoke a previously-minted token. Idempotent. */
|
|
189
|
+
revokeBlobToken(attachmentId: string, tokenId: string): Promise<{
|
|
190
|
+
token_id: string;
|
|
191
|
+
revoked: true;
|
|
192
|
+
}>;
|
|
193
|
+
/**
|
|
194
|
+
* GET /v1/attachments — list YOUR agent's non-deleted attachments (newest first).
|
|
195
|
+
* Paginated via opaque cursor: when `next_cursor` is non-null, pass it
|
|
196
|
+
* back as `cursor` on the next call.
|
|
197
|
+
*/
|
|
198
|
+
listBlobs(opts?: ListBlobsOptions): Promise<{
|
|
199
|
+
items: AttachmentRef[];
|
|
200
|
+
next_cursor: string | null;
|
|
201
|
+
}>;
|
|
202
|
+
/**
|
|
203
|
+
* GET /v1/attachments/:id/tokens — enumerate the capability tokens minted
|
|
204
|
+
* against one attachment, including revoked rows (for audit). The plaintext
|
|
205
|
+
* token is NEVER returned — it isn't stored, only its sha256 is.
|
|
206
|
+
*/
|
|
207
|
+
listBlobTokens(attachmentId: string): Promise<AttachmentTokenListResponse>;
|
|
208
|
+
/**
|
|
209
|
+
* Issue a presigned PUT URL for direct-to-storage upload. Returns the
|
|
210
|
+
* upload URL + the attachment_id (already reserved in the relay's DB with
|
|
211
|
+
* status=pending) + expiry. After PUTting the bytes to the URL, call
|
|
212
|
+
* `confirmBlob(attachment_id)` to finalise.
|
|
213
|
+
*
|
|
214
|
+
* Filesystem backend returns 501 not_implemented — use uploadBlob()
|
|
215
|
+
* (multipart fallback) instead. Azure backend returns a SAS URL.
|
|
216
|
+
*/
|
|
217
|
+
presignBlob(opts: PresignBlobOptions): Promise<{
|
|
218
|
+
attachment_id: string;
|
|
219
|
+
upload_url: string;
|
|
220
|
+
expires_at: string;
|
|
221
|
+
}>;
|
|
222
|
+
/** Finalise a presigned upload — relay HEADs the bytes, verifies, flips ready. */
|
|
223
|
+
confirmBlob(attachmentId: string): Promise<AttachmentRef>;
|
|
224
|
+
/** POST /v1/apps — create (deploy) a new App + its first AppVersion. */
|
|
225
|
+
deployApp(req: DeployAppRequest): Promise<DeployAppResponse>;
|
|
226
|
+
/** POST /v1/apps/:id/versions — redeploy (compat-gated unless force). */
|
|
227
|
+
redeployApp(appId: string, req: RedeployAppRequest): Promise<RedeployAppResponse>;
|
|
228
|
+
/**
|
|
229
|
+
* GET /v1/apps — list apps scoped to the calling agent's owning human.
|
|
230
|
+
* `slug` is an exact-match filter — the one case a caller resolves a
|
|
231
|
+
* human-given slug to an id (see the class-level note above).
|
|
232
|
+
*/
|
|
233
|
+
listApps(opts?: {
|
|
234
|
+
status?: "active" | "dormant" | "archived" | "all";
|
|
235
|
+
limit?: number;
|
|
236
|
+
cursor?: string;
|
|
237
|
+
slug?: string;
|
|
238
|
+
}): Promise<AppsPage>;
|
|
239
|
+
/** GET /v1/apps/:id — full app detail (manifest, current_version, quota). */
|
|
240
|
+
getApp(appId: string): Promise<AppDetail>;
|
|
241
|
+
/** PATCH /v1/apps/:id — update visibility (the only mutable app field). */
|
|
242
|
+
updateApp(appId: string, visibility: "private" | "link" | "public"): Promise<{
|
|
243
|
+
id: string;
|
|
244
|
+
visibility: string;
|
|
245
|
+
}>;
|
|
246
|
+
/** DELETE /v1/apps/:id — soft-delete (idempotent). */
|
|
247
|
+
deleteApp(appId: string): Promise<void>;
|
|
248
|
+
/** POST /v1/apps/:id/wake — wake a dormant app; a no-op on a non-dormant one. */
|
|
249
|
+
wakeApp(appId: string): Promise<{
|
|
250
|
+
id: string;
|
|
251
|
+
status: string;
|
|
252
|
+
}>;
|
|
253
|
+
/**
|
|
254
|
+
* POST /v1/apps/:id/members — add or invite a member by email. If a Human
|
|
255
|
+
* already exists for the email, the member row is created immediately
|
|
256
|
+
* (`{ member }`); otherwise the relay mints a signed invite and emails a
|
|
257
|
+
* magic link (`{ ok: true, invited, expires_at }`). 503s if the relay has
|
|
258
|
+
* EMAIL_PROVIDER=none (no invite-email path available).
|
|
259
|
+
*/
|
|
260
|
+
addAppMember(appId: string, opts: {
|
|
261
|
+
email: string;
|
|
262
|
+
role?: "member";
|
|
263
|
+
}): Promise<AddAppMemberResult>;
|
|
264
|
+
/** GET /v1/apps/:id/members — list the app's owner + member rows. */
|
|
265
|
+
listAppMembers(appId: string): Promise<{
|
|
266
|
+
members: AppMember[];
|
|
267
|
+
}>;
|
|
268
|
+
/**
|
|
269
|
+
* DELETE /v1/apps/:id/members/:humanId — remove a member (idempotent);
|
|
270
|
+
* cascades to revoke that human's live app sessions. The app owner cannot
|
|
271
|
+
* be removed — the relay refuses with a 409 conflict.
|
|
272
|
+
*/
|
|
273
|
+
removeAppMember(appId: string, humanId: string): Promise<void>;
|
|
274
|
+
/** GET /v1/apps/:id/collections/:name — list rows (current-state page). */
|
|
275
|
+
listAppRows(appId: string, collection: string, opts?: {
|
|
276
|
+
since?: string;
|
|
277
|
+
limit?: number;
|
|
278
|
+
}): Promise<AppRowsPage>;
|
|
279
|
+
/**
|
|
280
|
+
* GET /v1/apps/:id/collections/:name/:key — read one row. A dedicated
|
|
281
|
+
* route (not a client-side scan like v1's `getRecord`) — spec-cli §8
|
|
282
|
+
* ruling 3 confirms it as a first-class relay route.
|
|
283
|
+
*/
|
|
284
|
+
getAppRow(appId: string, collection: string, key: string): Promise<{
|
|
285
|
+
row: AppRow;
|
|
286
|
+
}>;
|
|
287
|
+
/**
|
|
288
|
+
* POST /v1/apps/:id/collections/:name — upsert (create, or return the
|
|
289
|
+
* existing row when `key` collides — `deduped: true`). §8 ruling 4: this is
|
|
290
|
+
* the ONLY create-shaped verb — there is no strict create that errors on
|
|
291
|
+
* an existing key.
|
|
292
|
+
*/
|
|
293
|
+
upsertAppRow(appId: string, collection: string, body: {
|
|
294
|
+
key?: string;
|
|
295
|
+
data: unknown;
|
|
296
|
+
}): Promise<{
|
|
297
|
+
row: AppRow;
|
|
298
|
+
deduped?: true;
|
|
299
|
+
}>;
|
|
300
|
+
/** PATCH /v1/apps/:id/collections/:name/:key — optimistic-locked update. */
|
|
301
|
+
updateAppRow(appId: string, collection: string, key: string, body: {
|
|
302
|
+
data: unknown;
|
|
303
|
+
if_match?: number;
|
|
304
|
+
}): Promise<{
|
|
305
|
+
row: AppRow;
|
|
306
|
+
}>;
|
|
307
|
+
/** DELETE /v1/apps/:id/collections/:name/:key — soft-delete. */
|
|
308
|
+
deleteAppRow(appId: string, collection: string, key: string, opts?: {
|
|
309
|
+
ifMatch?: number;
|
|
310
|
+
}): Promise<void>;
|
|
311
|
+
/**
|
|
312
|
+
* GET /v1/apps/:id/feed — change-feed catch-up; the long-poll fallback
|
|
313
|
+
* transport for `watch` (spec-cli §2.3/§5). `wait` (0-30s) long-polls when
|
|
314
|
+
* the caller is already caught up.
|
|
315
|
+
*/
|
|
316
|
+
getAppFeed(appId: string, opts: {
|
|
317
|
+
since: number;
|
|
318
|
+
limit?: number;
|
|
319
|
+
wait?: number;
|
|
320
|
+
}): Promise<AppFeedPage>;
|
|
321
|
+
}
|
|
322
|
+
/** Request body for `POST /v1/apps` — create (deploy) a new App. */
|
|
323
|
+
export interface DeployAppRequest {
|
|
324
|
+
html: string;
|
|
325
|
+
manifest: unknown;
|
|
326
|
+
/** Defaults to "private" server-side when omitted. */
|
|
327
|
+
visibility?: "private" | "link" | "public";
|
|
328
|
+
/** Accepted for visibility public|private (including the omitted default); rejected for link. */
|
|
329
|
+
slug?: string;
|
|
330
|
+
}
|
|
331
|
+
export interface DeployAppResponse {
|
|
332
|
+
app_id: string;
|
|
333
|
+
slug: string;
|
|
334
|
+
visibility: string;
|
|
335
|
+
url: string;
|
|
336
|
+
version: number;
|
|
337
|
+
created: true;
|
|
338
|
+
}
|
|
339
|
+
/** Request body for `POST /v1/apps/:id/versions` — redeploy (compat-gated). */
|
|
340
|
+
export interface RedeployAppRequest {
|
|
341
|
+
html: string;
|
|
342
|
+
manifest: unknown;
|
|
343
|
+
/** Bypass the compat gate; a narrowing collection is detached, not deleted. */
|
|
344
|
+
force?: boolean;
|
|
345
|
+
}
|
|
346
|
+
export interface RedeployAppResponse {
|
|
347
|
+
app_id: string;
|
|
348
|
+
version: number;
|
|
349
|
+
compat: "clean" | "forced";
|
|
350
|
+
breaks?: Array<{
|
|
351
|
+
path: string;
|
|
352
|
+
message: string;
|
|
353
|
+
}>;
|
|
354
|
+
}
|
|
355
|
+
/** Lean per-app summary — the shape `listApps` returns. */
|
|
356
|
+
export interface AppSummary {
|
|
357
|
+
id: string;
|
|
358
|
+
slug: string;
|
|
359
|
+
visibility: string;
|
|
360
|
+
status: string;
|
|
361
|
+
url: string;
|
|
362
|
+
created_at: string;
|
|
363
|
+
last_activity_at: string;
|
|
364
|
+
}
|
|
365
|
+
/** Full app detail — the shape `getApp` returns. */
|
|
366
|
+
export interface AppDetail extends AppSummary {
|
|
367
|
+
manifest: unknown;
|
|
368
|
+
current_version: number | null;
|
|
369
|
+
owner_human_id: string;
|
|
370
|
+
row_count: number;
|
|
371
|
+
storage_bytes: string;
|
|
372
|
+
}
|
|
373
|
+
export interface AppsPage {
|
|
374
|
+
items: AppSummary[];
|
|
375
|
+
next_cursor: string | null;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* One member row — the shape `listAppMembers` returns and the `member` half
|
|
379
|
+
* of `addAppMember`'s union returns. Fields are camelCase on the wire (see
|
|
380
|
+
* the app-membership section above for why).
|
|
381
|
+
*/
|
|
382
|
+
export interface AppMember {
|
|
383
|
+
humanId: string;
|
|
384
|
+
email: string;
|
|
385
|
+
role: string;
|
|
386
|
+
createdAt: string;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* `POST /v1/apps/:id/members` response — either the member was attached
|
|
390
|
+
* immediately (existing Human) or an invite email was sent (no Human yet).
|
|
391
|
+
*/
|
|
392
|
+
export type AddAppMemberResult = {
|
|
393
|
+
member: AppMember;
|
|
394
|
+
} | {
|
|
395
|
+
ok: true;
|
|
396
|
+
invited: string;
|
|
397
|
+
expires_at: string;
|
|
398
|
+
};
|
|
399
|
+
/** One row in an app collection — the shape every row CRUD op returns. */
|
|
400
|
+
export interface AppRow {
|
|
401
|
+
key: string;
|
|
402
|
+
data: unknown;
|
|
403
|
+
version: number;
|
|
404
|
+
author: {
|
|
405
|
+
kind: string;
|
|
406
|
+
id: string;
|
|
407
|
+
};
|
|
408
|
+
created_at: string;
|
|
409
|
+
updated_at: string;
|
|
410
|
+
deleted_at: string | null;
|
|
411
|
+
}
|
|
412
|
+
export interface AppRowsPage {
|
|
413
|
+
rows: AppRow[];
|
|
414
|
+
next_cursor: string | null;
|
|
415
|
+
has_more: boolean;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* One entry in an app's change feed — the SAME shape whether it arrives via
|
|
419
|
+
* `GET /v1/apps/:id/feed` (long-poll) or the `/_hs/ws` live/batch frames
|
|
420
|
+
* (openAppStream, app-stream.ts) — the CLI's `apps watch` prints this object
|
|
421
|
+
* unchanged regardless of which transport served it (spec-cli §3.4/§5).
|
|
422
|
+
*/
|
|
423
|
+
export interface AppFeedEntry {
|
|
424
|
+
seq: number;
|
|
425
|
+
op: string;
|
|
426
|
+
collection_name: string;
|
|
427
|
+
row_key: string;
|
|
428
|
+
row_version?: number | null;
|
|
429
|
+
data: unknown;
|
|
430
|
+
author: {
|
|
431
|
+
kind: string;
|
|
432
|
+
id: string;
|
|
433
|
+
};
|
|
434
|
+
ts: string;
|
|
435
|
+
}
|
|
436
|
+
export interface AppFeedPage {
|
|
437
|
+
entries: AppFeedEntry[];
|
|
438
|
+
cursor: number;
|
|
439
|
+
truncated: boolean;
|
|
440
|
+
}
|
|
441
|
+
/** Per-attachment metadata as returned by `POST /v1/attachments` and friends. */
|
|
442
|
+
export interface AttachmentRef {
|
|
443
|
+
attachment_id: string;
|
|
444
|
+
scope: "agent" | "app";
|
|
445
|
+
mime: string;
|
|
446
|
+
size: number;
|
|
447
|
+
sha256: string;
|
|
448
|
+
url?: string;
|
|
449
|
+
width?: number | null;
|
|
450
|
+
height?: number | null;
|
|
451
|
+
filename?: string | null;
|
|
452
|
+
status?: string;
|
|
453
|
+
app_id?: string | null;
|
|
454
|
+
created_at?: string;
|
|
455
|
+
confirmed_at?: string | null;
|
|
456
|
+
deleted_at?: string | null;
|
|
457
|
+
}
|
|
458
|
+
export interface UploadBlobOptions {
|
|
459
|
+
scope?: "agent" | "app";
|
|
460
|
+
appId?: string;
|
|
461
|
+
/** Declared Content-Type. Defaults to `application/octet-stream`. The
|
|
462
|
+
* relay sniffs leading bytes and may reject with `mime_mismatch`. */
|
|
463
|
+
mime?: string;
|
|
464
|
+
/** Optional display name (the relay records it for UX; never a path component). */
|
|
465
|
+
filename?: string;
|
|
466
|
+
}
|
|
467
|
+
export interface PresignBlobOptions {
|
|
468
|
+
mime: string;
|
|
469
|
+
size: number;
|
|
470
|
+
sha256: string;
|
|
471
|
+
scope?: "agent" | "app";
|
|
472
|
+
appId?: string;
|
|
473
|
+
filename?: string;
|
|
474
|
+
}
|
|
475
|
+
export interface AttachmentTokenMintResponse {
|
|
476
|
+
token_id: string;
|
|
477
|
+
token: string;
|
|
478
|
+
token_prefix: string;
|
|
479
|
+
url: string;
|
|
480
|
+
expires_at: string;
|
|
481
|
+
once: boolean;
|
|
482
|
+
}
|
|
483
|
+
/** Options for `listBlobs()` — opaque cursor + page-size knob. */
|
|
484
|
+
export interface ListBlobsOptions {
|
|
485
|
+
/** Opaque pagination cursor from a prior `next_cursor`. */
|
|
486
|
+
cursor?: string;
|
|
487
|
+
/** Page size; relay clamps to 1..100. Defaults to the relay default (50). */
|
|
488
|
+
limit?: number;
|
|
489
|
+
}
|
|
490
|
+
/** One row in the response from `listBlobTokens()`. */
|
|
491
|
+
export interface AttachmentTokenAuditEntry {
|
|
492
|
+
token_id: string;
|
|
493
|
+
token_prefix: string;
|
|
494
|
+
expires_at: string;
|
|
495
|
+
once: boolean;
|
|
496
|
+
created_at: string;
|
|
497
|
+
last_used_at: string | null;
|
|
498
|
+
use_count: number;
|
|
499
|
+
/** Non-null when the token has been revoked. Expired-but-unrevoked rows
|
|
500
|
+
* carry `revoked_at: null` and an `expires_at` in the past — both are
|
|
501
|
+
* useful for audit. */
|
|
502
|
+
revoked_at: string | null;
|
|
503
|
+
}
|
|
504
|
+
/** Shape returned by `listBlobTokens()`. */
|
|
505
|
+
export interface AttachmentTokenListResponse {
|
|
506
|
+
attachment_id: string;
|
|
507
|
+
items: AttachmentTokenAuditEntry[];
|
|
508
|
+
}
|