@lotics/cli 0.133.0 → 0.135.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
@@ -45622,8 +45622,6 @@ import { randomUUID } from "node:crypto";
45622
45622
  import { tmpdir } from "node:os";
45623
45623
 
45624
45624
  // src/starter_template.ts
45625
- var STARTER_FALLBACK_UI_VERSION = "31.0.0";
45626
- var STARTER_FALLBACK_SDK_VERSION = "0.75.1";
45627
45625
  var STARTER_REACT_NATIVE_VERSION = "0.85.3";
45628
45626
  var VITEST_SETUP_FILENAME = "vitest.setup.ts";
45629
45627
  var VITEST_SETUP_CONTENT = `import { vi } from "vitest";
@@ -45658,8 +45656,8 @@ vi.mock("@lotics/app-sdk", async (importOriginal) => {
45658
45656
  });
45659
45657
  `;
45660
45658
  function buildStarterTemplate(args) {
45661
- const uiVersion = args.ui_version ?? `^${STARTER_FALLBACK_UI_VERSION}`;
45662
- const sdkVersion = args.sdk_version ?? `^${STARTER_FALLBACK_SDK_VERSION}`;
45659
+ const uiVersion = args.ui_version;
45660
+ const sdkVersion = args.sdk_version;
45663
45661
  const sanitizedPkgName = args.app_name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64) || "lotics-app";
45664
45662
  return [
45665
45663
  {
@@ -65482,6 +65480,11 @@ var cForStepSchema = zod_default.lazy(
65482
65480
  );
65483
65481
  var workflowStepsSchema = zod_default.array(workflowStepSchema).min(1);
65484
65482
 
65483
+ // ../shared/src/zod_issues.ts
65484
+ function formatZodIssues(issues) {
65485
+ return issues.map((i2) => `${i2.path.join(".") || "<root>"}: ${i2.message}`).join("; ");
65486
+ }
65487
+
65485
65488
  // ../shared/src/expression_array.ts
65486
65489
  var expression_array_exports = {};
65487
65490
  __export(expression_array_exports, {
@@ -70007,8 +70010,10 @@ function walkAgentInvocation(call, scope, stepId, description) {
70007
70010
  if (!outputNode) fail(arg, "agent requires `output`.");
70008
70011
  const outputResult = agentOutputSpecSchema.safeParse(walkStaticJsonLiteral(outputNode));
70009
70012
  if (!outputResult.success) {
70010
- const detail = outputResult.error.issues.map((i2) => `${i2.path.join(".") || "<root>"}: ${i2.message}`).join("; ");
70011
- fail(outputNode, `Invalid agent \`output\`: ${detail}`);
70013
+ fail(
70014
+ outputNode,
70015
+ `Invalid agent \`output\`: ${formatZodIssues(outputResult.error.issues)}`
70016
+ );
70012
70017
  }
70013
70018
  const modelNode = kw.get("model");
70014
70019
  let model_tier;
@@ -70438,6 +70443,15 @@ async function fetchLatestNpmVersion(packageName, { timeoutMs = 1500 } = {}) {
70438
70443
  return null;
70439
70444
  }
70440
70445
  }
70446
+ async function requireLatestNpmVersion(packageName, { timeoutMs = 1e4 } = {}) {
70447
+ const version2 = await fetchLatestNpmVersion(packageName, { timeoutMs });
70448
+ if (version2 === null) {
70449
+ throw new Error(
70450
+ `Could not resolve the latest published ${packageName} from registry.npmjs.org. Refusing to fall back to the static starter pin, which may name a version this repo has not published yet.`
70451
+ );
70452
+ }
70453
+ return version2;
70454
+ }
70441
70455
 
70442
70456
  // src/app_commands.ts
70443
70457
  function orderedLike(value2, template) {
@@ -71174,18 +71188,18 @@ async function appCreate(client, args) {
71174
71188
  }
71175
71189
  }
71176
71190
  fs6.mkdirSync(targetPath, { recursive: true });
71177
- const app = await client.createApp({ name: args.name });
71178
- console.error(`Created app: ${app.name} (${app.id})`);
71179
71191
  const [uiLatest, sdkLatest] = await Promise.all([
71180
- fetchLatestNpmVersion("@lotics/ui"),
71181
- fetchLatestNpmVersion("@lotics/app-sdk")
71192
+ requireLatestNpmVersion("@lotics/ui"),
71193
+ requireLatestNpmVersion("@lotics/app-sdk")
71182
71194
  ]);
71195
+ const app = await client.createApp({ name: args.name });
71196
+ console.error(`Created app: ${app.name} (${app.id})`);
71183
71197
  const files = buildStarterTemplate({
71184
71198
  app_name: args.name,
71185
71199
  app_id: app.id,
71186
71200
  workspace_id: app.workspace_id,
71187
- ui_version: uiLatest ? `^${uiLatest}` : void 0,
71188
- sdk_version: sdkLatest ? `^${sdkLatest}` : void 0
71201
+ ui_version: `^${uiLatest}`,
71202
+ sdk_version: `^${sdkLatest}`
71189
71203
  });
71190
71204
  for (const file2 of files) {
71191
71205
  const fullPath = path7.join(targetPath, file2.path);
@@ -72034,23 +72048,28 @@ async function appAgentSet(client, args) {
72034
72048
  noteScope({ alias: args.alias });
72035
72049
  const app = await client.getApp(meta3.app_id);
72036
72050
  const live = app.agents?.[args.alias];
72037
- if (!live) {
72038
- console.error(
72039
- `App ${meta3.app_id} has no bound agent "${args.alias}". Bind it first (set_app_agent), then 'lotics app pull' to write its instructions.`
72040
- );
72041
- process.exit(1);
72042
- }
72051
+ const isNew = live === void 0;
72043
72052
  const declared = meta3.agents?.[args.alias];
72044
72053
  const hasDeclaration = declared?.inputs !== void 0 || declared?.outputs !== void 0;
72045
72054
  const file2 = agentFilePath2(projectDir, args.alias);
72046
- if (!fs6.existsSync(file2) && !hasDeclaration) {
72047
- console.error(
72048
- `No instructions at ${path7.relative(projectDir, file2)}, and nothing declared in package.json#lotics.agents.${args.alias} to push instead.
72055
+ const hasProse = fs6.existsSync(file2);
72056
+ if (!hasProse) {
72057
+ if (isNew) {
72058
+ console.error(
72059
+ `Agent "${args.alias}" is not bound yet, so this would create it \u2014 and an agent cannot be created without instructions.
72060
+ Write ${AGENTS_DIR}/${args.alias}.md, then re-run this.`
72061
+ );
72062
+ process.exit(1);
72063
+ }
72064
+ if (!hasDeclaration) {
72065
+ console.error(
72066
+ `No instructions at ${path7.relative(projectDir, file2)}, and nothing declared in package.json#lotics.agents.${args.alias} to push instead.
72049
72067
  Run 'lotics app pull ${meta3.app_id}' to write ${AGENTS_DIR}/${args.alias}.md, then edit it.`
72050
- );
72051
- process.exit(1);
72068
+ );
72069
+ process.exit(1);
72070
+ }
72052
72071
  }
72053
- const instructions = fs6.existsSync(file2) ? stripAgentHeader(fs6.readFileSync(file2, "utf-8")) : void 0;
72072
+ const instructions = hasProse ? stripAgentHeader(fs6.readFileSync(file2, "utf-8")) : void 0;
72054
72073
  if (instructions === "") {
72055
72074
  console.error(
72056
72075
  `${path7.relative(projectDir, file2)} is empty after stripping the header \u2014 refusing to push an empty prompt.`
@@ -72080,7 +72099,10 @@ async function appAgentSet(client, args) {
72080
72099
  ...declared?.outputs !== void 0 ? ["outputs"] : []
72081
72100
  ];
72082
72101
  console.error(
72083
- `Set agent "${args.alias}" (${pushed.join(", ")}` + (live.model_tier ? `, ${live.model_tier}` : "") + `). Everything else left as it is.`
72102
+ // "Created" is not decoration: an alias that only LOOKS like the one the
72103
+ // author meant reaches here with a matching file and writes a second agent.
72104
+ // The verb is the one line that says which of the two just happened.
72105
+ `${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.`)
72084
72106
  );
72085
72107
  }
72086
72108
  async function appWorkflowSet(client, args) {
@@ -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 the edited `src/agents/<alias>.md` instructions back through `set_app_agent` the agent mirror of `app workflow set`, and the deploy-free authoring path for an agent's PROSE. It sends the instructions and nothing else: the server merges against the stored declaration, so every typed field keeps exactly what is bound. This is deliberate and it is the opposite of what the symmetry with `app workflow set` suggests **the manifest is a snapshot from the last `app pull`, so replaying its typed half would silently revert whatever was bound since** (the chat authoring agent adding `knowledge_doc_ids`, another operator granting `query_aliases`), and the CLI would print success while the agent quietly lost its knowledge and its read surface. To change a typed field, call `set_app_agent` with just that field (`lotics run set_app_agent '{"app_id":…,"alias":…,"outputs":{…}}'` — it merges), then `app pull` to bring the manifest back in step. Editing `package.json#lotics.agents` by hand pushes nothing, and since that block is what types `useAgentRun`, `codegen` warns and `deploy` REFUSES while it disagrees with the live app. Clear error + non-zero exit on a missing file, an alias absent from the manifest, or a file that is empty once the header is stripped (refusing to push an empty prompt). `app pull` writes the file; edit, then `set`. |
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. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.133.0",
3
+ "version": "0.135.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {