@lotics/cli 0.86.1 → 0.87.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 CHANGED
@@ -195,84 +195,6 @@ 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
- ## Packages
199
-
200
- A maintained, versioned package library: author once, install into many workspaces, upgrade per-workspace. A package carries an app, content (knowledge docs and/or document templates), or both. **Nothing starts as a package** — build a bespoke app first (the `lotics app` loop above), then first-release it as a package when it proves worth distributing. Any org authors its own packages (publishing is owner-org-only); Lotics-backed packages carry the `official` badge. See `docs/packages.md` for the model.
201
-
202
- ```bash
203
- # 1. Make the app distributable — first-release it as a package (one call: extract
204
- # an alias-keyed contract, create the registry package, publish v1 from the
205
- # deployed source + dist, pin the app as installation #1). Blocking findings
206
- # (a hardcoded fld_/opt_ id in src/ or a workflow body, a button field) → 409.
207
- lotics app publish # from the pulled app project (or: app publish app_...)
208
- lotics app publish --rename item=deal -m "v1" # fix an auto-minted alias before v1 freezes it
209
-
210
- # 2. Iterate IN PLACE — the origin app is the permanent working copy
211
- lotics app pull app_... # the same loop as any app: pull, edit, dev
212
- lotics app deploy -m "Add quote drawer" # commit a new app version
213
-
214
- # 3. Release the next package version from the origin you are happy with
215
- lotics app release -m "v2: adds deals" # preview (new/changed aliases + findings)
216
- lotics app release -m "v2: adds deals" --yes # publish v2 + re-pin the origin
217
-
218
- # Take it off the shelf (existing installations keep working + upgrading)
219
- lotics app unpublish app_... [--undo] # by app id (resolves the package) or package id
220
-
221
- # Operate installations (workspace admin). The high-traffic verbs are top-level
222
- # (install / uninstall / upgrade); low-traffic ops live on `lotics package`.
223
- lotics package show apg_... # registry metadata + version history (trust badge, yank)
224
- lotics install apg_... --version 2 # scaffold + materialize + deploy + pin
225
- lotics install apg_... --config heading="Ops board" # per-knob overrides over contract defaults
226
- lotics package config app_... --set show_done=false # edit a live installation's config (partial merge)
227
- lotics uninstall app_... [--archive-tables] # remove an installation; workflows stop firing
228
- lotics package doctor app_... # version pin vs latest + drift + local edits (exit 1 on findings)
229
- lotics upgrade app_... # preview, then apply (additive; overlay preserved)
230
- lotics upgrade apg_... # APP package → FLEET: upgrade EVERY org installation (clean ones apply; findings skip, exit 1)
231
- lotics upgrade app_... --resolve fields.deal.stage=recreate # resolve reported drift
232
- lotics upgrade app_... --resolve queries.tasks=keep # consent for a local edit (or =revert)
233
- lotics upgrade app_... --resolve template.quote=revert # a locally-edited template (clean ones auto-update; or =keep)
234
- lotics upgrade app_... --resolve knowledge.playbook=apply # a bundled knowledge doc: apply|keep|archive|recreate|unbind
235
- lotics upgrade app_... --resolve roles.approver=grp_... # re-point a LIVE package role at another group
236
- lotics package yank apg_... 3 [--undo] # refuse new installs/upgrades of a broken version
237
- lotics package eject app_... # one-way: sever the package link
238
- lotics workspace doctor # dangling schema references across the workspace (exit 1 on findings)
239
- ```
240
-
241
- After the first publish **the origin is the master** — develop it in its workspace (`app pull` / `app deploy` / `app workflow set` / table tools), and `lotics app release` snapshots each version. Its **binding-aware re-extract** keeps aliases stable through the origin's current binding, so re-extraction never churns the upgrade diff (which is why extract-again is the loop, not a hazard). Release is also the **bundled-knowledge declaration surface**: run from the app project, the manifest's `lotics.knowledge` (`[{alias,doc_id}]`) re-declares which docs the package bundles — an alias added ships as a new doc, a bound alias dropped surfaces to consumers as `removed` (archive|keep), a present one re-snapshots; a bare id from elsewhere reconstructs the current corpus from the pin (never drops it). So a published app package evolves its bundled docs across versions without ejecting.
242
-
243
- ### Content packages
244
-
245
- A **content** package ships **knowledge docs and/or document templates** — no app, just the artifacts. Knowledge is the reference corpus an agent looks up by name; templates are standalone document templates. Content comes two ways: bundled *with an app* (installing the app delivers its agents' docs) or as a standalone content package (a versioned corpus). Install delivers, upgrade refreshes — preserving stable `kdc_`/`tmpl_` ids + a version snapshot per change. See `docs/packages.md` § Content.
246
-
247
- ```bash
248
- # Publish a content package from a SET of live knowledge docs + templates — the
249
- # publish_content tool (there is no content-package project). Aliases + shas are
250
- # computed from live content; v1 is created and the origin workspace is pinned.
251
- lotics run publish_content '{"name":"My SOPs","knowledge_doc_ids":["kdc_..."],"template_ids":["tmpl_..."]}'
252
-
253
- # Cut the next version after editing those docs/templates in place (add "dry_run":true to preview the diff)
254
- lotics run release_content '{"package_id":"apg_...","changelog":"v2: revised SOPs"}'
255
-
256
- # Install into a workspace (a same-named local DOC collides — consent with --bind-to; templates never collide)
257
- lotics install apg_... # app OR content, shape-branched
258
- lotics install apg_... --bind-to policy=kdc_... # adopt an existing doc as package-managed
259
-
260
- # Discover installed content (each row leads with the PACKAGE id)
261
- lotics package list-content # package id · package · version (standalone content installs)
262
-
263
- # Refresh this workspace's content install — addressed by PACKAGE id (one install per
264
- # package per workspace, so the package id is the address; one gate over docs + templates)
265
- lotics upgrade apg_... # CONTENT package → preview, then apply (this workspace's install)
266
- lotics upgrade apg_... --resolve knowledge.policy=apply # a doc: apply|keep|archive|recreate|unbind
267
- lotics upgrade apg_... --resolve template.quote=revert # a locally-edited template: revert|keep (clean ones auto-update)
268
- lotics upgrade apg_... --apply-all # accept upstream for every doc + template (OVERWRITES local edits)
269
- lotics uninstall apg_... --keep-content # remove the pin; keep docs + templates (default archives them)
270
- ```
271
-
272
- A package's knowledge can also arrive **bundled with an app**. Upgrading such an app (`lotics upgrade <app_id>`) previews its bundled-knowledge changes alongside the plan and resolves them in the SAME namespaced grammar — `--resolve knowledge.<alias>=apply|keep|archive|recreate|unbind` (the flags pass to the server verbatim; no routing, no ambiguity), `--bind-to` consents an added-doc collision, `--apply-all` takes the package's version for every consent-requiring doc.
273
-
274
- To package the knowledge an existing app's agents route to, declare it in the app project's `package.json#lotics.knowledge` (`[{ alias, doc_id }]`, + `lotics.knowledge_expects` for docs the agents assume but the package doesn't own), then `lotics app publish` from the app project — it forwards the declaration, and the docs ship with the package.
275
-
276
198
  ## SDK
277
199
 
278
200
  ```typescript
package/dist/args.d.ts CHANGED
@@ -36,45 +36,6 @@ export declare function parseArgs(argv: string[]): {
36
36
  printCreated: boolean;
37
37
  /** `--cleanup`: also delete the harvested created records (records only). */
38
38
  cleanup: boolean;
39
- /**
40
- * `--version <n>`: a package version number (`lotics install … --version 2`).
41
- * Distinct from the boolean `version` flag below: `--version` followed by an
42
- * integer captures the number; a bare `--version` (or `-v`) prints the CLI
43
- * version. No other command takes a numeric value after `--version`, so the
44
- * peek is unambiguous.
45
- */
46
- packageVersion?: string;
47
- /**
48
- * `--resolve <key>=recreate|revert|keep|<id>` (repeatable): upgrade
49
- * resolutions — drift entries (`<namespace.alias>`) take `recreate` or an
50
- * existing id; modified-core entries (`<kind>.<alias>`) take `revert` or
51
- * `keep`. For a standalone KNOWLEDGE upgrade, `<alias>=apply|keep|archive|
52
- * recreate|unbind`.
53
- */
54
- resolve: string[];
55
- /**
56
- * `--bind-to <alias>=<kdc_id>` (repeatable): consent to adopt an existing
57
- * same-named doc as package-managed — resolves a knowledge name collision at
58
- * install / a created-doc collision at upgrade.
59
- */
60
- bindTo: string[];
61
- /** `--keep-content` (`package uninstall` of a content install): retain the package-bound docs instead of archiving them. */
62
- keepContent: boolean;
63
- /** `--apply-all` (knowledge `package upgrade`): accept the package's version for every consent entry. */
64
- applyAll: boolean;
65
- /**
66
- * `--rename <old>=<new>` (repeatable, `lotics app publish`): fix an auto-minted
67
- * alias before the package's v1 contract freezes it.
68
- */
69
- rename: string[];
70
- /** `--config key=value` (repeatable): per-knob config overrides at `package install`. */
71
- config: string[];
72
- /** `--set key=value` (repeatable): per-knob config edits at `package config`. */
73
- set: string[];
74
- /** `--archive-tables` (`package uninstall` of an app install): also archive the scaffolded tables. */
75
- archiveTables: boolean;
76
- /** `--undo`: reverse a `package retire` / `package yank`. */
77
- undo: boolean;
78
39
  version: boolean;
79
40
  help: boolean;
80
41
  };
package/dist/args.js CHANGED
@@ -28,16 +28,6 @@ export function parseArgs(argv) {
28
28
  yes: false,
29
29
  printCreated: false,
30
30
  cleanup: false,
31
- packageVersion: undefined,
32
- resolve: [],
33
- bindTo: [],
34
- keepContent: false,
35
- applyAll: false,
36
- rename: [],
37
- config: [],
38
- set: [],
39
- archiveTables: false,
40
- undo: false,
41
31
  version: false,
42
32
  help: false,
43
33
  };
@@ -102,81 +92,10 @@ export function parseArgs(argv) {
102
92
  case "--cleanup":
103
93
  flags.cleanup = true;
104
94
  break;
105
- case "--resolve": {
106
- const value = argv[++i];
107
- if (value === undefined || value.startsWith("-")) {
108
- throw new Error("--resolve requires a value: <key>=recreate|revert|keep or <key>=<existing_id> (drift: <namespace.alias>; modified core: <kind>.<alias>).");
109
- }
110
- flags.resolve.push(value);
111
- break;
112
- }
113
- case "--bind-to": {
114
- const value = argv[++i];
115
- if (value === undefined || value.startsWith("-")) {
116
- throw new Error("--bind-to requires a value: <alias>=<kdc_id>.");
117
- }
118
- flags.bindTo.push(value);
119
- break;
120
- }
121
- case "--keep-content":
122
- flags.keepContent = true;
123
- break;
124
- case "--apply-all":
125
- flags.applyAll = true;
126
- break;
127
- case "--rename": {
128
- const value = argv[++i];
129
- if (value === undefined || value.startsWith("-")) {
130
- throw new Error("--rename requires a value: old=new (an alias to rename before v1 freezes it).");
131
- }
132
- flags.rename.push(value);
133
- break;
134
- }
135
- case "--config": {
136
- const value = argv[++i];
137
- if (value === undefined || value.startsWith("-")) {
138
- throw new Error("--config requires a value: key=value (repeatable).");
139
- }
140
- flags.config.push(value);
141
- break;
142
- }
143
- case "--set": {
144
- const value = argv[++i];
145
- if (value === undefined || value.startsWith("-")) {
146
- throw new Error("--set requires a value: key=value (repeatable).");
147
- }
148
- flags.set.push(value);
149
- break;
150
- }
151
- case "--archive-tables":
152
- flags.archiveTables = true;
153
- break;
154
- case "--undo":
155
- flags.undo = true;
156
- break;
157
95
  case "--version":
158
- case "-v": {
159
- // For a package version-taking command (`package …`, or the top-level
160
- // consumer verbs `install` / `upgrade`), `--version` carries a value
161
- // (the package version) and the value is REQUIRED — a bare or malformed
162
- // `--version` here must never fall back to the CLI-version early-exit
163
- // boolean, which would print the CLI version and exit 0 (a provisioning
164
- // script reads that as a successful install). The value is captured raw;
165
- // the consuming command validates integer-ness with a loud error.
166
- // Everywhere else, this is the CLI-version boolean.
167
- const next = argv[i + 1];
168
- if (command === "package" || command === "install" || command === "upgrade") {
169
- if (next === undefined || next.startsWith("-")) {
170
- throw new Error("--version requires a version number for install/upgrade/package commands (e.g. --version 2).");
171
- }
172
- flags.packageVersion = next;
173
- i++;
174
- }
175
- else {
176
- flags.version = true;
177
- }
96
+ case "-v":
97
+ flags.version = true;
178
98
  break;
179
- }
180
99
  case "--help":
181
100
  case "-h":
182
101
  flags.help = true;
package/dist/args.test.js CHANGED
@@ -23,53 +23,13 @@ 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 on the top-level install verb", () => {
27
- const r = parseArgs(["install", "apg_123", "--version", "2"]);
28
- expect(r.command).toBe("install");
29
- expect(r.subcommand).toBe("apg_123");
30
- expect(r.flags.packageVersion).toBe("2");
31
- expect(r.flags.version).toBe(false);
32
- expect(r.restArgs).toEqual([]);
33
- });
34
- it("captures --version <int> as packageVersion on the top-level upgrade verb", () => {
35
- const r = parseArgs(["upgrade", "app_1", "--version", "3"]);
36
- expect(r.command).toBe("upgrade");
37
- expect(r.subcommand).toBe("app_1");
38
- expect(r.flags.packageVersion).toBe("3");
39
- expect(r.flags.version).toBe(false);
40
- });
41
26
  it("treats a bare --version as the boolean CLI-version flag", () => {
42
27
  const r = parseArgs(["--version"]);
43
28
  expect(r.flags.version).toBe(true);
44
- expect(r.flags.packageVersion).toBeUndefined();
45
29
  });
46
30
  it("treats -v as the boolean CLI-version flag", () => {
47
31
  const r = parseArgs(["-v"]);
48
32
  expect(r.flags.version).toBe(true);
49
- expect(r.flags.packageVersion).toBeUndefined();
50
- });
51
- it("captures a non-integer --version value under version-taking commands for loud command-level validation", () => {
52
- const r = parseArgs(["install", "apg_123", "--version", "2.0"]);
53
- expect(r.flags.packageVersion).toBe("2.0");
54
- expect(r.flags.version).toBe(false);
55
- });
56
- it("collects repeated --resolve values", () => {
57
- const r = parseArgs([
58
- "upgrade",
59
- "app_1",
60
- "--resolve",
61
- "fields.deal.stage=recreate",
62
- "--resolve",
63
- "templates.quote=dtl_abc",
64
- ]);
65
- expect(r.flags.resolve).toEqual(["fields.deal.stage=recreate", "templates.quote=dtl_abc"]);
66
- });
67
- it("errors on --resolve without a value", () => {
68
- expect(() => parseArgs(["upgrade", "app_1", "--resolve"])).toThrow(/--resolve requires a value/);
69
- });
70
- it("errors loudly on a bare --version under version-taking commands instead of printing the CLI version", () => {
71
- expect(() => parseArgs(["install", "apg_123", "--version"])).toThrow(/requires a version number/);
72
- expect(() => parseArgs(["install", "apg_123", "--version", "--json"])).toThrow(/requires a version number/);
73
33
  });
74
34
  it("parses --workspace as a value flag", () => {
75
35
  const r = parseArgs(["run", "query_tables", "{}", "--workspace", "wsp_123"]);
@@ -111,15 +71,6 @@ describe("parseArgs", () => {
111
71
  expect(parseArgs(["app", "workflow", "run", "wf"]).flags.cleanup).toBe(false);
112
72
  expect(parseArgs(["app", "workflow", "run", "wf"]).flags.printCreated).toBe(false);
113
73
  });
114
- it("collects repeated --rename old=new flags (default empty)", () => {
115
- const r = parseArgs(["app", "publish", ".", "--rename", "item=deal", "--rename", "don_hang=orders"]);
116
- expect(r.flags.rename).toEqual(["item=deal", "don_hang=orders"]);
117
- expect(r.toolArgs).toBe(".");
118
- expect(parseArgs(["app", "publish"]).flags.rename).toEqual([]);
119
- });
120
- it("throws when --rename has no value", () => {
121
- expect(() => parseArgs(["app", "publish", "--rename"])).toThrow(/--rename requires a value/);
122
- });
123
74
  it("parses --yes and -y as the same boolean confirmation flag (default false)", () => {
124
75
  expect(parseArgs(["workspace", "delete", "wsp_1", "--yes"]).flags.yes).toBe(true);
125
76
  expect(parseArgs(["workspace", "delete", "wsp_1", "-y"]).flags.yes).toBe(true);
package/dist/cli.js CHANGED
@@ -14,7 +14,6 @@ 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, packageUninstall, packageListContent, packageConfig, packageShow, parseInstallConfigFlags, packageEject, packageDoctor, packageUpgrade, packageUpgradeByPackageId, redirectContentPciForm, parseBindToFlags, appPublish, appRelease, appUnpublish, packageYank, } from "./package_commands.js";
18
17
  import { parseArgs } from "./args.js";
19
18
  import { ingestJsonArgs } from "./inputs.js";
20
19
  import { runXlsxCommand } from "./xlsx.js";
@@ -75,18 +74,6 @@ COMMANDS
75
74
  (-m is required — it's the version's audit trail;
76
75
  carries code + queries only — workflow bindings are
77
76
  managed by set_app_workflow / remove_app_workflow)
78
- lotics app publish [app_id|.] [--rename old=new ...] [-m <changelog>] [--yes]
79
- Make the app DISTRIBUTABLE — first-release it (v1) as a
80
- package; later versions ship via "lotics app release".
81
- Previews the auto-minted aliases + findings; --yes applies
82
- (extract → create → publish v1 → pin origin). --rename fixes
83
- an alias before v1 freezes; -m is an optional v1 changelog
84
- lotics app release [app_id|.] -m <changelog> [--yes]
85
- RELEASE the next package version from the origin app you
86
- are happy with (preview; --yes publishes + re-pins)
87
- lotics app unpublish <app_id|package_id> [--undo]
88
- Take the published package off the shelf (installations
89
- keep working); --undo restores it
90
77
  lotics app versions [app_id] Show deploy history newest-first (version,
91
78
  timestamp, deployer, build status, -m message;
92
79
  * marks the currently served version)
@@ -108,42 +95,6 @@ COMMANDS
108
95
  lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
109
96
  lotics app rename "<new name>" Rename the app's display name (launcher title)
110
97
  lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
111
- # PACKAGES — consumer verbs are top-level; low-traffic ops live on "lotics package"
112
- # (author verbs live on "lotics app")
113
- lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]
114
- Install a package (app: scaffolds/deploys/materializes,
115
- --config sets its knobs; content: installs the doc corpus,
116
- --bind-to consents to adopt a same-named doc on a collision)
117
- lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content]
118
- Uninstall — dispatched by id. App (app_id): archives
119
- artifacts (+ --archive-tables also archives scaffolded
120
- tables). Content package (apg_): archives its package-bound
121
- docs unless --keep-content
122
- lotics upgrade <app_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all]
123
- Preview-then-apply an upgrade, dispatched by id. App
124
- (app_id): refuses while any drift/modified/bundled-knowledge
125
- finding lacks a --resolve. Package (apg_): a CONTENT package
126
- upgrades THIS workspace's install (--resolve
127
- <alias>=apply|keep|archive|recreate|unbind); an APP package
128
- FLEET-upgrades every installation across your org (clean
129
- apply, findings skip). --apply-all accepts the package's version
130
- lotics package doctor [app_id] Installation health: version pin vs latest, binding
131
- drift, locally modified core, knowledge drift/edits
132
- (exit 1 on findings)
133
- lotics package config <app_id> [--set key=value ...]
134
- Show or edit an installation's config knobs
135
- lotics package eject <app_id> Sever an installation's package link
136
- (re-deploys the pinned source as a bespoke app)
137
- lotics package show <package_id> Registry metadata + version history
138
- lotics package list-content List the workspace's content installations
139
- (standalone content installs) — each row leads with
140
- the package id for upgrade/uninstall
141
- lotics package yank <package_id> <version> [--undo]
142
- Refuse new installs/upgrades of a broken published
143
- version (pinned installations keep running)
144
- lotics run publish_content '{"name":"…","knowledge_doc_ids":[…],"template_ids":[…]}'
145
- Publish a SET of live docs + templates as a content
146
- package (v1 + origin pin); release_content cuts the next
147
98
  lotics ui link <component> [--ui-src <path>] [--remove]
148
99
  Dev-link @lotics/ui to packages/ui/src (Vite alias
149
100
  + tsc paths) for live HMR + typecheck. Monorepo apps
@@ -172,13 +123,6 @@ FLAGS
172
123
  is_current_member / row-scoping resolve to them (also
173
124
  LOTICS_VIEW_AS env; admin key only; writes stay yours)
174
125
  --local Pin the current directory (lotics org use / auth api-key)
175
- --rename <old>=<new> (lotics app publish, repeatable) Fix an auto-minted alias before
176
- the package's v1 contract freezes it
177
- --bind-to <a>=<id> (lotics install/upgrade, repeatable) Adopt an existing
178
- same-named doc as package-managed (resolves a knowledge collision)
179
- --keep-content (lotics uninstall) Keep the docs instead of archiving them
180
- --apply-all (lotics upgrade, knowledge) Accept the package's version
181
- for every doc (overwrites local edits)
182
126
  --all (lotics auth logout) Remove every saved credential
183
127
  --version Show version
184
128
 
@@ -680,11 +624,7 @@ async function main() {
680
624
  command !== "run" &&
681
625
  command !== "download" &&
682
626
  command !== "workspace" &&
683
- command !== "app" &&
684
- command !== "package" &&
685
- command !== "install" &&
686
- command !== "uninstall" &&
687
- command !== "upgrade") {
627
+ command !== "app") {
688
628
  console.error(`Unknown command: ${command}`);
689
629
  console.error('Run "lotics --help" for usage.');
690
630
  process.exit(1);
@@ -706,27 +646,6 @@ async function main() {
706
646
  console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
707
647
  console.error(" lotics app rename \"<new name>\" Rename the app's display name (launcher title)");
708
648
  console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
709
- console.error(" lotics app publish [app_id|.] [--rename old=new ...] [-m <changelog>] [--yes] First-release the app as a package (v1; preview, --yes applies)");
710
- console.error(" lotics app release [app_id|.] -m <changelog> [--yes] Release the next package version from the origin");
711
- console.error(" lotics app unpublish <app_id|package_id> [--undo] Take a published package off the shelf (any kind — app or content)");
712
- process.exit(1);
713
- }
714
- if (command === "package" && !subcommand) {
715
- console.error("Usage (low-traffic consumer / registry ops — author verbs live on `lotics app`):");
716
- console.error(" The high-traffic consumer verbs are top-level:");
717
- console.error(" lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
718
- console.error(" lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content] Uninstall — dispatched by id (app vs content)");
719
- console.error(" lotics upgrade <app_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Upgrade — app install / content install / whole app fleet (apg_)");
720
- console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding/knowledge drift");
721
- console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
722
- console.error(" lotics package eject <app_id> Sever an installation's package link");
723
- console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
724
- console.error(" lotics package list-content List the workspace's content installations (standalone content installs)");
725
- console.error(" lotics package yank <package_id> <version> [--undo] Refuse new installs/upgrades of a broken published version (pinned installations keep running)");
726
- console.error(" A content package (a SET of live docs + templates) — publish + release with the tools, retire on `lotics app`:");
727
- console.error(" lotics run publish_content '{\"name\":\"…\",\"knowledge_doc_ids\":[…],\"template_ids\":[…]}' Publish v1 (a fixed set)");
728
- console.error(" lotics run release_content '{\"package_id\":\"apg_…\",\"changelog\":\"…\"}' Cut the next version (add \"dry_run\":true to preview)");
729
- console.error(" lotics app unpublish <package_id> Take the whole package off the shelf (kind-agnostic — retires content too)");
730
649
  process.exit(1);
731
650
  }
732
651
  if (command === "run" && !subcommand) {
@@ -873,173 +792,6 @@ async function main() {
873
792
  }
874
793
  // Ensure workspace is resolved for all remaining commands
875
794
  await resolveWorkspace(client, ctx);
876
- // --- lotics install / uninstall / upgrade — top-level CONSUMER verbs ---
877
- // The high-traffic operator surface reads noun-less: `lotics install <pkg>`,
878
- // `lotics uninstall <id>`, `lotics upgrade <id>`. Low-traffic ops keep their
879
- // `lotics package <verb>` home. The package id is the SECOND positional at top
880
- // level, which the parser puts in `subcommand` (not `toolArgs`).
881
- if (command === "install") {
882
- const packageId = subcommand;
883
- if (!packageId) {
884
- console.error("Usage: lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]");
885
- process.exit(1);
886
- }
887
- let version;
888
- if (flags.packageVersion !== undefined) {
889
- version = Number(flags.packageVersion);
890
- if (!Number.isInteger(version) || version <= 0) {
891
- console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
892
- process.exit(1);
893
- }
894
- }
895
- const config = flags.config.length > 0 ? parseInstallConfigFlags(flags.config) : undefined;
896
- await packageInstall(client, {
897
- package_id: packageId,
898
- version,
899
- bind_to: parseBindToFlags(flags.bindTo),
900
- ...(config !== undefined ? { config } : {}),
901
- });
902
- return;
903
- }
904
- if (command === "uninstall") {
905
- const id = subcommand;
906
- if (!id) {
907
- console.error("Usage: lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content]");
908
- console.error("Dispatched by id: an app installation (app_id; --archive-tables to also archive its " +
909
- "scaffolded tables) or a content package (apg_ id from `lotics install` / " +
910
- "lotics package list-content; --keep-content to retain its docs).");
911
- process.exit(1);
912
- }
913
- // A `pci_` resource id is retired from human sight — redirect to the package-id form.
914
- if (id.startsWith("pci_")) {
915
- await redirectContentPciForm(client, id, "uninstall");
916
- return;
917
- }
918
- await packageUninstall(client, {
919
- id,
920
- keep_content: flags.keepContent,
921
- archive_tables: flags.archiveTables,
922
- });
923
- return;
924
- }
925
- if (command === "upgrade") {
926
- const target = subcommand;
927
- if (!target) {
928
- console.error("Usage: lotics upgrade <app_id | package_id> [--version N] [--resolve <key>=... ...] [--bind-to ...] [--apply-all]");
929
- console.error("Dispatched by id: an app installation (app_id), or a package id (apg_) — a CONTENT package " +
930
- "upgrades this workspace's install; an APP package fleet-upgrades every installation across your org.");
931
- process.exit(1);
932
- }
933
- let version;
934
- if (flags.packageVersion !== undefined) {
935
- version = Number(flags.packageVersion);
936
- if (!Number.isInteger(version) || version <= 0) {
937
- console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
938
- process.exit(1);
939
- }
940
- }
941
- // A `pci_` resource id is retired from human sight — redirect to the package-id form.
942
- if (target.startsWith("pci_")) {
943
- await redirectContentPciForm(client, target, "upgrade");
944
- return;
945
- }
946
- // A package id (apg_) is kind-branched: a content package upgrades THIS
947
- // workspace's install; an app package fleet-upgrades the whole org.
948
- if (target.startsWith("apg_")) {
949
- await packageUpgradeByPackageId(client, {
950
- package_id: target,
951
- version,
952
- resolve: flags.resolve,
953
- bindTo: flags.bindTo,
954
- applyAll: flags.applyAll,
955
- });
956
- return;
957
- }
958
- // Anything else is an app installation (addressed by its app id).
959
- await packageUpgrade(client, {
960
- app_id: target,
961
- version,
962
- resolve: flags.resolve,
963
- bindTo: flags.bindTo,
964
- applyAll: flags.applyAll,
965
- });
966
- return;
967
- }
968
- // lotics package <verb> — the low-traffic consumer / registry surface. The
969
- // high-traffic install / uninstall / upgrade / fleet-upgrade verbs moved to
970
- // the top level above; they are refused here (below) so the old forms fail
971
- // loudly instead of silently doing nothing.
972
- if (command === "package") {
973
- if (subcommand === "list-content") {
974
- await packageListContent(client);
975
- return;
976
- }
977
- if (subcommand === "config") {
978
- const appId = toolArgs;
979
- if (!appId) {
980
- console.error("Usage: lotics package config <app_id> [--set key=value ...]");
981
- process.exit(1);
982
- }
983
- await packageConfig(client, { app_id: appId, sets: flags.set });
984
- return;
985
- }
986
- if (subcommand === "show") {
987
- const packageId = toolArgs;
988
- if (!packageId) {
989
- console.error("Usage: lotics package show <package_id>");
990
- process.exit(1);
991
- }
992
- await packageShow(client, { package_id: packageId });
993
- return;
994
- }
995
- if (subcommand === "eject") {
996
- const appId = toolArgs;
997
- if (!appId) {
998
- console.error("Usage: lotics package eject <app_id>");
999
- process.exit(1);
1000
- }
1001
- await packageEject(client, { app_id: appId });
1002
- return;
1003
- }
1004
- if (subcommand === "yank") {
1005
- const packageId = toolArgs;
1006
- const version = Number(restArgs[0]);
1007
- if (!packageId || !Number.isInteger(version) || version <= 0) {
1008
- console.error("Usage: lotics package yank <package_id> <version> [--undo]");
1009
- process.exit(1);
1010
- }
1011
- await packageYank(client, { package_id: packageId, version, undo: flags.undo });
1012
- return;
1013
- }
1014
- if (subcommand === "doctor") {
1015
- await packageDoctor(client, { app_id: toolArgs });
1016
- return;
1017
- }
1018
- // Re-pointing a live role rides the upgrade's ONE resolutions grammar —
1019
- // redirect the removed verb loudly, never a silent unknown-command fall-through.
1020
- if (subcommand === "rebind-role") {
1021
- const appId = toolArgs;
1022
- const [roleAlias, groupId] = restArgs;
1023
- console.error("`lotics package rebind-role` was folded into the upgrade grammar. Re-point a live role with:\n" +
1024
- ` lotics upgrade ${appId ?? "<app_id>"} --resolve roles.${roleAlias ?? "<alias>"}=${groupId ?? "<grp_id>"}`);
1025
- process.exit(1);
1026
- }
1027
- // The high-traffic consumer verbs moved to the top level. Refuse the old
1028
- // `lotics package <verb>` forms loudly with a redirect, never a silent no-op.
1029
- const movedVerbs = {
1030
- install: "lotics install <package_id>",
1031
- uninstall: "lotics uninstall <app_id|package_id>",
1032
- upgrade: "lotics upgrade <app_id|package_id>",
1033
- "fleet-upgrade": "lotics upgrade <package_id>",
1034
- };
1035
- if (subcommand !== undefined && subcommand in movedVerbs) {
1036
- console.error(`\`lotics package ${subcommand}\` moved to the top level — run \`${movedVerbs[subcommand]}\` instead.`);
1037
- process.exit(1);
1038
- }
1039
- console.error(`Unknown package subcommand: ${subcommand}`);
1040
- console.error("Run 'lotics package' for usage.");
1041
- process.exit(1);
1042
- }
1043
795
  // lotics app create / pull / deploy
1044
796
  if (command === "app") {
1045
797
  if (subcommand === "create") {
@@ -1075,40 +827,6 @@ async function main() {
1075
827
  await appDeploy(client, { message });
1076
828
  return;
1077
829
  }
1078
- if (subcommand === "publish") {
1079
- // First-release a bespoke app AS a package. Positional app id (`.`/omitted →
1080
- // the local app project manifest). Previews the minted aliases + findings,
1081
- // then applies only with `--yes` (mirrors `app release`). `--rename old=new`
1082
- // fixes an auto-minted alias before v1 freezes it; `-m` is an optional v1 changelog.
1083
- await appPublish(client, {
1084
- app_id: toolArgs,
1085
- renames: flags.rename,
1086
- changelog: flags.message,
1087
- yes: flags.yes,
1088
- });
1089
- return;
1090
- }
1091
- if (subcommand === "release") {
1092
- // -m <changelog> is REQUIRED — each release is a version row read back by
1093
- // `lotics package show`, so a blank changelog loses the audit trail.
1094
- if (!flags.message) {
1095
- console.error("Usage: lotics app release [app_id|.] -m <changelog> [--yes]");
1096
- console.error("Snapshots the origin app into its next package version (preview, then --yes to publish).");
1097
- process.exit(1);
1098
- }
1099
- await appRelease(client, { app_id: toolArgs, changelog: flags.message, yes: flags.yes });
1100
- return;
1101
- }
1102
- if (subcommand === "unpublish") {
1103
- const id = toolArgs;
1104
- if (!id) {
1105
- console.error("Usage: lotics app unpublish <app_id|package_id> [--undo]");
1106
- console.error("Takes a published package off the shelf; existing installations keep working.");
1107
- process.exit(1);
1108
- }
1109
- await appUnpublish(client, { id, undo: flags.undo });
1110
- return;
1111
- }
1112
830
  if (subcommand === "subdomain") {
1113
831
  const newSubdomain = toolArgs;
1114
832
  if (!newSubdomain) {