@lotics/cli 0.188.0 → 0.190.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 +11 -0
- package/README.md +63 -2
- package/dist/src/cli.js +3483 -417
- package/dist/src/client.d.ts +333 -10
- package/dist/src/client.js +133 -14
- package/docs/building_an_app.md +1 -1
- package/docs/cli_reference.md +12 -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,105 @@ 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 workspace read BACK as a model — the inverse of the scaffold above.
|
|
311
|
+
*
|
|
312
|
+
* `entities` and `roles` are the model file's own two keys, whose authoritative
|
|
313
|
+
* shapes are `contractEntitySchema` / `contractRoleSchema` in `@lotics/shared` —
|
|
314
|
+
* specifiers a published `.d.ts` cannot resolve, so they are typed here as what
|
|
315
|
+
* this client does with them, which is hand them on whole. The same trade
|
|
316
|
+
* `ScaffoldWorkspaceRequest` makes in the other direction.
|
|
317
|
+
*
|
|
318
|
+
* `findings` are about the export rather than part of it: a workspace holds
|
|
319
|
+
* things a model file cannot express, and a file that dropped them silently
|
|
320
|
+
* would be read as the whole workspace.
|
|
321
|
+
*/
|
|
322
|
+
export interface WorkspaceModelExport {
|
|
323
|
+
contract: {
|
|
324
|
+
entities: Array<Record<string, unknown>>;
|
|
325
|
+
roles: Array<Record<string, unknown>>;
|
|
326
|
+
templates: Array<Record<string, unknown>>;
|
|
327
|
+
};
|
|
328
|
+
findings: Array<{
|
|
329
|
+
severity: "error" | "warning" | "info";
|
|
330
|
+
area: string;
|
|
331
|
+
message: string;
|
|
332
|
+
}>;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* A request the API refused. The message carries the status and the server's
|
|
336
|
+
* sentence, which is what reaches a person; `status` and `body` are for the
|
|
337
|
+
* few callers that branch on a refusal — a 409 whose `body.reason` says the
|
|
338
|
+
* run had nothing to do, rather than that it failed.
|
|
339
|
+
*/
|
|
340
|
+
export declare class LoticsRequestError extends Error {
|
|
341
|
+
readonly status: number;
|
|
342
|
+
readonly body: Record<string, unknown>;
|
|
343
|
+
constructor(message: string, status: number, body: Record<string, unknown>);
|
|
344
|
+
}
|
|
238
345
|
export declare const API_BASE_URL: string;
|
|
239
346
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
347
|
+
* The website: where a person goes when the CLI cannot finish the job, and where
|
|
348
|
+
* the presets are SERVED FROM. Held beside the API base so the pair is read
|
|
349
|
+
* together, named once rather than inlined at each message that points
|
|
350
|
+
* somewhere, and overridable for the same reason the API base is — a run against
|
|
351
|
+
* a local site must read that site's presets, not production's.
|
|
244
352
|
*/
|
|
245
|
-
export declare const WEB_APP_URL
|
|
246
|
-
/** One
|
|
353
|
+
export declare const WEB_APP_URL: string;
|
|
354
|
+
/** One package on the public shelf — what a chooser decides with, nothing else. */
|
|
247
355
|
export interface OfficialStarter {
|
|
248
356
|
id: string;
|
|
249
357
|
name: string;
|
|
250
358
|
description: string | null;
|
|
251
359
|
icon: string | null;
|
|
252
360
|
latest_version: number;
|
|
361
|
+
/**
|
|
362
|
+
* The apps the copy creates. A name and a sentence leave the chooser
|
|
363
|
+
* guessing; these are what someone's own words get matched against when
|
|
364
|
+
* deciding between a package and a model.
|
|
365
|
+
*/
|
|
366
|
+
apps: Array<{
|
|
367
|
+
alias: string;
|
|
368
|
+
name: string;
|
|
369
|
+
description?: string;
|
|
370
|
+
}>;
|
|
371
|
+
/** How many tables the copy creates — the other half of "what is in this?". */
|
|
372
|
+
table_count: number;
|
|
253
373
|
}
|
|
254
374
|
/**
|
|
255
375
|
* The starters Lotics publishes, fetched with no credential.
|
|
@@ -260,6 +380,152 @@ export interface OfficialStarter {
|
|
|
260
380
|
* exists, so needing one to ask means signing up to find out the answer was no.
|
|
261
381
|
*/
|
|
262
382
|
export declare function fetchOfficialStarters(): Promise<OfficialStarter[]>;
|
|
383
|
+
/**
|
|
384
|
+
* One keyless GET of JSON, bounded — the shape every read that predates an
|
|
385
|
+
* account takes.
|
|
386
|
+
*
|
|
387
|
+
* Bounded because these are the FIRST commands a new user runs and a hang with
|
|
388
|
+
* nothing on screen is the worst version of the failure; `config.ts` bounds its
|
|
389
|
+
* own unattended fetch for the same reason, and `docs/network_reliability.md` is
|
|
390
|
+
* a log of connections from our users' networks degrading rather than refusing,
|
|
391
|
+
* which is the shape that hangs.
|
|
392
|
+
*
|
|
393
|
+
* A transport failure THROWS and a status is RETURNED, because only the first is
|
|
394
|
+
* "check your connection": a status means the server replied and the network is
|
|
395
|
+
* demonstrably fine, and a caller that answered both the same way would tell an
|
|
396
|
+
* owner their package is missing every time the link drops.
|
|
397
|
+
*/
|
|
398
|
+
export declare function getPublicJson(url: string, what: string): Promise<{
|
|
399
|
+
ok: true;
|
|
400
|
+
body: unknown;
|
|
401
|
+
} | {
|
|
402
|
+
ok: false;
|
|
403
|
+
status: number;
|
|
404
|
+
}>;
|
|
405
|
+
/**
|
|
406
|
+
* One official package READ WHOLE, with no credential — or the status that says
|
|
407
|
+
* the shelf does not serve it.
|
|
408
|
+
*
|
|
409
|
+
* The shelf row says what a package is; this says what is in it — entities with
|
|
410
|
+
* their fields, the roles, each template's metadata and each app's name. Never a
|
|
411
|
+
* workflow body: this is a read for deciding and for writing a model from, not a
|
|
412
|
+
* copy.
|
|
413
|
+
*
|
|
414
|
+
* Unauthenticated for the same reason the shelf is: a preset is READ rather than
|
|
415
|
+
* instantiated, and the whole point of reading one is that it happens before an
|
|
416
|
+
* account exists. Only official packages are served — the `is_official` filter,
|
|
417
|
+
* exactly as on the shelf — so a caller's OWN package answers a status here and
|
|
418
|
+
* is read through the authenticated pair instead. A transport failure still
|
|
419
|
+
* throws: "unreachable" is not "not on the shelf", and answering both the same
|
|
420
|
+
* way would tell an owner their package is missing every time the link drops.
|
|
421
|
+
*/
|
|
422
|
+
export declare function readOfficialStarter(starter_id: string): Promise<{
|
|
423
|
+
ok: true;
|
|
424
|
+
package: OfficialStarterRead;
|
|
425
|
+
} | {
|
|
426
|
+
ok: false;
|
|
427
|
+
status: number;
|
|
428
|
+
}>;
|
|
429
|
+
/** The same read for a caller with nothing to fall back to — a refusal is the end. */
|
|
430
|
+
export declare function getOfficialStarter(starter_id: string): Promise<OfficialStarterRead>;
|
|
431
|
+
/** A column as the public read shows it: enough to write a model field from. */
|
|
432
|
+
export interface OfficialStarterField {
|
|
433
|
+
alias: string;
|
|
434
|
+
label: string;
|
|
435
|
+
type: string;
|
|
436
|
+
}
|
|
437
|
+
/** A table as the public read shows it: its columns. */
|
|
438
|
+
export interface OfficialStarterEntity {
|
|
439
|
+
alias: string;
|
|
440
|
+
label: string;
|
|
441
|
+
description?: string;
|
|
442
|
+
fields: OfficialStarterField[];
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* What the public read of one package carries.
|
|
446
|
+
*
|
|
447
|
+
* Every namespace is declared structurally, NARROWED to what a reader turning
|
|
448
|
+
* this into a model needs — fields carry their type, apps only their name — the
|
|
449
|
+
* same trade `StarterVersionContract` makes: a published `.d.ts` cannot resolve
|
|
450
|
+
* `@lotics/shared`. `packageContractSchema` is the definition, and the CLI
|
|
451
|
+
* parses a preset's block through `modelPresetSchema` before resolving a `from`
|
|
452
|
+
* file against it.
|
|
453
|
+
*/
|
|
454
|
+
export interface OfficialStarterRead {
|
|
455
|
+
id: string;
|
|
456
|
+
name: string;
|
|
457
|
+
description: string | null;
|
|
458
|
+
latest_version: number;
|
|
459
|
+
contract: {
|
|
460
|
+
entities: OfficialStarterEntity[];
|
|
461
|
+
roles: Array<{
|
|
462
|
+
alias: string;
|
|
463
|
+
label: string;
|
|
464
|
+
}>;
|
|
465
|
+
templates: Array<{
|
|
466
|
+
alias: string;
|
|
467
|
+
label: string;
|
|
468
|
+
type: string;
|
|
469
|
+
}>;
|
|
470
|
+
/** The apps a copy creates. */
|
|
471
|
+
apps: Array<{
|
|
472
|
+
alias: string;
|
|
473
|
+
name: string;
|
|
474
|
+
description?: string;
|
|
475
|
+
}>;
|
|
476
|
+
/** Sample rows per entity alias — how many, never the rows. */
|
|
477
|
+
fixtures: Record<string, {
|
|
478
|
+
row_count: number;
|
|
479
|
+
}>;
|
|
480
|
+
knowledge: Record<string, {
|
|
481
|
+
name: string;
|
|
482
|
+
description: string;
|
|
483
|
+
}>;
|
|
484
|
+
knowledge_expects: string[];
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* What a terminal holds while it waits to be let in: the handle the server knows
|
|
489
|
+
* the request by, the `secret` that proves this is the same terminal that asked,
|
|
490
|
+
* and the `code` the person matches against the confirm page before pressing it.
|
|
491
|
+
*
|
|
492
|
+
* `secret` is a credential and is never printed — the `code` is what a person
|
|
493
|
+
* reads, and it proves nothing on its own.
|
|
494
|
+
*/
|
|
495
|
+
export interface CliLoginRequest {
|
|
496
|
+
request_id: string;
|
|
497
|
+
secret: string;
|
|
498
|
+
code: string;
|
|
499
|
+
expires_at: string;
|
|
500
|
+
}
|
|
501
|
+
/** Where a login request stands. `approved` carries the key, and only once. */
|
|
502
|
+
export type CliLoginState = {
|
|
503
|
+
status: "pending";
|
|
504
|
+
} | {
|
|
505
|
+
status: "expired";
|
|
506
|
+
} | {
|
|
507
|
+
status: "claimed";
|
|
508
|
+
} | {
|
|
509
|
+
status: "approved";
|
|
510
|
+
api_key: string;
|
|
511
|
+
organization_id: string;
|
|
512
|
+
organization_name: string;
|
|
513
|
+
workspace_id: string;
|
|
514
|
+
};
|
|
515
|
+
/**
|
|
516
|
+
* Ask Lotics to mail a sign-in link, and read back whether it was confirmed.
|
|
517
|
+
*
|
|
518
|
+
* Plain functions rather than `LoticsClient` methods for the same reason
|
|
519
|
+
* `fetchOfficialStarters` is one: there is no key to build a client around, and
|
|
520
|
+
* that is the whole point — this is the pair a terminal holding NO credential
|
|
521
|
+
* uses to obtain one. Sending an `Authorization` header would make the endpoint
|
|
522
|
+
* answerable only to callers who no longer need it.
|
|
523
|
+
*
|
|
524
|
+
* The same answer whether or not the address has an account: it would
|
|
525
|
+
* otherwise tell any stranger which emails are registered here.
|
|
526
|
+
*/
|
|
527
|
+
export declare function startCliLogin(email: string): Promise<CliLoginRequest>;
|
|
528
|
+
export declare function pollCliLogin(request_id: string, secret: string): Promise<CliLoginState>;
|
|
263
529
|
export declare class LoticsClient {
|
|
264
530
|
private apiKey;
|
|
265
531
|
private workspaceId;
|
|
@@ -322,6 +588,26 @@ export declare class LoticsClient {
|
|
|
322
588
|
name: string;
|
|
323
589
|
timezone?: string;
|
|
324
590
|
}): Promise<WorkspaceInfo>;
|
|
591
|
+
/**
|
|
592
|
+
* Apply a workspace MODEL to the current workspace — the from-scratch half of
|
|
593
|
+
* the starter pipeline, on a contract nobody published.
|
|
594
|
+
*
|
|
595
|
+
* Additive: with `adopt`, an entity whose label already names a table here
|
|
596
|
+
* binds to it and gains the fields, options and views it is missing; without
|
|
597
|
+
* `adopt`, that label is refused. Nothing is ever modified or deleted, and
|
|
598
|
+
* rows land only where every bound table is empty. Admin-only.
|
|
599
|
+
*/
|
|
600
|
+
scaffoldWorkspace(body: ScaffoldWorkspaceRequest): Promise<ScaffoldWorkspaceResult>;
|
|
601
|
+
/**
|
|
602
|
+
* Read this workspace's schema back as a model — the tables it has (or only
|
|
603
|
+
* the ones named), their fields, options and views, plus its roles.
|
|
604
|
+
*
|
|
605
|
+
* A pure read, and admin-only for the same reason the scaffold is: the whole
|
|
606
|
+
* schema is what comes back.
|
|
607
|
+
*/
|
|
608
|
+
exportWorkspaceModel(opts?: {
|
|
609
|
+
tables?: string[];
|
|
610
|
+
}): Promise<WorkspaceModelExport>;
|
|
325
611
|
/** Renames (and re-settings) the CURRENT workspace — the endpoint reads the
|
|
326
612
|
* target from the request's workspace, never a path id. */
|
|
327
613
|
updateWorkspace(body: {
|
|
@@ -455,7 +741,7 @@ export declare class LoticsClient {
|
|
|
455
741
|
}>;
|
|
456
742
|
/**
|
|
457
743
|
* Take a starter off the shelf, or `undo` to put it back (backs `opctl
|
|
458
|
-
*
|
|
744
|
+
* library unpublish`). It hides from non-owning orgs and can no longer be
|
|
459
745
|
* copied; copies already made are unaffected — they never linked back.
|
|
460
746
|
* Owner-org admin-only.
|
|
461
747
|
*/
|
|
@@ -499,6 +785,14 @@ export declare class LoticsClient {
|
|
|
499
785
|
latest_version: number;
|
|
500
786
|
is_official: boolean;
|
|
501
787
|
owned_by_caller: boolean;
|
|
788
|
+
/** The apps a copy creates — the same contents the public shelf names. */
|
|
789
|
+
apps: Array<{
|
|
790
|
+
alias: string;
|
|
791
|
+
name: string;
|
|
792
|
+
description?: string;
|
|
793
|
+
}>;
|
|
794
|
+
/** How many tables the copy creates. */
|
|
795
|
+
table_count: number;
|
|
502
796
|
}>>;
|
|
503
797
|
/**
|
|
504
798
|
* Copy a starter into the current workspace.
|
|
@@ -513,6 +807,20 @@ export declare class LoticsClient {
|
|
|
513
807
|
version?: number;
|
|
514
808
|
no_sample_data?: boolean;
|
|
515
809
|
adopt?: boolean;
|
|
810
|
+
/**
|
|
811
|
+
* The labels THIS workspace calls the package's entities and fields.
|
|
812
|
+
*
|
|
813
|
+
* Scaffold adopts by label, so binding renames the contract's labels
|
|
814
|
+
* before it runs and the copy lands on the tables the caller already has.
|
|
815
|
+
* A bound field whose type differs from the one the contract declares is
|
|
816
|
+
* refused — the type is the contract, only the naming moves. Structural
|
|
817
|
+
* for the same reason the rest of this file's wire shapes are;
|
|
818
|
+
* `packageBindSchema` in `@lotics/shared` is the definition.
|
|
819
|
+
*/
|
|
820
|
+
bind?: Record<string, {
|
|
821
|
+
label?: string;
|
|
822
|
+
fields?: Record<string, string>;
|
|
823
|
+
}>;
|
|
516
824
|
}): Promise<{
|
|
517
825
|
/** Each app's deploy, in contract order. `error` set and `deployed` null when one did not land. */
|
|
518
826
|
apps: Array<{
|
|
@@ -541,6 +849,21 @@ export declare class LoticsClient {
|
|
|
541
849
|
starter_id: string;
|
|
542
850
|
latest_version: number;
|
|
543
851
|
}>;
|
|
852
|
+
/**
|
|
853
|
+
* Apply the latest version of the package this app was copied from.
|
|
854
|
+
*
|
|
855
|
+
* The other provenance direction from `getAppOriginStarter`, and the only one
|
|
856
|
+
* that writes: that one answers which package this app PUBLISHED, this one
|
|
857
|
+
* reads what a copy recorded and re-derives the app against a later release.
|
|
858
|
+
*
|
|
859
|
+
* The offer is partial by design and the result says how partial: schema is
|
|
860
|
+
* additive (`created_fields`, and a `dropped_fields` the new version stopped
|
|
861
|
+
* declaring is left standing), and an artifact the owner has edited is kept
|
|
862
|
+
* and named (`skipped_workflows` / `skipped_agents`). An app with no
|
|
863
|
+
* provenance is a 400, and one already on the latest release a 409 — both
|
|
864
|
+
* refusals, both before any write. Admin-only.
|
|
865
|
+
*/
|
|
866
|
+
upgradeApp(app_id: string): Promise<AppUpgradeResult>;
|
|
544
867
|
/**
|
|
545
868
|
* Capture live records from this workspace as a starter's sample data.
|
|
546
869
|
*
|
|
@@ -587,7 +910,7 @@ export declare class LoticsClient {
|
|
|
587
910
|
created_at: string;
|
|
588
911
|
updated_at: string;
|
|
589
912
|
}>;
|
|
590
|
-
/** Version history newest-first (no contract payloads) — backs `opctl
|
|
913
|
+
/** Version history newest-first (no contract payloads) — backs `opctl library show`. Admin-only. */
|
|
591
914
|
listStarterVersions(starter_id: string): Promise<{
|
|
592
915
|
versions: Array<{
|
|
593
916
|
version: number;
|
|
@@ -622,7 +945,7 @@ export declare class LoticsClient {
|
|
|
622
945
|
}>>;
|
|
623
946
|
/**
|
|
624
947
|
* Preview publishing a set of this workspace's apps as one starter version —
|
|
625
|
-
* the GET behind `opctl
|
|
948
|
+
* the GET behind `opctl library publish` (no `--yes`). The server runs the
|
|
626
949
|
* same extraction the publish runs and reports which starter it would
|
|
627
950
|
* release into (null: it would mint one), the next version, the aliases a
|
|
628
951
|
* 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) {
|
|
145
164
|
let response;
|
|
146
165
|
try {
|
|
147
|
-
response = await fetch(
|
|
148
|
-
|
|
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) {
|
|
194
|
+
let response;
|
|
195
|
+
try {
|
|
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,33 @@ 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
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Read this workspace's schema back as a model — the tables it has (or only
|
|
361
|
+
* the ones named), their fields, options and views, plus its roles.
|
|
362
|
+
*
|
|
363
|
+
* A pure read, and admin-only for the same reason the scaffold is: the whole
|
|
364
|
+
* schema is what comes back.
|
|
365
|
+
*/
|
|
366
|
+
async exportWorkspaceModel(opts = {}) {
|
|
367
|
+
const params = new URLSearchParams();
|
|
368
|
+
if (opts.tables !== void 0 && opts.tables.length > 0) {
|
|
369
|
+
params.set("tables", opts.tables.join(","));
|
|
370
|
+
}
|
|
371
|
+
const qs = params.toString();
|
|
372
|
+
return this.request("GET", `/v1/workspaces/model${qs ? `?${qs}` : ""}`);
|
|
373
|
+
}
|
|
279
374
|
/** Renames (and re-settings) the CURRENT workspace — the endpoint reads the
|
|
280
375
|
* target from the request's workspace, never a path id. */
|
|
281
376
|
async updateWorkspace(body) {
|
|
@@ -355,7 +450,7 @@ var LoticsClient = class {
|
|
|
355
450
|
}
|
|
356
451
|
/**
|
|
357
452
|
* Take a starter off the shelf, or `undo` to put it back (backs `opctl
|
|
358
|
-
*
|
|
453
|
+
* library unpublish`). It hides from non-owning orgs and can no longer be
|
|
359
454
|
* copied; copies already made are unaffected — they never linked back.
|
|
360
455
|
* Owner-org admin-only.
|
|
361
456
|
*/
|
|
@@ -408,6 +503,23 @@ var LoticsClient = class {
|
|
|
408
503
|
async getAppOriginStarter(app_id) {
|
|
409
504
|
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/origin-starter`);
|
|
410
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Apply the latest version of the package this app was copied from.
|
|
508
|
+
*
|
|
509
|
+
* The other provenance direction from `getAppOriginStarter`, and the only one
|
|
510
|
+
* that writes: that one answers which package this app PUBLISHED, this one
|
|
511
|
+
* reads what a copy recorded and re-derives the app against a later release.
|
|
512
|
+
*
|
|
513
|
+
* The offer is partial by design and the result says how partial: schema is
|
|
514
|
+
* additive (`created_fields`, and a `dropped_fields` the new version stopped
|
|
515
|
+
* declaring is left standing), and an artifact the owner has edited is kept
|
|
516
|
+
* and named (`skipped_workflows` / `skipped_agents`). An app with no
|
|
517
|
+
* provenance is a 400, and one already on the latest release a 409 — both
|
|
518
|
+
* refusals, both before any write. Admin-only.
|
|
519
|
+
*/
|
|
520
|
+
async upgradeApp(app_id) {
|
|
521
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/upgrade`, {});
|
|
522
|
+
}
|
|
411
523
|
/**
|
|
412
524
|
* Capture live records from this workspace as a starter's sample data.
|
|
413
525
|
*
|
|
@@ -436,7 +548,7 @@ var LoticsClient = class {
|
|
|
436
548
|
async getStarter(starter_id) {
|
|
437
549
|
return this.request("GET", `/v1/starters/${encodeURIComponent(starter_id)}`);
|
|
438
550
|
}
|
|
439
|
-
/** Version history newest-first (no contract payloads) — backs `opctl
|
|
551
|
+
/** Version history newest-first (no contract payloads) — backs `opctl library show`. Admin-only. */
|
|
440
552
|
async listStarterVersions(starter_id) {
|
|
441
553
|
return this.request("GET", `/v1/starters/${encodeURIComponent(starter_id)}/versions`);
|
|
442
554
|
}
|
|
@@ -464,7 +576,7 @@ var LoticsClient = class {
|
|
|
464
576
|
// --- Starter publishing (the authoring verbs; copying is `instantiateStarter`) ---
|
|
465
577
|
/**
|
|
466
578
|
* Preview publishing a set of this workspace's apps as one starter version —
|
|
467
|
-
* the GET behind `opctl
|
|
579
|
+
* the GET behind `opctl library publish` (no `--yes`). The server runs the
|
|
468
580
|
* same extraction the publish runs and reports which starter it would
|
|
469
581
|
* release into (null: it would mint one), the next version, the aliases a
|
|
470
582
|
* first publish can still rename, the diff against the current version, the
|
|
@@ -474,6 +586,7 @@ var LoticsClient = class {
|
|
|
474
586
|
async previewStarterPublish(opts) {
|
|
475
587
|
const params = new URLSearchParams();
|
|
476
588
|
params.set("app_ids", opts.app_ids.join(","));
|
|
589
|
+
if (opts.starter_id !== void 0) params.set("starter_id", opts.starter_id);
|
|
477
590
|
if (opts.knowledge_doc_ids !== void 0) params.set("knowledge_doc_ids", opts.knowledge_doc_ids.join(","));
|
|
478
591
|
if (opts.renames !== void 0 && opts.renames.length > 0) params.set("renames", JSON.stringify(opts.renames));
|
|
479
592
|
if (opts.name !== void 0) params.set("name", opts.name);
|
|
@@ -1050,6 +1163,12 @@ var LoticsClient = class {
|
|
|
1050
1163
|
export {
|
|
1051
1164
|
API_BASE_URL,
|
|
1052
1165
|
LoticsClient,
|
|
1166
|
+
LoticsRequestError,
|
|
1053
1167
|
WEB_APP_URL,
|
|
1054
|
-
fetchOfficialStarters
|
|
1168
|
+
fetchOfficialStarters,
|
|
1169
|
+
getOfficialStarter,
|
|
1170
|
+
getPublicJson,
|
|
1171
|
+
pollCliLogin,
|
|
1172
|
+
readOfficialStarter,
|
|
1173
|
+
startCliLogin
|
|
1055
1174
|
};
|
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
|
|