@lotics/cli 0.196.0 → 0.197.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/README.md +15 -6
- package/dist/render_page.js +152 -41
- package/dist/src/cli.js +895 -275
- package/dist/src/client.d.ts +58 -12
- package/dist/src/client.js +24 -0
- package/docs/building_an_app.md +6 -0
- package/docs/cli_reference.md +13 -9
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -15464,6 +15464,15 @@ var LoticsClient = class {
|
|
|
15464
15464
|
async getAppWorkflow(app_id, alias) {
|
|
15465
15465
|
return this.execute("get_app_workflow", { app_id, alias });
|
|
15466
15466
|
}
|
|
15467
|
+
/**
|
|
15468
|
+
* The app's capability catalog exactly as a chat or MCP caller reads it —
|
|
15469
|
+
* every query, workflow and agent alias the caller's scope reaches, with the
|
|
15470
|
+
* description each is chosen BY. One read for the whole app, so a check over
|
|
15471
|
+
* what those readers see costs one request rather than one per alias.
|
|
15472
|
+
*/
|
|
15473
|
+
async getAppCapabilities(app_id) {
|
|
15474
|
+
return this.execute("get_app_capabilities", { app_id });
|
|
15475
|
+
}
|
|
15467
15476
|
/**
|
|
15468
15477
|
* Fetch the server-generated workspace `.d.ts` + the wrapper envelope that
|
|
15469
15478
|
* make a `src/workflows/<alias>.ts` body locally typecheckable (GAP-59).
|
|
@@ -15657,6 +15666,21 @@ var LoticsClient = class {
|
|
|
15657
15666
|
await fs2.promises.writeFile(absolutePath, buffer);
|
|
15658
15667
|
return absolutePath;
|
|
15659
15668
|
}
|
|
15669
|
+
/**
|
|
15670
|
+
* One page of this workspace's stored files, newest first — `GET /v1/files`.
|
|
15671
|
+
*
|
|
15672
|
+
* The only enumeration there is: every other file surface takes an id, so
|
|
15673
|
+
* without this "what is in the store" is answerable from Postgres alone, which
|
|
15674
|
+
* is exactly the escape hatch a CLI-first workflow exists to remove. Paged by a
|
|
15675
|
+
* keyset cursor the server mints; a caller walks until `next_cursor` is null.
|
|
15676
|
+
*/
|
|
15677
|
+
async listFiles(opts) {
|
|
15678
|
+
const params = new URLSearchParams();
|
|
15679
|
+
if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
|
|
15680
|
+
if (opts?.cursor !== void 0) params.set("cursor", opts.cursor);
|
|
15681
|
+
const query = params.toString();
|
|
15682
|
+
return this.request("GET", `/v1/files${query ? `?${query}` : ""}`);
|
|
15683
|
+
}
|
|
15660
15684
|
async downloadFileById(fileId, destination, options) {
|
|
15661
15685
|
const signedHeaders = this.buildHeaders();
|
|
15662
15686
|
const signedUrlRes = await fetch(
|
|
@@ -33628,19 +33652,7 @@ var queryWindowFrameSchema = zod_default.object({
|
|
|
33628
33652
|
following: zod_default.number().int().nonnegative().optional().describe("Rows following current. Omit for unbounded.")
|
|
33629
33653
|
});
|
|
33630
33654
|
var queryNodeSchema = zod_default.lazy(
|
|
33631
|
-
() => zod_default.discriminatedUnion("kind",
|
|
33632
|
-
queryFromTableNodeSchema,
|
|
33633
|
-
queryProjectNodeSchema,
|
|
33634
|
-
queryFilterNodeSchema,
|
|
33635
|
-
queryJoinNodeSchema,
|
|
33636
|
-
queryUnionNodeSchema,
|
|
33637
|
-
queryGroupNodeSchema,
|
|
33638
|
-
queryWindowNodeSchema,
|
|
33639
|
-
querySortNodeSchema,
|
|
33640
|
-
queryLimitNodeSchema,
|
|
33641
|
-
queryUnpivotNodeSchema,
|
|
33642
|
-
queryUnnestNodeSchema
|
|
33643
|
-
])
|
|
33655
|
+
() => zod_default.discriminatedUnion("kind", queryNodeSchemas)
|
|
33644
33656
|
);
|
|
33645
33657
|
var queryFromTableNodeSchema = zod_default.object({
|
|
33646
33658
|
kind: zod_default.literal("from_table"),
|
|
@@ -33770,6 +33782,25 @@ var queryUnnestNodeSchema = zod_default.object({
|
|
|
33770
33782
|
"Keep source rows whose cell is NULL / empty as one output row with NULL element columns. Defaults to false (such rows are dropped)."
|
|
33771
33783
|
)
|
|
33772
33784
|
});
|
|
33785
|
+
var queryNodeSchemas = [
|
|
33786
|
+
queryFromTableNodeSchema,
|
|
33787
|
+
queryProjectNodeSchema,
|
|
33788
|
+
queryFilterNodeSchema,
|
|
33789
|
+
queryJoinNodeSchema,
|
|
33790
|
+
queryUnionNodeSchema,
|
|
33791
|
+
queryGroupNodeSchema,
|
|
33792
|
+
queryWindowNodeSchema,
|
|
33793
|
+
querySortNodeSchema,
|
|
33794
|
+
queryLimitNodeSchema,
|
|
33795
|
+
queryUnpivotNodeSchema,
|
|
33796
|
+
queryUnnestNodeSchema
|
|
33797
|
+
];
|
|
33798
|
+
var queryNodeKeysByKind = new Map(
|
|
33799
|
+
queryNodeSchemas.map((schema) => [
|
|
33800
|
+
schema.shape.kind.value,
|
|
33801
|
+
new Set(Object.keys(schema.shape))
|
|
33802
|
+
])
|
|
33803
|
+
);
|
|
33773
33804
|
var appWorkflowInputBaseSchema = zod_default.object({
|
|
33774
33805
|
description: zod_default.string().optional().describe("Human-readable description of this input \u2014 surfaces in agent + CLI tooling"),
|
|
33775
33806
|
required: zod_default.boolean().optional().describe("Whether the input must be provided. Defaults to true.")
|
|
@@ -36684,7 +36715,7 @@ function resultSideEffects(result) {
|
|
|
36684
36715
|
}
|
|
36685
36716
|
|
|
36686
36717
|
// src/version.ts
|
|
36687
|
-
var VERSION = "0.
|
|
36718
|
+
var VERSION = "0.197.0";
|
|
36688
36719
|
|
|
36689
36720
|
// src/timezone.ts
|
|
36690
36721
|
function machineTimezone() {
|
|
@@ -37113,8 +37144,15 @@ var COMMANDS = [
|
|
|
37113
37144
|
help: [
|
|
37114
37145
|
" lotics workspace List workspaces in the active org (marks current)",
|
|
37115
37146
|
" lotics workspace select <id> Switch to a different workspace",
|
|
37116
|
-
" lotics workspace create <name>
|
|
37117
|
-
"
|
|
37147
|
+
" lotics workspace create <name> [--timezone <Area/City>] [--currency <ISO>]",
|
|
37148
|
+
" Create a new workspace (admin only). Without them it",
|
|
37149
|
+
" takes the org's oldest workspace's zone and currency \u2014",
|
|
37150
|
+
" which decide how every date buckets and every money",
|
|
37151
|
+
" field renders, and are invisible once wrong",
|
|
37152
|
+
" lotics workspace settings [--name <n>] [--currency <ISO>] [--timezone <Area/City>]",
|
|
37153
|
+
" Change the current workspace's name, currency or zone",
|
|
37154
|
+
" (admin only). Only what you name changes",
|
|
37155
|
+
" lotics workspace rename <name> Rename the current workspace \u2014 settings, by name alone",
|
|
37118
37156
|
" lotics workspace delete <id> --yes Delete a workspace (admin only; soft delete, recoverable)",
|
|
37119
37157
|
" lotics workspace doctor Report dangling schema references (admin only)"
|
|
37120
37158
|
]
|
|
@@ -37194,7 +37232,9 @@ var COMMANDS = [
|
|
|
37194
37232
|
help: [
|
|
37195
37233
|
" lotics run <tool> '<json>' Execute a tool",
|
|
37196
37234
|
" lotics run <tool> @args.json Read JSON args from a file (large payloads)",
|
|
37197
|
-
" cat args.json | lotics run <tool>
|
|
37235
|
+
" cat args.json | lotics run <tool> - Read JSON args from stdin (large payloads).",
|
|
37236
|
+
" The trailing - is required: without it the tool",
|
|
37237
|
+
" runs with no arguments and never waits.",
|
|
37198
37238
|
" Exits non-zero when the RESULT reports the work",
|
|
37199
37239
|
" failed (a refused workflow, a failed agent run) \u2014",
|
|
37200
37240
|
" not only when the call did.",
|
|
@@ -37215,9 +37255,14 @@ var COMMANDS = [
|
|
|
37215
37255
|
" (-m is required \u2014 it's the version's audit trail;",
|
|
37216
37256
|
" carries code + queries only \u2014 workflow bindings are",
|
|
37217
37257
|
" managed by set_app_workflow / remove_app_workflow.",
|
|
37218
|
-
" --prune also UNBINDS
|
|
37219
|
-
"
|
|
37220
|
-
"
|
|
37258
|
+
" --prune also UNBINDS what this project retired \u2014 an",
|
|
37259
|
+
" alias this bundle stopped calling, and one whose",
|
|
37260
|
+
" declaration you deleted; off by default because",
|
|
37261
|
+
" `run_app_workflow` and chat reach a binding the",
|
|
37262
|
+
" bundle never calls.",
|
|
37263
|
+
" --prune-invoked <alias> unbinds a workflow the",
|
|
37264
|
+
" workspace has already RUN \u2014 named per alias, only",
|
|
37265
|
+
" when you know that caller is gone)",
|
|
37221
37266
|
" lotics app versions [app_id] Show deploy history newest-first (version,",
|
|
37222
37267
|
" timestamp, deployer, build status, -m message;",
|
|
37223
37268
|
" * marks the currently served version)",
|
|
@@ -37249,8 +37294,13 @@ var COMMANDS = [
|
|
|
37249
37294
|
" lotics app workflow set <alias> Push the edited src/workflows/<alias>.ts body",
|
|
37250
37295
|
" through set_app_workflow (server verifies)",
|
|
37251
37296
|
" lotics app workflow pull Rewrite src/workflows/*.ts from the server",
|
|
37252
|
-
" lotics app workflow check [alias]
|
|
37253
|
-
" isolated program per alias; the app's own tsc)",
|
|
37297
|
+
" lotics app workflow check [alias...] Typecheck src/workflows bodies locally (one",
|
|
37298
|
+
" isolated program per alias; the app's own tsc).",
|
|
37299
|
+
" Name several to check several; none checks all",
|
|
37300
|
+
" lotics app workflow diff [alias...] Show how src/workflows/<alias>.ts differs from",
|
|
37301
|
+
" the body the server is running, line by line.",
|
|
37302
|
+
" None named diffs every alias that reads as",
|
|
37303
|
+
" drifted. Exits 1 when anything differs",
|
|
37254
37304
|
" lotics app query set <alias> Push package.json#lotics.queries.<alias> to",
|
|
37255
37305
|
" apps.queries via set_app_query and regenerate",
|
|
37256
37306
|
" .lotics/app_queries.d.ts (no deploy; re-synced by",
|
|
@@ -37262,7 +37312,12 @@ var COMMANDS = [
|
|
|
37262
37312
|
" those with set_app_agent, then pull.",
|
|
37263
37313
|
" lotics app subdomain <new-subdomain> Rename the app's public address (its subdomain)",
|
|
37264
37314
|
` lotics app rename "<new name>" Rename the app's display name (launcher title)`,
|
|
37265
|
-
" lotics app dev [path]
|
|
37315
|
+
" lotics app dev [path] [--port=<n>] [--vite-port=<n>]",
|
|
37316
|
+
" Run the app locally with HMR (RPC forwarded to prod).",
|
|
37317
|
+
" --port is the wrapper you open, --vite-port the module",
|
|
37318
|
+
" server. Pin both to run several apps at once; with no",
|
|
37319
|
+
" --vite-port, vite.config's server.port is used",
|
|
37320
|
+
" lotics app dev --view-as <member_id> \u2026 as that member sees it (admin only, reads only)"
|
|
37266
37321
|
]
|
|
37267
37322
|
},
|
|
37268
37323
|
{
|
|
@@ -37294,6 +37349,13 @@ var COMMANDS = [
|
|
|
37294
37349
|
verbs: ["file"],
|
|
37295
37350
|
aliases: ["upload", "download", "preview"],
|
|
37296
37351
|
help: [
|
|
37352
|
+
" lotics file list [--limit <n>] [--cursor <token>]",
|
|
37353
|
+
" List this workspace's files newest-first \u2014",
|
|
37354
|
+
" id, date, bytes, type, name. One page; the last",
|
|
37355
|
+
" line prints the command for the next one",
|
|
37356
|
+
" lotics file delete <file_id> Archive a stored file. Refused while a record,",
|
|
37357
|
+
" comment, knowledge doc, document template or",
|
|
37358
|
+
" voice session still references it \u2014 it names them",
|
|
37297
37359
|
" lotics file upload <file|dir...> Upload files (alias: lotics upload)",
|
|
37298
37360
|
" lotics file upload --stdin --as <name>",
|
|
37299
37361
|
" Upload bytes you already hold, piped on stdin",
|
|
@@ -37315,9 +37377,33 @@ var COMMANDS = [
|
|
|
37315
37377
|
function renderCommandsHelp() {
|
|
37316
37378
|
return COMMANDS.flatMap((group) => group.help).join("\n");
|
|
37317
37379
|
}
|
|
37380
|
+
function helpEntries(help) {
|
|
37381
|
+
const entries2 = [];
|
|
37382
|
+
for (const line of help) {
|
|
37383
|
+
if (line.startsWith(" lotics ") || entries2.length === 0) entries2.push([line]);
|
|
37384
|
+
else entries2[entries2.length - 1].push(line);
|
|
37385
|
+
}
|
|
37386
|
+
return entries2;
|
|
37387
|
+
}
|
|
37388
|
+
function commandHelp(path15) {
|
|
37389
|
+
const words = path15.filter((word) => word !== "");
|
|
37390
|
+
if (words.length === 0) return null;
|
|
37391
|
+
const group = COMMANDS.find(
|
|
37392
|
+
(g) => g.verbs.includes(words[0]) || (g.aliases ?? []).includes(words[0])
|
|
37393
|
+
);
|
|
37394
|
+
if (!group) return null;
|
|
37395
|
+
if (words.length === 1) return group.help.join("\n");
|
|
37396
|
+
const prefix = ` lotics ${words.join(" ")}`;
|
|
37397
|
+
const matched = helpEntries(group.help).filter((entry) => entry[0].startsWith(prefix));
|
|
37398
|
+
return (matched.length > 0 ? matched.flat() : group.help).join("\n");
|
|
37399
|
+
}
|
|
37318
37400
|
function knownVerbs() {
|
|
37319
37401
|
return new Set(COMMANDS.flatMap((group) => [...group.verbs, ...group.aliases ?? []]));
|
|
37320
37402
|
}
|
|
37403
|
+
function commandAliases(verb) {
|
|
37404
|
+
const group = COMMANDS.find((g) => g.verbs.includes(verb));
|
|
37405
|
+
return new Set(group?.aliases ?? []);
|
|
37406
|
+
}
|
|
37321
37407
|
|
|
37322
37408
|
// src/inputs.ts
|
|
37323
37409
|
function shellQuotingHint(raw, source) {
|
|
@@ -37336,7 +37422,10 @@ function readStdin() {
|
|
|
37336
37422
|
async function ingestJsonArgs(opts) {
|
|
37337
37423
|
let raw = opts.rawArg;
|
|
37338
37424
|
let source = "inline";
|
|
37339
|
-
if (raw
|
|
37425
|
+
if (raw === "-") {
|
|
37426
|
+
source = "stdin";
|
|
37427
|
+
raw = await opts.readStdin();
|
|
37428
|
+
} else if (raw && raw.startsWith("@")) {
|
|
37340
37429
|
source = "file";
|
|
37341
37430
|
const argsPath = raw.slice(1);
|
|
37342
37431
|
try {
|
|
@@ -37347,9 +37436,6 @@ async function ingestJsonArgs(opts) {
|
|
|
37347
37436
|
message: `Cannot read args file "${argsPath}": ${err instanceof Error ? err.message : String(err)}`
|
|
37348
37437
|
};
|
|
37349
37438
|
}
|
|
37350
|
-
} else if (!raw && !opts.stdinIsTTY) {
|
|
37351
|
-
source = "stdin";
|
|
37352
|
-
raw = await opts.readStdin();
|
|
37353
37439
|
}
|
|
37354
37440
|
if (!raw) return { kind: "ok", args: {} };
|
|
37355
37441
|
try {
|
|
@@ -38521,6 +38607,13 @@ function queryOutputNames(node) {
|
|
|
38521
38607
|
}
|
|
38522
38608
|
|
|
38523
38609
|
// ../shared/src/app_dts.ts
|
|
38610
|
+
function selectOptionValues(decl, optionsByFieldKey) {
|
|
38611
|
+
const inline = Array.isArray(decl.options) ? decl.options : null;
|
|
38612
|
+
const source = inline ?? (typeof decl.field === "string" ? optionsByFieldKey?.get(decl.field) : void 0) ?? [];
|
|
38613
|
+
return [...source].map(
|
|
38614
|
+
(o) => o !== null && typeof o === "object" && "value" in o && typeof o.value === "string" ? o.value : null
|
|
38615
|
+
).filter((v) => v !== null);
|
|
38616
|
+
}
|
|
38524
38617
|
var IDENTIFIER_REGEX = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
38525
38618
|
function isValidIdentifier(name) {
|
|
38526
38619
|
return IDENTIFIER_REGEX.test(name);
|
|
@@ -38531,7 +38624,7 @@ function inputsToType(inputs, opts) {
|
|
|
38531
38624
|
for (const [key, decl] of Object.entries(inputs)) {
|
|
38532
38625
|
if (decl === null || typeof decl !== "object") continue;
|
|
38533
38626
|
const d = decl;
|
|
38534
|
-
const tsType = inputDeclToTsType(d);
|
|
38627
|
+
const tsType = inputDeclToTsType(d, opts?.optionsByFieldKey);
|
|
38535
38628
|
const isOptional = d.required === false;
|
|
38536
38629
|
const fieldType = isOptional && nullableOptional ? `${tsType} | null` : tsType;
|
|
38537
38630
|
const optional2 = isOptional ? "?" : "";
|
|
@@ -38543,7 +38636,7 @@ function inputsToType(inputs, opts) {
|
|
|
38543
38636
|
${fields.join("\n")}
|
|
38544
38637
|
}`;
|
|
38545
38638
|
}
|
|
38546
|
-
function inputDeclToTsType(decl) {
|
|
38639
|
+
function inputDeclToTsType(decl, optionsByFieldKey) {
|
|
38547
38640
|
const type = decl.type;
|
|
38548
38641
|
switch (type) {
|
|
38549
38642
|
case "text":
|
|
@@ -38561,13 +38654,7 @@ function inputDeclToTsType(decl) {
|
|
|
38561
38654
|
return decl.multi === true ? `ReadonlyArray<${inner}>` : inner;
|
|
38562
38655
|
}
|
|
38563
38656
|
case "select": {
|
|
38564
|
-
const
|
|
38565
|
-
const literals = options.map((o) => {
|
|
38566
|
-
if (o !== null && typeof o === "object" && "value" in o && typeof o.value === "string") {
|
|
38567
|
-
return JSON.stringify(o.value);
|
|
38568
|
-
}
|
|
38569
|
-
return null;
|
|
38570
|
-
}).filter((v) => v !== null);
|
|
38657
|
+
const literals = selectOptionValues(decl, optionsByFieldKey).map((v) => JSON.stringify(v));
|
|
38571
38658
|
const inner = literals.length > 0 ? `${literals.join(" | ")} | (string & {})` : "string";
|
|
38572
38659
|
return decl.multi === true ? `ReadonlyArray<${inner}>` : inner;
|
|
38573
38660
|
}
|
|
@@ -38577,11 +38664,11 @@ function inputDeclToTsType(decl) {
|
|
|
38577
38664
|
return decl.multi === true ? "ReadonlyArray<string>" : "string";
|
|
38578
38665
|
case "object": {
|
|
38579
38666
|
const fields = decl.fields !== null && typeof decl.fields === "object" ? decl.fields : {};
|
|
38580
|
-
return inputsToType(fields);
|
|
38667
|
+
return inputsToType(fields, { optionsByFieldKey });
|
|
38581
38668
|
}
|
|
38582
38669
|
case "array": {
|
|
38583
38670
|
const items = decl.items !== null && typeof decl.items === "object" ? decl.items : null;
|
|
38584
|
-
return items ? `ReadonlyArray<${inputDeclToTsType(items)}>` : "ReadonlyArray<unknown>";
|
|
38671
|
+
return items ? `ReadonlyArray<${inputDeclToTsType(items, optionsByFieldKey)}>` : "ReadonlyArray<unknown>";
|
|
38585
38672
|
}
|
|
38586
38673
|
case "json":
|
|
38587
38674
|
return "unknown";
|
|
@@ -38589,19 +38676,20 @@ function inputDeclToTsType(decl) {
|
|
|
38589
38676
|
return "unknown";
|
|
38590
38677
|
}
|
|
38591
38678
|
}
|
|
38592
|
-
function objectFieldsToType(fields) {
|
|
38679
|
+
function objectFieldsToType(fields, optionsByFieldKey) {
|
|
38593
38680
|
const parts = [];
|
|
38594
38681
|
for (const [key, decl] of Object.entries(fields)) {
|
|
38595
38682
|
if (decl === null || typeof decl !== "object") continue;
|
|
38596
38683
|
const d = decl;
|
|
38597
|
-
const
|
|
38684
|
+
const isOptional = d.required === false;
|
|
38685
|
+
const tsType = outputDeclToTsType(d, optionsByFieldKey);
|
|
38598
38686
|
const fieldKey = isValidIdentifier(key) ? key : JSON.stringify(key);
|
|
38599
|
-
parts.push(`${fieldKey}${
|
|
38687
|
+
parts.push(`${fieldKey}${isOptional ? "?" : ""}: ${isOptional ? `${tsType} | null` : tsType}`);
|
|
38600
38688
|
}
|
|
38601
38689
|
if (parts.length === 0) return "Record<string, never>";
|
|
38602
38690
|
return `{ ${parts.join("; ")} }`;
|
|
38603
38691
|
}
|
|
38604
|
-
function outputDeclToTsType(decl) {
|
|
38692
|
+
function outputDeclToTsType(decl, optionsByFieldKey) {
|
|
38605
38693
|
const type = decl.type;
|
|
38606
38694
|
switch (type) {
|
|
38607
38695
|
case "text":
|
|
@@ -38616,20 +38704,17 @@ function outputDeclToTsType(decl) {
|
|
|
38616
38704
|
case "record_link":
|
|
38617
38705
|
return decl.multi === true ? "ReadonlyArray<string>" : "string";
|
|
38618
38706
|
case "select": {
|
|
38619
|
-
const
|
|
38620
|
-
const literals = options.map(
|
|
38621
|
-
(o) => o !== null && typeof o === "object" && "value" in o && typeof o.value === "string" ? JSON.stringify(o.value) : null
|
|
38622
|
-
).filter((v) => v !== null);
|
|
38707
|
+
const literals = selectOptionValues(decl, optionsByFieldKey).map((v) => JSON.stringify(v));
|
|
38623
38708
|
const inner = literals.length > 0 ? `${literals.join(" | ")} | (string & {})` : "string";
|
|
38624
38709
|
return decl.multi === true ? `ReadonlyArray<${inner}>` : inner;
|
|
38625
38710
|
}
|
|
38626
38711
|
case "object": {
|
|
38627
38712
|
const fields = decl.fields !== null && typeof decl.fields === "object" ? decl.fields : {};
|
|
38628
|
-
return objectFieldsToType(fields);
|
|
38713
|
+
return objectFieldsToType(fields, optionsByFieldKey);
|
|
38629
38714
|
}
|
|
38630
38715
|
case "array": {
|
|
38631
38716
|
const items = decl.items !== null && typeof decl.items === "object" ? decl.items : null;
|
|
38632
|
-
return items ? `ReadonlyArray<${outputDeclToTsType(items)}>` : "ReadonlyArray<unknown>";
|
|
38717
|
+
return items ? `ReadonlyArray<${outputDeclToTsType(items, optionsByFieldKey)}>` : "ReadonlyArray<unknown>";
|
|
38633
38718
|
}
|
|
38634
38719
|
case "json":
|
|
38635
38720
|
return "unknown";
|
|
@@ -38645,7 +38730,7 @@ var QUERIES_HEADER = `// Auto-generated by 'lotics app pull/dev/deploy'.
|
|
|
38645
38730
|
|
|
38646
38731
|
import "@lotics/app-sdk";
|
|
38647
38732
|
`;
|
|
38648
|
-
function generateAppQueriesDts(queries) {
|
|
38733
|
+
function generateAppQueriesDts(queries, optionsByFieldKey) {
|
|
38649
38734
|
const entries2 = Object.entries(queries ?? {});
|
|
38650
38735
|
if (entries2.length === 0) {
|
|
38651
38736
|
return `${QUERIES_HEADER}
|
|
@@ -38660,7 +38745,7 @@ declare module "@lotics/app-sdk" {
|
|
|
38660
38745
|
const lines = [];
|
|
38661
38746
|
const columnLines = [];
|
|
38662
38747
|
for (const [alias, declaration] of entries2) {
|
|
38663
|
-
const valueType = inputsToType(declaration.params ?? {});
|
|
38748
|
+
const valueType = inputsToType(declaration.params ?? {}, { optionsByFieldKey });
|
|
38664
38749
|
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
38665
38750
|
lines.push(` ${aliasKey}: ${valueType};`);
|
|
38666
38751
|
const parsed = declaration.ast === void 0 ? void 0 : queryNodeSchema.safeParse(declaration.ast);
|
|
@@ -38692,7 +38777,7 @@ var WORKFLOWS_HEADER = `// Auto-generated by 'lotics app pull/dev/deploy'.
|
|
|
38692
38777
|
|
|
38693
38778
|
import "@lotics/app-sdk";
|
|
38694
38779
|
`;
|
|
38695
|
-
function generateAppWorkflowsDts(workflows) {
|
|
38780
|
+
function generateAppWorkflowsDts(workflows, optionsByFieldKey) {
|
|
38696
38781
|
const entries2 = Object.entries(workflows ?? {});
|
|
38697
38782
|
if (entries2.length === 0) {
|
|
38698
38783
|
return `${WORKFLOWS_HEADER}
|
|
@@ -38707,11 +38792,13 @@ declare module "@lotics/app-sdk" {
|
|
|
38707
38792
|
const inputLines = [];
|
|
38708
38793
|
const resultLines = [];
|
|
38709
38794
|
for (const [alias, declaration] of entries2) {
|
|
38710
|
-
const valueType = declaration.inputs ? inputsToType(declaration.inputs, { nullableOptional: true }) : "Record<string, unknown>";
|
|
38795
|
+
const valueType = declaration.inputs ? inputsToType(declaration.inputs, { nullableOptional: true, optionsByFieldKey }) : "Record<string, unknown>";
|
|
38711
38796
|
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
38712
38797
|
inputLines.push(` ${aliasKey}: ${valueType};`);
|
|
38713
38798
|
if (declaration.outputs) {
|
|
38714
|
-
resultLines.push(
|
|
38799
|
+
resultLines.push(
|
|
38800
|
+
` ${aliasKey}: ${objectFieldsToType(declaration.outputs, optionsByFieldKey)};`
|
|
38801
|
+
);
|
|
38715
38802
|
}
|
|
38716
38803
|
}
|
|
38717
38804
|
const resultsBlock = resultLines.length > 0 ? `
|
|
@@ -38734,7 +38821,7 @@ var AGENTS_HEADER = `// Auto-generated by 'lotics app pull/dev/deploy'.
|
|
|
38734
38821
|
|
|
38735
38822
|
import "@lotics/app-sdk";
|
|
38736
38823
|
`;
|
|
38737
|
-
function generateAppAgentsDts(agents) {
|
|
38824
|
+
function generateAppAgentsDts(agents, optionsByFieldKey) {
|
|
38738
38825
|
const entries2 = Object.entries(agents ?? {});
|
|
38739
38826
|
if (entries2.length === 0) {
|
|
38740
38827
|
return `${AGENTS_HEADER}
|
|
@@ -38749,11 +38836,13 @@ declare module "@lotics/app-sdk" {
|
|
|
38749
38836
|
const inputLines = [];
|
|
38750
38837
|
const resultLines = [];
|
|
38751
38838
|
for (const [alias, declaration] of entries2) {
|
|
38752
|
-
const valueType = declaration.inputs ? inputsToType(declaration.inputs) : "Record<string, unknown>";
|
|
38839
|
+
const valueType = declaration.inputs ? inputsToType(declaration.inputs, { optionsByFieldKey }) : "Record<string, unknown>";
|
|
38753
38840
|
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
38754
38841
|
inputLines.push(` ${aliasKey}: ${valueType};`);
|
|
38755
38842
|
if (declaration.outputs) {
|
|
38756
|
-
resultLines.push(
|
|
38843
|
+
resultLines.push(
|
|
38844
|
+
` ${aliasKey}: ${objectFieldsToType(declaration.outputs, optionsByFieldKey)};`
|
|
38845
|
+
);
|
|
38757
38846
|
}
|
|
38758
38847
|
}
|
|
38759
38848
|
const resultsBlock = resultLines.length > 0 ? `
|
|
@@ -39074,6 +39163,20 @@ export type AppFields = typeof F;
|
|
|
39074
39163
|
export type AppOptions = typeof OPT;
|
|
39075
39164
|
`;
|
|
39076
39165
|
}
|
|
39166
|
+
function selectOptionsByFieldKey(tables) {
|
|
39167
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
39168
|
+
for (const table of tables) {
|
|
39169
|
+
for (const field of table.fields) {
|
|
39170
|
+
if (field.options && field.options.length > 0) {
|
|
39171
|
+
byKey.set(
|
|
39172
|
+
field.id,
|
|
39173
|
+
field.options.map((option) => ({ value: option.id }))
|
|
39174
|
+
);
|
|
39175
|
+
}
|
|
39176
|
+
}
|
|
39177
|
+
}
|
|
39178
|
+
return byKey;
|
|
39179
|
+
}
|
|
39077
39180
|
function codegenTableIds(queries, allowlist) {
|
|
39078
39181
|
const ids = new Set(allowlist);
|
|
39079
39182
|
for (const declaration of Object.values(queries)) {
|
|
@@ -41123,15 +41226,7 @@ async function dispatchRpc(client, body, opts) {
|
|
|
41123
41226
|
if (!p || typeof p.alias !== "string") {
|
|
41124
41227
|
throw new Error("query payload must include `alias`");
|
|
41125
41228
|
}
|
|
41126
|
-
return client.appQuery(body.app_id, {
|
|
41127
|
-
alias: p.alias,
|
|
41128
|
-
params: p.params,
|
|
41129
|
-
limit: p.limit,
|
|
41130
|
-
offset: p.offset,
|
|
41131
|
-
sort: p.sort,
|
|
41132
|
-
filter: p.filter,
|
|
41133
|
-
count: p.count
|
|
41134
|
-
});
|
|
41229
|
+
return client.appQuery(body.app_id, { ...p, alias: p.alias });
|
|
41135
41230
|
}
|
|
41136
41231
|
case "field_options": {
|
|
41137
41232
|
const p = body.payload;
|
|
@@ -41265,6 +41360,12 @@ function openAppTarget(payload) {
|
|
|
41265
41360
|
}
|
|
41266
41361
|
|
|
41267
41362
|
// src/dev/wrapper_page.ts
|
|
41363
|
+
function deepLinkLoc(pathname, search) {
|
|
41364
|
+
var explicit = new URLSearchParams(search).get("_loc");
|
|
41365
|
+
if (explicit) return explicit;
|
|
41366
|
+
if (pathname === "/" || pathname === "/index.html") return null;
|
|
41367
|
+
return pathname + search;
|
|
41368
|
+
}
|
|
41268
41369
|
function buildWrapperPage(args) {
|
|
41269
41370
|
const { app_name, app_id, workspace_id, vite_url, api_url, web_app_url } = args;
|
|
41270
41371
|
return `<!doctype html>
|
|
@@ -41322,7 +41423,8 @@ function buildWrapperPage(args) {
|
|
|
41322
41423
|
// server (a url param flowing into an iframe src is a redirect vector);
|
|
41323
41424
|
// anything else falls back to the app root.
|
|
41324
41425
|
const appSrc = new URL(VITE_URL);
|
|
41325
|
-
|
|
41426
|
+
var deepLinkLoc = (${deepLinkLoc.toString()});
|
|
41427
|
+
const savedLoc = deepLinkLoc(window.location.pathname, window.location.search);
|
|
41326
41428
|
if (savedLoc) {
|
|
41327
41429
|
try {
|
|
41328
41430
|
const resolved = new URL(savedLoc, VITE_ORIGIN);
|
|
@@ -41682,6 +41784,18 @@ var STOP_DEADLINE_MS = 2e3;
|
|
|
41682
41784
|
function serializeRpcResult(result) {
|
|
41683
41785
|
return JSON.stringify(result ?? null);
|
|
41684
41786
|
}
|
|
41787
|
+
function servesWrapperPage(method, pathname) {
|
|
41788
|
+
return method === "GET" && !pathname.startsWith("/_");
|
|
41789
|
+
}
|
|
41790
|
+
function viteChildArgs(viteEntry, vitePort, uiSrc) {
|
|
41791
|
+
return [
|
|
41792
|
+
viteEntry,
|
|
41793
|
+
"--port",
|
|
41794
|
+
String(vitePort),
|
|
41795
|
+
"--strictPort",
|
|
41796
|
+
...uiSrc ? ["--force"] : []
|
|
41797
|
+
];
|
|
41798
|
+
}
|
|
41685
41799
|
function nodeForChildProcess() {
|
|
41686
41800
|
return process.versions.bun === void 0 ? process.execPath : "node";
|
|
41687
41801
|
}
|
|
@@ -41705,7 +41819,7 @@ async function startDevServer(args) {
|
|
|
41705
41819
|
}
|
|
41706
41820
|
const viteChild = spawn(
|
|
41707
41821
|
nodeForChildProcess(),
|
|
41708
|
-
|
|
41822
|
+
viteChildArgs(viteEntry, vitePort, process.env.LOTICS_UI_SRC),
|
|
41709
41823
|
{
|
|
41710
41824
|
cwd: args.projectDir,
|
|
41711
41825
|
stdio: [process.stdin.isTTY ? "inherit" : "ignore", "inherit", "inherit"],
|
|
@@ -41754,7 +41868,7 @@ async function startDevServer(args) {
|
|
|
41754
41868
|
const server = http.createServer(async (req, res) => {
|
|
41755
41869
|
const url2 = req.url ?? "/";
|
|
41756
41870
|
const pathname = url2.split("?")[0];
|
|
41757
|
-
if (req.method
|
|
41871
|
+
if (servesWrapperPage(req.method, pathname)) {
|
|
41758
41872
|
res.writeHead(200, {
|
|
41759
41873
|
"Content-Type": "text/html; charset=utf-8",
|
|
41760
41874
|
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
@@ -43411,6 +43525,7 @@ __export(expression_string_exports, {
|
|
|
43411
43525
|
capitalize: () => capitalize,
|
|
43412
43526
|
contains: () => contains,
|
|
43413
43527
|
endsWith: () => endsWith,
|
|
43528
|
+
formatDecimal: () => formatDecimal,
|
|
43414
43529
|
formatNumber: () => formatNumber,
|
|
43415
43530
|
join: () => join3,
|
|
43416
43531
|
length: () => length,
|
|
@@ -43426,6 +43541,18 @@ __export(expression_string_exports, {
|
|
|
43426
43541
|
trim: () => trim,
|
|
43427
43542
|
upper: () => upper
|
|
43428
43543
|
});
|
|
43544
|
+
|
|
43545
|
+
// ../shared/src/expression_locale.ts
|
|
43546
|
+
function isUsableLocale(locale) {
|
|
43547
|
+
try {
|
|
43548
|
+
Intl.NumberFormat.supportedLocalesOf(locale);
|
|
43549
|
+
return true;
|
|
43550
|
+
} catch {
|
|
43551
|
+
return false;
|
|
43552
|
+
}
|
|
43553
|
+
}
|
|
43554
|
+
|
|
43555
|
+
// ../shared/src/expression_string.ts
|
|
43429
43556
|
function join3(arr, separator) {
|
|
43430
43557
|
if (arr == null) return "";
|
|
43431
43558
|
if (!Array.isArray(arr)) {
|
|
@@ -43658,10 +43785,29 @@ function formatNumber(value, decimals) {
|
|
|
43658
43785
|
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
43659
43786
|
throw new Error("formatNumber: expected number, got " + typeof value);
|
|
43660
43787
|
}
|
|
43788
|
+
assertDecimals(decimals, "formatNumber");
|
|
43789
|
+
return value.toFixed(decimals);
|
|
43790
|
+
}
|
|
43791
|
+
function formatDecimal(value, decimals, locale) {
|
|
43792
|
+
if (value == null) return "";
|
|
43793
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
43794
|
+
throw new Error("formatDecimal: expected number, got " + typeof value);
|
|
43795
|
+
}
|
|
43796
|
+
assertDecimals(decimals, "formatDecimal");
|
|
43797
|
+
if (typeof locale !== "string" || locale === "" || !isUsableLocale(locale)) {
|
|
43798
|
+
throw new Error(
|
|
43799
|
+
`formatDecimal(value, decimals, locale) requires a locale tag. Got ${JSON.stringify(locale)}. Example: formatDecimal(151000, 0, "vi-VN").`
|
|
43800
|
+
);
|
|
43801
|
+
}
|
|
43802
|
+
return new Intl.NumberFormat(locale, {
|
|
43803
|
+
minimumFractionDigits: decimals,
|
|
43804
|
+
maximumFractionDigits: decimals
|
|
43805
|
+
}).format(value);
|
|
43806
|
+
}
|
|
43807
|
+
function assertDecimals(decimals, funcName) {
|
|
43661
43808
|
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 100) {
|
|
43662
|
-
throw new Error(
|
|
43809
|
+
throw new Error(`${funcName}: decimals must be an integer between 0 and 100`);
|
|
43663
43810
|
}
|
|
43664
|
-
return value.toFixed(decimals);
|
|
43665
43811
|
}
|
|
43666
43812
|
|
|
43667
43813
|
// ../shared/src/expression_math.ts
|
|
@@ -44074,7 +44220,7 @@ function coalesce(...values2) {
|
|
|
44074
44220
|
for (const val of values2) {
|
|
44075
44221
|
if (val != null) return val;
|
|
44076
44222
|
}
|
|
44077
|
-
return
|
|
44223
|
+
return null;
|
|
44078
44224
|
}
|
|
44079
44225
|
function toNumber(val) {
|
|
44080
44226
|
if (val == null) return null;
|
|
@@ -44143,6 +44289,15 @@ function parseDateStringInTimezone(dateStr, timezone) {
|
|
|
44143
44289
|
const zonedDate = new TZDate(year, month, day, hour, minute, second, ms, timezone);
|
|
44144
44290
|
return new Date(zonedDate.getTime());
|
|
44145
44291
|
}
|
|
44292
|
+
function isDayOnly(date5) {
|
|
44293
|
+
return typeof date5 === "string" && /^\d{4}(?:-\d{2}(?:-\d{2})?)?$/.test(date5);
|
|
44294
|
+
}
|
|
44295
|
+
function compareCalendarDays(left, right, timezone) {
|
|
44296
|
+
if (timezone) {
|
|
44297
|
+
return differenceInCalendarDays(new TZDate(left, timezone), new TZDate(right, timezone));
|
|
44298
|
+
}
|
|
44299
|
+
return differenceInCalendarDays(left, right);
|
|
44300
|
+
}
|
|
44146
44301
|
function normalizeDate(date5, timezone) {
|
|
44147
44302
|
if (date5 instanceof Date) {
|
|
44148
44303
|
return date5;
|
|
@@ -44424,6 +44579,13 @@ function getDateExpressionFunctions(defaultTimezone) {
|
|
|
44424
44579
|
// ========================================================================
|
|
44425
44580
|
/**
|
|
44426
44581
|
* Check if dateLeft is before dateRight. Returns false if either is null.
|
|
44582
|
+
*
|
|
44583
|
+
* A DAY-precision operand (a `date` field, which holds no time) compares at
|
|
44584
|
+
* DAY granularity in the workspace timezone. Resolving the day to midnight
|
|
44585
|
+
* and comparing instants made `isBefore({due}, now())` answer by the
|
|
44586
|
+
* reader's offset: the deadline "had passed" from the first hour of its own
|
|
44587
|
+
* day, and a countdown rendered from the same cell disagreed with the flag
|
|
44588
|
+
* beside it. Two instants still compare as instants.
|
|
44427
44589
|
*/
|
|
44428
44590
|
isBefore: (dateLeft, dateRight, timezone) => {
|
|
44429
44591
|
if (dateLeft == null || dateLeft === "" || dateRight == null || dateRight === "") return false;
|
|
@@ -44433,10 +44595,14 @@ function getDateExpressionFunctions(defaultTimezone) {
|
|
|
44433
44595
|
const tz = getTimezone(timezone);
|
|
44434
44596
|
const left = normalizeDateWithValidation(dateLeft, tz, "isBefore");
|
|
44435
44597
|
const right = normalizeDateWithValidation(dateRight, tz, "isBefore");
|
|
44598
|
+
if (isDayOnly(dateLeft) || isDayOnly(dateRight)) {
|
|
44599
|
+
return compareCalendarDays(left, right, tz) < 0;
|
|
44600
|
+
}
|
|
44436
44601
|
return isBefore(left, right);
|
|
44437
44602
|
},
|
|
44438
44603
|
/**
|
|
44439
44604
|
* Check if dateLeft is after dateRight. Returns false if either is null.
|
|
44605
|
+
* Day-precision operands compare at day granularity — see isBefore.
|
|
44440
44606
|
*/
|
|
44441
44607
|
isAfter: (dateLeft, dateRight, timezone) => {
|
|
44442
44608
|
if (dateLeft == null || dateLeft === "" || dateRight == null || dateRight === "") return false;
|
|
@@ -44446,6 +44612,9 @@ function getDateExpressionFunctions(defaultTimezone) {
|
|
|
44446
44612
|
const tz = getTimezone(timezone);
|
|
44447
44613
|
const left = normalizeDateWithValidation(dateLeft, tz, "isAfter");
|
|
44448
44614
|
const right = normalizeDateWithValidation(dateRight, tz, "isAfter");
|
|
44615
|
+
if (isDayOnly(dateLeft) || isDayOnly(dateRight)) {
|
|
44616
|
+
return compareCalendarDays(left, right, tz) > 0;
|
|
44617
|
+
}
|
|
44449
44618
|
return isAfter(left, right);
|
|
44450
44619
|
},
|
|
44451
44620
|
/**
|
|
@@ -44486,6 +44655,9 @@ function getDateExpressionFunctions(defaultTimezone) {
|
|
|
44486
44655
|
},
|
|
44487
44656
|
/**
|
|
44488
44657
|
* Check if a date is within a range (inclusive). Returns false if any input is null.
|
|
44658
|
+
* It is the two comparisons above, so a day-precision operand anywhere in it
|
|
44659
|
+
* puts the whole check at day granularity — otherwise `isWithinRange(d, a, b)`
|
|
44660
|
+
* and `!isBefore(d, a) && !isAfter(d, b)` would answer differently.
|
|
44489
44661
|
*/
|
|
44490
44662
|
isWithinRange: (date5, startDate, endDate, timezone) => {
|
|
44491
44663
|
if (date5 == null || date5 === "" || startDate == null || startDate === "" || endDate == null || endDate === "") return false;
|
|
@@ -44497,6 +44669,9 @@ function getDateExpressionFunctions(defaultTimezone) {
|
|
|
44497
44669
|
const normalizedDate = normalizeDateWithValidation(date5, tz, "isWithinRange");
|
|
44498
44670
|
const normalizedStart = normalizeDateWithValidation(startDate, tz, "isWithinRange");
|
|
44499
44671
|
const normalizedEnd = normalizeDateWithValidation(endDate, tz, "isWithinRange");
|
|
44672
|
+
if (isDayOnly(date5) || isDayOnly(startDate) || isDayOnly(endDate)) {
|
|
44673
|
+
return compareCalendarDays(normalizedDate, normalizedStart, tz) >= 0 && compareCalendarDays(normalizedDate, normalizedEnd, tz) <= 0;
|
|
44674
|
+
}
|
|
44500
44675
|
return isWithinInterval(normalizedDate, {
|
|
44501
44676
|
start: normalizedStart,
|
|
44502
44677
|
end: normalizedEnd
|
|
@@ -46988,9 +47163,12 @@ function walkToolInvocation(call, scope, opts, allowWaits) {
|
|
|
46988
47163
|
const input = walkToolInputObject(arg, scope);
|
|
46989
47164
|
return { tool_name: name, input };
|
|
46990
47165
|
}
|
|
47166
|
+
var TOOL_INPUT_ARRAY_SPREAD = "Spread is not supported inside a tool input. Write concat(<array>, <array>) here, or bind the merged array first (const xs = [...a, ...b];) and pass xs.";
|
|
47167
|
+
var TOOL_INPUT_OBJECT_SPREAD = "Spread is not supported inside a tool input. Bind the merged object first (const x = { ...a, b: 1 };) and pass x.";
|
|
46991
47168
|
function walkToolInputObject(obj, scope) {
|
|
46992
47169
|
const out = {};
|
|
46993
47170
|
for (const p of obj.properties) {
|
|
47171
|
+
if (p.type === "SpreadElement") fail2(p, TOOL_INPUT_OBJECT_SPREAD, "spread_element");
|
|
46994
47172
|
if (p.type !== "ObjectProperty") fail2(p, "Tool kwargs must be plain key: value pairs.");
|
|
46995
47173
|
if (p.computed) fail2(p, "Computed property keys are not supported.", "computed_key");
|
|
46996
47174
|
const key = p.key.type === "StringLiteral" ? p.key.value : p.key.type === "Identifier" ? p.key.name : null;
|
|
@@ -47003,6 +47181,7 @@ function walkToolInputValue(node, scope) {
|
|
|
47003
47181
|
if (node.type === "ObjectExpression") {
|
|
47004
47182
|
const inner = {};
|
|
47005
47183
|
for (const p of node.properties) {
|
|
47184
|
+
if (p.type === "SpreadElement") fail2(p, TOOL_INPUT_OBJECT_SPREAD, "spread_element");
|
|
47006
47185
|
if (p.type !== "ObjectProperty") fail2(p, "Tool kwargs must be plain key: value pairs.");
|
|
47007
47186
|
if (p.computed) fail2(p, "Computed property keys are not supported.");
|
|
47008
47187
|
const key = p.key.type === "StringLiteral" ? p.key.value : p.key.type === "Identifier" ? p.key.name : null;
|
|
@@ -47015,7 +47194,7 @@ function walkToolInputValue(node, scope) {
|
|
|
47015
47194
|
const arr = [];
|
|
47016
47195
|
for (const elem of node.elements) {
|
|
47017
47196
|
if (elem === null) fail2(node, "Sparse arrays are not supported.", "sparse_array");
|
|
47018
|
-
if (elem.type === "SpreadElement") fail2(elem,
|
|
47197
|
+
if (elem.type === "SpreadElement") fail2(elem, TOOL_INPUT_ARRAY_SPREAD, "spread_element");
|
|
47019
47198
|
arr.push(walkToolInputValue(elem, scope));
|
|
47020
47199
|
}
|
|
47021
47200
|
return arr;
|
|
@@ -47525,6 +47704,7 @@ function generatePrefixedId(prefix) {
|
|
|
47525
47704
|
return `${prefix}_${nanoid3()}`;
|
|
47526
47705
|
}
|
|
47527
47706
|
var RECORD_ID_PATTERN = new RegExp(`^rec_[${ID_ALPHABET}]{${ID_LENGTH}}$`);
|
|
47707
|
+
var FILE_ID_PATTERN = new RegExp(`^fil_[${ID_ALPHABET}]{${ID_LENGTH}}$`);
|
|
47528
47708
|
function generateWorkspaceId() {
|
|
47529
47709
|
return generatePrefixedId("wsp");
|
|
47530
47710
|
}
|
|
@@ -47697,6 +47877,137 @@ async function requireLatestNpmVersion(packageName, { timeoutMs = 1e4 } = {}) {
|
|
|
47697
47877
|
return version2;
|
|
47698
47878
|
}
|
|
47699
47879
|
|
|
47880
|
+
// src/line_diff.ts
|
|
47881
|
+
function lineDiff(before, after) {
|
|
47882
|
+
const rows = before.length;
|
|
47883
|
+
const cols = after.length;
|
|
47884
|
+
const lcs = Array.from(
|
|
47885
|
+
{ length: rows + 1 },
|
|
47886
|
+
() => Array.from({ length: cols + 1 }, () => 0)
|
|
47887
|
+
);
|
|
47888
|
+
for (let i2 = rows - 1; i2 >= 0; i2--) {
|
|
47889
|
+
for (let j2 = cols - 1; j2 >= 0; j2--) {
|
|
47890
|
+
lcs[i2][j2] = before[i2] === after[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
|
|
47891
|
+
}
|
|
47892
|
+
}
|
|
47893
|
+
const out = [];
|
|
47894
|
+
let i = 0;
|
|
47895
|
+
let j = 0;
|
|
47896
|
+
while (i < rows && j < cols) {
|
|
47897
|
+
if (before[i] === after[j]) {
|
|
47898
|
+
out.push({ kind: "same", text: before[i] });
|
|
47899
|
+
i++;
|
|
47900
|
+
j++;
|
|
47901
|
+
} else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
47902
|
+
out.push({ kind: "removed", text: before[i] });
|
|
47903
|
+
i++;
|
|
47904
|
+
} else {
|
|
47905
|
+
out.push({ kind: "added", text: after[j] });
|
|
47906
|
+
j++;
|
|
47907
|
+
}
|
|
47908
|
+
}
|
|
47909
|
+
for (; i < rows; i++) out.push({ kind: "removed", text: before[i] });
|
|
47910
|
+
for (; j < cols; j++) out.push({ kind: "added", text: after[j] });
|
|
47911
|
+
return out;
|
|
47912
|
+
}
|
|
47913
|
+
var MARK = { same: " ", removed: "-", added: "+" };
|
|
47914
|
+
function renderLineDiff(before, after, opts = {}) {
|
|
47915
|
+
const context = opts.context ?? 3;
|
|
47916
|
+
const lines = lineDiff(before.split("\n"), after.split("\n"));
|
|
47917
|
+
const changed = lines.map((line) => line.kind !== "same");
|
|
47918
|
+
const keep = lines.map(
|
|
47919
|
+
(_, index) => changed.slice(Math.max(0, index - context), index + context + 1).some(Boolean)
|
|
47920
|
+
);
|
|
47921
|
+
const out = [];
|
|
47922
|
+
let elided = false;
|
|
47923
|
+
for (const [index, line] of lines.entries()) {
|
|
47924
|
+
if (!keep[index]) {
|
|
47925
|
+
elided = true;
|
|
47926
|
+
continue;
|
|
47927
|
+
}
|
|
47928
|
+
if (elided && out.length > 0) out.push(" \u2026");
|
|
47929
|
+
elided = false;
|
|
47930
|
+
out.push(`${MARK[line.kind]} ${line.text}`);
|
|
47931
|
+
}
|
|
47932
|
+
return out.join("\n");
|
|
47933
|
+
}
|
|
47934
|
+
|
|
47935
|
+
// src/vite_config_text.ts
|
|
47936
|
+
var VITE_CONFIG_NAMES = [
|
|
47937
|
+
"vite.config.ts",
|
|
47938
|
+
"vite.config.mts",
|
|
47939
|
+
"vite.config.cts",
|
|
47940
|
+
"vite.config.js",
|
|
47941
|
+
"vite.config.mjs",
|
|
47942
|
+
"vite.config.cjs"
|
|
47943
|
+
];
|
|
47944
|
+
function codeOnly(source) {
|
|
47945
|
+
let out = "";
|
|
47946
|
+
let i = 0;
|
|
47947
|
+
while (i < source.length) {
|
|
47948
|
+
const two = source.slice(i, i + 2);
|
|
47949
|
+
if (two === "//") {
|
|
47950
|
+
while (i < source.length && source[i] !== "\n") {
|
|
47951
|
+
out += " ";
|
|
47952
|
+
i++;
|
|
47953
|
+
}
|
|
47954
|
+
continue;
|
|
47955
|
+
}
|
|
47956
|
+
if (two === "/*") {
|
|
47957
|
+
while (i < source.length && source.slice(i, i + 2) !== "*/") {
|
|
47958
|
+
out += source[i] === "\n" ? "\n" : " ";
|
|
47959
|
+
i++;
|
|
47960
|
+
}
|
|
47961
|
+
out += " ";
|
|
47962
|
+
i += 2;
|
|
47963
|
+
continue;
|
|
47964
|
+
}
|
|
47965
|
+
const quote2 = source[i];
|
|
47966
|
+
if (quote2 === '"' || quote2 === "'" || quote2 === "`") {
|
|
47967
|
+
out += " ";
|
|
47968
|
+
i++;
|
|
47969
|
+
while (i < source.length && source[i] !== quote2) {
|
|
47970
|
+
if (source[i] === "\\") {
|
|
47971
|
+
out += " ";
|
|
47972
|
+
i += 2;
|
|
47973
|
+
continue;
|
|
47974
|
+
}
|
|
47975
|
+
out += source[i] === "\n" ? "\n" : " ";
|
|
47976
|
+
i++;
|
|
47977
|
+
}
|
|
47978
|
+
out += " ";
|
|
47979
|
+
i++;
|
|
47980
|
+
continue;
|
|
47981
|
+
}
|
|
47982
|
+
out += source[i];
|
|
47983
|
+
i++;
|
|
47984
|
+
}
|
|
47985
|
+
return out;
|
|
47986
|
+
}
|
|
47987
|
+
function objectBlocks(code, key) {
|
|
47988
|
+
const blocks = [];
|
|
47989
|
+
const literal2 = key.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
47990
|
+
for (const opener of code.matchAll(new RegExp(String.raw`\b${literal2}\s*:\s*\{`, "g"))) {
|
|
47991
|
+
let depth = 1;
|
|
47992
|
+
let i = opener.index + opener[0].length;
|
|
47993
|
+
const start = i;
|
|
47994
|
+
while (i < code.length && depth > 0) {
|
|
47995
|
+
if (code[i] === "{") depth++;
|
|
47996
|
+
else if (code[i] === "}") depth--;
|
|
47997
|
+
i++;
|
|
47998
|
+
}
|
|
47999
|
+
blocks.push(code.slice(start, i));
|
|
48000
|
+
}
|
|
48001
|
+
return blocks;
|
|
48002
|
+
}
|
|
48003
|
+
function declaredVitePort(config2) {
|
|
48004
|
+
for (const block of objectBlocks(codeOnly(config2), "server")) {
|
|
48005
|
+
const found = block.match(/(?:^|[{,;])\s*port\s*:\s*(\d+)\s*(?:[,}]|$)/);
|
|
48006
|
+
if (found) return Number(found[1]);
|
|
48007
|
+
}
|
|
48008
|
+
return null;
|
|
48009
|
+
}
|
|
48010
|
+
|
|
47700
48011
|
// ../shared/src/schemas/workflow_issues.ts
|
|
47701
48012
|
var issueSchema = external_exports.object({
|
|
47702
48013
|
source: external_exports.enum(["parse", "typecheck", "resolve", "lint", "structural"]),
|
|
@@ -48321,16 +48632,16 @@ function writeAppMeta(projectDir, meta3) {
|
|
|
48321
48632
|
pkg.lotics = { ...meta3, ...preserved };
|
|
48322
48633
|
fs11.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
48323
48634
|
}
|
|
48324
|
-
function
|
|
48635
|
+
function writeWorkflowDeclarationFields(projectDir, alias, fields) {
|
|
48325
48636
|
const pkgPath = path12.join(projectDir, "package.json");
|
|
48326
48637
|
const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
|
|
48327
48638
|
const declaration = pkg.lotics?.workflows?.[alias];
|
|
48328
48639
|
if (!declaration) {
|
|
48329
48640
|
throw new Error(
|
|
48330
|
-
`package.json#lotics.workflows.${alias} disappeared before the
|
|
48641
|
+
`package.json#lotics.workflows.${alias} disappeared before the post-set write-back.`
|
|
48331
48642
|
);
|
|
48332
48643
|
}
|
|
48333
|
-
declaration
|
|
48644
|
+
Object.assign(declaration, fields);
|
|
48334
48645
|
fs11.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
48335
48646
|
}
|
|
48336
48647
|
function workflowBodyDrift(projectDir, workflows, synced = {}) {
|
|
@@ -48440,13 +48751,22 @@ function writeAppDtsKitLinks(projectDir) {
|
|
|
48440
48751
|
writeDevLinkTsconfig(projectDir);
|
|
48441
48752
|
writeKitTypeAugmentation(projectDir);
|
|
48442
48753
|
}
|
|
48443
|
-
function writeAppDts(projectDir, manifest) {
|
|
48754
|
+
function writeAppDts(projectDir, manifest, optionsByFieldKey) {
|
|
48444
48755
|
const dotLotics = path12.join(projectDir, ".lotics");
|
|
48445
48756
|
fs11.mkdirSync(dotLotics, { recursive: true });
|
|
48446
48757
|
const written = [
|
|
48447
|
-
[
|
|
48448
|
-
|
|
48449
|
-
|
|
48758
|
+
[
|
|
48759
|
+
path12.join(dotLotics, "app_workflows.d.ts"),
|
|
48760
|
+
generateAppWorkflowsDts(manifest.workflows, optionsByFieldKey)
|
|
48761
|
+
],
|
|
48762
|
+
[
|
|
48763
|
+
path12.join(dotLotics, "app_queries.d.ts"),
|
|
48764
|
+
generateAppQueriesDts(manifest.queries, optionsByFieldKey)
|
|
48765
|
+
],
|
|
48766
|
+
[
|
|
48767
|
+
path12.join(dotLotics, "app_agents.d.ts"),
|
|
48768
|
+
generateAppAgentsDts(manifest.agents, optionsByFieldKey)
|
|
48769
|
+
]
|
|
48450
48770
|
];
|
|
48451
48771
|
for (const [file2, content] of written) fs11.writeFileSync(file2, content);
|
|
48452
48772
|
writeAppDtsKitLinks(projectDir);
|
|
@@ -48469,34 +48789,36 @@ function writeAppFields(projectDir, tables) {
|
|
|
48469
48789
|
fs11.writeFileSync(file2, generateAppFields(tables));
|
|
48470
48790
|
return file2;
|
|
48471
48791
|
}
|
|
48472
|
-
async function writeGeneratedAppFields(client, projectDir, app,
|
|
48792
|
+
async function writeGeneratedAppFields(client, projectDir, app, manifest) {
|
|
48793
|
+
const queries = manifest.queries ?? {};
|
|
48473
48794
|
if (client.viewAsMemberId) {
|
|
48474
48795
|
console.error(
|
|
48475
48796
|
`\u26A0 Skipped .lotics/app_fields.ts \u2014 running with --view-as ${client.viewAsMemberId}. The schema is read as that member, so any table they cannot see would be silently missing from F/OPT. Re-run without --view-as to regenerate it.`
|
|
48476
48797
|
);
|
|
48477
48798
|
return;
|
|
48478
48799
|
}
|
|
48800
|
+
let tables;
|
|
48479
48801
|
try {
|
|
48480
|
-
|
|
48481
|
-
|
|
48482
|
-
|
|
48483
|
-
|
|
48484
|
-
|
|
48485
|
-
|
|
48486
|
-
`\u26A0 Skipped .lotics/app_fields.ts \u2014 ${missing.length} table(s) the app queries could not be read: ${missing.join(", ")}.
|
|
48802
|
+
const tableIds = resolveCodegenTableIds(projectDir, queries);
|
|
48803
|
+
tables = await client.getWorkspaceSchema(tableIds);
|
|
48804
|
+
const missing = tableIds.filter((id) => !tables.some((t) => t.id === id));
|
|
48805
|
+
if (missing.length > 0) {
|
|
48806
|
+
console.error(
|
|
48807
|
+
`\u26A0 Skipped .lotics/app_fields.ts \u2014 ${missing.length} table(s) the app queries could not be read: ${missing.join(", ")}.
|
|
48487
48808
|
Writing anyway would bake a narrowed F/OPT map that compiles and then throws at runtime. Check this key's access to those tables.
|
|
48488
48809
|
An existing app_fields.ts is kept and still works; a project that has none yet will fail to BUILD until this resolves \u2014 which is the loud failure, not a new problem.`
|
|
48489
|
-
);
|
|
48490
|
-
return;
|
|
48491
|
-
}
|
|
48492
|
-
const fieldsPath = writeAppFields(projectDir, tables);
|
|
48493
|
-
note(
|
|
48494
|
-
`Wrote ${fieldsPath} (baked form \u2014 bespoke; ${tables.length} table${tables.length === 1 ? "" : "s"})`
|
|
48495
48810
|
);
|
|
48811
|
+
return;
|
|
48496
48812
|
}
|
|
48813
|
+
const fieldsPath = writeAppFields(projectDir, tables);
|
|
48814
|
+
note(
|
|
48815
|
+
`Wrote ${fieldsPath} (baked form \u2014 bespoke; ${tables.length} table${tables.length === 1 ? "" : "s"})`
|
|
48816
|
+
);
|
|
48497
48817
|
} catch (err) {
|
|
48498
48818
|
warnAppFieldsUnwritten(projectDir, err);
|
|
48819
|
+
return;
|
|
48499
48820
|
}
|
|
48821
|
+
writeAppDts(projectDir, manifest, selectOptionsByFieldKey(tables));
|
|
48500
48822
|
}
|
|
48501
48823
|
function warnAppFieldsUnwritten(projectDir, err) {
|
|
48502
48824
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -48540,6 +48862,13 @@ function installedKitVersion(projectDir, pkg) {
|
|
|
48540
48862
|
return null;
|
|
48541
48863
|
}
|
|
48542
48864
|
}
|
|
48865
|
+
function installedKitMajorAtLeast(projectDir, pkg, major) {
|
|
48866
|
+
const version2 = installedKitVersion(projectDir, pkg);
|
|
48867
|
+
if (version2 === null) return false;
|
|
48868
|
+
const installedMajor = parseInt(version2.split(".")[0], 10);
|
|
48869
|
+
if (Number.isNaN(installedMajor)) return false;
|
|
48870
|
+
return installedMajor >= major;
|
|
48871
|
+
}
|
|
48543
48872
|
async function publishedKitVersion(pkg) {
|
|
48544
48873
|
const controller = new AbortController();
|
|
48545
48874
|
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
@@ -48695,6 +49024,7 @@ async function appCodegen(args) {
|
|
|
48695
49024
|
for (const alias of pruneOrphanWorkflowGlobals(projectDir, declared)) {
|
|
48696
49025
|
console.error(`Removed stale workflow types for ${alias} \u2014 no longer in package.json#lotics.workflows`);
|
|
48697
49026
|
}
|
|
49027
|
+
reportUndeclaredCalledAliases(calledAppAliases(readAppSourceText(projectDir)), meta3);
|
|
48698
49028
|
for (const alias of orphanWorkflowBodies(projectDir, declared)) {
|
|
48699
49029
|
console.error(
|
|
48700
49030
|
`\u26A0 ${path12.join(WORKFLOWS_DIR, `${alias}.ts`)} is not declared in package.json#lotics.workflows \u2014 'workflow check' and 'workflow set' both skip it. Declare the alias or delete the file.`
|
|
@@ -48709,12 +49039,11 @@ async function appCodegen(args) {
|
|
|
48709
49039
|
let app = null;
|
|
48710
49040
|
try {
|
|
48711
49041
|
app = await args.client.getApp(meta3.app_id);
|
|
48712
|
-
await writeGeneratedAppFields(
|
|
48713
|
-
|
|
48714
|
-
|
|
48715
|
-
|
|
48716
|
-
|
|
48717
|
-
);
|
|
49042
|
+
await writeGeneratedAppFields(args.client, projectDir, { app_id: meta3.app_id }, {
|
|
49043
|
+
workflows: meta3.workflows,
|
|
49044
|
+
queries: meta3.queries,
|
|
49045
|
+
agents: meta3.agents
|
|
49046
|
+
});
|
|
48718
49047
|
} catch (err) {
|
|
48719
49048
|
warnAppFieldsUnwritten(projectDir, err);
|
|
48720
49049
|
}
|
|
@@ -48743,17 +49072,24 @@ async function refreshWorkflowGlobals(client, projectDir, app_id, workflows) {
|
|
|
48743
49072
|
alias,
|
|
48744
49073
|
toWorkflowDtsDeclaration(declaration)
|
|
48745
49074
|
);
|
|
48746
|
-
if (refreshed
|
|
49075
|
+
if (refreshed.kind === "refreshed") {
|
|
49076
|
+
console.error(`Refreshed workflow types for ${alias}`);
|
|
49077
|
+
continue;
|
|
49078
|
+
}
|
|
49079
|
+
console.error(
|
|
49080
|
+
`Wrote workflow types for ${alias} \u2014 ${refreshed.hasFile ? "its body file is empty" : `no body at ${path12.join(WORKFLOWS_DIR, `${alias}.ts`)}`}. Write the body there, then 'lotics app workflow check ${alias}'.`
|
|
49081
|
+
);
|
|
48747
49082
|
}
|
|
48748
49083
|
}
|
|
48749
49084
|
async function refreshWorkflowTypes(client, projectDir, app_id, alias, declaration) {
|
|
48750
49085
|
const file2 = workflowFilePath(projectDir, alias);
|
|
48751
|
-
|
|
48752
|
-
const body = stripWorkflowHeader(fs11.readFileSync(file2, "utf-8"));
|
|
48753
|
-
if (body.trim() === "") return false;
|
|
49086
|
+
const body = fs11.existsSync(file2) ? stripWorkflowHeader(fs11.readFileSync(file2, "utf-8")) : "";
|
|
48754
49087
|
const envelope2 = await fetchWorkflowGlobals(client, projectDir, app_id, alias, declaration);
|
|
49088
|
+
if (body.trim() === "") {
|
|
49089
|
+
return { kind: "types_only", hasFile: fs11.existsSync(file2) };
|
|
49090
|
+
}
|
|
48755
49091
|
writeWorkflowFile(projectDir, alias, body, envelope2);
|
|
48756
|
-
return
|
|
49092
|
+
return { kind: "refreshed" };
|
|
48757
49093
|
}
|
|
48758
49094
|
function readPriorStamp(projectDir) {
|
|
48759
49095
|
const pkgPath = path12.join(projectDir, "package.json");
|
|
@@ -48861,7 +49197,7 @@ async function appCreate(client, args) {
|
|
|
48861
49197
|
await materializeScaffold(targetPath, files);
|
|
48862
49198
|
if (plan !== null) {
|
|
48863
49199
|
writeAppDts(targetPath, { queries: plan.queries });
|
|
48864
|
-
await writeGeneratedAppFields(client, targetPath, { app_id: app.id }, plan.queries);
|
|
49200
|
+
await writeGeneratedAppFields(client, targetPath, { app_id: app.id }, { queries: plan.queries });
|
|
48865
49201
|
if (!fs11.existsSync(path12.join(targetPath, ".lotics", "app_fields.ts"))) {
|
|
48866
49202
|
throw new Error(
|
|
48867
49203
|
`.lotics/app_fields.ts could not be written (the warning above says why), and every generated screen reads it \u2014 resolve that, then \`cd ${path12.relative(process.cwd(), targetPath) || "."} && lotics app codegen && lotics app deploy\`.`
|
|
@@ -49080,12 +49416,11 @@ async function hydrateAppProject(client, targetPath, app, opts) {
|
|
|
49080
49416
|
const sha = declarationSha(declaration);
|
|
49081
49417
|
writeSynced(targetPath, "queries", alias, { content: sha, live: sha });
|
|
49082
49418
|
}
|
|
49083
|
-
await writeGeneratedAppFields(
|
|
49084
|
-
|
|
49085
|
-
|
|
49086
|
-
|
|
49087
|
-
|
|
49088
|
-
);
|
|
49419
|
+
await writeGeneratedAppFields(client, targetPath, { app_id: app.id }, {
|
|
49420
|
+
workflows: toManifestWorkflows(app.workflows ?? {}),
|
|
49421
|
+
queries: app.queries ?? {},
|
|
49422
|
+
agents: toManifestAgents(app.agents ?? {})
|
|
49423
|
+
});
|
|
49089
49424
|
const workflows = app.workflows ?? {};
|
|
49090
49425
|
if (Object.keys(workflows).length > 0) {
|
|
49091
49426
|
const { written, kept, unseenLive } = await writeWorkflowFiles(
|
|
@@ -49278,12 +49613,11 @@ async function appDeploy(client, args) {
|
|
|
49278
49613
|
warnIfProjectFootguns(projectDir, sourceText);
|
|
49279
49614
|
writeAppDts(projectDir, { workflows: meta3.workflows, queries: meta3.queries, agents: meta3.agents });
|
|
49280
49615
|
try {
|
|
49281
|
-
await writeGeneratedAppFields(
|
|
49282
|
-
|
|
49283
|
-
|
|
49284
|
-
|
|
49285
|
-
|
|
49286
|
-
);
|
|
49616
|
+
await writeGeneratedAppFields(client, projectDir, { app_id: meta3.app_id }, {
|
|
49617
|
+
workflows: meta3.workflows,
|
|
49618
|
+
queries: meta3.queries,
|
|
49619
|
+
agents: meta3.agents
|
|
49620
|
+
});
|
|
49287
49621
|
} catch (err) {
|
|
49288
49622
|
warn(
|
|
49289
49623
|
`\u26A0 Could not regenerate .lotics/app_fields.ts (${err.message}).
|
|
@@ -49357,7 +49691,8 @@ async function appDeploy(client, args) {
|
|
|
49357
49691
|
try {
|
|
49358
49692
|
const drifted = staleWorkflowGlobals(projectDir, meta3.workflows ?? {});
|
|
49359
49693
|
for (const { alias, declaration } of drifted) {
|
|
49360
|
-
|
|
49694
|
+
const refreshed = await refreshWorkflowTypes(client, projectDir, meta3.app_id, alias, declaration);
|
|
49695
|
+
if (refreshed.kind === "refreshed") {
|
|
49361
49696
|
note(`Refreshed workflow types for ${alias} \u2014 this deploy moved its declaration.`);
|
|
49362
49697
|
}
|
|
49363
49698
|
}
|
|
@@ -49377,10 +49712,16 @@ async function appDeploy(client, args) {
|
|
|
49377
49712
|
warnIfInertAgentTools(liveAfter);
|
|
49378
49713
|
}
|
|
49379
49714
|
if (liveAfter) {
|
|
49380
|
-
const
|
|
49715
|
+
const undeclared = undeclaredBindings({
|
|
49716
|
+
meta: meta3,
|
|
49717
|
+
synced: readSynced(projectDir),
|
|
49718
|
+
live: liveAfter
|
|
49719
|
+
});
|
|
49720
|
+
const lostCallSite = orphanedBindings(called, liveAfter, meta3.bundle_calls);
|
|
49721
|
+
const orphans = allOrphans(lostCallSite, undeclared);
|
|
49381
49722
|
let outstanding = orphans;
|
|
49382
49723
|
if (args.prune) {
|
|
49383
|
-
if (!meta3.bundle_calls) {
|
|
49724
|
+
if (!meta3.bundle_calls && nothingOrphaned(undeclared)) {
|
|
49384
49725
|
note(
|
|
49385
49726
|
"Nothing to prune yet: this project has no record of what the previous bundle called, so there is no way to tell a retired binding from an agent-facing one. This deploy records it; the next can compare."
|
|
49386
49727
|
);
|
|
@@ -49390,12 +49731,14 @@ async function appDeploy(client, args) {
|
|
|
49390
49731
|
projectDir,
|
|
49391
49732
|
{ app_id: meta3.app_id },
|
|
49392
49733
|
orphans,
|
|
49393
|
-
|
|
49734
|
+
undeclared,
|
|
49735
|
+
called.dynamic,
|
|
49736
|
+
args.pruneInvoked ?? []
|
|
49394
49737
|
);
|
|
49395
49738
|
} else {
|
|
49396
|
-
warnAboutOrphanedBindings(called, liveAfter, meta3.bundle_calls);
|
|
49739
|
+
warnAboutOrphanedBindings(called, liveAfter, meta3.bundle_calls, undeclared);
|
|
49397
49740
|
}
|
|
49398
|
-
const settled = outstanding
|
|
49741
|
+
const settled = nothingOrphaned(bothOf(outstanding, lostCallSite));
|
|
49399
49742
|
if (settled) {
|
|
49400
49743
|
writeAppMeta(projectDir, {
|
|
49401
49744
|
...readAppMeta(projectDir),
|
|
@@ -49463,8 +49806,14 @@ async function appCheck(client, args = {}) {
|
|
|
49463
49806
|
writeAppDts(projectDir, { workflows: meta3.workflows, queries: meta3.queries, agents: meta3.agents });
|
|
49464
49807
|
const sourceText = readAppSourceText(projectDir);
|
|
49465
49808
|
const called = calledAppAliases(sourceText);
|
|
49809
|
+
const undeclaredCalls = reportUndeclaredCalledAliases(called, meta3);
|
|
49466
49810
|
warnAboutDevLink(projectDir, "deploy");
|
|
49467
|
-
warnAboutOrphanedBindings(
|
|
49811
|
+
warnAboutOrphanedBindings(
|
|
49812
|
+
called,
|
|
49813
|
+
app,
|
|
49814
|
+
meta3.bundle_calls,
|
|
49815
|
+
undeclaredBindings({ meta: meta3, synced: readSynced(projectDir), live: app })
|
|
49816
|
+
);
|
|
49468
49817
|
const pending = pendingBindings({
|
|
49469
49818
|
projectDir,
|
|
49470
49819
|
meta: meta3,
|
|
@@ -49480,6 +49829,7 @@ async function appCheck(client, args = {}) {
|
|
|
49480
49829
|
warnIfProjectFootguns(projectDir, sourceText);
|
|
49481
49830
|
warnIfUnbranded(app);
|
|
49482
49831
|
warnIfUndescribed(app);
|
|
49832
|
+
await warnIfWorkflowsUndescribed(client, meta3);
|
|
49483
49833
|
const unportable = scanProjectForIds(projectDir);
|
|
49484
49834
|
reportPortabilityIds(unportable);
|
|
49485
49835
|
const bodiesFailing = await countFailingWorkflowBodies(client, projectDir, meta3);
|
|
@@ -49506,13 +49856,43 @@ async function appCheck(client, args = {}) {
|
|
|
49506
49856
|
});
|
|
49507
49857
|
console.error(formatScreenFindings(report));
|
|
49508
49858
|
}
|
|
49509
|
-
if (!nothingPending(pending) || unportable.length > 0 || bodiesFailing > 0 || typesFailing || screenFindings > 0) {
|
|
49859
|
+
if (!nothingPending(pending) || unportable.length > 0 || undeclaredCalls > 0 || bodiesFailing > 0 || typesFailing || screenFindings > 0) {
|
|
49510
49860
|
process.exit(1);
|
|
49511
49861
|
}
|
|
49512
49862
|
console.error(
|
|
49513
49863
|
"Checked the app's types, bindings, capabilities, agent schemas, workflow bodies and id portability" + (args.screens === true ? ", and every screen at 1280 and 375" : "") + " \u2014 nothing blocking."
|
|
49514
49864
|
);
|
|
49515
49865
|
}
|
|
49866
|
+
var capabilityAliasesSchema = zod_default.object({
|
|
49867
|
+
aliases: zod_default.array(
|
|
49868
|
+
zod_default.object({
|
|
49869
|
+
alias: zod_default.string(),
|
|
49870
|
+
kind: zod_default.string(),
|
|
49871
|
+
description: zod_default.string().nullish()
|
|
49872
|
+
})
|
|
49873
|
+
).optional()
|
|
49874
|
+
});
|
|
49875
|
+
async function warnIfWorkflowsUndescribed(client, meta3) {
|
|
49876
|
+
let res;
|
|
49877
|
+
try {
|
|
49878
|
+
res = await client.getAppCapabilities(meta3.app_id);
|
|
49879
|
+
} catch {
|
|
49880
|
+
return;
|
|
49881
|
+
}
|
|
49882
|
+
if (res.error) return;
|
|
49883
|
+
const parsed = capabilityAliasesSchema.safeParse(res.result);
|
|
49884
|
+
if (!parsed.success || parsed.data.aliases === void 0) return;
|
|
49885
|
+
const workflows = parsed.data.aliases.filter((entry) => entry.kind === "workflow");
|
|
49886
|
+
const undescribed = workflows.filter((entry) => entry.description == null || entry.description === "").map((entry) => entry.alias);
|
|
49887
|
+
if (undescribed.length === 0) return;
|
|
49888
|
+
console.error(
|
|
49889
|
+
`
|
|
49890
|
+
\u26A0 ${undescribed.length} of ${workflows.length} workflow${workflows.length === 1 ? "" : "s"} are offered with no description: ${undescribed.join(", ")}.
|
|
49891
|
+
That text is what chat and MCP read to choose between aliases; without it they choose by
|
|
49892
|
+
the alias. Write one line per workflow in
|
|
49893
|
+
package.json#lotics.workflows.<alias>.description, then 'lotics app workflow set <alias>'.`
|
|
49894
|
+
);
|
|
49895
|
+
}
|
|
49516
49896
|
async function countFailingWorkflowBodies(client, projectDir, meta3) {
|
|
49517
49897
|
const bound = Object.keys(meta3.workflows ?? {});
|
|
49518
49898
|
const typed = bound.filter((alias) => fs11.existsSync(workflowGlobalsPath(projectDir, alias)));
|
|
@@ -49532,8 +49912,29 @@ async function countFailingWorkflowBodies(client, projectDir, meta3) {
|
|
|
49532
49912
|
}
|
|
49533
49913
|
const results = checkWorkflowBodies(await loadProjectTypescript(projectDir), toCheck);
|
|
49534
49914
|
printWorkflowCheckResults(projectDir, results);
|
|
49915
|
+
reportStoredBodiesOwedMigration(
|
|
49916
|
+
projectDir,
|
|
49917
|
+
results.filter((r) => r.issues.length > 0).map((r) => r.alias)
|
|
49918
|
+
);
|
|
49535
49919
|
return results.filter((r) => r.issues.length > 0).length;
|
|
49536
49920
|
}
|
|
49921
|
+
function reportStoredBodiesOwedMigration(projectDir, failing) {
|
|
49922
|
+
const baselines = readSynced(projectDir).workflows;
|
|
49923
|
+
const unedited = failing.filter((alias) => {
|
|
49924
|
+
const baseline = baselines[alias]?.content;
|
|
49925
|
+
if (baseline === void 0) return false;
|
|
49926
|
+
const file2 = workflowFilePath(projectDir, alias);
|
|
49927
|
+
if (!fs11.existsSync(file2)) return false;
|
|
49928
|
+
return contentSha(stripWorkflowHeader(fs11.readFileSync(file2, "utf-8"))) === baseline;
|
|
49929
|
+
});
|
|
49930
|
+
if (unedited.length === 0) return;
|
|
49931
|
+
console.error(
|
|
49932
|
+
`
|
|
49933
|
+
\u26A0 ${unedited.length} of those bod${unedited.length === 1 ? "y is" : "ies are"} unchanged since this checkout pushed or pulled ${unedited.length === 1 ? "it" : "them"} \u2014 ${unedited.join(", ")}. That is the body the server is RUNNING, so this is a migration it is owed, not a stale tree.
|
|
49934
|
+
The usual cause is a grammar move: a link field reads as an id array, so drop '.id' or descend with linked(\u2026).
|
|
49935
|
+
Edit each body, then 'lotics app workflow set <alias>'. Until then the stored body keeps running as it always has.`
|
|
49936
|
+
);
|
|
49937
|
+
}
|
|
49537
49938
|
async function pushPendingBindings(client, projectDir, pending) {
|
|
49538
49939
|
const plan = [
|
|
49539
49940
|
...pending.queries.map((alias) => `query ${alias}`),
|
|
@@ -49662,36 +50063,64 @@ function orphanedBindings(called, live, previouslyCalled) {
|
|
|
49662
50063
|
referenced
|
|
49663
50064
|
);
|
|
49664
50065
|
}
|
|
49665
|
-
function
|
|
49666
|
-
const
|
|
50066
|
+
function undeclaredBindings(args) {
|
|
50067
|
+
const { meta: meta3, synced, live } = args;
|
|
50068
|
+
const removed = (kind, declared, bound) => Object.keys(bound ?? {}).filter((alias) => synced[kind][alias] !== void 0 && (declared ?? {})[alias] === void 0).sort();
|
|
50069
|
+
return {
|
|
50070
|
+
queries: removed("queries", meta3.queries, live.queries),
|
|
50071
|
+
workflows: removed("workflows", meta3.workflows, live.workflows),
|
|
50072
|
+
agents: removed("agents", meta3.agents, live.agents)
|
|
50073
|
+
};
|
|
50074
|
+
}
|
|
50075
|
+
function nothingOrphaned(orphans) {
|
|
50076
|
+
return orphans.queries.length === 0 && orphans.workflows.length === 0 && orphans.agents.length === 0;
|
|
50077
|
+
}
|
|
50078
|
+
function allOrphans(lostCallSite, undeclared) {
|
|
50079
|
+
const merge3 = (a, b) => [.../* @__PURE__ */ new Set([...a, ...b])].sort();
|
|
50080
|
+
return {
|
|
50081
|
+
queries: merge3(lostCallSite.queries, undeclared.queries),
|
|
50082
|
+
workflows: merge3(lostCallSite.workflows, undeclared.workflows),
|
|
50083
|
+
agents: merge3(lostCallSite.agents, undeclared.agents)
|
|
50084
|
+
};
|
|
50085
|
+
}
|
|
50086
|
+
function bothOf(a, b) {
|
|
50087
|
+
const both = (x, y) => x.filter((alias) => y.includes(alias));
|
|
50088
|
+
return {
|
|
50089
|
+
queries: both(a.queries, b.queries),
|
|
50090
|
+
workflows: both(a.workflows, b.workflows),
|
|
50091
|
+
agents: both(a.agents, b.agents)
|
|
50092
|
+
};
|
|
50093
|
+
}
|
|
50094
|
+
function warnAboutOrphanedBindings(called, live, previouslyCalled, undeclared) {
|
|
50095
|
+
const lostCallSite = orphanedBindings(called, live, previouslyCalled);
|
|
50096
|
+
const label = (kind, alias, undeclaredOfKind) => ` \u2022 ${kind} ${alias}${undeclaredOfKind.includes(alias) ? " (no longer declared in package.json)" : ""}`;
|
|
50097
|
+
const merged = allOrphans(lostCallSite, undeclared);
|
|
49667
50098
|
const lines = [
|
|
49668
|
-
...
|
|
49669
|
-
...
|
|
49670
|
-
...
|
|
50099
|
+
...merged.queries.map((a) => label("query", a, undeclared.queries)),
|
|
50100
|
+
...merged.workflows.map((a) => label("workflow", a, undeclared.workflows)),
|
|
50101
|
+
...merged.agents.map((a) => label("agent", a, undeclared.agents))
|
|
49671
50102
|
];
|
|
49672
50103
|
if (lines.length === 0) return 0;
|
|
50104
|
+
const dynamicCaveat = called.dynamic.length > 0 && !nothingOrphaned(lostCallSite) ? `
|
|
50105
|
+
|
|
50106
|
+
This app computes an alias at runtime, so one of the bindings above still
|
|
50107
|
+
declared in package.json may be called after all \u2014 \`--prune\` leaves those in
|
|
50108
|
+
place. Make those call sites literal to prune them.` + (nothingOrphaned(undeclared) ? `` : ` The ones marked "no longer
|
|
50109
|
+
declared" are unbound anyway.`) : ``;
|
|
49673
50110
|
console.error(
|
|
49674
50111
|
`
|
|
49675
|
-
\u26A0 ${lines.length} binding(s) still live that this
|
|
49676
|
-
` + lines.join("\n") +
|
|
49677
|
-
|
|
49678
|
-
This app computes an alias at runtime, so one of these may be called after
|
|
49679
|
-
all \u2014 and \`--prune\` therefore leaves them ALL in place. Make those call
|
|
49680
|
-
sites literal to prune them.` : `
|
|
50112
|
+
\u26A0 ${lines.length} binding(s) still live that this project has retired:
|
|
50113
|
+
` + lines.join("\n") + `
|
|
49681
50114
|
|
|
49682
|
-
Removing the call site does not unbind it, so it keeps
|
|
49683
|
-
stays published to chat and to MCP, which reach an alias
|
|
49684
|
-
site \u2014 so an agent can still invoke what you meant to retire.
|
|
49685
|
-
To unbind: lotics app deploy --prune`
|
|
50115
|
+
Removing the call site or the declaration does not unbind it, so it keeps
|
|
50116
|
+
serving. It also stays published to chat and to MCP, which reach an alias
|
|
50117
|
+
with no call site \u2014 so an agent can still invoke what you meant to retire.
|
|
50118
|
+
To unbind: lotics app deploy --prune` + dynamicCaveat
|
|
49686
50119
|
);
|
|
49687
50120
|
return lines.length;
|
|
49688
50121
|
}
|
|
49689
|
-
async function pruneOrphanedBindings(client, projectDir, app, orphans, dynamic) {
|
|
49690
|
-
const stillBound = {
|
|
49691
|
-
queries: [],
|
|
49692
|
-
workflows: [],
|
|
49693
|
-
agents: []
|
|
49694
|
-
};
|
|
50122
|
+
async function pruneOrphanedBindings(client, projectDir, app, orphans, undeclared, dynamic, pruneInvoked) {
|
|
50123
|
+
const stillBound = { queries: [], workflows: [], agents: [] };
|
|
49695
50124
|
const targets = [
|
|
49696
50125
|
...orphans.queries.map((alias) => ({
|
|
49697
50126
|
tool: "remove_app_query",
|
|
@@ -49716,22 +50145,45 @@ async function pruneOrphanedBindings(client, projectDir, app, orphans, dynamic)
|
|
|
49716
50145
|
}))
|
|
49717
50146
|
];
|
|
49718
50147
|
if (targets.length === 0) return stillBound;
|
|
49719
|
-
|
|
50148
|
+
const deferred = dynamic.length > 0 ? targets.filter((t) => !undeclared[t.kind].includes(t.alias)) : [];
|
|
50149
|
+
if (deferred.length > 0) {
|
|
49720
50150
|
console.error(
|
|
49721
50151
|
`
|
|
49722
|
-
\u26A0 Left ${
|
|
49723
|
-
` +
|
|
50152
|
+
\u26A0 Left ${deferred.length} unused binding(s) in place: this app calls ${dynamic.join(" / ")} with an alias it computes at runtime, and a static scan cannot tell which binding that reaches \u2014 so any of these may be live:
|
|
50153
|
+
` + deferred.map((t) => ` \u2022 ${t.label}`).join("\n") + `
|
|
49724
50154
|
Make those call sites literal and the next deploy will clean them up, or remove the ones you know are dead by hand.`
|
|
49725
50155
|
);
|
|
49726
|
-
for (const t of
|
|
49727
|
-
return stillBound;
|
|
50156
|
+
for (const t of deferred) stillBound[t.kind].push(t.alias);
|
|
49728
50157
|
}
|
|
50158
|
+
const prunable = targets.filter((t) => !deferred.includes(t));
|
|
50159
|
+
if (prunable.length === 0) return stillBound;
|
|
49729
50160
|
const tablesBefore = new Set(resolveCodegenTableIds(projectDir, readAppMeta(projectDir).queries ?? {}));
|
|
50161
|
+
const notTargeted = pruneInvoked.filter(
|
|
50162
|
+
(alias) => !prunable.some((t) => t.kind === "workflows" && t.alias === alias)
|
|
50163
|
+
);
|
|
50164
|
+
if (notTargeted.length > 0) {
|
|
50165
|
+
console.error(
|
|
50166
|
+
` \u26A0 --prune-invoked named ${notTargeted.map((a) => `"${a}"`).join(", ")}, which this prune is not removing. The flag applies to a workflow alias this prune is unbinding.`
|
|
50167
|
+
);
|
|
50168
|
+
}
|
|
49730
50169
|
let removedLocally = false;
|
|
49731
|
-
for (const target of
|
|
49732
|
-
const
|
|
50170
|
+
for (const target of prunable) {
|
|
50171
|
+
const overrideInvocationGuard = target.kind === "workflows" && pruneInvoked.includes(target.alias);
|
|
50172
|
+
const res = await client.execute(target.tool, {
|
|
50173
|
+
app_id: app.app_id,
|
|
50174
|
+
alias: target.alias,
|
|
50175
|
+
// Sent only for the aliases the operator named, so the default stays the
|
|
50176
|
+
// guard: a run recorded against an alias means something outside the
|
|
50177
|
+
// bundle reaches it.
|
|
50178
|
+
...overrideInvocationGuard ? { even_if_invoked: true } : {}
|
|
50179
|
+
});
|
|
49733
50180
|
if (res.error) {
|
|
49734
50181
|
console.error(` \u2717 could not unbind ${target.label} \u2014 ${res.error}`);
|
|
50182
|
+
if (target.kind === "workflows" && !overrideInvocationGuard) {
|
|
50183
|
+
console.error(
|
|
50184
|
+
` If nothing outside this bundle calls it: lotics app deploy --prune --prune-invoked ${target.alias} -m "<message>"`
|
|
50185
|
+
);
|
|
50186
|
+
}
|
|
49735
50187
|
stillBound[target.kind].push(target.alias);
|
|
49736
50188
|
continue;
|
|
49737
50189
|
}
|
|
@@ -49774,7 +50226,11 @@ async function pruneOrphanedBindings(client, projectDir, app, orphans, dynamic)
|
|
|
49774
50226
|
` \u26A0 .lotics/app_fields.ts no longer covers ${dropped.join(", ")} \u2014 ${dropped.length === 1 ? "that table was" : "those tables were"} named only by the pruned quer${orphans.queries.length === 1 ? "y" : "ies"}. If your source still uses F/OPT for ${dropped.length === 1 ? "it" : "them"}, add ${dropped.map((id) => `"${id}"`).join(", ")} to package.json#lotics.codegen.tables and re-run lotics app codegen.`
|
|
49775
50227
|
);
|
|
49776
50228
|
}
|
|
49777
|
-
await writeGeneratedAppFields(client, projectDir, app,
|
|
50229
|
+
await writeGeneratedAppFields(client, projectDir, app, {
|
|
50230
|
+
workflows: meta3.workflows,
|
|
50231
|
+
queries: meta3.queries,
|
|
50232
|
+
agents: meta3.agents
|
|
50233
|
+
});
|
|
49778
50234
|
}
|
|
49779
50235
|
} catch (err) {
|
|
49780
50236
|
console.error(
|
|
@@ -49880,72 +50336,10 @@ function warnIfUndeclaredCapabilities(sourceText, capabilities) {
|
|
|
49880
50336
|
Add to package.json#lotics.capabilities: ${block}`
|
|
49881
50337
|
);
|
|
49882
50338
|
}
|
|
49883
|
-
var VITE_CONFIG_NAMES = [
|
|
49884
|
-
"vite.config.ts",
|
|
49885
|
-
"vite.config.mts",
|
|
49886
|
-
"vite.config.cts",
|
|
49887
|
-
"vite.config.js",
|
|
49888
|
-
"vite.config.mjs",
|
|
49889
|
-
"vite.config.cjs"
|
|
49890
|
-
];
|
|
49891
|
-
function codeOnly(source) {
|
|
49892
|
-
let out = "";
|
|
49893
|
-
let i = 0;
|
|
49894
|
-
while (i < source.length) {
|
|
49895
|
-
const two = source.slice(i, i + 2);
|
|
49896
|
-
if (two === "//") {
|
|
49897
|
-
while (i < source.length && source[i] !== "\n") {
|
|
49898
|
-
out += " ";
|
|
49899
|
-
i++;
|
|
49900
|
-
}
|
|
49901
|
-
continue;
|
|
49902
|
-
}
|
|
49903
|
-
if (two === "/*") {
|
|
49904
|
-
while (i < source.length && source.slice(i, i + 2) !== "*/") {
|
|
49905
|
-
out += source[i] === "\n" ? "\n" : " ";
|
|
49906
|
-
i++;
|
|
49907
|
-
}
|
|
49908
|
-
out += " ";
|
|
49909
|
-
i += 2;
|
|
49910
|
-
continue;
|
|
49911
|
-
}
|
|
49912
|
-
const quote2 = source[i];
|
|
49913
|
-
if (quote2 === '"' || quote2 === "'" || quote2 === "`") {
|
|
49914
|
-
out += " ";
|
|
49915
|
-
i++;
|
|
49916
|
-
while (i < source.length && source[i] !== quote2) {
|
|
49917
|
-
if (source[i] === "\\") {
|
|
49918
|
-
out += " ";
|
|
49919
|
-
i += 2;
|
|
49920
|
-
continue;
|
|
49921
|
-
}
|
|
49922
|
-
out += source[i] === "\n" ? "\n" : " ";
|
|
49923
|
-
i++;
|
|
49924
|
-
}
|
|
49925
|
-
out += " ";
|
|
49926
|
-
i++;
|
|
49927
|
-
continue;
|
|
49928
|
-
}
|
|
49929
|
-
out += source[i];
|
|
49930
|
-
i++;
|
|
49931
|
-
}
|
|
49932
|
-
return out;
|
|
49933
|
-
}
|
|
49934
50339
|
function defineBlockKeys(config2) {
|
|
49935
|
-
const code = codeOnly(config2);
|
|
49936
50340
|
const keys2 = /* @__PURE__ */ new Set();
|
|
49937
|
-
for (const
|
|
49938
|
-
|
|
49939
|
-
let i = opener.index + opener[0].length;
|
|
49940
|
-
const start = i;
|
|
49941
|
-
while (i < code.length && depth > 0) {
|
|
49942
|
-
if (code[i] === "{") depth++;
|
|
49943
|
-
else if (code[i] === "}") depth--;
|
|
49944
|
-
i++;
|
|
49945
|
-
}
|
|
49946
|
-
for (const key of code.slice(start, i).matchAll(/(?:^|[{,])\s*([A-Za-z_$][\w$]*)\s*:/g)) {
|
|
49947
|
-
keys2.add(key[1]);
|
|
49948
|
-
}
|
|
50341
|
+
for (const block of objectBlocks(codeOnly(config2), "define")) {
|
|
50342
|
+
for (const key of block.matchAll(/(?:^|[{,])\s*([A-Za-z_$][\w$]*)\s*:/g)) keys2.add(key[1]);
|
|
49949
50343
|
}
|
|
49950
50344
|
return keys2;
|
|
49951
50345
|
}
|
|
@@ -49995,6 +50389,22 @@ function warnIfProjectFootguns(projectDir, sourceText) {
|
|
|
49995
50389
|
);
|
|
49996
50390
|
}
|
|
49997
50391
|
}
|
|
50392
|
+
if (configName !== void 0 && installedKitMajorAtLeast(projectDir, "@lotics/ui", 48)) {
|
|
50393
|
+
const config2 = fs11.readFileSync(path12.join(projectDir, configName), "utf-8");
|
|
50394
|
+
const handWritten = objectBlocks(codeOnly(config2), "optimizeDeps").some(
|
|
50395
|
+
(block) => /(?:^|[{,;])\s*include\s*:/.test(block)
|
|
50396
|
+
);
|
|
50397
|
+
if (handWritten) {
|
|
50398
|
+
lines.push(
|
|
50399
|
+
[
|
|
50400
|
+
` \u2022 ${configName} sets optimizeDeps.include \u2014 @lotics/ui 48 ships built ESM, so Vite`,
|
|
50401
|
+
" pre-bundles it unaided. The list cannot help and a stale entry still fails the",
|
|
50402
|
+
" dev boot ('Failed to resolve dependency: \u2026, present in optimizeDeps.include').",
|
|
50403
|
+
" delete the optimizeDeps block (and any loticsOptimizeDeps import \u2014 48 has none)"
|
|
50404
|
+
].join("\n")
|
|
50405
|
+
);
|
|
50406
|
+
}
|
|
50407
|
+
}
|
|
49998
50408
|
if (/\bwindow\.open\s*\(/.test(sourceText)) {
|
|
49999
50409
|
lines.push(
|
|
50000
50410
|
[
|
|
@@ -50053,6 +50463,35 @@ function warnIfInertAgentTools(app) {
|
|
|
50053
50463
|
function anyToolReaches(tools, field) {
|
|
50054
50464
|
return DECLARATION_BOUND_TOOLS.some((g) => g.field === field && tools.includes(g.tool));
|
|
50055
50465
|
}
|
|
50466
|
+
function undeclaredCalledAliases(called, meta3) {
|
|
50467
|
+
const declared = {
|
|
50468
|
+
queries: new Set(Object.keys(meta3.queries ?? {})),
|
|
50469
|
+
workflows: new Set(Object.keys(meta3.workflows ?? {}))
|
|
50470
|
+
};
|
|
50471
|
+
return {
|
|
50472
|
+
queries: [...new Set(called.queries)].filter((a) => !declared.queries.has(a)).sort(),
|
|
50473
|
+
workflows: [...new Set(called.workflows)].filter((a) => !declared.workflows.has(a)).sort()
|
|
50474
|
+
};
|
|
50475
|
+
}
|
|
50476
|
+
function reportUndeclaredCalledAliases(called, meta3) {
|
|
50477
|
+
const undeclared = undeclaredCalledAliases(called, meta3);
|
|
50478
|
+
const lines = [
|
|
50479
|
+
...undeclared.queries.map((a) => ` \u2022 useQuery("${a}") \u2014 no package.json#lotics.queries.${a}`),
|
|
50480
|
+
...undeclared.workflows.map(
|
|
50481
|
+
(a) => ` \u2022 useWorkflow("${a}") \u2014 no package.json#lotics.workflows.${a}`
|
|
50482
|
+
)
|
|
50483
|
+
];
|
|
50484
|
+
if (lines.length === 0) return 0;
|
|
50485
|
+
console.error(
|
|
50486
|
+
`
|
|
50487
|
+
\u2717 ${lines.length} call site${lines.length === 1 ? "" : "s"} name${lines.length === 1 ? "s" : ""} an alias this project does not declare:
|
|
50488
|
+
` + lines.join("\n") + `
|
|
50489
|
+
|
|
50490
|
+
These COMPILE \u2014 the hooks keep a bare-string overload for a computed alias \u2014 and fail
|
|
50491
|
+
when the screen renders. Declare the alias, or fix the call site.`
|
|
50492
|
+
);
|
|
50493
|
+
return lines.length;
|
|
50494
|
+
}
|
|
50056
50495
|
function warnIfUnboundAliases(app, called) {
|
|
50057
50496
|
const {
|
|
50058
50497
|
queries: unboundQueries,
|
|
@@ -50076,10 +50515,23 @@ function warnIfUnboundAliases(app, called) {
|
|
|
50076
50515
|
for (const alias of unboundAgents) lines.push(` \u2022 agent "${alias}" \u2192 bind with set_app_agent (lotics run set_app_agent \u2026)`);
|
|
50077
50516
|
console.error(lines.join("\n"));
|
|
50078
50517
|
}
|
|
50518
|
+
function resolveDeclaredVitePort(projectDir, flagPort) {
|
|
50519
|
+
const configName = VITE_CONFIG_NAMES.find((name) => fs11.existsSync(path12.join(projectDir, name)));
|
|
50520
|
+
const declared = configName === void 0 ? null : declaredVitePort(fs11.readFileSync(path12.join(projectDir, configName), "utf-8"));
|
|
50521
|
+
if (declared === null) return flagPort;
|
|
50522
|
+
if (flagPort === void 0) return declared;
|
|
50523
|
+
if (flagPort !== declared) {
|
|
50524
|
+
console.error(
|
|
50525
|
+
`\u26A0 --vite-port=${flagPort} overrides ${configName}'s server.port ${declared}. Vite is bound to ${flagPort}; drop the flag to use the config's port.`
|
|
50526
|
+
);
|
|
50527
|
+
}
|
|
50528
|
+
return flagPort;
|
|
50529
|
+
}
|
|
50079
50530
|
async function appDev(client, args) {
|
|
50080
50531
|
const projectDir = path12.resolve(args.projectDir ?? process.cwd());
|
|
50081
50532
|
const meta3 = readAppMeta(projectDir);
|
|
50082
50533
|
warnAboutDevLink(projectDir, "dev");
|
|
50534
|
+
const vitePort = resolveDeclaredVitePort(projectDir, args.vitePort);
|
|
50083
50535
|
writeAppDts(projectDir, { workflows: meta3.workflows, queries: meta3.queries, agents: meta3.agents });
|
|
50084
50536
|
const app = await client.getApp(meta3.app_id);
|
|
50085
50537
|
const handle = await startDevServer({
|
|
@@ -50089,7 +50541,7 @@ async function appDev(client, args) {
|
|
|
50089
50541
|
workspace_id: meta3.workspace_id,
|
|
50090
50542
|
api_url: client.baseUrl,
|
|
50091
50543
|
port: args.port,
|
|
50092
|
-
vitePort
|
|
50544
|
+
vitePort,
|
|
50093
50545
|
client,
|
|
50094
50546
|
commentsEnabled: meta3.capabilities?.comments
|
|
50095
50547
|
});
|
|
@@ -50350,13 +50802,16 @@ async function appWorkflowSet(client, args) {
|
|
|
50350
50802
|
...typeof declaration.description === "string" ? { description: declaration.description } : {}
|
|
50351
50803
|
});
|
|
50352
50804
|
console.error(`Set workflow "${args.alias}" \u2192 ${result.workflow_id}`);
|
|
50805
|
+
if (declaration.workflow_id !== result.workflow_id) {
|
|
50806
|
+
writeWorkflowDeclarationFields(projectDir, args.alias, { workflow_id: result.workflow_id });
|
|
50807
|
+
}
|
|
50353
50808
|
for (const warning of result.warnings) {
|
|
50354
50809
|
const issue2 = issueSchema.safeParse(warning);
|
|
50355
50810
|
console.error(issue2.success ? renderWorkflowIssue(issue2.data) : ` ${JSON.stringify(warning)}`);
|
|
50356
50811
|
}
|
|
50357
50812
|
const derivedOutputs = result.outputs;
|
|
50358
50813
|
if (!declaration.outputs && derivedOutputs && Object.keys(derivedOutputs).length > 0) {
|
|
50359
|
-
|
|
50814
|
+
writeWorkflowDeclarationFields(projectDir, args.alias, { outputs: derivedOutputs });
|
|
50360
50815
|
console.error(
|
|
50361
50816
|
` Wrote the derived result.data schema into package.json#lotics.workflows.${args.alias}.outputs.`
|
|
50362
50817
|
);
|
|
@@ -50364,7 +50819,7 @@ async function appWorkflowSet(client, args) {
|
|
|
50364
50819
|
inputs: declaration.inputs,
|
|
50365
50820
|
outputs: derivedOutputs
|
|
50366
50821
|
});
|
|
50367
|
-
if (refreshed) console.error(` Refreshed workflow types for ${args.alias}.`);
|
|
50822
|
+
if (refreshed.kind === "refreshed") console.error(` Refreshed workflow types for ${args.alias}.`);
|
|
50368
50823
|
} else if (derivedOutputs) {
|
|
50369
50824
|
console.error(` result.data schema: ${JSON.stringify(derivedOutputs)}`);
|
|
50370
50825
|
}
|
|
@@ -50432,22 +50887,79 @@ async function appWorkflowPull(client, args = {}) {
|
|
|
50432
50887
|
reportUnseenLive("workflow body", unseenLive, meta3.app_id);
|
|
50433
50888
|
ensureAppTsconfig(projectDir);
|
|
50434
50889
|
}
|
|
50435
|
-
async function
|
|
50890
|
+
async function appWorkflowDiff(client, args = {}) {
|
|
50436
50891
|
const projectDir = process.cwd();
|
|
50437
50892
|
const meta3 = readAppMeta(projectDir);
|
|
50438
50893
|
const bound = Object.keys(meta3.workflows ?? {});
|
|
50439
|
-
|
|
50440
|
-
|
|
50441
|
-
|
|
50442
|
-
|
|
50443
|
-
|
|
50444
|
-
|
|
50445
|
-
|
|
50894
|
+
const requested = args.aliases ?? [];
|
|
50895
|
+
const unbound = requested.filter((alias) => !bound.includes(alias));
|
|
50896
|
+
if (unbound.length > 0) {
|
|
50897
|
+
fail(
|
|
50898
|
+
`No workflow ${unbound.map((a) => `"${a}"`).join(", ")} in package.json#lotics.workflows. Bound aliases: ${bound.length > 0 ? bound.join(", ") : "(none)"}.`
|
|
50899
|
+
);
|
|
50900
|
+
}
|
|
50901
|
+
const aliases = requested.length > 0 ? [...new Set(requested)] : workflowBodyDrift(projectDir, meta3.workflows, readSynced(projectDir).workflows);
|
|
50902
|
+
if (aliases.length === 0) {
|
|
50903
|
+
console.error("No workflow body differs from the baseline this checkout last pushed or pulled.");
|
|
50904
|
+
return;
|
|
50905
|
+
}
|
|
50906
|
+
let differing = 0;
|
|
50907
|
+
for (const alias of aliases) {
|
|
50908
|
+
const file2 = workflowFilePath(projectDir, alias);
|
|
50909
|
+
if (!fs11.existsSync(file2)) {
|
|
50910
|
+
console.error(`\u26A0 Skipped "${alias}" \u2014 no body at ${path12.relative(projectDir, file2)}.`);
|
|
50911
|
+
continue;
|
|
50446
50912
|
}
|
|
50447
|
-
|
|
50448
|
-
|
|
50449
|
-
|
|
50913
|
+
const res = await client.getAppWorkflow(meta3.app_id, alias);
|
|
50914
|
+
if (res.error) {
|
|
50915
|
+
if (isMissingWorkflowBodyError(res.error)) {
|
|
50916
|
+
console.error(
|
|
50917
|
+
`\u26A0 Skipped "${alias}" \u2014 the server has no rendered body for it (never set, or legacy).`
|
|
50918
|
+
);
|
|
50919
|
+
continue;
|
|
50920
|
+
}
|
|
50921
|
+
fail(`Could not read workflow "${alias}" of ${meta3.app_id}: ${res.error}`);
|
|
50922
|
+
}
|
|
50923
|
+
const parsed = zod_default.object({ source: zod_default.string() }).safeParse(res.result);
|
|
50924
|
+
if (!parsed.success) {
|
|
50925
|
+
console.error(`\u26A0 Skipped "${alias}" \u2014 the server returned no body source.`);
|
|
50926
|
+
continue;
|
|
50927
|
+
}
|
|
50928
|
+
const live = normalizeWorkflowBody(parsed.data.source);
|
|
50929
|
+
const local = stripWorkflowHeader(fs11.readFileSync(file2, "utf-8"));
|
|
50930
|
+
if (live === local) {
|
|
50931
|
+
console.error(`\u2713 ${alias} \u2014 identical to the body the server is running.`);
|
|
50932
|
+
continue;
|
|
50933
|
+
}
|
|
50934
|
+
differing++;
|
|
50935
|
+
console.error(`
|
|
50936
|
+
${alias} (- live, + ${path12.join(WORKFLOWS_DIR, `${alias}.ts`)})`);
|
|
50937
|
+
console.log(renderLineDiff(live, local));
|
|
50938
|
+
}
|
|
50939
|
+
if (differing > 0) {
|
|
50940
|
+
console.error(
|
|
50941
|
+
`
|
|
50942
|
+
${differing} workflow ${differing === 1 ? "body differs" : "bodies differ"} from the server. 'lotics app workflow set <alias>' pushes one; 'lotics app workflow pull --force' takes the server's.`
|
|
50943
|
+
);
|
|
50944
|
+
process.exitCode = 1;
|
|
50450
50945
|
}
|
|
50946
|
+
}
|
|
50947
|
+
function resolveCheckAliases(requested, bound) {
|
|
50948
|
+
const unbound = [...new Set(requested)].filter((alias) => !bound.includes(alias));
|
|
50949
|
+
if (unbound.length > 0) return { kind: "unbound", unbound };
|
|
50950
|
+
return { kind: "ok", aliases: requested.length > 0 ? [...new Set(requested)] : [...bound] };
|
|
50951
|
+
}
|
|
50952
|
+
async function appWorkflowCheck(args) {
|
|
50953
|
+
const projectDir = process.cwd();
|
|
50954
|
+
const meta3 = readAppMeta(projectDir);
|
|
50955
|
+
const bound = Object.keys(meta3.workflows ?? {});
|
|
50956
|
+
const resolved = resolveCheckAliases(args.aliases ?? [], bound);
|
|
50957
|
+
if (resolved.kind === "unbound") {
|
|
50958
|
+
fail(
|
|
50959
|
+
`No workflow ${resolved.unbound.map((a) => `"${a}"`).join(", ")} in package.json#lotics.workflows. Bound aliases: ${bound.length > 0 ? bound.join(", ") : "(none)"}.`
|
|
50960
|
+
);
|
|
50961
|
+
}
|
|
50962
|
+
const aliases = resolved.aliases;
|
|
50451
50963
|
if (aliases.length === 0) {
|
|
50452
50964
|
console.error(`App ${meta3.app_id} has no bound workflows to check.`);
|
|
50453
50965
|
return;
|
|
@@ -50460,8 +50972,7 @@ async function appWorkflowCheck(args) {
|
|
|
50460
50972
|
const tsApi = await loadProjectTypescript(projectDir);
|
|
50461
50973
|
const results = checkWorkflowBodies(tsApi, toCheck);
|
|
50462
50974
|
printWorkflowCheckResults(projectDir, results);
|
|
50463
|
-
|
|
50464
|
-
if (failed.length > 0) process.exit(1);
|
|
50975
|
+
if (results.some((r) => r.issues.length > 0)) process.exitCode = 1;
|
|
50465
50976
|
}
|
|
50466
50977
|
async function collectWorkflowBodyChecks(projectDir, meta3, aliases, client) {
|
|
50467
50978
|
const toCheck = [];
|
|
@@ -50470,15 +50981,14 @@ async function collectWorkflowBodyChecks(projectDir, meta3, aliases, client) {
|
|
|
50470
50981
|
const globalsPath = workflowGlobalsPath(projectDir, alias);
|
|
50471
50982
|
if (!fs11.existsSync(bodyPath)) {
|
|
50472
50983
|
console.error(
|
|
50473
|
-
`\u26A0 Skipped "${alias}" \u2014 no body at ${path12.relative(projectDir, bodyPath)}.
|
|
50984
|
+
`\u26A0 Skipped "${alias}" \u2014 no body at ${path12.relative(projectDir, bodyPath)}. Write it there (its types are in ${path12.relative(projectDir, globalsPath)}), or 'lotics app workflow pull' if the server already has one.`
|
|
50474
50985
|
);
|
|
50475
50986
|
continue;
|
|
50476
50987
|
}
|
|
50477
50988
|
if (!fs11.existsSync(globalsPath)) {
|
|
50478
|
-
|
|
50989
|
+
fail(
|
|
50479
50990
|
`Cannot check "${alias}" \u2014 missing types at ${path12.relative(projectDir, globalsPath)}. Run 'lotics app codegen' to fetch them (it refreshes types WITHOUT touching your body; a pull would overwrite it).`
|
|
50480
50991
|
);
|
|
50481
|
-
process.exit(1);
|
|
50482
50992
|
}
|
|
50483
50993
|
toCheck.push({ alias, input: { bodyPath, globalsPath } });
|
|
50484
50994
|
}
|
|
@@ -50556,6 +51066,7 @@ function parseArgs(argv) {
|
|
|
50556
51066
|
remove: void 0,
|
|
50557
51067
|
includeHidden: false,
|
|
50558
51068
|
timezone: void 0,
|
|
51069
|
+
currency: void 0,
|
|
50559
51070
|
email: void 0,
|
|
50560
51071
|
message: void 0,
|
|
50561
51072
|
session: void 0,
|
|
@@ -50568,11 +51079,13 @@ function parseArgs(argv) {
|
|
|
50568
51079
|
printCreated: false,
|
|
50569
51080
|
cleanup: false,
|
|
50570
51081
|
prune: false,
|
|
51082
|
+
pruneInvoked: [],
|
|
50571
51083
|
screens: false,
|
|
50572
51084
|
noSampleData: false,
|
|
50573
51085
|
adopt: false,
|
|
50574
51086
|
entity: [],
|
|
50575
51087
|
limit: void 0,
|
|
51088
|
+
cursor: void 0,
|
|
50576
51089
|
tables: [],
|
|
50577
51090
|
bind: [],
|
|
50578
51091
|
version: false,
|
|
@@ -50670,6 +51183,9 @@ function parseArgs(argv) {
|
|
|
50670
51183
|
case "--timezone":
|
|
50671
51184
|
flags.timezone = takeValue(arg, "<Area/City>");
|
|
50672
51185
|
break;
|
|
51186
|
+
case "--currency":
|
|
51187
|
+
flags.currency = takeValue(arg, "<ISO code>");
|
|
51188
|
+
break;
|
|
50673
51189
|
case "-m":
|
|
50674
51190
|
case "--message":
|
|
50675
51191
|
flags.message = takeValue(arg, "<message>");
|
|
@@ -50711,6 +51227,9 @@ function parseArgs(argv) {
|
|
|
50711
51227
|
case "--limit":
|
|
50712
51228
|
flags.limit = takeValue(arg, "<n>");
|
|
50713
51229
|
break;
|
|
51230
|
+
case "--cursor":
|
|
51231
|
+
flags.cursor = takeValue(arg, "<token>");
|
|
51232
|
+
break;
|
|
50714
51233
|
case "--tables":
|
|
50715
51234
|
flags.tables = [...flags.tables, ...takeList(arg, "<tbl_id,tbl_id>")];
|
|
50716
51235
|
break;
|
|
@@ -50719,6 +51238,9 @@ function parseArgs(argv) {
|
|
|
50719
51238
|
case "--bind":
|
|
50720
51239
|
flags.bind = [...flags.bind, takeValue(arg, "<entity>=<Label>")];
|
|
50721
51240
|
break;
|
|
51241
|
+
case "--prune-invoked":
|
|
51242
|
+
flags.pruneInvoked = [...flags.pruneInvoked, ...takeList(arg, "<alias>")];
|
|
51243
|
+
break;
|
|
50722
51244
|
case "--prune":
|
|
50723
51245
|
flags.prune = true;
|
|
50724
51246
|
break;
|
|
@@ -51175,7 +51697,14 @@ async function runPreviewCommand(filePath, flags) {
|
|
|
51175
51697
|
if (!existsSync4(abs2)) fail(`File not found: ${abs2}`);
|
|
51176
51698
|
const ext = extname(abs2).toLowerCase();
|
|
51177
51699
|
const type = ext === ".docx" ? "docx" : ext === ".xlsx" || ext === ".xls" || ext === ".csv" ? "xlsx" : ext === ".html" || ext === ".htm" ? "html" : null;
|
|
51178
|
-
if (!type)
|
|
51700
|
+
if (!type) {
|
|
51701
|
+
const route = ext === ".pdf" ? `
|
|
51702
|
+
A PDF is rasterised outside this CLI: lotics file download <fil_id>, then
|
|
51703
|
+
'pdftoppm -png -r 150 <file.pdf> <out-prefix>' (poppler-utils) for one PNG per page.` : "";
|
|
51704
|
+
fail(
|
|
51705
|
+
`Unsupported file type "${ext}" \u2014 preview supports .docx, .xlsx/.csv and .html.${route}`
|
|
51706
|
+
);
|
|
51707
|
+
}
|
|
51179
51708
|
let server;
|
|
51180
51709
|
let pageUrl;
|
|
51181
51710
|
if (type === "html") {
|
|
@@ -51787,23 +52316,32 @@ async function main() {
|
|
|
51787
52316
|
console.error(`${flag} needs a value \u2014 write it as: ${flag} ${expects}`);
|
|
51788
52317
|
process.exit(1);
|
|
51789
52318
|
}
|
|
51790
|
-
if (command === "file") {
|
|
51791
|
-
if (!subcommand) {
|
|
51792
|
-
printHelp();
|
|
51793
|
-
return;
|
|
51794
|
-
}
|
|
51795
|
-
command = subcommand;
|
|
51796
|
-
subcommand = toolArgs;
|
|
51797
|
-
toolArgs = restArgs.shift();
|
|
51798
|
-
}
|
|
51799
52319
|
if (flags.help && command === "report") {
|
|
51800
52320
|
console.error(REPORT_USAGE);
|
|
51801
52321
|
return;
|
|
51802
52322
|
}
|
|
51803
52323
|
if (flags.help || !command && !flags.version) {
|
|
52324
|
+
const scoped = flags.help ? commandHelp([parsed.command, parsed.subcommand, parsed.toolArgs].filter((w) => w !== void 0)) : null;
|
|
52325
|
+
if (scoped !== null) {
|
|
52326
|
+
console.log(scoped);
|
|
52327
|
+
return;
|
|
52328
|
+
}
|
|
51804
52329
|
printHelp();
|
|
51805
52330
|
return;
|
|
51806
52331
|
}
|
|
52332
|
+
if (command === "file") {
|
|
52333
|
+
if (!subcommand) {
|
|
52334
|
+
const help = commandHelp(["file"]);
|
|
52335
|
+
if (help !== null) console.log(help);
|
|
52336
|
+
else printHelp();
|
|
52337
|
+
return;
|
|
52338
|
+
}
|
|
52339
|
+
if (commandAliases("file").has(subcommand)) {
|
|
52340
|
+
command = subcommand;
|
|
52341
|
+
subcommand = toolArgs;
|
|
52342
|
+
toolArgs = restArgs.shift();
|
|
52343
|
+
}
|
|
52344
|
+
}
|
|
51807
52345
|
if (flags.version && command !== void 0) {
|
|
51808
52346
|
console.error(
|
|
51809
52347
|
`--version prints this CLI's version and takes no argument; it cannot be combined with a command.
|
|
@@ -52307,7 +52845,7 @@ async function main() {
|
|
|
52307
52845
|
);
|
|
52308
52846
|
client2 = void 0;
|
|
52309
52847
|
}
|
|
52310
|
-
await appWorkflowCheck({
|
|
52848
|
+
await appWorkflowCheck({ aliases: restArgs, client: client2 });
|
|
52311
52849
|
return;
|
|
52312
52850
|
}
|
|
52313
52851
|
if (command === "app" && subcommand === "codegen") {
|
|
@@ -52476,6 +53014,59 @@ async function main() {
|
|
|
52476
53014
|
}
|
|
52477
53015
|
return;
|
|
52478
53016
|
}
|
|
53017
|
+
if (command === "file") {
|
|
53018
|
+
if (subcommand !== "list" && subcommand !== "delete") {
|
|
53019
|
+
console.error(`Unknown file subcommand: ${subcommand}
|
|
53020
|
+
`);
|
|
53021
|
+
console.error(commandHelp(["file"]));
|
|
53022
|
+
process.exit(1);
|
|
53023
|
+
}
|
|
53024
|
+
const { client: client2, ctx: ctx2 } = await requireClient(flags);
|
|
53025
|
+
await resolveWorkspace(client2, ctx2);
|
|
53026
|
+
if (subcommand === "list") {
|
|
53027
|
+
let limit;
|
|
53028
|
+
if (flags.limit !== void 0) {
|
|
53029
|
+
limit = Number(flags.limit);
|
|
53030
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
53031
|
+
console.error(`Invalid --limit "${flags.limit}" \u2014 expected a positive integer.`);
|
|
53032
|
+
process.exit(1);
|
|
53033
|
+
}
|
|
53034
|
+
}
|
|
53035
|
+
const page = await client2.listFiles({ limit, cursor: flags.cursor });
|
|
53036
|
+
if (flags.json) {
|
|
53037
|
+
console.log(JSON.stringify(page, null, 2));
|
|
53038
|
+
} else {
|
|
53039
|
+
for (const file2 of page.files) {
|
|
53040
|
+
const size2 = file2.size == null ? "-" : String(file2.size);
|
|
53041
|
+
console.log(`${file2.id} ${file2.created_at} ${size2} ${file2.mime_type} ${file2.filename}`);
|
|
53042
|
+
}
|
|
53043
|
+
console.error(
|
|
53044
|
+
page.next_cursor === null ? `${page.files.length} file${page.files.length === 1 ? "" : "s"} \u2014 end of the store.` : `${page.files.length} files. Next page: lotics file list --cursor ${page.next_cursor}`
|
|
53045
|
+
);
|
|
53046
|
+
}
|
|
53047
|
+
return;
|
|
53048
|
+
}
|
|
53049
|
+
const fileId = toolArgs;
|
|
53050
|
+
if (!fileId || !isStoredFileId(fileId)) {
|
|
53051
|
+
console.error("Usage: lotics file delete <file_id>");
|
|
53052
|
+
console.error(
|
|
53053
|
+
"Archives a stored file. Refused while a record, comment, knowledge doc, document"
|
|
53054
|
+
);
|
|
53055
|
+
console.error("template or voice session still references it \u2014 those are named back.");
|
|
53056
|
+
process.exit(1);
|
|
53057
|
+
}
|
|
53058
|
+
const deleted = await client2.execute("delete_file", { file_id: fileId }, { format: "text" });
|
|
53059
|
+
if (deleted.error) {
|
|
53060
|
+
console.error(deleted.error);
|
|
53061
|
+
process.exit(1);
|
|
53062
|
+
}
|
|
53063
|
+
if (flags.json) {
|
|
53064
|
+
console.log(JSON.stringify(deleted.result, null, 2));
|
|
53065
|
+
} else {
|
|
53066
|
+
console.log(deleted.model_output ?? JSON.stringify(deleted.result));
|
|
53067
|
+
}
|
|
53068
|
+
return;
|
|
53069
|
+
}
|
|
52479
53070
|
const appPathArg = subcommand === "dev" ? toolArgs : void 0;
|
|
52480
53071
|
const { client, ctx } = await requireClient(
|
|
52481
53072
|
flags,
|
|
@@ -52501,6 +53092,22 @@ async function main() {
|
|
|
52501
53092
|
process.exitCode = 1;
|
|
52502
53093
|
return;
|
|
52503
53094
|
}
|
|
53095
|
+
const settingsName = subcommand === "rename" ? toolArgs : flags.name;
|
|
53096
|
+
if (subcommand === "rename" && !settingsName) {
|
|
53097
|
+
console.error("Usage: lotics workspace rename <new name>");
|
|
53098
|
+
console.error('Renames the CURRENT workspace. Switch first with "lotics workspace select <id>".');
|
|
53099
|
+
process.exit(1);
|
|
53100
|
+
}
|
|
53101
|
+
if (subcommand === "settings" && settingsName === void 0 && flags.currency === void 0 && flags.timezone === void 0) {
|
|
53102
|
+
console.error(
|
|
53103
|
+
"Usage: lotics workspace settings [--name <name>] [--currency <ISO>] [--timezone <Area/City>]"
|
|
53104
|
+
);
|
|
53105
|
+
console.error(
|
|
53106
|
+
"Changes the CURRENT workspace. The currency decides how every money field renders and the"
|
|
53107
|
+
);
|
|
53108
|
+
console.error("zone decides how every date buckets; neither is visible once it is wrong.");
|
|
53109
|
+
process.exit(1);
|
|
53110
|
+
}
|
|
52504
53111
|
const workspaces = await client.listWorkspaces();
|
|
52505
53112
|
const currentWorkspaceId = ctx.workspaceId;
|
|
52506
53113
|
if (subcommand === "select") {
|
|
@@ -52526,45 +53133,47 @@ Available workspaces:`);
|
|
|
52526
53133
|
console.error(`Switched to workspace: ${target.name} (${target.id})${where}`);
|
|
52527
53134
|
return;
|
|
52528
53135
|
}
|
|
52529
|
-
if (subcommand === "
|
|
52530
|
-
const
|
|
52531
|
-
if (!
|
|
52532
|
-
console.error("
|
|
53136
|
+
if (subcommand === "settings" || subcommand === "rename") {
|
|
53137
|
+
const current = workspaces.find((ws) => ws.id === currentWorkspaceId);
|
|
53138
|
+
if (!current) {
|
|
53139
|
+
console.error('No workspace selected. Choose one with "lotics workspace select <id>".');
|
|
52533
53140
|
process.exit(1);
|
|
52534
53141
|
}
|
|
52535
|
-
const
|
|
52536
|
-
|
|
52537
|
-
|
|
52538
|
-
|
|
53142
|
+
const updated = await client.updateWorkspace({
|
|
53143
|
+
name: settingsName ?? current.name,
|
|
53144
|
+
default_currency: flags.currency ?? current.default_currency,
|
|
53145
|
+
timezone: flags.timezone ?? current.timezone
|
|
53146
|
+
});
|
|
52539
53147
|
if (flags.json) {
|
|
52540
|
-
console.log(JSON.stringify(
|
|
53148
|
+
console.log(JSON.stringify(updated, null, 2));
|
|
52541
53149
|
} else {
|
|
52542
|
-
console.error(
|
|
52543
|
-
|
|
53150
|
+
console.error(
|
|
53151
|
+
`Workspace ${updated.id}: ${updated.name} \xB7 ${updated.default_currency} \xB7 ${updated.timezone}`
|
|
53152
|
+
);
|
|
52544
53153
|
}
|
|
52545
53154
|
return;
|
|
52546
53155
|
}
|
|
52547
|
-
if (subcommand === "
|
|
53156
|
+
if (subcommand === "create") {
|
|
52548
53157
|
const name = toolArgs;
|
|
52549
53158
|
if (!name) {
|
|
52550
|
-
console.error("Usage: lotics workspace
|
|
52551
|
-
console.error('Renames the CURRENT workspace. Switch first with "lotics workspace select <id>".');
|
|
53159
|
+
console.error("Usage: lotics workspace create <name> [--timezone <tz>] [--currency <ISO>]");
|
|
52552
53160
|
process.exit(1);
|
|
52553
53161
|
}
|
|
52554
|
-
const
|
|
52555
|
-
|
|
52556
|
-
console.error('No workspace selected. Choose one with "lotics workspace select <id>".');
|
|
52557
|
-
process.exit(1);
|
|
52558
|
-
}
|
|
52559
|
-
const renamed = await client.updateWorkspace({
|
|
53162
|
+
const timezone = flags.timezone;
|
|
53163
|
+
const created = await client.createWorkspace({
|
|
52560
53164
|
name,
|
|
52561
|
-
|
|
52562
|
-
|
|
53165
|
+
timezone,
|
|
53166
|
+
default_currency: flags.currency
|
|
52563
53167
|
});
|
|
53168
|
+
setSelectedWorkspace(created.id, ctx.orgId);
|
|
53169
|
+
client.setWorkspaceId(created.id);
|
|
52564
53170
|
if (flags.json) {
|
|
52565
|
-
console.log(JSON.stringify(
|
|
53171
|
+
console.log(JSON.stringify(created, null, 2));
|
|
52566
53172
|
} else {
|
|
52567
|
-
console.error(
|
|
53173
|
+
console.error(
|
|
53174
|
+
`Created workspace: ${created.name} (${created.id}) \xB7 ${created.default_currency} \xB7 ${created.timezone}`
|
|
53175
|
+
);
|
|
53176
|
+
console.error(`Switched to ${created.id}`);
|
|
52568
53177
|
}
|
|
52569
53178
|
return;
|
|
52570
53179
|
}
|
|
@@ -52653,9 +53262,16 @@ Available workspaces:`);
|
|
|
52653
53262
|
return;
|
|
52654
53263
|
}
|
|
52655
53264
|
if (subcommand === "deploy") {
|
|
53265
|
+
if (flags.pruneInvoked.length > 0 && !flags.prune) {
|
|
53266
|
+
console.error(
|
|
53267
|
+
"--prune-invoked lifts one guard inside --prune; it does nothing on its own. Add --prune."
|
|
53268
|
+
);
|
|
53269
|
+
process.exit(1);
|
|
53270
|
+
}
|
|
52656
53271
|
await appDeploy(client, {
|
|
52657
53272
|
message: (flags.message ?? toolArgs)?.trim() || void 0,
|
|
52658
|
-
prune: flags.prune
|
|
53273
|
+
prune: flags.prune,
|
|
53274
|
+
pruneInvoked: flags.pruneInvoked
|
|
52659
53275
|
});
|
|
52660
53276
|
return;
|
|
52661
53277
|
}
|
|
@@ -52698,7 +53314,8 @@ Available workspaces:`);
|
|
|
52698
53314
|
console.error("Usage:");
|
|
52699
53315
|
console.error(" lotics app workflow set <alias> Push src/workflows/<alias>.ts");
|
|
52700
53316
|
console.error(" lotics app workflow pull Rewrite src/workflows/*.ts from the server");
|
|
52701
|
-
console.error(" lotics app workflow check [alias]
|
|
53317
|
+
console.error(" lotics app workflow check [alias...] Typecheck src/workflows bodies locally");
|
|
53318
|
+
console.error(" lotics app workflow diff [alias...] Show how a body differs from the server's");
|
|
52702
53319
|
process.exit(1);
|
|
52703
53320
|
};
|
|
52704
53321
|
if (action === "set") {
|
|
@@ -52715,6 +53332,10 @@ Available workspaces:`);
|
|
|
52715
53332
|
await appWorkflowPull(client, { force: flags.force === true });
|
|
52716
53333
|
return;
|
|
52717
53334
|
}
|
|
53335
|
+
if (action === "diff") {
|
|
53336
|
+
await appWorkflowDiff(client, { aliases: restArgs });
|
|
53337
|
+
return;
|
|
53338
|
+
}
|
|
52718
53339
|
workflowUsage();
|
|
52719
53340
|
}
|
|
52720
53341
|
if (subcommand === "agent") {
|
|
@@ -52859,7 +53480,6 @@ ${JSON.stringify(info.input_schema, null, 2)}`);
|
|
|
52859
53480
|
const toolName = subcommand;
|
|
52860
53481
|
const ingested = await ingestJsonArgs({
|
|
52861
53482
|
rawArg: toolArgs,
|
|
52862
|
-
stdinIsTTY: process.stdin.isTTY ?? false,
|
|
52863
53483
|
readFile: (p) => fs14.readFileSync(p, "utf-8"),
|
|
52864
53484
|
readStdin
|
|
52865
53485
|
});
|