@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.
- package/AGENTS.md +30 -50
- package/README.md +49 -205
- package/dist/src/cli.js +31069 -84675
- package/dist/src/cli.js.LEGAL.txt +0 -16
- package/dist/src/client.d.ts +18 -1231
- package/dist/src/client.js +14 -727
- package/dist/src/invocation.d.ts +1 -1
- package/docs/building_an_app.md +88 -409
- package/docs/cli_reference.md +9 -42
- package/docs/knowledge_docs.md +0 -8
- package/docs/migration.md +64 -97
- package/package.json +1 -1
- package/dist/probe_page.js +0 -2381
- package/dist/render_page.js +0 -67559
- package/dist/render_page.js.LEGAL.txt +0 -14
package/dist/src/client.js
CHANGED
|
@@ -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.
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
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
|
|
865
|
-
//
|
|
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
|
-
*
|
|
994
|
-
*
|
|
995
|
-
*
|
|
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
|
-
|
|
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
|
};
|