@lotics/cli 0.174.0 → 0.176.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 CHANGED
@@ -45696,7 +45696,7 @@ function resultSideEffects(result) {
45696
45696
  }
45697
45697
 
45698
45698
  // src/version.ts
45699
- var VERSION = "0.174.0";
45699
+ var VERSION = "0.176.0";
45700
45700
 
45701
45701
  // src/timezone.ts
45702
45702
  function machineTimezone() {
@@ -45708,6 +45708,9 @@ function machineTimezone() {
45708
45708
  }
45709
45709
  }
45710
45710
 
45711
+ // src/cli.ts
45712
+ import { spawn as spawn4 } from "node:child_process";
45713
+
45711
45714
  // src/docs_command.ts
45712
45715
  import fs5 from "node:fs";
45713
45716
  import path5 from "node:path";
@@ -45919,6 +45922,12 @@ var COMMANDS = [
45919
45922
  " these rows ship to everyone who copies the starter"
45920
45923
  ]
45921
45924
  },
45925
+ {
45926
+ verbs: ["upgrade"],
45927
+ help: [
45928
+ " lotics upgrade Update this CLI to the latest version"
45929
+ ]
45930
+ },
45922
45931
  {
45923
45932
  verbs: ["docs"],
45924
45933
  help: [
@@ -72466,6 +72475,8 @@ async function hydrateAppProject(client, targetPath, app, opts) {
72466
72475
  }
72467
72476
  reportUnseenLive("prompt", unseenLiveProse, app.id);
72468
72477
  }
72478
+ }
72479
+ async function prepareProjectToolchain(targetPath) {
72469
72480
  await runNpmInstall(targetPath);
72470
72481
  writeAppDtsKitLinks(targetPath);
72471
72482
  }
@@ -72512,6 +72523,7 @@ async function appPull(client, args) {
72512
72523
  ...args.force !== void 0 ? { force: args.force } : {},
72513
72524
  kept: keptLocal
72514
72525
  });
72526
+ await prepareProjectToolchain(targetPath);
72515
72527
  reportRestoredFiles(restoredFromArchive, prior !== null);
72516
72528
  reportKeptLocalFiles(keptLocal, app.id, {
72517
72529
  // `prior` is non-null whenever the stamp was held — `stampAfterPull` cannot
@@ -74121,6 +74133,7 @@ Building ${starter.name} \u2014 this runs its build on your machine (its package
74121
74133
  if (serverDeployed !== null) {
74122
74134
  note(`Deployed v${serverDeployed.version_number} \u2014 built on Lotics, no Node needed here.`);
74123
74135
  } else {
74136
+ await prepareProjectToolchain(targetPath);
74124
74137
  await appDeploy(client, {
74125
74138
  projectDir: targetPath,
74126
74139
  message: `Copied from starter ${starter.name}`
@@ -103335,6 +103348,31 @@ function alreadySetUp(email3) {
103335
103348
  if (config2?.email !== email3) return false;
103336
103349
  return Object.keys(config2.profiles ?? {}).length > 0;
103337
103350
  }
103351
+ async function runUpgrade() {
103352
+ const cmd = updateCommand();
103353
+ console.error(`Upgrading ${VERSION} \u2192 latest
103354
+ ${cmd}
103355
+ `);
103356
+ const proc = spawn4(cmd, {
103357
+ // The command is one of three literals this binary chooses between, never
103358
+ // anything a caller supplied — and each is a pipeline that needs a shell.
103359
+ shell: true,
103360
+ stdio: "inherit",
103361
+ env: process.env
103362
+ });
103363
+ const code = await new Promise((resolve2, reject2) => {
103364
+ proc.on("error", reject2);
103365
+ proc.on("exit", (exitCode) => resolve2(exitCode ?? 1));
103366
+ });
103367
+ if (code !== 0) {
103368
+ console.error(
103369
+ `
103370
+ Upgrade failed (exit ${code}). Run it yourself to see why:
103371
+ ${cmd}`
103372
+ );
103373
+ process.exit(1);
103374
+ }
103375
+ }
103338
103376
  async function handleSignup(positionalEmail, flags) {
103339
103377
  const email3 = positionalEmail ?? (process.stdin.isTTY ? await prompt("Email: ") : "");
103340
103378
  if (!email3) {
@@ -103683,6 +103721,10 @@ async function main() {
103683
103721
  docsCommand({ area: subcommand });
103684
103722
  return;
103685
103723
  }
103724
+ if (command === "upgrade") {
103725
+ await runUpgrade();
103726
+ return;
103727
+ }
103686
103728
  if (command === "report") {
103687
103729
  let input = subcommand ?? "";
103688
103730
  if (input.startsWith("@")) {
@@ -46,6 +46,7 @@ Per-command syntax, flags, contracts, and gotchas for the public `lotics` CLI. S
46
46
  | `lotics starter show <starter_id>` | One starter's name, description, current version and trust standing (`official` — reviewed by Lotics; `your organization's own`). Read it before copying a starter you did not publish. |
47
47
  | `lotics starter init <starter_id> [path]` | **Copy a starter into this workspace.** Server-side it scaffolds the tables and fields, creates the document templates and knowledge docs, inserts the sample records, creates a BESPOKE app and materializes its queries, workflows and agents onto it. Then it stops: a starter ships **source only**, with no prebuilt bundle, so the app source is downloaded here, hydrated against the live app (the same steps `app pull` runs), built, and deployed — which is why this needs node and a few minutes, and why the app is not servable until the deploy lands. **What you get is yours outright**: an ordinary app plus ordinary tables, with no link back to the starter, nothing pinned, and nothing to upgrade. Edit any of it. **It builds the publisher's code on your machine** — the copy has to build where your workspace's field ids are, so `npm run build` runs their build script and vite loads their `vite.config.ts` in Node, as you. Dependencies install with `npm ci --ignore-scripts` — the lockfile's exact tree (so a starter published months ago resolves the same packages today), and no lifecycle scripts (the path that fires before you run anything). Neither changes the build itself. That is why provenance is the gate: **copyable only if the starter is Lotics-reviewed or your own organization published it** — copying runs the author's code (its bundle, its workflows, its agents) under YOUR authority, so provenance is the gate, enforced server-side. **Refuses a workspace that already has tables** unless `--adopt`: scaffold matches an entity by DISPLAY NAME, so a starter declaring `Contacts` would bind to yours. `--json` prints one object on stdout instead of progress (the shape is under `lotics start`) and captures npm/vite output unless the build fails. `--no-sample-data` skips the sample records; with them, the created record ids are reported — they are ordinary records, delete them whenever. Resolves and ANNOUNCES its workspace first (`lotics → <org> / <workspace>` on stderr). Admin-only. Authoring the registry (`starter publish/release/unpublish`) stays operator-only. |
48
48
  | `lotics starter fixtures capture [--entity <alias> ...] [--limit <n>]` | **(authoring)** Write this app's live records into the project as `fixtures/<entity-alias>.json` — the sample data a starter carries, so a copy lands with something in it. Run from an app project; the app id comes from its manifest. The alias-keyed shape is produced server-side, because the aliases are minted when the starter is extracted and exist nowhere a project can read them. `--entity` is repeatable and comma-separated; omitted, every table the app declares is captured. **Capture a linked set in ONE call** — a link between two rows only resolves within a single capture, so taking companies and contacts separately drops the edge between them (it says so when it happens). `--limit` bounds rows per table (default 10, max 200). **READ WHAT IT WROTE before committing**: these rows are created verbatim in every workspace that copies the starter, so a real customer name, price or address captured here is published. Files, formulas, rollups, lookups and autonumbers are never captured — the platform writes those. Admin-only; writes nothing to the workspace. |
49
+ | `lotics upgrade` | Update this CLI in place. Runs the same installer a person would, chosen by how THIS copy arrived: an npm install upgrades through npm, a script install re-runs the script — the runtime knows which (the executable is compiled, the npm bin runs under node), so nobody has to. It downloads nothing itself; resolving a version, verifying the checksum and replacing a running executable already exist in the installers, and a second copy of that inside the binary would be a second thing to get right. Replacing the binary while it runs is safe — a rename leaves the running image mapped on unix, and on Windows the installer moves the old aside precisely because the file is in use. Already current is a no-op that says so. Needs no auth. |
49
50
  | `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. |
50
51
  | `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. |
51
52
  | `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. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.174.0",
3
+ "version": "0.176.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {