@civitai/app-sdk 0.63.0 → 0.64.1

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
@@ -85,12 +85,13 @@ over `window.postMessage({ type, payload }, targetOrigin)`, discriminated by
85
85
  `type`. The full union is exported:
86
86
 
87
87
  - **parent → block**: `BLOCK_INIT`, `TOKEN_REFRESH`, `TOKEN_REFRESH_RESPONSE`,
88
- `ESTIMATE_RESULT`, `WORKFLOW_SUBMITTED`, `WORKFLOW_STATUS`,
88
+ `ESTIMATE_RESULT`, `ESTIMATE_BATCH_RESULT`, `WORKFLOW_SUBMITTED`, `WORKFLOW_STATUS`,
89
89
  `BUZZ_PURCHASE_RESULT`, `CHECKPOINT_PICKER_RESULT`, `USER_CHECKPOINT_SET`,
90
90
  `APP_STORAGE_*_RESULT`, `SUSPEND`, `RESUME`, `THEME_CHANGE`, `ROUTE_CHANGED`,
91
91
  `CONSENT_UNAVAILABLE` (`ParentToBlockMessage`).
92
92
  - **block → parent**: `BLOCK_READY`, `BLOCK_ERROR`, `REQUEST_TOKEN`,
93
- `RESIZE_IFRAME`, `SUBMIT_WORKFLOW`, `ESTIMATE_WORKFLOW`, `POLL_WORKFLOW`,
93
+ `RESIZE_IFRAME`, `SUBMIT_WORKFLOW`, `ESTIMATE_WORKFLOW`,
94
+ `ESTIMATE_WORKFLOW_BATCH`, `POLL_WORKFLOW`,
94
95
  `QUERY_APP_WORKFLOWS`, `CANCEL_APP_WORKFLOW`, `OPEN_BUZZ_PURCHASE`,
95
96
  `OPEN_CHECKPOINT_PICKER`, `SET_USER_CHECKPOINT`, `NAVIGATE`, `TRACK_EVENT`,
96
97
  `APP_STORAGE_*` (`BlockToParentMessage`).
@@ -96,7 +96,7 @@ export type { AppStorageRejectionReason } from './appStorageErrors.js';
96
96
  export { BLOCK_INIT_FRAGMENT_MARKER_KEY, BLOCK_INIT_FRAGMENT_VERSION, BLOCK_INIT_FRAGMENT_KEYS, encodeBlockInitFragment, parseBlockInitFragment, stripBlockInitFragment, } from './initFragment.js';
97
97
  export type { BlockInitFragment } from './initFragment.js';
98
98
  export { isMessage, BLOCK_TO_PARENT_MESSAGE_TYPES, OTHER_MESSAGE_TYPE_LABEL, boundBlockToParentMessageType, } from './messages.js';
99
- export type { BlockInitPayload, BlockToParentMessage, BlockToParentMessageType, ConsentUnavailablePayload, ConsentUnavailableReason, ParentToBlockMessage, ParentToBlockMessageType, SharedStorageItemWire, SharedStorageValue, WrappedToken, BlockBuzzTransactionsParams, BlockDailyCompensationParams, AppWorkflowsParams, } from './messages.js';
99
+ export type { BlockInitPayload, BlockToParentMessage, BlockToParentMessageType, ConsentUnavailablePayload, ConsentUnavailableReason, ParentToBlockMessage, ParentToBlockMessageType, SharedStorageItemWire, SharedStorageRateLimit, SharedStorageValue, WrappedToken, BlockBuzzTransactionsParams, BlockDailyCompensationParams, AppWorkflowsParams, } from './messages.js';
100
100
  /**
101
101
  * Runtime narrowing for the `slotId`-discriminated {@link BlockContext} union.
102
102
  * Values, not types — a slot context crosses a `postMessage` boundary, so its
@@ -109,7 +109,7 @@ export { isModelSlotContext, isPageSlotContext } from './types.js';
109
109
  * in `types.ts` for which of the two it uses and why.
110
110
  */
111
111
  export { isSignedIn } from './types.js';
112
- export type { BlockContext, KnownSlotId, ModelSlotId, PageSlotContext, PageSlotId, UnknownSlotContext, BlockAnalyticsDeclaration, BlockAnalyticsEventDeclaration, BlockAnalyticsPropertyDeclaration, BlockManifest, BlockManifestGood, BlockManifestV1, BlockSettings, BlockToken, ContentRating, ManifestBooleanField, ManifestIframe, ManifestNumberField, ManifestPage, ManifestPreview, ManifestSettingField, ManifestSettings, ManifestStringField, ManifestTarget, ModelSlotContext, SettingScope, SettingWidget, Theme, ViewerInfo, BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockSourceImage, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockNavigateScope, BlockTextToImageParams, BlockWorkflowSnapshot, BlockModelSubstitution, ModelSubstitutionReason, BlockStepToolCall, BlockTrainedEpoch, BlockPublishedModel, BuzzAccountType, ShowcaseImage, WorkflowBody, WorkflowBodyTextToImage, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyRecipe, WorkflowBodyCustomComfyInline, InlineComfyNode, WorkflowBodyStep, WorkflowBodyPassThroughStep, WorkflowStatus, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, AppWorkflowImage, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostRequest, BlockCreatePostResult, BlockCreatePostHostError, WorkflowBodyTraining, AiToolkitTrainingParams, AiToolkitTrainingParamsBase, AiToolkitPlainEcosystem, AiToolkitLrScheduler, AiToolkitOptimizerType, BlockTrainingQuote, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason, BlockPrepareTrainingDatasetHostError, BlockRunTrainingHostError, } from './types.js';
112
+ export type { BlockContext, KnownSlotId, ModelSlotId, PageSlotContext, PageSlotId, UnknownSlotContext, BlockAnalyticsDeclaration, BlockAnalyticsEventDeclaration, BlockAnalyticsPropertyDeclaration, BlockManifest, BlockManifestGood, BlockManifestV1, BlockSettings, BlockToken, ContentRating, ManifestBooleanField, ManifestIframe, ManifestNumberField, ManifestPage, ManifestPreview, ManifestSettingField, ManifestSettings, ManifestStringField, ManifestTarget, ModelSlotContext, SettingScope, SettingWidget, Theme, ViewerInfo, BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockSourceImage, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockNavigateScope, BlockTextToImageParams, BlockWorkflowSnapshot, BlockEstimateBatchAggregate, BlockModelSubstitution, ModelSubstitutionReason, BlockStepToolCall, BlockTrainedEpoch, BlockPublishedModel, BuzzAccountType, ShowcaseImage, WorkflowBody, WorkflowBodyTextToImage, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyRecipe, WorkflowBodyCustomComfyInline, InlineComfyNode, WorkflowBodyStep, WorkflowBodyPassThroughStep, WorkflowStatus, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, AppWorkflowImage, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostRequest, BlockCreatePostResult, BlockCreatePostHostError, WorkflowBodyTraining, AiToolkitTrainingParams, AiToolkitTrainingParamsBase, AiToolkitPlainEcosystem, AiToolkitLrScheduler, AiToolkitOptimizerType, BlockTrainingQuote, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason, BlockPrepareTrainingDatasetHostError, BlockRunTrainingHostError, } from './types.js';
113
113
  /**
114
114
  * Host bounds for the App Blocks `kind: 'training'` flow, mirrored from
115
115
  * civitai/civitai's zod — see `WorkflowBodyTraining`.
@@ -8,7 +8,7 @@
8
8
  * Wire format: `window.postMessage({ type, payload }, targetOrigin)`.
9
9
  */
10
10
  import type { ColorDomain } from './browsingLevel.js';
11
- import type { BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockNavigateScope, BlockContext, BlockSettings, Theme, ViewerInfo, WorkflowBody, BlockWorkflowSnapshot, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostResult, BlockTrainingDatasetItem, BlockTrainingDatasetResult, WorkflowBodyTraining } from './types.js';
11
+ import type { BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockNavigateScope, BlockContext, BlockSettings, Theme, ViewerInfo, WorkflowBody, BlockWorkflowSnapshot, BlockEstimateBatchAggregate, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostResult, BlockTrainingDatasetItem, BlockTrainingDatasetResult, WorkflowBodyTraining } from './types.js';
12
12
  /**
13
13
  * Filter params for `GET_BUZZ_TRANSACTIONS`. All optional; the host validates
14
14
  * them server-side (they are NEVER trusted for auth — the account is self-bound
@@ -239,6 +239,27 @@ export interface SharedStorageValue {
239
239
  */
240
240
  data?: unknown;
241
241
  }
242
+ /**
243
+ * Retry metadata on a RATE-LIMITED shared-storage refusal. The host adds it
244
+ * beside `error` on the six write replies (`SHARED_APPEND_RESULT`,
245
+ * `SHARED_UPDATE_RESULT`, `SHARED_VOTE_RESULT`, `SHARED_UNVOTE_RESULT`,
246
+ * `SHARED_WITHDRAW_RESULT`, `SHARED_REPORT_RESULT`) when the server refused the
247
+ * write because a per-(viewer, app) budget is empty, so a block can render the
248
+ * wait instead of parsing it out of the prose `error`.
249
+ *
250
+ * Its PRESENCE marks the refusal as rate-limited; every other refusal omits it.
251
+ * ADDITIVE + OPTIONAL: a host that predates it never sends it, so a block must
252
+ * treat "absent" as "not known to be rate-limited", never as "retry now".
253
+ *
254
+ * Same shape as the platform's tRPC `error.data.rateLimit` and the REST 429
255
+ * body's `rateLimit` (which also comes with a `Retry-After` header).
256
+ */
257
+ export interface SharedStorageRateLimit {
258
+ /** Whole seconds until a retry can succeed. An integer, at least 1. */
259
+ retryAfterSeconds: number;
260
+ /** ISO-8601 instant at which a retry can succeed. */
261
+ retryAt: string;
262
+ }
242
263
  /**
243
264
  * One SHARED-storage entry as it appears on the wire in `SHARED_LIST_RESULT`.
244
265
  * `createdAt`/`updatedAt` are ISO-8601 strings; the block-side hook rehydrates
@@ -360,6 +381,14 @@ export type ParentToBlockMessage = {
360
381
  requestId: string;
361
382
  snapshot: BlockWorkflowSnapshot;
362
383
  };
384
+ } | {
385
+ type: 'ESTIMATE_BATCH_RESULT';
386
+ payload: {
387
+ requestId: string;
388
+ snapshots?: BlockWorkflowSnapshot[];
389
+ aggregate?: BlockEstimateBatchAggregate;
390
+ error?: string;
391
+ };
363
392
  } | {
364
393
  type: 'WORKFLOW_SUBMITTED';
365
394
  payload: {
@@ -515,6 +544,8 @@ export type ParentToBlockMessage = {
515
544
  payload: {
516
545
  requestId: string;
517
546
  selected?: BlockResourceInfo;
547
+ selectedResources?: BlockResourceInfo[];
548
+ error?: string;
518
549
  };
519
550
  } | {
520
551
  type: 'IMAGE_UPLOAD_RESULT';
@@ -610,6 +641,7 @@ export type ParentToBlockMessage = {
610
641
  requestId: string;
611
642
  key: string;
612
643
  error?: string;
644
+ rateLimit?: SharedStorageRateLimit;
613
645
  };
614
646
  } | {
615
647
  type: 'SHARED_VOTE_RESULT';
@@ -617,6 +649,7 @@ export type ParentToBlockMessage = {
617
649
  requestId: string;
618
650
  count: number;
619
651
  error?: string;
652
+ rateLimit?: SharedStorageRateLimit;
620
653
  };
621
654
  } | {
622
655
  type: 'SHARED_UNVOTE_RESULT';
@@ -624,6 +657,7 @@ export type ParentToBlockMessage = {
624
657
  requestId: string;
625
658
  count: number;
626
659
  error?: string;
660
+ rateLimit?: SharedStorageRateLimit;
627
661
  };
628
662
  } | {
629
663
  type: 'SHARED_WITHDRAW_RESULT';
@@ -632,6 +666,7 @@ export type ParentToBlockMessage = {
632
666
  ok?: boolean;
633
667
  deleted?: boolean;
634
668
  error?: string;
669
+ rateLimit?: SharedStorageRateLimit;
635
670
  };
636
671
  } | {
637
672
  type: 'SHARED_UPDATE_RESULT';
@@ -639,6 +674,7 @@ export type ParentToBlockMessage = {
639
674
  requestId: string;
640
675
  ok?: boolean;
641
676
  error?: string;
677
+ rateLimit?: SharedStorageRateLimit;
642
678
  };
643
679
  } | {
644
680
  type: 'SHARED_GET_RESULT';
@@ -653,6 +689,7 @@ export type ParentToBlockMessage = {
653
689
  requestId: string;
654
690
  ok?: boolean;
655
691
  error?: string;
692
+ rateLimit?: SharedStorageRateLimit;
656
693
  };
657
694
  } | {
658
695
  type: 'SAVE_IMAGE_RESULT';
@@ -712,6 +749,12 @@ export type BlockToParentMessage = {
712
749
  requestId: string;
713
750
  body: WorkflowBody;
714
751
  };
752
+ } | {
753
+ type: 'ESTIMATE_WORKFLOW_BATCH';
754
+ payload: {
755
+ requestId: string;
756
+ bodies: WorkflowBody[];
757
+ };
715
758
  } | {
716
759
  type: 'POLL_WORKFLOW';
717
760
  payload: {
@@ -831,6 +874,16 @@ export type BlockToParentMessage = {
831
874
  resourceType: BlockResourcePickerType;
832
875
  /** Optional base-model family hint (ecosystem key or baseModel name). */
833
876
  baseModelGroup?: string;
877
+ /**
878
+ * Ask for SEVERAL resources in one picker session. LoRA only — the host
879
+ * refuses it with a Checkpoint. `max` is a whole number ≥ 1; the host
880
+ * clamps anything above 5 (the `additionalResources` cap) to 5. The
881
+ * reply is then `selectedResources`, not `selected`. OMIT the key for
882
+ * a single pick: its absence is what keeps that path unchanged.
883
+ */
884
+ multiple?: {
885
+ max: number;
886
+ };
834
887
  };
835
888
  } | {
836
889
  type: 'OPEN_IMAGE_UPLOAD';
@@ -1040,7 +1093,7 @@ export type BlockToParentMessageType = BlockToParentMessage['type'];
1040
1093
  * it is a measurement, not an invariant, and civitai's own compile-time gate
1041
1094
  * (published SDK union ⊆ `keyof INVENTORY`) is what holds one direction of it.
1042
1095
  */
1043
- export declare const BLOCK_TO_PARENT_MESSAGE_TYPES: readonly ["BLOCK_HELLO", "BLOCK_READY", "BLOCK_ERROR", "BLOCK_MESSAGE_REJECTED", "REQUEST_TOKEN", "RESIZE_IFRAME", "SUBMIT_WORKFLOW", "ESTIMATE_WORKFLOW", "POLL_WORKFLOW", "CANCEL_WORKFLOW", "OPEN_BUZZ_PURCHASE", "GET_BUZZ_BALANCE", "GET_VIEWER", "GET_BUZZ_TRANSACTIONS", "GET_BUZZ_ACCOUNTS", "GET_DAILY_COMPENSATION", "GET_WILDCARD_PACK", "QUERY_APP_WORKFLOWS", "PUBLISH_GENERATION_OUTPUTS", "CREATE_POST_FROM_APP", "PREPARE_TRAINING_DATASET", "RUN_TRAINING", "GET_IMAGES_BY_IDS", "CANCEL_APP_WORKFLOW", "OPEN_CHECKPOINT_PICKER", "OPEN_RESOURCE_PICKER", "OPEN_IMAGE_UPLOAD", "SET_USER_CHECKPOINT", "NAVIGATE", "REQUEST_SIGN_IN", "REQUEST_CONSENT", "TRACK_EVENT", "APP_STORAGE_GET", "APP_STORAGE_SET", "APP_STORAGE_DELETE", "APP_STORAGE_LIST", "APP_STORAGE_QUOTA", "SHARED_LIST", "SHARED_GET_COUNT", "SHARED_GET_COUNTS", "SHARED_APPEND", "SHARED_VOTE", "SHARED_UNVOTE", "SHARED_WITHDRAW", "SHARED_UPDATE", "SHARED_GET", "SHARED_REPORT", "SAVE_IMAGE", "SET_COLLECTION_FOLLOW"];
1096
+ export declare const BLOCK_TO_PARENT_MESSAGE_TYPES: readonly ["BLOCK_HELLO", "BLOCK_READY", "BLOCK_ERROR", "BLOCK_MESSAGE_REJECTED", "REQUEST_TOKEN", "RESIZE_IFRAME", "SUBMIT_WORKFLOW", "ESTIMATE_WORKFLOW", "ESTIMATE_WORKFLOW_BATCH", "POLL_WORKFLOW", "CANCEL_WORKFLOW", "OPEN_BUZZ_PURCHASE", "GET_BUZZ_BALANCE", "GET_VIEWER", "GET_BUZZ_TRANSACTIONS", "GET_BUZZ_ACCOUNTS", "GET_DAILY_COMPENSATION", "GET_WILDCARD_PACK", "QUERY_APP_WORKFLOWS", "PUBLISH_GENERATION_OUTPUTS", "CREATE_POST_FROM_APP", "PREPARE_TRAINING_DATASET", "RUN_TRAINING", "GET_IMAGES_BY_IDS", "CANCEL_APP_WORKFLOW", "OPEN_CHECKPOINT_PICKER", "OPEN_RESOURCE_PICKER", "OPEN_IMAGE_UPLOAD", "SET_USER_CHECKPOINT", "NAVIGATE", "REQUEST_SIGN_IN", "REQUEST_CONSENT", "TRACK_EVENT", "APP_STORAGE_GET", "APP_STORAGE_SET", "APP_STORAGE_DELETE", "APP_STORAGE_LIST", "APP_STORAGE_QUOTA", "SHARED_LIST", "SHARED_GET_COUNT", "SHARED_GET_COUNTS", "SHARED_APPEND", "SHARED_VOTE", "SHARED_UNVOTE", "SHARED_WITHDRAW", "SHARED_UPDATE", "SHARED_GET", "SHARED_REPORT", "SAVE_IMAGE", "SET_COLLECTION_FOLLOW"];
1044
1097
  /**
1045
1098
  * The bucket a message type falls into when the protocol does not declare it.
1046
1099
  *
@@ -57,6 +57,7 @@ export const BLOCK_TO_PARENT_MESSAGE_TYPES = [
57
57
  'RESIZE_IFRAME',
58
58
  'SUBMIT_WORKFLOW',
59
59
  'ESTIMATE_WORKFLOW',
60
+ 'ESTIMATE_WORKFLOW_BATCH',
60
61
  'POLL_WORKFLOW',
61
62
  'CANCEL_WORKFLOW',
62
63
  'OPEN_BUZZ_PURCHASE',
@@ -1468,7 +1468,27 @@ export interface BlockWorkflowSnapshot {
1468
1468
  workflowId: string;
1469
1469
  status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'expired' | 'canceled';
1470
1470
  cost?: {
1471
+ /** Whole Buzz. Includes {@link authorFee} whenever that field is present. */
1471
1472
  total: number;
1473
+ /**
1474
+ * The part of `total` that is the app author's per-generation fee, in whole
1475
+ * Buzz — so `total - authorFee` is the generation's own price. Display only:
1476
+ * it is the amount the host added into `total` for this reply.
1477
+ *
1478
+ * - **A number** wherever `total` was built from a fee quote: the
1479
+ * `estimate()` result for a `textToImage` or registered-`step` body, and
1480
+ * the refusal `submit()` resolves for those kinds when a budget, spend cap
1481
+ * or rate limit refuses the quoted price. **`0` means a fee was looked up
1482
+ * and none applies** to this request.
1483
+ * - **Absent** when the total is not itemised: a host that predates the
1484
+ * field; every snapshot of a submitted workflow (`submit` success, `poll`,
1485
+ * `cancel` — its total is the realized generation cost and the fee is a
1486
+ * separate charge); the registered-`step` `submit()` refusal for a missing
1487
+ * orchestrator price quote, which refuses before any fee is looked up; and
1488
+ * the kinds that price no fee (`customComfy`, pass-through `step`,
1489
+ * `training`). **Do not read absent as `0`** — show `total` on its own.
1490
+ */
1491
+ authorFee?: number;
1472
1492
  };
1473
1493
  imageUrls?: string[];
1474
1494
  error?: string;
@@ -1674,6 +1694,27 @@ export interface BlockWorkflowSnapshot {
1674
1694
  */
1675
1695
  trainingQuote?: BlockTrainingQuote;
1676
1696
  }
1697
+ /**
1698
+ * The run total a BATCH estimate reports beside its per-cell snapshots
1699
+ * (`ESTIMATE_BATCH_RESULT`, sent in reply to `ESTIMATE_WORKFLOW_BATCH`).
1700
+ *
1701
+ * `total` is the sum of `cost.total` over the cells that PRICED: a snapshot that
1702
+ * is not `'failed'` and carries a numeric `cost.total`, the same rule one
1703
+ * estimate is held to. It is the total for the whole list only when
1704
+ * `pricedCells === cellCount`. Otherwise it is a partial sum; show it as one.
1705
+ *
1706
+ * 🔴 IT IS A QUOTE, NOT A LOCK, exactly as each cell is. Nothing reserves it. The
1707
+ * host creates no workflow and no batch for the list, and each cell is priced
1708
+ * again when the app submits it.
1709
+ */
1710
+ export interface BlockEstimateBatchAggregate {
1711
+ /** Sum of `cost.total` over the priced cells, in Buzz. `0` when none priced. */
1712
+ total: number;
1713
+ /** How many cells priced and are therefore in `total`. */
1714
+ pricedCells: number;
1715
+ /** How many cells the host answered — the length of the list it was sent. */
1716
+ cellCount: number;
1717
+ }
1677
1718
  /** {@link BlockWorkflowSnapshot.trainingQuote}. */
1678
1719
  export interface BlockTrainingQuote {
1679
1720
  /** `tq_` + 32 hex characters. Opaque — pass it back verbatim. */
package/dist/index.d.ts CHANGED
@@ -25,11 +25,11 @@
25
25
  *
26
26
  * - **`./blocks`** — importing it RUNS `./safe-storage` for its side effect
27
27
  * (repairing `localStorage`/`sessionStorage` at an opaque origin). Folding
28
- * 157 symbols and that side effect into the root would make every OAuth app
28
+ * 159 symbols and that side effect into the root would make every OAuth app
29
29
  * that writes `import { exchangeCode } from '@civitai/app-sdk'` pay for the
30
30
  * Civitai-Apps contract it does not use. Blocks and OAuth apps are disjoint
31
31
  * audiences; see this package's README.
32
- * 🔴 That 157 is ASSERTED, not merely quoted — by
32
+ * 🔴 That 159 is ASSERTED, not merely quoted — by
33
33
  * `packages/civitai-app-sdk/test/export-surface.test.ts`, which pins
34
34
  * `surface.get('./blocks').size`. It said 119 here against a real 125 until
35
35
  * 2026-10-04, because the existing checks pin the root as an exact union and
package/dist/index.js CHANGED
@@ -25,11 +25,11 @@
25
25
  *
26
26
  * - **`./blocks`** — importing it RUNS `./safe-storage` for its side effect
27
27
  * (repairing `localStorage`/`sessionStorage` at an opaque origin). Folding
28
- * 157 symbols and that side effect into the root would make every OAuth app
28
+ * 159 symbols and that side effect into the root would make every OAuth app
29
29
  * that writes `import { exchangeCode } from '@civitai/app-sdk'` pay for the
30
30
  * Civitai-Apps contract it does not use. Blocks and OAuth apps are disjoint
31
31
  * audiences; see this package's README.
32
- * 🔴 That 157 is ASSERTED, not merely quoted — by
32
+ * 🔴 That 159 is ASSERTED, not merely quoted — by
33
33
  * `packages/civitai-app-sdk/test/export-surface.test.ts`, which pins
34
34
  * `surface.get('./blocks').size`. It said 119 here against a real 125 until
35
35
  * 2026-10-04, because the existing checks pin the root as an exact union and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civitai/app-sdk",
3
- "version": "0.63.0",
3
+ "version": "0.64.1",
4
4
  "description": "OAuth + PKCE, encrypted-cookie sessions, scopes, and orchestrator helpers for building third-party Civitai apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "repository": {
39
39
  "type": "string",
40
- "description": "Optional PUBLIC SOURCE-REPOSITORY link for an open-source app, rendered as a `Source` row on the app's `/apps` store DETAIL page (never on a store grid card). Manifest-governed: it flows to the store listing on moderator-approve and is re-synced from the manifest on every subsequent approved version; remove the key and the link is cleared. This is NOT your app's internal Civitai repository — it is a link you publish. Must be an `https://` URL, carrying no embedded credentials and no port, whose host is EXACTLY one of github.com, gitlab.com or codeberg.org (an exact-host allowlist — `www.github.com`, `gist.github.com` and `raw.githubusercontent.com` are all rejected), and whose path is exactly `/<owner>/<repo>`, the repository ROOT (a deep link such as `/owner/repo/tree/main` is rejected). A trailing `/` or `.git`, a query string and a fragment are accepted and STRIPPED — the stored value is normalised to `https://<host>/<owner>/<repo>`. Capped at 200 characters, kept in lockstep with MAX_REPOSITORY_URL_LENGTH in src/server/schema/blocks/external-app.schema.ts (a drift-guard test enforces equality, and enforces that this description names exactly the hosts REPOSITORY_HOST_ALLOWLIST contains). NOTE: as with `tagline`, the server measures the TRIMMED string while this maxLength counts the raw one, so the LENGTH bound here is never looser than the server's. The `pattern`, however, is a coarse SHAPE check and NOT the whole rule, so unlike `tagline` this field CAN pass local validation and still be rejected at submit. The server additionally requires each of the two path segments to begin with an ASCII letter or digit and to contain only letters, digits, `.`, `-` and `_`, and it strips a trailing `.git` BEFORE applying that test. Values such as a repository segment that is only `.git`, one starting with a dash, one containing a percent-escape, and one containing a non-ASCII character all match this pattern and are refused by the server. The pattern is deliberately left coarse rather than tightened: the CLI and the starter templates byte-mirror this file, and a stricter regex would be a compatibility change for every vendored copy. Treat passing local validation as necessary, not sufficient — `validateRepositoryUrl` in src/server/schema/blocks/external-app.schema.ts is authoritative.",
40
+ "description": "Optional PUBLIC SOURCE-REPOSITORY link for an open-source app, rendered as a `Source` row on the app's `/apps` store DETAIL page (never on a store grid card). Manifest-governed: it flows to the store listing on moderator-approve and is re-synced from the manifest on every subsequent approved version; remove the key and the link is cleared. This is NOT your app's internal Civitai repository — it is a link you publish. Must be an `https://` URL, carrying no embedded credentials and no port, whose host is EXACTLY one of github.com, gitlab.com or codeberg.org (an exact-host allowlist — `www.github.com`, `gist.github.com` and `raw.githubusercontent.com` are all rejected), and whose path is exactly `/<owner>/<repo>`, the repository ROOT (a deep link such as `/owner/repo/tree/main` is rejected). A trailing `/` or `.git`, a query string and a fragment are accepted and STRIPPED — the stored value is normalised to `https://<host>/<owner>/<repo>`. Capped at 200 characters, kept in lockstep with MAX_REPOSITORY_URL_LENGTH in src/server/schema/blocks/external-app.schema.ts (a drift-guard test enforces equality, and enforces that this description names exactly the hosts REPOSITORY_HOST_ALLOWLIST contains). NOTE: as with `tagline`, the server measures the TRIMMED string while this maxLength counts the raw one, so the LENGTH bound here is never looser than the server's. The `pattern`, however, is a coarse SHAPE check and NOT the whole rule, so unlike `tagline` this field CAN pass local validation and still be rejected by the server's manifest validation. The server additionally requires each of the two path segments to begin with an ASCII letter or digit and to contain only letters, digits, `.`, `-` and `_`, and it strips a trailing `.git` BEFORE applying that test. Values such as a repository segment that is only `.git`, one starting with a dash, one containing a percent-escape, and one containing a non-ASCII character all match this pattern and are refused by the server. The pattern is deliberately left coarse rather than tightened: the CLI and the starter templates byte-mirror this file, and a stricter regex would be a compatibility change for every vendored copy. Treat passing local validation as necessary, not sufficient — `validateRepositoryUrl` in src/server/schema/blocks/external-app.schema.ts is authoritative.",
41
41
  "minLength": 1,
42
42
  "maxLength": 200,
43
43
  "pattern": "^https://(github\\.com|gitlab\\.com|codeberg\\.org)/[^/]+/[^/]+/?$"
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "auth": {
74
74
  "type": "string",
75
- "description": "Which credential the host hands the block: \"block-token\" (default when omitted) is the block-scoped JWT; \"oauth\" asks for an opaque OAuth access token for the block's own OauthClient, which ordinary /api/v1 routes accept as a viewer bearer. Minting it is gated on a server flag that is not generally enabled, and also requires a signed-in viewer and a declared \"user:read:self\" scope; when any of those is unmet the host hands back a block JWT instead of failing, so branch on the token kind the host reports rather than on this field. An OAuth token is refused by the postMessage bridge procedures, which verify a block JWT only, and declaring \"oauth\" alongside any \"apps:storage:*\" scope is refused at submit time.",
75
+ "description": "Which credential the host hands the block: \"block-token\" (default when omitted) is the block-scoped JWT; \"oauth\" asks for an opaque OAuth access token for the block's own OauthClient, which ordinary /api/v1 routes accept as a viewer bearer. Minting it is gated on a server flag that is not generally enabled, and also requires a signed-in viewer and a declared \"user:read:self\" scope; when any of those is unmet the host hands back a block JWT instead of failing, so branch on the token kind the host reports rather than on this field. An OAuth token is refused by the postMessage bridge procedures, which verify a block JWT only, and a manifest declaring \"oauth\" alongside any \"apps:storage:*\" scope is rejected by manifest validation.",
76
76
  "enum": ["block-token", "oauth"]
77
77
  },
78
78
  "trustTier": {
@@ -253,7 +253,7 @@
253
253
  },
254
254
  "priceBuzz": {
255
255
  "type": "integer",
256
- "description": "Price in whole Buzz. Buzz has no sub-unit, so this must be a whole number, and the minimum is 2 rather than 1 because the app owner's 70% share is floored — a 1 Buzz item would earn its owner nothing, permanently. Capped so a single purchase can never drain an account; a viewer also has a daily ceiling across every app they have installed, so a purchase can be refused with a clean 4xx even at a legal price. Both bounds are kept in lockstep with BLOCK_GOOD_MIN_PRICE_BUZZ / BLOCK_GOOD_MAX_PRICE_BUZZ in src/shared/constants/block-goods.constants.ts by a drift-guard test. \u26a0\ufe0f THE MAXIMUM HERE IS THE WIDEST ANY KIND MAY CHARGE, NOT THE LIMIT FOR YOUR GOOD: a good with kind \"app_unlock\" is capped at 5000 Buzz by the platform validator, which this schema deliberately does not restate (it declares that validator authoritative). See the `kind` description. A value above that validates offline and is rejected at submit.",
256
+ "description": "Price in whole Buzz. Buzz has no sub-unit, so this must be a whole number, and the minimum is 2 rather than 1 because the app owner's 70% share is floored — a 1 Buzz item would earn its owner nothing, permanently. Capped so a single purchase can never drain an account; a viewer also has a daily ceiling across every app they have installed, so a purchase can be refused with a clean 4xx even at a legal price. Both bounds are kept in lockstep with BLOCK_GOOD_MIN_PRICE_BUZZ / BLOCK_GOOD_MAX_PRICE_BUZZ in src/shared/constants/block-goods.constants.ts by a drift-guard test. \u26a0\ufe0f THE MAXIMUM HERE IS THE WIDEST ANY KIND MAY CHARGE, NOT THE LIMIT FOR YOUR GOOD: a good with kind \"app_unlock\" is capped at 5000 Buzz by the platform validator, which this schema deliberately does not restate (it declares that validator authoritative). See the `kind` description. A value above that validates offline and is rejected by the server's manifest validation.",
257
257
  "minimum": 2,
258
258
  "maximum": 50000
259
259
  },