@lotics/cli 0.76.1 → 0.86.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/cli.js CHANGED
@@ -14,7 +14,7 @@ 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, packageRetire, packageShow, parseInstallConfigFlags, packageEject, packageDoctor, packageUpgrade, packageUpgradeKnowledge, parseBindToFlags, packageNew, packageBuild, packagePublish, packageDev, packageSync, packageReset, packageExtract, packageAdopt, packageFleetUpgrade, packageYank, } from "./package_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
18
  import { parseArgs } from "./args.js";
19
19
  import { ingestJsonArgs } from "./inputs.js";
20
20
  import { runXlsxCommand } from "./xlsx.js";
@@ -71,10 +71,22 @@ COMMANDS
71
71
  cat args.json | lotics run <tool> Read JSON args from stdin (large payloads)
72
72
  lotics app create <name> [path] Create a new custom-code app + scaffold locally
73
73
  lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
74
- lotics app deploy -m <message> Build + upload current dir as a new version
75
- (-m is required — it's the version's audit trail)
76
- (code + queries only — workflow bindings are
74
+ lotics app deploy -m <message> Build + upload current dir as a new version — COMMIT
75
+ (-m is required — it's the version's audit trail;
76
+ carries code + queries only — workflow bindings are
77
77
  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
78
90
  lotics app versions [app_id] Show deploy history newest-first (version,
79
91
  timestamp, deployer, build status, -m message;
80
92
  * marks the currently served version)
@@ -96,57 +108,42 @@ COMMANDS
96
108
  lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
97
109
  lotics app rename "<new name>" Rename the app's display name (launcher title)
98
110
  lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
99
- lotics package new <name> [path] [--kind app|content]
100
- Scaffold a package project an app (contract + source)
101
- or a content corpus (docs-only, no app source)
102
- lotics package build [path] Build the publishable bundle (source + dist)
103
- lotics package publish [path] Publish a new immutable package version
104
- lotics package dev [path] Sync the package into a dev workspace + run the app
105
- dev server (--workspace <dev_ws> selects it)
106
- lotics package sync [path] Re-sync (additive migrate + materialize) into a dev ws
107
- lotics package reset [path] DEV-ONLY: drop scaffolded tables + re-scaffold clean
108
- lotics package install <package> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]
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 ...]
109
114
  Install a package (app: scaffolds/deploys/materializes,
110
115
  --config sets its knobs; content: installs the doc corpus,
111
116
  --bind-to consents to adopt a same-named doc on a collision)
112
- lotics package uninstall <app_id|pci_id> [--archive-tables] [--keep-content]
117
+ lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content]
113
118
  Uninstall — dispatched by id. App (app_id): archives
114
119
  artifacts (+ --archive-tables also archives scaffolded
115
- tables). Content (pci_): archives its package-bound docs
116
- unless --keep-content
117
- lotics package config <app_id> [--set key=value ...]
118
- Show or edit an installation's config knobs
119
- lotics package show <package_id> Registry metadata + version history
120
- lotics package retire <package_id> [--undo]
121
- Retire a package (refuse new installs, hide from other
122
- orgs); existing installations keep working + upgrade
123
- lotics package list-content List the workspace's content installations
124
- (standalone pci_ + app-bundled) source for a pci_ id
125
- lotics package upgrade <app_id|pci_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all]
126
- Preview-then-apply an upgrade. App: refuses while any
127
- drift/modified/bundled-knowledge finding lacks a --resolve
128
- (a knowledge-alias --resolve routes to the doc). Content
129
- (pci_): --resolve <alias>=apply|keep|archive|recreate|unbind;
130
- --apply-all accepts the package's version for every doc
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
131
130
  lotics package doctor [app_id] Installation health: version pin vs latest, binding
132
131
  drift, locally modified core, knowledge drift/edits
133
132
  (exit 1 on findings)
134
- lotics package rebind-role <app_id> <alias> <grp_id>
135
- Re-point a package role at a different group
136
- (re-materializes at the pinned version)
133
+ lotics package config <app_id> [--set key=value ...]
134
+ Show or edit an installation's config knobs
137
135
  lotics package eject <app_id> Sever an installation's package link
138
136
  (re-deploys the pinned source as a bespoke app)
139
- lotics package extract <app_id> [path] Promote a bespoke app to a draft package
140
- project (contract + binding + templates); always
141
- writes the draft, exits 1 on error findings
142
- lotics package adopt <app_id> [path] Bind the published package project onto the
143
- origin app (reads .lotics/adopt_binding.json;
144
- --version N pins a specific version)
145
- lotics package fleet-upgrade <package_id> [--version N]
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
146
141
  lotics package yank <package_id> <version> [--undo]
147
- Upgrade EVERY installation of the package across
148
- your org: applies where the preview is clean,
149
- skips + reports findings (exit 1 unless all current)
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
150
147
  lotics ui link <component> [--ui-src <path>] [--remove]
151
148
  Dev-link @lotics/ui to packages/ui/src (Vite alias
152
149
  + tsc paths) for live HMR + typecheck. Monorepo apps
@@ -175,13 +172,12 @@ FLAGS
175
172
  is_current_member / row-scoping resolve to them (also
176
173
  LOTICS_VIEW_AS env; admin key only; writes stay yours)
177
174
  --local Pin the current directory (lotics org use / auth api-key)
178
- --dev (lotics workspace create) Mark a throwaway app-package dev
179
- workspace required for "lotics package reset"
180
- --kind <k> (lotics package new) Package kind: app (default) or content
181
- --bind-to <a>=<id> (lotics package install/upgrade, repeatable) Adopt an existing
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
182
178
  same-named doc as package-managed (resolves a knowledge collision)
183
- --keep-content (lotics package uninstall) Keep the docs instead of archiving them
184
- --apply-all (lotics package upgrade, knowledge) Accept the package's version
179
+ --keep-content (lotics uninstall) Keep the docs instead of archiving them
180
+ --apply-all (lotics upgrade, knowledge) Accept the package's version
185
181
  for every doc (overwrites local edits)
186
182
  --all (lotics auth logout) Remove every saved credential
187
183
  --version Show version
@@ -597,28 +593,6 @@ async function main() {
597
593
  console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
598
594
  process.exit(1);
599
595
  }
600
- // --- lotics package new / build — local scaffold + build, no auth required ---
601
- if (command === "package" && subcommand === "new") {
602
- const name = toolArgs;
603
- if (!name) {
604
- console.error("Usage: lotics package new <name> [path] [--kind app|content]");
605
- process.exit(1);
606
- }
607
- if (flags.kind !== undefined && flags.kind !== "app" && flags.kind !== "content") {
608
- console.error(`Invalid --kind "${flags.kind}" — expected "app" or "content".`);
609
- process.exit(1);
610
- }
611
- await packageNew({
612
- name,
613
- targetPath: restArgs[0],
614
- kind: flags.kind === "content" ? "content" : "app",
615
- });
616
- return;
617
- }
618
- if (command === "package" && subcommand === "build") {
619
- await packageBuild({ projectDir: toolArgs });
620
- return;
621
- }
622
596
  // --- lotics app workflow check [alias] — local typecheck, no auth, no network ---
623
597
  // Reads src/workflows/*.ts + .lotics/workflows/*.globals.d.ts and the app's own
624
598
  // typescript; builds one isolated program per alias. Handled before the auth
@@ -701,7 +675,16 @@ async function main() {
701
675
  return;
702
676
  }
703
677
  // --- Validate command before auth ---
704
- if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "package") {
678
+ if (command !== "tools" &&
679
+ command !== "upload" &&
680
+ command !== "run" &&
681
+ command !== "download" &&
682
+ command !== "workspace" &&
683
+ command !== "app" &&
684
+ command !== "package" &&
685
+ command !== "install" &&
686
+ command !== "uninstall" &&
687
+ command !== "upgrade") {
705
688
  console.error(`Unknown command: ${command}`);
706
689
  console.error('Run "lotics --help" for usage.');
707
690
  process.exit(1);
@@ -723,30 +706,27 @@ async function main() {
723
706
  console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
724
707
  console.error(" lotics app rename \"<new name>\" Rename the app's display name (launcher title)");
725
708
  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)");
726
712
  process.exit(1);
727
713
  }
728
714
  if (command === "package" && !subcommand) {
729
- console.error("Usage:");
730
- console.error(" lotics package new <name> [path] [--kind app|content] Scaffold a package project (app source, or docs-only)");
731
- console.error(" lotics package build [path] Build the publishable bundle (source + dist)");
732
- console.error(" lotics package publish [path] [-m <changelog>] Publish a new immutable package version");
733
- console.error(" lotics package dev [path] [--workspace <ws>] Sync into a dev workspace + run the app dev server");
734
- console.error(" lotics package sync [path] [--workspace <ws>] Re-sync (additive migrate + materialize) into a dev workspace");
735
- console.error(" lotics package reset [path] [--workspace <ws>] DEV-ONLY: drop scaffolded tables + re-scaffold clean");
736
- console.error(" lotics package install <package> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
737
- console.error(" lotics package uninstall <app_id|pci_id> [--archive-tables] [--keep-content] Uninstall — dispatched by id (app vs content)");
738
- console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
739
- console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
740
- console.error(" lotics package retire <package_id> [--undo] Retire a package (refuse new installs; installs keep working)");
741
- console.error(" lotics package list-content List the workspace's content installations (standalone + app-bundled)");
742
- console.error(" lotics package upgrade <app_id|pci_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Preview + apply an upgrade");
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_)");
743
720
  console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding/knowledge drift");
744
- console.error(" lotics package rebind-role <app_id> <alias> <grp_id> Re-point a package role at another group");
721
+ console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
745
722
  console.error(" lotics package eject <app_id> Sever an installation's package link");
746
- console.error(" lotics package extract <app_id> [path] Promote a bespoke app to a draft package project");
747
- console.error(" lotics package adopt <app_id> [path] Bind the published project onto the origin app");
748
- console.error(" lotics package fleet-upgrade <package_id> [--version N] Upgrade every org installation (clean ones apply; findings skip)");
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)");
749
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)");
750
730
  process.exit(1);
751
731
  }
752
732
  if (command === "run" && !subcommand) {
@@ -820,11 +800,11 @@ async function main() {
820
800
  if (subcommand === "create") {
821
801
  const name = toolArgs;
822
802
  if (!name) {
823
- console.error('Usage: lotics workspace create <name> [--timezone <tz>] [--dev]');
803
+ console.error('Usage: lotics workspace create <name> [--timezone <tz>]');
824
804
  process.exit(1);
825
805
  }
826
806
  const timezone = flags.timezone;
827
- const created = await client.createWorkspace({ name, timezone, is_dev: flags.dev || undefined });
807
+ const created = await client.createWorkspace({ name, timezone });
828
808
  setSelectedWorkspace(created.id);
829
809
  client.setWorkspaceId(created.id);
830
810
  if (flags.json) {
@@ -893,47 +873,103 @@ async function main() {
893
873
  }
894
874
  // Ensure workspace is resolved for all remaining commands
895
875
  await resolveWorkspace(client, ctx);
896
- // lotics package install <package> [--version N]
897
- if (command === "package") {
898
- if (subcommand === "install") {
899
- const packageId = toolArgs;
900
- if (!packageId) {
901
- console.error("Usage: lotics package install <package> [--version N] [--bind-to <alias>=<kdc_id> ...]");
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.`);
902
892
  process.exit(1);
903
893
  }
904
- let version;
905
- if (flags.packageVersion !== undefined) {
906
- version = Number(flags.packageVersion);
907
- if (!Number.isInteger(version) || version <= 0) {
908
- console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
909
- process.exit(1);
910
- }
911
- }
912
- const config = flags.config.length > 0 ? parseInstallConfigFlags(flags.config) : undefined;
913
- await packageInstall(client, {
914
- package_id: packageId,
915
- version,
916
- bind_to: parseBindToFlags(flags.bindTo),
917
- ...(config !== undefined ? { config } : {}),
918
- });
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");
919
916
  return;
920
917
  }
921
- if (subcommand === "uninstall") {
922
- const id = toolArgs;
923
- if (!id) {
924
- console.error("Usage: lotics package uninstall <app_id|pci_id> [--archive-tables] [--keep-content]");
925
- console.error("Dispatched by id: an app installation (app_id; --archive-tables to also archive its " +
926
- "scaffolded tables) or a standalone content installation (pci_ id from install / " +
927
- "lotics package list-content; --keep-content to retain its docs).");
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.`);
928
938
  process.exit(1);
929
939
  }
930
- await packageUninstall(client, {
931
- id,
932
- keep_content: flags.keepContent,
933
- archive_tables: flags.archiveTables,
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,
934
955
  });
935
956
  return;
936
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") {
937
973
  if (subcommand === "list-content") {
938
974
  await packageListContent(client);
939
975
  return;
@@ -947,15 +983,6 @@ async function main() {
947
983
  await packageConfig(client, { app_id: appId, sets: flags.set });
948
984
  return;
949
985
  }
950
- if (subcommand === "retire") {
951
- const packageId = toolArgs;
952
- if (!packageId) {
953
- console.error("Usage: lotics package retire <package_id> [--undo]");
954
- process.exit(1);
955
- }
956
- await packageRetire(client, { package_id: packageId, undo: flags.undo });
957
- return;
958
- }
959
986
  if (subcommand === "show") {
960
987
  const packageId = toolArgs;
961
988
  if (!packageId) {
@@ -974,34 +1001,6 @@ async function main() {
974
1001
  await packageEject(client, { app_id: appId });
975
1002
  return;
976
1003
  }
977
- if (subcommand === "extract") {
978
- const appId = toolArgs;
979
- if (!appId) {
980
- console.error("Usage: lotics package extract <app_id> [path]");
981
- console.error("Promotes a bespoke app to a draft package project (contract + binding + templates).");
982
- process.exit(1);
983
- }
984
- await packageExtract(client, { app_id: appId, targetPath: restArgs[0] });
985
- return;
986
- }
987
- if (subcommand === "adopt") {
988
- const appId = toolArgs;
989
- if (!appId) {
990
- console.error("Usage: lotics package adopt <app_id> [path]");
991
- console.error("Binds the published package project (this dir, or [path]) onto the origin app.");
992
- process.exit(1);
993
- }
994
- let version;
995
- if (flags.packageVersion !== undefined) {
996
- version = Number(flags.packageVersion);
997
- if (!Number.isInteger(version) || version <= 0) {
998
- console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
999
- process.exit(1);
1000
- }
1001
- }
1002
- await packageAdopt(client, { app_id: appId, version, projectDir: restArgs[0] });
1003
- return;
1004
- }
1005
1004
  if (subcommand === "yank") {
1006
1005
  const packageId = toolArgs;
1007
1006
  const version = Number(restArgs[0]);
@@ -1012,106 +1011,30 @@ async function main() {
1012
1011
  await packageYank(client, { package_id: packageId, version, undo: flags.undo });
1013
1012
  return;
1014
1013
  }
1015
- if (subcommand === "fleet-upgrade") {
1016
- const packageId = toolArgs;
1017
- if (!packageId) {
1018
- console.error("Usage: lotics package fleet-upgrade <package_id> [--version N]");
1019
- process.exit(1);
1020
- }
1021
- let version;
1022
- if (flags.packageVersion !== undefined) {
1023
- version = Number(flags.packageVersion);
1024
- if (!Number.isInteger(version) || version <= 0) {
1025
- console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
1026
- process.exit(1);
1027
- }
1028
- }
1029
- await packageFleetUpgrade(client, { package_id: packageId, version });
1030
- return;
1031
- }
1032
1014
  if (subcommand === "doctor") {
1033
1015
  await packageDoctor(client, { app_id: toolArgs });
1034
1016
  return;
1035
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.
1036
1020
  if (subcommand === "rebind-role") {
1037
1021
  const appId = toolArgs;
1038
1022
  const [roleAlias, groupId] = restArgs;
1039
- if (!appId || !roleAlias || !groupId) {
1040
- console.error("Usage: lotics package rebind-role <app_id> <role_alias> <grp_id>");
1041
- process.exit(1);
1042
- }
1043
- const result = await client.rebindPackageRole(appId, {
1044
- role_alias: roleAlias,
1045
- group_id: groupId,
1046
- });
1047
- console.error(`Rebound role "${result.role_alias}" → ${result.group_id}` +
1048
- (result.previous_group_id ? ` (was ${result.previous_group_id})` : "") +
1049
- ". Package workflows/agents were re-materialized against the new group.");
1050
- return;
1051
- }
1052
- if (subcommand === "upgrade") {
1053
- const target = toolArgs;
1054
- if (!target) {
1055
- console.error("Usage: lotics package upgrade <app_id | pci_installation_id> [--version N] [--resolve <key>=... ...]");
1056
- process.exit(1);
1057
- }
1058
- let version;
1059
- if (flags.packageVersion !== undefined) {
1060
- version = Number(flags.packageVersion);
1061
- if (!Number.isInteger(version) || version <= 0) {
1062
- console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
1063
- process.exit(1);
1064
- }
1065
- }
1066
- // A `pci_` id is a standalone content installation (its own upgrade
1067
- // pipeline); everything else is an app installation.
1068
- if (target.startsWith("pci_")) {
1069
- await packageUpgradeKnowledge(client, {
1070
- installation_id: target,
1071
- version,
1072
- resolve: flags.resolve,
1073
- bind_to: parseBindToFlags(flags.bindTo),
1074
- applyAll: flags.applyAll,
1075
- });
1076
- return;
1077
- }
1078
- await packageUpgrade(client, {
1079
- app_id: target,
1080
- version,
1081
- resolve: flags.resolve,
1082
- bindTo: flags.bindTo,
1083
- applyAll: flags.applyAll,
1084
- });
1085
- return;
1086
- }
1087
- if (subcommand === "publish") {
1088
- await packagePublish(client, { projectDir: toolArgs, changelog: flags.message });
1089
- return;
1090
- }
1091
- if (subcommand === "sync") {
1092
- await packageSync(client, { projectDir: toolArgs });
1093
- return;
1094
- }
1095
- if (subcommand === "reset") {
1096
- await packageReset(client, { projectDir: toolArgs });
1097
- return;
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);
1098
1026
  }
1099
- if (subcommand === "dev") {
1100
- // First positional is an optional project path; --port / --vite-port
1101
- // override the wrapper / Vite ports (same shape as `lotics app dev`).
1102
- const projectDir = toolArgs;
1103
- let port;
1104
- let vitePort;
1105
- for (const a of restArgs) {
1106
- const portMatch = /^--port=(\d+)$/.exec(a);
1107
- const vitePortMatch = /^--vite-port=(\d+)$/.exec(a);
1108
- if (portMatch)
1109
- port = Number(portMatch[1]);
1110
- else if (vitePortMatch)
1111
- vitePort = Number(vitePortMatch[1]);
1112
- }
1113
- await packageDev(client, { projectDir, port, vitePort });
1114
- return;
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);
1115
1038
  }
1116
1039
  console.error(`Unknown package subcommand: ${subcommand}`);
1117
1040
  console.error("Run 'lotics package' for usage.");
@@ -1152,6 +1075,40 @@ async function main() {
1152
1075
  await appDeploy(client, { message });
1153
1076
  return;
1154
1077
  }
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
+ }
1155
1112
  if (subcommand === "subdomain") {
1156
1113
  const newSubdomain = toolArgs;
1157
1114
  if (!newSubdomain) {
@@ -1382,8 +1339,10 @@ async function main() {
1382
1339
  console.log(JSON.stringify({ error: result.error }, null, 2));
1383
1340
  }
1384
1341
  else {
1342
+ // Execution errors (not-found, refusals) carry the server's message;
1343
+ // the input already passed schema validation, so no schema hint —
1344
+ // validation 400s bring the server's own hint via throwResponseError.
1385
1345
  console.error(result.error);
1386
- console.error(`\nHint: run "lotics tools ${toolName}" to see the expected input schema.`);
1387
1346
  }
1388
1347
  process.exit(1);
1389
1348
  }