@lotics/cli 0.187.1 → 0.189.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 +6 -0
- package/README.md +53 -2
- package/dist/src/cli.js +3462 -614
- package/dist/src/client.d.ts +298 -10
- package/dist/src/client.js +118 -14
- package/docs/building_an_app.md +1 -1
- package/docs/cli_reference.md +11 -6
- package/package.json +1 -1
package/dist/src/client.d.ts
CHANGED
|
@@ -78,6 +78,34 @@ export interface ToolInfo {
|
|
|
78
78
|
description: string;
|
|
79
79
|
input_schema: unknown;
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* What `POST /v1/apps/{app_id}/upgrade` reports it did — an offer applied in
|
|
83
|
+
* part, and every part it declined to touch named back.
|
|
84
|
+
*
|
|
85
|
+
* Both `skipped_*` lists are aliases the owner has EDITED since the copy, so
|
|
86
|
+
* the new version's body was left alone; `dropped_fields` are columns the new
|
|
87
|
+
* version stopped declaring and that still hold the owner's data. Neither is an
|
|
88
|
+
* error, and both are the reason this result is printed rather than counted.
|
|
89
|
+
*/
|
|
90
|
+
export interface AppUpgradeResult {
|
|
91
|
+
app_id: string;
|
|
92
|
+
from_version: number;
|
|
93
|
+
to_version: number;
|
|
94
|
+
deployed: {
|
|
95
|
+
version_id: string;
|
|
96
|
+
version_number: number;
|
|
97
|
+
};
|
|
98
|
+
created_fields: Array<{
|
|
99
|
+
entity: string;
|
|
100
|
+
field: string;
|
|
101
|
+
}>;
|
|
102
|
+
skipped_workflows: string[];
|
|
103
|
+
skipped_agents: string[];
|
|
104
|
+
dropped_fields: Array<{
|
|
105
|
+
entity: string;
|
|
106
|
+
field: string;
|
|
107
|
+
}>;
|
|
108
|
+
}
|
|
81
109
|
/**
|
|
82
110
|
* A single knowledge doc with its HYDRATED body — the shape of
|
|
83
111
|
* `GET /v1/knowledge_docs/{id}`, and the one content-read path a non-sandbox
|
|
@@ -114,13 +142,15 @@ export interface FileUploadResult {
|
|
|
114
142
|
error: string;
|
|
115
143
|
}>;
|
|
116
144
|
}
|
|
117
|
-
/** One finding from the extract behind a
|
|
145
|
+
/** One finding from the extract behind a package publish. */
|
|
118
146
|
export interface ExtractFinding {
|
|
119
147
|
severity: "error" | "warning" | "info";
|
|
120
148
|
area: string;
|
|
121
149
|
message: string;
|
|
122
150
|
}
|
|
123
151
|
export interface StarterPublishRequest {
|
|
152
|
+
/** The package to release into. Omitted, the origin apps resolve it. */
|
|
153
|
+
starter_id?: string;
|
|
124
154
|
/** The origin apps that ship, in alias-minting order. */
|
|
125
155
|
app_ids: string[];
|
|
126
156
|
/** Live knowledge doc ids to bundle. Omitted keeps the previous version's set; [] drops them all. */
|
|
@@ -144,6 +174,11 @@ export interface StarterPublishPreview {
|
|
|
144
174
|
starter_id: string | null;
|
|
145
175
|
starter_name: string;
|
|
146
176
|
version: number;
|
|
177
|
+
/** The entities it would carry. */
|
|
178
|
+
entities: Array<{
|
|
179
|
+
alias: string;
|
|
180
|
+
label: string;
|
|
181
|
+
}>;
|
|
147
182
|
apps: Array<{
|
|
148
183
|
alias: string;
|
|
149
184
|
app_id: string;
|
|
@@ -199,6 +234,7 @@ export interface StarterVersionContract {
|
|
|
199
234
|
entities: Array<{
|
|
200
235
|
alias: string;
|
|
201
236
|
label: string;
|
|
237
|
+
description?: string;
|
|
202
238
|
fields: Array<{
|
|
203
239
|
alias: string;
|
|
204
240
|
label: string;
|
|
@@ -235,21 +271,80 @@ export interface KnowledgeWarnings {
|
|
|
235
271
|
/** `knowledge_expects` doc names with no matching workspace doc. */
|
|
236
272
|
missing_expected_docs: string[];
|
|
237
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* A workspace MODEL on the wire — a package contract with no apps, plus the
|
|
276
|
+
* first rows that travel beside it keyed by entity alias.
|
|
277
|
+
*
|
|
278
|
+
* The contract's authoritative shape is `packageContractSchema` in
|
|
279
|
+
* `@lotics/shared`, a specifier a published `.d.ts` cannot resolve — so the wire
|
|
280
|
+
* body is typed structurally here, the same trade `StarterVersionContract`
|
|
281
|
+
* makes. The CLI passes a value it has already parsed through that schema.
|
|
282
|
+
*/
|
|
283
|
+
export interface ScaffoldWorkspaceRequest {
|
|
284
|
+
contract: Record<string, unknown>;
|
|
285
|
+
rows?: Record<string, Array<{
|
|
286
|
+
ref: string;
|
|
287
|
+
fields: Record<string, unknown>;
|
|
288
|
+
}>>;
|
|
289
|
+
/** Bind an entity whose label already names a table here. Absent, a colliding label is refused. */
|
|
290
|
+
adopt?: boolean;
|
|
291
|
+
}
|
|
292
|
+
export interface ScaffoldWorkspaceResult {
|
|
293
|
+
/** Every entity the model declares, in contract order. */
|
|
294
|
+
entities: Array<{
|
|
295
|
+
alias: string;
|
|
296
|
+
table_id: string;
|
|
297
|
+
/** False when a table of that label already existed and was adopted. */
|
|
298
|
+
created: boolean;
|
|
299
|
+
}>;
|
|
300
|
+
roles: Array<{
|
|
301
|
+
alias: string;
|
|
302
|
+
group_id: string;
|
|
303
|
+
}>;
|
|
304
|
+
/** Ids of the rows written, per entity alias — the handle for deleting them again. */
|
|
305
|
+
record_ids: Record<string, string[]>;
|
|
306
|
+
/** Rows were sent and none were written, because the run adopted a table. */
|
|
307
|
+
rows_skipped: boolean;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* A request the API refused. The message carries the status and the server's
|
|
311
|
+
* sentence, which is what reaches a person; `status` and `body` are for the
|
|
312
|
+
* few callers that branch on a refusal — a 409 whose `body.reason` says the
|
|
313
|
+
* run had nothing to do, rather than that it failed.
|
|
314
|
+
*/
|
|
315
|
+
export declare class LoticsRequestError extends Error {
|
|
316
|
+
readonly status: number;
|
|
317
|
+
readonly body: Record<string, unknown>;
|
|
318
|
+
constructor(message: string, status: number, body: Record<string, unknown>);
|
|
319
|
+
}
|
|
238
320
|
export declare const API_BASE_URL: string;
|
|
239
321
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
322
|
+
* The website: where a person goes when the CLI cannot finish the job, and where
|
|
323
|
+
* the presets are SERVED FROM. Held beside the API base so the pair is read
|
|
324
|
+
* together, named once rather than inlined at each message that points
|
|
325
|
+
* somewhere, and overridable for the same reason the API base is — a run against
|
|
326
|
+
* a local site must read that site's presets, not production's.
|
|
244
327
|
*/
|
|
245
|
-
export declare const WEB_APP_URL
|
|
246
|
-
/** One
|
|
328
|
+
export declare const WEB_APP_URL: string;
|
|
329
|
+
/** One package on the public shelf — what a chooser decides with, nothing else. */
|
|
247
330
|
export interface OfficialStarter {
|
|
248
331
|
id: string;
|
|
249
332
|
name: string;
|
|
250
333
|
description: string | null;
|
|
251
334
|
icon: string | null;
|
|
252
335
|
latest_version: number;
|
|
336
|
+
/**
|
|
337
|
+
* The apps the copy creates. A name and a sentence leave the chooser
|
|
338
|
+
* guessing; these are what someone's own words get matched against when
|
|
339
|
+
* deciding between a package and a model.
|
|
340
|
+
*/
|
|
341
|
+
apps: Array<{
|
|
342
|
+
alias: string;
|
|
343
|
+
name: string;
|
|
344
|
+
description?: string;
|
|
345
|
+
}>;
|
|
346
|
+
/** How many tables the copy creates — the other half of "what is in this?". */
|
|
347
|
+
table_count: number;
|
|
253
348
|
}
|
|
254
349
|
/**
|
|
255
350
|
* The starters Lotics publishes, fetched with no credential.
|
|
@@ -260,6 +355,152 @@ export interface OfficialStarter {
|
|
|
260
355
|
* exists, so needing one to ask means signing up to find out the answer was no.
|
|
261
356
|
*/
|
|
262
357
|
export declare function fetchOfficialStarters(): Promise<OfficialStarter[]>;
|
|
358
|
+
/**
|
|
359
|
+
* One keyless GET of JSON, bounded — the shape every read that predates an
|
|
360
|
+
* account takes.
|
|
361
|
+
*
|
|
362
|
+
* Bounded because these are the FIRST commands a new user runs and a hang with
|
|
363
|
+
* nothing on screen is the worst version of the failure; `config.ts` bounds its
|
|
364
|
+
* own unattended fetch for the same reason, and `docs/network_reliability.md` is
|
|
365
|
+
* a log of connections from our users' networks degrading rather than refusing,
|
|
366
|
+
* which is the shape that hangs.
|
|
367
|
+
*
|
|
368
|
+
* A transport failure THROWS and a status is RETURNED, because only the first is
|
|
369
|
+
* "check your connection": a status means the server replied and the network is
|
|
370
|
+
* demonstrably fine, and a caller that answered both the same way would tell an
|
|
371
|
+
* owner their package is missing every time the link drops.
|
|
372
|
+
*/
|
|
373
|
+
export declare function getPublicJson(url: string, what: string): Promise<{
|
|
374
|
+
ok: true;
|
|
375
|
+
body: unknown;
|
|
376
|
+
} | {
|
|
377
|
+
ok: false;
|
|
378
|
+
status: number;
|
|
379
|
+
}>;
|
|
380
|
+
/**
|
|
381
|
+
* One official package READ WHOLE, with no credential — or the status that says
|
|
382
|
+
* the shelf does not serve it.
|
|
383
|
+
*
|
|
384
|
+
* The shelf row says what a package is; this says what is in it — entities with
|
|
385
|
+
* their fields, the roles, each template's metadata and each app's name. Never a
|
|
386
|
+
* workflow body: this is a read for deciding and for writing a model from, not a
|
|
387
|
+
* copy.
|
|
388
|
+
*
|
|
389
|
+
* Unauthenticated for the same reason the shelf is: a preset is READ rather than
|
|
390
|
+
* instantiated, and the whole point of reading one is that it happens before an
|
|
391
|
+
* account exists. Only official packages are served — the `is_official` filter,
|
|
392
|
+
* exactly as on the shelf — so a caller's OWN package answers a status here and
|
|
393
|
+
* is read through the authenticated pair instead. A transport failure still
|
|
394
|
+
* throws: "unreachable" is not "not on the shelf", and answering both the same
|
|
395
|
+
* way would tell an owner their package is missing every time the link drops.
|
|
396
|
+
*/
|
|
397
|
+
export declare function readOfficialStarter(starter_id: string): Promise<{
|
|
398
|
+
ok: true;
|
|
399
|
+
package: OfficialStarterRead;
|
|
400
|
+
} | {
|
|
401
|
+
ok: false;
|
|
402
|
+
status: number;
|
|
403
|
+
}>;
|
|
404
|
+
/** The same read for a caller with nothing to fall back to — a refusal is the end. */
|
|
405
|
+
export declare function getOfficialStarter(starter_id: string): Promise<OfficialStarterRead>;
|
|
406
|
+
/** A column as the public read shows it: enough to write a model field from. */
|
|
407
|
+
export interface OfficialStarterField {
|
|
408
|
+
alias: string;
|
|
409
|
+
label: string;
|
|
410
|
+
type: string;
|
|
411
|
+
}
|
|
412
|
+
/** A table as the public read shows it: its columns. */
|
|
413
|
+
export interface OfficialStarterEntity {
|
|
414
|
+
alias: string;
|
|
415
|
+
label: string;
|
|
416
|
+
description?: string;
|
|
417
|
+
fields: OfficialStarterField[];
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* What the public read of one package carries.
|
|
421
|
+
*
|
|
422
|
+
* Every namespace is declared structurally, NARROWED to what a reader turning
|
|
423
|
+
* this into a model needs — fields carry their type, apps only their name — the
|
|
424
|
+
* same trade `StarterVersionContract` makes: a published `.d.ts` cannot resolve
|
|
425
|
+
* `@lotics/shared`. `packageContractSchema` is the definition, and the CLI
|
|
426
|
+
* parses a preset's block through `modelPresetSchema` before resolving a `from`
|
|
427
|
+
* file against it.
|
|
428
|
+
*/
|
|
429
|
+
export interface OfficialStarterRead {
|
|
430
|
+
id: string;
|
|
431
|
+
name: string;
|
|
432
|
+
description: string | null;
|
|
433
|
+
latest_version: number;
|
|
434
|
+
contract: {
|
|
435
|
+
entities: OfficialStarterEntity[];
|
|
436
|
+
roles: Array<{
|
|
437
|
+
alias: string;
|
|
438
|
+
label: string;
|
|
439
|
+
}>;
|
|
440
|
+
templates: Array<{
|
|
441
|
+
alias: string;
|
|
442
|
+
label: string;
|
|
443
|
+
type: string;
|
|
444
|
+
}>;
|
|
445
|
+
/** The apps a copy creates. */
|
|
446
|
+
apps: Array<{
|
|
447
|
+
alias: string;
|
|
448
|
+
name: string;
|
|
449
|
+
description?: string;
|
|
450
|
+
}>;
|
|
451
|
+
/** Sample rows per entity alias — how many, never the rows. */
|
|
452
|
+
fixtures: Record<string, {
|
|
453
|
+
row_count: number;
|
|
454
|
+
}>;
|
|
455
|
+
knowledge: Record<string, {
|
|
456
|
+
name: string;
|
|
457
|
+
description: string;
|
|
458
|
+
}>;
|
|
459
|
+
knowledge_expects: string[];
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* What a terminal holds while it waits to be let in: the handle the server knows
|
|
464
|
+
* the request by, the `secret` that proves this is the same terminal that asked,
|
|
465
|
+
* and the `code` the person matches against the confirm page before pressing it.
|
|
466
|
+
*
|
|
467
|
+
* `secret` is a credential and is never printed — the `code` is what a person
|
|
468
|
+
* reads, and it proves nothing on its own.
|
|
469
|
+
*/
|
|
470
|
+
export interface CliLoginRequest {
|
|
471
|
+
request_id: string;
|
|
472
|
+
secret: string;
|
|
473
|
+
code: string;
|
|
474
|
+
expires_at: string;
|
|
475
|
+
}
|
|
476
|
+
/** Where a login request stands. `approved` carries the key, and only once. */
|
|
477
|
+
export type CliLoginState = {
|
|
478
|
+
status: "pending";
|
|
479
|
+
} | {
|
|
480
|
+
status: "expired";
|
|
481
|
+
} | {
|
|
482
|
+
status: "claimed";
|
|
483
|
+
} | {
|
|
484
|
+
status: "approved";
|
|
485
|
+
api_key: string;
|
|
486
|
+
organization_id: string;
|
|
487
|
+
organization_name: string;
|
|
488
|
+
workspace_id: string;
|
|
489
|
+
};
|
|
490
|
+
/**
|
|
491
|
+
* Ask Lotics to mail a sign-in link, and read back whether it was confirmed.
|
|
492
|
+
*
|
|
493
|
+
* Plain functions rather than `LoticsClient` methods for the same reason
|
|
494
|
+
* `fetchOfficialStarters` is one: there is no key to build a client around, and
|
|
495
|
+
* that is the whole point — this is the pair a terminal holding NO credential
|
|
496
|
+
* uses to obtain one. Sending an `Authorization` header would make the endpoint
|
|
497
|
+
* answerable only to callers who no longer need it.
|
|
498
|
+
*
|
|
499
|
+
* The same answer whether or not the address has an account: it would
|
|
500
|
+
* otherwise tell any stranger which emails are registered here.
|
|
501
|
+
*/
|
|
502
|
+
export declare function startCliLogin(email: string): Promise<CliLoginRequest>;
|
|
503
|
+
export declare function pollCliLogin(request_id: string, secret: string): Promise<CliLoginState>;
|
|
263
504
|
export declare class LoticsClient {
|
|
264
505
|
private apiKey;
|
|
265
506
|
private workspaceId;
|
|
@@ -322,6 +563,16 @@ export declare class LoticsClient {
|
|
|
322
563
|
name: string;
|
|
323
564
|
timezone?: string;
|
|
324
565
|
}): Promise<WorkspaceInfo>;
|
|
566
|
+
/**
|
|
567
|
+
* Apply a workspace MODEL to the current workspace — the from-scratch half of
|
|
568
|
+
* the starter pipeline, on a contract nobody published.
|
|
569
|
+
*
|
|
570
|
+
* Additive: with `adopt`, an entity whose label already names a table here
|
|
571
|
+
* binds to it and gains the fields, options and views it is missing; without
|
|
572
|
+
* `adopt`, that label is refused. Nothing is ever modified or deleted, and
|
|
573
|
+
* rows land only where every bound table is empty. Admin-only.
|
|
574
|
+
*/
|
|
575
|
+
scaffoldWorkspace(body: ScaffoldWorkspaceRequest): Promise<ScaffoldWorkspaceResult>;
|
|
325
576
|
/** Renames (and re-settings) the CURRENT workspace — the endpoint reads the
|
|
326
577
|
* target from the request's workspace, never a path id. */
|
|
327
578
|
updateWorkspace(body: {
|
|
@@ -455,7 +706,7 @@ export declare class LoticsClient {
|
|
|
455
706
|
}>;
|
|
456
707
|
/**
|
|
457
708
|
* Take a starter off the shelf, or `undo` to put it back (backs `opctl
|
|
458
|
-
*
|
|
709
|
+
* library unpublish`). It hides from non-owning orgs and can no longer be
|
|
459
710
|
* copied; copies already made are unaffected — they never linked back.
|
|
460
711
|
* Owner-org admin-only.
|
|
461
712
|
*/
|
|
@@ -499,6 +750,14 @@ export declare class LoticsClient {
|
|
|
499
750
|
latest_version: number;
|
|
500
751
|
is_official: boolean;
|
|
501
752
|
owned_by_caller: boolean;
|
|
753
|
+
/** The apps a copy creates — the same contents the public shelf names. */
|
|
754
|
+
apps: Array<{
|
|
755
|
+
alias: string;
|
|
756
|
+
name: string;
|
|
757
|
+
description?: string;
|
|
758
|
+
}>;
|
|
759
|
+
/** How many tables the copy creates. */
|
|
760
|
+
table_count: number;
|
|
502
761
|
}>>;
|
|
503
762
|
/**
|
|
504
763
|
* Copy a starter into the current workspace.
|
|
@@ -513,6 +772,20 @@ export declare class LoticsClient {
|
|
|
513
772
|
version?: number;
|
|
514
773
|
no_sample_data?: boolean;
|
|
515
774
|
adopt?: boolean;
|
|
775
|
+
/**
|
|
776
|
+
* The labels THIS workspace calls the package's entities and fields.
|
|
777
|
+
*
|
|
778
|
+
* Scaffold adopts by label, so binding renames the contract's labels
|
|
779
|
+
* before it runs and the copy lands on the tables the caller already has.
|
|
780
|
+
* A bound field whose type differs from the one the contract declares is
|
|
781
|
+
* refused — the type is the contract, only the naming moves. Structural
|
|
782
|
+
* for the same reason the rest of this file's wire shapes are;
|
|
783
|
+
* `packageBindSchema` in `@lotics/shared` is the definition.
|
|
784
|
+
*/
|
|
785
|
+
bind?: Record<string, {
|
|
786
|
+
label?: string;
|
|
787
|
+
fields?: Record<string, string>;
|
|
788
|
+
}>;
|
|
516
789
|
}): Promise<{
|
|
517
790
|
/** Each app's deploy, in contract order. `error` set and `deployed` null when one did not land. */
|
|
518
791
|
apps: Array<{
|
|
@@ -541,6 +814,21 @@ export declare class LoticsClient {
|
|
|
541
814
|
starter_id: string;
|
|
542
815
|
latest_version: number;
|
|
543
816
|
}>;
|
|
817
|
+
/**
|
|
818
|
+
* Apply the latest version of the package this app was copied from.
|
|
819
|
+
*
|
|
820
|
+
* The other provenance direction from `getAppOriginStarter`, and the only one
|
|
821
|
+
* that writes: that one answers which package this app PUBLISHED, this one
|
|
822
|
+
* reads what a copy recorded and re-derives the app against a later release.
|
|
823
|
+
*
|
|
824
|
+
* The offer is partial by design and the result says how partial: schema is
|
|
825
|
+
* additive (`created_fields`, and a `dropped_fields` the new version stopped
|
|
826
|
+
* declaring is left standing), and an artifact the owner has edited is kept
|
|
827
|
+
* and named (`skipped_workflows` / `skipped_agents`). An app with no
|
|
828
|
+
* provenance is a 400, and one already on the latest release a 409 — both
|
|
829
|
+
* refusals, both before any write. Admin-only.
|
|
830
|
+
*/
|
|
831
|
+
upgradeApp(app_id: string): Promise<AppUpgradeResult>;
|
|
544
832
|
/**
|
|
545
833
|
* Capture live records from this workspace as a starter's sample data.
|
|
546
834
|
*
|
|
@@ -587,7 +875,7 @@ export declare class LoticsClient {
|
|
|
587
875
|
created_at: string;
|
|
588
876
|
updated_at: string;
|
|
589
877
|
}>;
|
|
590
|
-
/** Version history newest-first (no contract payloads) — backs `opctl
|
|
878
|
+
/** Version history newest-first (no contract payloads) — backs `opctl library show`. Admin-only. */
|
|
591
879
|
listStarterVersions(starter_id: string): Promise<{
|
|
592
880
|
versions: Array<{
|
|
593
881
|
version: number;
|
|
@@ -622,7 +910,7 @@ export declare class LoticsClient {
|
|
|
622
910
|
}>>;
|
|
623
911
|
/**
|
|
624
912
|
* Preview publishing a set of this workspace's apps as one starter version —
|
|
625
|
-
* the GET behind `opctl
|
|
913
|
+
* the GET behind `opctl library publish` (no `--yes`). The server runs the
|
|
626
914
|
* same extraction the publish runs and reports which starter it would
|
|
627
915
|
* release into (null: it would mint one), the next version, the aliases a
|
|
628
916
|
* first publish can still rename, the diff against the current version, the
|
package/dist/src/client.js
CHANGED
|
@@ -139,27 +139,94 @@ function getMimeType(filename) {
|
|
|
139
139
|
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
140
140
|
}
|
|
141
141
|
var MULTIPART_THRESHOLD_BYTES = 8 * 1024 * 1024;
|
|
142
|
+
var LoticsRequestError = class extends Error {
|
|
143
|
+
constructor(message, status, body) {
|
|
144
|
+
super(message);
|
|
145
|
+
this.status = status;
|
|
146
|
+
this.body = body;
|
|
147
|
+
this.name = "LoticsRequestError";
|
|
148
|
+
}
|
|
149
|
+
status;
|
|
150
|
+
body;
|
|
151
|
+
};
|
|
142
152
|
var API_BASE_URL = process.env.LOTICS_API_URL ?? "https://api.lotics.ai";
|
|
143
|
-
var WEB_APP_URL = "https://lotics.ai";
|
|
153
|
+
var WEB_APP_URL = process.env.LOTICS_WEB_URL ?? "https://lotics.ai";
|
|
144
154
|
async function fetchOfficialStarters() {
|
|
155
|
+
const read = await getPublicJson(`${API_BASE_URL}/v1/starters/official`, "list the packages");
|
|
156
|
+
if (!read.ok) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Lotics answered ${read.status} listing the packages. If this keeps happening, browse ${WEB_APP_URL}/docs/cli.`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return read.body;
|
|
162
|
+
}
|
|
163
|
+
async function getPublicJson(url, what) {
|
|
164
|
+
let response;
|
|
165
|
+
try {
|
|
166
|
+
response = await fetch(url, { signal: AbortSignal.timeout(PUBLIC_FETCH_TIMEOUT_MS) });
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Could not reach Lotics to ${what} (${error instanceof Error ? error.message : String(error)}). Check your connection, or browse ${WEB_APP_URL}/docs/cli.`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (!response.ok) return { ok: false, status: response.status };
|
|
173
|
+
return { ok: true, body: await response.json() };
|
|
174
|
+
}
|
|
175
|
+
async function readOfficialStarter(starter_id) {
|
|
176
|
+
const read = await getPublicJson(
|
|
177
|
+
`${API_BASE_URL}/v1/starters/official/${encodeURIComponent(starter_id)}`,
|
|
178
|
+
`read ${starter_id}`
|
|
179
|
+
);
|
|
180
|
+
if (!read.ok) return read;
|
|
181
|
+
return { ok: true, package: read.body };
|
|
182
|
+
}
|
|
183
|
+
async function getOfficialStarter(starter_id) {
|
|
184
|
+
const read = await readOfficialStarter(starter_id);
|
|
185
|
+
if (!read.ok) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Lotics answered ${read.status} reading ${starter_id}. Only packages Lotics publishes can be read without an account.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return read.package;
|
|
191
|
+
}
|
|
192
|
+
var PUBLIC_FETCH_TIMEOUT_MS = 1e4;
|
|
193
|
+
async function startCliLogin(email) {
|
|
145
194
|
let response;
|
|
146
195
|
try {
|
|
147
|
-
response = await fetch(`${API_BASE_URL}/v1/
|
|
148
|
-
|
|
196
|
+
response = await fetch(`${API_BASE_URL}/v1/cli/login_requests`, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: { "Content-Type": "application/json" },
|
|
199
|
+
body: JSON.stringify({ email }),
|
|
200
|
+
signal: AbortSignal.timeout(PUBLIC_FETCH_TIMEOUT_MS)
|
|
149
201
|
});
|
|
150
202
|
} catch (error) {
|
|
151
203
|
throw new Error(
|
|
152
|
-
`Could not reach Lotics to
|
|
204
|
+
`Could not reach Lotics to send a sign-in link (${error instanceof Error ? error.message : String(error)}). Check your connection, or sign in at ${WEB_APP_URL}.`
|
|
153
205
|
);
|
|
154
206
|
}
|
|
155
207
|
if (!response.ok) {
|
|
208
|
+
throw new Error(`Lotics answered ${response.status} asking for a sign-in link.`);
|
|
209
|
+
}
|
|
210
|
+
return await response.json();
|
|
211
|
+
}
|
|
212
|
+
async function pollCliLogin(request_id, secret) {
|
|
213
|
+
const url = `${API_BASE_URL}/v1/cli/login_requests/${encodeURIComponent(request_id)}`;
|
|
214
|
+
let response;
|
|
215
|
+
try {
|
|
216
|
+
response = await fetch(url, {
|
|
217
|
+
headers: { "x-lotics-login-secret": secret },
|
|
218
|
+
signal: AbortSignal.timeout(PUBLIC_FETCH_TIMEOUT_MS)
|
|
219
|
+
});
|
|
220
|
+
} catch (error) {
|
|
156
221
|
throw new Error(
|
|
157
|
-
`Lotics
|
|
222
|
+
`Could not reach Lotics while waiting for the sign-in (${error instanceof Error ? error.message : String(error)}). Run "lotics auth login" again once the connection is back.`
|
|
158
223
|
);
|
|
159
224
|
}
|
|
225
|
+
if (!response.ok) {
|
|
226
|
+
throw new Error(`Lotics answered ${response.status} waiting for the sign-in.`);
|
|
227
|
+
}
|
|
160
228
|
return await response.json();
|
|
161
229
|
}
|
|
162
|
-
var SHELF_FETCH_TIMEOUT_MS = 1e4;
|
|
163
230
|
function newRequestId() {
|
|
164
231
|
return crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
165
232
|
}
|
|
@@ -192,17 +259,18 @@ var LoticsClient = class {
|
|
|
192
259
|
*/
|
|
193
260
|
async throwResponseError(response, requestId) {
|
|
194
261
|
const text = await response.text();
|
|
195
|
-
let
|
|
262
|
+
let body = {};
|
|
196
263
|
try {
|
|
197
264
|
const json = JSON.parse(text);
|
|
198
|
-
|
|
265
|
+
if (typeof json === "object" && json !== null && !Array.isArray(json)) body = { ...json };
|
|
199
266
|
} catch {
|
|
200
|
-
|
|
267
|
+
body = {};
|
|
201
268
|
}
|
|
269
|
+
const message = typeof body.message === "string" ? body.message : text;
|
|
202
270
|
const trace = requestId === void 0 ? "" : `
|
|
203
271
|
|
|
204
272
|
Request id: ${requestId} \u2014 quote this to Lotics support.`;
|
|
205
|
-
throw new
|
|
273
|
+
throw new LoticsRequestError(`${response.status}: ${message}${trace}`, response.status, body);
|
|
206
274
|
}
|
|
207
275
|
/**
|
|
208
276
|
* The backend's `log()` middleware registers `user-agent`,
|
|
@@ -276,6 +344,18 @@ var LoticsClient = class {
|
|
|
276
344
|
async createWorkspace(body) {
|
|
277
345
|
return this.request("POST", "/v1/workspaces", body);
|
|
278
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Apply a workspace MODEL to the current workspace — the from-scratch half of
|
|
349
|
+
* the starter pipeline, on a contract nobody published.
|
|
350
|
+
*
|
|
351
|
+
* Additive: with `adopt`, an entity whose label already names a table here
|
|
352
|
+
* binds to it and gains the fields, options and views it is missing; without
|
|
353
|
+
* `adopt`, that label is refused. Nothing is ever modified or deleted, and
|
|
354
|
+
* rows land only where every bound table is empty. Admin-only.
|
|
355
|
+
*/
|
|
356
|
+
async scaffoldWorkspace(body) {
|
|
357
|
+
return this.request("POST", "/v1/workspaces/scaffold", body);
|
|
358
|
+
}
|
|
279
359
|
/** Renames (and re-settings) the CURRENT workspace — the endpoint reads the
|
|
280
360
|
* target from the request's workspace, never a path id. */
|
|
281
361
|
async updateWorkspace(body) {
|
|
@@ -355,7 +435,7 @@ var LoticsClient = class {
|
|
|
355
435
|
}
|
|
356
436
|
/**
|
|
357
437
|
* Take a starter off the shelf, or `undo` to put it back (backs `opctl
|
|
358
|
-
*
|
|
438
|
+
* library unpublish`). It hides from non-owning orgs and can no longer be
|
|
359
439
|
* copied; copies already made are unaffected — they never linked back.
|
|
360
440
|
* Owner-org admin-only.
|
|
361
441
|
*/
|
|
@@ -408,6 +488,23 @@ var LoticsClient = class {
|
|
|
408
488
|
async getAppOriginStarter(app_id) {
|
|
409
489
|
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/origin-starter`);
|
|
410
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* Apply the latest version of the package this app was copied from.
|
|
493
|
+
*
|
|
494
|
+
* The other provenance direction from `getAppOriginStarter`, and the only one
|
|
495
|
+
* that writes: that one answers which package this app PUBLISHED, this one
|
|
496
|
+
* reads what a copy recorded and re-derives the app against a later release.
|
|
497
|
+
*
|
|
498
|
+
* The offer is partial by design and the result says how partial: schema is
|
|
499
|
+
* additive (`created_fields`, and a `dropped_fields` the new version stopped
|
|
500
|
+
* declaring is left standing), and an artifact the owner has edited is kept
|
|
501
|
+
* and named (`skipped_workflows` / `skipped_agents`). An app with no
|
|
502
|
+
* provenance is a 400, and one already on the latest release a 409 — both
|
|
503
|
+
* refusals, both before any write. Admin-only.
|
|
504
|
+
*/
|
|
505
|
+
async upgradeApp(app_id) {
|
|
506
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/upgrade`, {});
|
|
507
|
+
}
|
|
411
508
|
/**
|
|
412
509
|
* Capture live records from this workspace as a starter's sample data.
|
|
413
510
|
*
|
|
@@ -436,7 +533,7 @@ var LoticsClient = class {
|
|
|
436
533
|
async getStarter(starter_id) {
|
|
437
534
|
return this.request("GET", `/v1/starters/${encodeURIComponent(starter_id)}`);
|
|
438
535
|
}
|
|
439
|
-
/** Version history newest-first (no contract payloads) — backs `opctl
|
|
536
|
+
/** Version history newest-first (no contract payloads) — backs `opctl library show`. Admin-only. */
|
|
440
537
|
async listStarterVersions(starter_id) {
|
|
441
538
|
return this.request("GET", `/v1/starters/${encodeURIComponent(starter_id)}/versions`);
|
|
442
539
|
}
|
|
@@ -464,7 +561,7 @@ var LoticsClient = class {
|
|
|
464
561
|
// --- Starter publishing (the authoring verbs; copying is `instantiateStarter`) ---
|
|
465
562
|
/**
|
|
466
563
|
* Preview publishing a set of this workspace's apps as one starter version —
|
|
467
|
-
* the GET behind `opctl
|
|
564
|
+
* the GET behind `opctl library publish` (no `--yes`). The server runs the
|
|
468
565
|
* same extraction the publish runs and reports which starter it would
|
|
469
566
|
* release into (null: it would mint one), the next version, the aliases a
|
|
470
567
|
* first publish can still rename, the diff against the current version, the
|
|
@@ -474,6 +571,7 @@ var LoticsClient = class {
|
|
|
474
571
|
async previewStarterPublish(opts) {
|
|
475
572
|
const params = new URLSearchParams();
|
|
476
573
|
params.set("app_ids", opts.app_ids.join(","));
|
|
574
|
+
if (opts.starter_id !== void 0) params.set("starter_id", opts.starter_id);
|
|
477
575
|
if (opts.knowledge_doc_ids !== void 0) params.set("knowledge_doc_ids", opts.knowledge_doc_ids.join(","));
|
|
478
576
|
if (opts.renames !== void 0 && opts.renames.length > 0) params.set("renames", JSON.stringify(opts.renames));
|
|
479
577
|
if (opts.name !== void 0) params.set("name", opts.name);
|
|
@@ -1050,6 +1148,12 @@ var LoticsClient = class {
|
|
|
1050
1148
|
export {
|
|
1051
1149
|
API_BASE_URL,
|
|
1052
1150
|
LoticsClient,
|
|
1151
|
+
LoticsRequestError,
|
|
1053
1152
|
WEB_APP_URL,
|
|
1054
|
-
fetchOfficialStarters
|
|
1153
|
+
fetchOfficialStarters,
|
|
1154
|
+
getOfficialStarter,
|
|
1155
|
+
getPublicJson,
|
|
1156
|
+
pollCliLogin,
|
|
1157
|
+
readOfficialStarter,
|
|
1158
|
+
startCliLogin
|
|
1055
1159
|
};
|
package/docs/building_an_app.md
CHANGED
|
@@ -237,7 +237,7 @@ actually carries, not what the source says it should.
|
|
|
237
237
|
```
|
|
238
238
|
npm run typecheck && npm run lint && npm test
|
|
239
239
|
lotics app check # every pre-flight a deploy runs, without building or shipping,
|
|
240
|
-
# plus the portability gate a
|
|
240
|
+
# plus the portability gate a library publish applies
|
|
241
241
|
lotics app deploy -m "<what changed + why>"
|
|
242
242
|
```
|
|
243
243
|
|