@lotics/cli 0.134.0 → 0.136.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/dist/src/cli.js +71 -16
- package/docs/cli_reference.md +1 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -47483,9 +47483,10 @@ var CAPABILITY_GATED_CALLS = {
|
|
|
47483
47483
|
comments: ["useComments", "createComment", "updateComment", "deleteComment"]
|
|
47484
47484
|
};
|
|
47485
47485
|
function undeclaredCapabilities(sourceText, declared) {
|
|
47486
|
+
const code = codeWithoutComments(sourceText);
|
|
47486
47487
|
const used = [];
|
|
47487
47488
|
for (const [capability, calls] of Object.entries(CAPABILITY_GATED_CALLS)) {
|
|
47488
|
-
const isCalled = calls.some((call) => new RegExp(`\\b${call}\\b`).test(
|
|
47489
|
+
const isCalled = calls.some((call) => new RegExp(`\\b${call}\\b`).test(code));
|
|
47489
47490
|
if (isCalled && declared?.[capability] !== true) used.push(capability);
|
|
47490
47491
|
}
|
|
47491
47492
|
return used;
|
|
@@ -47506,6 +47507,51 @@ function canonicalJson(value2) {
|
|
|
47506
47507
|
};
|
|
47507
47508
|
return JSON.stringify(normalize(value2));
|
|
47508
47509
|
}
|
|
47510
|
+
function codeWithoutComments(sourceText) {
|
|
47511
|
+
const out = sourceText.split("");
|
|
47512
|
+
const n = sourceText.length;
|
|
47513
|
+
const blank = (from, to) => {
|
|
47514
|
+
for (let k = from; k < to; k++) if (out[k] !== "\n") out[k] = " ";
|
|
47515
|
+
};
|
|
47516
|
+
let i2 = 0;
|
|
47517
|
+
while (i2 < n) {
|
|
47518
|
+
const c = sourceText[i2];
|
|
47519
|
+
const next = sourceText[i2 + 1];
|
|
47520
|
+
if (c === "/" && next === "/") {
|
|
47521
|
+
let j = i2;
|
|
47522
|
+
while (j < n && sourceText[j] !== "\n") j++;
|
|
47523
|
+
blank(i2, j);
|
|
47524
|
+
i2 = j;
|
|
47525
|
+
continue;
|
|
47526
|
+
}
|
|
47527
|
+
if (c === "/" && next === "*") {
|
|
47528
|
+
let j = i2 + 2;
|
|
47529
|
+
while (j < n && !(sourceText[j] === "*" && sourceText[j + 1] === "/")) j++;
|
|
47530
|
+
j = j < n ? j + 2 : n;
|
|
47531
|
+
blank(i2, j);
|
|
47532
|
+
i2 = j;
|
|
47533
|
+
continue;
|
|
47534
|
+
}
|
|
47535
|
+
if (c === '"' || c === "'") {
|
|
47536
|
+
let j = i2 + 1;
|
|
47537
|
+
while (j < n && sourceText[j] !== c && sourceText[j] !== "\n") {
|
|
47538
|
+
j += sourceText[j] === "\\" ? 2 : 1;
|
|
47539
|
+
}
|
|
47540
|
+
i2 = j < n && sourceText[j] === c ? j + 1 : j;
|
|
47541
|
+
continue;
|
|
47542
|
+
}
|
|
47543
|
+
if (c === "`") {
|
|
47544
|
+
let j = i2 + 1;
|
|
47545
|
+
while (j < n && sourceText[j] !== "`") {
|
|
47546
|
+
j += sourceText[j] === "\\" ? 2 : 1;
|
|
47547
|
+
}
|
|
47548
|
+
i2 = j < n ? j + 1 : n;
|
|
47549
|
+
continue;
|
|
47550
|
+
}
|
|
47551
|
+
i2++;
|
|
47552
|
+
}
|
|
47553
|
+
return out.join("");
|
|
47554
|
+
}
|
|
47509
47555
|
var ALIAS_CALL_HOOKS = {
|
|
47510
47556
|
queries: "useQuery",
|
|
47511
47557
|
workflows: "useWorkflow",
|
|
@@ -47513,6 +47559,7 @@ var ALIAS_CALL_HOOKS = {
|
|
|
47513
47559
|
};
|
|
47514
47560
|
var LITERAL_ALIAS_ARG = `["'\`]([A-Za-z_$][A-Za-z0-9_$]*)["'\`]`;
|
|
47515
47561
|
function calledAppAliases(sourceText) {
|
|
47562
|
+
const code = codeWithoutComments(sourceText);
|
|
47516
47563
|
const out = {
|
|
47517
47564
|
queries: [],
|
|
47518
47565
|
workflows: [],
|
|
@@ -47522,8 +47569,8 @@ function calledAppAliases(sourceText) {
|
|
|
47522
47569
|
for (const [kind, hook] of Object.entries(ALIAS_CALL_HOOKS)) {
|
|
47523
47570
|
const seen = /* @__PURE__ */ new Set();
|
|
47524
47571
|
let isDynamic = false;
|
|
47525
|
-
for (const call of
|
|
47526
|
-
const rest2 =
|
|
47572
|
+
for (const call of code.matchAll(new RegExp(`\\b${hook}\\s*\\(`, "g"))) {
|
|
47573
|
+
const rest2 = code.slice(call.index + call[0].length);
|
|
47527
47574
|
const literal2 = new RegExp(`^\\s*${LITERAL_ALIAS_ARG}`).exec(rest2);
|
|
47528
47575
|
if (literal2) seen.add(literal2[1]);
|
|
47529
47576
|
else isDynamic = true;
|
|
@@ -72048,23 +72095,28 @@ async function appAgentSet(client, args) {
|
|
|
72048
72095
|
noteScope({ alias: args.alias });
|
|
72049
72096
|
const app = await client.getApp(meta3.app_id);
|
|
72050
72097
|
const live = app.agents?.[args.alias];
|
|
72051
|
-
|
|
72052
|
-
console.error(
|
|
72053
|
-
`App ${meta3.app_id} has no bound agent "${args.alias}". Bind it first (set_app_agent), then 'lotics app pull' to write its instructions.`
|
|
72054
|
-
);
|
|
72055
|
-
process.exit(1);
|
|
72056
|
-
}
|
|
72098
|
+
const isNew = live === void 0;
|
|
72057
72099
|
const declared = meta3.agents?.[args.alias];
|
|
72058
72100
|
const hasDeclaration = declared?.inputs !== void 0 || declared?.outputs !== void 0;
|
|
72059
72101
|
const file2 = agentFilePath2(projectDir, args.alias);
|
|
72060
|
-
|
|
72061
|
-
|
|
72062
|
-
|
|
72102
|
+
const hasProse = fs6.existsSync(file2);
|
|
72103
|
+
if (!hasProse) {
|
|
72104
|
+
if (isNew) {
|
|
72105
|
+
console.error(
|
|
72106
|
+
`Agent "${args.alias}" is not bound yet, so this would create it \u2014 and an agent cannot be created without instructions.
|
|
72107
|
+
Write ${AGENTS_DIR}/${args.alias}.md, then re-run this.`
|
|
72108
|
+
);
|
|
72109
|
+
process.exit(1);
|
|
72110
|
+
}
|
|
72111
|
+
if (!hasDeclaration) {
|
|
72112
|
+
console.error(
|
|
72113
|
+
`No instructions at ${path7.relative(projectDir, file2)}, and nothing declared in package.json#lotics.agents.${args.alias} to push instead.
|
|
72063
72114
|
Run 'lotics app pull ${meta3.app_id}' to write ${AGENTS_DIR}/${args.alias}.md, then edit it.`
|
|
72064
|
-
|
|
72065
|
-
|
|
72115
|
+
);
|
|
72116
|
+
process.exit(1);
|
|
72117
|
+
}
|
|
72066
72118
|
}
|
|
72067
|
-
const instructions =
|
|
72119
|
+
const instructions = hasProse ? stripAgentHeader(fs6.readFileSync(file2, "utf-8")) : void 0;
|
|
72068
72120
|
if (instructions === "") {
|
|
72069
72121
|
console.error(
|
|
72070
72122
|
`${path7.relative(projectDir, file2)} is empty after stripping the header \u2014 refusing to push an empty prompt.`
|
|
@@ -72094,7 +72146,10 @@ async function appAgentSet(client, args) {
|
|
|
72094
72146
|
...declared?.outputs !== void 0 ? ["outputs"] : []
|
|
72095
72147
|
];
|
|
72096
72148
|
console.error(
|
|
72097
|
-
|
|
72149
|
+
// "Created" is not decoration: an alias that only LOOKS like the one the
|
|
72150
|
+
// author meant reaches here with a matching file and writes a second agent.
|
|
72151
|
+
// The verb is the one line that says which of the two just happened.
|
|
72152
|
+
`${isNew ? "Created" : "Set"} agent "${args.alias}" (${pushed.join(", ")}` + (live?.model_tier ? `, ${live.model_tier}` : "") + (isNew ? `). Nothing else is declared yet \u2014 set tool_names / knowledge_doc_ids / query_aliases with set_app_agent.` : `). Everything else left as it is.`)
|
|
72098
72153
|
);
|
|
72099
72154
|
}
|
|
72100
72155
|
async function appWorkflowSet(client, args) {
|
package/docs/cli_reference.md
CHANGED
|
@@ -36,7 +36,7 @@ Per-command syntax, flags, contracts, and gotchas for the public `lotics` CLI. S
|
|
|
36
36
|
| `lotics app check` | Every pre-flight `deploy` runs, WITHOUT building or shipping: the manifest's agent schemas against the live app row, every binding a deploy would push, aliases the source calls that nothing bound (queries, workflows AND agents), bindings the app serves that the source names nowhere, capability-gated SDK calls the manifest doesn't declare, a missing icon/theme, and a notice for any alias the source computes at runtime (invisible to every check here and to the deploy's unbind guard). Adds no rule of its own — each finding is the same helper `deploy` calls, so a green check means a deploy will not complain. **Exits 1 on what a `deploy` would REFUSE or PUSH** — an agent schema that disagrees with the live app, and any binding the project has ahead of the app (an edited workflow body or declaration, edited agent prose, a changed query). Both are things a deploy would act on, so CI gating on a green check means a deploy has nothing left to do; genuine advisories (capabilities, branding, a runtime-computed alias, orphaned bindings) stay advisory and never fail it. The point is the question being ASKABLE: these checks used to cost a build, a tar, an upload and a version row in the audit trail, which is expensive enough that the honest move was to skip them and find out in production. |
|
|
37
37
|
| `lotics app workflow run <alias> '<json>'` | Execute a bound app workflow end-to-end via `appWorkflow`. `app_id` comes from the local manifest; the alias must be bound (`set_app_workflow`). Inputs ingest exactly like `lotics run` (inline JSON / `@file` / stdin — bulk inputs bypass `ARG_MAX`). Prints the full `{status,message,data,files,side_effects}` JSON to stdout + a one-line summary to stderr; exits non-zero on `status:"error"` (assertable). `--print-created` (alias `--report-effects`) renders the honest post-run harvest: created records grouped by table, a paste-ready `lotics run delete_records …` per table, then the **mandatory caveat** naming what cannot be auto-undone (external integrations + notifications) and that sub-workflows may have run. `--cleanup` (DEFAULT OFF, implies the report) additionally runs the deletes for harvested records ONLY — never files / external / notifications. Neither is a rollback — a rollback is structurally impossible here. |
|
|
38
38
|
| `lotics app workflow set <alias>` | Push the edited `src/workflows/<alias>.ts` body through `set_app_workflow` (the single author of `apps.workflows`). Reads the body from disk (header + `/// <reference>` + `export {};` marker + the `__workflow` wrapper all stripped) + the typed `inputs`/`outputs` **and the `description`** from `package.json#lotics.workflows.<alias>`; the **server** re-verifies the body and echoes the bound `outputs` (declared, else DERIVED from `return({ data })`). The `description` is the one line an agent reads when choosing between the app's aliases (the workflow counterpart to a query's) — authored in the manifest so it lives beside the body in version control and rides every push; omit it and the workflow keeps whatever description it already has, so a push can never blank one set elsewhere. When the manifest declared NO `outputs`, the DERIVED echo is written back into `package.json#lotics.workflows.<alias>.outputs` (a SURGICAL write — preserves `knowledge`/`config` and every other manifest field) and that alias's types are refreshed in place, so `useWorkflow("<alias>")`'s `result.data` is typed immediately with no hand-copy and no second `lotics app codegen`; an explicitly-declared `outputs` is authoritative and never overwritten. Deploy still never authors workflows — this is a CLI convenience over the existing tool. Clear error + non-zero exit on a missing file, an alias absent from the manifest, or a verify failure. |
|
|
39
|
-
| `lotics app agent set <alias>` | Push
|
|
39
|
+
| `lotics app agent set <alias>` | Push `src/agents/<alias>.md` — plus `inputs`/`outputs` when `package.json#lotics.agents.<alias>` declares them — through `set_app_agent`. The agent mirror of `app workflow set`, and the deploy-free authoring path for an agent's prose and its typed edges. **It sends only those fields.** Everything else is absent, and absent means unchanged, so a declaration this CLI does not model cannot be reverted by a push from a checkout that predates it — the chat authoring agent's `knowledge_doc_ids`, another operator's `query_aliases` grant. To change one of those, call `set_app_agent` with just that field (`lotics run set_app_agent '{"app_id":…,"alias":…,"tool_names":[…]}'` — it merges), then `app pull` to bring the manifest back in step. **CREATES the alias when the app has not bound one yet**, so a new agent is authored the same way a new workflow is: write the prose, declare the typed half, push. A create needs the prose file (an agent without instructions is not an agent); it is gated on nothing else, because what keeps a binding alive is a `useAppAgentRun("<alias>")` call site in the shipped bundle — a deploy prunes an agent the bundle never names, manifest entry or not. The prose push is a conditional write against the fingerprint this project last saw, so it is refused rather than allowed to overwrite prose someone else changed. Clear error + non-zero exit when there is no prose file and nothing declared to push instead, when a create has no prose to create from, or when the file is empty once the header is stripped. |
|
|
40
40
|
| `lotics app query set <alias>` \| `--all` | Push `package.json#lotics.queries` (`{ ast, params? }` per alias) to `apps.queries` through `set_app_query` — **the only author of a query binding**, the mirror of `app workflow set`. A deploy ships code and binds nothing. The **server** validates each one exactly as it always did (alias identifier, workspace-only tables, resolvable fields, declared params). `--all` pushes every declared alias, alias-sorted, stopping at the first failure and naming what already landed. Clear error + non-zero exit on an alias absent from the manifest or a validation failure. |
|
|
41
41
|
| `lotics app agent run <app_id> <alias> ['<json>'\|@file\|stdin]` | Run a bound app agent end-to-end. A run needs no deployed UI bundle — just the app row + the bound agent declaration + member auth — so the **`app_id` is explicit** (not read from a local manifest). Inputs ingest exactly like `lotics run` (inline JSON / `@file` / stdin; empty = `{}`). Opens the run's SSE (`appAgentRunStream`), streams `text-delta` prose to **stderr** as live progress, then reports from the **settled run RECORD** (`listAgentRuns`, polled to a terminal status — the client stream can close a beat before the run settles, or drop while it runs on server-side): default prints the run's structured `output` (JSON) or final text to **stdout** + a status line to stderr; `--json` prints the full run summary to stdout. Selects THIS run by the `x-app-agent-run-id` header (ordering-independent). Exits 0 **only** when the settled status is `completed`; otherwise non-zero with the run's error surfaced. A settled run that never appears fails loudly (never a silent success). A fresh `session_id` is minted per run (self-contained); `--session <id>` continues an existing thread (prior runs become the agent's context). |
|
|
42
42
|
| `lotics app workflow pull` | Rewrite every `src/workflows/<alias>.ts` from the server (faithful body per bound alias via `get_app_workflow`) **+ its `.lotics/workflows/<alias>.globals.d.ts`** (via `getAppWorkflowDts`, so the body is locally typecheckable via `lotics app workflow check`) without a full `app pull` (no source archive, no npm install). A legacy alias with no rendered source warns and is skipped; a dts-fetch failure is non-fatal (body still written with the fallback wrapper, typecheck degraded). Each alias's `description` is folded back into `package.json#lotics.workflows.<alias>` from the same read — the alias binding the manifest is otherwise stamped from carries `inputs`/`outputs` but not the description, which lives on the workflow ROW, so without this a pull would erase an authored one. The server's GENERATED default is skipped, so an app that never described its workflows gains no manifest noise. Also idempotently patches the main `tsconfig.json` `exclude` to cover `src/workflows` + `.lotics/workflows` so a pre-existing app's `npm run typecheck` never loads the bodies or the colliding per-alias globals. |
|