@lotics/cli 0.57.0 → 0.62.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 +37 -0
- package/dist/app_commands.d.ts +165 -2
- package/dist/app_commands.js +803 -7
- package/dist/app_commands.test.js +569 -2
- package/dist/app_workflow_check.d.ts +77 -0
- package/dist/app_workflow_check.js +169 -0
- package/dist/app_workflow_check.test.d.ts +1 -0
- package/dist/app_workflow_check.test.js +166 -0
- package/dist/args.d.ts +4 -0
- package/dist/args.js +9 -0
- package/dist/args.test.js +12 -0
- package/dist/child_env.d.ts +13 -0
- package/dist/child_env.js +24 -0
- package/dist/cli.js +144 -30
- package/dist/client.d.ts +58 -0
- package/dist/client.js +85 -0
- package/dist/dev/server.js +2 -1
- package/dist/generate_app_fields.d.ts +54 -0
- package/dist/generate_app_fields.js +148 -0
- package/dist/generate_app_fields.test.d.ts +1 -0
- package/dist/generate_app_fields.test.js +108 -0
- package/dist/inputs.d.ts +38 -0
- package/dist/inputs.js +50 -0
- package/dist/inputs.test.d.ts +1 -0
- package/dist/inputs.test.js +89 -0
- package/dist/src/cli.js +1208 -187
- package/dist/starter_template.js +72 -2
- package/dist/starter_template.test.js +15 -0
- package/package.json +3 -1
package/dist/src/cli.js
CHANGED
|
@@ -12243,12 +12243,12 @@ var require_tokens = __commonJS({
|
|
|
12243
12243
|
});
|
|
12244
12244
|
}
|
|
12245
12245
|
exports2.assignCategoriesMapProp = assignCategoriesMapProp;
|
|
12246
|
-
function singleAssignCategoriesToksMap(
|
|
12247
|
-
utils_1.forEach(
|
|
12246
|
+
function singleAssignCategoriesToksMap(path8, nextNode) {
|
|
12247
|
+
utils_1.forEach(path8, function(pathNode) {
|
|
12248
12248
|
nextNode.categoryMatchesMap[pathNode.tokenTypeIdx] = true;
|
|
12249
12249
|
});
|
|
12250
12250
|
utils_1.forEach(nextNode.CATEGORIES, function(nextCategory) {
|
|
12251
|
-
var newPath =
|
|
12251
|
+
var newPath = path8.concat(nextNode);
|
|
12252
12252
|
if (!utils_1.contains(newPath, nextCategory)) {
|
|
12253
12253
|
singleAssignCategoriesToksMap(newPath, nextCategory);
|
|
12254
12254
|
}
|
|
@@ -14011,10 +14011,10 @@ var require_interpreter = __commonJS({
|
|
|
14011
14011
|
/** @class */
|
|
14012
14012
|
(function(_super) {
|
|
14013
14013
|
__extends(AbstractNextPossibleTokensWalker2, _super);
|
|
14014
|
-
function AbstractNextPossibleTokensWalker2(topProd,
|
|
14014
|
+
function AbstractNextPossibleTokensWalker2(topProd, path8) {
|
|
14015
14015
|
var _this = _super.call(this) || this;
|
|
14016
14016
|
_this.topProd = topProd;
|
|
14017
|
-
_this.path =
|
|
14017
|
+
_this.path = path8;
|
|
14018
14018
|
_this.possibleTokTypes = [];
|
|
14019
14019
|
_this.nextProductionName = "";
|
|
14020
14020
|
_this.nextProductionOccurrence = 0;
|
|
@@ -14068,9 +14068,9 @@ var require_interpreter = __commonJS({
|
|
|
14068
14068
|
/** @class */
|
|
14069
14069
|
(function(_super) {
|
|
14070
14070
|
__extends(NextAfterTokenWalker2, _super);
|
|
14071
|
-
function NextAfterTokenWalker2(topProd,
|
|
14072
|
-
var _this = _super.call(this, topProd,
|
|
14073
|
-
_this.path =
|
|
14071
|
+
function NextAfterTokenWalker2(topProd, path8) {
|
|
14072
|
+
var _this = _super.call(this, topProd, path8) || this;
|
|
14073
|
+
_this.path = path8;
|
|
14074
14074
|
_this.nextTerminalName = "";
|
|
14075
14075
|
_this.nextTerminalOccurrence = 0;
|
|
14076
14076
|
_this.nextTerminalName = _this.path.lastTok.name;
|
|
@@ -14767,10 +14767,10 @@ var require_lookahead = __commonJS({
|
|
|
14767
14767
|
}
|
|
14768
14768
|
return result;
|
|
14769
14769
|
}
|
|
14770
|
-
function pathToHashKeys(
|
|
14770
|
+
function pathToHashKeys(path8) {
|
|
14771
14771
|
var keys2 = [""];
|
|
14772
|
-
for (var i2 = 0; i2 <
|
|
14773
|
-
var tokType =
|
|
14772
|
+
for (var i2 = 0; i2 < path8.length; i2++) {
|
|
14773
|
+
var tokType = path8[i2];
|
|
14774
14774
|
var longerKeys = [];
|
|
14775
14775
|
for (var j = 0; j < keys2.length; j++) {
|
|
14776
14776
|
var currShorterKey = keys2[j];
|
|
@@ -15082,9 +15082,9 @@ var require_checks = __commonJS({
|
|
|
15082
15082
|
return errors;
|
|
15083
15083
|
}
|
|
15084
15084
|
exports2.validateRuleIsOverridden = validateRuleIsOverridden;
|
|
15085
|
-
function validateNoLeftRecursion(topRule, currRule, errMsgProvider,
|
|
15086
|
-
if (
|
|
15087
|
-
|
|
15085
|
+
function validateNoLeftRecursion(topRule, currRule, errMsgProvider, path8) {
|
|
15086
|
+
if (path8 === void 0) {
|
|
15087
|
+
path8 = [];
|
|
15088
15088
|
}
|
|
15089
15089
|
var errors = [];
|
|
15090
15090
|
var nextNonTerminals = getFirstNoneTerminal(currRule.definition);
|
|
@@ -15097,15 +15097,15 @@ var require_checks = __commonJS({
|
|
|
15097
15097
|
errors.push({
|
|
15098
15098
|
message: errMsgProvider.buildLeftRecursionError({
|
|
15099
15099
|
topLevelRule: topRule,
|
|
15100
|
-
leftRecursionPath:
|
|
15100
|
+
leftRecursionPath: path8
|
|
15101
15101
|
}),
|
|
15102
15102
|
type: parser_1.ParserDefinitionErrorType.LEFT_RECURSION,
|
|
15103
15103
|
ruleName
|
|
15104
15104
|
});
|
|
15105
15105
|
}
|
|
15106
|
-
var validNextSteps = utils.difference(nextNonTerminals,
|
|
15106
|
+
var validNextSteps = utils.difference(nextNonTerminals, path8.concat([topRule]));
|
|
15107
15107
|
var errorsFromNextSteps = utils.map(validNextSteps, function(currRefRule) {
|
|
15108
|
-
var newPath = utils.cloneArr(
|
|
15108
|
+
var newPath = utils.cloneArr(path8);
|
|
15109
15109
|
newPath.push(currRefRule);
|
|
15110
15110
|
return validateNoLeftRecursion(topRule, currRefRule, errMsgProvider, newPath);
|
|
15111
15111
|
});
|
|
@@ -22714,8 +22714,8 @@ var require_utils4 = __commonJS({
|
|
|
22714
22714
|
var result = transform[inputType][outputType](input);
|
|
22715
22715
|
return result;
|
|
22716
22716
|
};
|
|
22717
|
-
exports2.resolve = function(
|
|
22718
|
-
var parts =
|
|
22717
|
+
exports2.resolve = function(path8) {
|
|
22718
|
+
var parts = path8.split("/");
|
|
22719
22719
|
var result = [];
|
|
22720
22720
|
for (var index = 0; index < parts.length; index++) {
|
|
22721
22721
|
var part = parts[index];
|
|
@@ -28568,18 +28568,18 @@ var require_object = __commonJS({
|
|
|
28568
28568
|
var object = new ZipObject(name, zipObjectContent, o);
|
|
28569
28569
|
this.files[name] = object;
|
|
28570
28570
|
};
|
|
28571
|
-
var parentFolder = function(
|
|
28572
|
-
if (
|
|
28573
|
-
|
|
28571
|
+
var parentFolder = function(path8) {
|
|
28572
|
+
if (path8.slice(-1) === "/") {
|
|
28573
|
+
path8 = path8.substring(0, path8.length - 1);
|
|
28574
28574
|
}
|
|
28575
|
-
var lastSlash =
|
|
28576
|
-
return lastSlash > 0 ?
|
|
28575
|
+
var lastSlash = path8.lastIndexOf("/");
|
|
28576
|
+
return lastSlash > 0 ? path8.substring(0, lastSlash) : "";
|
|
28577
28577
|
};
|
|
28578
|
-
var forceTrailingSlash = function(
|
|
28579
|
-
if (
|
|
28580
|
-
|
|
28578
|
+
var forceTrailingSlash = function(path8) {
|
|
28579
|
+
if (path8.slice(-1) !== "/") {
|
|
28580
|
+
path8 += "/";
|
|
28581
28581
|
}
|
|
28582
|
-
return
|
|
28582
|
+
return path8;
|
|
28583
28583
|
};
|
|
28584
28584
|
var folderAdd = function(name, createFolders) {
|
|
28585
28585
|
createFolders = typeof createFolders !== "undefined" ? createFolders : defaults2.createFolders;
|
|
@@ -29579,8 +29579,10 @@ var require_lib3 = __commonJS({
|
|
|
29579
29579
|
});
|
|
29580
29580
|
|
|
29581
29581
|
// src/cli.ts
|
|
29582
|
-
import
|
|
29583
|
-
import
|
|
29582
|
+
import dns from "node:dns";
|
|
29583
|
+
import net2 from "node:net";
|
|
29584
|
+
import fs8 from "node:fs";
|
|
29585
|
+
import path7 from "node:path";
|
|
29584
29586
|
import readline from "node:readline";
|
|
29585
29587
|
|
|
29586
29588
|
// src/client.ts
|
|
@@ -29678,8 +29680,8 @@ var LoticsClient = class {
|
|
|
29678
29680
|
}
|
|
29679
29681
|
return headers;
|
|
29680
29682
|
}
|
|
29681
|
-
async request(method,
|
|
29682
|
-
const url = `${this.baseUrl}${
|
|
29683
|
+
async request(method, path8, body) {
|
|
29684
|
+
const url = `${this.baseUrl}${path8}`;
|
|
29683
29685
|
const headers = this.buildHeaders();
|
|
29684
29686
|
const init = { method, headers };
|
|
29685
29687
|
if (body !== void 0) {
|
|
@@ -29744,6 +29746,38 @@ var LoticsClient = class {
|
|
|
29744
29746
|
async createApp(body) {
|
|
29745
29747
|
return this.request("POST", "/v1/apps", body);
|
|
29746
29748
|
}
|
|
29749
|
+
/**
|
|
29750
|
+
* Resolve the display name + fields (incl. select options) of the given tables
|
|
29751
|
+
* — the schema `lotics app codegen` turns into the runtime `.lotics/app_fields.ts`
|
|
29752
|
+
* alias maps. One `get_table` call per id (the tool surface has no batch
|
|
29753
|
+
* variant); a missing/inaccessible table is dropped rather than throwing, so a
|
|
29754
|
+
* stale id in the scope set never fails codegen.
|
|
29755
|
+
*/
|
|
29756
|
+
async getWorkspaceSchema(tableIds) {
|
|
29757
|
+
const tables = await Promise.all(
|
|
29758
|
+
tableIds.map(async (table_id) => {
|
|
29759
|
+
const res = await this.execute("get_table", { table_id });
|
|
29760
|
+
if (res.error || res.result === null || typeof res.result !== "object") return null;
|
|
29761
|
+
const table = res.result;
|
|
29762
|
+
if (typeof table.id !== "string" || typeof table.name !== "string" || !Array.isArray(table.fields)) {
|
|
29763
|
+
return null;
|
|
29764
|
+
}
|
|
29765
|
+
const fields = table.fields.flatMap((field) => {
|
|
29766
|
+
if (field === null || typeof field !== "object") return [];
|
|
29767
|
+
const f = field;
|
|
29768
|
+
if (typeof f.key !== "string" || typeof f.name !== "string") return [];
|
|
29769
|
+
const options = Array.isArray(f.options) ? f.options.flatMap((opt) => {
|
|
29770
|
+
if (opt === null || typeof opt !== "object") return [];
|
|
29771
|
+
const o = opt;
|
|
29772
|
+
return typeof o.key === "string" && typeof o.name === "string" ? [{ id: o.key, label: o.name }] : [];
|
|
29773
|
+
}) : void 0;
|
|
29774
|
+
return [{ id: f.key, name: f.name, ...options && options.length > 0 ? { options } : {} }];
|
|
29775
|
+
});
|
|
29776
|
+
return { id: table.id, name: table.name, fields };
|
|
29777
|
+
})
|
|
29778
|
+
);
|
|
29779
|
+
return tables.filter((t) => t !== null);
|
|
29780
|
+
}
|
|
29747
29781
|
/**
|
|
29748
29782
|
* Rename an app's public subdomain — its `<slug>.lotics.app` address.
|
|
29749
29783
|
* Mirrors PUT /v1/apps/{app_id}/subdomain. The old subdomain stops
|
|
@@ -29818,6 +29852,52 @@ var LoticsClient = class {
|
|
|
29818
29852
|
if (response.ok) return parsed ?? {};
|
|
29819
29853
|
return { status: "error", message: transportErrorMessage(response.status, parsed) };
|
|
29820
29854
|
}
|
|
29855
|
+
/**
|
|
29856
|
+
* Bind (create or replace) an app workflow by alias via the `set_app_workflow`
|
|
29857
|
+
* tool — the SINGLE author of `apps.workflows` + the workflow row. `source` is
|
|
29858
|
+
* the verbatim JS-subset body (no `on({...})` trigger). `inputs`/`outputs` are
|
|
29859
|
+
* the typed schemas declared in `package.json#lotics.workflows.<alias>`. The
|
|
29860
|
+
* server re-verifies the body and echoes the bound `outputs` (declared, else
|
|
29861
|
+
* DERIVED from `return({ data })`), so the CLI can show the author what shape
|
|
29862
|
+
* `result.data` will carry. Wraps the tool rather than a bespoke endpoint so
|
|
29863
|
+
* the file flow stays a convenience over the existing single-author contract.
|
|
29864
|
+
*/
|
|
29865
|
+
async setAppWorkflow(app_id, alias, body) {
|
|
29866
|
+
return this.execute("set_app_workflow", {
|
|
29867
|
+
app_id,
|
|
29868
|
+
alias,
|
|
29869
|
+
source: body.source,
|
|
29870
|
+
...body.inputs ? { inputs: body.inputs } : {},
|
|
29871
|
+
...body.outputs ? { outputs: body.outputs } : {},
|
|
29872
|
+
...body.name ? { name: body.name } : {},
|
|
29873
|
+
...body.description ? { description: body.description } : {}
|
|
29874
|
+
});
|
|
29875
|
+
}
|
|
29876
|
+
/**
|
|
29877
|
+
* Fetch one app workflow's faithful source + bound input/output schemas via
|
|
29878
|
+
* `get_app_workflow`. `source` is the JS-subset body re-rendered from the
|
|
29879
|
+
* persisted step tree (incl. the `return({ data })` clause, opaque field/option
|
|
29880
|
+
* keys) — the exact text `lotics app workflow set` would push back. Feeds
|
|
29881
|
+
* `lotics app pull`, which writes it to `src/workflows/<alias>.ts`.
|
|
29882
|
+
*/
|
|
29883
|
+
async getAppWorkflow(app_id, alias) {
|
|
29884
|
+
return this.execute("get_app_workflow", { app_id, alias });
|
|
29885
|
+
}
|
|
29886
|
+
/**
|
|
29887
|
+
* Fetch the server-generated workspace `.d.ts` + the wrapper envelope that
|
|
29888
|
+
* make a `src/workflows/<alias>.ts` body locally typecheckable (GAP-59).
|
|
29889
|
+
* The server is the single source of the type model — the CLI never
|
|
29890
|
+
* re-implements it. `envelope_prefix`/`envelope_suffix` are the exact
|
|
29891
|
+
* `async function __workflow(): …` wrapper the server compiles inside, so the
|
|
29892
|
+
* local typecheck mirrors the set-time verdict. Mirrors
|
|
29893
|
+
* POST /v1/apps/{app_id}/workflows/{alias}/dts.
|
|
29894
|
+
*/
|
|
29895
|
+
async getAppWorkflowDts(app_id, alias) {
|
|
29896
|
+
return this.request(
|
|
29897
|
+
"POST",
|
|
29898
|
+
`/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts`
|
|
29899
|
+
);
|
|
29900
|
+
}
|
|
29821
29901
|
/**
|
|
29822
29902
|
* Open a streaming agent run and return the RAW streamed `Response` (the
|
|
29823
29903
|
* caller reads `res.body`). Unlike `request`, this does not buffer/parse the
|
|
@@ -30262,8 +30342,8 @@ var pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
|
30262
30342
|
var VERSION = pkg.version;
|
|
30263
30343
|
|
|
30264
30344
|
// src/app_commands.ts
|
|
30265
|
-
import
|
|
30266
|
-
import
|
|
30345
|
+
import fs4 from "node:fs";
|
|
30346
|
+
import path5 from "node:path";
|
|
30267
30347
|
import { spawn as spawn2 } from "node:child_process";
|
|
30268
30348
|
import { tmpdir } from "node:os";
|
|
30269
30349
|
|
|
@@ -30323,7 +30403,9 @@ function buildStarterTemplate(args) {
|
|
|
30323
30403
|
"@vitejs/plugin-react": "^4.3.0",
|
|
30324
30404
|
jsdom: "^25.0.0",
|
|
30325
30405
|
oxlint: "^0.13.0",
|
|
30326
|
-
|
|
30406
|
+
// Track the platform's TS major so `lotics app workflow check` (which runs
|
|
30407
|
+
// the app's OWN typescript) gives the same verdict the server does at set-time.
|
|
30408
|
+
typescript: "~6.0.3",
|
|
30327
30409
|
vite: "^5.4.0",
|
|
30328
30410
|
vitest: "^2.1.0"
|
|
30329
30411
|
},
|
|
@@ -30366,7 +30448,18 @@ function buildStarterTemplate(args) {
|
|
|
30366
30448
|
// fall to `unknown`. The explicit glob makes the dot-dir the non-wildcard
|
|
30367
30449
|
// base, which IS read. (GAP-38.)
|
|
30368
30450
|
include: ["src", ".lotics/**/*"],
|
|
30369
|
-
|
|
30451
|
+
// src/workflows/<alias>.ts bodies run on the server (workflow globals:
|
|
30452
|
+
// trigger / runtime / tool calls), and each pulls its own per-alias
|
|
30453
|
+
// ambient globals under `.lotics/workflows/`. BOTH are excluded from
|
|
30454
|
+
// the main typecheck: the bodies would apply the app's DOM lib (the
|
|
30455
|
+
// server doesn't), and the per-alias `.globals.d.ts` files each declare
|
|
30456
|
+
// their OWN ambient `trigger` — loading several into one program
|
|
30457
|
+
// collides those declarations and reports false errors. Type-check the
|
|
30458
|
+
// bodies with `lotics app workflow check` instead — it builds ONE
|
|
30459
|
+
// isolated program per alias (mirroring the server's set-time verify),
|
|
30460
|
+
// so the ambient `trigger` is unambiguous. The server stays the single
|
|
30461
|
+
// verifier on `lotics app workflow set`.
|
|
30462
|
+
exclude: ["node_modules", "src/workflows", ".lotics/workflows"]
|
|
30370
30463
|
},
|
|
30371
30464
|
null,
|
|
30372
30465
|
2
|
|
@@ -30705,6 +30798,63 @@ const routes = [
|
|
|
30705
30798
|
export default function App() {
|
|
30706
30799
|
return <AppRouter routes={routes} />;
|
|
30707
30800
|
}
|
|
30801
|
+
`
|
|
30802
|
+
},
|
|
30803
|
+
{
|
|
30804
|
+
// The home for editable workflow bodies. `lotics app pull` writes one
|
|
30805
|
+
// `src/workflows/<alias>.ts` per bound workflow (faithful server source);
|
|
30806
|
+
// edit it, then `lotics app workflow set <alias>` pushes it back through
|
|
30807
|
+
// set_app_workflow (the server verifies). A new app has no bound workflows
|
|
30808
|
+
// yet, so this is just the docked directory + the loop reference. It's a
|
|
30809
|
+
// .md (not a .ts) so tsc's `include: ["src"]` never tries to compile it —
|
|
30810
|
+
// a workflow body is a JS-subset expression over server globals
|
|
30811
|
+
// (trigger / runtime / tool calls) that does NOT typecheck standalone.
|
|
30812
|
+
path: "src/workflows/README.md",
|
|
30813
|
+
content: `# Workflow bodies
|
|
30814
|
+
|
|
30815
|
+
Editable JS-subset bodies of this app's workflows live here, one file per alias:
|
|
30816
|
+
\`src/workflows/<alias>.ts\`.
|
|
30817
|
+
|
|
30818
|
+
## Loop
|
|
30819
|
+
|
|
30820
|
+
\`\`\`bash
|
|
30821
|
+
lotics app workflow pull # write/refresh every src/workflows/<alias>.ts from the server
|
|
30822
|
+
# edit src/workflows/<alias>.ts
|
|
30823
|
+
lotics app workflow check <alias> # typecheck the body locally (same verdict as \`set\`)
|
|
30824
|
+
lotics app workflow set <alias> # push it back through set_app_workflow (the server verifies)
|
|
30825
|
+
\`\`\`
|
|
30826
|
+
|
|
30827
|
+
\`lotics app pull <app_id>\` also writes these files (alongside the rest of the project).
|
|
30828
|
+
|
|
30829
|
+
## What a body is
|
|
30830
|
+
|
|
30831
|
+
A workflow body is a **JS-subset expression** that runs server-side using workflow
|
|
30832
|
+
globals (\`trigger\`, \`runtime\`, tool calls) and ends with \`return({ data })\`. Each
|
|
30833
|
+
pulled file wraps the body in the server's \`__workflow\` envelope and references its
|
|
30834
|
+
per-alias ambient globals at \`.lotics/workflows/<alias>.globals.d.ts\`, so it **is**
|
|
30835
|
+
locally typecheckable:
|
|
30836
|
+
|
|
30837
|
+
\`\`\`bash
|
|
30838
|
+
lotics app workflow check # check every body; [alias] to check one
|
|
30839
|
+
\`\`\`
|
|
30840
|
+
|
|
30841
|
+
\`check\` builds ONE isolated program per alias (the app's own \`typescript\`, the
|
|
30842
|
+
server's compile options \u2014 strict, lib es2022 with no DOM), so each body's ambient
|
|
30843
|
+
\`trigger\` is unambiguous and the verdict mirrors set-time verify. The main
|
|
30844
|
+
\`npm run typecheck\` excludes \`src/workflows\` + \`.lotics/workflows\` (the bodies would
|
|
30845
|
+
apply the app's DOM lib, and the per-alias globals collide on \`trigger\`). The
|
|
30846
|
+
verification that matters still runs on the **server** when you \`set\` \u2014 the same
|
|
30847
|
+
guarantee as authoring via \`set_app_workflow\` directly. Edit only the body BETWEEN
|
|
30848
|
+
the wrapper lines; the wrapper, the \`/// <reference>\`, and the \`export {};\` marker
|
|
30849
|
+
are CLI bookkeeping (stripped on \`set\`). Do not rename a file (the filename is the
|
|
30850
|
+
alias the binding is keyed by).
|
|
30851
|
+
|
|
30852
|
+
## Authority
|
|
30853
|
+
|
|
30854
|
+
\`lotics app deploy\` never authors workflows \u2014 it carries code, queries, and
|
|
30855
|
+
capabilities only. \`apps.workflows\` has exactly one author: \`set_app_workflow\`
|
|
30856
|
+
(which \`lotics app workflow set\` calls). The typed \`inputs\`/\`outputs\` schema for
|
|
30857
|
+
each alias lives in \`package.json#lotics.workflows.<alias>\`.
|
|
30708
30858
|
`
|
|
30709
30859
|
},
|
|
30710
30860
|
{
|
|
@@ -30894,6 +31044,18 @@ import http from "node:http";
|
|
|
30894
31044
|
import net from "node:net";
|
|
30895
31045
|
import { spawn } from "node:child_process";
|
|
30896
31046
|
|
|
31047
|
+
// src/child_env.ts
|
|
31048
|
+
function ipv4ChildEnv(env) {
|
|
31049
|
+
return {
|
|
31050
|
+
...env,
|
|
31051
|
+
NODE_OPTIONS: [
|
|
31052
|
+
env.NODE_OPTIONS,
|
|
31053
|
+
"--dns-result-order=ipv4first",
|
|
31054
|
+
"--network-family-autoselection-attempt-timeout=2000"
|
|
31055
|
+
].filter(Boolean).join(" ")
|
|
31056
|
+
};
|
|
31057
|
+
}
|
|
31058
|
+
|
|
30897
31059
|
// src/dev/rpc_handler.ts
|
|
30898
31060
|
var SUPPORTED_OPS = /* @__PURE__ */ new Set([
|
|
30899
31061
|
"query",
|
|
@@ -31354,7 +31516,7 @@ async function startDevServer(args) {
|
|
|
31354
31516
|
{
|
|
31355
31517
|
cwd: args.projectDir,
|
|
31356
31518
|
stdio: [process.stdin.isTTY ? "inherit" : "ignore", "inherit", "inherit"],
|
|
31357
|
-
env: { ...process.env, FORCE_COLOR: "1" }
|
|
31519
|
+
env: ipv4ChildEnv({ ...process.env, FORCE_COLOR: "1" })
|
|
31358
31520
|
}
|
|
31359
31521
|
);
|
|
31360
31522
|
let stopped = false;
|
|
@@ -31778,6 +31940,254 @@ ${lines.join("\n")}
|
|
|
31778
31940
|
`;
|
|
31779
31941
|
}
|
|
31780
31942
|
|
|
31943
|
+
// ../shared/src/app_query_ast.ts
|
|
31944
|
+
function collectQueryTableIds(node) {
|
|
31945
|
+
const result = /* @__PURE__ */ new Set();
|
|
31946
|
+
walk(node, (n) => {
|
|
31947
|
+
if (n.kind === "from_table") result.add(n.table_id);
|
|
31948
|
+
});
|
|
31949
|
+
return result;
|
|
31950
|
+
}
|
|
31951
|
+
function walk(node, visit) {
|
|
31952
|
+
visit(node);
|
|
31953
|
+
switch (node.kind) {
|
|
31954
|
+
case "from_table":
|
|
31955
|
+
return;
|
|
31956
|
+
case "project":
|
|
31957
|
+
case "filter":
|
|
31958
|
+
case "group":
|
|
31959
|
+
case "window":
|
|
31960
|
+
case "sort":
|
|
31961
|
+
case "limit":
|
|
31962
|
+
case "unpivot":
|
|
31963
|
+
walk(node.from, visit);
|
|
31964
|
+
return;
|
|
31965
|
+
case "join":
|
|
31966
|
+
walk(node.left, visit);
|
|
31967
|
+
walk(node.right, visit);
|
|
31968
|
+
return;
|
|
31969
|
+
case "union":
|
|
31970
|
+
for (const child of node.sources) walk(child, visit);
|
|
31971
|
+
return;
|
|
31972
|
+
default: {
|
|
31973
|
+
const _exhaustive = node;
|
|
31974
|
+
void _exhaustive;
|
|
31975
|
+
return;
|
|
31976
|
+
}
|
|
31977
|
+
}
|
|
31978
|
+
}
|
|
31979
|
+
|
|
31980
|
+
// src/generate_app_fields.ts
|
|
31981
|
+
var HEADER4 = `// Auto-generated by 'lotics app codegen' (and app pull/dev/deploy).
|
|
31982
|
+
// DO NOT EDIT \u2014 regenerated from the workspace schema.
|
|
31983
|
+
//
|
|
31984
|
+
// Runtime field + option ids addressed by stable display-name aliases:
|
|
31985
|
+
// record.data[F.<TABLE>.<field>] \u2192 "fld_\u2026"
|
|
31986
|
+
// value === OPT.<TABLE>.<field>.<option> \u2192 "opt_\u2026"
|
|
31987
|
+
// A rename on the platform re-runs codegen and moves these in lockstep.
|
|
31988
|
+
`;
|
|
31989
|
+
function slugifyAlias(name, upper) {
|
|
31990
|
+
const stripped = name.normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/đ/g, "d").replace(/Đ/g, "D");
|
|
31991
|
+
const cased = upper ? stripped.toUpperCase() : stripped.toLowerCase();
|
|
31992
|
+
const slug = cased.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
31993
|
+
if (slug === "") return "_";
|
|
31994
|
+
return /^[0-9]/.test(slug) ? `_${slug}` : slug;
|
|
31995
|
+
}
|
|
31996
|
+
function dedupeAliases(names, upper) {
|
|
31997
|
+
const used = /* @__PURE__ */ new Map();
|
|
31998
|
+
return names.map((name) => {
|
|
31999
|
+
const base = slugifyAlias(name, upper);
|
|
32000
|
+
const seen = used.get(base);
|
|
32001
|
+
if (seen === void 0) {
|
|
32002
|
+
used.set(base, 1);
|
|
32003
|
+
return base;
|
|
32004
|
+
}
|
|
32005
|
+
let n = seen + 1;
|
|
32006
|
+
while (used.has(`${base}_${n}`)) n++;
|
|
32007
|
+
used.set(base, n);
|
|
32008
|
+
used.set(`${base}_${n}`, 1);
|
|
32009
|
+
return `${base}_${n}`;
|
|
32010
|
+
});
|
|
32011
|
+
}
|
|
32012
|
+
function propKey(alias) {
|
|
32013
|
+
return isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
32014
|
+
}
|
|
32015
|
+
function aliasTables(tables) {
|
|
32016
|
+
const tableAliases = dedupeAliases(
|
|
32017
|
+
tables.map((t) => t.name),
|
|
32018
|
+
true
|
|
32019
|
+
);
|
|
32020
|
+
return tables.map((table, i2) => {
|
|
32021
|
+
const fieldAliases = dedupeAliases(
|
|
32022
|
+
table.fields.map((f) => f.name),
|
|
32023
|
+
false
|
|
32024
|
+
);
|
|
32025
|
+
return {
|
|
32026
|
+
alias: tableAliases[i2],
|
|
32027
|
+
table,
|
|
32028
|
+
fields: table.fields.map((field, j) => ({ alias: fieldAliases[j], field }))
|
|
32029
|
+
};
|
|
32030
|
+
});
|
|
32031
|
+
}
|
|
32032
|
+
function emitFieldMap(aliased) {
|
|
32033
|
+
const tableBlocks = aliased.map(({ alias, fields }) => {
|
|
32034
|
+
const fieldLines = fields.map(
|
|
32035
|
+
({ alias: fieldAlias, field }) => ` ${propKey(fieldAlias)}: ${JSON.stringify(field.id)},`
|
|
32036
|
+
);
|
|
32037
|
+
return ` ${propKey(alias)}: {
|
|
32038
|
+
${fieldLines.join("\n")}
|
|
32039
|
+
},`;
|
|
32040
|
+
});
|
|
32041
|
+
return `export const F = {
|
|
32042
|
+
${tableBlocks.join("\n")}
|
|
32043
|
+
} as const;`;
|
|
32044
|
+
}
|
|
32045
|
+
function emitOptionMap(aliased) {
|
|
32046
|
+
const tableBlocks = [];
|
|
32047
|
+
for (const { alias, fields } of aliased) {
|
|
32048
|
+
const fieldBlocks = [];
|
|
32049
|
+
for (const { alias: fieldAlias, field } of fields) {
|
|
32050
|
+
const options = field.options ?? [];
|
|
32051
|
+
if (options.length === 0) continue;
|
|
32052
|
+
const optionAliases = dedupeAliases(
|
|
32053
|
+
options.map((o) => o.label),
|
|
32054
|
+
false
|
|
32055
|
+
);
|
|
32056
|
+
const optionLines = options.map(
|
|
32057
|
+
(option, i2) => ` ${propKey(optionAliases[i2])}: ${JSON.stringify(option.id)},`
|
|
32058
|
+
);
|
|
32059
|
+
fieldBlocks.push(` ${propKey(fieldAlias)}: {
|
|
32060
|
+
${optionLines.join("\n")}
|
|
32061
|
+
},`);
|
|
32062
|
+
}
|
|
32063
|
+
if (fieldBlocks.length === 0) continue;
|
|
32064
|
+
tableBlocks.push(` ${propKey(alias)}: {
|
|
32065
|
+
${fieldBlocks.join("\n")}
|
|
32066
|
+
},`);
|
|
32067
|
+
}
|
|
32068
|
+
if (tableBlocks.length === 0) return `export const OPT = {} as const;`;
|
|
32069
|
+
return `export const OPT = {
|
|
32070
|
+
${tableBlocks.join("\n")}
|
|
32071
|
+
} as const;`;
|
|
32072
|
+
}
|
|
32073
|
+
function generateAppFields(tables) {
|
|
32074
|
+
if (tables.length === 0) {
|
|
32075
|
+
return `${HEADER4}
|
|
32076
|
+
export const F = {} as const;
|
|
32077
|
+
|
|
32078
|
+
export const OPT = {} as const;
|
|
32079
|
+
|
|
32080
|
+
/** Field-id alias map (empty \u2014 no tables in scope). */
|
|
32081
|
+
export type AppFields = typeof F;
|
|
32082
|
+
/** Select-option alias map (empty \u2014 no tables in scope). */
|
|
32083
|
+
export type AppOptions = typeof OPT;
|
|
32084
|
+
`;
|
|
32085
|
+
}
|
|
32086
|
+
const aliased = aliasTables(tables);
|
|
32087
|
+
return `${HEADER4}
|
|
32088
|
+
${emitFieldMap(aliased)}
|
|
32089
|
+
|
|
32090
|
+
${emitOptionMap(aliased)}
|
|
32091
|
+
|
|
32092
|
+
/** Field-id alias map: \`F[<TABLE>][<field>]\` is the \`fld_\u2026\` id (literal-typed). */
|
|
32093
|
+
export type AppFields = typeof F;
|
|
32094
|
+
/** Select-option alias map: \`OPT[<TABLE>][<field>][<option>]\` is the \`opt_\u2026\` id. */
|
|
32095
|
+
export type AppOptions = typeof OPT;
|
|
32096
|
+
`;
|
|
32097
|
+
}
|
|
32098
|
+
|
|
32099
|
+
// src/app_workflow_check.ts
|
|
32100
|
+
import fs3 from "node:fs";
|
|
32101
|
+
import path4 from "node:path";
|
|
32102
|
+
import { createRequire } from "node:module";
|
|
32103
|
+
function workflowCheckCompilerOptions(tsApi) {
|
|
32104
|
+
return {
|
|
32105
|
+
target: tsApi.ScriptTarget.ES2022,
|
|
32106
|
+
module: tsApi.ModuleKind.ESNext,
|
|
32107
|
+
lib: ["lib.es2022.d.ts"],
|
|
32108
|
+
strict: true,
|
|
32109
|
+
noImplicitAny: true,
|
|
32110
|
+
strictNullChecks: true,
|
|
32111
|
+
noImplicitReturns: false,
|
|
32112
|
+
noEmit: true,
|
|
32113
|
+
allowJs: false,
|
|
32114
|
+
isolatedModules: false,
|
|
32115
|
+
skipLibCheck: true,
|
|
32116
|
+
moduleResolution: tsApi.ModuleResolutionKind.NodeNext,
|
|
32117
|
+
types: [],
|
|
32118
|
+
declaration: false
|
|
32119
|
+
};
|
|
32120
|
+
}
|
|
32121
|
+
function bodyLineOffset(wrappedSource) {
|
|
32122
|
+
const lines = wrappedSource.split("\n");
|
|
32123
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
32124
|
+
if (/^\s*async\s+function\s+__workflow\s*\(/.test(lines[i2])) return i2 + 1;
|
|
32125
|
+
}
|
|
32126
|
+
return 0;
|
|
32127
|
+
}
|
|
32128
|
+
async function loadProjectTypescript(projectDir) {
|
|
32129
|
+
const requireFromProject = createRequire(path4.join(projectDir, "package.json"));
|
|
32130
|
+
let resolved;
|
|
32131
|
+
try {
|
|
32132
|
+
resolved = requireFromProject.resolve("typescript");
|
|
32133
|
+
} catch {
|
|
32134
|
+
throw new Error(
|
|
32135
|
+
`Could not resolve 'typescript' from ${projectDir}. 'lotics app workflow check' type-checks bodies with the app's own compiler \u2014 run 'npm install' (the scaffold ships typescript as a devDependency).`
|
|
32136
|
+
);
|
|
32137
|
+
}
|
|
32138
|
+
const mod = await import(pathToImportUrl(resolved));
|
|
32139
|
+
return mod.default ?? mod;
|
|
32140
|
+
}
|
|
32141
|
+
function pathToImportUrl(p) {
|
|
32142
|
+
const resolved = path4.resolve(p);
|
|
32143
|
+
const prefixed = resolved.startsWith("/") ? resolved : `/${resolved}`;
|
|
32144
|
+
return `file://${prefixed.split(path4.sep).join("/")}`;
|
|
32145
|
+
}
|
|
32146
|
+
function checkOneWorkflowBody(tsApi, input) {
|
|
32147
|
+
const wrapped = fs3.readFileSync(input.bodyPath, "utf-8");
|
|
32148
|
+
const offset = bodyLineOffset(wrapped);
|
|
32149
|
+
const program = tsApi.createProgram({
|
|
32150
|
+
rootNames: [input.globalsPath, input.bodyPath],
|
|
32151
|
+
options: workflowCheckCompilerOptions(tsApi)
|
|
32152
|
+
});
|
|
32153
|
+
const bodyFile = program.getSourceFile(input.bodyPath);
|
|
32154
|
+
if (!bodyFile) {
|
|
32155
|
+
return [
|
|
32156
|
+
{
|
|
32157
|
+
line: 1,
|
|
32158
|
+
col: 1,
|
|
32159
|
+
code: "INTERNAL",
|
|
32160
|
+
message: `workflow check: body file was not loaded (${input.bodyPath})`
|
|
32161
|
+
}
|
|
32162
|
+
];
|
|
32163
|
+
}
|
|
32164
|
+
const issues = [];
|
|
32165
|
+
for (const d of tsApi.getPreEmitDiagnostics(program)) {
|
|
32166
|
+
if (d.file !== bodyFile) continue;
|
|
32167
|
+
let line = 1;
|
|
32168
|
+
let col = 1;
|
|
32169
|
+
if (typeof d.start === "number") {
|
|
32170
|
+
const lc2 = bodyFile.getLineAndCharacterOfPosition(d.start);
|
|
32171
|
+
line = Math.max(1, lc2.line + 1 - offset);
|
|
32172
|
+
col = lc2.character + 1;
|
|
32173
|
+
}
|
|
32174
|
+
issues.push({
|
|
32175
|
+
line,
|
|
32176
|
+
col,
|
|
32177
|
+
code: `TS${d.code}`,
|
|
32178
|
+
message: tsApi.flattenDiagnosticMessageText(d.messageText, "\n")
|
|
32179
|
+
});
|
|
32180
|
+
}
|
|
32181
|
+
return issues;
|
|
32182
|
+
}
|
|
32183
|
+
function checkWorkflowBodies(tsApi, aliases) {
|
|
32184
|
+
return aliases.map(({ alias, input }) => ({
|
|
32185
|
+
alias,
|
|
32186
|
+
bodyPath: input.bodyPath,
|
|
32187
|
+
issues: checkOneWorkflowBody(tsApi, input)
|
|
32188
|
+
}));
|
|
32189
|
+
}
|
|
32190
|
+
|
|
31781
32191
|
// src/app_commands.ts
|
|
31782
32192
|
async function fetchLatestNpmVersion(packageName) {
|
|
31783
32193
|
try {
|
|
@@ -31795,6 +32205,100 @@ async function fetchLatestNpmVersion(packageName) {
|
|
|
31795
32205
|
return null;
|
|
31796
32206
|
}
|
|
31797
32207
|
}
|
|
32208
|
+
var WORKFLOWS_DIR = path5.join("src", "workflows");
|
|
32209
|
+
var WORKFLOW_GLOBALS_DIR = path5.join(".lotics", "workflows");
|
|
32210
|
+
var WORKFLOW_TSCONFIG_EXCLUDES = ["src/workflows", ".lotics/workflows"];
|
|
32211
|
+
var FALLBACK_ENVELOPE_PREFIX = "async function __workflow(): Promise<__WorkflowReturn | void> {\n";
|
|
32212
|
+
var FALLBACK_ENVELOPE_SUFFIX = "\n}";
|
|
32213
|
+
function workflowFileHeader(alias) {
|
|
32214
|
+
const refPath = path5.join("..", "..", WORKFLOW_GLOBALS_DIR, `${alias}.globals.d.ts`).split(path5.sep).join("/");
|
|
32215
|
+
return `/// <reference path="${refPath}" />
|
|
32216
|
+
// Auto-pulled workflow body for "${alias}". Edit the BODY between the wrapper
|
|
32217
|
+
// lines below, then check + push with:
|
|
32218
|
+
// lotics app workflow check ${alias}
|
|
32219
|
+
// lotics app workflow set ${alias}
|
|
32220
|
+
// The push goes through set_app_workflow, where the SERVER verifies the body.
|
|
32221
|
+
// The __workflow wrapper + the reference above are CLI bookkeeping (stripped on
|
|
32222
|
+
// set) \u2014 they only make the body typecheck locally against the workspace types.
|
|
32223
|
+
// Do NOT rename this file \u2014 the filename is the alias the binding is keyed by.
|
|
32224
|
+
export {};
|
|
32225
|
+
`;
|
|
32226
|
+
}
|
|
32227
|
+
function workflowFilePath(projectDir, alias) {
|
|
32228
|
+
return path5.join(projectDir, WORKFLOWS_DIR, `${alias}.ts`);
|
|
32229
|
+
}
|
|
32230
|
+
function workflowGlobalsPath(projectDir, alias) {
|
|
32231
|
+
return path5.join(projectDir, WORKFLOW_GLOBALS_DIR, `${alias}.globals.d.ts`);
|
|
32232
|
+
}
|
|
32233
|
+
function writeWorkflowGlobals(projectDir, alias, dts) {
|
|
32234
|
+
const dir = path5.join(projectDir, WORKFLOW_GLOBALS_DIR);
|
|
32235
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
32236
|
+
const file = workflowGlobalsPath(projectDir, alias);
|
|
32237
|
+
fs4.writeFileSync(file, `${dts.replace(/\s+$/, "")}
|
|
32238
|
+
`);
|
|
32239
|
+
return file;
|
|
32240
|
+
}
|
|
32241
|
+
function writeWorkflowFile(projectDir, alias, source, envelope = { prefix: FALLBACK_ENVELOPE_PREFIX, suffix: FALLBACK_ENVELOPE_SUFFIX }) {
|
|
32242
|
+
const dir = path5.join(projectDir, WORKFLOWS_DIR);
|
|
32243
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
32244
|
+
const file = workflowFilePath(projectDir, alias);
|
|
32245
|
+
const body = source.replace(/\s+$/, "");
|
|
32246
|
+
fs4.writeFileSync(file, `${workflowFileHeader(alias)}
|
|
32247
|
+
${envelope.prefix}${body}${envelope.suffix}
|
|
32248
|
+
`);
|
|
32249
|
+
return file;
|
|
32250
|
+
}
|
|
32251
|
+
var WORKFLOW_WRAPPER_OPENER = /^\s*async\s+function\s+__workflow\s*\(/;
|
|
32252
|
+
function stripWorkflowHeader(content) {
|
|
32253
|
+
const lines = content.split("\n");
|
|
32254
|
+
let headerEnd = 0;
|
|
32255
|
+
while (headerEnd < lines.length && /^\s*\/\//.test(lines[headerEnd])) headerEnd++;
|
|
32256
|
+
while (headerEnd < lines.length && /^\s*export\s*\{\s*\}\s*;?\s*$/.test(lines[headerEnd])) headerEnd++;
|
|
32257
|
+
while (headerEnd < lines.length && lines[headerEnd].trim() === "") headerEnd++;
|
|
32258
|
+
let start;
|
|
32259
|
+
if (headerEnd < lines.length && WORKFLOW_WRAPPER_OPENER.test(lines[headerEnd])) {
|
|
32260
|
+
start = headerEnd;
|
|
32261
|
+
} else if (lines.length > 0 && WORKFLOW_WRAPPER_OPENER.test(lines[0])) {
|
|
32262
|
+
start = 0;
|
|
32263
|
+
} else {
|
|
32264
|
+
return content.replace(/\s+$/, "");
|
|
32265
|
+
}
|
|
32266
|
+
let end = lines.length - 1;
|
|
32267
|
+
while (end > start && lines[end].trim() === "") end--;
|
|
32268
|
+
if (lines[end]?.trim() === "}") {
|
|
32269
|
+
return lines.slice(start + 1, end).join("\n").replace(/\s+$/, "");
|
|
32270
|
+
}
|
|
32271
|
+
return lines.slice(start + 1).join("\n").replace(/\s+$/, "");
|
|
32272
|
+
}
|
|
32273
|
+
async function writeWorkflowFiles(client, projectDir, app_id, aliases) {
|
|
32274
|
+
const written = [];
|
|
32275
|
+
for (const alias of aliases) {
|
|
32276
|
+
const res = await client.getAppWorkflow(app_id, alias);
|
|
32277
|
+
const source = res.error || res.result === null || typeof res.result !== "object" ? null : res.result.source;
|
|
32278
|
+
if (typeof source !== "string" || source.trim() === "") {
|
|
32279
|
+
console.error(
|
|
32280
|
+
`\u26A0 Skipped src/workflows/${alias}.ts \u2014 the server returned no readable source (${res.error ?? "legacy workflow with no rendered body"}).`
|
|
32281
|
+
);
|
|
32282
|
+
continue;
|
|
32283
|
+
}
|
|
32284
|
+
const envelope = await fetchWorkflowGlobals(client, projectDir, app_id, alias);
|
|
32285
|
+
writeWorkflowFile(projectDir, alias, source, envelope);
|
|
32286
|
+
written.push(alias);
|
|
32287
|
+
}
|
|
32288
|
+
return written;
|
|
32289
|
+
}
|
|
32290
|
+
async function fetchWorkflowGlobals(client, projectDir, app_id, alias) {
|
|
32291
|
+
try {
|
|
32292
|
+
const { dts, envelope_prefix, envelope_suffix } = await client.getAppWorkflowDts(app_id, alias);
|
|
32293
|
+
writeWorkflowGlobals(projectDir, alias, dts);
|
|
32294
|
+
return { prefix: envelope_prefix, suffix: envelope_suffix };
|
|
32295
|
+
} catch (err2) {
|
|
32296
|
+
console.error(
|
|
32297
|
+
`\u26A0 Could not fetch workflow types for "${alias}" (${err2 instanceof Error ? err2.message : String(err2)}). Wrote the body with the fallback wrapper; its local typecheck may be degraded.`
|
|
32298
|
+
);
|
|
32299
|
+
return { prefix: FALLBACK_ENVELOPE_PREFIX, suffix: FALLBACK_ENVELOPE_SUFFIX };
|
|
32300
|
+
}
|
|
32301
|
+
}
|
|
31798
32302
|
function runTar(args, cwd) {
|
|
31799
32303
|
return new Promise((resolve, reject2) => {
|
|
31800
32304
|
const proc = spawn2("tar", args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
|
|
@@ -31811,7 +32315,7 @@ function runTar(args, cwd) {
|
|
|
31811
32315
|
}
|
|
31812
32316
|
function runNpm(args, cwd) {
|
|
31813
32317
|
return new Promise((resolve, reject2) => {
|
|
31814
|
-
const proc = spawn2("npm", args, { cwd, stdio: "inherit" });
|
|
32318
|
+
const proc = spawn2("npm", args, { cwd, stdio: "inherit", env: ipv4ChildEnv(process.env) });
|
|
31815
32319
|
proc.on("error", reject2);
|
|
31816
32320
|
proc.on("exit", (code) => {
|
|
31817
32321
|
if (code === 0) resolve();
|
|
@@ -31820,11 +32324,11 @@ function runNpm(args, cwd) {
|
|
|
31820
32324
|
});
|
|
31821
32325
|
}
|
|
31822
32326
|
function readAppMeta(projectDir) {
|
|
31823
|
-
const pkgPath2 =
|
|
31824
|
-
if (!
|
|
32327
|
+
const pkgPath2 = path5.join(projectDir, "package.json");
|
|
32328
|
+
if (!fs4.existsSync(pkgPath2)) {
|
|
31825
32329
|
throw new Error(`No package.json found at ${projectDir}. Run 'lotics app create' first.`);
|
|
31826
32330
|
}
|
|
31827
|
-
const pkg2 = JSON.parse(
|
|
32331
|
+
const pkg2 = JSON.parse(fs4.readFileSync(pkgPath2, "utf-8"));
|
|
31828
32332
|
if (!pkg2.lotics?.app_id || !pkg2.lotics.workspace_id) {
|
|
31829
32333
|
throw new Error(
|
|
31830
32334
|
`package.json is missing the 'lotics' field with app_id and workspace_id. Was this folder created with 'lotics app create'?`
|
|
@@ -31842,27 +32346,109 @@ function readAppMeta(projectDir) {
|
|
|
31842
32346
|
};
|
|
31843
32347
|
}
|
|
31844
32348
|
function writeAppMeta(projectDir, meta) {
|
|
31845
|
-
const pkgPath2 =
|
|
31846
|
-
const pkg2 = JSON.parse(
|
|
32349
|
+
const pkgPath2 = path5.join(projectDir, "package.json");
|
|
32350
|
+
const pkg2 = JSON.parse(fs4.readFileSync(pkgPath2, "utf-8"));
|
|
31847
32351
|
pkg2.lotics = meta;
|
|
31848
|
-
|
|
32352
|
+
fs4.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
|
|
31849
32353
|
}
|
|
31850
|
-
function
|
|
31851
|
-
const
|
|
31852
|
-
|
|
31853
|
-
|
|
31854
|
-
|
|
31855
|
-
|
|
31856
|
-
|
|
31857
|
-
|
|
31858
|
-
|
|
31859
|
-
|
|
31860
|
-
|
|
31861
|
-
|
|
31862
|
-
|
|
31863
|
-
|
|
32354
|
+
function ensureWorkflowTsconfigExcludes(projectDir) {
|
|
32355
|
+
const tsconfigPath = path5.join(projectDir, "tsconfig.json");
|
|
32356
|
+
if (!fs4.existsSync(tsconfigPath)) {
|
|
32357
|
+
console.error(
|
|
32358
|
+
`\u26A0 No tsconfig.json at ${projectDir} \u2014 could not ensure ${WORKFLOW_TSCONFIG_EXCLUDES.join(", ")} are excluded. Add them to your tsconfig's "exclude" so npm run typecheck skips the workflow bodies.`
|
|
32359
|
+
);
|
|
32360
|
+
return;
|
|
32361
|
+
}
|
|
32362
|
+
let parsed;
|
|
32363
|
+
try {
|
|
32364
|
+
parsed = JSON.parse(fs4.readFileSync(tsconfigPath, "utf-8"));
|
|
32365
|
+
} catch (err2) {
|
|
32366
|
+
console.error(
|
|
32367
|
+
`\u26A0 Could not parse tsconfig.json (${err2 instanceof Error ? err2.message : String(err2)}) \u2014 add ${WORKFLOW_TSCONFIG_EXCLUDES.join(", ")} to its "exclude" manually so npm run typecheck skips the workflow bodies.`
|
|
32368
|
+
);
|
|
32369
|
+
return;
|
|
32370
|
+
}
|
|
32371
|
+
if (!parsed || typeof parsed !== "object") return;
|
|
32372
|
+
const current = Array.isArray(parsed.exclude) ? parsed.exclude : [];
|
|
32373
|
+
const currentStrings = current.filter((e) => typeof e === "string");
|
|
32374
|
+
const toAdd = WORKFLOW_TSCONFIG_EXCLUDES.filter((g) => !currentStrings.includes(g));
|
|
32375
|
+
if (toAdd.length === 0) return;
|
|
32376
|
+
parsed.exclude = [...currentStrings, ...toAdd];
|
|
32377
|
+
fs4.writeFileSync(tsconfigPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
32378
|
+
console.error(
|
|
32379
|
+
`Patched tsconfig.json: added ${toAdd.join(", ")} to "exclude" so npm run typecheck skips the workflow bodies (check them with: lotics app workflow check).`
|
|
31864
32380
|
);
|
|
31865
32381
|
}
|
|
32382
|
+
function writeAppDts(projectDir, manifest) {
|
|
32383
|
+
const dotLotics = path5.join(projectDir, ".lotics");
|
|
32384
|
+
fs4.mkdirSync(dotLotics, { recursive: true });
|
|
32385
|
+
const written = [
|
|
32386
|
+
[path5.join(dotLotics, "app_workflows.d.ts"), generateAppWorkflowsDts(manifest.workflows)],
|
|
32387
|
+
[path5.join(dotLotics, "app_queries.d.ts"), generateAppQueriesDts(manifest.queries)],
|
|
32388
|
+
[path5.join(dotLotics, "app_agents.d.ts"), generateAppAgentsDts(manifest.agents)]
|
|
32389
|
+
];
|
|
32390
|
+
for (const [file, content] of written) fs4.writeFileSync(file, content);
|
|
32391
|
+
return written.map(([file]) => file);
|
|
32392
|
+
}
|
|
32393
|
+
function readCodegenTablesAllowlist(projectDir) {
|
|
32394
|
+
const pkg2 = JSON.parse(fs4.readFileSync(path5.join(projectDir, "package.json"), "utf-8"));
|
|
32395
|
+
const tables = pkg2.lotics?.codegen?.tables;
|
|
32396
|
+
return Array.isArray(tables) ? tables.filter((t) => typeof t === "string") : [];
|
|
32397
|
+
}
|
|
32398
|
+
function resolveCodegenTableIds(projectDir, queries) {
|
|
32399
|
+
const ids = /* @__PURE__ */ new Set();
|
|
32400
|
+
for (const decl of Object.values(queries)) {
|
|
32401
|
+
for (const id of collectQueryTableIds(decl.ast)) ids.add(id);
|
|
32402
|
+
}
|
|
32403
|
+
for (const id of readCodegenTablesAllowlist(projectDir)) ids.add(id);
|
|
32404
|
+
return [...ids];
|
|
32405
|
+
}
|
|
32406
|
+
function writeAppFields(projectDir, tables) {
|
|
32407
|
+
const dotLotics = path5.join(projectDir, ".lotics");
|
|
32408
|
+
fs4.mkdirSync(dotLotics, { recursive: true });
|
|
32409
|
+
const file = path5.join(dotLotics, "app_fields.ts");
|
|
32410
|
+
fs4.writeFileSync(file, generateAppFields(tables));
|
|
32411
|
+
return file;
|
|
32412
|
+
}
|
|
32413
|
+
async function appCodegen(args) {
|
|
32414
|
+
const projectDir = path5.resolve(args.projectDir ?? process.cwd());
|
|
32415
|
+
const meta = readAppMeta(projectDir);
|
|
32416
|
+
const dtsPaths = writeAppDts(projectDir, {
|
|
32417
|
+
workflows: meta.workflows,
|
|
32418
|
+
queries: meta.queries,
|
|
32419
|
+
agents: meta.agents
|
|
32420
|
+
});
|
|
32421
|
+
for (const p of dtsPaths) console.error(`Regenerated ${p}`);
|
|
32422
|
+
if (!args.client) {
|
|
32423
|
+
console.error(
|
|
32424
|
+
"Skipped .lotics/app_fields.ts \u2014 no workspace credentials resolved. Run authenticated (or set LOTICS_API_KEY) to regenerate field/option ids."
|
|
32425
|
+
);
|
|
32426
|
+
return;
|
|
32427
|
+
}
|
|
32428
|
+
const tableIds = resolveCodegenTableIds(projectDir, meta.queries ?? {});
|
|
32429
|
+
try {
|
|
32430
|
+
const tables = await args.client.getWorkspaceSchema(tableIds);
|
|
32431
|
+
const fieldsPath = writeAppFields(projectDir, tables);
|
|
32432
|
+
console.error(`Regenerated ${fieldsPath} (${tables.length} table${tables.length === 1 ? "" : "s"})`);
|
|
32433
|
+
} catch (err2) {
|
|
32434
|
+
console.error(
|
|
32435
|
+
`\u26A0 Could not fetch the workspace schema (${err2 instanceof Error ? err2.message : String(err2)}). Kept the existing .lotics/app_fields.ts.`
|
|
32436
|
+
);
|
|
32437
|
+
}
|
|
32438
|
+
await refreshWorkflowGlobals(args.client, projectDir, meta.app_id, Object.keys(meta.workflows ?? {}));
|
|
32439
|
+
if (Object.keys(meta.workflows ?? {}).length > 0) ensureWorkflowTsconfigExcludes(projectDir);
|
|
32440
|
+
}
|
|
32441
|
+
async function refreshWorkflowGlobals(client, projectDir, app_id, aliases) {
|
|
32442
|
+
for (const alias of aliases) {
|
|
32443
|
+
const file = workflowFilePath(projectDir, alias);
|
|
32444
|
+
if (!fs4.existsSync(file)) continue;
|
|
32445
|
+
const body = stripWorkflowHeader(fs4.readFileSync(file, "utf-8"));
|
|
32446
|
+
if (body.trim() === "") continue;
|
|
32447
|
+
const envelope = await fetchWorkflowGlobals(client, projectDir, app_id, alias);
|
|
32448
|
+
writeWorkflowFile(projectDir, alias, body, envelope);
|
|
32449
|
+
console.error(`Refreshed ${path5.relative(projectDir, file)} + its workflow types`);
|
|
32450
|
+
}
|
|
32451
|
+
}
|
|
31866
32452
|
function stampPulledManifest(projectDir, args) {
|
|
31867
32453
|
writeAppMeta(projectDir, {
|
|
31868
32454
|
app_id: args.app_id,
|
|
@@ -31881,21 +32467,21 @@ async function downloadToFile(url, destPath) {
|
|
|
31881
32467
|
throw new Error(`Failed to download ${url}: ${response.status}`);
|
|
31882
32468
|
}
|
|
31883
32469
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
31884
|
-
|
|
32470
|
+
fs4.writeFileSync(destPath, buffer);
|
|
31885
32471
|
}
|
|
31886
32472
|
function appDirName(name) {
|
|
31887
32473
|
const cleaned = name.replace(/[/\\]+/g, "-").replace(/[<>:"|?*]/g, "").replace(/\s+/g, " ").trim().replace(/^[.\s-]+|[.\s-]+$/g, "");
|
|
31888
32474
|
return cleaned || "app";
|
|
31889
32475
|
}
|
|
31890
32476
|
async function appCreate(client, args) {
|
|
31891
|
-
const targetPath =
|
|
31892
|
-
if (
|
|
31893
|
-
const entries =
|
|
32477
|
+
const targetPath = path5.resolve(args.targetPath ?? appDirName(args.name));
|
|
32478
|
+
if (fs4.existsSync(targetPath)) {
|
|
32479
|
+
const entries = fs4.readdirSync(targetPath);
|
|
31894
32480
|
if (entries.length > 0) {
|
|
31895
32481
|
throw new Error(`Target directory ${targetPath} is not empty.`);
|
|
31896
32482
|
}
|
|
31897
32483
|
}
|
|
31898
|
-
|
|
32484
|
+
fs4.mkdirSync(targetPath, { recursive: true });
|
|
31899
32485
|
const app = await client.createApp({ name: args.name });
|
|
31900
32486
|
console.error(`Created app: ${app.name} (${app.id})`);
|
|
31901
32487
|
const [uiLatest, sdkLatest] = await Promise.all([
|
|
@@ -31910,9 +32496,9 @@ async function appCreate(client, args) {
|
|
|
31910
32496
|
sdk_version: sdkLatest ? `^${sdkLatest}` : void 0
|
|
31911
32497
|
});
|
|
31912
32498
|
for (const file of files) {
|
|
31913
|
-
const fullPath =
|
|
31914
|
-
|
|
31915
|
-
|
|
32499
|
+
const fullPath = path5.join(targetPath, file.path);
|
|
32500
|
+
fs4.mkdirSync(path5.dirname(fullPath), { recursive: true });
|
|
32501
|
+
fs4.writeFileSync(fullPath, file.content);
|
|
31916
32502
|
}
|
|
31917
32503
|
console.error(`Scaffolded ${files.length} files into ${targetPath}`);
|
|
31918
32504
|
writeAppDts(targetPath, {});
|
|
@@ -31925,7 +32511,7 @@ async function appCreate(client, args) {
|
|
|
31925
32511
|
});
|
|
31926
32512
|
console.error(`
|
|
31927
32513
|
Ready. Next steps:`);
|
|
31928
|
-
console.error(` cd ${
|
|
32514
|
+
console.error(` cd ${path5.relative(process.cwd(), targetPath) || "."}`);
|
|
31929
32515
|
console.error(` # edit src/App.tsx, then:`);
|
|
31930
32516
|
console.error(` lotics app deploy`);
|
|
31931
32517
|
}
|
|
@@ -31940,6 +32526,21 @@ async function appRename(client, args) {
|
|
|
31940
32526
|
if (res.error) throw new Error(res.error);
|
|
31941
32527
|
console.error(`App renamed: "${args.name}" (${meta.app_id})`);
|
|
31942
32528
|
}
|
|
32529
|
+
function defaultPullTarget(appId, appName) {
|
|
32530
|
+
const cwdPkgPath = path5.join(process.cwd(), "package.json");
|
|
32531
|
+
if (fs4.existsSync(cwdPkgPath)) {
|
|
32532
|
+
let pkg2 = null;
|
|
32533
|
+
try {
|
|
32534
|
+
pkg2 = JSON.parse(fs4.readFileSync(cwdPkgPath, "utf-8"));
|
|
32535
|
+
} catch (err2) {
|
|
32536
|
+
console.error(
|
|
32537
|
+
`\u26A0 Could not parse ${cwdPkgPath} (${err2 instanceof Error ? err2.message : String(err2)}) \u2014 pulling into a fresh ${appDirName(appName)}/ subdir.`
|
|
32538
|
+
);
|
|
32539
|
+
}
|
|
32540
|
+
if (pkg2?.lotics?.app_id === appId) return process.cwd();
|
|
32541
|
+
}
|
|
32542
|
+
return appDirName(appName);
|
|
32543
|
+
}
|
|
31943
32544
|
async function appPull(client, args) {
|
|
31944
32545
|
const app = await client.getApp(args.app_id);
|
|
31945
32546
|
if (!app.current_version_id) {
|
|
@@ -31949,16 +32550,16 @@ async function appPull(client, args) {
|
|
|
31949
32550
|
}
|
|
31950
32551
|
const version = await client.getAppVersion(app.id, app.current_version_id);
|
|
31951
32552
|
const sourceUrl = await client.getAppVersionSourceUrl(app.id, version.id);
|
|
31952
|
-
const targetPath =
|
|
31953
|
-
|
|
31954
|
-
const tmpFile =
|
|
32553
|
+
const targetPath = path5.resolve(args.targetPath ?? defaultPullTarget(app.id, app.name));
|
|
32554
|
+
fs4.mkdirSync(targetPath, { recursive: true });
|
|
32555
|
+
const tmpFile = path5.join(tmpdir(), `lotics-app-${app.id}-${Date.now()}.tar.gz`);
|
|
31955
32556
|
console.error(`Downloading source archive...`);
|
|
31956
32557
|
await downloadToFile(sourceUrl, tmpFile);
|
|
31957
32558
|
try {
|
|
31958
32559
|
console.error(`Extracting to ${targetPath}...`);
|
|
31959
32560
|
await runTar(["-xzf", tmpFile, "-C", targetPath], targetPath);
|
|
31960
32561
|
} finally {
|
|
31961
|
-
if (
|
|
32562
|
+
if (fs4.existsSync(tmpFile)) fs4.unlinkSync(tmpFile);
|
|
31962
32563
|
}
|
|
31963
32564
|
stampPulledManifest(targetPath, {
|
|
31964
32565
|
app_id: app.id,
|
|
@@ -31969,11 +32570,21 @@ async function appPull(client, args) {
|
|
|
31969
32570
|
queries: app.queries ?? {},
|
|
31970
32571
|
agents: app.agents ?? {}
|
|
31971
32572
|
});
|
|
32573
|
+
const aliases = Object.keys(app.workflows ?? {});
|
|
32574
|
+
if (aliases.length > 0) {
|
|
32575
|
+
const written = await writeWorkflowFiles(client, targetPath, app.id, aliases);
|
|
32576
|
+
if (written.length > 0) {
|
|
32577
|
+
console.error(
|
|
32578
|
+
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/ (${written.join(", ")})`
|
|
32579
|
+
);
|
|
32580
|
+
}
|
|
32581
|
+
ensureWorkflowTsconfigExcludes(targetPath);
|
|
32582
|
+
}
|
|
31972
32583
|
console.error(`Installing npm dependencies...`);
|
|
31973
32584
|
await runNpm(["install"], targetPath);
|
|
31974
32585
|
console.error(`
|
|
31975
32586
|
Ready. Next steps:`);
|
|
31976
|
-
console.error(` cd ${
|
|
32587
|
+
console.error(` cd ${path5.relative(process.cwd(), targetPath) || "."}`);
|
|
31977
32588
|
console.error(` # edit src/App.tsx`);
|
|
31978
32589
|
console.error(` lotics app deploy`);
|
|
31979
32590
|
}
|
|
@@ -31989,21 +32600,21 @@ function undeclaredCapabilities(sourceText, declared) {
|
|
|
31989
32600
|
return used;
|
|
31990
32601
|
}
|
|
31991
32602
|
function readAppSourceText(projectDir) {
|
|
31992
|
-
const srcDir =
|
|
31993
|
-
if (!
|
|
32603
|
+
const srcDir = path5.join(projectDir, "src");
|
|
32604
|
+
if (!fs4.existsSync(srcDir)) return "";
|
|
31994
32605
|
const parts = [];
|
|
31995
|
-
const
|
|
31996
|
-
for (const entry of
|
|
31997
|
-
const full =
|
|
31998
|
-
if (entry.isDirectory())
|
|
31999
|
-
else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) parts.push(
|
|
32606
|
+
const walk2 = (dir) => {
|
|
32607
|
+
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
32608
|
+
const full = path5.join(dir, entry.name);
|
|
32609
|
+
if (entry.isDirectory()) walk2(full);
|
|
32610
|
+
else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) parts.push(fs4.readFileSync(full, "utf8"));
|
|
32000
32611
|
}
|
|
32001
32612
|
};
|
|
32002
|
-
|
|
32613
|
+
walk2(srcDir);
|
|
32003
32614
|
return parts.join("\n");
|
|
32004
32615
|
}
|
|
32005
32616
|
async function appDeploy(client, args) {
|
|
32006
|
-
const projectDir =
|
|
32617
|
+
const projectDir = path5.resolve(args.projectDir ?? process.cwd());
|
|
32007
32618
|
const meta = readAppMeta(projectDir);
|
|
32008
32619
|
const undeclared = undeclaredCapabilities(readAppSourceText(projectDir), meta.capabilities);
|
|
32009
32620
|
if (undeclared.length > 0) {
|
|
@@ -32017,14 +32628,14 @@ async function appDeploy(client, args) {
|
|
|
32017
32628
|
writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries, agents: meta.agents });
|
|
32018
32629
|
console.error("Building...");
|
|
32019
32630
|
await runNpm(["run", "build"], projectDir);
|
|
32020
|
-
const distDir =
|
|
32021
|
-
if (!
|
|
32631
|
+
const distDir = path5.join(projectDir, "dist");
|
|
32632
|
+
if (!fs4.existsSync(distDir)) {
|
|
32022
32633
|
throw new Error(
|
|
32023
32634
|
`Build did not produce a dist/ directory in ${projectDir}. Check that 'npm run build' is configured correctly (Vite/etc. defaults to dist/).`
|
|
32024
32635
|
);
|
|
32025
32636
|
}
|
|
32026
|
-
const tmpSource =
|
|
32027
|
-
const tmpDist =
|
|
32637
|
+
const tmpSource = path5.join(tmpdir(), `lotics-source-${Date.now()}.tar.gz`);
|
|
32638
|
+
const tmpDist = path5.join(tmpdir(), `lotics-dist-${Date.now()}.tar.gz`);
|
|
32028
32639
|
try {
|
|
32029
32640
|
console.error("Packaging source...");
|
|
32030
32641
|
await runTar(
|
|
@@ -32046,8 +32657,8 @@ async function appDeploy(client, args) {
|
|
|
32046
32657
|
try {
|
|
32047
32658
|
const result = await client.deployAppVersion({
|
|
32048
32659
|
app_id: meta.app_id,
|
|
32049
|
-
source_archive:
|
|
32050
|
-
dist_archive:
|
|
32660
|
+
source_archive: fs4.readFileSync(tmpSource),
|
|
32661
|
+
dist_archive: fs4.readFileSync(tmpDist),
|
|
32051
32662
|
prev_version_id: meta.current_version_id,
|
|
32052
32663
|
message: args.message,
|
|
32053
32664
|
// Sync apps.queries from the manifest. Server validates each query
|
|
@@ -32081,8 +32692,8 @@ async function appDeploy(client, args) {
|
|
|
32081
32692
|
throw err2;
|
|
32082
32693
|
}
|
|
32083
32694
|
} finally {
|
|
32084
|
-
if (
|
|
32085
|
-
if (
|
|
32695
|
+
if (fs4.existsSync(tmpSource)) fs4.unlinkSync(tmpSource);
|
|
32696
|
+
if (fs4.existsSync(tmpDist)) fs4.unlinkSync(tmpDist);
|
|
32086
32697
|
}
|
|
32087
32698
|
}
|
|
32088
32699
|
async function warnIfUnbranded(client, appId) {
|
|
@@ -32103,7 +32714,7 @@ async function warnIfUnbranded(client, appId) {
|
|
|
32103
32714
|
}
|
|
32104
32715
|
}
|
|
32105
32716
|
async function appDev(client, args) {
|
|
32106
|
-
const projectDir =
|
|
32717
|
+
const projectDir = path5.resolve(args.projectDir ?? process.cwd());
|
|
32107
32718
|
const meta = readAppMeta(projectDir);
|
|
32108
32719
|
writeAppDts(projectDir, { workflows: meta.workflows, queries: meta.queries, agents: meta.agents });
|
|
32109
32720
|
const app = await client.getApp(meta.app_id);
|
|
@@ -32146,6 +32757,284 @@ async function appDev(client, args) {
|
|
|
32146
32757
|
console.error("\nStopping\u2026");
|
|
32147
32758
|
await handle.stop();
|
|
32148
32759
|
}
|
|
32760
|
+
function parseCreatedRecords(value) {
|
|
32761
|
+
if (!Array.isArray(value)) return [];
|
|
32762
|
+
const groups = [];
|
|
32763
|
+
for (const entry of value) {
|
|
32764
|
+
if (!entry || typeof entry !== "object") continue;
|
|
32765
|
+
const tableId = entry.table_id;
|
|
32766
|
+
const recordIds = entry.record_ids;
|
|
32767
|
+
if (typeof tableId !== "string" || !Array.isArray(recordIds)) continue;
|
|
32768
|
+
const ids = recordIds.filter((id) => typeof id === "string");
|
|
32769
|
+
if (ids.length > 0) groups.push({ table_id: tableId, record_ids: ids });
|
|
32770
|
+
}
|
|
32771
|
+
return groups;
|
|
32772
|
+
}
|
|
32773
|
+
function parseIrreversibleToolNames(value) {
|
|
32774
|
+
if (!Array.isArray(value)) return [];
|
|
32775
|
+
const names = [];
|
|
32776
|
+
for (const entry of value) {
|
|
32777
|
+
if (!entry || typeof entry !== "object") continue;
|
|
32778
|
+
const name = entry.tool_name;
|
|
32779
|
+
if (typeof name === "string" && !names.includes(name)) names.push(name);
|
|
32780
|
+
}
|
|
32781
|
+
return names;
|
|
32782
|
+
}
|
|
32783
|
+
function printSideEffects(summary) {
|
|
32784
|
+
const created = parseCreatedRecords(summary.created_records);
|
|
32785
|
+
const irreversibleTools = parseIrreversibleToolNames(summary.irreversible_tool_calls);
|
|
32786
|
+
const subWorkflows = summary.sub_workflows_possible === true;
|
|
32787
|
+
console.error("\nCreated records:");
|
|
32788
|
+
if (created.length === 0) {
|
|
32789
|
+
console.error(" (none with ids to clean up)");
|
|
32790
|
+
} else {
|
|
32791
|
+
for (const group of created) {
|
|
32792
|
+
console.error(` ${group.table_id}: ${group.record_ids.length} record(s)`);
|
|
32793
|
+
const payload = JSON.stringify({ table_id: group.table_id, record_ids: group.record_ids });
|
|
32794
|
+
console.error(` lotics run delete_records '${payload}'`);
|
|
32795
|
+
}
|
|
32796
|
+
}
|
|
32797
|
+
const irreversiblePart = irreversibleTools.length > 0 ? `Could NOT auto-undo (clean up manually): ${irreversibleTools.join(", ")}.` : "Could NOT auto-undo: none.";
|
|
32798
|
+
const subPart = subWorkflows ? " Sub-workflows may have run (after_* table workflows) \u2014 their effects are NOT in this list." : "";
|
|
32799
|
+
console.error(`
|
|
32800
|
+
${irreversiblePart}${subPart}`);
|
|
32801
|
+
}
|
|
32802
|
+
async function cleanupCreatedRecords(client, created) {
|
|
32803
|
+
if (created.length === 0) {
|
|
32804
|
+
console.error("\nNo created records to clean up.");
|
|
32805
|
+
return true;
|
|
32806
|
+
}
|
|
32807
|
+
console.error("\nCleaning up created records (delete_records \u2014 records only):");
|
|
32808
|
+
let allDeleted = true;
|
|
32809
|
+
for (const group of created) {
|
|
32810
|
+
const res = await client.execute("delete_records", {
|
|
32811
|
+
table_id: group.table_id,
|
|
32812
|
+
record_ids: group.record_ids
|
|
32813
|
+
});
|
|
32814
|
+
if (res.error) {
|
|
32815
|
+
console.error(` \u2717 ${group.table_id}: ${res.error}`);
|
|
32816
|
+
allDeleted = false;
|
|
32817
|
+
} else {
|
|
32818
|
+
console.error(` \u2713 ${group.table_id}: deleted ${group.record_ids.length} record(s)`);
|
|
32819
|
+
}
|
|
32820
|
+
}
|
|
32821
|
+
return allDeleted;
|
|
32822
|
+
}
|
|
32823
|
+
async function appExecuteWorkflow(client, args) {
|
|
32824
|
+
const meta = readAppMeta(process.cwd());
|
|
32825
|
+
const result = await client.appWorkflow(meta.app_id, args.alias, args.inputs);
|
|
32826
|
+
console.log(JSON.stringify(result, null, 2));
|
|
32827
|
+
const status = typeof result.status === "string" ? result.status : "unknown";
|
|
32828
|
+
const message = typeof result.message === "string" ? result.message : "";
|
|
32829
|
+
console.error(`Workflow "${args.alias}" \u2192 ${status}${message ? `: ${message}` : ""}`);
|
|
32830
|
+
let cleanupFailed = false;
|
|
32831
|
+
if ((args.printCreated || args.cleanup) && result.side_effects) {
|
|
32832
|
+
printSideEffects(result.side_effects);
|
|
32833
|
+
if (args.cleanup) {
|
|
32834
|
+
const allDeleted = await cleanupCreatedRecords(
|
|
32835
|
+
client,
|
|
32836
|
+
parseCreatedRecords(result.side_effects.created_records)
|
|
32837
|
+
);
|
|
32838
|
+
cleanupFailed = !allDeleted;
|
|
32839
|
+
}
|
|
32840
|
+
} else if (args.printCreated || args.cleanup) {
|
|
32841
|
+
console.error("\n(no side-effect summary returned by the server)");
|
|
32842
|
+
}
|
|
32843
|
+
if (status === "error" || cleanupFailed) process.exit(1);
|
|
32844
|
+
}
|
|
32845
|
+
async function appWorkflowSet(client, args) {
|
|
32846
|
+
const projectDir = process.cwd();
|
|
32847
|
+
const meta = readAppMeta(projectDir);
|
|
32848
|
+
const declaration = meta.workflows?.[args.alias];
|
|
32849
|
+
if (!declaration) {
|
|
32850
|
+
console.error(
|
|
32851
|
+
`No workflow "${args.alias}" in package.json#lotics.workflows. Bind it first (set_app_workflow), then 'lotics app pull' to write its body and manifest entry.`
|
|
32852
|
+
);
|
|
32853
|
+
process.exit(1);
|
|
32854
|
+
}
|
|
32855
|
+
const file = workflowFilePath(projectDir, args.alias);
|
|
32856
|
+
if (!fs4.existsSync(file)) {
|
|
32857
|
+
console.error(
|
|
32858
|
+
`No workflow body at ${path5.relative(projectDir, file)}. Run 'lotics app pull ${meta.app_id}' to write src/workflows/${args.alias}.ts, then edit it.`
|
|
32859
|
+
);
|
|
32860
|
+
process.exit(1);
|
|
32861
|
+
}
|
|
32862
|
+
const source = stripWorkflowHeader(fs4.readFileSync(file, "utf-8"));
|
|
32863
|
+
if (source.trim() === "") {
|
|
32864
|
+
console.error(`Workflow body ${path5.relative(projectDir, file)} is empty after stripping the header.`);
|
|
32865
|
+
process.exit(1);
|
|
32866
|
+
}
|
|
32867
|
+
const res = await client.setAppWorkflow(meta.app_id, args.alias, {
|
|
32868
|
+
source,
|
|
32869
|
+
inputs: declaration.inputs,
|
|
32870
|
+
outputs: declaration.outputs
|
|
32871
|
+
});
|
|
32872
|
+
if (res.error) {
|
|
32873
|
+
console.error(`Failed to set workflow "${args.alias}": ${res.error}`);
|
|
32874
|
+
process.exit(1);
|
|
32875
|
+
}
|
|
32876
|
+
const result = res.result ?? {};
|
|
32877
|
+
const workflowId = typeof result.workflow_id === "string" ? result.workflow_id : "(unknown)";
|
|
32878
|
+
console.error(`Set workflow "${args.alias}" \u2192 ${workflowId}`);
|
|
32879
|
+
if (result.outputs && typeof result.outputs === "object") {
|
|
32880
|
+
console.error(` result.data schema: ${JSON.stringify(result.outputs)}`);
|
|
32881
|
+
}
|
|
32882
|
+
}
|
|
32883
|
+
async function appWorkflowPull(client) {
|
|
32884
|
+
const projectDir = process.cwd();
|
|
32885
|
+
const meta = readAppMeta(projectDir);
|
|
32886
|
+
const app = await client.getApp(meta.app_id);
|
|
32887
|
+
const aliases = Object.keys(app.workflows ?? {});
|
|
32888
|
+
if (aliases.length === 0) {
|
|
32889
|
+
console.error(`App ${meta.app_id} has no bound workflows.`);
|
|
32890
|
+
return;
|
|
32891
|
+
}
|
|
32892
|
+
const written = await writeWorkflowFiles(client, projectDir, meta.app_id, aliases);
|
|
32893
|
+
console.error(
|
|
32894
|
+
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/` + (written.length > 0 ? ` (${written.join(", ")})` : "")
|
|
32895
|
+
);
|
|
32896
|
+
ensureWorkflowTsconfigExcludes(projectDir);
|
|
32897
|
+
}
|
|
32898
|
+
async function appWorkflowCheck(args) {
|
|
32899
|
+
const projectDir = process.cwd();
|
|
32900
|
+
const meta = readAppMeta(projectDir);
|
|
32901
|
+
const bound = Object.keys(meta.workflows ?? {});
|
|
32902
|
+
let aliases;
|
|
32903
|
+
if (args.alias) {
|
|
32904
|
+
if (!bound.includes(args.alias)) {
|
|
32905
|
+
console.error(
|
|
32906
|
+
`No workflow "${args.alias}" in package.json#lotics.workflows. Bound aliases: ${bound.length > 0 ? bound.join(", ") : "(none)"}.`
|
|
32907
|
+
);
|
|
32908
|
+
process.exit(1);
|
|
32909
|
+
}
|
|
32910
|
+
aliases = [args.alias];
|
|
32911
|
+
} else {
|
|
32912
|
+
aliases = bound;
|
|
32913
|
+
}
|
|
32914
|
+
if (aliases.length === 0) {
|
|
32915
|
+
console.error(`App ${meta.app_id} has no bound workflows to check.`);
|
|
32916
|
+
return;
|
|
32917
|
+
}
|
|
32918
|
+
const toCheck = [];
|
|
32919
|
+
for (const alias of aliases) {
|
|
32920
|
+
const bodyPath = workflowFilePath(projectDir, alias);
|
|
32921
|
+
const globalsPath = workflowGlobalsPath(projectDir, alias);
|
|
32922
|
+
if (!fs4.existsSync(bodyPath)) {
|
|
32923
|
+
console.error(
|
|
32924
|
+
`\u26A0 Skipped "${alias}" \u2014 no body at ${path5.relative(projectDir, bodyPath)}. Run 'lotics app workflow pull' to write it.`
|
|
32925
|
+
);
|
|
32926
|
+
continue;
|
|
32927
|
+
}
|
|
32928
|
+
if (!fs4.existsSync(globalsPath)) {
|
|
32929
|
+
console.error(
|
|
32930
|
+
`Cannot check "${alias}" \u2014 missing types at ${path5.relative(projectDir, globalsPath)}. Run 'lotics app workflow pull' (or 'lotics app codegen') to fetch them.`
|
|
32931
|
+
);
|
|
32932
|
+
process.exit(1);
|
|
32933
|
+
}
|
|
32934
|
+
toCheck.push({ alias, input: { bodyPath, globalsPath } });
|
|
32935
|
+
}
|
|
32936
|
+
if (toCheck.length === 0) {
|
|
32937
|
+
console.error("No workflow bodies to check (every bound alias was skipped).");
|
|
32938
|
+
return;
|
|
32939
|
+
}
|
|
32940
|
+
const tsApi = await loadProjectTypescript(projectDir);
|
|
32941
|
+
const results = checkWorkflowBodies(tsApi, toCheck);
|
|
32942
|
+
printWorkflowCheckResults(projectDir, results);
|
|
32943
|
+
const failed = results.filter((r) => r.issues.length > 0);
|
|
32944
|
+
if (failed.length > 0) process.exit(1);
|
|
32945
|
+
}
|
|
32946
|
+
function printWorkflowCheckResults(projectDir, results) {
|
|
32947
|
+
let totalErrors = 0;
|
|
32948
|
+
for (const r of results) {
|
|
32949
|
+
const rel = path5.relative(projectDir, r.bodyPath);
|
|
32950
|
+
if (r.issues.length === 0) {
|
|
32951
|
+
console.error(`\u2713 ${r.alias} (${rel}) \u2014 no type errors`);
|
|
32952
|
+
continue;
|
|
32953
|
+
}
|
|
32954
|
+
totalErrors += r.issues.length;
|
|
32955
|
+
console.error(
|
|
32956
|
+
`\u2717 ${r.alias} (${rel}) \u2014 ${r.issues.length} error${r.issues.length === 1 ? "" : "s"}:`
|
|
32957
|
+
);
|
|
32958
|
+
for (const issue of r.issues) {
|
|
32959
|
+
const [first2, ...rest] = issue.message.split("\n");
|
|
32960
|
+
console.error(` ${rel}:${issue.line}:${issue.col} - ${issue.code}: ${first2}`);
|
|
32961
|
+
for (const line of rest) console.error(` ${line}`);
|
|
32962
|
+
}
|
|
32963
|
+
}
|
|
32964
|
+
const passed = results.length - results.filter((r) => r.issues.length > 0).length;
|
|
32965
|
+
console.error(
|
|
32966
|
+
totalErrors === 0 ? results.length === 1 ? `
|
|
32967
|
+
The workflow body type-checks clean.` : `
|
|
32968
|
+
All ${results.length} workflow bodies type-check clean.` : `
|
|
32969
|
+
${totalErrors} error${totalErrors === 1 ? "" : "s"} across ${results.length - passed} of ${results.length} ${results.length === 1 ? "body" : "bodies"}.`
|
|
32970
|
+
);
|
|
32971
|
+
}
|
|
32972
|
+
function findUiSrcDir(start) {
|
|
32973
|
+
let dir = path5.resolve(start);
|
|
32974
|
+
for (; ; ) {
|
|
32975
|
+
const candidate = path5.join(dir, "packages", "ui", "src");
|
|
32976
|
+
if (fs4.existsSync(candidate) && fs4.statSync(candidate).isDirectory()) return candidate;
|
|
32977
|
+
const parent = path5.dirname(dir);
|
|
32978
|
+
if (parent === dir) return null;
|
|
32979
|
+
dir = parent;
|
|
32980
|
+
}
|
|
32981
|
+
}
|
|
32982
|
+
var UI_ALIAS_FIND_SOURCE = String.raw`/^@lotics\/ui\/(.+)$/`;
|
|
32983
|
+
function appUiLink(args) {
|
|
32984
|
+
const projectDir = path5.resolve(args.projectDir ?? process.cwd());
|
|
32985
|
+
const viteConfigPath = path5.join(projectDir, "vite.config.ts");
|
|
32986
|
+
if (!fs4.existsSync(viteConfigPath)) {
|
|
32987
|
+
throw new Error(
|
|
32988
|
+
`No vite.config.ts at ${projectDir}. Run inside a 'lotics app' project directory.`
|
|
32989
|
+
);
|
|
32990
|
+
}
|
|
32991
|
+
const uiSrc = findUiSrcDir(projectDir);
|
|
32992
|
+
if (!uiSrc) {
|
|
32993
|
+
throw new Error(
|
|
32994
|
+
"Cannot find packages/ui/src by walking up from this directory \u2014 `lotics ui link` requires a monorepo checkout. External apps consume @lotics/ui from npm; bump the package version and widen the app's dependency range instead."
|
|
32995
|
+
);
|
|
32996
|
+
}
|
|
32997
|
+
const hasComponent = fs4.existsSync(path5.join(uiSrc, `${args.component}.tsx`)) || fs4.existsSync(path5.join(uiSrc, `${args.component}.ts`)) || fs4.existsSync(path5.join(uiSrc, args.component));
|
|
32998
|
+
if (!hasComponent) {
|
|
32999
|
+
throw new Error(
|
|
33000
|
+
`No '@lotics/ui/${args.component}' under ${uiSrc} (expected ${args.component}.tsx/.ts). Check the component name.`
|
|
33001
|
+
);
|
|
33002
|
+
}
|
|
33003
|
+
const source = fs4.readFileSync(viteConfigPath, "utf-8");
|
|
33004
|
+
const aliasEntry = `{ find: ${UI_ALIAS_FIND_SOURCE}, replacement: ${JSON.stringify(`${uiSrc}/$1`)} },`;
|
|
33005
|
+
const alreadyLinked = source.includes(UI_ALIAS_FIND_SOURCE);
|
|
33006
|
+
if (args.remove) {
|
|
33007
|
+
if (!alreadyLinked) {
|
|
33008
|
+
console.error("No @lotics/ui dev-link alias present \u2014 nothing to remove.");
|
|
33009
|
+
return;
|
|
33010
|
+
}
|
|
33011
|
+
const stripped = source.replace(new RegExp(`^\\s*\\{ find: ${escapeRegExp(UI_ALIAS_FIND_SOURCE)}.*$\\n?`, "m"), "");
|
|
33012
|
+
fs4.writeFileSync(viteConfigPath, stripped);
|
|
33013
|
+
console.error(`Removed the @lotics/ui dev-link alias from ${viteConfigPath}.`);
|
|
33014
|
+
console.error("Restart `lotics app dev` and rm -rf node_modules/.vite to clear cached modules.");
|
|
33015
|
+
return;
|
|
33016
|
+
}
|
|
33017
|
+
if (alreadyLinked) {
|
|
33018
|
+
console.error(`@lotics/ui is already dev-linked in ${viteConfigPath}.`);
|
|
33019
|
+
return;
|
|
33020
|
+
}
|
|
33021
|
+
const aliasMatch = /resolve\s*:\s*\{[\s\S]*?alias\s*:\s*\[/.exec(source);
|
|
33022
|
+
if (!aliasMatch) {
|
|
33023
|
+
throw new Error(
|
|
33024
|
+
`Could not find a resolve.alias array literal in ${viteConfigPath}. Refresh vite.config.ts from the starter (packages/sdk/src/starter_template.ts) and retry.`
|
|
33025
|
+
);
|
|
33026
|
+
}
|
|
33027
|
+
const insertAt = aliasMatch.index + aliasMatch[0].length;
|
|
33028
|
+
const updated = `${source.slice(0, insertAt)}
|
|
33029
|
+
${aliasEntry}${source.slice(insertAt)}`;
|
|
33030
|
+
fs4.writeFileSync(viteConfigPath, updated);
|
|
33031
|
+
console.error(`Dev-linked @lotics/ui \u2192 ${uiSrc} in ${viteConfigPath}.`);
|
|
33032
|
+
console.error("Restart `lotics app dev` and rm -rf node_modules/.vite to clear cached modules.");
|
|
33033
|
+
console.error("Finalize: PR the packages/ui change \u2192 publish \u2192 `lotics ui link <component> --remove` + bump the app's dep.");
|
|
33034
|
+
}
|
|
33035
|
+
function escapeRegExp(s) {
|
|
33036
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33037
|
+
}
|
|
32149
33038
|
|
|
32150
33039
|
// src/args.ts
|
|
32151
33040
|
function parseArgs(argv) {
|
|
@@ -32162,6 +33051,8 @@ function parseArgs(argv) {
|
|
|
32162
33051
|
message: void 0,
|
|
32163
33052
|
local: false,
|
|
32164
33053
|
all: false,
|
|
33054
|
+
printCreated: false,
|
|
33055
|
+
cleanup: false,
|
|
32165
33056
|
version: false,
|
|
32166
33057
|
help: false
|
|
32167
33058
|
};
|
|
@@ -32212,6 +33103,13 @@ function parseArgs(argv) {
|
|
|
32212
33103
|
case "--all":
|
|
32213
33104
|
flags.all = true;
|
|
32214
33105
|
break;
|
|
33106
|
+
case "--print-created":
|
|
33107
|
+
case "--report-effects":
|
|
33108
|
+
flags.printCreated = true;
|
|
33109
|
+
break;
|
|
33110
|
+
case "--cleanup":
|
|
33111
|
+
flags.cleanup = true;
|
|
33112
|
+
break;
|
|
32215
33113
|
case "--version":
|
|
32216
33114
|
case "-v":
|
|
32217
33115
|
flags.version = true;
|
|
@@ -32237,12 +33135,40 @@ function parseArgs(argv) {
|
|
|
32237
33135
|
return { command, subcommand, toolArgs, restArgs, flags };
|
|
32238
33136
|
}
|
|
32239
33137
|
|
|
33138
|
+
// src/inputs.ts
|
|
33139
|
+
async function ingestJsonArgs(opts) {
|
|
33140
|
+
let raw = opts.rawArg;
|
|
33141
|
+
if (raw && raw.startsWith("@")) {
|
|
33142
|
+
const argsPath = raw.slice(1);
|
|
33143
|
+
try {
|
|
33144
|
+
raw = opts.readFile(argsPath);
|
|
33145
|
+
} catch (err2) {
|
|
33146
|
+
return {
|
|
33147
|
+
kind: "error",
|
|
33148
|
+
message: `Cannot read args file "${argsPath}": ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
33149
|
+
};
|
|
33150
|
+
}
|
|
33151
|
+
} else if (!raw && !opts.stdinIsTTY) {
|
|
33152
|
+
raw = await opts.readStdin();
|
|
33153
|
+
}
|
|
33154
|
+
if (!raw) return { kind: "ok", args: {} };
|
|
33155
|
+
try {
|
|
33156
|
+
const parsed = JSON.parse(raw);
|
|
33157
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
33158
|
+
return { kind: "error", message: `JSON args must be an object, got: ${raw}` };
|
|
33159
|
+
}
|
|
33160
|
+
return { kind: "ok", args: parsed };
|
|
33161
|
+
} catch {
|
|
33162
|
+
return { kind: "error", message: `Invalid JSON: ${raw}` };
|
|
33163
|
+
}
|
|
33164
|
+
}
|
|
33165
|
+
|
|
32240
33166
|
// src/xlsx.ts
|
|
32241
|
-
import
|
|
33167
|
+
import fs6 from "node:fs";
|
|
32242
33168
|
|
|
32243
33169
|
// src/file_command_io.ts
|
|
32244
|
-
import
|
|
32245
|
-
import
|
|
33170
|
+
import fs5 from "node:fs";
|
|
33171
|
+
import path6 from "node:path";
|
|
32246
33172
|
var CliError = class extends Error {
|
|
32247
33173
|
constructor(message) {
|
|
32248
33174
|
super(message);
|
|
@@ -32253,14 +33179,14 @@ function fail(message) {
|
|
|
32253
33179
|
throw new CliError(message);
|
|
32254
33180
|
}
|
|
32255
33181
|
function writeFileAtomic(filePath, bytes) {
|
|
32256
|
-
const dir =
|
|
32257
|
-
const tmp =
|
|
32258
|
-
|
|
33182
|
+
const dir = path6.dirname(path6.resolve(filePath));
|
|
33183
|
+
const tmp = path6.join(dir, `.${path6.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
33184
|
+
fs5.writeFileSync(tmp, bytes);
|
|
32259
33185
|
try {
|
|
32260
|
-
|
|
33186
|
+
fs5.renameSync(tmp, filePath);
|
|
32261
33187
|
} catch (error) {
|
|
32262
33188
|
try {
|
|
32263
|
-
|
|
33189
|
+
fs5.unlinkSync(tmp);
|
|
32264
33190
|
} catch {
|
|
32265
33191
|
}
|
|
32266
33192
|
throw error;
|
|
@@ -35043,16 +35969,16 @@ var MatcherView = class {
|
|
|
35043
35969
|
* @returns {string|undefined}
|
|
35044
35970
|
*/
|
|
35045
35971
|
getCurrentTag() {
|
|
35046
|
-
const
|
|
35047
|
-
return
|
|
35972
|
+
const path8 = this._matcher.path;
|
|
35973
|
+
return path8.length > 0 ? path8[path8.length - 1].tag : void 0;
|
|
35048
35974
|
}
|
|
35049
35975
|
/**
|
|
35050
35976
|
* Get current namespace.
|
|
35051
35977
|
* @returns {string|undefined}
|
|
35052
35978
|
*/
|
|
35053
35979
|
getCurrentNamespace() {
|
|
35054
|
-
const
|
|
35055
|
-
return
|
|
35980
|
+
const path8 = this._matcher.path;
|
|
35981
|
+
return path8.length > 0 ? path8[path8.length - 1].namespace : void 0;
|
|
35056
35982
|
}
|
|
35057
35983
|
/**
|
|
35058
35984
|
* Get current node's attribute value.
|
|
@@ -35060,9 +35986,9 @@ var MatcherView = class {
|
|
|
35060
35986
|
* @returns {*}
|
|
35061
35987
|
*/
|
|
35062
35988
|
getAttrValue(attrName) {
|
|
35063
|
-
const
|
|
35064
|
-
if (
|
|
35065
|
-
return
|
|
35989
|
+
const path8 = this._matcher.path;
|
|
35990
|
+
if (path8.length === 0) return void 0;
|
|
35991
|
+
return path8[path8.length - 1].values?.[attrName];
|
|
35066
35992
|
}
|
|
35067
35993
|
/**
|
|
35068
35994
|
* Check if current node has an attribute.
|
|
@@ -35070,9 +35996,9 @@ var MatcherView = class {
|
|
|
35070
35996
|
* @returns {boolean}
|
|
35071
35997
|
*/
|
|
35072
35998
|
hasAttr(attrName) {
|
|
35073
|
-
const
|
|
35074
|
-
if (
|
|
35075
|
-
const current =
|
|
35999
|
+
const path8 = this._matcher.path;
|
|
36000
|
+
if (path8.length === 0) return false;
|
|
36001
|
+
const current = path8[path8.length - 1];
|
|
35076
36002
|
return current.values !== void 0 && attrName in current.values;
|
|
35077
36003
|
}
|
|
35078
36004
|
/**
|
|
@@ -35080,18 +36006,18 @@ var MatcherView = class {
|
|
|
35080
36006
|
* @returns {number}
|
|
35081
36007
|
*/
|
|
35082
36008
|
getPosition() {
|
|
35083
|
-
const
|
|
35084
|
-
if (
|
|
35085
|
-
return
|
|
36009
|
+
const path8 = this._matcher.path;
|
|
36010
|
+
if (path8.length === 0) return -1;
|
|
36011
|
+
return path8[path8.length - 1].position ?? 0;
|
|
35086
36012
|
}
|
|
35087
36013
|
/**
|
|
35088
36014
|
* Get current node's repeat counter (occurrence count of this tag name).
|
|
35089
36015
|
* @returns {number}
|
|
35090
36016
|
*/
|
|
35091
36017
|
getCounter() {
|
|
35092
|
-
const
|
|
35093
|
-
if (
|
|
35094
|
-
return
|
|
36018
|
+
const path8 = this._matcher.path;
|
|
36019
|
+
if (path8.length === 0) return -1;
|
|
36020
|
+
return path8[path8.length - 1].counter ?? 0;
|
|
35095
36021
|
}
|
|
35096
36022
|
/**
|
|
35097
36023
|
* Get current node's sibling index (alias for getPosition).
|
|
@@ -36946,8 +37872,8 @@ function isAttribute(name) {
|
|
|
36946
37872
|
}
|
|
36947
37873
|
|
|
36948
37874
|
// ../../node_modules/fflate/esm/index.mjs
|
|
36949
|
-
import { createRequire } from "module";
|
|
36950
|
-
var require2 =
|
|
37875
|
+
import { createRequire as createRequire2 } from "module";
|
|
37876
|
+
var require2 = createRequire2("/");
|
|
36951
37877
|
var Worker;
|
|
36952
37878
|
try {
|
|
36953
37879
|
Worker = require2("worker_threads").Worker;
|
|
@@ -40547,10 +41473,10 @@ function resolveRelativePath(basePath, relativePath) {
|
|
|
40547
41473
|
}
|
|
40548
41474
|
return resolved.join("/");
|
|
40549
41475
|
}
|
|
40550
|
-
function findZipEntry(zipEntries,
|
|
40551
|
-
if (zipEntries[
|
|
40552
|
-
if (zipEntries[`xl/${
|
|
40553
|
-
const noSlash =
|
|
41476
|
+
function findZipEntry(zipEntries, path8) {
|
|
41477
|
+
if (zipEntries[path8]) return zipEntries[path8];
|
|
41478
|
+
if (zipEntries[`xl/${path8}`]) return zipEntries[`xl/${path8}`];
|
|
41479
|
+
const noSlash = path8.replace(/^\//, "");
|
|
40554
41480
|
if (zipEntries[noSlash]) return zipEntries[noSlash];
|
|
40555
41481
|
return void 0;
|
|
40556
41482
|
}
|
|
@@ -41848,9 +42774,9 @@ function parseExcelFromZip(zip, options) {
|
|
|
41848
42774
|
}
|
|
41849
42775
|
return result;
|
|
41850
42776
|
}
|
|
41851
|
-
function findEntry(zip,
|
|
41852
|
-
if (zip[
|
|
41853
|
-
const lower =
|
|
42777
|
+
function findEntry(zip, path8) {
|
|
42778
|
+
if (zip[path8]) return zip[path8];
|
|
42779
|
+
const lower = path8.toLowerCase();
|
|
41854
42780
|
for (const key of Object.keys(zip)) {
|
|
41855
42781
|
if (key.toLowerCase() === lower) return zip[key];
|
|
41856
42782
|
}
|
|
@@ -42461,12 +43387,12 @@ function extractPivotRoundTripInfo(originalZip) {
|
|
|
42461
43387
|
if (paths.length > 0) pivotTablesBySheetIndex.set(i2, paths);
|
|
42462
43388
|
}
|
|
42463
43389
|
const pivotXmlPaths = [];
|
|
42464
|
-
for (const
|
|
42465
|
-
if (
|
|
42466
|
-
pivotXmlPaths.push(
|
|
43390
|
+
for (const path8 of Object.keys(originalZip)) {
|
|
43391
|
+
if (path8.startsWith("xl/pivotTables/") && path8.endsWith(".xml")) {
|
|
43392
|
+
pivotXmlPaths.push(path8);
|
|
42467
43393
|
}
|
|
42468
|
-
if (
|
|
42469
|
-
pivotXmlPaths.push(
|
|
43394
|
+
if (path8.startsWith("xl/pivotCache/") && path8.endsWith(".xml")) {
|
|
43395
|
+
pivotXmlPaths.push(path8);
|
|
42470
43396
|
}
|
|
42471
43397
|
}
|
|
42472
43398
|
return { cachesByWorkbook, pivotTablesBySheetIndex, pivotXmlPaths };
|
|
@@ -42474,15 +43400,15 @@ function extractPivotRoundTripInfo(originalZip) {
|
|
|
42474
43400
|
function decode(bytes) {
|
|
42475
43401
|
return new TextDecoder().decode(bytes);
|
|
42476
43402
|
}
|
|
42477
|
-
function pivotContentTypeFor(
|
|
42478
|
-
if (
|
|
42479
|
-
return `<Override PartName="/${
|
|
43403
|
+
function pivotContentTypeFor(path8) {
|
|
43404
|
+
if (path8.startsWith("xl/pivotTables/") && path8.endsWith(".xml")) {
|
|
43405
|
+
return `<Override PartName="/${path8}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"/>`;
|
|
42480
43406
|
}
|
|
42481
|
-
if (
|
|
42482
|
-
return `<Override PartName="/${
|
|
43407
|
+
if (path8.includes("/pivotCacheDefinition") && path8.endsWith(".xml")) {
|
|
43408
|
+
return `<Override PartName="/${path8}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"/>`;
|
|
42483
43409
|
}
|
|
42484
|
-
if (
|
|
42485
|
-
return `<Override PartName="/${
|
|
43410
|
+
if (path8.includes("/pivotCacheRecords") && path8.endsWith(".xml")) {
|
|
43411
|
+
return `<Override PartName="/${path8}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"/>`;
|
|
42486
43412
|
}
|
|
42487
43413
|
return void 0;
|
|
42488
43414
|
}
|
|
@@ -42554,13 +43480,13 @@ function exportWorkbook(workbook, originalZip) {
|
|
|
42554
43480
|
regeneratedPaths.add(`xl/worksheets/sheet${i2 + 1}.xml`);
|
|
42555
43481
|
regeneratedPaths.add(`xl/worksheets/_rels/sheet${i2 + 1}.xml.rels`);
|
|
42556
43482
|
}
|
|
42557
|
-
for (const
|
|
42558
|
-
if (
|
|
42559
|
-
regeneratedPaths.add(
|
|
43483
|
+
for (const path8 of Object.keys(originalZip)) {
|
|
43484
|
+
if (path8.startsWith("xl/drawings/") || path8.startsWith("xl/charts/") || path8.startsWith("xl/tables/") || path8.startsWith("xl/media/")) {
|
|
43485
|
+
regeneratedPaths.add(path8);
|
|
42560
43486
|
}
|
|
42561
43487
|
}
|
|
42562
|
-
for (const [
|
|
42563
|
-
if (!regeneratedPaths.has(
|
|
43488
|
+
for (const [path8, data] of Object.entries(originalZip)) {
|
|
43489
|
+
if (!regeneratedPaths.has(path8)) entries[path8] = data;
|
|
42564
43490
|
}
|
|
42565
43491
|
}
|
|
42566
43492
|
const sharedStrings = buildSharedStrings(workbook);
|
|
@@ -42575,8 +43501,8 @@ function exportWorkbook(workbook, originalZip) {
|
|
|
42575
43501
|
buildWorkbookRels(workbook, pivotInfo)
|
|
42576
43502
|
);
|
|
42577
43503
|
const extraContentTypes = [];
|
|
42578
|
-
for (const
|
|
42579
|
-
const ct = pivotContentTypeFor(
|
|
43504
|
+
for (const path8 of pivotInfo.pivotXmlPaths) {
|
|
43505
|
+
const ct = pivotContentTypeFor(path8);
|
|
42580
43506
|
if (ct) extraContentTypes.push(ct);
|
|
42581
43507
|
}
|
|
42582
43508
|
let globalChartIndex = 1;
|
|
@@ -44337,14 +45263,14 @@ var DependencyGraph = class {
|
|
|
44337
45263
|
stack.push({ cell: p, path: [cell, p] });
|
|
44338
45264
|
}
|
|
44339
45265
|
while (stack.length > 0) {
|
|
44340
|
-
const { cell: current, path:
|
|
44341
|
-
if (current === cell) return
|
|
45266
|
+
const { cell: current, path: path8 } = stack.pop();
|
|
45267
|
+
if (current === cell) return path8;
|
|
44342
45268
|
if (visited.has(current)) continue;
|
|
44343
45269
|
visited.add(current);
|
|
44344
45270
|
const nextPrec = this.precedents.get(current);
|
|
44345
45271
|
if (!nextPrec) continue;
|
|
44346
45272
|
for (const p of nextPrec) {
|
|
44347
|
-
stack.push({ cell: p, path: [...
|
|
45273
|
+
stack.push({ cell: p, path: [...path8, p] });
|
|
44348
45274
|
}
|
|
44349
45275
|
}
|
|
44350
45276
|
return null;
|
|
@@ -45335,8 +46261,8 @@ function adjustMergedCellsForColDelete(sheet, atCol, count) {
|
|
|
45335
46261
|
|
|
45336
46262
|
// src/xlsx.ts
|
|
45337
46263
|
function loadFile(filePath) {
|
|
45338
|
-
if (!
|
|
45339
|
-
const buffer =
|
|
46264
|
+
if (!fs6.existsSync(filePath)) fail(`File not found: ${filePath}`);
|
|
46265
|
+
const buffer = fs6.readFileSync(filePath);
|
|
45340
46266
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
45341
46267
|
const originalZip = unzipXlsx(arrayBuffer);
|
|
45342
46268
|
const parsed = parseExcelBuffer(arrayBuffer);
|
|
@@ -45386,7 +46312,7 @@ function lazyEngine(workbook) {
|
|
|
45386
46312
|
};
|
|
45387
46313
|
}
|
|
45388
46314
|
function readToJson(filePath) {
|
|
45389
|
-
const buffer =
|
|
46315
|
+
const buffer = fs6.readFileSync(filePath);
|
|
45390
46316
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
45391
46317
|
const parsed = parseExcelBuffer(arrayBuffer);
|
|
45392
46318
|
return {
|
|
@@ -45776,7 +46702,7 @@ async function runXlsxCommand(subcommand, toolArgs, restArgs) {
|
|
|
45776
46702
|
}
|
|
45777
46703
|
|
|
45778
46704
|
// src/docx.ts
|
|
45779
|
-
import
|
|
46705
|
+
import fs7 from "node:fs";
|
|
45780
46706
|
|
|
45781
46707
|
// ../docx/src/parse/parser.ts
|
|
45782
46708
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
@@ -47547,11 +48473,11 @@ async function parseDocx(buffer) {
|
|
|
47547
48473
|
const zip = await import_jszip.default.loadAsync(buffer);
|
|
47548
48474
|
const parts = /* @__PURE__ */ new Map();
|
|
47549
48475
|
const filePromises = [];
|
|
47550
|
-
zip.forEach((
|
|
48476
|
+
zip.forEach((path8, file) => {
|
|
47551
48477
|
if (file.dir) return;
|
|
47552
48478
|
filePromises.push(
|
|
47553
48479
|
file.async("uint8array").then((bytes) => {
|
|
47554
|
-
parts.set(
|
|
48480
|
+
parts.set(path8, bytes);
|
|
47555
48481
|
})
|
|
47556
48482
|
);
|
|
47557
48483
|
});
|
|
@@ -47780,9 +48706,9 @@ var DEFAULT_PARTS = [
|
|
|
47780
48706
|
["word/theme/theme1.xml", () => encoder.encode(THEME_XML)]
|
|
47781
48707
|
];
|
|
47782
48708
|
function fillDefaultParts(parts) {
|
|
47783
|
-
for (const [
|
|
47784
|
-
if (!parts.has(
|
|
47785
|
-
parts.set(
|
|
48709
|
+
for (const [path8, makeBytes] of DEFAULT_PARTS) {
|
|
48710
|
+
if (!parts.has(path8)) {
|
|
48711
|
+
parts.set(path8, makeBytes());
|
|
47786
48712
|
}
|
|
47787
48713
|
}
|
|
47788
48714
|
return parts;
|
|
@@ -47792,9 +48718,9 @@ function fillDefaultParts(parts) {
|
|
|
47792
48718
|
async function serializeDocx(doc) {
|
|
47793
48719
|
const zip = new import_jszip2.default();
|
|
47794
48720
|
const parts = fillDefaultParts(new Map(doc.parts));
|
|
47795
|
-
for (const [
|
|
47796
|
-
if (
|
|
47797
|
-
zip.file(
|
|
48721
|
+
for (const [path8, bytes] of parts) {
|
|
48722
|
+
if (path8 === "word/document.xml") continue;
|
|
48723
|
+
zip.file(path8, bytes);
|
|
47798
48724
|
}
|
|
47799
48725
|
const documentXml = serializeDocumentXml(doc);
|
|
47800
48726
|
zip.file("word/document.xml", documentXml);
|
|
@@ -47880,8 +48806,8 @@ var DEFAULT_DOC_ATTRS = {
|
|
|
47880
48806
|
"@_xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
47881
48807
|
};
|
|
47882
48808
|
async function loadFile2(filePath) {
|
|
47883
|
-
if (!
|
|
47884
|
-
const bytes =
|
|
48809
|
+
if (!fs7.existsSync(filePath)) fail(`File not found: ${filePath}`);
|
|
48810
|
+
const bytes = fs7.readFileSync(filePath);
|
|
47885
48811
|
return parseDocx(new Uint8Array(bytes));
|
|
47886
48812
|
}
|
|
47887
48813
|
async function writeDoc(filePath, doc) {
|
|
@@ -48170,6 +49096,8 @@ async function runDocxCommand(subcommand, toolArgs, restArgs) {
|
|
|
48170
49096
|
}
|
|
48171
49097
|
|
|
48172
49098
|
// src/cli.ts
|
|
49099
|
+
dns.setDefaultResultOrder("ipv4first");
|
|
49100
|
+
net2.setDefaultAutoSelectFamilyAttemptTimeout(2e3);
|
|
48173
49101
|
function printHelp() {
|
|
48174
49102
|
console.log(`Lotics CLI v${VERSION} \u2014 AI agent interface for Lotics
|
|
48175
49103
|
|
|
@@ -48226,9 +49154,23 @@ COMMANDS
|
|
|
48226
49154
|
lotics app deploy [-m <message>] Build + upload current dir as a new version
|
|
48227
49155
|
(code + queries only \u2014 workflow bindings are
|
|
48228
49156
|
managed by set_app_workflow / remove_app_workflow)
|
|
49157
|
+
lotics app codegen [path] Regenerate .lotics/* (types + field/option ids)
|
|
49158
|
+
from the manifest + workspace schema \u2014 no deploy
|
|
49159
|
+
lotics app workflow run <alias> '<json>' Execute a bound app workflow end-to-end
|
|
49160
|
+
(inputs: inline JSON, @file, or stdin;
|
|
49161
|
+
--print-created reports created records +
|
|
49162
|
+
a paste-ready cleanup plan; --cleanup also
|
|
49163
|
+
deletes those records \u2014 NOT a rollback)
|
|
49164
|
+
lotics app workflow set <alias> Push the edited src/workflows/<alias>.ts body
|
|
49165
|
+
through set_app_workflow (server verifies)
|
|
49166
|
+
lotics app workflow pull Rewrite src/workflows/*.ts from the server
|
|
49167
|
+
lotics app workflow check [alias] Typecheck src/workflows bodies locally (one
|
|
49168
|
+
isolated program per alias; the app's own tsc)
|
|
48229
49169
|
lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
|
|
48230
49170
|
lotics app rename "<new name>" Rename the app's display name (launcher title)
|
|
48231
49171
|
lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
|
|
49172
|
+
lotics ui link <component> [--remove] Dev-link @lotics/ui to the monorepo's
|
|
49173
|
+
packages/ui/src for live HMR (monorepo only)
|
|
48232
49174
|
lotics xlsx <subcommand> ... Read/write/edit .xlsx files on your local filesystem
|
|
48233
49175
|
(uses the bundled Lotics xlsx engine; prefer over
|
|
48234
49176
|
npm xlsx/exceljs for round-trip fidelity)
|
|
@@ -48331,8 +49273,8 @@ function prompt(question) {
|
|
|
48331
49273
|
});
|
|
48332
49274
|
});
|
|
48333
49275
|
}
|
|
48334
|
-
async function publicPost(
|
|
48335
|
-
const response = await fetch(`${API_BASE_URL}${
|
|
49276
|
+
async function publicPost(path8, body) {
|
|
49277
|
+
const response = await fetch(`${API_BASE_URL}${path8}`, {
|
|
48336
49278
|
method: "POST",
|
|
48337
49279
|
headers: { "Content-Type": "application/json" },
|
|
48338
49280
|
body: JSON.stringify(body)
|
|
@@ -48478,13 +49420,13 @@ async function resolveWorkspace(client, ctx) {
|
|
|
48478
49420
|
function resolveUploadPaths(rawPaths) {
|
|
48479
49421
|
const result = [];
|
|
48480
49422
|
for (const p of rawPaths) {
|
|
48481
|
-
const resolved =
|
|
48482
|
-
const stat =
|
|
49423
|
+
const resolved = path7.resolve(p);
|
|
49424
|
+
const stat = fs8.statSync(resolved);
|
|
48483
49425
|
if (stat.isDirectory()) {
|
|
48484
|
-
const entries =
|
|
49426
|
+
const entries = fs8.readdirSync(resolved, { withFileTypes: true });
|
|
48485
49427
|
for (const entry of entries) {
|
|
48486
49428
|
if (entry.isFile()) {
|
|
48487
|
-
result.push(
|
|
49429
|
+
result.push(path7.join(resolved, entry.name));
|
|
48488
49430
|
}
|
|
48489
49431
|
}
|
|
48490
49432
|
} else {
|
|
@@ -48588,6 +49530,38 @@ async function main() {
|
|
|
48588
49530
|
await runDocxCommand(subcommand, toolArgs, restArgs);
|
|
48589
49531
|
return;
|
|
48590
49532
|
}
|
|
49533
|
+
if (command === "ui") {
|
|
49534
|
+
if (subcommand === "link") {
|
|
49535
|
+
const component = toolArgs;
|
|
49536
|
+
if (!component) {
|
|
49537
|
+
console.error("Usage: lotics ui link <component> [--remove]");
|
|
49538
|
+
console.error("Dev-links @lotics/ui to the monorepo's packages/ui/src for live HMR.");
|
|
49539
|
+
process.exit(1);
|
|
49540
|
+
}
|
|
49541
|
+
appUiLink({ component, remove: restArgs.includes("--remove") });
|
|
49542
|
+
return;
|
|
49543
|
+
}
|
|
49544
|
+
console.error(`Unknown ui subcommand: ${subcommand ?? "(none)"}`);
|
|
49545
|
+
console.error("Usage: lotics ui link <component> [--remove]");
|
|
49546
|
+
process.exit(1);
|
|
49547
|
+
}
|
|
49548
|
+
if (command === "app" && subcommand === "workflow" && toolArgs === "check") {
|
|
49549
|
+
await appWorkflowCheck({ alias: restArgs[0] });
|
|
49550
|
+
return;
|
|
49551
|
+
}
|
|
49552
|
+
if (command === "app" && subcommand === "codegen") {
|
|
49553
|
+
const projectDir = toolArgs;
|
|
49554
|
+
const ctx2 = resolveContext(flags);
|
|
49555
|
+
if (!ctx2) {
|
|
49556
|
+
await appCodegen({ projectDir });
|
|
49557
|
+
return;
|
|
49558
|
+
}
|
|
49559
|
+
const viewAsMemberId = flags.viewAs ?? process.env.LOTICS_VIEW_AS;
|
|
49560
|
+
const client2 = new LoticsClient({ apiKey: ctx2.apiKey, workspaceId: ctx2.workspaceId, viewAsMemberId });
|
|
49561
|
+
await resolveWorkspace(client2, ctx2);
|
|
49562
|
+
await appCodegen({ projectDir, client: client2 });
|
|
49563
|
+
return;
|
|
49564
|
+
}
|
|
48591
49565
|
if (command === "org") {
|
|
48592
49566
|
const global2 = loadGlobalConfig() ?? {};
|
|
48593
49567
|
const profiles = global2.profiles ?? {};
|
|
@@ -48655,6 +49629,11 @@ async function main() {
|
|
|
48655
49629
|
console.error(" lotics app create <name> [path] Scaffold a new app locally");
|
|
48656
49630
|
console.error(" lotics app pull <app_id> [path] Pull an existing app for local editing");
|
|
48657
49631
|
console.error(" lotics app deploy [-m <message>] Build + upload the current directory");
|
|
49632
|
+
console.error(" lotics app codegen [path] Regenerate .lotics/* (types + field ids) \u2014 no deploy");
|
|
49633
|
+
console.error(" lotics app workflow run <alias> '<json>' Execute a bound app workflow end-to-end");
|
|
49634
|
+
console.error(" lotics app workflow set <alias> Push the edited src/workflows/<alias>.ts body");
|
|
49635
|
+
console.error(" lotics app workflow pull Rewrite src/workflows/*.ts from the server");
|
|
49636
|
+
console.error(" lotics app workflow check [alias] Typecheck src/workflows bodies locally");
|
|
48658
49637
|
console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
|
|
48659
49638
|
console.error(` lotics app rename "<new name>" Rename the app's display name (launcher title)`);
|
|
48660
49639
|
console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
|
|
@@ -48796,6 +49775,60 @@ Available workspaces:`);
|
|
|
48796
49775
|
await appRename(client, { name: newName });
|
|
48797
49776
|
return;
|
|
48798
49777
|
}
|
|
49778
|
+
if (subcommand === "workflow") {
|
|
49779
|
+
const action = toolArgs;
|
|
49780
|
+
const workflowUsage = () => {
|
|
49781
|
+
console.error("Usage:");
|
|
49782
|
+
console.error(" lotics app workflow run <alias> '<json>' Execute a bound app workflow");
|
|
49783
|
+
console.error(" lotics app workflow set <alias> Push src/workflows/<alias>.ts");
|
|
49784
|
+
console.error(" lotics app workflow pull Rewrite src/workflows/*.ts from the server");
|
|
49785
|
+
console.error(" lotics app workflow check [alias] Typecheck src/workflows bodies locally");
|
|
49786
|
+
process.exit(1);
|
|
49787
|
+
};
|
|
49788
|
+
if (action === "run") {
|
|
49789
|
+
const alias = restArgs[0];
|
|
49790
|
+
if (!alias) {
|
|
49791
|
+
console.error("Usage: lotics app workflow run <alias> '<json>'");
|
|
49792
|
+
console.error(" lotics app workflow run <alias> @inputs.json (read inputs from a file)");
|
|
49793
|
+
console.error(" cat inputs.json | lotics app workflow run <alias> (read inputs from stdin)");
|
|
49794
|
+
console.error("Flags: --print-created (report created records + cleanup plan + caveat)");
|
|
49795
|
+
console.error(" --cleanup (also delete the created records \u2014 records only, NOT a rollback)");
|
|
49796
|
+
process.exit(1);
|
|
49797
|
+
}
|
|
49798
|
+
const ingested = await ingestJsonArgs({
|
|
49799
|
+
rawArg: restArgs[1],
|
|
49800
|
+
stdinIsTTY: process.stdin.isTTY ?? false,
|
|
49801
|
+
readFile: (p) => fs8.readFileSync(p, "utf-8"),
|
|
49802
|
+
readStdin
|
|
49803
|
+
});
|
|
49804
|
+
if (ingested.kind === "error") {
|
|
49805
|
+
console.error(ingested.message);
|
|
49806
|
+
process.exit(1);
|
|
49807
|
+
}
|
|
49808
|
+
await appExecuteWorkflow(client, {
|
|
49809
|
+
alias,
|
|
49810
|
+
inputs: ingested.args,
|
|
49811
|
+
printCreated: flags.printCreated,
|
|
49812
|
+
cleanup: flags.cleanup
|
|
49813
|
+
});
|
|
49814
|
+
return;
|
|
49815
|
+
}
|
|
49816
|
+
if (action === "set") {
|
|
49817
|
+
const alias = restArgs[0];
|
|
49818
|
+
if (!alias) {
|
|
49819
|
+
console.error("Usage: lotics app workflow set <alias>");
|
|
49820
|
+
console.error("Pushes the edited src/workflows/<alias>.ts body via set_app_workflow.");
|
|
49821
|
+
process.exit(1);
|
|
49822
|
+
}
|
|
49823
|
+
await appWorkflowSet(client, { alias });
|
|
49824
|
+
return;
|
|
49825
|
+
}
|
|
49826
|
+
if (action === "pull") {
|
|
49827
|
+
await appWorkflowPull(client);
|
|
49828
|
+
return;
|
|
49829
|
+
}
|
|
49830
|
+
workflowUsage();
|
|
49831
|
+
}
|
|
48799
49832
|
if (subcommand === "dev") {
|
|
48800
49833
|
const projectDir = toolArgs;
|
|
48801
49834
|
let port;
|
|
@@ -48894,29 +49927,17 @@ ${JSON.stringify(info.input_schema, null, 2)}`);
|
|
|
48894
49927
|
}
|
|
48895
49928
|
if (command === "run") {
|
|
48896
49929
|
const toolName = subcommand;
|
|
48897
|
-
|
|
48898
|
-
|
|
48899
|
-
|
|
48900
|
-
|
|
48901
|
-
|
|
48902
|
-
|
|
48903
|
-
|
|
48904
|
-
|
|
48905
|
-
|
|
48906
|
-
process.exit(1);
|
|
48907
|
-
}
|
|
48908
|
-
} else if (!rawArgs && !process.stdin.isTTY) {
|
|
48909
|
-
rawArgs = await readStdin();
|
|
48910
|
-
}
|
|
48911
|
-
let args = {};
|
|
48912
|
-
if (rawArgs) {
|
|
48913
|
-
try {
|
|
48914
|
-
args = JSON.parse(rawArgs);
|
|
48915
|
-
} catch {
|
|
48916
|
-
console.error(`Invalid JSON: ${rawArgs}`);
|
|
48917
|
-
process.exit(1);
|
|
48918
|
-
}
|
|
49930
|
+
const ingested = await ingestJsonArgs({
|
|
49931
|
+
rawArg: toolArgs,
|
|
49932
|
+
stdinIsTTY: process.stdin.isTTY ?? false,
|
|
49933
|
+
readFile: (p) => fs8.readFileSync(p, "utf-8"),
|
|
49934
|
+
readStdin
|
|
49935
|
+
});
|
|
49936
|
+
if (ingested.kind === "error") {
|
|
49937
|
+
console.error(ingested.message);
|
|
49938
|
+
process.exit(1);
|
|
48919
49939
|
}
|
|
49940
|
+
const args = ingested.args;
|
|
48920
49941
|
const timeoutMs = flags.timeout ?? 6e4;
|
|
48921
49942
|
const result = await client.execute(toolName, args, { format: "text", timeoutMs });
|
|
48922
49943
|
if (result.error) {
|