@lotics/cli 0.263.0 → 0.264.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.
@@ -1,20 +1,3 @@
1
- // ../shared/src/transport_error.ts
2
- function gatewayErrorMessage(status) {
3
- if (status === 524) {
4
- return "The request took too long to finish (gateway timeout). It may still be running \u2014 check back in a moment, or try again.";
5
- }
6
- if (status >= 500) {
7
- return "The service is temporarily unavailable. Please try again shortly.";
8
- }
9
- return "The service returned an unexpected response. Please try again.";
10
- }
11
- function transportErrorMessage(status, parsed) {
12
- const jsonMessage = parsed && typeof parsed.message === "string" ? parsed.message : null;
13
- if (jsonMessage === null) return gatewayErrorMessage(status);
14
- const authored = typeof parsed.code === "string" && parsed.code.length > 0;
15
- return status < 500 || authored ? jsonMessage : gatewayErrorMessage(status);
16
- }
17
-
18
1
  // ../shared/src/multipart_parts.ts
19
2
  var DEFAULT_MAX_RETRIES = 5;
20
3
  var MAX_RETRY_DELAY_MS = 15e3;
@@ -150,44 +133,6 @@ var LoticsRequestError = class extends Error {
150
133
  body;
151
134
  };
152
135
  var WEB_APP_URL = process.env.LOTICS_WEB_URL ?? "https://lotics.ai";
153
- async function fetchOfficialStarters(apiUrl) {
154
- const read = await getPublicJson(`${apiUrl}/v1/starters/official`, "list the packages");
155
- if (!read.ok) {
156
- throw new Error(
157
- `Lotics answered ${read.status} listing the packages. If this keeps happening, browse ${WEB_APP_URL}/docs/cli.`
158
- );
159
- }
160
- return read.body;
161
- }
162
- async function getPublicJson(url, what) {
163
- let response;
164
- try {
165
- response = await fetch(url, { signal: AbortSignal.timeout(PUBLIC_FETCH_TIMEOUT_MS) });
166
- } catch (error) {
167
- throw new Error(
168
- `Could not reach Lotics to ${what} (${error instanceof Error ? error.message : String(error)}). Check your connection, or browse ${WEB_APP_URL}/docs/cli.`
169
- );
170
- }
171
- if (!response.ok) return { ok: false, status: response.status };
172
- return { ok: true, body: await response.json() };
173
- }
174
- async function readOfficialStarter(apiUrl, starter_id) {
175
- const read = await getPublicJson(
176
- `${apiUrl}/v1/starters/official/${encodeURIComponent(starter_id)}`,
177
- `read ${starter_id}`
178
- );
179
- if (!read.ok) return read;
180
- return { ok: true, package: read.body };
181
- }
182
- async function getOfficialStarter(apiUrl, starter_id) {
183
- const read = await readOfficialStarter(apiUrl, starter_id);
184
- if (!read.ok) {
185
- throw new Error(
186
- `Lotics answered ${read.status} reading ${starter_id}. Only packages Lotics publishes can be read without an account.`
187
- );
188
- }
189
- return read.package;
190
- }
191
136
  var PUBLIC_FETCH_TIMEOUT_MS = 1e4;
192
137
  async function startCliLogin(apiUrl, email) {
193
138
  let response;
@@ -232,12 +177,9 @@ function newRequestId() {
232
177
  var LoticsClient = class {
233
178
  apiKey;
234
179
  workspaceId;
235
- /** The active "View as" target member id, if any. Read-only after
236
- * construction — surfaced so `lotics app dev` can show it in the banner. */
180
+ /** The active "View as" target member id, if any; sent on every request. */
237
181
  viewAsMemberId;
238
- /** API URL the client is configured against. Read-only after construction.
239
- * Surfaced for callers that need to display it (`lotics app dev`'s banner) or
240
- * to hand it on (the wrapper page's RPC target, the scaffold's font proxy). */
182
+ /** The instance this client sends every request to. */
241
183
  baseUrl;
242
184
  constructor(options) {
243
185
  this.apiKey = options.apiKey;
@@ -278,7 +220,7 @@ var LoticsClient = class {
278
220
  * `x-posthog-session-id` and `x-request-id` onto the per-request Logger, so
279
221
  * they ride EVERY log line that request emits — the validation 400, the tool
280
222
  * error, the timing. Sending them is therefore the whole of the correlation
281
- * work: it turns an anonymous API-key request into "`app workflow set`, from
223
+ * work: it turns an anonymous API-key request into "`app deploy`, from
282
224
  * cli 0.117.0, the fourth command of this session".
283
225
  *
284
226
  * The request id is minted HERE, with the other headers, so it cannot reach
@@ -360,33 +302,6 @@ var LoticsClient = class {
360
302
  async createWorkspace(body) {
361
303
  return this.request("POST", "/v1/workspaces", body);
362
304
  }
363
- /**
364
- * Apply a workspace MODEL to the current workspace — the from-scratch half of
365
- * the starter pipeline, on a contract nobody published.
366
- *
367
- * Additive: with `adopt`, an entity whose label already names a table here
368
- * binds to it and gains the fields, options and views it is missing; without
369
- * `adopt`, that label is refused. Nothing is ever modified or deleted, and
370
- * rows land only where every bound table is empty. Admin-only.
371
- */
372
- async scaffoldWorkspace(body) {
373
- return this.request("POST", "/v1/workspaces/scaffold", body);
374
- }
375
- /**
376
- * Read this workspace's schema back as a model — the tables it has (or only
377
- * the ones named), their fields, options and views, plus its roles.
378
- *
379
- * A pure read, and admin-only for the same reason the scaffold is: the whole
380
- * schema is what comes back.
381
- */
382
- async exportWorkspaceModel(opts = {}) {
383
- const params = new URLSearchParams();
384
- if (opts.tables !== void 0 && opts.tables.length > 0) {
385
- params.set("tables", opts.tables.join(","));
386
- }
387
- const qs = params.toString();
388
- return this.request("GET", `/v1/workspaces/model${qs ? `?${qs}` : ""}`);
389
- }
390
305
  /**
391
306
  * What each alias of a workspace model became here, with what this workspace
392
307
  * calls every one of them now.
@@ -399,43 +314,6 @@ var LoticsClient = class {
399
314
  async getModelBinding() {
400
315
  return this.request("GET", "/v1/workspaces/model/binding");
401
316
  }
402
- /**
403
- * Forget what one alias is bound to here — the escape from a binding whose
404
- * target has been deleted. Forgets everything addressed under it too: an
405
- * entity owns its fields, its options and its rows.
406
- */
407
- async deleteModelBinding(kind, alias) {
408
- const params = new URLSearchParams({ kind, alias });
409
- return this.request("DELETE", `/v1/workspaces/model/binding?${params.toString()}`);
410
- }
411
- /**
412
- * This workspace's tables, id and display name — the label side of every
413
- * alias bind, and the one reading of `query_tables` the CLI has.
414
- *
415
- * Here rather than at each caller because the tool answers a row shape
416
- * (`table_id` / `table_name`) that four commands were each unpacking by hand,
417
- * and four unpackings of one wire shape are four places a field rename breaks.
418
- */
419
- async listTables() {
420
- const listed = await this.execute("query_tables", {}, { format: "json" });
421
- if (listed.error) throw new Error(`could not list this workspace's tables: ${listed.error}`);
422
- return (Array.isArray(listed.result) ? listed.result : []).flatMap((row) => {
423
- if (row === null || typeof row !== "object") return [];
424
- const { table_id, table_name } = row;
425
- return typeof table_id === "string" && typeof table_name === "string" ? [{ id: table_id, name: table_name }] : [];
426
- });
427
- }
428
- /** This workspace's document templates, id and name — the same reading, one level over. */
429
- async listTemplates() {
430
- const read = await this.execute("query_templates", {}, { format: "json" });
431
- if (read.error) throw new Error(`could not list this workspace's document templates: ${read.error}`);
432
- const listed = read.result === null || typeof read.result !== "object" ? [] : Reflect.get(read.result, "templates");
433
- return (Array.isArray(listed) ? listed : []).flatMap((row) => {
434
- if (row === null || typeof row !== "object") return [];
435
- const { id, name } = row;
436
- return typeof id === "string" && typeof name === "string" ? [{ id, name }] : [];
437
- });
438
- }
439
317
  /**
440
318
  * Record which file each of a model's document PATHS was uploaded as.
441
319
  *
@@ -447,14 +325,6 @@ var LoticsClient = class {
447
325
  async recordModelDocuments(documents) {
448
326
  return this.request("PUT", "/v1/workspaces/model/binding/documents", { documents });
449
327
  }
450
- /**
451
- * Record which app each of a plan's app aliases became — stated by the create
452
- * that made it, since an app row is made from a name and the alias is the
453
- * author's file. What a sibling act naming that app resolves through.
454
- */
455
- async recordModelApps(apps) {
456
- return this.request("PUT", "/v1/workspaces/model/binding/apps", { apps });
457
- }
458
328
  /** Renames (and re-settings) the CURRENT workspace — the endpoint reads the
459
329
  * target from the request's workspace, never a path id. */
460
330
  async updateWorkspace(body) {
@@ -526,132 +396,9 @@ var LoticsClient = class {
526
396
  return this.request("PATCH", "/v1/knowledge_docs", body);
527
397
  }
528
398
  // --- Apps ---
529
- async getApp(app_id) {
530
- return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}`);
531
- }
532
399
  async createApp(body) {
533
400
  return this.request("POST", "/v1/apps", body);
534
401
  }
535
- /**
536
- * Take a starter off the shelf, or `undo` to put it back (backs `opctl
537
- * library unpublish`). It hides from non-owning orgs and can no longer be
538
- * copied; copies already made are unaffected — they never linked back.
539
- * Owner-org admin-only.
540
- */
541
- async unpublishStarter(starter_id, body) {
542
- return this.request("POST", `/v1/starters/${encodeURIComponent(starter_id)}/unpublish`, body);
543
- }
544
- /**
545
- * Edit a starter's registry listing — the name and description a stranger
546
- * reads before copying, and what every copy's app row is created from.
547
- *
548
- * A version is an immutable snapshot; the listing is not. Omit a field to
549
- * leave it, pass `description: null` to clear it. Owner-org admin-only.
550
- */
551
- async editStarterListing(starter_id, body) {
552
- return this.request("POST", `/v1/starters/${encodeURIComponent(starter_id)}/listing`, body);
553
- }
554
- // --- Starters (registry reads + copies) ---
555
- // Authoring is server-side, through the publish job (`requestStarterPublish`).
556
- // There is no client-side create-starter / upload-bundle path.
557
- /**
558
- * The starters this organization can copy — Lotics-reviewed ones plus its own,
559
- * never a catalogue of everything published. The server returns exactly what
560
- * instantiate would accept, so the list cannot offer a refusal. Admin-only.
561
- */
562
- async listStarters() {
563
- return this.request("GET", "/v1/starters");
564
- }
565
- /**
566
- * Copy a starter into the current workspace.
567
- *
568
- * Server-side this scaffolds the schema, creates the templates, docs and
569
- * sample records, creates every app the starter carries and deploys each
570
- * from its prebuilt dist — no build anywhere. `apps` reports each deploy;
571
- * one that failed carries its `error` and the copy is complete around it.
572
- * Admin-only.
573
- */
574
- async instantiateStarter(starter_id, body) {
575
- return this.request(
576
- "POST",
577
- `/v1/starters/${encodeURIComponent(starter_id)}/instantiate`,
578
- body
579
- );
580
- }
581
- /**
582
- * Which starter this app is the origin of. 404 when it has published none.
583
- *
584
- * The app row carries no pin, so this is the only app→starter direction there
585
- * is — provenance lives on the published version. Admin-only.
586
- */
587
- async getAppOriginStarter(app_id) {
588
- return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/origin-starter`);
589
- }
590
- /**
591
- * Apply the latest version of the package this app was copied from.
592
- *
593
- * The other provenance direction from `getAppOriginStarter`, and the only one
594
- * that writes: that one answers which package this app PUBLISHED, this one
595
- * reads what a copy recorded and re-derives the app against a later release.
596
- *
597
- * The offer is partial by design and the result says how partial: schema is
598
- * additive (`created_fields`, and a `dropped_fields` the new version stopped
599
- * declaring is left standing), and an artifact the owner has edited is kept
600
- * and named (`skipped_workflows` / `skipped_agents`). An app with no
601
- * provenance is a 400, and one already on the latest release a 409 — both
602
- * refusals, both before any write. Admin-only.
603
- */
604
- async upgradeApp(app_id, opts = {}) {
605
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/upgrade`, {
606
- ...opts.acknowledge_breaking_api_change ? { acknowledge_breaking_api_change: true } : {},
607
- ...opts.connections === void 0 ? {} : { connections: opts.connections }
608
- });
609
- }
610
- /**
611
- * Capture live records from this workspace as a starter's sample data.
612
- *
613
- * The alias-keyed shape is produced SERVER-side, because the contract alias
614
- * space is minted by extract and exists nowhere a project can read it. Pure
615
- * read — the caller writes the returned files into the project and reviews
616
- * them, which matters: these rows are copied verbatim into every workspace
617
- * that takes the starter. Admin-only.
618
- */
619
- async captureStarterFixtures(app_id, opts = {}) {
620
- const params = new URLSearchParams();
621
- if (opts.entities !== void 0 && opts.entities.length > 0) {
622
- params.set("entities", opts.entities.join(","));
623
- }
624
- if (opts.limit !== void 0) params.set("limit", String(opts.limit));
625
- const qs = params.toString();
626
- return this.request(
627
- "GET",
628
- `/v1/apps/${encodeURIComponent(app_id)}/fixtures-capture${qs ? `?${qs}` : ""}`
629
- );
630
- }
631
- /**
632
- * Fetch a registry starter's metadata (`latest_version` and the Lotics-backed
633
- * `is_official` trust badge). Admin-only; cross-tenant by id.
634
- */
635
- async getStarter(starter_id) {
636
- return this.request("GET", `/v1/starters/${encodeURIComponent(starter_id)}`);
637
- }
638
- /** Version history newest-first (no contract payloads) — backs `opctl library show`. Admin-only. */
639
- async listStarterVersions(starter_id) {
640
- return this.request("GET", `/v1/starters/${encodeURIComponent(starter_id)}/versions`);
641
- }
642
- /**
643
- * One version's contract — what a copy of it is supposed to produce. The
644
- * history above omits it (heavy per row); this is the read that carries it,
645
- * so a check compares a copy against the declaration itself rather than
646
- * against a description of it. Admin-only; cross-tenant by id like
647
- * `getStarter`.
648
- */
649
- async getStarterVersion(starter_id, version) {
650
- return this.request(
651
- "GET",
652
- `/v1/starters/${encodeURIComponent(starter_id)}/versions/${encodeURIComponent(String(version))}`
653
- );
654
- }
655
402
  /**
656
403
  * Workspace-wide dangling-reference sweep — active app/workflow artifacts
657
404
  * whose prefixed schema ids no longer resolve. Backs
@@ -660,106 +407,11 @@ var LoticsClient = class {
660
407
  async getWorkspaceDanglingReferences() {
661
408
  return this.request("GET", "/v1/workspaces/dangling-references");
662
409
  }
663
- // --- Starter publishing (the authoring verbs; copying is `instantiateStarter`) ---
664
- /**
665
- * Preview publishing a set of this workspace's apps as one starter version —
666
- * the GET behind `opctl library publish` (no `--yes`). The server runs the
667
- * same extraction the publish runs and reports which starter it would
668
- * release into (null: it would mint one), the next version, the aliases a
669
- * first publish can still rename, the diff against the current version, the
670
- * knowledge delta, and the findings (an `error` blocks the publish). No
671
- * writes. Admin-only.
672
- */
673
- async previewStarterPublish(opts) {
674
- const params = new URLSearchParams();
675
- params.set("app_ids", opts.app_ids.join(","));
676
- if (opts.starter_id !== void 0) params.set("starter_id", opts.starter_id);
677
- if (opts.knowledge_doc_ids !== void 0) params.set("knowledge_doc_ids", opts.knowledge_doc_ids.join(","));
678
- if (opts.renames !== void 0 && opts.renames.length > 0) params.set("renames", JSON.stringify(opts.renames));
679
- if (opts.name !== void 0) params.set("name", opts.name);
680
- if (opts.description !== void 0) params.set("description", opts.description);
681
- if (opts.icon !== void 0) params.set("icon", opts.icon);
682
- if (opts.color !== void 0) params.set("color", opts.color);
683
- return this.request("GET", `/v1/starters/publish-preview?${params.toString()}`);
684
- }
685
- /**
686
- * Publish this workspace's apps as a starter version — a JOB, because every
687
- * app is built once against sentinel field keys and eleven builds outlast a
688
- * request. Everything a request can refuse is refused here with nothing
689
- * written: a blocking finding or another publish still running for this
690
- * org (409), a missing deploy or a bad declaration (400). The response is
691
- * the job to poll with `getStarterPublish`. Admin-only.
692
- */
693
- async requestStarterPublish(body) {
694
- const { color, ...rest } = body;
695
- return this.request("POST", "/v1/starters/publishes", {
696
- ...rest,
697
- ...color !== void 0 ? { theme: { color } } : {}
698
- });
699
- }
700
- /** The state of a publish: which app is building, and the version once every dist is in. */
701
- async getStarterPublish(publish_id) {
702
- return this.request("GET", `/v1/starters/publishes/${encodeURIComponent(publish_id)}`);
703
- }
704
- /**
705
- * The ROW RULE each of these tables declares — `private_filters` as stored, or
706
- * `null` where the table has none.
707
- *
708
- * `GET /v1/tables/{id}` rather than the `get_table` tool beside it: the rule is
709
- * an IAM fact about who may read a row, and the tool's output is the schema an
710
- * agent writes records against, which is why it carries fields and not this.
711
- * One call per id. A table this credential may not read — 403, or 404 for one
712
- * that is gone — is DROPPED: the only caller warns about rules it can see, and
713
- * a table it cannot read is not evidence of one. EVERY other failure throws.
714
- * An expired key, a 500, a timeout and an offline host all mean the scan has
715
- * no answer, and swallowing them would render as "this table declares no
716
- * rule" — the guard at its quietest exactly where it knows least.
717
- *
718
- * The filter is carried untyped: a published `.d.ts` cannot name
719
- * `@lotics/shared`'s filter schema, and the one reader asks a single question
720
- * of the tree rather than interpreting it.
721
- */
722
- async getTableRowRules(tableIds) {
723
- const tables = await Promise.all(
724
- tableIds.map(async (table_id) => {
725
- try {
726
- const table = await this.request(
727
- "GET",
728
- `/v1/tables/${encodeURIComponent(table_id)}`
729
- );
730
- return { id: table.id, name: table.name, private_filters: table.private_filters ?? null };
731
- } catch (error) {
732
- const notVisible = error instanceof LoticsRequestError && (error.status === 403 || error.status === 404);
733
- if (!notVisible) throw error;
734
- return null;
735
- }
736
- })
737
- );
738
- return tables.filter((table) => table !== null);
739
- }
740
- /**
741
- * The organization's member groups — the directory `lotics app codegen` turns
742
- * into the `GRP` alias map.
743
- *
744
- * Over the HTTP route rather than `query_member_groups`, because the tool is
745
- * admin-only and the route is member-visible by design (`docs/iam.md` § Groups:
746
- * reading the directory is not administration). Codegen runs for every author,
747
- * so an admin-only read here would leave a non-admin's `GRP` empty and their
748
- * app unbuildable.
749
- */
750
- async getMemberGroups() {
751
- const { groups } = await this.request(
752
- "GET",
753
- "/v1/iam/member_groups"
754
- );
755
- return groups.map((group) => ({ id: group.id, name: group.name }));
756
- }
757
410
  /**
758
- * Resolve the display name + fields (incl. select options) of the given tables
759
- * — the schema `lotics app codegen` turns into the runtime `.lotics/app_fields.ts`
760
- * alias maps. One `get_table` call per id (the tool surface has no batch
761
- * variant); a missing/inaccessible table is dropped rather than throwing, so a
762
- * stale id in the scope set never fails codegen.
411
+ * Resolve the display name + fields (incl. select options) of the given tables.
412
+ * One `get_table` call per id (the tool surface has no batch variant); a
413
+ * missing/inaccessible table is dropped rather than throwing, so a stale id in
414
+ * the scope set never fails the caller.
763
415
  */
764
416
  async getWorkspaceSchema(tableIds) {
765
417
  const tables = await Promise.all(
@@ -794,75 +446,11 @@ var LoticsClient = class {
794
446
  );
795
447
  return tables.filter((t) => t !== null);
796
448
  }
797
- /**
798
- * Rename an app's public subdomain — the label its origin is built on.
799
- * Mirrors PUT /v1/apps/{app_id}/subdomain, and returns the finished `origin`
800
- * because only the instance knows the domain and scheme it serves apps on.
801
- * The old subdomain stops resolving once the change lands.
802
- */
803
- async setAppSubdomain(app_id, public_subdomain) {
804
- return this.request(
805
- "PUT",
806
- `/v1/apps/${encodeURIComponent(app_id)}/subdomain`,
807
- { public_subdomain }
808
- );
809
- }
810
- /**
811
- * Publish the app's API — the owner's promise about what its declared queries
812
- * and workflows return to a caller outside the app.
813
- *
814
- * Snapshots the contract and answers the version it took, plus what the
815
- * published surface exposes that the owner may not have intended. A query
816
- * that does not name its columns is refused (400) before anything is written:
817
- * the app cannot promise field names it never stated.
818
- */
819
- async publishAppApi(app_id) {
820
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/api/publish`, {});
821
- }
822
- /** End the promise. `unpublished: false` means the app was publishing nothing,
823
- * which this leaves unchanged. The superseded snapshot stays, so a later
824
- * publish continues the numbering rather than reusing a version. */
825
- async unpublishAppApi(app_id) {
826
- return this.request("DELETE", `/v1/apps/${encodeURIComponent(app_id)}/api/publish`);
827
- }
828
- /** Whether the app is publishing an API, and which contract version. */
829
- async getAppApiPublication(app_id) {
830
- return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/api/publication`);
831
- }
832
- /**
833
- * The published API as an OpenAPI 3.1 document — what a consumer's own
834
- * generator reads. Rendered from the live SNAPSHOT rather than the manifest,
835
- * so it describes what the app has promised; 404 while nothing is published.
836
- */
837
- async getAppOpenApiDocument(app_id) {
838
- return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/openapi.json`);
839
- }
840
- async getAppVersion(app_id, version_id) {
841
- return this.request(
842
- "GET",
843
- `/v1/apps/${encodeURIComponent(app_id)}/versions/${encodeURIComponent(version_id)}`
844
- );
845
- }
846
- async getAppVersionSourceUrl(app_id, version_id) {
847
- const result = await this.request(
848
- "GET",
849
- `/v1/apps/${encodeURIComponent(app_id)}/versions/${encodeURIComponent(version_id)}/source`
850
- );
851
- return result.url;
852
- }
853
- /** Deploy history for an app — newest first. Backs `lotics app versions`. */
854
- async listAppVersions(app_id, opts) {
855
- const qs = new URLSearchParams();
856
- if (opts?.limit != null) qs.set("limit", String(opts.limit));
857
- if (opts?.offset != null) qs.set("offset", String(opts.offset));
858
- const suffix = qs.toString() ? `?${qs.toString()}` : "";
859
- return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/versions${suffix}`);
860
- }
861
449
  // ── App iframe RPC endpoints ──────────────────────────────────────────────
862
450
  // These mirror the two ops handled by frontend/features/app_ui/app_iframe_host.tsx.
863
451
  // The deployed iframe sends postMessage to the parent frontend, which calls
864
- // these same endpoints via the user's session cookie. `lotics app dev`
865
- // forwards the iframe's postMessage to these methods using the CLI's API key.
452
+ // these same endpoints via the user's session cookie; here they run under the
453
+ // client's API key.
866
454
  /**
867
455
  * Run a named query declared in the app's manifest, scoped to the app's IAM
868
456
  * principal. Mirrors POST /v1/apps/{app_id}/query.
@@ -870,294 +458,11 @@ var LoticsClient = class {
870
458
  async appQuery(app_id, body) {
871
459
  return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/query`, body);
872
460
  }
873
- async appMembers(app_id, group_id) {
874
- const qs = group_id ? `?group_id=${encodeURIComponent(group_id)}` : "";
875
- return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/members${qs}`);
876
- }
877
- /**
878
- * Resolve the full option set (key, label, color) of a named query's select
879
- * columns, and the units of its figures read per row — the picker companion
880
- * to `appQuery`. Mirrors
881
- * POST /v1/apps/{app_id}/field-options.
882
- */
883
- async appFieldOptions(app_id, alias) {
884
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/field-options`, { alias });
885
- }
886
- /**
887
- * What the app's writes will change — each bound workflow's record-write subset, each query's column
888
- * sources, the fields they name. Mirrors GET /v1/apps/{app_id}/write_model; the dev server hands it to
889
- * the app's SDK whole, so its parts stay opaque here.
890
- */
891
- async appWriteModel(app_id) {
892
- return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/write_model`);
893
- }
894
- /**
895
- * Where the app's SDK drew a write the server then stored differently, counted by workflow, field and kind.
896
- * Mirrors POST /v1/apps/{app_id}/prediction_misses; the server checks the report's shape.
897
- */
898
- async appPredictionMisses(app_id, report) {
899
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/prediction_misses`, report);
900
- }
901
- /**
902
- * Execute a workflow by alias declared in package.json#lotics.workflows.
903
- * Mirrors POST /v1/apps/{app_id}/workflows/{alias}/execute.
904
- */
905
- async appWorkflow(app_id, alias, inputs, minted_ids) {
906
- const url = `${this.baseUrl}/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/execute`;
907
- const headers = this.buildHeaders();
908
- headers["Content-Type"] = "application/json";
909
- let response;
910
- try {
911
- response = await fetch(url, { method: "POST", headers, body: JSON.stringify({ inputs, ...minted_ids === void 0 ? {} : { minted_ids } }) });
912
- } catch (err) {
913
- return { status: "error", message: err instanceof Error ? err.message : "The workflow request failed." };
914
- }
915
- const text = await response.text();
916
- let parsed = null;
917
- if (text) {
918
- try {
919
- parsed = JSON.parse(text);
920
- } catch {
921
- }
922
- }
923
- if (response.ok) return parsed ?? {};
924
- return { status: "error", message: transportErrorMessage(response.status, parsed) };
925
- }
926
- /**
927
- * Bind (create or replace) an app workflow by alias via the `set_app_workflow`
928
- * tool — the SINGLE author of `apps.workflows` + the workflow row. `source` is
929
- * the verbatim JS-subset body (no `on({...})` trigger). `inputs`/`outputs` are
930
- * the typed schemas declared in `package.json#lotics.workflows.<alias>`. The
931
- * server re-verifies the body and echoes the bound `outputs` (declared, else
932
- * DERIVED from `return({ data })`), so the CLI can show the author what shape
933
- * `result.data` will carry. Wraps the tool rather than a bespoke endpoint so
934
- * the file flow stays a convenience over the existing single-author contract.
935
- */
936
- async setAppWorkflow(app_id, alias, body) {
937
- return this.execute("set_app_workflow", {
938
- app_id,
939
- alias,
940
- source: body.source,
941
- ...body.inputs ? { inputs: body.inputs } : {},
942
- ...body.outputs ? { outputs: body.outputs } : {},
943
- ...body.name ? { name: body.name } : {},
944
- ...body.description ? { description: body.description } : {},
945
- ...body.expected_body_sha ? { expected_body_sha: body.expected_body_sha } : {},
946
- // Sent only when the caller asked for it: absent means "refuse a break",
947
- // which is the answer a caller who said nothing gave.
948
- ...body.acknowledge_breaking_api_change ? { acknowledge_breaking_api_change: true } : {},
949
- // Sent only when asked: a server that predates it refuses the parameter
950
- // rather than performing the write.
951
- ...body.verify_only ? { verify_only: true } : {}
952
- });
953
- }
954
- /**
955
- * Bind (create or replace) an app query by alias via the `set_app_query` tool
956
- * — the deploy-free authoring path for `apps.queries`, parallel to
957
- * `setAppWorkflow`. `declaration` is the `{ ast, params? }` from
958
- * `package.json#lotics.queries.<alias>`. The server validates it exactly as a
959
- * deploy validates the manifest. Note: `apps.queries` is manifest-authoritative,
960
- * so the next `lotics app deploy` overwrites this from the manifest.
961
- */
962
- async setAppQuery(app_id, alias, declaration, expected_sha, acknowledge_breaking_api_change) {
963
- return this.execute("set_app_query", {
964
- app_id,
965
- alias,
966
- declaration,
967
- ...expected_sha ? { expected_sha } : {},
968
- ...acknowledge_breaking_api_change ? { acknowledge_breaking_api_change: true } : {}
969
- });
970
- }
971
- /**
972
- * Bind (create or replace) an app agent by alias via the `set_app_agent` tool
973
- * — the deploy-free authoring path for `apps.agents`, parallel to
974
- * `setAppWorkflow`/`setAppQuery`.
975
- *
976
- * `set_app_agent` REPLACES the whole declaration, so this takes the whole
977
- * declaration. `lotics app agent set` is the caller that assembles it (prose
978
- * from `src/agents/<alias>.md`, typed fields from the manifest) precisely so
979
- * no caller has to remember that a partial payload silently drops
980
- * `instructions`, `outputs` and the model pin.
981
- */
982
- async setAppAgent(app_id, alias, patch, acknowledge_breaking_api_change) {
983
- return this.execute("set_app_agent", {
984
- app_id,
985
- alias,
986
- ...patch,
987
- // Sent only when the caller asked for it: absent means "refuse a break",
988
- // which is the answer a caller who said nothing gave.
989
- ...acknowledge_breaking_api_change ? { acknowledge_breaking_api_change: true } : {}
990
- });
991
- }
992
461
  /**
993
- * Fetch one app workflow's faithful source + bound input/output schemas via
994
- * `get_app_workflow`. `source` is the JS-subset body re-rendered from the
995
- * persisted step tree (incl. the `return({ data })` clause, opaque field/option
996
- * keys) — the exact text `lotics app workflow set` would push back. Feeds
997
- * `lotics app pull`, which writes it to `src/workflows/<alias>.ts`.
462
+ * Upload a custom app's build as a new version of the live app — `POST /v1/apps/{id}/versions`.
463
+ * The version carries the app's queries, workflows, agents and capabilities forward unchanged;
464
+ * those are written through their own tools.
998
465
  */
999
- async getAppWorkflow(app_id, alias) {
1000
- return this.execute("get_app_workflow", { app_id, alias });
1001
- }
1002
- /**
1003
- * The app's capability catalog exactly as a chat or MCP caller reads it —
1004
- * every query, workflow and agent alias the caller's scope reaches, with the
1005
- * description each is chosen BY. One read for the whole app, so a check over
1006
- * what those readers see costs one request rather than one per alias.
1007
- */
1008
- async getAppCapabilities(app_id) {
1009
- return this.execute("get_app_capabilities", { app_id });
1010
- }
1011
- /**
1012
- * Fetch the server-generated workspace `.d.ts` + the wrapper envelope that
1013
- * make a `src/workflows/<alias>.ts` body locally typecheckable (GAP-59).
1014
- * The server is the single source of the type model — the CLI never
1015
- * re-implements it. `envelope_prefix`/`envelope_suffix` are the exact
1016
- * `async function __workflow(): …` wrapper the server compiles inside, so the
1017
- * local typecheck mirrors the set-time verdict. Mirrors
1018
- * POST /v1/apps/{app_id}/workflows/{alias}/dts.
1019
- *
1020
- * `declaration` (the manifest's `{ inputs?, outputs? }`) is posted as the body
1021
- * `{ declaration }` ONLY when the alias isn't `set` on the server yet — the
1022
- * server then synthesizes the dts from the declared schemas instead of 400ing
1023
- * "no workflow alias". A registered alias needs no declaration (the server's
1024
- * own bound contract wins), so the field is omitted in that case.
1025
- */
1026
- async getAppWorkflowDts(app_id, alias, declaration) {
1027
- return this.request(
1028
- "POST",
1029
- `/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts`,
1030
- declaration ? { declaration } : void 0
1031
- );
1032
- }
1033
- /**
1034
- * Open a streaming agent run and return the RAW streamed `Response` (the
1035
- * caller reads `res.body`). Unlike `request`, this does not buffer/parse the
1036
- * body — it's the SSE stream the `lotics app dev` harness proxies to the
1037
- * iframe. Mirrors POST /v1/apps/{app_id}/agents/{alias}/runs.
1038
- */
1039
- async appAgentRunStream(app_id, alias, body, signal) {
1040
- const headers = { ...this.buildHeaders(), "Content-Type": "application/json" };
1041
- const res = await fetch(
1042
- `${this.baseUrl}/v1/apps/${encodeURIComponent(app_id)}/agents/${encodeURIComponent(alias)}/runs`,
1043
- { method: "POST", headers, body: JSON.stringify(body), signal }
1044
- );
1045
- if (!res.ok) await this.throwResponseError(res, headers["x-request-id"]);
1046
- return res;
1047
- }
1048
- /**
1049
- * Continue a PARKED (`awaiting_input`) agent run with the user's answer to its
1050
- * pending `ask_user_choice` — returns the RAW streamed continuation `Response`,
1051
- * exactly like `appAgentRunStream`. Mirrors
1052
- * POST /v1/apps/{app_id}/agent-runs/{run_id}/continue.
1053
- */
1054
- async appAgentRunContinueStream(app_id, run_id, body, signal) {
1055
- const headers = { ...this.buildHeaders(), "Content-Type": "application/json" };
1056
- const res = await fetch(
1057
- `${this.baseUrl}/v1/apps/${encodeURIComponent(app_id)}/agent-runs/${encodeURIComponent(run_id)}/continue`,
1058
- { method: "POST", headers, body: JSON.stringify(body), signal }
1059
- );
1060
- if (!res.ok) await this.throwResponseError(res, headers["x-request-id"]);
1061
- return res;
1062
- }
1063
- /**
1064
- * A session's app-agent run history, oldest-first (the run just started is the
1065
- * last, and its exact id is on the stream response's `x-app-agent-run-id`
1066
- * header). Transcript excluded; structured `output`/`input` included. Mirrors
1067
- * GET /v1/apps/{app_id}/agent-runs.
1068
- */
1069
- async listAgentRuns(app_id, session_id) {
1070
- return this.request(
1071
- "GET",
1072
- `/v1/apps/${encodeURIComponent(app_id)}/agent-runs?session_id=${encodeURIComponent(session_id)}`
1073
- );
1074
- }
1075
- /**
1076
- * A single run by id — the poll read a client follows after its stream drops
1077
- * (a parked `awaiting_input` row carries `pending_interactive` so the question
1078
- * survives reconnection). Mirrors GET /v1/apps/{app_id}/agent-runs/{run_id}.
1079
- */
1080
- async getAgentRun(app_id, run_id) {
1081
- return this.request(
1082
- "GET",
1083
- `/v1/apps/${encodeURIComponent(app_id)}/agent-runs/${encodeURIComponent(run_id)}`
1084
- );
1085
- }
1086
- /**
1087
- * Request cancellation of an in-flight (or parked) run. Mirrors
1088
- * POST /v1/apps/{app_id}/agent-runs/{run_id}/cancel.
1089
- */
1090
- async cancelAgentRun(app_id, run_id) {
1091
- return this.request(
1092
- "POST",
1093
- `/v1/apps/${encodeURIComponent(app_id)}/agent-runs/${encodeURIComponent(run_id)}/cancel`
1094
- );
1095
- }
1096
- /**
1097
- * Mint a presigned URL for uploading a file into an app. Mirrors
1098
- * POST /v1/apps/{app_id}/files/upload-url.
1099
- */
1100
- async appRequestFileUpload(app_id, body) {
1101
- return this.request(
1102
- "POST",
1103
- `/v1/apps/${encodeURIComponent(app_id)}/files/upload-url`,
1104
- body
1105
- );
1106
- }
1107
- /**
1108
- * Finalize a presigned upload once the bytes are in storage. Mirrors
1109
- * POST /v1/apps/{app_id}/files/complete.
1110
- */
1111
- async appCompleteFileUpload(app_id, body) {
1112
- return this.request(
1113
- "POST",
1114
- `/v1/apps/${encodeURIComponent(app_id)}/files/complete`,
1115
- body
1116
- );
1117
- }
1118
- /** A file of the app under another name — a new file over the same bytes, for a save to put in the old one's place. */
1119
- async appRenameFile(app_id, file_id, filename) {
1120
- return this.request(
1121
- "POST",
1122
- `/v1/apps/${encodeURIComponent(app_id)}/files/${encodeURIComponent(file_id)}/rename`,
1123
- { filename }
1124
- );
1125
- }
1126
- // App-scoped record comments — the `lotics app dev` loop forwards the iframe's
1127
- // `comments.*` ops to these (production routes them through the iframe host).
1128
- // App authority + tenant floor are enforced server-side; these are thin.
1129
- async appGetRecordComments(app_id, record_id) {
1130
- return this.request(
1131
- "GET",
1132
- `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments`
1133
- );
1134
- }
1135
- async appCreateRecordComment(app_id, record_id, body) {
1136
- return this.request(
1137
- "POST",
1138
- `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments`,
1139
- body
1140
- );
1141
- }
1142
- async appUpdateRecordComment(app_id, record_id, comment_id, body) {
1143
- return this.request(
1144
- "PATCH",
1145
- `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments/${encodeURIComponent(comment_id)}`,
1146
- body
1147
- );
1148
- }
1149
- async appDeleteRecordComment(app_id, record_id, comment_id) {
1150
- await this.request(
1151
- "DELETE",
1152
- `/v1/apps/${encodeURIComponent(app_id)}/records/${encodeURIComponent(record_id)}/comments/${encodeURIComponent(comment_id)}`
1153
- );
1154
- }
1155
- async appGetTableCommentCounts(app_id, table_id) {
1156
- return this.request(
1157
- "GET",
1158
- `/v1/apps/${encodeURIComponent(app_id)}/tables/${encodeURIComponent(table_id)}/comment-counts`
1159
- );
1160
- }
1161
466
  async deployAppVersion(args) {
1162
467
  const formData = new FormData();
1163
468
  formData.append(
@@ -1170,22 +475,8 @@ var LoticsClient = class {
1170
475
  new Blob([new Uint8Array(args.dist_archive)], { type: "application/gzip" }),
1171
476
  "dist.tar.gz"
1172
477
  );
1173
- if (args.prev_version_id) {
1174
- formData.append("prev_version_id", args.prev_version_id);
1175
- }
1176
- if (args.message) {
1177
- formData.append("message", args.message);
1178
- }
1179
- formData.append("queries", JSON.stringify(args.queries ?? {}));
1180
- if (args.capabilities !== void 0) {
1181
- formData.append("capabilities", JSON.stringify(args.capabilities));
1182
- }
1183
- formData.append("workflow_aliases", JSON.stringify(args.workflow_aliases ?? []));
1184
- formData.append("agent_aliases", JSON.stringify(args.agent_aliases ?? []));
1185
- formData.append("query_aliases", JSON.stringify(args.query_aliases ?? []));
1186
- if (args.acknowledge_breaking_api_change) {
1187
- formData.append("acknowledge_breaking_api_change", "true");
1188
- }
478
+ if (args.prev_version_id) formData.append("prev_version_id", args.prev_version_id);
479
+ if (args.message) formData.append("message", args.message);
1189
480
  const url = `${this.baseUrl}/v1/apps/${encodeURIComponent(args.app_id)}/versions`;
1190
481
  const headers = this.buildHeaders();
1191
482
  const response = await fetch(url, { method: "POST", headers, body: formData });
@@ -1405,10 +696,6 @@ export {
1405
696
  LoticsClient,
1406
697
  LoticsRequestError,
1407
698
  WEB_APP_URL,
1408
- fetchOfficialStarters,
1409
- getOfficialStarter,
1410
- getPublicJson,
1411
699
  pollCliLogin,
1412
- readOfficialStarter,
1413
700
  startCliLogin
1414
701
  };