@lotics/cli 0.70.0 → 0.73.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 +38 -0
- package/dist/app_commands.d.ts +4 -0
- package/dist/app_commands.js +2 -2
- package/dist/args.d.ts +17 -0
- package/dist/args.js +35 -2
- package/dist/args.test.js +49 -0
- package/dist/cli.js +262 -8
- package/dist/client.d.ts +315 -0
- package/dist/client.js +160 -0
- package/dist/dev/rpc_handler.js +7 -2
- package/dist/dev/rpc_handler.test.js +24 -6
- package/dist/package_commands.d.ts +227 -0
- package/dist/package_commands.js +849 -0
- package/dist/package_commands.test.d.ts +1 -0
- package/dist/package_commands.test.js +303 -0
- package/dist/preview.d.ts +3 -0
- package/dist/preview.js +233 -0
- package/dist/render_page.js +59068 -0
- package/dist/render_page.js.LEGAL.txt +14 -0
- package/dist/src/cli.js +1512 -173
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,6 +195,44 @@ lotics ui link card --remove # finalize: PR + publish, then drop t
|
|
|
195
195
|
|
|
196
196
|
`app codegen` reads `package.json#lotics.queries` to decide which tables to put in `app_fields.ts`; widen the set with `package.json#lotics.codegen.tables` (an array of `tbl_…` ids) for tables the app only writes via workflows.
|
|
197
197
|
|
|
198
|
+
## App packages
|
|
199
|
+
|
|
200
|
+
A maintained, versioned app library: author once, install into many workspaces, upgrade per-workspace. Any org authors its own packages (publishing is owner-org-only); Lotics-backed packages carry the `official` badge. See `docs/app_packages.md` for the model.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Author (any org admin; the package is owned by your org)
|
|
204
|
+
lotics package new my-crm # scaffold project + contract.json
|
|
205
|
+
lotics package publish -m "v2: adds deals" # build + publish an immutable version
|
|
206
|
+
|
|
207
|
+
# Dev loop against a dev workspace (created with: lotics workspace create <name> --dev)
|
|
208
|
+
lotics package dev --workspace wsp_dev # scaffold-sync + run the app dev server
|
|
209
|
+
lotics package sync --workspace wsp_dev # migrate the dev installation only
|
|
210
|
+
lotics package reset --workspace wsp_dev # DEV-ONLY: drop scaffolded tables, re-scaffold clean
|
|
211
|
+
|
|
212
|
+
# Operate installations (workspace admin)
|
|
213
|
+
lotics package install apg_... --version 2 # scaffold + materialize + deploy + pin
|
|
214
|
+
lotics package doctor app_... # version pin vs latest + drift + local edits (exit 1 on findings)
|
|
215
|
+
lotics package rebind-role app_... <alias> <grp_id> # re-point a package role at another group (re-materializes)
|
|
216
|
+
lotics package upgrade app_... # preview, then apply (additive; overlay preserved)
|
|
217
|
+
lotics package fleet-upgrade apg_... # upgrade EVERY org installation (clean ones apply; findings skip, exit 1)
|
|
218
|
+
lotics package upgrade app_... --resolve fields.deal.stage=recreate # resolve reported drift
|
|
219
|
+
lotics package upgrade app_... --resolve queries.tasks=keep # consent for a local edit (or =revert)
|
|
220
|
+
lotics package eject app_... # one-way: sever the package link
|
|
221
|
+
lotics workspace doctor # dangling schema references across the workspace (exit 1 on findings)
|
|
222
|
+
|
|
223
|
+
# Promote an EXISTING bespoke app to a package (build it in a real workspace first,
|
|
224
|
+
# distribute once it proves itself — no re-authoring)
|
|
225
|
+
lotics package extract app_... # → draft project: contract + binding + templates
|
|
226
|
+
# prints a findings report; always writes the draft,
|
|
227
|
+
# exits 1 on error findings (fix them, publish re-validates)
|
|
228
|
+
cd <slug-of-app-name> # review contract.json aliases
|
|
229
|
+
lotics package publish -m "v1" # publish the reviewed project
|
|
230
|
+
lotics package adopt app_... # bind the package onto the origin app (same workspace);
|
|
231
|
+
# verifies the binding is faithful, then pins installation #1
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Extract writes `.lotics/adopt_binding.json` (the origin pin, excluded from published bundles); `adopt` reads it back and refuses a pin recorded for a different app. After promotion the package project is the master — iterate contract-first (`package dev`/`sync` → `publish`), never extract again.
|
|
235
|
+
|
|
198
236
|
## SDK
|
|
199
237
|
|
|
200
238
|
```typescript
|
package/dist/app_commands.d.ts
CHANGED
|
@@ -86,6 +86,10 @@ export declare function writeWorkflowFile(projectDir: string, alias: string, sou
|
|
|
86
86
|
* real source, not bookkeeping, and must not be silently eaten.
|
|
87
87
|
*/
|
|
88
88
|
export declare function stripWorkflowHeader(content: string): string;
|
|
89
|
+
/** Run `tar` and resolve when it exits cleanly. Throws with stderr on failure. */
|
|
90
|
+
export declare function runTar(args: string[], cwd: string): Promise<void>;
|
|
91
|
+
/** Run `npm` (run/install/etc.) inheriting stdio so the user sees progress. */
|
|
92
|
+
export declare function runNpm(args: string[], cwd: string): Promise<void>;
|
|
89
93
|
/**
|
|
90
94
|
* Heal a pre-existing app's `tsconfig.json` so its generated types load and its
|
|
91
95
|
* `npm run typecheck` stays honest. Idempotent, run on every pull / codegen /
|
package/dist/app_commands.js
CHANGED
|
@@ -258,7 +258,7 @@ async function fetchWorkflowGlobals(client, projectDir, app_id, alias) {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
/** Run `tar` and resolve when it exits cleanly. Throws with stderr on failure. */
|
|
261
|
-
function runTar(args, cwd) {
|
|
261
|
+
export function runTar(args, cwd) {
|
|
262
262
|
return new Promise((resolve, reject) => {
|
|
263
263
|
const proc = spawn("tar", args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
|
|
264
264
|
let stderr = "";
|
|
@@ -275,7 +275,7 @@ function runTar(args, cwd) {
|
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
277
|
/** Run `npm` (run/install/etc.) inheriting stdio so the user sees progress. */
|
|
278
|
-
function runNpm(args, cwd) {
|
|
278
|
+
export function runNpm(args, cwd) {
|
|
279
279
|
return new Promise((resolve, reject) => {
|
|
280
280
|
const proc = spawn("npm", args, { cwd, stdio: "inherit", env: ipv4ChildEnv(process.env) });
|
|
281
281
|
proc.on("error", reject);
|
package/dist/args.d.ts
CHANGED
|
@@ -30,12 +30,29 @@ export declare function parseArgs(argv: string[]): {
|
|
|
30
30
|
message?: string;
|
|
31
31
|
local: boolean;
|
|
32
32
|
all: boolean;
|
|
33
|
+
/** `--dev`: mark a workspace as an app-package dev workspace (`workspace create`). */
|
|
34
|
+
dev: boolean;
|
|
33
35
|
/** `--yes` (alias `-y`): confirm a destructive command (e.g. `workspace delete`) non-interactively. */
|
|
34
36
|
yes: boolean;
|
|
35
37
|
/** `--print-created` (alias `--report-effects`): print the honest post-run side-effect harvest. */
|
|
36
38
|
printCreated: boolean;
|
|
37
39
|
/** `--cleanup`: also delete the harvested created records (records only). */
|
|
38
40
|
cleanup: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* `--version <n>`: a package version number (`lotics package install … --version 2`).
|
|
43
|
+
* Distinct from the boolean `version` flag below: `--version` followed by an
|
|
44
|
+
* integer captures the number; a bare `--version` (or `-v`) prints the CLI
|
|
45
|
+
* version. No other command takes a numeric value after `--version`, so the
|
|
46
|
+
* peek is unambiguous.
|
|
47
|
+
*/
|
|
48
|
+
packageVersion?: string;
|
|
49
|
+
/**
|
|
50
|
+
* `--resolve <key>=recreate|revert|keep|<id>` (repeatable): upgrade
|
|
51
|
+
* resolutions — drift entries (`<namespace.alias>`) take `recreate` or an
|
|
52
|
+
* existing id; modified-core entries (`<kind>.<alias>`) take `revert` or
|
|
53
|
+
* `keep`.
|
|
54
|
+
*/
|
|
55
|
+
resolve: string[];
|
|
39
56
|
version: boolean;
|
|
40
57
|
help: boolean;
|
|
41
58
|
};
|
package/dist/args.js
CHANGED
|
@@ -25,9 +25,12 @@ export function parseArgs(argv) {
|
|
|
25
25
|
message: undefined,
|
|
26
26
|
local: false,
|
|
27
27
|
all: false,
|
|
28
|
+
dev: false,
|
|
28
29
|
yes: false,
|
|
29
30
|
printCreated: false,
|
|
30
31
|
cleanup: false,
|
|
32
|
+
packageVersion: undefined,
|
|
33
|
+
resolve: [],
|
|
31
34
|
version: false,
|
|
32
35
|
help: false,
|
|
33
36
|
};
|
|
@@ -78,6 +81,9 @@ export function parseArgs(argv) {
|
|
|
78
81
|
case "--local":
|
|
79
82
|
flags.local = true;
|
|
80
83
|
break;
|
|
84
|
+
case "--dev":
|
|
85
|
+
flags.dev = true;
|
|
86
|
+
break;
|
|
81
87
|
case "--all":
|
|
82
88
|
flags.all = true;
|
|
83
89
|
break;
|
|
@@ -92,10 +98,37 @@ export function parseArgs(argv) {
|
|
|
92
98
|
case "--cleanup":
|
|
93
99
|
flags.cleanup = true;
|
|
94
100
|
break;
|
|
101
|
+
case "--resolve": {
|
|
102
|
+
const value = argv[++i];
|
|
103
|
+
if (value === undefined || value.startsWith("-")) {
|
|
104
|
+
throw new Error("--resolve requires a value: <key>=recreate|revert|keep or <key>=<existing_id> (drift: <namespace.alias>; modified core: <kind>.<alias>).");
|
|
105
|
+
}
|
|
106
|
+
flags.resolve.push(value);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
95
109
|
case "--version":
|
|
96
|
-
case "-v":
|
|
97
|
-
|
|
110
|
+
case "-v": {
|
|
111
|
+
// Inside a `package` command, `--version` carries a value (the
|
|
112
|
+
// package version to install) and the value is REQUIRED — a bare or
|
|
113
|
+
// malformed `--version` here must never fall back to the CLI-version
|
|
114
|
+
// early-exit boolean, which would print the CLI version and exit 0
|
|
115
|
+
// (a provisioning script reads that as a successful install). The
|
|
116
|
+
// value is captured raw; the consuming command validates
|
|
117
|
+
// integer-ness with a loud error. Outside `package`, this is the
|
|
118
|
+
// CLI-version boolean.
|
|
119
|
+
const next = argv[i + 1];
|
|
120
|
+
if (command === "package") {
|
|
121
|
+
if (next === undefined || next.startsWith("-")) {
|
|
122
|
+
throw new Error("--version requires a version number for package commands (e.g. --version 2).");
|
|
123
|
+
}
|
|
124
|
+
flags.packageVersion = next;
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
flags.version = true;
|
|
129
|
+
}
|
|
98
130
|
break;
|
|
131
|
+
}
|
|
99
132
|
case "--help":
|
|
100
133
|
case "-h":
|
|
101
134
|
flags.help = true;
|
package/dist/args.test.js
CHANGED
|
@@ -23,6 +23,49 @@ describe("parseArgs", () => {
|
|
|
23
23
|
const r = parseArgs(["app", "deploy"]);
|
|
24
24
|
expect(r.flags.message).toBeUndefined();
|
|
25
25
|
});
|
|
26
|
+
it("captures --version <int> as packageVersion, not the boolean version flag", () => {
|
|
27
|
+
const r = parseArgs(["package", "install", "apg_123", "--version", "2"]);
|
|
28
|
+
expect(r.command).toBe("package");
|
|
29
|
+
expect(r.subcommand).toBe("install");
|
|
30
|
+
expect(r.toolArgs).toBe("apg_123");
|
|
31
|
+
expect(r.flags.packageVersion).toBe("2");
|
|
32
|
+
expect(r.flags.version).toBe(false);
|
|
33
|
+
expect(r.restArgs).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
it("treats a bare --version as the boolean CLI-version flag", () => {
|
|
36
|
+
const r = parseArgs(["--version"]);
|
|
37
|
+
expect(r.flags.version).toBe(true);
|
|
38
|
+
expect(r.flags.packageVersion).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
it("treats -v as the boolean CLI-version flag", () => {
|
|
41
|
+
const r = parseArgs(["-v"]);
|
|
42
|
+
expect(r.flags.version).toBe(true);
|
|
43
|
+
expect(r.flags.packageVersion).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
it("captures a non-integer --version value under package commands for loud command-level validation", () => {
|
|
46
|
+
const r = parseArgs(["package", "install", "apg_123", "--version", "2.0"]);
|
|
47
|
+
expect(r.flags.packageVersion).toBe("2.0");
|
|
48
|
+
expect(r.flags.version).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
it("collects repeated --resolve values", () => {
|
|
51
|
+
const r = parseArgs([
|
|
52
|
+
"package",
|
|
53
|
+
"upgrade",
|
|
54
|
+
"app_1",
|
|
55
|
+
"--resolve",
|
|
56
|
+
"fields.deal.stage=recreate",
|
|
57
|
+
"--resolve",
|
|
58
|
+
"templates.quote=dtl_abc",
|
|
59
|
+
]);
|
|
60
|
+
expect(r.flags.resolve).toEqual(["fields.deal.stage=recreate", "templates.quote=dtl_abc"]);
|
|
61
|
+
});
|
|
62
|
+
it("errors on --resolve without a value", () => {
|
|
63
|
+
expect(() => parseArgs(["package", "upgrade", "app_1", "--resolve"])).toThrow(/--resolve requires a value/);
|
|
64
|
+
});
|
|
65
|
+
it("errors loudly on a bare --version under package commands instead of printing the CLI version", () => {
|
|
66
|
+
expect(() => parseArgs(["package", "install", "apg_123", "--version"])).toThrow(/requires a version number/);
|
|
67
|
+
expect(() => parseArgs(["package", "install", "apg_123", "--version", "--json"])).toThrow(/requires a version number/);
|
|
68
|
+
});
|
|
26
69
|
it("parses --workspace as a value flag", () => {
|
|
27
70
|
const r = parseArgs(["run", "query_tables", "{}", "--workspace", "wsp_123"]);
|
|
28
71
|
expect(r.flags.workspace).toBe("wsp_123");
|
|
@@ -63,6 +106,12 @@ describe("parseArgs", () => {
|
|
|
63
106
|
expect(parseArgs(["app", "workflow", "run", "wf"]).flags.cleanup).toBe(false);
|
|
64
107
|
expect(parseArgs(["app", "workflow", "run", "wf"]).flags.printCreated).toBe(false);
|
|
65
108
|
});
|
|
109
|
+
it("parses --dev as a boolean flag (default false)", () => {
|
|
110
|
+
expect(parseArgs(["workspace", "create", "Dev WS", "--dev"]).flags.dev).toBe(true);
|
|
111
|
+
expect(parseArgs(["workspace", "create", "Dev WS"]).flags.dev).toBe(false);
|
|
112
|
+
// The boolean flag does not consume the workspace name positional.
|
|
113
|
+
expect(parseArgs(["workspace", "create", "Dev WS", "--dev"]).toolArgs).toBe("Dev WS");
|
|
114
|
+
});
|
|
66
115
|
it("parses --yes and -y as the same boolean confirmation flag (default false)", () => {
|
|
67
116
|
expect(parseArgs(["workspace", "delete", "wsp_1", "--yes"]).flags.yes).toBe(true);
|
|
68
117
|
expect(parseArgs(["workspace", "delete", "wsp_1", "-y"]).flags.yes).toBe(true);
|
package/dist/cli.js
CHANGED
|
@@ -14,10 +14,12 @@ import { LoticsClient, API_BASE_URL } from "./client.js";
|
|
|
14
14
|
import { resolveContext, deleteConfig, getConfigPath, loadGlobalConfig, saveGlobalConfig, loadLocalConfig, upsertProfile, removeProfile, setActiveOrg, setSelectedWorkspace, resolveProfileByNameOrId, checkForUpdate, } from "./config.js";
|
|
15
15
|
import { VERSION } from "./version.js";
|
|
16
16
|
import { appCreate, appPull, appDeploy, appDev, appSetSubdomain, appRename, appVersions, appCodegen, appExecuteWorkflow, appWorkflowSet, appWorkflowPull, appWorkflowCheck, appQuerySet, appUiLink, } from "./app_commands.js";
|
|
17
|
+
import { packageInstall, packageEject, packageDoctor, packageUpgrade, parseResolveFlags, packageNew, packageBuild, packagePublish, packageDev, packageSync, packageReset, packageExtract, packageAdopt, packageFleetUpgrade, } from "./package_commands.js";
|
|
17
18
|
import { parseArgs } from "./args.js";
|
|
18
19
|
import { ingestJsonArgs } from "./inputs.js";
|
|
19
20
|
import { runXlsxCommand } from "./xlsx.js";
|
|
20
21
|
import { runDocxCommand } from "./docx.js";
|
|
22
|
+
import { runPreviewCommand } from "./preview.js";
|
|
21
23
|
function printHelp() {
|
|
22
24
|
console.log(`Lotics CLI v${VERSION} — AI agent interface for Lotics
|
|
23
25
|
|
|
@@ -61,15 +63,12 @@ COMMANDS
|
|
|
61
63
|
lotics workspace select <id> Switch to a different workspace
|
|
62
64
|
lotics workspace create <name> Create a new workspace (admin only)
|
|
63
65
|
lotics workspace delete <id> --yes Delete a workspace (admin only; soft delete, recoverable)
|
|
66
|
+
lotics workspace doctor Report dangling schema references (admin only)
|
|
64
67
|
lotics tools List all available tools
|
|
65
68
|
lotics tools <name> Show tool description and input schema
|
|
66
69
|
lotics run <tool> '<json>' Execute a tool
|
|
67
70
|
lotics run <tool> @args.json Read JSON args from a file (large payloads)
|
|
68
71
|
cat args.json | lotics run <tool> Read JSON args from stdin (large payloads)
|
|
69
|
-
lotics upload <file|dir...> Upload files (directories expand to their immediate files)
|
|
70
|
-
lotics download <file_id> Download a file by ID
|
|
71
|
-
lotics download record <record_id> <field_key>
|
|
72
|
-
Download all files on a record file field
|
|
73
72
|
lotics app create <name> [path] Create a new custom-code app + scaffold locally
|
|
74
73
|
lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
|
|
75
74
|
lotics app deploy -m <message> Build + upload current dir as a new version
|
|
@@ -97,6 +96,37 @@ COMMANDS
|
|
|
97
96
|
lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
|
|
98
97
|
lotics app rename "<new name>" Rename the app's display name (launcher title)
|
|
99
98
|
lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
|
|
99
|
+
lotics package new <name> [path] Scaffold a package project (contract + app source)
|
|
100
|
+
lotics package build [path] Build the publishable bundle (source + dist)
|
|
101
|
+
lotics package publish [path] Publish a new immutable package version
|
|
102
|
+
lotics package dev [path] Sync the package into a dev workspace + run the app
|
|
103
|
+
dev server (--workspace <dev_ws> selects it)
|
|
104
|
+
lotics package sync [path] Re-sync (additive migrate + materialize) into a dev ws
|
|
105
|
+
lotics package reset [path] DEV-ONLY: drop scaffolded tables + re-scaffold clean
|
|
106
|
+
lotics package install <package> [--version N]
|
|
107
|
+
Install an app package into this workspace
|
|
108
|
+
(scaffolds the data model + deploys + materializes)
|
|
109
|
+
lotics package upgrade <app_id> [--version N] [--resolve <key>=recreate|revert|keep|<id> ...]
|
|
110
|
+
Preview-then-apply a package upgrade; refuses while
|
|
111
|
+
any drift/modified finding lacks a --resolve, and
|
|
112
|
+
hard-stops on breaking contract changes
|
|
113
|
+
lotics package doctor [app_id] Installation health: version pin vs latest, binding
|
|
114
|
+
drift, locally modified core (exit 1 on findings)
|
|
115
|
+
lotics package rebind-role <app_id> <alias> <grp_id>
|
|
116
|
+
Re-point a package role at a different group
|
|
117
|
+
(re-materializes at the pinned version)
|
|
118
|
+
lotics package eject <app_id> Sever an installation's package link
|
|
119
|
+
(re-deploys the pinned source as a bespoke app)
|
|
120
|
+
lotics package extract <app_id> [path] Promote a bespoke app to a draft package
|
|
121
|
+
project (contract + binding + templates); always
|
|
122
|
+
writes the draft, exits 1 on error findings
|
|
123
|
+
lotics package adopt <app_id> [path] Bind the published package project onto the
|
|
124
|
+
origin app (reads .lotics/adopt_binding.json;
|
|
125
|
+
--version N pins a specific version)
|
|
126
|
+
lotics package fleet-upgrade <package_id> [--version N]
|
|
127
|
+
Upgrade EVERY installation of the package across
|
|
128
|
+
your org: applies where the preview is clean,
|
|
129
|
+
skips + reports findings (exit 1 unless all current)
|
|
100
130
|
lotics ui link <component> [--ui-src <path>] [--remove]
|
|
101
131
|
Dev-link @lotics/ui to packages/ui/src (Vite alias
|
|
102
132
|
+ tsc paths) for live HMR + typecheck. Monorepo apps
|
|
@@ -107,6 +137,12 @@ COMMANDS
|
|
|
107
137
|
lotics docx <subcommand> ... Read/write/edit .docx files on your local filesystem
|
|
108
138
|
(uses the bundled Lotics OOXML engine; preserves
|
|
109
139
|
tables, images, and unknown markup verbatim)
|
|
140
|
+
lotics file upload <file|dir...> Upload files (alias: lotics upload)
|
|
141
|
+
lotics file download <file_id> Download a file by ID (alias: lotics download)
|
|
142
|
+
lotics file download record <record_id> <field_key>
|
|
143
|
+
Download all files on a record file field
|
|
144
|
+
lotics file preview <file> [-o png] Render a .docx/.xlsx to a PNG (frontend engines;
|
|
145
|
+
needs a Chrome/Chromium on the machine)
|
|
110
146
|
|
|
111
147
|
FLAGS
|
|
112
148
|
--json Full JSON output (default is human-readable text)
|
|
@@ -119,6 +155,8 @@ FLAGS
|
|
|
119
155
|
is_current_member / row-scoping resolve to them (also
|
|
120
156
|
LOTICS_VIEW_AS env; admin key only; writes stay yours)
|
|
121
157
|
--local Pin the current directory (lotics org use / auth api-key)
|
|
158
|
+
--dev (lotics workspace create) Mark a throwaway app-package dev
|
|
159
|
+
workspace — required for "lotics package reset"
|
|
122
160
|
--all (lotics auth logout) Remove every saved credential
|
|
123
161
|
--version Show version
|
|
124
162
|
|
|
@@ -392,7 +430,22 @@ function resolveUploadPaths(rawPaths) {
|
|
|
392
430
|
return result;
|
|
393
431
|
}
|
|
394
432
|
async function main() {
|
|
395
|
-
const
|
|
433
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
434
|
+
const { restArgs, flags } = parsed;
|
|
435
|
+
let { command, subcommand, toolArgs } = parsed;
|
|
436
|
+
// `file` is a thin noun-group over the file verbs: `lotics file <upload|download|preview>
|
|
437
|
+
// <args…>` strips the prefix and re-dispatches to the same handlers as the top-level
|
|
438
|
+
// verbs (which stay as aliases). Positional shift only — flags are untouched, and
|
|
439
|
+
// `download record <id> <field>` still works as `file download record <id> <field>`.
|
|
440
|
+
if (command === "file") {
|
|
441
|
+
if (!subcommand) {
|
|
442
|
+
printHelp();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
command = subcommand;
|
|
446
|
+
subcommand = toolArgs;
|
|
447
|
+
toolArgs = restArgs.shift();
|
|
448
|
+
}
|
|
396
449
|
if (flags.help || (!command && !flags.version)) {
|
|
397
450
|
printHelp();
|
|
398
451
|
return;
|
|
@@ -494,6 +547,10 @@ async function main() {
|
|
|
494
547
|
await runDocxCommand(subcommand, toolArgs, restArgs);
|
|
495
548
|
return;
|
|
496
549
|
}
|
|
550
|
+
if (command === "preview") {
|
|
551
|
+
await runPreviewCommand(subcommand, flags);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
497
554
|
// --- lotics ui link <component> [--remove] — local vite.config edit, no auth ---
|
|
498
555
|
if (command === "ui") {
|
|
499
556
|
if (subcommand === "link") {
|
|
@@ -514,6 +571,20 @@ async function main() {
|
|
|
514
571
|
console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
|
|
515
572
|
process.exit(1);
|
|
516
573
|
}
|
|
574
|
+
// --- lotics package new / build — local scaffold + build, no auth required ---
|
|
575
|
+
if (command === "package" && subcommand === "new") {
|
|
576
|
+
const name = toolArgs;
|
|
577
|
+
if (!name) {
|
|
578
|
+
console.error("Usage: lotics package new <name> [path]");
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
await packageNew({ name, targetPath: restArgs[0] });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (command === "package" && subcommand === "build") {
|
|
585
|
+
await packageBuild({ projectDir: toolArgs });
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
517
588
|
// --- lotics app workflow check [alias] — local typecheck, no auth, no network ---
|
|
518
589
|
// Reads src/workflows/*.ts + .lotics/workflows/*.globals.d.ts and the app's own
|
|
519
590
|
// typescript; builds one isolated program per alias. Handled before the auth
|
|
@@ -596,7 +667,7 @@ async function main() {
|
|
|
596
667
|
return;
|
|
597
668
|
}
|
|
598
669
|
// --- Validate command before auth ---
|
|
599
|
-
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app") {
|
|
670
|
+
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "package") {
|
|
600
671
|
console.error(`Unknown command: ${command}`);
|
|
601
672
|
console.error('Run "lotics --help" for usage.');
|
|
602
673
|
process.exit(1);
|
|
@@ -620,6 +691,24 @@ async function main() {
|
|
|
620
691
|
console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
|
|
621
692
|
process.exit(1);
|
|
622
693
|
}
|
|
694
|
+
if (command === "package" && !subcommand) {
|
|
695
|
+
console.error("Usage:");
|
|
696
|
+
console.error(" lotics package new <name> [path] Scaffold a package project (contract + app source)");
|
|
697
|
+
console.error(" lotics package build [path] Build the publishable bundle (source + dist)");
|
|
698
|
+
console.error(" lotics package publish [path] [-m <changelog>] Publish a new immutable package version");
|
|
699
|
+
console.error(" lotics package dev [path] [--workspace <ws>] Sync into a dev workspace + run the app dev server");
|
|
700
|
+
console.error(" lotics package sync [path] [--workspace <ws>] Re-sync (additive migrate + materialize) into a dev workspace");
|
|
701
|
+
console.error(" lotics package reset [path] [--workspace <ws>] DEV-ONLY: drop scaffolded tables + re-scaffold clean");
|
|
702
|
+
console.error(" lotics package install <package> [--version N] Install a package into this workspace");
|
|
703
|
+
console.error(" lotics package upgrade <app_id> [--version N] [--resolve ns.alias=recreate|<id>] Preview + apply an upgrade");
|
|
704
|
+
console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding drift");
|
|
705
|
+
console.error(" lotics package rebind-role <app_id> <alias> <grp_id> Re-point a package role at another group");
|
|
706
|
+
console.error(" lotics package eject <app_id> Sever an installation's package link");
|
|
707
|
+
console.error(" lotics package extract <app_id> [path] Promote a bespoke app to a draft package project");
|
|
708
|
+
console.error(" lotics package adopt <app_id> [path] Bind the published project onto the origin app");
|
|
709
|
+
console.error(" lotics package fleet-upgrade <package_id> [--version N] Upgrade every org installation (clean ones apply; findings skip)");
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
623
712
|
if (command === "run" && !subcommand) {
|
|
624
713
|
console.error("Usage: lotics run <tool> '<json_args>'\n" +
|
|
625
714
|
" lotics run <tool> @args.json (read args from a file — for large payloads)\n" +
|
|
@@ -647,6 +736,24 @@ async function main() {
|
|
|
647
736
|
const { client, ctx } = requireClient(flags);
|
|
648
737
|
// lotics workspace / lotics workspace list / lotics workspace select <id>
|
|
649
738
|
if (command === "workspace") {
|
|
739
|
+
if (subcommand === "doctor") {
|
|
740
|
+
// The workspace command family runs before the global workspace
|
|
741
|
+
// resolution — doctor needs the same first-workspace fallback every
|
|
742
|
+
// data command gets.
|
|
743
|
+
await resolveWorkspace(client, ctx);
|
|
744
|
+
const dangling = await client.getWorkspaceDanglingReferences();
|
|
745
|
+
if (dangling.length === 0) {
|
|
746
|
+
console.error("Workspace references: healthy — every referenced schema id resolves.");
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
console.error(`Dangling schema references (${dangling.length}):`);
|
|
750
|
+
for (const d of dangling) {
|
|
751
|
+
console.error(` - ${d.referent.kind} "${d.referent.name}" (${d.referent.id}) → ${d.namespace} ${d.id} (missing)`);
|
|
752
|
+
}
|
|
753
|
+
console.error("Each referent points at schema that no longer exists — edit or archive it, or recreate the missing schema.");
|
|
754
|
+
process.exitCode = 1;
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
650
757
|
const workspaces = await client.listWorkspaces();
|
|
651
758
|
const currentWorkspaceId = ctx.workspaceId;
|
|
652
759
|
if (subcommand === "select") {
|
|
@@ -673,11 +780,11 @@ async function main() {
|
|
|
673
780
|
if (subcommand === "create") {
|
|
674
781
|
const name = toolArgs;
|
|
675
782
|
if (!name) {
|
|
676
|
-
console.error('Usage: lotics workspace create <name> [--timezone <tz>]');
|
|
783
|
+
console.error('Usage: lotics workspace create <name> [--timezone <tz>] [--dev]');
|
|
677
784
|
process.exit(1);
|
|
678
785
|
}
|
|
679
786
|
const timezone = flags.timezone;
|
|
680
|
-
const created = await client.createWorkspace({ name, timezone });
|
|
787
|
+
const created = await client.createWorkspace({ name, timezone, is_dev: flags.dev || undefined });
|
|
681
788
|
setSelectedWorkspace(created.id);
|
|
682
789
|
client.setWorkspaceId(created.id);
|
|
683
790
|
if (flags.json) {
|
|
@@ -746,6 +853,153 @@ async function main() {
|
|
|
746
853
|
}
|
|
747
854
|
// Ensure workspace is resolved for all remaining commands
|
|
748
855
|
await resolveWorkspace(client, ctx);
|
|
856
|
+
// lotics package install <package> [--version N]
|
|
857
|
+
if (command === "package") {
|
|
858
|
+
if (subcommand === "install") {
|
|
859
|
+
const packageId = toolArgs;
|
|
860
|
+
if (!packageId) {
|
|
861
|
+
console.error("Usage: lotics package install <package> [--version N]");
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
let version;
|
|
865
|
+
if (flags.packageVersion !== undefined) {
|
|
866
|
+
version = Number(flags.packageVersion);
|
|
867
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
868
|
+
console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
await packageInstall(client, { package_id: packageId, version });
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if (subcommand === "eject") {
|
|
876
|
+
const appId = toolArgs;
|
|
877
|
+
if (!appId) {
|
|
878
|
+
console.error("Usage: lotics package eject <app_id>");
|
|
879
|
+
process.exit(1);
|
|
880
|
+
}
|
|
881
|
+
await packageEject(client, { app_id: appId });
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (subcommand === "extract") {
|
|
885
|
+
const appId = toolArgs;
|
|
886
|
+
if (!appId) {
|
|
887
|
+
console.error("Usage: lotics package extract <app_id> [path]");
|
|
888
|
+
console.error("Promotes a bespoke app to a draft package project (contract + binding + templates).");
|
|
889
|
+
process.exit(1);
|
|
890
|
+
}
|
|
891
|
+
await packageExtract(client, { app_id: appId, targetPath: restArgs[0] });
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
if (subcommand === "adopt") {
|
|
895
|
+
const appId = toolArgs;
|
|
896
|
+
if (!appId) {
|
|
897
|
+
console.error("Usage: lotics package adopt <app_id> [path]");
|
|
898
|
+
console.error("Binds the published package project (this dir, or [path]) onto the origin app.");
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
let version;
|
|
902
|
+
if (flags.packageVersion !== undefined) {
|
|
903
|
+
version = Number(flags.packageVersion);
|
|
904
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
905
|
+
console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
|
|
906
|
+
process.exit(1);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
await packageAdopt(client, { app_id: appId, version, projectDir: restArgs[0] });
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (subcommand === "fleet-upgrade") {
|
|
913
|
+
const packageId = toolArgs;
|
|
914
|
+
if (!packageId) {
|
|
915
|
+
console.error("Usage: lotics package fleet-upgrade <package_id> [--version N]");
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
let version;
|
|
919
|
+
if (flags.packageVersion !== undefined) {
|
|
920
|
+
version = Number(flags.packageVersion);
|
|
921
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
922
|
+
console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
await packageFleetUpgrade(client, { package_id: packageId, version });
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
if (subcommand === "doctor") {
|
|
930
|
+
await packageDoctor(client, { app_id: toolArgs });
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (subcommand === "rebind-role") {
|
|
934
|
+
const appId = toolArgs;
|
|
935
|
+
const [roleAlias, groupId] = restArgs;
|
|
936
|
+
if (!appId || !roleAlias || !groupId) {
|
|
937
|
+
console.error("Usage: lotics package rebind-role <app_id> <role_alias> <grp_id>");
|
|
938
|
+
process.exit(1);
|
|
939
|
+
}
|
|
940
|
+
const result = await client.rebindAppPackageRole(appId, {
|
|
941
|
+
role_alias: roleAlias,
|
|
942
|
+
group_id: groupId,
|
|
943
|
+
});
|
|
944
|
+
console.error(`Rebound role "${result.role_alias}" → ${result.group_id}` +
|
|
945
|
+
(result.previous_group_id ? ` (was ${result.previous_group_id})` : "") +
|
|
946
|
+
". Package workflows/agents were re-materialized against the new group.");
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (subcommand === "upgrade") {
|
|
950
|
+
const appId = toolArgs;
|
|
951
|
+
if (!appId) {
|
|
952
|
+
console.error("Usage: lotics package upgrade <app_id> [--version N] [--resolve <namespace.alias>=recreate|<id> ...]");
|
|
953
|
+
process.exit(1);
|
|
954
|
+
}
|
|
955
|
+
let version;
|
|
956
|
+
if (flags.packageVersion !== undefined) {
|
|
957
|
+
version = Number(flags.packageVersion);
|
|
958
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
959
|
+
console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
|
|
960
|
+
process.exit(1);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
await packageUpgrade(client, {
|
|
964
|
+
app_id: appId,
|
|
965
|
+
version,
|
|
966
|
+
resolutions: parseResolveFlags(flags.resolve),
|
|
967
|
+
});
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
if (subcommand === "publish") {
|
|
971
|
+
await packagePublish(client, { projectDir: toolArgs, changelog: flags.message });
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (subcommand === "sync") {
|
|
975
|
+
await packageSync(client, { projectDir: toolArgs });
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
if (subcommand === "reset") {
|
|
979
|
+
await packageReset(client, { projectDir: toolArgs });
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
if (subcommand === "dev") {
|
|
983
|
+
// First positional is an optional project path; --port / --vite-port
|
|
984
|
+
// override the wrapper / Vite ports (same shape as `lotics app dev`).
|
|
985
|
+
const projectDir = toolArgs;
|
|
986
|
+
let port;
|
|
987
|
+
let vitePort;
|
|
988
|
+
for (const a of restArgs) {
|
|
989
|
+
const portMatch = /^--port=(\d+)$/.exec(a);
|
|
990
|
+
const vitePortMatch = /^--vite-port=(\d+)$/.exec(a);
|
|
991
|
+
if (portMatch)
|
|
992
|
+
port = Number(portMatch[1]);
|
|
993
|
+
else if (vitePortMatch)
|
|
994
|
+
vitePort = Number(vitePortMatch[1]);
|
|
995
|
+
}
|
|
996
|
+
await packageDev(client, { projectDir, port, vitePort });
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
console.error(`Unknown package subcommand: ${subcommand}`);
|
|
1000
|
+
console.error("Run 'lotics package' for usage.");
|
|
1001
|
+
process.exit(1);
|
|
1002
|
+
}
|
|
749
1003
|
// lotics app create / pull / deploy
|
|
750
1004
|
if (command === "app") {
|
|
751
1005
|
if (subcommand === "create") {
|