needle-cloud 2.4.6 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as _logto_browser from '@logto/browser';
2
2
  import { UserScope } from '@logto/browser';
3
+ import * as _needle_tools_cloud_sdk_types from '@needle-tools/cloud-sdk/types';
3
4
  import Stripe from 'stripe';
4
5
 
5
6
  /**
@@ -35,9 +36,9 @@ type LogtoUserCustomData$1 = {
35
36
  declare namespace Auth {
36
37
  /** @param {boolean} _debug */
37
38
  function setDebug(_debug: boolean): void;
38
- let baseUrl: string;
39
- let redirectUri: string;
40
- let postSignOutRedirectUri: string;
39
+ let baseUrl: any;
40
+ let redirectUri: any;
41
+ let postSignOutRedirectUri: any;
41
42
  /**
42
43
  * Register a callback to be called when the authentication state changes
43
44
  * @param {Function} callback - The callback to register
@@ -126,15 +127,7 @@ type AuthInitOpts = {
126
127
  debug?: boolean;
127
128
  };
128
129
 
129
- /**
130
- * Basic web component for login/logout
131
- */
132
- declare class NeedleLoginButton extends HTMLButtonElement {
133
- connectedCallback(): void;
134
- __unsubscribeOnChange: Function;
135
- disconnectedCallback(): void;
136
- __update(): void;
137
- }
130
+ declare let NeedleLoginButton: any;
138
131
 
139
132
  /**
140
133
  * The authentication token
@@ -291,6 +284,23 @@ type AIChatSyncResponse = {
291
284
  chat_url?: string,
292
285
  }
293
286
 
287
+ /** Options for a one-shot completion (POST /v1/ai/completions). */
288
+ type AICompletionOptions = {
289
+ /** System prompt. Counts toward the 32k char input cap together with the message. */
290
+ system?: string,
291
+ /** Requested output ceiling in tokens; the server clamps it. */
292
+ max_tokens?: number,
293
+ signal?: AbortSignal,
294
+ }
295
+
296
+ /** Response of a one-shot completion (POST /v1/ai/completions). */
297
+ type AICompletionResponse = {
298
+ text: string,
299
+ /** The model that served the completion (server-resolved). */
300
+ model: string,
301
+ token_usage: AiTokenUsageInfo,
302
+ }
303
+
294
304
  /**
295
305
  * Discriminated union for events yielded by the SSE chat stream.
296
306
  *
@@ -385,6 +395,15 @@ type ToolDefinition$1 = {
385
395
  name: string,
386
396
  description: string,
387
397
  inputSchema: Record<string, any>,
398
+ /**
399
+ * What the tool TOUCHES — drives the server's origin gate (#127):
400
+ * `scene` (the inspected scene; offered on any page), `local` (the user's
401
+ * machine — files/exec) and `account` (the cloud account) are withheld
402
+ * from chats running on untrusted origins. Omitted = the server falls
403
+ * back to judging by the registering source; anything beyond the scene
404
+ * should declare itself.
405
+ */
406
+ capability?: "scene" | "local" | "account",
388
407
  }
389
408
 
390
409
  /**
@@ -417,6 +436,23 @@ type ToolRequestOutcome = {
417
436
  images?: Array<{ url: string, detail?: ImageDetail }>,
418
437
  }
419
438
 
439
+ /** Options for `connectToolWebSocket()`. */
440
+ /**
441
+ * The minimal WebSocket surface `connectToolWebSocket` uses — small on purpose so a
442
+ * relayed/bridged transport is easy to implement (e.g. one whose real socket lives in
443
+ * a browser-extension service worker, to escape the host page's CSP).
444
+ */
445
+ type WebSocketLike = {
446
+ /** WebSocket.OPEN (1) is the only state compared. */
447
+ readonly readyState: number,
448
+ send(data: string): void,
449
+ close(code?: number, reason?: string): void,
450
+ onopen: ((ev?: any) => any) | null,
451
+ onmessage: ((ev: { data: any }) => any) | null,
452
+ onclose: ((ev?: any) => any) | null,
453
+ onerror: ((ev?: any) => any) | null,
454
+ }
455
+
420
456
 
421
457
  // ─── ChatSession Types ──────────────────────────────────────────────
422
458
 
@@ -447,6 +483,11 @@ type ChatSessionOptions$1 = {
447
483
  toolReadyTimeout?: number,
448
484
  /** Use personal chat context instead of team. Defaults to false (team). */
449
485
  personal?: boolean,
486
+ /**
487
+ * How the tool socket is opened — see {@link ToolConnectionOptions.createWebSocket}.
488
+ * Defaults to `new WebSocket`; the chrome extension passes a SW-relayed socket.
489
+ */
490
+ createWebSocket?: (url: string) => WebSocketLike,
450
491
  }
451
492
 
452
493
  /**
@@ -493,6 +534,14 @@ type ChatSessionHandle$1 = {
493
534
  interruptChat(slug: string): Promise<void>,
494
535
  /** Read chat history for a slug. */
495
536
  readChat(slug: string, options?: { offset?: number, limit?: number }): Promise<AIChatGETResponse>,
537
+ /**
538
+ * Reattach to an ACTIVE generation for this chat (after a dropped SSE
539
+ * connection). The server keeps generating and replays every buffered
540
+ * event from the start, then streams live. Returns `null` when no
541
+ * generation is running — the caller decides between resending (nothing
542
+ * arrived) and reading the finished answer via `readChat`.
543
+ */
544
+ reconnectChat(slug: string): Promise<AsyncGenerator<ChatStreamEvent> | null>,
496
545
  /** List recent chats. */
497
546
  listChats(): Promise<RecentChatsResponse$1>,
498
547
  /**
@@ -542,8 +591,32 @@ type ChatSessionSendOptions$1 = {
542
591
  */
543
592
  /**
544
593
  * @param {Array<Filelike>} files
545
- * @param {{org?:string|null} & { name?:string, abort?:AbortSignal, authToken?:string|null }} opts
546
- * @returns {Promise<{error:string} | {job_id:string, content_id:string, url?:string | null, skipped?:boolean}>}
594
+ * @param {{org?:string|null} & { name?:string, abort?:AbortSignal, authToken?:string|null, type?:"upload"|"deployment",
595
+ * content_id?:string, thumbnail?:File,
596
+ * metadata?:import("@needle-tools/cloud-sdk/types").JobMetadata }} opts
597
+ * `metadata` records where this upload came from — the change that produced it, what
598
+ * built it, and anything else you want to keep with it. It shows on the version in
599
+ * Needle Cloud, and is visible to your team only. `source` and `runner` are open
600
+ * shapes, and you can add your own fields alongside them:
601
+ *
602
+ * metadata: {
603
+ * source: { type: "unity", id: sceneGuid, label: "MainScene.unity" },
604
+ * client: { name: "my-app", version: "1.2.3" },
605
+ * }
606
+ *
607
+ * Up to 8 KB in total; `source.message` up to 200 characters. Send a display name
608
+ * rather than an email address — email fields are rejected.
609
+ * `thumbnail` is set as the content
610
+ * item's picture after the upload lands — pass your own render (a canvas blob, a
611
+ * screenshot) when your app knows better than a generated preview what the asset looks
612
+ * like. JPEG/PNG/WebP up to 5 MB. A thumbnail that fails to upload does NOT fail the
613
+ * upload: the result carries `thumbnail_error` instead.
614
+ * `content_id` targets an EXISTING content item:
615
+ * the upload lands as a new version of it (deployment redeploys). It never
616
+ * CREATES an item under that id — an unknown/foreign id is a 404 from
617
+ * upload/begin. Without it, the server mints an identifier (or reuses an
618
+ * org-scoped item whose NAME matches — the CLI redeploy behavior).
619
+ * @returns {Promise<{error:string} | {job_id:string, content_id:string, url?:string | null, skipped?:boolean, thumbnail_error?:string}>}
547
620
  */
548
621
  declare function uploadFiles(files: Array<Filelike>, opts: {
549
622
  org?: string | null;
@@ -551,6 +624,10 @@ declare function uploadFiles(files: Array<Filelike>, opts: {
551
624
  name?: string;
552
625
  abort?: AbortSignal;
553
626
  authToken?: string | null;
627
+ type?: "upload" | "deployment";
628
+ content_id?: string;
629
+ thumbnail?: File;
630
+ metadata?: _needle_tools_cloud_sdk_types.JobMetadata;
554
631
  }): Promise<{
555
632
  error: string;
556
633
  } | {
@@ -558,6 +635,7 @@ declare function uploadFiles(files: Array<Filelike>, opts: {
558
635
  content_id: string;
559
636
  url?: string | null;
560
637
  skipped?: boolean;
638
+ thumbnail_error?: string;
561
639
  }>;
562
640
  type Filelike = {
563
641
  name: string;
@@ -728,6 +806,17 @@ declare function listChats(opts?: ChatAuthOpts): Promise<RecentChatsResponse>;
728
806
  * @returns {Promise<import("@needle-tools/cloud-sdk/types/api.ai.js").ChatSessionHandle>}
729
807
  */
730
808
  declare function createChatSession(options?: ChatSessionOptions$1, authOpts?: ChatAuthOpts): Promise<ChatSessionHandle$1>;
809
+ /**
810
+ * One-shot AI completion — a single request/response with no chat session,
811
+ * history or tools (POST /v1/ai/completions). For small tasks like generating
812
+ * a revision description. Billed to the org's monthly AI token quota.
813
+ *
814
+ * @param {string} message
815
+ * @param {import("@needle-tools/cloud-sdk/types/api.ai.js").AICompletionOptions} [options]
816
+ * @param {ChatAuthOpts} [authOpts]
817
+ * @returns {Promise<import("@needle-tools/cloud-sdk/types/api.ai.js").AICompletionResponse>}
818
+ */
819
+ declare function aiComplete(message: string, options?: AICompletionOptions, authOpts?: ChatAuthOpts): Promise<AICompletionResponse>;
731
820
  type ChatAuthOpts = {
732
821
  org?: string;
733
822
  authToken?: string;
@@ -784,7 +873,7 @@ type JobModel = {
784
873
  /**
785
874
  * Parameters is a JSON object that contains the parameters for the job
786
875
  */
787
- parameters?: UploadParameters | OptimizationParameters | AiMaterialParameters | HdriCompressParameters | SplatCompressParameters | null;
876
+ parameters?: UploadParameters | OptimizationParameters | AiMaterialParameters | Ai3dAssetParameters | HdriCompressParameters | SplatCompressParameters | null;
788
877
 
789
878
  triggered_by?: TriggerType;
790
879
  /**
@@ -804,7 +893,7 @@ type JobModel = {
804
893
  error?: string | null;
805
894
  };
806
895
 
807
- type JobType = "upload" | "conversion" | "optimization" | "deployment" | "ai-material" | "hdri-compress" | "splat-compress" | (string & {});
896
+ type JobType = "upload" | "conversion" | "optimization" | "deployment" | "ai-material" | "ai-3d-asset" | "hdri-compress" | "splat-compress" | (string & {});
808
897
  type JobStatus = "new" | "pending" | "running" | "completed" | "failed" | "canceled" | "stalled";
809
898
  declare type OptimizationParameters = {
810
899
  /** The job identifier to optimize (injected by createJob) */
@@ -864,6 +953,87 @@ declare type AiMaterialParameters = {
864
953
  seed?: number,
865
954
  }
866
955
 
956
+ /** Quality tier for 3D asset generation.
957
+ * - `draft` — fastest and cheapest. Geometry with a base color texture.
958
+ * - `standard` — adds physically based material maps (metallic, roughness, normal).
959
+ * Requires a commercial plan. */
960
+ declare type Asset3dQuality = "draft" | "standard";
961
+
962
+ /** Parameters for the `ai-3d-asset` job — generates a 3D model (GLB) from a text prompt. */
963
+ declare type Ai3dAssetParameters = {
964
+ /** The job identifier (injected by createJob) */
965
+ job_identifier?: string,
966
+ /** Describe the object you want. One object, not a scene — e.g.
967
+ * "a weathered wooden crate with iron bands". Max 2000 characters. */
968
+ prompt: string,
969
+ /** A richer description derived from `prompt`, filled in for you and used for the
970
+ * actual generation. Set by the server — requests that include it are rejected. */
971
+ expanded_prompt?: string,
972
+ /** Quality tier (default: `standard`). */
973
+ quality?: Asset3dQuality,
974
+ /** Target polygon count. Leave unset to let the generator choose.
975
+ * Accepted range: 40000–1500000. */
976
+ face_count?: number,
977
+ /** Optional idempotency key. Two requests with the same prompt and the same key
978
+ * return the same asset instead of generating a second one. When omitted, every
979
+ * request generates a new asset. */
980
+ request_key?: string,
981
+ /** Reserved — image-guided generation is not available yet and passing this is
982
+ * rejected. Present so adding it later does not change this type. */
983
+ image_url?: string,
984
+ /** Reserved — see `image_url`. */
985
+ image_urls?: string[],
986
+ }
987
+
988
+
989
+
990
+ /**
991
+ * Provenance for a job — what produced it. Optional on job creation; shown on the version
992
+ * row in the Cloud UI so an asset answers "where did this come from?".
993
+ *
994
+ * Deliberately NOT git-shaped. `source` names whatever system the input came from and
995
+ * `runner` whatever executed it; git is one `source.type` among many, and a DCC export or
996
+ * a hosted runner fits the same slots. The generic fields (`label`, `author`, `message`,
997
+ * `url`) are what the UI renders, so a producer nobody has written yet still displays
998
+ * something useful.
999
+ */
1000
+ declare type JobMetadata = {
1001
+ /** Where the INPUT came from. */
1002
+ source?: {
1003
+ /** `"git"`, `"unity"`, `"blender"`, … — open on purpose. */
1004
+ type?: string,
1005
+ /** Stable id within that system: a commit sha, an asset guid, a document id. */
1006
+ id?: string,
1007
+ /** Short label for display when the specific fields don't tell the story. */
1008
+ label?: string,
1009
+ /** Link back to it — the commit, the asset page, the document. */
1010
+ url?: string,
1011
+ /** Display name of whoever authored the change. Email addresses are rejected. */
1012
+ author?: string,
1013
+ /** One short line: a commit subject, an export note. Max 200 characters. */
1014
+ message?: string,
1015
+ /** System-specific extras (branch, tag, repository, project, scene, …). */
1016
+ [key: string]: unknown,
1017
+ },
1018
+ /** What EXECUTED the job — a CI runner, a workstation, a service. */
1019
+ runner?: {
1020
+ /** `"github-actions"`, `"gitlab-ci"`, `"local"`, … — open on purpose. */
1021
+ type?: string,
1022
+ /** Run id within that system. */
1023
+ id?: string,
1024
+ /** Link to the run. */
1025
+ url?: string,
1026
+ /** Who or what set it off. */
1027
+ actor?: string,
1028
+ /** `"push"`, `"pull_request"`, `"manual"`, `"schedule"`, … */
1029
+ event?: string,
1030
+ },
1031
+ /** What talked to the API. */
1032
+ client?: { name?: string, version?: string },
1033
+ /** Your own keys. */
1034
+ [key: string]: unknown,
1035
+ }
1036
+
867
1037
  // #region Tone mapping
868
1038
 
869
1039
  /**
@@ -1146,6 +1316,14 @@ type ContentVersion = {
1146
1316
 
1147
1317
  /** Derived variants (optimizations, deployments, AI generations) */
1148
1318
  variants: ContentVariant[],
1319
+
1320
+ /**
1321
+ * Where this version came from — the change that produced it, and what ran it.
1322
+ *
1323
+ * Only present when whoever created it supplied provenance, so treat it as optional
1324
+ * everywhere. Visible to your team only; it is never part of a public listing.
1325
+ */
1326
+ metadata?: JobMetadata,
1149
1327
  } & ContentProcessingState
1150
1328
 
1151
1329
  /** A derived variant produced from an upload (optimization, deployment, etc). */
@@ -1327,6 +1505,10 @@ type ContentListResponse = {
1327
1505
  // change on one side, decide whether the other side needs the same change.
1328
1506
  // Public is intentionally slimmer — no internal IDs (user, updated_by, etc.),
1329
1507
  // no per-file enumeration, no labels/variants/processing-state.
1508
+ //
1509
+ // `metadata` (provenance) is DELIBERATELY absent here and must stay absent: it carries
1510
+ // branch names, CI run urls and author names. Adding it to a public type would leak all
1511
+ // of that to anonymous callers. See plans/job-metadata.md.
1330
1512
 
1331
1513
  /** One version of a public content item.
1332
1514
  * Each version exposes a single entrypoint URL — relative file references
@@ -1425,6 +1607,36 @@ type ListPublicContentResponse = {
1425
1607
  /** Cursor to fetch the next page; omitted when there are no more items. */
1426
1608
  next_cursor?: string;
1427
1609
  };
1610
+ // #endregion
1611
+
1612
+
1613
+ // #region Content mutations — PATCH, DELETE, restore
1614
+
1615
+ /** Request body for PATCH /v1/content/:identifier */
1616
+ type UpdateContentRequestBody = {
1617
+ /** Content title (1-255 chars) */
1618
+ title?: string;
1619
+ /** Content description (max 512 chars) */
1620
+ description?: string;
1621
+ /** Set or clear password protection (null = remove). Requires commercial license. */
1622
+ password?: string | null;
1623
+ /** Comma-separated tags (max 512 chars total, each tag < 50 chars, max 40 tags) */
1624
+ tags?: string | null;
1625
+ /** Public visibility — when true, content is readable by anyone (authenticated or not)
1626
+ * via the public discovery API. UI for setting this is currently gated by a frontend
1627
+ * feature flag; the API itself does not enforce additional restrictions. */
1628
+ is_public?: boolean;
1629
+ /** Viewer presentation settings (lighting, tone mapping, background).
1630
+ * Pass `null` to clear; omit to leave unchanged. */
1631
+ presentation?: PresentationV1 | null;
1632
+ /** Thumbnail upload request — server returns presigned URL in response */
1633
+ upload_thumbnail?: {
1634
+ name: string;
1635
+ content_size: number;
1636
+ content_type: string;
1637
+ content_md5: string;
1638
+ };
1639
+ };
1428
1640
 
1429
1641
  /**
1430
1642
  * List publicly discoverable content items on Needle Cloud.
@@ -1490,6 +1702,91 @@ declare function getContent(identifier: string, opts?: {
1490
1702
  org?: string;
1491
1703
  authToken?: string;
1492
1704
  }): Promise<GetContentDetailResponse>;
1705
+ /**
1706
+ * Update a content item's metadata, and optionally set its thumbnail.
1707
+ *
1708
+ * Pass a `thumbnail` File — a canvas render, a screenshot, whatever your app produced —
1709
+ * and it is uploaded and set as the item's picture. Useful when the client knows better
1710
+ * than a generated preview what the asset actually looks like. JPEG, PNG or WebP, up to
1711
+ * 5 MB.
1712
+ *
1713
+ * Only the fields you pass are changed; anything you leave out stays as it is. Note that
1714
+ * a field you DO pass replaces the stored value outright — there is no merging, and
1715
+ * (today) no way to undo it.
1716
+ *
1717
+ * Visibility settings (password protection, public listing) are deliberately not
1718
+ * accepted here — see {@link REFUSED_FIELDS}.
1719
+ *
1720
+ * Auth: uses the browser session (`Auth.requestAccessToken(org)`) unless an explicit
1721
+ * `authToken` is passed — hosts that manage tokens themselves pass one and skip the
1722
+ * Auth singleton.
1723
+ *
1724
+ * @param {string} identifier - The content item identifier
1725
+ * @param {{ thumbnail?: File, org?: string, authToken?: string }
1726
+ * & Omit<import("@needle-tools/cloud-sdk/types/api.content").UpdateContentRequestBody,
1727
+ * "upload_thumbnail" | "password" | "is_public">} data
1728
+ * @returns {Promise<{ success: boolean }>}
1729
+ *
1730
+ * @example
1731
+ * import { updateContent } from "needle-cloud";
1732
+ * await updateContent("abc123", { title: "Baked Crate", tags: "baked,crate" });
1733
+ *
1734
+ * @example
1735
+ * // Use your own render as the thumbnail
1736
+ * const blob = await new Promise(r => canvas.toBlob(r, "image/png"));
1737
+ * const thumbnail = new File([blob], "preview.png", { type: "image/png" });
1738
+ * await updateContent("abc123", { thumbnail });
1739
+ */
1740
+ declare function updateContent(identifier: string, data: {
1741
+ thumbnail?: File;
1742
+ org?: string;
1743
+ authToken?: string;
1744
+ } & Omit<UpdateContentRequestBody, "upload_thumbnail" | "password" | "is_public">): Promise<{
1745
+ success: boolean;
1746
+ }>;
1747
+
1748
+ /**
1749
+ * Generate a 3D model from a text prompt and wait until it is ready.
1750
+ *
1751
+ * Describe a single object — "a weathered wooden crate with iron bands" — rather than
1752
+ * a scene. The prompt is enriched server-side before generation, so short prompts are
1753
+ * fine. Generating a 3D asset requires a paid plan and counts against the plan's
1754
+ * monthly allowance.
1755
+ *
1756
+ * Resolves once the model is ready and returns it as a GLB you can hand straight to a
1757
+ * loader, or save. Pass `download: false` if you only want the job and will fetch the
1758
+ * result later.
1759
+ *
1760
+ * Auth: uses the browser session (`Auth.requestAccessToken(org)`) unless an explicit
1761
+ * `authToken` is passed — hosts that manage tokens themselves pass one and skip the
1762
+ * Auth singleton.
1763
+ *
1764
+ * @param {import("@needle-tools/cloud-sdk/types").Ai3dAssetRequestParameters
1765
+ * & import("@needle-tools/cloud-sdk/types").Generate3dAssetOptions
1766
+ * & { org?: string, authToken?: string, signal?: AbortSignal }} opts
1767
+ * @returns {Promise<{ job: import("@needle-tools/cloud-sdk/types").JobModel, glb?: ArrayBuffer, filename?: string }>}
1768
+ *
1769
+ * @example
1770
+ * import { generate3dAsset } from "needle-cloud";
1771
+ * const { glb, filename } = await generate3dAsset({ prompt: "a wooden crate" });
1772
+ *
1773
+ * @example
1774
+ * // Higher quality, with progress
1775
+ * const { glb } = await generate3dAsset({
1776
+ * prompt: "a brass telescope on a tripod",
1777
+ * quality: "standard",
1778
+ * onProgress: ({ status, progress }) => console.log(status, progress),
1779
+ * });
1780
+ */
1781
+ declare function generate3dAsset(opts: _needle_tools_cloud_sdk_types.Ai3dAssetRequestParameters & _needle_tools_cloud_sdk_types.Generate3dAssetOptions & {
1782
+ org?: string;
1783
+ authToken?: string;
1784
+ signal?: AbortSignal;
1785
+ }): Promise<{
1786
+ job: _needle_tools_cloud_sdk_types.JobModel;
1787
+ glb?: ArrayBuffer;
1788
+ filename?: string;
1789
+ }>;
1493
1790
 
1494
1791
  /**
1495
1792
  * @internal Needle editor cloud projects — versioned revisions of OPAQUE
@@ -1541,6 +1838,32 @@ interface ProjectRevisionInfo {
1541
1838
  actor: string;
1542
1839
  /** ISO timestamp */
1543
1840
  timestamp: string | null;
1841
+ /** hash of the revision's `.needle/thumbnail.jpg` manifest entry — fetch
1842
+ * via blobs/download for a per-revision preview; null when the revision
1843
+ * carried no thumbnail */
1844
+ thumbnail?: string | null;
1845
+ /** byte length of the document blob — revisions LISTING only (null when
1846
+ * the blob is unexpectedly absent) */
1847
+ size?: number | null;
1848
+ /** total bytes of the folder files riding this revision (manifest sum;
1849
+ * blobs are shared/deduped — footprint, not storage cost) */
1850
+ filesSize?: number | null;
1851
+ /** short human handle ("client-review") — user-editable via revisions/update */
1852
+ label?: string | null;
1853
+ /** what changed in this revision — user-written (a generated default may
1854
+ * fill it later; user text wins by being set) */
1855
+ description?: string | null;
1856
+ }
1857
+
1858
+ /** PARTIAL revision metadata update: absent = keep, null = clear */
1859
+ interface UpdateProjectRevisionRequest {
1860
+ /** project id (public handle) */
1861
+ project: string;
1862
+ revision: number;
1863
+ /** 1-64 characters, or null to clear */
1864
+ label?: string | null;
1865
+ /** max 4000 characters, or null to clear */
1866
+ description?: string | null;
1544
1867
  }
1545
1868
 
1546
1869
  interface CreateProjectResponse {
@@ -1756,18 +2079,24 @@ declare function checkProjectBlobs(hashes: readonly string[], opts?: {
1756
2079
  missing: string[];
1757
2080
  }>;
1758
2081
  /**
1759
- * @internal Presigned PUT url for one file blob.
2082
+ * @internal Presigned PUT url for one file blob. A provided filename rides
2083
+ * the storage metadata; the response's `contentType` (derived from it) is
2084
+ * signed into the url — the PUT must send that exact Content-Type header.
1760
2085
  * @param {string} hash
1761
2086
  * @param {number} size
1762
2087
  * @param {{ org?: string, authToken?: string }} [opts]
1763
- * @returns {Promise<{ url: string, key: string }>}
2088
+ * @param {{ filename?: string }} [meta]
2089
+ * @returns {Promise<{ url: string, key: string, contentType?: string }>}
1764
2090
  */
1765
2091
  declare function requestProjectBlobUpload(hash: string, size: number, opts?: {
1766
2092
  org?: string;
1767
2093
  authToken?: string;
2094
+ }, meta?: {
2095
+ filename?: string;
1768
2096
  }): Promise<{
1769
2097
  url: string;
1770
2098
  key: string;
2099
+ contentType?: string;
1771
2100
  }>;
1772
2101
  /**
1773
2102
  * @internal Complete (HEAD-verify + index) an uploaded file blob.
@@ -1807,6 +2136,90 @@ declare function listProjectRevisions(project: string, opts?: {
1807
2136
  }): Promise<{
1808
2137
  revisions: ProjectRevisionInfo[];
1809
2138
  }>;
2139
+ /**
2140
+ * @internal Label/description a revision — partial patch (absent = keep, null = clear).
2141
+ * @param {import("@needle-tools/cloud-sdk/types/api.projects").UpdateProjectRevisionRequest} request
2142
+ * @param {{ org?: string, authToken?: string }} [opts]
2143
+ * @returns {Promise<import("@needle-tools/cloud-sdk/types/api.projects").ProjectRevisionInfo>}
2144
+ */
2145
+ declare function updateProjectRevision(request: UpdateProjectRevisionRequest, opts?: {
2146
+ org?: string;
2147
+ authToken?: string;
2148
+ }): Promise<ProjectRevisionInfo>;
2149
+ /**
2150
+ * @internal Deploy attempts of a project: `contentId` = the CURRENT deployment
2151
+ * identity (newest completed attempt), `attempts` = per-revision history
2152
+ * (status/error from the upload job, url = per-job site link).
2153
+ * @param {string} project
2154
+ * @param {{ org?: string, authToken?: string }} [opts]
2155
+ */
2156
+ declare function getProjectDeployments(project: string, opts?: {
2157
+ org?: string;
2158
+ authToken?: string;
2159
+ }): Promise<any>;
2160
+ /**
2161
+ * @internal Record a deploy attempt (write-back after uploadFiles returned the
2162
+ * content_id/job_id). Idempotent per job_id.
2163
+ * @param {{ project: string, content_id: string, job_id: string, revision?: number | null }} request
2164
+ * @param {{ org?: string, authToken?: string }} [opts]
2165
+ */
2166
+ declare function recordProjectDeployment(request: {
2167
+ project: string;
2168
+ content_id: string;
2169
+ job_id: string;
2170
+ revision?: number | null;
2171
+ }, opts?: {
2172
+ org?: string;
2173
+ authToken?: string;
2174
+ }): Promise<any>;
2175
+ /**
2176
+ * @internal Promote a pushed project asset to a content item — server-side,
2177
+ * bytes stay in the org blob pool and are served in place; the promotion row
2178
+ * marks the asset used/exposed. Path = asset identity (re-promote versions
2179
+ * the same item; unchanged returns type "exists").
2180
+ * @param {{ project: string, path: string, revision?: number | null }} request
2181
+ * @param {{ org?: string, authToken?: string }} [opts]
2182
+ * @returns {Promise<{ type: "promoted" | "exists", content_id: string, job_id: string, url: string | null }>}
2183
+ */
2184
+ declare function promoteProjectAsset(request: {
2185
+ project: string;
2186
+ path: string;
2187
+ revision?: number | null;
2188
+ }, opts?: {
2189
+ org?: string;
2190
+ authToken?: string;
2191
+ }): Promise<{
2192
+ type: "promoted" | "exists";
2193
+ content_id: string;
2194
+ job_id: string;
2195
+ url: string | null;
2196
+ }>;
2197
+ /**
2198
+ * @internal Promotion attempts of a project (the used/exposed listing) —
2199
+ * status/error from the upload job, newest first.
2200
+ * @param {string} project
2201
+ * @param {{ org?: string, authToken?: string }} [opts]
2202
+ */
2203
+ declare function getProjectPromotions(project: string, opts?: {
2204
+ org?: string;
2205
+ authToken?: string;
2206
+ }): Promise<{
2207
+ attempts: Array<{
2208
+ path: string;
2209
+ hash: string;
2210
+ revision: number | null;
2211
+ contentId: string;
2212
+ jobId: string;
2213
+ createdAt: string | null;
2214
+ actor: string;
2215
+ status: string | null;
2216
+ error: string | null;
2217
+ url: string | null;
2218
+ editUrl: string | null;
2219
+ thumbnailUrl: string | null;
2220
+ contentType: string | null;
2221
+ }>;
2222
+ }>;
1810
2223
 
1811
2224
  /**
1812
2225
  * Start the checkout process
@@ -1819,11 +2232,96 @@ declare function startCheckoutSession(args: {
1819
2232
  status: number;
1820
2233
  }>;
1821
2234
 
2235
+ /**
2236
+ * Public price lookup. Deliberately kept in its own module, free of any auth
2237
+ * import: this call needs no token, and `../auth` reads `window` at module scope
2238
+ * — so importing it here would make a price lookup impossible in a service
2239
+ * worker or a node test, neither of which has a `window`.
2240
+ *
2241
+ * @typedef {import("@needle-tools/cloud-sdk/types").ProductInformation} ProductInformation
2242
+ * @typedef {import("@needle-tools/cloud-sdk/types").GetProductInformationOptions} GetProductInformationOptions
2243
+ */
2244
+ /**
2245
+ * What a product costs right now, for display: amount, currency, and the sale
2246
+ * fields when one is running (`original` for a strikethrough, `label`,
2247
+ * `expires_at`). Amounts are in MAJOR currency units (29 means €29).
2248
+ *
2249
+ * Pass the same `price` you pass to `checkoutProduct` — showing one price and
2250
+ * charging another is exactly what that parameter prevents.
2251
+ *
2252
+ * Resolves `null` when the product is unknown or the API is unreachable, so a
2253
+ * caller can simply omit the price: a missing price should never take down a buy
2254
+ * button that otherwise works.
2255
+ *
2256
+ * @param {string} product product name, e.g. "Needle Inspector"
2257
+ * @param {GetProductInformationOptions} [options]
2258
+ * @returns {Promise<ProductInformation | null>}
2259
+ */
2260
+ declare function getProductInformation(product: string, options?: GetProductInformationOptions): Promise<ProductInformation | null>;
2261
+ /**
2262
+ * Public price lookup. Deliberately kept in its own module, free of any auth
2263
+ * import: this call needs no token, and `../auth` reads `window` at module scope
2264
+ * — so importing it here would make a price lookup impossible in a service
2265
+ * worker or a node test, neither of which has a `window`.
2266
+ */
2267
+ type ProductInformation = _needle_tools_cloud_sdk_types.ProductInformation;
2268
+ /**
2269
+ * Public price lookup. Deliberately kept in its own module, free of any auth
2270
+ * import: this call needs no token, and `../auth` reads `window` at module scope
2271
+ * — so importing it here would make a price lookup impossible in a service
2272
+ * worker or a node test, neither of which has a `window`.
2273
+ */
2274
+ type GetProductInformationOptions = _needle_tools_cloud_sdk_types.GetProductInformationOptions;
2275
+
2276
+ /**
2277
+ * Purchase flow BY PRODUCT NAME.
2278
+ *
2279
+ * The older `startCheckoutSession` takes a Stripe `price_id`, so every caller has
2280
+ * to know Stripe's internal ids. These two work from the product name the rest of
2281
+ * the product surface already uses ("needle_inspector"), and they are a matched
2282
+ * pair: query the price with `getProductInformation` (payment.info.js — kept
2283
+ * auth-free so it also works without a `window`), charge it with
2284
+ * {@link checkoutProduct}, passing the same `price` to both so the number shown
2285
+ * is the number charged.
2286
+ *
2287
+ * @typedef {import("@needle-tools/cloud-sdk/types").CheckoutProductOptions} CheckoutProductOptions
2288
+ */
2289
+ /**
2290
+ * Start a checkout for a product and return the Stripe URL to send the buyer to.
2291
+ *
2292
+ * Returns the url rather than navigating, so the caller decides: a web app can
2293
+ * assign `location.href`, an editor can open a tab and poll for the license on
2294
+ * focus. Resolves `null` when checkout could not be created (not signed in, or
2295
+ * the API is unreachable).
2296
+ *
2297
+ * Authenticated: the server derives the buyer from the token, which is what makes
2298
+ * prefilling — and locking — the checkout email safe.
2299
+ *
2300
+ * @param {string} product product name, e.g. "needle_inspector"
2301
+ * @param {CheckoutProductOptions} [options]
2302
+ * @returns {Promise<{ url: string } | null>}
2303
+ */
2304
+ declare function checkoutProduct(product: string, options?: CheckoutProductOptions): Promise<{
2305
+ url: string;
2306
+ } | null>;
2307
+ /**
2308
+ * Purchase flow BY PRODUCT NAME.
2309
+ *
2310
+ * The older `startCheckoutSession` takes a Stripe `price_id`, so every caller has
2311
+ * to know Stripe's internal ids. These two work from the product name the rest of
2312
+ * the product surface already uses ("needle_inspector"), and they are a matched
2313
+ * pair: query the price with `getProductInformation` (payment.info.js — kept
2314
+ * auth-free so it also works without a `window`), charge it with
2315
+ * {@link checkoutProduct}, passing the same `price` to both so the number shown
2316
+ * is the number charged.
2317
+ */
2318
+ type CheckoutProductOptions = _needle_tools_cloud_sdk_types.CheckoutProductOptions;
2319
+
1822
2320
  declare namespace AuthScopes {
1823
2321
  let Basic: UserScope[];
1824
2322
  let Payment: string[];
1825
2323
  let All: string[];
1826
2324
  }
1827
2325
 
1828
- export { Auth, AuthScopes, NeedleLoginButton, archiveProject, checkProjectBlobs, cloneProject, completeProjectBlobUpload, createChatSession, createProject, deleteProject, getContent, getDeployments, getProjectHead, getPublicContent, getUserLicenses, listChats, listContent, listProjectRevisions, listProjects, listPublicContent, ownsProduct, pullProjectRevision, pushProjectRevision, requestProjectBlobDownload, requestProjectBlobUpload, restoreProject, startCheckoutSession, unarchiveProject, updateProject, uploadFiles };
2326
+ export { Auth, AuthScopes, NeedleLoginButton, aiComplete, archiveProject, checkProjectBlobs, checkoutProduct, cloneProject, completeProjectBlobUpload, createChatSession, createProject, deleteProject, generate3dAsset, getContent, getDeployments, getProductInformation, getProjectDeployments, getProjectHead, getProjectPromotions, getPublicContent, getUserLicenses, listChats, listContent, listProjectRevisions, listProjects, listPublicContent, ownsProduct, promoteProjectAsset, pullProjectRevision, pushProjectRevision, recordProjectDeployment, requestProjectBlobDownload, requestProjectBlobUpload, restoreProject, startCheckoutSession, unarchiveProject, updateContent, updateProject, updateProjectRevision, uploadFiles };
1829
2327
  export type { AuthData, AuthInitOpts, ChatAuthOpts, ChatSessionHandle, ChatSessionOptions, ChatSessionSendOptions, Deployment, Filelike, LogtoUserCustomData, RecentChatsResponse, ResourceUrl, ToolDefinition, UserInfoResponse };