@lotics/cli 0.156.4 → 0.156.6
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 +119 -7
- package/docs/cli_reference.md +1 -1
- package/docs/knowledge_docs.md +4 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -48165,6 +48165,35 @@ function canonicalJson(value2) {
|
|
|
48165
48165
|
};
|
|
48166
48166
|
return JSON.stringify(normalize(value2));
|
|
48167
48167
|
}
|
|
48168
|
+
var REGEX_MAY_FOLLOW = /* @__PURE__ */ new Set([
|
|
48169
|
+
"return",
|
|
48170
|
+
"typeof",
|
|
48171
|
+
"case",
|
|
48172
|
+
"in",
|
|
48173
|
+
"of",
|
|
48174
|
+
"delete",
|
|
48175
|
+
"void",
|
|
48176
|
+
"instanceof",
|
|
48177
|
+
"new",
|
|
48178
|
+
"do",
|
|
48179
|
+
"else",
|
|
48180
|
+
"yield",
|
|
48181
|
+
"await",
|
|
48182
|
+
"throw"
|
|
48183
|
+
]);
|
|
48184
|
+
function opensRegex(out, at2) {
|
|
48185
|
+
let k = at2 - 1;
|
|
48186
|
+
while (k >= 0 && (out[k] === " " || out[k] === "\n" || out[k] === " " || out[k] === "\r")) k--;
|
|
48187
|
+
if (k < 0) return true;
|
|
48188
|
+
const prev = out[k];
|
|
48189
|
+
if ("=(,:[!&|?{;+-*%>~^".includes(prev)) return true;
|
|
48190
|
+
if (/[A-Za-z0-9_$]/.test(prev)) {
|
|
48191
|
+
let s = k;
|
|
48192
|
+
while (s >= 0 && /[A-Za-z0-9_$]/.test(out[s])) s--;
|
|
48193
|
+
return REGEX_MAY_FOLLOW.has(out.slice(s + 1, k + 1).join(""));
|
|
48194
|
+
}
|
|
48195
|
+
return false;
|
|
48196
|
+
}
|
|
48168
48197
|
function codeWithoutComments(sourceText) {
|
|
48169
48198
|
const out = sourceText.split("");
|
|
48170
48199
|
const n = sourceText.length;
|
|
@@ -48206,6 +48235,27 @@ function codeWithoutComments(sourceText) {
|
|
|
48206
48235
|
i2 = j < n ? j + 1 : n;
|
|
48207
48236
|
continue;
|
|
48208
48237
|
}
|
|
48238
|
+
if (c === "/" && opensRegex(out, i2)) {
|
|
48239
|
+
let j = i2 + 1;
|
|
48240
|
+
let inClass = false;
|
|
48241
|
+
while (j < n) {
|
|
48242
|
+
const ch = sourceText[j];
|
|
48243
|
+
if (ch === "\\") {
|
|
48244
|
+
j += 2;
|
|
48245
|
+
continue;
|
|
48246
|
+
}
|
|
48247
|
+
if (ch === "\n") break;
|
|
48248
|
+
if (ch === "[") inClass = true;
|
|
48249
|
+
else if (ch === "]") inClass = false;
|
|
48250
|
+
else if (ch === "/" && !inClass) {
|
|
48251
|
+
j++;
|
|
48252
|
+
break;
|
|
48253
|
+
}
|
|
48254
|
+
j++;
|
|
48255
|
+
}
|
|
48256
|
+
i2 = j;
|
|
48257
|
+
continue;
|
|
48258
|
+
}
|
|
48209
48259
|
i2++;
|
|
48210
48260
|
}
|
|
48211
48261
|
return out.join("");
|
|
@@ -72924,6 +72974,72 @@ function warnAboutDevLink(projectDir, command) {
|
|
|
72924
72974
|
);
|
|
72925
72975
|
}
|
|
72926
72976
|
}
|
|
72977
|
+
var KIT_PACKAGES = ["@lotics/ui", "@lotics/app-sdk"];
|
|
72978
|
+
function installedKitVersion(projectDir, pkg2) {
|
|
72979
|
+
try {
|
|
72980
|
+
const manifest = path10.join(projectDir, "node_modules", ...pkg2.split("/"), "package.json");
|
|
72981
|
+
const parsed = JSON.parse(fs8.readFileSync(manifest, "utf-8"));
|
|
72982
|
+
const version2 = parsed.version;
|
|
72983
|
+
return typeof version2 === "string" ? version2 : null;
|
|
72984
|
+
} catch {
|
|
72985
|
+
return null;
|
|
72986
|
+
}
|
|
72987
|
+
}
|
|
72988
|
+
async function publishedKitVersion(pkg2) {
|
|
72989
|
+
const controller = new AbortController();
|
|
72990
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
72991
|
+
timeout.unref();
|
|
72992
|
+
try {
|
|
72993
|
+
const response = await fetch(`https://registry.npmjs.org/${pkg2}/latest`, {
|
|
72994
|
+
signal: controller.signal
|
|
72995
|
+
});
|
|
72996
|
+
if (!response.ok) return null;
|
|
72997
|
+
const data2 = await response.json();
|
|
72998
|
+
return data2.version ?? null;
|
|
72999
|
+
} catch {
|
|
73000
|
+
return null;
|
|
73001
|
+
} finally {
|
|
73002
|
+
clearTimeout(timeout);
|
|
73003
|
+
}
|
|
73004
|
+
}
|
|
73005
|
+
async function warnIfKitBehind(projectDir) {
|
|
73006
|
+
const checks = await Promise.all(
|
|
73007
|
+
KIT_PACKAGES.map(async (pkg2) => {
|
|
73008
|
+
const installed = installedKitVersion(projectDir, pkg2);
|
|
73009
|
+
if (!installed) return null;
|
|
73010
|
+
const latest = await publishedKitVersion(pkg2);
|
|
73011
|
+
if (!latest || !isNewerVersion(latest, installed)) return null;
|
|
73012
|
+
const behindMajor = (Number.parseInt(latest, 10) || 0) > (Number.parseInt(installed, 10) || 0);
|
|
73013
|
+
return { pkg: pkg2, installed, latest, behindMajor };
|
|
73014
|
+
})
|
|
73015
|
+
);
|
|
73016
|
+
const findings = checks.filter((c) => c !== null);
|
|
73017
|
+
if (findings.length === 0) return;
|
|
73018
|
+
const lines = findings.map(
|
|
73019
|
+
(f) => ` \u2022 ${f.pkg} ${f.installed} installed \xB7 ${f.latest} published` + (f.behindMajor ? " \u2014 a MAJOR behind" : "")
|
|
73020
|
+
);
|
|
73021
|
+
const majors = findings.filter((f) => f.behindMajor);
|
|
73022
|
+
if (majors.length > 0) {
|
|
73023
|
+
const install = `npm install ${majors.map((f) => `${f.pkg}@latest`).join(" ")}`;
|
|
73024
|
+
const migration = majors.some((f) => f.pkg === "@lotics/ui") ? `
|
|
73025
|
+
|
|
73026
|
+
Breaking changes are listed newest-first in
|
|
73027
|
+
node_modules/@lotics/ui/MIGRATION.md.` : "";
|
|
73028
|
+
console.error(
|
|
73029
|
+
`
|
|
73030
|
+
\u26A0 This app builds against a kit a major release behind:
|
|
73031
|
+
` + lines.join("\n") + migration + `
|
|
73032
|
+
|
|
73033
|
+
\`${install}\``
|
|
73034
|
+
);
|
|
73035
|
+
return;
|
|
73036
|
+
}
|
|
73037
|
+
console.error(
|
|
73038
|
+
`
|
|
73039
|
+
A newer kit is published \u2014 \`npm update ${findings.map((f) => f.pkg).join(" ")}\`:
|
|
73040
|
+
` + lines.join("\n")
|
|
73041
|
+
);
|
|
73042
|
+
}
|
|
72927
73043
|
function ensureAppVitestSetup(projectDir) {
|
|
72928
73044
|
const setupPath = path10.join(projectDir, VITEST_SETUP_FILENAME);
|
|
72929
73045
|
if (!fs8.existsSync(setupPath)) {
|
|
@@ -73343,6 +73459,7 @@ async function appDeploy(client, args) {
|
|
|
73343
73459
|
const called = calledAppAliases(sourceText);
|
|
73344
73460
|
warnIfDynamicAliases(called);
|
|
73345
73461
|
warnIfUndeclaredCapabilities(sourceText, meta3.capabilities);
|
|
73462
|
+
await warnIfKitBehind(projectDir);
|
|
73346
73463
|
warnIfProjectFootguns(projectDir, sourceText);
|
|
73347
73464
|
writeAppDts(projectDir, { workflows: meta3.workflows, queries: meta3.queries, agents: meta3.agents });
|
|
73348
73465
|
try {
|
|
@@ -73599,6 +73716,7 @@ async function appCheck(client, args = {}) {
|
|
|
73599
73716
|
live: app
|
|
73600
73717
|
});
|
|
73601
73718
|
reportPending(pending);
|
|
73719
|
+
await warnIfKitBehind(projectDir);
|
|
73602
73720
|
warnIfDynamicAliases(called);
|
|
73603
73721
|
warnIfUndeclaredCapabilities(sourceText, meta3.capabilities);
|
|
73604
73722
|
warnIfUnboundAliases(app, called);
|
|
@@ -73949,12 +74067,6 @@ function defineBlockKeys(config2) {
|
|
|
73949
74067
|
return keys3;
|
|
73950
74068
|
}
|
|
73951
74069
|
var REALTIME_SDK_FLOOR = "0.80.0";
|
|
73952
|
-
function installedAppSdkVersion(projectDir) {
|
|
73953
|
-
const manifest = path10.join(projectDir, "node_modules", "@lotics", "app-sdk", "package.json");
|
|
73954
|
-
if (!fs8.existsSync(manifest)) return null;
|
|
73955
|
-
const version2 = JSON.parse(fs8.readFileSync(manifest, "utf-8")).version;
|
|
73956
|
-
return typeof version2 === "string" ? version2 : null;
|
|
73957
|
-
}
|
|
73958
74070
|
function isVersionBelow(version2, floor2) {
|
|
73959
74071
|
const parts = (value2) => value2.split(".").map((part) => parseInt(part, 10) || 0);
|
|
73960
74072
|
const [major, minor, patch] = parts(version2);
|
|
@@ -73965,7 +74077,7 @@ function isVersionBelow(version2, floor2) {
|
|
|
73965
74077
|
}
|
|
73966
74078
|
function warnIfProjectFootguns(projectDir, sourceText) {
|
|
73967
74079
|
const lines = [];
|
|
73968
|
-
const sdkVersion =
|
|
74080
|
+
const sdkVersion = installedKitVersion(projectDir, "@lotics/app-sdk");
|
|
73969
74081
|
if (sdkVersion !== null && isVersionBelow(sdkVersion, REALTIME_SDK_FLOOR)) {
|
|
73970
74082
|
lines.push(
|
|
73971
74083
|
[
|
package/docs/cli_reference.md
CHANGED
|
@@ -45,7 +45,7 @@ Per-command syntax, flags, contracts, and gotchas for the public `lotics` CLI. S
|
|
|
45
45
|
| `lotics upgrade` | Take the installed package's next version for the app in this directory (`app_id` from the local manifest — and the WORKSPACE from the same manifest, exactly as `lotics app *` does, so the command that knows which workspace it belongs to never rides the ambient profile). Announces its target on stderr before acting. **Applies the version it PREVIEWED**, not "latest" re-resolved server-side, so a release landing mid-command cannot install a contract whose diff was never checked. **Previews first and applies only a CLEAN upgrade**: a breaking contract change, a locally modified artifact, binding drift, or bundled knowledge needing consent (all FOUR sources the preview returns) is reported per item and REFUSED with exit 1, because each resolves by choosing what to keep and a guess discards work nobody asked to lose. Already-current is a no-op that says so. On success it names the new version and the changelog, and points at `lotics app pull` to bring the checkout in step. The resolutions flow for a conflicted upgrade stays in `opctl` — that case needs a person, and the person is an operator. |
|
|
46
46
|
| `lotics docs` \| `lotics docs <area>` | The index of the reference docs, **resolved out of the packages installed beside this project** — never carried by this CLI. **Both levels are discovered by looking**: every `@lotics/*` package carrying an `AGENTS.md` or a `docs/` in any `node_modules/@lotics` from the current directory UPWARD (nearest wins, so a hoisted root copy never shadows the one a project's own imports resolve to), and within each, every area it actually ships. Titles come from each file's own `# heading` and the version from the installed `package.json`, so a doc OR a whole package added upstream appears with no change to this CLI, and a skewed install is visible rather than reassuring. A package's index is named after the package (`lotics docs ui`), never `index`. `@lotics/app-sdk`, `@lotics/ui` and `@lotics/cli` sort first as a reading ORDER, not a filter. Both the index and `<area>` print to **stdout** — the index is the payload of a bare `lotics docs`, so `lotics docs | grep -i excel` works — with only the provenance line on stderr, so `lotics docs ai > ai.md` is the doc alone; a name two packages share is refused with both qualified forms (`lotics docs ui/templates`) rather than resolved silently. Needs no auth. Outside a project only `@lotics/cli`'s own resolve, and it says so. |
|
|
47
47
|
| `lotics report '<json>'` \| `lotics report @report.json` | File a report with the Lotics team about what got in your way. **Covers the classes telemetry structurally cannot see**: a capability that does not exist (no command ran, so nothing was recorded), a command that exited 0 having done the wrong thing, an error whose message did not name the remedy, and anything that made authoring slower than it should be. **A frame, not a paragraph** — `{goal, actual, expected?, tried?, wanted?}`, `goal` and `actual` required, unknown keys dropped rather than refused. **No severity or category.** Ingest is inline JSON, `@file`, or `-` for stdin. A bare sentence is refused with the frame printed beside it, so the fix is one step; a bare invocation prints the frame BEFORE asking for a credential, since someone whose key will not resolve is exactly who has something to report. **Not spooled**: unlike telemetry it posts inline, prints whether it landed, and exits non-zero if it did not, echoing the report back so a failed send never loses it. Runs regardless of `LOTICS_TELEMETRY` — invoking it IS the consent that passive collection needs an opt-in for — but with telemetry off there are no recorded commands to attach, and it says so rather than implying context it does not have. Requires auth. Never paste records, file contents, or credentials. |
|
|
48
|
-
| `lotics app check` | Every pre-flight `deploy` runs, WITHOUT building or shipping. **First, whether this project is even based on the served version** — the one thing a deploy REFUSES outright rather than pushing (the server 409s a stale `prev_version_id`), and the one finding that invalidates every other: a stale tree and the live app are two different apps, so comparing them reports nothing trustworthy. Stale exits 1 naming both versions and stops before the rest; a project with no stamp at all — or an app with no version yet — is a first deploy, not a conflict. `deploy` runs the SAME assertion off the app row it already fetched, so a stale tree fails before it pushes a binding or builds, instead of after the upload arrives and the server 409s. Then: 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, a missing app `description` (it heads the capability catalog the chat agent reads every turn, and its absence has no other symptom), a `vite.config.ts` that never defines `global`/`__DEV__`, a `window.open` in the app's own source, and an INSTALLED `@lotics/app-sdk` below the version that understands the host's realtime push — read from `node_modules`, not the dependency range, because a caret is minor-locked below 1.0 so `^0.79.x` can never resolve `0.80` and `npm update` does nothing (all three fail ONLY in the deployed app — dev bundles with esbuild and production with rollup, so typecheck, lint, build and `app dev` are all green while react-native-web reads `global.cancelAnimationFrame` as a free variable and the sandboxed iframe drops a popup silently), an agent holding `run_app_query`/`run_app_workflow` with an EMPTY `query_aliases`/`workflow_aliases` (the tool is the capability, the alias list is the reach — empty means every call it makes is refused while the run still COMPLETES, so it surfaces as a model ignoring its prompt; read off the live row, never the manifest, which mirrors those fields but is pushed by no verb), and a notice for any alias the source computes at runtime (invisible to every check here and to `--prune`'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. |
|
|
48
|
+
| `lotics app check` | Every pre-flight `deploy` runs, WITHOUT building or shipping. **First, whether this project is even based on the served version** — the one thing a deploy REFUSES outright rather than pushing (the server 409s a stale `prev_version_id`), and the one finding that invalidates every other: a stale tree and the live app are two different apps, so comparing them reports nothing trustworthy. Stale exits 1 naming both versions and stops before the rest; a project with no stamp at all — or an app with no version yet — is a first deploy, not a conflict. `deploy` runs the SAME assertion off the app row it already fetched, so a stale tree fails before it pushes a binding or builds, instead of after the upload arrives and the server 409s. Then: 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, a missing app `description` (it heads the capability catalog the chat agent reads every turn, and its absence has no other symptom), a `vite.config.ts` that never defines `global`/`__DEV__`, a `window.open` in the app's own source, and an INSTALLED `@lotics/app-sdk` below the version that understands the host's realtime push — read from `node_modules`, not the dependency range, because a caret is minor-locked below 1.0 so `^0.79.x` can never resolve `0.80` and `npm update` does nothing (all three fail ONLY in the deployed app — dev bundles with esbuild and production with rollup, so typecheck, lint, build and `app dev` are all green while react-native-web reads `global.cancelAnimationFrame` as a free variable and the sandboxed iframe drops a popup silently), an agent holding `run_app_query`/`run_app_workflow` with an EMPTY `query_aliases`/`workflow_aliases` (the tool is the capability, the alias list is the reach — empty means every call it makes is refused while the run still COMPLETES, so it surfaces as a model ignoring its prompt; read off the live row, never the manifest, which mirrors those fields but is pushed by no verb), and a notice for any alias the source computes at runtime (invisible to every check here and to `--prune`'s unbind guard). **And whether the kit this app builds against has fallen behind what is published** — `@lotics/ui` and `@lotics/app-sdk`, read from `node_modules` for the same reason as the floor check above: a range keeps accepting, so an app pinned `^44.x` reads healthy for a year, and even an in-range one sits on the lockfile's older patch until `npm update` (never `npm install`, which honours the lock). A MAJOR behind is loud and names the packages actually behind — plus `@lotics/ui`'s `MIGRATION.md`, when ui is one of them, since it is the only half that keeps one; anything smaller is one quiet line, because a warning that fires on every deploy is one the reader stops seeing. The registry lookup is bounded and every failure — offline, slow, private — is silence: a version check must never become a new way for a deploy to fail. 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. |
|
|
49
49
|
| `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. |
|
|
50
50
|
| `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. |
|
|
51
51
|
| `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 pushes a DRIFTED declaration through this same verb before it ships (see `app deploy`), so this is the explicit single-alias path, not the only way a query reaches the app. 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. **The declaration's fields MERGE**, so the manifest is not a snapshot: deleting `params` from an alias and pushing leaves the live params exactly where they were, because an absent key means "unchanged". Clear one with `params: null`, or replace the map with the set you want. |
|
package/docs/knowledge_docs.md
CHANGED
|
@@ -98,7 +98,10 @@ knowing. As an expression, `C/O (Form E)` searches for `C/O Form E` — not in t
|
|
|
98
98
|
returns a confident zero; `[CŨ]` becomes a one-character class and matches every `C` in the
|
|
99
99
|
corpus. Both look like ordinary answers. A literal search has no such failure: the worst case
|
|
100
100
|
is an expression sent without the flag, which finds nothing and says so, naming the search that
|
|
101
|
-
ran and what to pass instead.
|
|
101
|
+
ran and what to pass instead. That last part is **measured, not guessed** — an empty result tells
|
|
102
|
+
you whether reading the pattern as an expression would have matched, so an alternation sent without
|
|
103
|
+
the flag (`phrase A|phrase B`) comes back naming `regex: true` rather than advising you to shorten a
|
|
104
|
+
pattern that was never too broad.
|
|
102
105
|
|
|
103
106
|
### Matching options
|
|
104
107
|
|