@homespunapps/core 1.0.1 → 1.4.2
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/dist/client.d.ts +620 -4
- package/dist/client.js +362 -5
- package/dist/index.d.ts +1 -1
- package/package.json +11 -6
package/dist/client.d.ts
CHANGED
|
@@ -90,6 +90,20 @@ export declare class HomespunClient {
|
|
|
90
90
|
* self-destruct. Returns 204 with no body on success.
|
|
91
91
|
*/
|
|
92
92
|
revokeKey(id: string): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* POST /v1/keys: mint a NEW sibling API key for the calling agent's OWN
|
|
95
|
+
* identity. The relay derives the identity from the bearer token, so a caller
|
|
96
|
+
* can only ever mint a sibling of itself (never another agent's key). The new
|
|
97
|
+
* key has the same scope/ownership as the caller and shows up in a subsequent
|
|
98
|
+
* `listKeys()` call made WITH the new key.
|
|
99
|
+
*
|
|
100
|
+
* The raw `api_key` is returned exactly ONCE in this response and is never
|
|
101
|
+
* retrievable again (only its hash is stored). Bootstraps a fresh CLI /
|
|
102
|
+
* process credential from an MCP-driven agent that has no key of its own to
|
|
103
|
+
* hand off. The owner can revoke any minted sibling via the normal revoke
|
|
104
|
+
* path.
|
|
105
|
+
*/
|
|
106
|
+
mintKey(): Promise<KeyMintResult>;
|
|
93
107
|
/**
|
|
94
108
|
* POST /v1/agents/claim — bind this agent to a human via a one-shot
|
|
95
109
|
* claim code the human generated in their settings UI. After a
|
|
@@ -258,6 +272,16 @@ export declare class HomespunClient {
|
|
|
258
272
|
deployApp(req: DeployAppRequest): Promise<DeployAppResponse>;
|
|
259
273
|
/** POST /v1/apps/:id/versions — redeploy (compat-gated unless force). */
|
|
260
274
|
redeployApp(appId: string, req: RedeployAppRequest): Promise<RedeployAppResponse>;
|
|
275
|
+
/**
|
|
276
|
+
* Dry-run a deploy: validate the manifest + asset shapes, run the compat gate
|
|
277
|
+
* (for a redeploy) and compute the deploy advisories, WITHOUT creating a
|
|
278
|
+
* version or mutating anything. Posts the deploy body with `dry_run: true` to
|
|
279
|
+
* the same route the real deploy uses (`POST /v1/apps` for a create check,
|
|
280
|
+
* `POST /v1/apps/:id/versions` for a redeploy check), so the relay returns the
|
|
281
|
+
* SAME validation error a real deploy would for an invalid manifest, and the
|
|
282
|
+
* SAME warnings/compat result for a valid one. Nothing is persisted.
|
|
283
|
+
*/
|
|
284
|
+
checkDeploy(req: DeployCheckRequest): Promise<DeployCheckResult>;
|
|
261
285
|
/**
|
|
262
286
|
* GET /v1/apps — list apps scoped to the calling agent's owning human.
|
|
263
287
|
* `slug` is an exact-match filter — the one case a caller resolves a
|
|
@@ -271,10 +295,35 @@ export declare class HomespunClient {
|
|
|
271
295
|
}): Promise<AppsPage>;
|
|
272
296
|
/** GET /v1/apps/:id — full app detail (manifest, current_version, quota). */
|
|
273
297
|
getApp(appId: string): Promise<AppDetail>;
|
|
274
|
-
/**
|
|
275
|
-
|
|
298
|
+
/**
|
|
299
|
+
* PATCH /v1/apps/:id updates the app's mutable settings. Both fields are
|
|
300
|
+
* optional but at least one must be given: `visibility` (private|link|public)
|
|
301
|
+
* and/or `timezone` (an IANA zone name like "Europe/Berlin", used for
|
|
302
|
+
* `schedules` reminders). Returns the updated `{ id, visibility, timezone }`.
|
|
303
|
+
* A transition INTO `link` visibility also returns a `share_url` carrying the
|
|
304
|
+
* app's freshly minted share token in its `#k=` fragment, shown ONCE (the
|
|
305
|
+
* token is hashed at rest and never recoverable). A transition AWAY from
|
|
306
|
+
* `link` clears the token (every prior share URL stops working).
|
|
307
|
+
*/
|
|
308
|
+
updateApp(appId: string, update: {
|
|
309
|
+
visibility?: "private" | "link" | "public";
|
|
310
|
+
timezone?: string;
|
|
311
|
+
}): Promise<{
|
|
276
312
|
id: string;
|
|
277
313
|
visibility: string;
|
|
314
|
+
timezone: string | null;
|
|
315
|
+
share_url?: string;
|
|
316
|
+
}>;
|
|
317
|
+
/**
|
|
318
|
+
* POST /v1/apps/:id/share-link/rotate rotates a `link` app's share token.
|
|
319
|
+
* Mints a fresh token and returns the new `share_url` (its `#k=` fragment
|
|
320
|
+
* carries the raw token, shown ONCE). Rotating instantly revokes the previous
|
|
321
|
+
* share URL and every pass derived from it. Also serves as "generate" for a
|
|
322
|
+
* `link` app that has no share token yet. Only valid for a `link` app; a
|
|
323
|
+
* public/private app returns a conflict.
|
|
324
|
+
*/
|
|
325
|
+
rotateShareLink(appId: string): Promise<{
|
|
326
|
+
share_url: string;
|
|
278
327
|
}>;
|
|
279
328
|
/** DELETE /v1/apps/:id — soft-delete (idempotent). */
|
|
280
329
|
deleteApp(appId: string): Promise<void>;
|
|
@@ -310,21 +359,107 @@ export declare class HomespunClient {
|
|
|
310
359
|
addAppMember(appId: string, opts: {
|
|
311
360
|
email: string;
|
|
312
361
|
role?: "member";
|
|
362
|
+
customRole?: string;
|
|
313
363
|
}): Promise<AddAppMemberResult>;
|
|
314
364
|
/** GET /v1/apps/:id/members — list the app's owner + member rows. */
|
|
315
365
|
listAppMembers(appId: string): Promise<{
|
|
316
366
|
members: AppMember[];
|
|
317
367
|
}>;
|
|
368
|
+
/**
|
|
369
|
+
* PATCH /v1/apps/:id/members/:humanId — change an existing member's custom
|
|
370
|
+
* role in place. `customRole: null` clears it back to a plain member. The
|
|
371
|
+
* role must be one the app's manifest declares (a reserved base role or an
|
|
372
|
+
* undeclared name is a 400), and the app owner's own membership cannot be
|
|
373
|
+
* re-roled.
|
|
374
|
+
*
|
|
375
|
+
* Unlike remove-then-re-add, this does NOT revoke the member's live app
|
|
376
|
+
* sessions: they stay signed in and pick up the new role on their next
|
|
377
|
+
* request. Roles are not cached: they are re-derived from the Member row on
|
|
378
|
+
* every request, so a downgrade takes effect immediately and a live session
|
|
379
|
+
* can never escalate.
|
|
380
|
+
*/
|
|
381
|
+
setAppMemberRole(appId: string, humanId: string, opts: {
|
|
382
|
+
customRole: string | null;
|
|
383
|
+
}): Promise<{
|
|
384
|
+
member: AppMember;
|
|
385
|
+
}>;
|
|
386
|
+
/**
|
|
387
|
+
* GET /v1/apps/:id/roles — the derived roles summary: every custom role the
|
|
388
|
+
* app's manifest declares, what holding it actually allows per collection
|
|
389
|
+
* (reported separately for members and for grant-link holders, whose role
|
|
390
|
+
* floors differ), and how many members and live grant links hold it.
|
|
391
|
+
* Read-only and computed on the fly; permissions themselves stay
|
|
392
|
+
* manifest-declared. An app that declares no roles returns an empty list.
|
|
393
|
+
*/
|
|
394
|
+
listAppRoles(appId: string): Promise<{
|
|
395
|
+
roles: AppRoleSummary[];
|
|
396
|
+
}>;
|
|
318
397
|
/**
|
|
319
398
|
* DELETE /v1/apps/:id/members/:humanId — remove a member (idempotent);
|
|
320
399
|
* cascades to revoke that human's live app sessions. The app owner cannot
|
|
321
400
|
* be removed — the relay refuses with a 409 conflict.
|
|
322
401
|
*/
|
|
323
402
|
removeAppMember(appId: string, humanId: string): Promise<void>;
|
|
324
|
-
/**
|
|
403
|
+
/**
|
|
404
|
+
* GET /v1/apps/:id/ingest-hooks lists the app's inbound catch-hooks with the
|
|
405
|
+
* full secret URL (decrypted server-side), the current rule metadata
|
|
406
|
+
* (collection/mode/wake/handshake), and per-hook delivery counts by status.
|
|
407
|
+
* The URL is the app owner's to share with a sender; it is never exposed on a
|
|
408
|
+
* public path, only here to the app's own owner/agent.
|
|
409
|
+
*/
|
|
410
|
+
listIngestHooks(appId: string): Promise<{
|
|
411
|
+
hooks: IngestHookInfo[];
|
|
412
|
+
}>;
|
|
413
|
+
/**
|
|
414
|
+
* POST /v1/apps/:id/ingest-hooks/:name/rotate mints a fresh secret for one
|
|
415
|
+
* hook and return its new full URL once. No redeploy needed; the old URL stops
|
|
416
|
+
* working immediately.
|
|
417
|
+
*/
|
|
418
|
+
rotateIngestHook(appId: string, name: string): Promise<{
|
|
419
|
+
hook: {
|
|
420
|
+
name: string;
|
|
421
|
+
url: string;
|
|
422
|
+
};
|
|
423
|
+
}>;
|
|
424
|
+
/**
|
|
425
|
+
* POST /v1/apps/:id/grants: mint a grant link. `role` must be a declared
|
|
426
|
+
* custom role for the app. `mode` is "once" (one-time, first-browser-claims)
|
|
427
|
+
* or "multi" (shared, capped by `maxUses` within expiry); defaults to "multi".
|
|
428
|
+
* An optional `pin` NARROWS the holder to a single `rowKey` OR a `where`
|
|
429
|
+
* filter (never widens). Returns the once-only `grant_url` carrying the token.
|
|
430
|
+
*/
|
|
431
|
+
mintAppGrant(appId: string, opts: {
|
|
432
|
+
role: string;
|
|
433
|
+
mode?: "once" | "multi";
|
|
434
|
+
maxUses?: number;
|
|
435
|
+
label?: string;
|
|
436
|
+
ttlSeconds?: number;
|
|
437
|
+
pin?: {
|
|
438
|
+
rowKey?: string;
|
|
439
|
+
where?: ListWhereCondition[];
|
|
440
|
+
};
|
|
441
|
+
}): Promise<MintAppGrantResult>;
|
|
442
|
+
/** GET /v1/apps/:id/grants: list the app's grant links (never any token). */
|
|
443
|
+
listAppGrants(appId: string): Promise<{
|
|
444
|
+
grants: AppGrantSummary[];
|
|
445
|
+
}>;
|
|
446
|
+
/** DELETE /v1/apps/:id/grants/:grantId: revoke one grant link (idempotent). */
|
|
447
|
+
revokeAppGrant(appId: string, grantId: string): Promise<void>;
|
|
448
|
+
/**
|
|
449
|
+
* GET /v1/apps/:id/collections/:name — list rows (current-state page).
|
|
450
|
+
*
|
|
451
|
+
* Wave C2 structured read query: pass `where` (an AND of `{field, op, value}`
|
|
452
|
+
* conditions) and/or `sort` (`{field, dir}` list) to filter/order DB-side. The
|
|
453
|
+
* relay applies read permission + author scoping FIRST, then the filter, so a
|
|
454
|
+
* filtered read is always a subset of what the caller could already read. The
|
|
455
|
+
* query is serialized into the `q` param as URL-encoded JSON. Note: a custom
|
|
456
|
+
* `sort` cannot be combined with cursor pagination (`since`) in this version.
|
|
457
|
+
*/
|
|
325
458
|
listAppRows(appId: string, collection: string, opts?: {
|
|
326
459
|
since?: string;
|
|
327
460
|
limit?: number;
|
|
461
|
+
where?: ListWhereCondition[];
|
|
462
|
+
sort?: ListSortSpec[];
|
|
328
463
|
}): Promise<AppRowsPage>;
|
|
329
464
|
/**
|
|
330
465
|
* GET /v1/apps/:id/collections/:name/:key — read one row. A dedicated
|
|
@@ -343,10 +478,28 @@ export declare class HomespunClient {
|
|
|
343
478
|
upsertAppRow(appId: string, collection: string, body: {
|
|
344
479
|
key?: string;
|
|
345
480
|
data: unknown;
|
|
481
|
+
on?: string;
|
|
346
482
|
}): Promise<{
|
|
347
483
|
row: AppRow;
|
|
348
484
|
deduped?: true;
|
|
349
485
|
}>;
|
|
486
|
+
/**
|
|
487
|
+
* POST /v1/apps/:id/collections/:name with `{ on, data }`, natural-key upsert
|
|
488
|
+
* (Wave C1). Matches the existing row whose `<field>` value equals data[field]
|
|
489
|
+
* and updates it in place (idempotent re-import), else creates a fresh row.
|
|
490
|
+
* `field` must be declared in the collection's manifest `unique` list.
|
|
491
|
+
*/
|
|
492
|
+
upsertAppRowOn(appId: string, collection: string, field: string, data: unknown): Promise<{
|
|
493
|
+
row: AppRow;
|
|
494
|
+
deduped?: true;
|
|
495
|
+
}>;
|
|
496
|
+
/**
|
|
497
|
+
* DELETE /v1/apps/:id/collections/:name/:key/purge, owner/agent-only removal
|
|
498
|
+
* that bypasses an append-only collection (Wave C1). Frees the row's unique
|
|
499
|
+
* values and writes an audited delete feed entry. A missing/already-deleted key
|
|
500
|
+
* is a 404 (HomespunApiError `row_not_found`), matching a normal delete.
|
|
501
|
+
*/
|
|
502
|
+
purgeAppRow(appId: string, collection: string, key: string): Promise<void>;
|
|
350
503
|
/** PATCH /v1/apps/:id/collections/:name/:key — optimistic-locked update. */
|
|
351
504
|
updateAppRow(appId: string, collection: string, key: string, body: {
|
|
352
505
|
data: unknown;
|
|
@@ -358,6 +511,20 @@ export declare class HomespunClient {
|
|
|
358
511
|
deleteAppRow(appId: string, collection: string, key: string, opts?: {
|
|
359
512
|
ifMatch?: number;
|
|
360
513
|
}): Promise<void>;
|
|
514
|
+
/**
|
|
515
|
+
* POST /v1/apps/:id/collections/:name/batch: bulk create/upsert (Wave B).
|
|
516
|
+
* Returns a per-row result array: a single invalid row is reported by index
|
|
517
|
+
* without aborting the good rows. A batch over the server's BATCH_MAX_ROWS cap
|
|
518
|
+
* is a clean 400 (throws HomespunApiError `invalid_request`). DEFAULTS TO
|
|
519
|
+
* SILENT (suppresses notify + webhooks): pass `{emitEffects:true}` or
|
|
520
|
+
* `{suppress:[]}` to fire effects. Owner/agent key only for suppression control.
|
|
521
|
+
*/
|
|
522
|
+
batchRows(appId: string, collection: string, rows: BatchRowInput[], opts?: BatchWriteOptions): Promise<BatchResult>;
|
|
523
|
+
/**
|
|
524
|
+
* DELETE /v1/apps/:id/collections/:name/batch: bulk soft-delete (Wave B).
|
|
525
|
+
* Same per-row result shape + cap as `batchRows`.
|
|
526
|
+
*/
|
|
527
|
+
deleteRows(appId: string, collection: string, keys: string[]): Promise<BatchResult>;
|
|
361
528
|
/**
|
|
362
529
|
* GET /v1/apps/:id/feed — change-feed catch-up; the long-poll fallback
|
|
363
530
|
* transport for `watch` (spec-cli §2.3/§5). `wait` (0-30s) long-polls when
|
|
@@ -386,6 +553,32 @@ export declare class HomespunClient {
|
|
|
386
553
|
decision: "approve" | "reject";
|
|
387
554
|
note?: string;
|
|
388
555
|
}): Promise<CommunitySubmissionDetail>;
|
|
556
|
+
/** GET /v1/publisher: the caller's own publisher profile. */
|
|
557
|
+
getPublisher(): Promise<PublisherProfile>;
|
|
558
|
+
/** POST /v1/publisher/claim: set the handle once (permanent after claiming). */
|
|
559
|
+
claimPublisherHandle(handle: string): Promise<PublisherProfile>;
|
|
560
|
+
/** POST /v1/publisher/update: update displayName/bio/url. */
|
|
561
|
+
updatePublisher(update: PublisherProfileUpdate): Promise<PublisherProfile>;
|
|
562
|
+
/**
|
|
563
|
+
* POST /v1/community/publishers/:handle/trust (operator, marketplace PR 11):
|
|
564
|
+
* set a publisher's trust level ("new" | "established"), the MVP promotion
|
|
565
|
+
* path for the review fast-track. Operator-gated server-side; returns the
|
|
566
|
+
* updated publisher profile.
|
|
567
|
+
*/
|
|
568
|
+
setPublisherTrustLevel(handle: string, trustLevel: "new" | "established"): Promise<PublisherProfile>;
|
|
569
|
+
/**
|
|
570
|
+
* POST /v1/reviews: review a template the caller installed. Identify the
|
|
571
|
+
* template by its namespaced `<handle>/<slug>` OR by explicit handle + slug.
|
|
572
|
+
*/
|
|
573
|
+
createReview(req: CreateCommunityReviewRequest): Promise<CommunityReview>;
|
|
574
|
+
/** POST /v1/reviews/:id/respond: respond to a review as the publisher. */
|
|
575
|
+
respondToReview(reviewId: string, response: string | null): Promise<CommunityReview>;
|
|
576
|
+
/** POST /v1/reviews/:id/report: flag a review for operator attention. */
|
|
577
|
+
reportReview(reviewId: string, reason: string): Promise<CommunityReviewReportResult>;
|
|
578
|
+
/** POST /v1/reviews/:id/remove (operator): take a review down. */
|
|
579
|
+
removeReview(reviewId: string): Promise<CommunityReview>;
|
|
580
|
+
/** POST /v1/reviews/:id/unhold (operator): publish a held review. */
|
|
581
|
+
unholdReview(reviewId: string): Promise<CommunityReview>;
|
|
389
582
|
}
|
|
390
583
|
/**
|
|
391
584
|
* One multi-file-deploy asset: bytes the deployed page references by a stable,
|
|
@@ -421,6 +614,20 @@ export interface DeployAppResponse {
|
|
|
421
614
|
url: string;
|
|
422
615
|
version: number;
|
|
423
616
|
created: true;
|
|
617
|
+
/**
|
|
618
|
+
* The tokenized share URL, present ONLY when the app was created as `link`. Its
|
|
619
|
+
* `#k=` fragment carries the raw share token and is shown exactly ONCE (the
|
|
620
|
+
* token is hashed at rest and never recoverable). Anyone with this exact URL
|
|
621
|
+
* can open the app until it is rotated (`rotateShareLink`).
|
|
622
|
+
*/
|
|
623
|
+
share_url?: string;
|
|
624
|
+
/**
|
|
625
|
+
* Non-fatal deploy advisories, present only when there is at least one. Today
|
|
626
|
+
* the schedules-without-timezone case: an app that declares `schedules` but has
|
|
627
|
+
* no timezone set will fire reminders at 08:00 UTC until one is set. Relay these
|
|
628
|
+
* to the human.
|
|
629
|
+
*/
|
|
630
|
+
warnings?: string[];
|
|
424
631
|
}
|
|
425
632
|
/** Request body for `POST /v1/apps/:id/versions` — redeploy (compat-gated). */
|
|
426
633
|
export interface RedeployAppRequest {
|
|
@@ -442,6 +649,66 @@ export interface RedeployAppResponse {
|
|
|
442
649
|
path: string;
|
|
443
650
|
message: string;
|
|
444
651
|
}>;
|
|
652
|
+
/**
|
|
653
|
+
* Non-fatal deploy advisories, present only when there is at least one; see
|
|
654
|
+
* DeployAppResponse.warnings. On redeploy the schedules-without-timezone
|
|
655
|
+
* warning fires only when the app STILL has no timezone set.
|
|
656
|
+
*/
|
|
657
|
+
warnings?: string[];
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Result of a DRY-RUN deploy (`checkDeploy`): the deploy validation + compat
|
|
661
|
+
* gate run WITHOUT creating a version or mutating anything. Mirrors what a real
|
|
662
|
+
* deploy would report: the manifest is validated (a structurally invalid
|
|
663
|
+
* manifest throws the SAME error a real deploy would), asset shapes are checked,
|
|
664
|
+
* the schedule-timezone / migration-mode advisories are computed, and (for a
|
|
665
|
+
* redeploy) the compat gate runs against the current version.
|
|
666
|
+
*
|
|
667
|
+
* - `ok` is true when a REAL deploy with the same inputs would succeed: a
|
|
668
|
+
* clean deploy, or a narrowing redeploy with `force: true`. It is false
|
|
669
|
+
* when a real un-forced redeploy would be REJECTED for narrowing the
|
|
670
|
+
* manifest (see `compat: "incompatible"` + `breaks`).
|
|
671
|
+
* - `compat` is present only for a redeploy check: "clean" (no narrowing),
|
|
672
|
+
* "forced" (narrows, but force was set so the real deploy would detach and
|
|
673
|
+
* proceed), or "incompatible" (narrows and force was NOT set, so the real
|
|
674
|
+
* deploy would return manifest_incompatible_redeploy).
|
|
675
|
+
* - `breaks` lists the narrowings, present only when there is at least one.
|
|
676
|
+
*/
|
|
677
|
+
export interface DeployCheckResult {
|
|
678
|
+
ok: boolean;
|
|
679
|
+
warnings: string[];
|
|
680
|
+
compat?: "clean" | "forced" | "incompatible";
|
|
681
|
+
breaks?: Array<{
|
|
682
|
+
path: string;
|
|
683
|
+
message: string;
|
|
684
|
+
}>;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Request for a dry-run deploy check. Omit `app_id` to check a CREATE; pass it
|
|
688
|
+
* to check a REDEPLOY of an existing app (the compat gate then runs against its
|
|
689
|
+
* current version, and `force` decides whether a narrowing would be accepted).
|
|
690
|
+
*/
|
|
691
|
+
export interface DeployCheckRequest {
|
|
692
|
+
app_id?: string;
|
|
693
|
+
html: string;
|
|
694
|
+
manifest: unknown;
|
|
695
|
+
force?: boolean;
|
|
696
|
+
assets?: AppAsset[];
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Result of minting a sibling API key for the calling agent's own identity
|
|
700
|
+
* (`mintKey`). The raw `api_key` is returned exactly ONCE here and is never
|
|
701
|
+
* retrievable again (only its hash is stored). The new key has the same
|
|
702
|
+
* scope/ownership as the caller and can be revoked by the owner via the normal
|
|
703
|
+
* revoke path.
|
|
704
|
+
*/
|
|
705
|
+
export interface KeyMintResult {
|
|
706
|
+
agent_id: string;
|
|
707
|
+
/** The raw sibling key, shown ONCE, never returned again. */
|
|
708
|
+
api_key: string;
|
|
709
|
+
key_prefix: string;
|
|
710
|
+
name: string | null;
|
|
711
|
+
created_at: string;
|
|
445
712
|
}
|
|
446
713
|
/** Lean per-app summary — the shape `listApps` returns. */
|
|
447
714
|
export interface AppSummary {
|
|
@@ -450,6 +717,12 @@ export interface AppSummary {
|
|
|
450
717
|
visibility: string;
|
|
451
718
|
status: string;
|
|
452
719
|
url: string;
|
|
720
|
+
/**
|
|
721
|
+
* Whether the app currently has an active share token (a `link` app with a
|
|
722
|
+
* live, revocable share link). NEVER the token itself: the raw token is
|
|
723
|
+
* returned only at create/rotate. False for public/private apps.
|
|
724
|
+
*/
|
|
725
|
+
has_share_token: boolean;
|
|
453
726
|
created_at: string;
|
|
454
727
|
last_activity_at: string;
|
|
455
728
|
}
|
|
@@ -460,6 +733,12 @@ export interface AppDetail extends AppSummary {
|
|
|
460
733
|
owner_human_id: string;
|
|
461
734
|
row_count: number;
|
|
462
735
|
storage_bytes: string;
|
|
736
|
+
/**
|
|
737
|
+
* The app's IANA timezone for `schedules` reminders, or null when unset. Set
|
|
738
|
+
* it with `updateApp(id, { timezone })`; an app that declares schedules with no
|
|
739
|
+
* timezone fires reminders at 08:00 UTC.
|
|
740
|
+
*/
|
|
741
|
+
timezone: string | null;
|
|
463
742
|
}
|
|
464
743
|
export interface AppsPage {
|
|
465
744
|
items: AppSummary[];
|
|
@@ -492,7 +771,70 @@ export interface AppMember {
|
|
|
492
771
|
humanId: string;
|
|
493
772
|
email: string;
|
|
494
773
|
role: string;
|
|
774
|
+
/** M5: an optional DECLARED custom role attached alongside base member powers. */
|
|
775
|
+
customRole?: string | null;
|
|
776
|
+
createdAt: string;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* One inbound catch-hook row from `listIngestHooks`. `url` is the full secret
|
|
780
|
+
* ingest URL to hand a sender. Rule-derived fields (collection/mode/wake/
|
|
781
|
+
* handshake) are null when the hook's rule has left the manifest (disabledAt
|
|
782
|
+
* set). `deliveries` are per-status counts of the hook's inbound journal.
|
|
783
|
+
*/
|
|
784
|
+
export interface IngestHookInfo {
|
|
785
|
+
name: string;
|
|
786
|
+
url: string;
|
|
787
|
+
collection: string | null;
|
|
788
|
+
mode: string | null;
|
|
789
|
+
wake: boolean | null;
|
|
790
|
+
handshake: string | null;
|
|
791
|
+
disabledAt: string | null;
|
|
495
792
|
createdAt: string;
|
|
793
|
+
deliveries: {
|
|
794
|
+
accepted: number;
|
|
795
|
+
failed: number;
|
|
796
|
+
dropped_duplicate: number;
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Effective per-verb access from `listAppRoles`: "all" = every row, "own" =
|
|
801
|
+
* only rows the holder authored (the `:own` / author narrowing), "none" =
|
|
802
|
+
* nothing. `create` is never "own" (no pre-existing row to be the author of).
|
|
803
|
+
* The relay computes these by probing its real enforcement functions, so they
|
|
804
|
+
* report what a holder can ACTUALLY do, including floors like `anyone`.
|
|
805
|
+
*/
|
|
806
|
+
export interface AppRoleVerbAccess {
|
|
807
|
+
read: "all" | "own" | "none";
|
|
808
|
+
create: "all" | "none";
|
|
809
|
+
update: "all" | "own" | "none";
|
|
810
|
+
delete: "all" | "own" | "none";
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* One collection's access from `listAppRoles`, reported PER POPULATION:
|
|
814
|
+
* `member_access` is what a signed-in member holding the role can do (their
|
|
815
|
+
* principal also carries the member floor), `grant_access` what a grant-link
|
|
816
|
+
* holder of the same role can do (no member floor, so the two can differ).
|
|
817
|
+
* `append_only` is context: such a collection refuses update/delete for every
|
|
818
|
+
* role while still allowing create, and the access tables reflect that.
|
|
819
|
+
*/
|
|
820
|
+
export interface AppRoleCollectionAccess {
|
|
821
|
+
name: string;
|
|
822
|
+
member_access: AppRoleVerbAccess;
|
|
823
|
+
grant_access: AppRoleVerbAccess;
|
|
824
|
+
append_only: boolean;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* One declared custom role from `listAppRoles`. `member_count` and
|
|
828
|
+
* `active_grant_count` are separate numbers, never summed: members are
|
|
829
|
+
* signed-in Humans, grant-link holders are anonymous per-holder identities.
|
|
830
|
+
*/
|
|
831
|
+
export interface AppRoleSummary {
|
|
832
|
+
name: string;
|
|
833
|
+
label: string;
|
|
834
|
+
description: string | null;
|
|
835
|
+
collections: AppRoleCollectionAccess[];
|
|
836
|
+
member_count: number;
|
|
837
|
+
active_grant_count: number;
|
|
496
838
|
}
|
|
497
839
|
/**
|
|
498
840
|
* `POST /v1/apps/:id/members` response — either the member was attached
|
|
@@ -505,6 +847,32 @@ export type AddAppMemberResult = {
|
|
|
505
847
|
invited: string;
|
|
506
848
|
expires_at: string;
|
|
507
849
|
};
|
|
850
|
+
/** `POST /v1/apps/:id/grants` response. `grant_url` carries the raw link token
|
|
851
|
+
* in its #g= fragment and is shown ONCE (it is never recoverable afterward). */
|
|
852
|
+
export interface MintAppGrantResult {
|
|
853
|
+
id: string;
|
|
854
|
+
grant_url: string;
|
|
855
|
+
role: string;
|
|
856
|
+
mode: string;
|
|
857
|
+
max_uses: number | null;
|
|
858
|
+
expires_at: string;
|
|
859
|
+
}
|
|
860
|
+
/** One grant-link row from `listAppGrants` (never carries any token material). */
|
|
861
|
+
export interface AppGrantSummary {
|
|
862
|
+
id: string;
|
|
863
|
+
role: string;
|
|
864
|
+
mode: string;
|
|
865
|
+
max_uses: number | null;
|
|
866
|
+
use_count: number;
|
|
867
|
+
claim_count: number;
|
|
868
|
+
pin_row_key: string | null;
|
|
869
|
+
pin_where: unknown;
|
|
870
|
+
label: string | null;
|
|
871
|
+
active: boolean;
|
|
872
|
+
expires_at: string;
|
|
873
|
+
revoked_at: string | null;
|
|
874
|
+
created_at: string;
|
|
875
|
+
}
|
|
508
876
|
/** One row in an app collection — the shape every row CRUD op returns. */
|
|
509
877
|
export interface AppRow {
|
|
510
878
|
key: string;
|
|
@@ -523,6 +891,68 @@ export interface AppRowsPage {
|
|
|
523
891
|
next_cursor: string | null;
|
|
524
892
|
has_more: boolean;
|
|
525
893
|
}
|
|
894
|
+
/** A scalar operand for a structured read-query condition (Wave C2). */
|
|
895
|
+
export type ListScalar = string | number | boolean;
|
|
896
|
+
/**
|
|
897
|
+
* One structured read-query condition (Wave C2). `op` is one of eq / neq / in /
|
|
898
|
+
* notIn / gt / lt / gte / lte, with the SAME type semantics as a notify `when`:
|
|
899
|
+
* comparisons are same-type only (a number never matches a string operand), and
|
|
900
|
+
* dates are compared as ISO-8601 strings. `value` is a single scalar for the
|
|
901
|
+
* scalar ops and a non-empty scalar array for `in`/`notIn`.
|
|
902
|
+
*/
|
|
903
|
+
export interface ListWhereCondition {
|
|
904
|
+
field: string;
|
|
905
|
+
op: "eq" | "neq" | "in" | "notIn" | "gt" | "lt" | "gte" | "lte";
|
|
906
|
+
value: ListScalar | ListScalar[];
|
|
907
|
+
}
|
|
908
|
+
/** One structured read-query sort key (Wave C2). `dir` defaults to "asc". */
|
|
909
|
+
export interface ListSortSpec {
|
|
910
|
+
field: string;
|
|
911
|
+
dir?: "asc" | "desc";
|
|
912
|
+
}
|
|
913
|
+
/** One row of a batch write (Wave B). `key` absent => create; present => upsert. */
|
|
914
|
+
export interface BatchRowInput {
|
|
915
|
+
key?: string;
|
|
916
|
+
data: unknown;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* The outcome of one row in a batch, addressed by its `index` in the input array
|
|
920
|
+
* so a caller maps a failure back to the exact row it sent. `ok:true` carries the
|
|
921
|
+
* written `key`; `ok:false` carries the per-row `error`.
|
|
922
|
+
*/
|
|
923
|
+
export interface BatchRowResult {
|
|
924
|
+
index: number;
|
|
925
|
+
ok: boolean;
|
|
926
|
+
key?: string;
|
|
927
|
+
error?: {
|
|
928
|
+
code: string;
|
|
929
|
+
message: string;
|
|
930
|
+
status: number;
|
|
931
|
+
details?: unknown;
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
/** The batch write / delete envelope: per-row results plus convenience counts. */
|
|
935
|
+
export interface BatchResult {
|
|
936
|
+
results: BatchRowResult[];
|
|
937
|
+
ok_count: number;
|
|
938
|
+
error_count: number;
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Effect-suppression options for a batch (silent migration, Wave B). The batch
|
|
942
|
+
* endpoint DEFAULTS to silent (suppresses both notify and webhooks). Pass
|
|
943
|
+
* `emitEffects:true` (or `suppress:[]`) to fire effects, or `suppress` to mute a
|
|
944
|
+
* specific subset. Honored only for an owner/agent key.
|
|
945
|
+
*/
|
|
946
|
+
export interface BatchWriteOptions {
|
|
947
|
+
suppress?: ("notify" | "webhooks")[];
|
|
948
|
+
emitEffects?: boolean;
|
|
949
|
+
/**
|
|
950
|
+
* Wave C1 natural-key upsert: match (or create) each row on this
|
|
951
|
+
* declared-unique field's value instead of the row id, making a re-import
|
|
952
|
+
* idempotent. Mutually exclusive with per-row `key`.
|
|
953
|
+
*/
|
|
954
|
+
on?: string;
|
|
955
|
+
}
|
|
526
956
|
/**
|
|
527
957
|
* One entry in an app's change feed — the SAME shape whether it arrives via
|
|
528
958
|
* `GET /v1/apps/:id/feed` (long-poll) or the `/_hs/ws` live/batch frames
|
|
@@ -548,6 +978,30 @@ export interface AppFeedPage {
|
|
|
548
978
|
truncated: boolean;
|
|
549
979
|
}
|
|
550
980
|
/** Request body for `publishCommunityTemplate()`. */
|
|
981
|
+
/**
|
|
982
|
+
* One typed agent-setup step (marketplace PR 9). An ordered list of these tells
|
|
983
|
+
* an installing agent what to configure after install. Carries only the
|
|
984
|
+
* publisher's own defaults + hints, never an installer's value; `secret: true`
|
|
985
|
+
* marks a step whose eventual value is sensitive (its default is masked on the
|
|
986
|
+
* public detail page). The relay validates + normalizes on publish.
|
|
987
|
+
*/
|
|
988
|
+
export interface CommunitySetupStep {
|
|
989
|
+
kind: "config" | "seed-data" | "connect" | "note" | "upload";
|
|
990
|
+
label: string;
|
|
991
|
+
description?: string;
|
|
992
|
+
required?: boolean;
|
|
993
|
+
secret?: boolean;
|
|
994
|
+
default?: string;
|
|
995
|
+
choices?: string[];
|
|
996
|
+
valueHint?: string;
|
|
997
|
+
/**
|
|
998
|
+
* The settings-collection field this step's answer is written into
|
|
999
|
+
* (install-config programme). Required for an `upload` step, optional for a
|
|
1000
|
+
* `config` step, and not allowed on the other kinds. Field-key identifier
|
|
1001
|
+
* (letters, digits, '_', up to 64 chars).
|
|
1002
|
+
*/
|
|
1003
|
+
key?: string;
|
|
1004
|
+
}
|
|
551
1005
|
export interface PublishCommunityTemplateRequest {
|
|
552
1006
|
/** The owned app to publish. */
|
|
553
1007
|
appId: string;
|
|
@@ -555,22 +1009,77 @@ export interface PublishCommunityTemplateRequest {
|
|
|
555
1009
|
title?: string;
|
|
556
1010
|
/** Listing blurb; defaults server-side to the manifest description. */
|
|
557
1011
|
description?: string;
|
|
558
|
-
/**
|
|
1012
|
+
/**
|
|
1013
|
+
* Optional long-form description (template-experience PR 5a): richer prose
|
|
1014
|
+
* rendered on the detail page below the short blurb. Plain text, length-capped
|
|
1015
|
+
* server-side; blank lines become escaped paragraph breaks (never raw HTML).
|
|
1016
|
+
*/
|
|
1017
|
+
longDescription?: string;
|
|
1018
|
+
/** Optional category (validated server-side against the fixed enum). */
|
|
559
1019
|
category?: string;
|
|
560
1020
|
/** Optional curation tags. */
|
|
561
1021
|
tags?: string[];
|
|
1022
|
+
/**
|
|
1023
|
+
* Optional per-publisher slug (marketplace PR 2). When set, the template gets
|
|
1024
|
+
* a namespaced id `<handle>/<slug>` and joins a versioned line.
|
|
1025
|
+
*/
|
|
1026
|
+
slug?: string;
|
|
1027
|
+
/** Optional semver version; defaults server-side to "1.0.0". */
|
|
1028
|
+
version?: string;
|
|
1029
|
+
/** Optional note recorded in this version's changelog entry. */
|
|
1030
|
+
changelogNote?: string;
|
|
1031
|
+
/**
|
|
1032
|
+
* Optional ordered typed setup steps (marketplace PR 9). Validated + stored
|
|
1033
|
+
* server-side; read back through the detail page + get_submission.
|
|
1034
|
+
*/
|
|
1035
|
+
setupSteps?: CommunitySetupStep[];
|
|
1036
|
+
/** Optional remix/fork lineage: the snapshot id this was derived from. */
|
|
1037
|
+
derivedFromSnapshotId?: string;
|
|
1038
|
+
/**
|
|
1039
|
+
* The example-only attestation (marketplace PR 10). Set true to attest the
|
|
1040
|
+
* captured template content AND its captured seed rows contain no real
|
|
1041
|
+
* personal data: publishing makes both PUBLIC to all platform users. Recorded
|
|
1042
|
+
* (not a hard gate) and surfaced to the operator review payload; omitting it
|
|
1043
|
+
* still publishes but is shown to the reviewer as "not attested".
|
|
1044
|
+
*/
|
|
1045
|
+
attestExampleOnly?: boolean;
|
|
562
1046
|
}
|
|
563
1047
|
export interface PublishCommunityTemplateResponse {
|
|
564
1048
|
snapshot_id: string;
|
|
565
1049
|
review_status: "pending" | "approved" | "rejected";
|
|
566
1050
|
name: string;
|
|
567
1051
|
seeded_row_count: number;
|
|
1052
|
+
slug: string | null;
|
|
1053
|
+
version: string;
|
|
1054
|
+
/** How many typed setup steps were stored (marketplace PR 9). */
|
|
1055
|
+
setup_step_count: number;
|
|
1056
|
+
/**
|
|
1057
|
+
* The example-only attestation as recorded (marketplace PR 10): true/false as
|
|
1058
|
+
* attested, or null when the publish carried no attestation.
|
|
1059
|
+
*/
|
|
1060
|
+
attest_example_only: boolean | null;
|
|
1061
|
+
/**
|
|
1062
|
+
* Fast-track outcome (marketplace PR 11). `expedited`: the publisher is
|
|
1063
|
+
* `established` and the submission still landed `pending`, flagged to sort
|
|
1064
|
+
* first in the operator review queue. `auto_approved`: the publisher is
|
|
1065
|
+
* `established` AND the relay enables true auto-approve, so it was approved
|
|
1066
|
+
* immediately at publish (review_status === "approved"). Both false for a
|
|
1067
|
+
* `new` publisher (ordinary full review).
|
|
1068
|
+
*/
|
|
1069
|
+
expedited: boolean;
|
|
1070
|
+
auto_approved: boolean;
|
|
568
1071
|
}
|
|
569
1072
|
/** A pending-submission summary in the operator review queue. */
|
|
570
1073
|
export interface CommunitySubmissionSummary {
|
|
571
1074
|
snapshot_id: string;
|
|
572
1075
|
name: string;
|
|
573
1076
|
description: string | null;
|
|
1077
|
+
/**
|
|
1078
|
+
* Publisher-provided long-form description (template-experience PR 5a),
|
|
1079
|
+
* surfaced in the review payload so the operator sees the full prose that
|
|
1080
|
+
* renders on the detail page. Null when the publish carried none.
|
|
1081
|
+
*/
|
|
1082
|
+
long_description: string | null;
|
|
574
1083
|
category: string | null;
|
|
575
1084
|
tags: string[];
|
|
576
1085
|
review_status: "pending" | "approved" | "rejected";
|
|
@@ -579,20 +1088,127 @@ export interface CommunitySubmissionSummary {
|
|
|
579
1088
|
source_app_id: string | null;
|
|
580
1089
|
seeded_row_count: number;
|
|
581
1090
|
published_at: string;
|
|
1091
|
+
/** Namespaced identity + version (marketplace PR 2); null when unset. */
|
|
1092
|
+
slug: string | null;
|
|
1093
|
+
version: string | null;
|
|
1094
|
+
/**
|
|
1095
|
+
* Publish-time PII attestation (marketplace PR 10): true/false as attested by
|
|
1096
|
+
* the publisher, or null when the publish carried no attestation. Surfaced in
|
|
1097
|
+
* the review queue so a missing attestation is visible to the operator.
|
|
1098
|
+
*/
|
|
1099
|
+
attest_example_only: boolean | null;
|
|
1100
|
+
/**
|
|
1101
|
+
* Fast-track flag (marketplace PR 11): true when this pending submission was
|
|
1102
|
+
* expedited (published by an `established` publisher, sorted first in the
|
|
1103
|
+
* queue). False for a `new` publisher and pre-PR-11 rows.
|
|
1104
|
+
*/
|
|
1105
|
+
expedited: boolean;
|
|
1106
|
+
/** The publisher's trust level (marketplace PR 11): "new" | "established". */
|
|
1107
|
+
publisher_trust_level: string;
|
|
582
1108
|
}
|
|
583
1109
|
export interface CommunitySubmissionsPage {
|
|
584
1110
|
items: CommunitySubmissionSummary[];
|
|
585
1111
|
next_cursor: string | null;
|
|
586
1112
|
}
|
|
1113
|
+
/** One collection's captured-seed footprint (marketplace PR 10). */
|
|
1114
|
+
export interface CommunitySeedCollectionSummary {
|
|
1115
|
+
collection: string;
|
|
1116
|
+
row_count: number;
|
|
1117
|
+
bytes: number;
|
|
1118
|
+
}
|
|
1119
|
+
/** A per-collection digest of a submission's captured seed rows (PR 10). */
|
|
1120
|
+
export interface CommunitySeedRowSummary {
|
|
1121
|
+
collections: CommunitySeedCollectionSummary[];
|
|
1122
|
+
total_rows: number;
|
|
1123
|
+
total_bytes: number;
|
|
1124
|
+
}
|
|
587
1125
|
/** A submission's FULL content, for operator review. */
|
|
588
1126
|
export interface CommunitySubmissionDetail extends CommunitySubmissionSummary {
|
|
589
1127
|
html: string;
|
|
590
1128
|
manifest: unknown;
|
|
591
1129
|
seed_rows: unknown;
|
|
1130
|
+
/**
|
|
1131
|
+
* A digest of seed_rows (marketplace PR 10): per-collection row counts + byte
|
|
1132
|
+
* sizes plus totals, so the operator sees how much (and which) captured
|
|
1133
|
+
* starter data would become public without parsing the raw rows.
|
|
1134
|
+
*/
|
|
1135
|
+
seed_summary: CommunitySeedRowSummary;
|
|
592
1136
|
version_id: string | null;
|
|
593
1137
|
reviewed_at: string | null;
|
|
594
1138
|
review_note: string | null;
|
|
595
1139
|
superseded_at: string | null;
|
|
1140
|
+
/** Namespaced identity + fork/license fields (marketplace PR 2). */
|
|
1141
|
+
changelog: unknown;
|
|
1142
|
+
license: string | null;
|
|
1143
|
+
license_terms: string | null;
|
|
1144
|
+
derived_from_snapshot_id: string | null;
|
|
1145
|
+
/**
|
|
1146
|
+
* Typed agent-setup steps (marketplace PR 9): the machine-readable structure
|
|
1147
|
+
* an agent reads. Full structure incl. secret-flagged steps (a step stores
|
|
1148
|
+
* only the publisher's default/hint, never an installer value). `null` when
|
|
1149
|
+
* the template declares no steps.
|
|
1150
|
+
*/
|
|
1151
|
+
setup_steps: CommunitySetupStep[] | null;
|
|
1152
|
+
}
|
|
1153
|
+
/** A publisher's own profile, as returned by the `/v1/publisher` routes. */
|
|
1154
|
+
export interface PublisherProfile {
|
|
1155
|
+
handle: string;
|
|
1156
|
+
display_name: string | null;
|
|
1157
|
+
bio: string | null;
|
|
1158
|
+
url: string | null;
|
|
1159
|
+
/** True once the handle has been claimed (and is therefore permanent). */
|
|
1160
|
+
claimed: boolean;
|
|
1161
|
+
claimed_at: string | null;
|
|
1162
|
+
created_at: string;
|
|
1163
|
+
first_published_at: string | null;
|
|
1164
|
+
approved_template_count: number;
|
|
1165
|
+
rating_count: number;
|
|
1166
|
+
rating_sum: number;
|
|
1167
|
+
trust_level: string;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* The mutable profile fields for `updatePublisher()`. A field left `undefined`
|
|
1171
|
+
* is untouched; `null` clears it.
|
|
1172
|
+
*/
|
|
1173
|
+
export interface PublisherProfileUpdate {
|
|
1174
|
+
displayName?: string | null;
|
|
1175
|
+
bio?: string | null;
|
|
1176
|
+
url?: string | null;
|
|
1177
|
+
}
|
|
1178
|
+
/** Request body for `createReview()`. Give `template` OR (`handle` + `slug`). */
|
|
1179
|
+
export interface CreateCommunityReviewRequest {
|
|
1180
|
+
/** Namespaced template id, "<handle>/<slug>". */
|
|
1181
|
+
template?: string;
|
|
1182
|
+
/** Publisher handle (with `slug`, as an alternative to `template`). */
|
|
1183
|
+
handle?: string;
|
|
1184
|
+
/** Per-publisher slug (with `handle`). */
|
|
1185
|
+
slug?: string;
|
|
1186
|
+
/** Star rating, integer 1..5. */
|
|
1187
|
+
stars: number;
|
|
1188
|
+
/** Optional written review body. */
|
|
1189
|
+
body?: string;
|
|
1190
|
+
}
|
|
1191
|
+
/** A review as returned by the `/v1/reviews` routes. */
|
|
1192
|
+
export interface CommunityReview {
|
|
1193
|
+
id: string;
|
|
1194
|
+
template_publisher_human_id: string;
|
|
1195
|
+
template_slug: string | null;
|
|
1196
|
+
snapshot_id: string;
|
|
1197
|
+
install_id: string | null;
|
|
1198
|
+
reviewer_human_id: string;
|
|
1199
|
+
stars: number;
|
|
1200
|
+
body: string | null;
|
|
1201
|
+
/** "visible" | "held" | "removed". A body with a link/email lands "held". */
|
|
1202
|
+
status: string;
|
|
1203
|
+
held_reason: string | null;
|
|
1204
|
+
publisher_response: string | null;
|
|
1205
|
+
publisher_responded_at: string | null;
|
|
1206
|
+
created_at: string;
|
|
1207
|
+
}
|
|
1208
|
+
/** Result of `reportReview()`. */
|
|
1209
|
+
export interface CommunityReviewReportResult {
|
|
1210
|
+
report_id: string;
|
|
1211
|
+
review_id: string;
|
|
596
1212
|
}
|
|
597
1213
|
/** Per-attachment metadata as returned by `POST /v1/attachments` and friends. */
|
|
598
1214
|
export interface AttachmentRef {
|
package/dist/client.js
CHANGED
|
@@ -143,6 +143,25 @@ export class HomespunClient {
|
|
|
143
143
|
if (!r.ok)
|
|
144
144
|
this.fail(r);
|
|
145
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* POST /v1/keys: mint a NEW sibling API key for the calling agent's OWN
|
|
148
|
+
* identity. The relay derives the identity from the bearer token, so a caller
|
|
149
|
+
* can only ever mint a sibling of itself (never another agent's key). The new
|
|
150
|
+
* key has the same scope/ownership as the caller and shows up in a subsequent
|
|
151
|
+
* `listKeys()` call made WITH the new key.
|
|
152
|
+
*
|
|
153
|
+
* The raw `api_key` is returned exactly ONCE in this response and is never
|
|
154
|
+
* retrievable again (only its hash is stored). Bootstraps a fresh CLI /
|
|
155
|
+
* process credential from an MCP-driven agent that has no key of its own to
|
|
156
|
+
* hand off. The owner can revoke any minted sibling via the normal revoke
|
|
157
|
+
* path.
|
|
158
|
+
*/
|
|
159
|
+
async mintKey() {
|
|
160
|
+
const r = await this.call("POST", "/v1/keys");
|
|
161
|
+
if (!r.ok)
|
|
162
|
+
this.fail(r);
|
|
163
|
+
return this.asObject(r);
|
|
164
|
+
}
|
|
146
165
|
/**
|
|
147
166
|
* POST /v1/agents/claim — bind this agent to a human via a one-shot
|
|
148
167
|
* claim code the human generated in their settings UI. After a
|
|
@@ -510,6 +529,33 @@ export class HomespunClient {
|
|
|
510
529
|
this.fail(r);
|
|
511
530
|
return this.asObject(r);
|
|
512
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Dry-run a deploy: validate the manifest + asset shapes, run the compat gate
|
|
534
|
+
* (for a redeploy) and compute the deploy advisories, WITHOUT creating a
|
|
535
|
+
* version or mutating anything. Posts the deploy body with `dry_run: true` to
|
|
536
|
+
* the same route the real deploy uses (`POST /v1/apps` for a create check,
|
|
537
|
+
* `POST /v1/apps/:id/versions` for a redeploy check), so the relay returns the
|
|
538
|
+
* SAME validation error a real deploy would for an invalid manifest, and the
|
|
539
|
+
* SAME warnings/compat result for a valid one. Nothing is persisted.
|
|
540
|
+
*/
|
|
541
|
+
async checkDeploy(req) {
|
|
542
|
+
const path = req.app_id !== undefined
|
|
543
|
+
? `/v1/apps/${encodeURIComponent(req.app_id)}/versions`
|
|
544
|
+
: "/v1/apps";
|
|
545
|
+
const body = {
|
|
546
|
+
html: req.html,
|
|
547
|
+
manifest: req.manifest,
|
|
548
|
+
dry_run: true,
|
|
549
|
+
};
|
|
550
|
+
if (req.app_id !== undefined && req.force !== undefined)
|
|
551
|
+
body["force"] = req.force;
|
|
552
|
+
if (req.assets !== undefined)
|
|
553
|
+
body["assets"] = req.assets;
|
|
554
|
+
const r = await this.call("POST", path, body);
|
|
555
|
+
if (!r.ok)
|
|
556
|
+
this.fail(r);
|
|
557
|
+
return this.asObject(r);
|
|
558
|
+
}
|
|
513
559
|
/**
|
|
514
560
|
* GET /v1/apps — list apps scoped to the calling agent's owning human.
|
|
515
561
|
* `slug` is an exact-match filter — the one case a caller resolves a
|
|
@@ -538,9 +584,37 @@ export class HomespunClient {
|
|
|
538
584
|
this.fail(r);
|
|
539
585
|
return this.asObject(r);
|
|
540
586
|
}
|
|
541
|
-
/**
|
|
542
|
-
|
|
543
|
-
|
|
587
|
+
/**
|
|
588
|
+
* PATCH /v1/apps/:id updates the app's mutable settings. Both fields are
|
|
589
|
+
* optional but at least one must be given: `visibility` (private|link|public)
|
|
590
|
+
* and/or `timezone` (an IANA zone name like "Europe/Berlin", used for
|
|
591
|
+
* `schedules` reminders). Returns the updated `{ id, visibility, timezone }`.
|
|
592
|
+
* A transition INTO `link` visibility also returns a `share_url` carrying the
|
|
593
|
+
* app's freshly minted share token in its `#k=` fragment, shown ONCE (the
|
|
594
|
+
* token is hashed at rest and never recoverable). A transition AWAY from
|
|
595
|
+
* `link` clears the token (every prior share URL stops working).
|
|
596
|
+
*/
|
|
597
|
+
async updateApp(appId, update) {
|
|
598
|
+
const body = {};
|
|
599
|
+
if (update.visibility !== undefined)
|
|
600
|
+
body["visibility"] = update.visibility;
|
|
601
|
+
if (update.timezone !== undefined)
|
|
602
|
+
body["timezone"] = update.timezone;
|
|
603
|
+
const r = await this.call("PATCH", `/v1/apps/${encodeURIComponent(appId)}`, body);
|
|
604
|
+
if (!r.ok)
|
|
605
|
+
this.fail(r);
|
|
606
|
+
return this.asObject(r);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* POST /v1/apps/:id/share-link/rotate rotates a `link` app's share token.
|
|
610
|
+
* Mints a fresh token and returns the new `share_url` (its `#k=` fragment
|
|
611
|
+
* carries the raw token, shown ONCE). Rotating instantly revokes the previous
|
|
612
|
+
* share URL and every pass derived from it. Also serves as "generate" for a
|
|
613
|
+
* `link` app that has no share token yet. Only valid for a `link` app; a
|
|
614
|
+
* public/private app returns a conflict.
|
|
615
|
+
*/
|
|
616
|
+
async rotateShareLink(appId) {
|
|
617
|
+
const r = await this.call("POST", `/v1/apps/${encodeURIComponent(appId)}/share-link/rotate`, {});
|
|
544
618
|
if (!r.ok)
|
|
545
619
|
this.fail(r);
|
|
546
620
|
return this.asObject(r);
|
|
@@ -613,7 +687,7 @@ export class HomespunClient {
|
|
|
613
687
|
* EMAIL_PROVIDER=none (no invite-email path available).
|
|
614
688
|
*/
|
|
615
689
|
async addAppMember(appId, opts) {
|
|
616
|
-
const r = await this.call("POST", `/v1/apps/${encodeURIComponent(appId)}/members`, { email: opts.email, role: opts.role });
|
|
690
|
+
const r = await this.call("POST", `/v1/apps/${encodeURIComponent(appId)}/members`, { email: opts.email, role: opts.role, custom_role: opts.customRole });
|
|
617
691
|
if (!r.ok)
|
|
618
692
|
this.fail(r);
|
|
619
693
|
return this.asObject(r);
|
|
@@ -625,6 +699,39 @@ export class HomespunClient {
|
|
|
625
699
|
this.fail(r);
|
|
626
700
|
return this.asObject(r);
|
|
627
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* PATCH /v1/apps/:id/members/:humanId — change an existing member's custom
|
|
704
|
+
* role in place. `customRole: null` clears it back to a plain member. The
|
|
705
|
+
* role must be one the app's manifest declares (a reserved base role or an
|
|
706
|
+
* undeclared name is a 400), and the app owner's own membership cannot be
|
|
707
|
+
* re-roled.
|
|
708
|
+
*
|
|
709
|
+
* Unlike remove-then-re-add, this does NOT revoke the member's live app
|
|
710
|
+
* sessions: they stay signed in and pick up the new role on their next
|
|
711
|
+
* request. Roles are not cached: they are re-derived from the Member row on
|
|
712
|
+
* every request, so a downgrade takes effect immediately and a live session
|
|
713
|
+
* can never escalate.
|
|
714
|
+
*/
|
|
715
|
+
async setAppMemberRole(appId, humanId, opts) {
|
|
716
|
+
const r = await this.call("PATCH", `/v1/apps/${encodeURIComponent(appId)}/members/${encodeURIComponent(humanId)}`, { custom_role: opts.customRole });
|
|
717
|
+
if (!r.ok)
|
|
718
|
+
this.fail(r);
|
|
719
|
+
return this.asObject(r);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* GET /v1/apps/:id/roles — the derived roles summary: every custom role the
|
|
723
|
+
* app's manifest declares, what holding it actually allows per collection
|
|
724
|
+
* (reported separately for members and for grant-link holders, whose role
|
|
725
|
+
* floors differ), and how many members and live grant links hold it.
|
|
726
|
+
* Read-only and computed on the fly; permissions themselves stay
|
|
727
|
+
* manifest-declared. An app that declares no roles returns an empty list.
|
|
728
|
+
*/
|
|
729
|
+
async listAppRoles(appId) {
|
|
730
|
+
const r = await this.call("GET", `/v1/apps/${encodeURIComponent(appId)}/roles`);
|
|
731
|
+
if (!r.ok)
|
|
732
|
+
this.fail(r);
|
|
733
|
+
return this.asObject(r);
|
|
734
|
+
}
|
|
628
735
|
/**
|
|
629
736
|
* DELETE /v1/apps/:id/members/:humanId — remove a member (idempotent);
|
|
630
737
|
* cascades to revoke that human's live app sessions. The app owner cannot
|
|
@@ -636,17 +743,106 @@ export class HomespunClient {
|
|
|
636
743
|
this.fail(r);
|
|
637
744
|
}
|
|
638
745
|
// -------------------------------------------------------------------------
|
|
746
|
+
// Inbound catch-hooks (inbound-webhooks PR 3). Read + rotate the smallest
|
|
747
|
+
// surface an agent needs: list an app's declared hooks with their full secret
|
|
748
|
+
// URL (so the agent can tell its owner the exact URL to paste into an external
|
|
749
|
+
// system during app setup), and rotate a leaked secret. Hooks themselves are
|
|
750
|
+
// manifest-declared, so there is no create/delete here.
|
|
751
|
+
// -------------------------------------------------------------------------
|
|
752
|
+
/**
|
|
753
|
+
* GET /v1/apps/:id/ingest-hooks lists the app's inbound catch-hooks with the
|
|
754
|
+
* full secret URL (decrypted server-side), the current rule metadata
|
|
755
|
+
* (collection/mode/wake/handshake), and per-hook delivery counts by status.
|
|
756
|
+
* The URL is the app owner's to share with a sender; it is never exposed on a
|
|
757
|
+
* public path, only here to the app's own owner/agent.
|
|
758
|
+
*/
|
|
759
|
+
async listIngestHooks(appId) {
|
|
760
|
+
const r = await this.call("GET", `/v1/apps/${encodeURIComponent(appId)}/ingest-hooks`);
|
|
761
|
+
if (!r.ok)
|
|
762
|
+
this.fail(r);
|
|
763
|
+
return this.asObject(r);
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* POST /v1/apps/:id/ingest-hooks/:name/rotate mints a fresh secret for one
|
|
767
|
+
* hook and return its new full URL once. No redeploy needed; the old URL stops
|
|
768
|
+
* working immediately.
|
|
769
|
+
*/
|
|
770
|
+
async rotateIngestHook(appId, name) {
|
|
771
|
+
const r = await this.call("POST", `/v1/apps/${encodeURIComponent(appId)}/ingest-hooks/${encodeURIComponent(name)}/rotate`);
|
|
772
|
+
if (!r.ok)
|
|
773
|
+
this.fail(r);
|
|
774
|
+
return this.asObject(r);
|
|
775
|
+
}
|
|
776
|
+
// -------------------------------------------------------------------------
|
|
777
|
+
// v2 grant links (M5). An owner/agent mints a capability URL carrying a
|
|
778
|
+
// DECLARED custom role (x-homespun-manifest.roles) plus an optional row/filter
|
|
779
|
+
// pin. The raw link token rides the returned grant_url's FRAGMENT (#g=), never
|
|
780
|
+
// a query param, so it is never logged; the page SDK exchanges it once for a
|
|
781
|
+
// per-holder credential. A grant can NEVER carry a built-in role and never
|
|
782
|
+
// escalates past its custom role on a stable per-holder identity.
|
|
783
|
+
// -------------------------------------------------------------------------
|
|
784
|
+
/**
|
|
785
|
+
* POST /v1/apps/:id/grants: mint a grant link. `role` must be a declared
|
|
786
|
+
* custom role for the app. `mode` is "once" (one-time, first-browser-claims)
|
|
787
|
+
* or "multi" (shared, capped by `maxUses` within expiry); defaults to "multi".
|
|
788
|
+
* An optional `pin` NARROWS the holder to a single `rowKey` OR a `where`
|
|
789
|
+
* filter (never widens). Returns the once-only `grant_url` carrying the token.
|
|
790
|
+
*/
|
|
791
|
+
async mintAppGrant(appId, opts) {
|
|
792
|
+
const pin = opts.pin
|
|
793
|
+
? opts.pin.rowKey !== undefined
|
|
794
|
+
? { row_key: opts.pin.rowKey }
|
|
795
|
+
: { where: opts.pin.where }
|
|
796
|
+
: undefined;
|
|
797
|
+
const r = await this.call("POST", `/v1/apps/${encodeURIComponent(appId)}/grants`, {
|
|
798
|
+
role: opts.role,
|
|
799
|
+
mode: opts.mode,
|
|
800
|
+
max_uses: opts.maxUses,
|
|
801
|
+
label: opts.label,
|
|
802
|
+
ttl_seconds: opts.ttlSeconds,
|
|
803
|
+
pin,
|
|
804
|
+
});
|
|
805
|
+
if (!r.ok)
|
|
806
|
+
this.fail(r);
|
|
807
|
+
return this.asObject(r);
|
|
808
|
+
}
|
|
809
|
+
/** GET /v1/apps/:id/grants: list the app's grant links (never any token). */
|
|
810
|
+
async listAppGrants(appId) {
|
|
811
|
+
const r = await this.call("GET", `/v1/apps/${encodeURIComponent(appId)}/grants`);
|
|
812
|
+
if (!r.ok)
|
|
813
|
+
this.fail(r);
|
|
814
|
+
return this.asObject(r);
|
|
815
|
+
}
|
|
816
|
+
/** DELETE /v1/apps/:id/grants/:grantId: revoke one grant link (idempotent). */
|
|
817
|
+
async revokeAppGrant(appId, grantId) {
|
|
818
|
+
const r = await this.call("DELETE", `/v1/apps/${encodeURIComponent(appId)}/grants/${encodeURIComponent(grantId)}`);
|
|
819
|
+
if (!r.ok)
|
|
820
|
+
this.fail(r);
|
|
821
|
+
}
|
|
822
|
+
// -------------------------------------------------------------------------
|
|
639
823
|
// v2 collection row CRUD + feed catch-up (spec-cli §2.2/§2.3). Same wire
|
|
640
824
|
// shapes as the v1 homespun-records methods above, mounted under
|
|
641
825
|
// /v1/apps/:id/collections/:name instead of /v1/apps/:id/records/:name.
|
|
642
826
|
// -------------------------------------------------------------------------
|
|
643
|
-
/**
|
|
827
|
+
/**
|
|
828
|
+
* GET /v1/apps/:id/collections/:name — list rows (current-state page).
|
|
829
|
+
*
|
|
830
|
+
* Wave C2 structured read query: pass `where` (an AND of `{field, op, value}`
|
|
831
|
+
* conditions) and/or `sort` (`{field, dir}` list) to filter/order DB-side. The
|
|
832
|
+
* relay applies read permission + author scoping FIRST, then the filter, so a
|
|
833
|
+
* filtered read is always a subset of what the caller could already read. The
|
|
834
|
+
* query is serialized into the `q` param as URL-encoded JSON. Note: a custom
|
|
835
|
+
* `sort` cannot be combined with cursor pagination (`since`) in this version.
|
|
836
|
+
*/
|
|
644
837
|
async listAppRows(appId, collection, opts = {}) {
|
|
645
838
|
const q = new URLSearchParams();
|
|
646
839
|
if (opts.since !== undefined)
|
|
647
840
|
q.set("since", opts.since);
|
|
648
841
|
if (opts.limit !== undefined)
|
|
649
842
|
q.set("limit", String(opts.limit));
|
|
843
|
+
const query = buildListQueryParam(opts.where, opts.sort);
|
|
844
|
+
if (query !== undefined)
|
|
845
|
+
q.set("q", query);
|
|
650
846
|
const qs = q.toString();
|
|
651
847
|
const r = await this.call("GET", `/v1/apps/${encodeURIComponent(appId)}/collections/${encodeURIComponent(collection)}${qs ? "?" + qs : ""}`);
|
|
652
848
|
if (!r.ok)
|
|
@@ -676,6 +872,26 @@ export class HomespunClient {
|
|
|
676
872
|
this.fail(r);
|
|
677
873
|
return this.asObject(r);
|
|
678
874
|
}
|
|
875
|
+
/**
|
|
876
|
+
* POST /v1/apps/:id/collections/:name with `{ on, data }`, natural-key upsert
|
|
877
|
+
* (Wave C1). Matches the existing row whose `<field>` value equals data[field]
|
|
878
|
+
* and updates it in place (idempotent re-import), else creates a fresh row.
|
|
879
|
+
* `field` must be declared in the collection's manifest `unique` list.
|
|
880
|
+
*/
|
|
881
|
+
async upsertAppRowOn(appId, collection, field, data) {
|
|
882
|
+
return this.upsertAppRow(appId, collection, { on: field, data });
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* DELETE /v1/apps/:id/collections/:name/:key/purge, owner/agent-only removal
|
|
886
|
+
* that bypasses an append-only collection (Wave C1). Frees the row's unique
|
|
887
|
+
* values and writes an audited delete feed entry. A missing/already-deleted key
|
|
888
|
+
* is a 404 (HomespunApiError `row_not_found`), matching a normal delete.
|
|
889
|
+
*/
|
|
890
|
+
async purgeAppRow(appId, collection, key) {
|
|
891
|
+
const r = await this.call("DELETE", `/v1/apps/${encodeURIComponent(appId)}/collections/${encodeURIComponent(collection)}/${encodeURIComponent(key)}/purge`);
|
|
892
|
+
if (!r.ok)
|
|
893
|
+
this.fail(r);
|
|
894
|
+
}
|
|
679
895
|
/** PATCH /v1/apps/:id/collections/:name/:key — optimistic-locked update. */
|
|
680
896
|
async updateAppRow(appId, collection, key, body) {
|
|
681
897
|
const r = await this.call("PATCH", `/v1/apps/${encodeURIComponent(appId)}/collections/${encodeURIComponent(collection)}/${encodeURIComponent(key)}`, body);
|
|
@@ -690,6 +906,37 @@ export class HomespunClient {
|
|
|
690
906
|
if (!r.ok)
|
|
691
907
|
this.fail(r);
|
|
692
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* POST /v1/apps/:id/collections/:name/batch: bulk create/upsert (Wave B).
|
|
911
|
+
* Returns a per-row result array: a single invalid row is reported by index
|
|
912
|
+
* without aborting the good rows. A batch over the server's BATCH_MAX_ROWS cap
|
|
913
|
+
* is a clean 400 (throws HomespunApiError `invalid_request`). DEFAULTS TO
|
|
914
|
+
* SILENT (suppresses notify + webhooks): pass `{emitEffects:true}` or
|
|
915
|
+
* `{suppress:[]}` to fire effects. Owner/agent key only for suppression control.
|
|
916
|
+
*/
|
|
917
|
+
async batchRows(appId, collection, rows, opts = {}) {
|
|
918
|
+
const body = { rows };
|
|
919
|
+
if (opts.suppress !== undefined)
|
|
920
|
+
body.suppress = opts.suppress;
|
|
921
|
+
if (opts.emitEffects !== undefined)
|
|
922
|
+
body.emitEffects = opts.emitEffects;
|
|
923
|
+
if (opts.on !== undefined)
|
|
924
|
+
body.on = opts.on;
|
|
925
|
+
const r = await this.call("POST", `/v1/apps/${encodeURIComponent(appId)}/collections/${encodeURIComponent(collection)}/batch`, body);
|
|
926
|
+
if (!r.ok)
|
|
927
|
+
this.fail(r);
|
|
928
|
+
return this.asObject(r);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* DELETE /v1/apps/:id/collections/:name/batch: bulk soft-delete (Wave B).
|
|
932
|
+
* Same per-row result shape + cap as `batchRows`.
|
|
933
|
+
*/
|
|
934
|
+
async deleteRows(appId, collection, keys) {
|
|
935
|
+
const r = await this.call("DELETE", `/v1/apps/${encodeURIComponent(appId)}/collections/${encodeURIComponent(collection)}/batch`, { keys });
|
|
936
|
+
if (!r.ok)
|
|
937
|
+
this.fail(r);
|
|
938
|
+
return this.asObject(r);
|
|
939
|
+
}
|
|
693
940
|
/**
|
|
694
941
|
* GET /v1/apps/:id/feed — change-feed catch-up; the long-poll fallback
|
|
695
942
|
* transport for `watch` (spec-cli §2.3/§5). `wait` (0-30s) long-polls when
|
|
@@ -718,8 +965,15 @@ export class HomespunClient {
|
|
|
718
965
|
app_id: req.appId,
|
|
719
966
|
title: req.title,
|
|
720
967
|
description: req.description,
|
|
968
|
+
long_description: req.longDescription,
|
|
721
969
|
category: req.category,
|
|
722
970
|
tags: req.tags,
|
|
971
|
+
slug: req.slug,
|
|
972
|
+
version: req.version,
|
|
973
|
+
changelog_note: req.changelogNote,
|
|
974
|
+
setup_steps: req.setupSteps,
|
|
975
|
+
derived_from_snapshot_id: req.derivedFromSnapshotId,
|
|
976
|
+
attest_example_only: req.attestExampleOnly,
|
|
723
977
|
});
|
|
724
978
|
if (!r.ok)
|
|
725
979
|
this.fail(r);
|
|
@@ -757,4 +1011,107 @@ export class HomespunClient {
|
|
|
757
1011
|
this.fail(r);
|
|
758
1012
|
return this.asObject(r);
|
|
759
1013
|
}
|
|
1014
|
+
// -------------------------------------------------------------------------
|
|
1015
|
+
// Community publisher identity (marketplace PR 1). All three act AS the
|
|
1016
|
+
// calling agent's owning human. claim/update additionally need a verified
|
|
1017
|
+
// email server-side.
|
|
1018
|
+
// -------------------------------------------------------------------------
|
|
1019
|
+
/** GET /v1/publisher: the caller's own publisher profile. */
|
|
1020
|
+
async getPublisher() {
|
|
1021
|
+
const r = await this.call("GET", "/v1/publisher");
|
|
1022
|
+
if (!r.ok)
|
|
1023
|
+
this.fail(r);
|
|
1024
|
+
return this.asObject(r);
|
|
1025
|
+
}
|
|
1026
|
+
/** POST /v1/publisher/claim: set the handle once (permanent after claiming). */
|
|
1027
|
+
async claimPublisherHandle(handle) {
|
|
1028
|
+
const r = await this.call("POST", "/v1/publisher/claim", { handle });
|
|
1029
|
+
if (!r.ok)
|
|
1030
|
+
this.fail(r);
|
|
1031
|
+
return this.asObject(r);
|
|
1032
|
+
}
|
|
1033
|
+
/** POST /v1/publisher/update: update displayName/bio/url. */
|
|
1034
|
+
async updatePublisher(update) {
|
|
1035
|
+
const r = await this.call("POST", "/v1/publisher/update", {
|
|
1036
|
+
display_name: update.displayName,
|
|
1037
|
+
bio: update.bio,
|
|
1038
|
+
url: update.url,
|
|
1039
|
+
});
|
|
1040
|
+
if (!r.ok)
|
|
1041
|
+
this.fail(r);
|
|
1042
|
+
return this.asObject(r);
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* POST /v1/community/publishers/:handle/trust (operator, marketplace PR 11):
|
|
1046
|
+
* set a publisher's trust level ("new" | "established"), the MVP promotion
|
|
1047
|
+
* path for the review fast-track. Operator-gated server-side; returns the
|
|
1048
|
+
* updated publisher profile.
|
|
1049
|
+
*/
|
|
1050
|
+
async setPublisherTrustLevel(handle, trustLevel) {
|
|
1051
|
+
const r = await this.call("POST", `/v1/community/publishers/${encodeURIComponent(handle)}/trust`, { trust_level: trustLevel });
|
|
1052
|
+
if (!r.ok)
|
|
1053
|
+
this.fail(r);
|
|
1054
|
+
return this.asObject(r);
|
|
1055
|
+
}
|
|
1056
|
+
// -------------------------------------------------------------------------
|
|
1057
|
+
// Community reviews (marketplace PR 7). createReview / respondToReview /
|
|
1058
|
+
// reportReview act AS the calling agent's owning human; the moderation
|
|
1059
|
+
// methods (removeReview / unholdReview) are operator-gated server-side.
|
|
1060
|
+
// -------------------------------------------------------------------------
|
|
1061
|
+
/**
|
|
1062
|
+
* POST /v1/reviews: review a template the caller installed. Identify the
|
|
1063
|
+
* template by its namespaced `<handle>/<slug>` OR by explicit handle + slug.
|
|
1064
|
+
*/
|
|
1065
|
+
async createReview(req) {
|
|
1066
|
+
const r = await this.call("POST", "/v1/reviews", {
|
|
1067
|
+
template: req.template,
|
|
1068
|
+
handle: req.handle,
|
|
1069
|
+
slug: req.slug,
|
|
1070
|
+
stars: req.stars,
|
|
1071
|
+
body: req.body,
|
|
1072
|
+
});
|
|
1073
|
+
if (!r.ok)
|
|
1074
|
+
this.fail(r);
|
|
1075
|
+
return this.asObject(r);
|
|
1076
|
+
}
|
|
1077
|
+
/** POST /v1/reviews/:id/respond: respond to a review as the publisher. */
|
|
1078
|
+
async respondToReview(reviewId, response) {
|
|
1079
|
+
const r = await this.call("POST", `/v1/reviews/${encodeURIComponent(reviewId)}/respond`, { response });
|
|
1080
|
+
if (!r.ok)
|
|
1081
|
+
this.fail(r);
|
|
1082
|
+
return this.asObject(r);
|
|
1083
|
+
}
|
|
1084
|
+
/** POST /v1/reviews/:id/report: flag a review for operator attention. */
|
|
1085
|
+
async reportReview(reviewId, reason) {
|
|
1086
|
+
const r = await this.call("POST", `/v1/reviews/${encodeURIComponent(reviewId)}/report`, { reason });
|
|
1087
|
+
if (!r.ok)
|
|
1088
|
+
this.fail(r);
|
|
1089
|
+
return this.asObject(r);
|
|
1090
|
+
}
|
|
1091
|
+
/** POST /v1/reviews/:id/remove (operator): take a review down. */
|
|
1092
|
+
async removeReview(reviewId) {
|
|
1093
|
+
const r = await this.call("POST", `/v1/reviews/${encodeURIComponent(reviewId)}/remove`);
|
|
1094
|
+
if (!r.ok)
|
|
1095
|
+
this.fail(r);
|
|
1096
|
+
return this.asObject(r);
|
|
1097
|
+
}
|
|
1098
|
+
/** POST /v1/reviews/:id/unhold (operator): publish a held review. */
|
|
1099
|
+
async unholdReview(reviewId) {
|
|
1100
|
+
const r = await this.call("POST", `/v1/reviews/${encodeURIComponent(reviewId)}/unhold`);
|
|
1101
|
+
if (!r.ok)
|
|
1102
|
+
this.fail(r);
|
|
1103
|
+
return this.asObject(r);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
// Serialize `where`/`sort` into the `q` query param (URL-encoded JSON), or
|
|
1107
|
+
// undefined when neither is given. URLSearchParams handles the percent-encoding.
|
|
1108
|
+
function buildListQueryParam(where, sort) {
|
|
1109
|
+
const q = {};
|
|
1110
|
+
if (where !== undefined && where.length > 0)
|
|
1111
|
+
q.where = where;
|
|
1112
|
+
if (sort !== undefined && sort.length > 0)
|
|
1113
|
+
q.sort = sort;
|
|
1114
|
+
if (q.where === undefined && q.sort === undefined)
|
|
1115
|
+
return undefined;
|
|
1116
|
+
return JSON.stringify(q);
|
|
760
1117
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { HomespunClient, HomespunApiError } from "./client.js";
|
|
2
|
-
export type { ClientOptions, RelayResponse, AttachmentRef, UploadBlobOptions, PresignBlobOptions, AttachmentTokenMintResponse, ListBlobsOptions, AttachmentTokenAuditEntry, AttachmentTokenListResponse, QueryResponse, AppAsset, DeployAppRequest, DeployAppResponse, RedeployAppRequest, RedeployAppResponse, AppSummary, AppDetail, AppsPage, AppDomain, AppDomainDnsRecord, AppMember, AddAppMemberResult, AppRow, AppRowsPage, AppFeedEntry, AppFeedPage, } from "./client.js";
|
|
2
|
+
export type { ClientOptions, RelayResponse, AttachmentRef, UploadBlobOptions, PresignBlobOptions, AttachmentTokenMintResponse, ListBlobsOptions, AttachmentTokenAuditEntry, AttachmentTokenListResponse, QueryResponse, AppAsset, DeployAppRequest, DeployAppResponse, RedeployAppRequest, RedeployAppResponse, DeployCheckRequest, DeployCheckResult, KeyMintResult, AppSummary, AppDetail, AppsPage, AppDomain, AppDomainDnsRecord, AppMember, AddAppMemberResult, AppRow, AppRowsPage, ListScalar, ListWhereCondition, ListSortSpec, BatchRowInput, BatchRowResult, BatchResult, BatchWriteOptions, AppFeedEntry, AppFeedPage, CommunitySetupStep, } from "./client.js";
|
|
3
3
|
export { openStream } from "./stream.js";
|
|
4
4
|
export type { OpenStreamOptions, StreamHandlers, StreamHandle, } from "./stream.js";
|
|
5
5
|
export { openAppStream, appWsUrlFromAppUrl } from "./app-stream.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homespunapps/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "Homespun relay client: typed HTTP + WebSocket operations against a Homespun relay. Framework-free.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"relay",
|
|
12
12
|
"human-in-the-loop"
|
|
13
13
|
],
|
|
14
|
-
"homepage": "https://homespun.dev",
|
|
14
|
+
"homepage": "https://docs.homespun.dev",
|
|
15
15
|
"bugs": {
|
|
16
|
-
"url": "
|
|
16
|
+
"url": "https://github.com/homespunapps/homespun/issues"
|
|
17
17
|
},
|
|
18
18
|
"engines": {
|
|
19
19
|
"node": ">=20"
|
|
@@ -41,13 +41,18 @@
|
|
|
41
41
|
"test:unit": "vitest run"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"ws": "^8.
|
|
44
|
+
"ws": "^8.21.1",
|
|
45
45
|
"zod": "^4.4.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@types/node": "^
|
|
48
|
+
"@types/node": "^26.1.1",
|
|
49
49
|
"@types/ws": "^8.18.1",
|
|
50
|
-
"typescript": "^
|
|
50
|
+
"typescript": "^7.0.2",
|
|
51
51
|
"vitest": "^4.1.8"
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/homespunapps/homespun.git",
|
|
56
|
+
"directory": "packages/core"
|
|
52
57
|
}
|
|
53
58
|
}
|