@lotics/cli 0.76.0 → 0.83.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, packageConfig, packageRetire, packageShow, parseInstallConfigFlags, packageEject, packageDoctor, packageUpgrade, parseResolveFlags, 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, packageUpgradeKnowledge, parseBindToFlags, appPublish, appRelease, appUnpublish, packageFleetUpgrade, 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,47 +108,40 @@ 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] Scaffold a package project (contract + app source)
100
- lotics package build [path] Build the publishable bundle (source + dist)
101
- lotics package publish [path] Publish a new immutable package version
102
- lotics package dev [path] Sync the package into a dev workspace + run the app
103
- dev server (--workspace <dev_ws> selects it)
104
- lotics package sync [path] Re-sync (additive migrate + materialize) into a dev ws
105
- lotics package reset [path] DEV-ONLY: drop scaffolded tables + re-scaffold clean
106
- lotics package install <package> [--version N] [--config key=value ...]
107
- Install an app package into this workspace
108
- (scaffolds the data model + deploys + materializes)
109
- lotics package uninstall <app_id> [--archive-tables]
110
- Remove an installation (archives artifacts; with the
111
- flag also archives the scaffolded tables it created)
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|pci_id> [--archive-tables] [--keep-content]
118
+ Uninstall dispatched by id. App (app_id): archives
119
+ artifacts (+ --archive-tables also archives scaffolded
120
+ tables). Content (pci_): archives its package-bound docs
121
+ unless --keep-content
122
+ lotics upgrade <app_id|pci_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. Content (pci_): --resolve
126
+ <alias>=apply|keep|archive|recreate|unbind. Package (apg_):
127
+ FLEET — every installation across your org (clean apply,
128
+ findings skip). --apply-all accepts the package's version
129
+ lotics package doctor [app_id] Installation health: version pin vs latest, binding
130
+ drift, locally modified core, knowledge drift/edits
131
+ (exit 1 on findings)
112
132
  lotics package config <app_id> [--set key=value ...]
113
133
  Show or edit an installation's config knobs
114
- lotics package show <package_id> Registry metadata + version history
115
- lotics package retire <package_id> [--undo]
116
- Retire a package (refuse new installs, hide from other
117
- orgs); existing installations keep working + upgrade
118
- lotics package upgrade <app_id> [--version N] [--resolve <key>=recreate|revert|keep|<id> ...]
119
- Preview-then-apply a package upgrade; refuses while
120
- any drift/modified finding lacks a --resolve, and
121
- hard-stops on breaking contract changes
122
- lotics package doctor [app_id] Installation health: version pin vs latest, binding
123
- drift, locally modified core (exit 1 on findings)
124
- lotics package rebind-role <app_id> <alias> <grp_id>
125
- Re-point a package role at a different group
126
- (re-materializes at the pinned version)
127
134
  lotics package eject <app_id> Sever an installation's package link
128
135
  (re-deploys the pinned source as a bespoke app)
129
- lotics package extract <app_id> [path] Promote a bespoke app to a draft package
130
- project (contract + binding + templates); always
131
- writes the draft, exits 1 on error findings
132
- lotics package adopt <app_id> [path] Bind the published package project onto the
133
- origin app (reads .lotics/adopt_binding.json;
134
- --version N pins a specific version)
135
- lotics package fleet-upgrade <package_id> [--version N]
136
+ lotics package show <package_id> Registry metadata + version history
137
+ lotics package list-content List the workspace's content installations
138
+ (standalone content installs) source for a pci_ id
136
139
  lotics package yank <package_id> <version> [--undo]
137
- Upgrade EVERY installation of the package across
138
- your org: applies where the preview is clean,
139
- skips + reports findings (exit 1 unless all current)
140
+ Refuse new installs/upgrades of a broken published
141
+ version (pinned installations keep running)
142
+ lotics run publish_content '{"name":"…","knowledge_doc_ids":[…],"template_ids":[…]}'
143
+ Publish a SET of live docs + templates as a content
144
+ package (v1 + origin pin); release_content cuts the next
140
145
  lotics ui link <component> [--ui-src <path>] [--remove]
141
146
  Dev-link @lotics/ui to packages/ui/src (Vite alias
142
147
  + tsc paths) for live HMR + typecheck. Monorepo apps
@@ -165,8 +170,13 @@ FLAGS
165
170
  is_current_member / row-scoping resolve to them (also
166
171
  LOTICS_VIEW_AS env; admin key only; writes stay yours)
167
172
  --local Pin the current directory (lotics org use / auth api-key)
168
- --dev (lotics workspace create) Mark a throwaway app-package dev
169
- workspace required for "lotics package reset"
173
+ --rename <old>=<new> (lotics app publish, repeatable) Fix an auto-minted alias before
174
+ the package's v1 contract freezes it
175
+ --bind-to <a>=<id> (lotics install/upgrade, repeatable) Adopt an existing
176
+ same-named doc as package-managed (resolves a knowledge collision)
177
+ --keep-content (lotics uninstall) Keep the docs instead of archiving them
178
+ --apply-all (lotics upgrade, knowledge) Accept the package's version
179
+ for every doc (overwrites local edits)
170
180
  --all (lotics auth logout) Remove every saved credential
171
181
  --version Show version
172
182
 
@@ -581,20 +591,6 @@ async function main() {
581
591
  console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
582
592
  process.exit(1);
583
593
  }
584
- // --- lotics package new / build — local scaffold + build, no auth required ---
585
- if (command === "package" && subcommand === "new") {
586
- const name = toolArgs;
587
- if (!name) {
588
- console.error("Usage: lotics package new <name> [path]");
589
- process.exit(1);
590
- }
591
- await packageNew({ name, targetPath: restArgs[0] });
592
- return;
593
- }
594
- if (command === "package" && subcommand === "build") {
595
- await packageBuild({ projectDir: toolArgs });
596
- return;
597
- }
598
594
  // --- lotics app workflow check [alias] — local typecheck, no auth, no network ---
599
595
  // Reads src/workflows/*.ts + .lotics/workflows/*.globals.d.ts and the app's own
600
596
  // typescript; builds one isolated program per alias. Handled before the auth
@@ -677,7 +673,16 @@ async function main() {
677
673
  return;
678
674
  }
679
675
  // --- Validate command before auth ---
680
- if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "package") {
676
+ if (command !== "tools" &&
677
+ command !== "upload" &&
678
+ command !== "run" &&
679
+ command !== "download" &&
680
+ command !== "workspace" &&
681
+ command !== "app" &&
682
+ command !== "package" &&
683
+ command !== "install" &&
684
+ command !== "uninstall" &&
685
+ command !== "upgrade") {
681
686
  console.error(`Unknown command: ${command}`);
682
687
  console.error('Run "lotics --help" for usage.');
683
688
  process.exit(1);
@@ -699,29 +704,27 @@ async function main() {
699
704
  console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
700
705
  console.error(" lotics app rename \"<new name>\" Rename the app's display name (launcher title)");
701
706
  console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
707
+ console.error(" lotics app publish [app_id|.] [--rename old=new ...] [-m <changelog>] [--yes] First-release the app as a package (v1; preview, --yes applies)");
708
+ console.error(" lotics app release [app_id|.] -m <changelog> [--yes] Release the next package version from the origin");
709
+ console.error(" lotics app unpublish <app_id|package_id> [--undo] Take a published package off the shelf (any kind — app or content)");
702
710
  process.exit(1);
703
711
  }
704
712
  if (command === "package" && !subcommand) {
705
- console.error("Usage:");
706
- console.error(" lotics package new <name> [path] Scaffold a package project (contract + app source)");
707
- console.error(" lotics package build [path] Build the publishable bundle (source + dist)");
708
- console.error(" lotics package publish [path] [-m <changelog>] Publish a new immutable package version");
709
- console.error(" lotics package dev [path] [--workspace <ws>] Sync into a dev workspace + run the app dev server");
710
- console.error(" lotics package sync [path] [--workspace <ws>] Re-sync (additive migrate + materialize) into a dev workspace");
711
- console.error(" lotics package reset [path] [--workspace <ws>] DEV-ONLY: drop scaffolded tables + re-scaffold clean");
712
- console.error(" lotics package install <package> [--version N] [--config key=value ...] Install a package into this workspace");
713
- console.error(" lotics package uninstall <app_id> [--archive-tables] Remove an installation (opt-in table archival)");
713
+ console.error("Usage (low-traffic consumer / registry ops — author verbs live on `lotics app`):");
714
+ console.error(" The high-traffic consumer verbs are top-level:");
715
+ console.error(" lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
716
+ console.error(" lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content] Uninstall dispatched by id (app vs content)");
717
+ console.error(" lotics upgrade <app_id|pci_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Upgrade app / content / whole fleet (apg_)");
718
+ console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding/knowledge drift");
714
719
  console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
715
- console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
716
- console.error(" lotics package retire <package_id> [--undo] Retire a package (refuse new installs; installs keep working)");
717
- console.error(" lotics package upgrade <app_id> [--version N] [--resolve ns.alias=recreate|<id>] Preview + apply an upgrade");
718
- console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding drift");
719
- console.error(" lotics package rebind-role <app_id> <alias> <grp_id> Re-point a package role at another group");
720
720
  console.error(" lotics package eject <app_id> Sever an installation's package link");
721
- console.error(" lotics package extract <app_id> [path] Promote a bespoke app to a draft package project");
722
- console.error(" lotics package adopt <app_id> [path] Bind the published project onto the origin app");
723
- console.error(" lotics package fleet-upgrade <package_id> [--version N] Upgrade every org installation (clean ones apply; findings skip)");
721
+ console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
722
+ console.error(" lotics package list-content List the workspace's content installations (standalone content installs)");
724
723
  console.error(" lotics package yank <package_id> <version> [--undo] Refuse new installs/upgrades of a broken published version (pinned installations keep running)");
724
+ console.error(" A content package (a SET of live docs + templates) — publish + release with the tools, retire on `lotics app`:");
725
+ console.error(" lotics run publish_content '{\"name\":\"…\",\"knowledge_doc_ids\":[…],\"template_ids\":[…]}' Publish v1 (a fixed set)");
726
+ console.error(" lotics run release_content '{\"package_id\":\"apg_…\",\"changelog\":\"…\"}' Cut the next version (add \"dry_run\":true to preview)");
727
+ console.error(" lotics app unpublish <package_id> Take the whole package off the shelf (kind-agnostic — retires content too)");
725
728
  process.exit(1);
726
729
  }
727
730
  if (command === "run" && !subcommand) {
@@ -795,11 +798,11 @@ async function main() {
795
798
  if (subcommand === "create") {
796
799
  const name = toolArgs;
797
800
  if (!name) {
798
- console.error('Usage: lotics workspace create <name> [--timezone <tz>] [--dev]');
801
+ console.error('Usage: lotics workspace create <name> [--timezone <tz>]');
799
802
  process.exit(1);
800
803
  }
801
804
  const timezone = flags.timezone;
802
- const created = await client.createWorkspace({ name, timezone, is_dev: flags.dev || undefined });
805
+ const created = await client.createWorkspace({ name, timezone });
803
806
  setSelectedWorkspace(created.id);
804
807
  client.setWorkspaceId(created.id);
805
808
  if (flags.json) {
@@ -868,37 +871,98 @@ async function main() {
868
871
  }
869
872
  // Ensure workspace is resolved for all remaining commands
870
873
  await resolveWorkspace(client, ctx);
871
- // lotics package install <package> [--version N]
872
- if (command === "package") {
873
- if (subcommand === "install") {
874
- const packageId = toolArgs;
875
- if (!packageId) {
876
- console.error("Usage: lotics package install <package> [--version N]");
874
+ // --- lotics install / uninstall / upgrade — top-level CONSUMER verbs ---
875
+ // The high-traffic operator surface reads noun-less: `lotics install <pkg>`,
876
+ // `lotics uninstall <id>`, `lotics upgrade <id>`. Low-traffic ops keep their
877
+ // `lotics package <verb>` home. The package id is the SECOND positional at top
878
+ // level, which the parser puts in `subcommand` (not `toolArgs`).
879
+ if (command === "install") {
880
+ const packageId = subcommand;
881
+ if (!packageId) {
882
+ console.error("Usage: lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]");
883
+ process.exit(1);
884
+ }
885
+ let version;
886
+ if (flags.packageVersion !== undefined) {
887
+ version = Number(flags.packageVersion);
888
+ if (!Number.isInteger(version) || version <= 0) {
889
+ console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
877
890
  process.exit(1);
878
891
  }
879
- let version;
880
- if (flags.packageVersion !== undefined) {
881
- version = Number(flags.packageVersion);
882
- if (!Number.isInteger(version) || version <= 0) {
883
- console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
884
- process.exit(1);
885
- }
892
+ }
893
+ const config = flags.config.length > 0 ? parseInstallConfigFlags(flags.config) : undefined;
894
+ await packageInstall(client, {
895
+ package_id: packageId,
896
+ version,
897
+ bind_to: parseBindToFlags(flags.bindTo),
898
+ ...(config !== undefined ? { config } : {}),
899
+ });
900
+ return;
901
+ }
902
+ if (command === "uninstall") {
903
+ const id = subcommand;
904
+ if (!id) {
905
+ console.error("Usage: lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content]");
906
+ console.error("Dispatched by id: an app installation (app_id; --archive-tables to also archive its " +
907
+ "scaffolded tables) or a standalone content installation (pci_ id from `lotics install` / " +
908
+ "lotics package list-content; --keep-content to retain its docs).");
909
+ process.exit(1);
910
+ }
911
+ await packageUninstall(client, {
912
+ id,
913
+ keep_content: flags.keepContent,
914
+ archive_tables: flags.archiveTables,
915
+ });
916
+ return;
917
+ }
918
+ if (command === "upgrade") {
919
+ const target = subcommand;
920
+ if (!target) {
921
+ console.error("Usage: lotics upgrade <app_id | pci_id | package_id> [--version N] [--resolve <key>=... ...] [--bind-to ...] [--apply-all]");
922
+ console.error("Dispatched by id: an app installation (app_id), a standalone content installation (pci_ id), " +
923
+ "or a package id (apg_ — fleet-upgrades every installation across your org).");
924
+ process.exit(1);
925
+ }
926
+ let version;
927
+ if (flags.packageVersion !== undefined) {
928
+ version = Number(flags.packageVersion);
929
+ if (!Number.isInteger(version) || version <= 0) {
930
+ console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
931
+ process.exit(1);
886
932
  }
887
- const config = flags.config.length > 0 ? parseInstallConfigFlags(flags.config) : undefined;
888
- await packageInstall(client, {
889
- package_id: packageId,
933
+ }
934
+ // A package id fleet-upgrades every installation across the org; a `pci_` id
935
+ // is a standalone content installation; anything else is an app installation.
936
+ if (target.startsWith("apg_")) {
937
+ await packageFleetUpgrade(client, { package_id: target, version });
938
+ return;
939
+ }
940
+ if (target.startsWith("pci_")) {
941
+ await packageUpgradeKnowledge(client, {
942
+ installation_id: target,
890
943
  version,
891
- ...(config !== undefined ? { config } : {}),
944
+ resolve: flags.resolve,
945
+ bind_to: parseBindToFlags(flags.bindTo),
946
+ applyAll: flags.applyAll,
892
947
  });
893
948
  return;
894
949
  }
895
- if (subcommand === "uninstall") {
896
- const appId = toolArgs;
897
- if (!appId) {
898
- console.error("Usage: lotics package uninstall <app_id> [--archive-tables]");
899
- process.exit(1);
900
- }
901
- await packageUninstall(client, { app_id: appId, archive_tables: flags.archiveTables });
950
+ await packageUpgrade(client, {
951
+ app_id: target,
952
+ version,
953
+ resolve: flags.resolve,
954
+ bindTo: flags.bindTo,
955
+ applyAll: flags.applyAll,
956
+ });
957
+ return;
958
+ }
959
+ // lotics package <verb> — the low-traffic consumer / registry surface. The
960
+ // high-traffic install / uninstall / upgrade / fleet-upgrade verbs moved to
961
+ // the top level above; they are refused here (below) so the old forms fail
962
+ // loudly instead of silently doing nothing.
963
+ if (command === "package") {
964
+ if (subcommand === "list-content") {
965
+ await packageListContent(client);
902
966
  return;
903
967
  }
904
968
  if (subcommand === "config") {
@@ -910,15 +974,6 @@ async function main() {
910
974
  await packageConfig(client, { app_id: appId, sets: flags.set });
911
975
  return;
912
976
  }
913
- if (subcommand === "retire") {
914
- const packageId = toolArgs;
915
- if (!packageId) {
916
- console.error("Usage: lotics package retire <package_id> [--undo]");
917
- process.exit(1);
918
- }
919
- await packageRetire(client, { package_id: packageId, undo: flags.undo });
920
- return;
921
- }
922
977
  if (subcommand === "show") {
923
978
  const packageId = toolArgs;
924
979
  if (!packageId) {
@@ -937,34 +992,6 @@ async function main() {
937
992
  await packageEject(client, { app_id: appId });
938
993
  return;
939
994
  }
940
- if (subcommand === "extract") {
941
- const appId = toolArgs;
942
- if (!appId) {
943
- console.error("Usage: lotics package extract <app_id> [path]");
944
- console.error("Promotes a bespoke app to a draft package project (contract + binding + templates).");
945
- process.exit(1);
946
- }
947
- await packageExtract(client, { app_id: appId, targetPath: restArgs[0] });
948
- return;
949
- }
950
- if (subcommand === "adopt") {
951
- const appId = toolArgs;
952
- if (!appId) {
953
- console.error("Usage: lotics package adopt <app_id> [path]");
954
- console.error("Binds the published package project (this dir, or [path]) onto the origin app.");
955
- process.exit(1);
956
- }
957
- let version;
958
- if (flags.packageVersion !== undefined) {
959
- version = Number(flags.packageVersion);
960
- if (!Number.isInteger(version) || version <= 0) {
961
- console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
962
- process.exit(1);
963
- }
964
- }
965
- await packageAdopt(client, { app_id: appId, version, projectDir: restArgs[0] });
966
- return;
967
- }
968
995
  if (subcommand === "yank") {
969
996
  const packageId = toolArgs;
970
997
  const version = Number(restArgs[0]);
@@ -975,92 +1002,30 @@ async function main() {
975
1002
  await packageYank(client, { package_id: packageId, version, undo: flags.undo });
976
1003
  return;
977
1004
  }
978
- if (subcommand === "fleet-upgrade") {
979
- const packageId = toolArgs;
980
- if (!packageId) {
981
- console.error("Usage: lotics package fleet-upgrade <package_id> [--version N]");
982
- process.exit(1);
983
- }
984
- let version;
985
- if (flags.packageVersion !== undefined) {
986
- version = Number(flags.packageVersion);
987
- if (!Number.isInteger(version) || version <= 0) {
988
- console.error(`Invalid --version "${flags.packageVersion}" — expected a positive integer.`);
989
- process.exit(1);
990
- }
991
- }
992
- await packageFleetUpgrade(client, { package_id: packageId, version });
993
- return;
994
- }
995
1005
  if (subcommand === "doctor") {
996
1006
  await packageDoctor(client, { app_id: toolArgs });
997
1007
  return;
998
1008
  }
1009
+ // Re-pointing a live role rides the upgrade's ONE resolutions grammar —
1010
+ // redirect the removed verb loudly, never a silent unknown-command fall-through.
999
1011
  if (subcommand === "rebind-role") {
1000
1012
  const appId = toolArgs;
1001
1013
  const [roleAlias, groupId] = restArgs;
1002
- if (!appId || !roleAlias || !groupId) {
1003
- console.error("Usage: lotics package rebind-role <app_id> <role_alias> <grp_id>");
1004
- process.exit(1);
1005
- }
1006
- const result = await client.rebindAppPackageRole(appId, {
1007
- role_alias: roleAlias,
1008
- group_id: groupId,
1009
- });
1010
- console.error(`Rebound role "${result.role_alias}" → ${result.group_id}` +
1011
- (result.previous_group_id ? ` (was ${result.previous_group_id})` : "") +
1012
- ". Package workflows/agents were re-materialized against the new group.");
1013
- return;
1014
- }
1015
- if (subcommand === "upgrade") {
1016
- const appId = toolArgs;
1017
- if (!appId) {
1018
- console.error("Usage: lotics package upgrade <app_id> [--version N] [--resolve <namespace.alias>=recreate|<id> ...]");
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 packageUpgrade(client, {
1030
- app_id: appId,
1031
- version,
1032
- resolutions: parseResolveFlags(flags.resolve),
1033
- });
1034
- return;
1035
- }
1036
- if (subcommand === "publish") {
1037
- await packagePublish(client, { projectDir: toolArgs, changelog: flags.message });
1038
- return;
1039
- }
1040
- if (subcommand === "sync") {
1041
- await packageSync(client, { projectDir: toolArgs });
1042
- return;
1043
- }
1044
- if (subcommand === "reset") {
1045
- await packageReset(client, { projectDir: toolArgs });
1046
- return;
1014
+ console.error("`lotics package rebind-role` was folded into the upgrade grammar. Re-point a live role with:\n" +
1015
+ ` lotics upgrade ${appId ?? "<app_id>"} --resolve roles.${roleAlias ?? "<alias>"}=${groupId ?? "<grp_id>"}`);
1016
+ process.exit(1);
1047
1017
  }
1048
- if (subcommand === "dev") {
1049
- // First positional is an optional project path; --port / --vite-port
1050
- // override the wrapper / Vite ports (same shape as `lotics app dev`).
1051
- const projectDir = toolArgs;
1052
- let port;
1053
- let vitePort;
1054
- for (const a of restArgs) {
1055
- const portMatch = /^--port=(\d+)$/.exec(a);
1056
- const vitePortMatch = /^--vite-port=(\d+)$/.exec(a);
1057
- if (portMatch)
1058
- port = Number(portMatch[1]);
1059
- else if (vitePortMatch)
1060
- vitePort = Number(vitePortMatch[1]);
1061
- }
1062
- await packageDev(client, { projectDir, port, vitePort });
1063
- return;
1018
+ // The high-traffic consumer verbs moved to the top level. Refuse the old
1019
+ // `lotics package <verb>` forms loudly with a redirect, never a silent no-op.
1020
+ const movedVerbs = {
1021
+ install: "lotics install <package_id>",
1022
+ uninstall: "lotics uninstall <app_id|pci_id>",
1023
+ upgrade: "lotics upgrade <app_id|pci_id|package_id>",
1024
+ "fleet-upgrade": "lotics upgrade <package_id>",
1025
+ };
1026
+ if (subcommand !== undefined && subcommand in movedVerbs) {
1027
+ console.error(`\`lotics package ${subcommand}\` moved to the top level — run \`${movedVerbs[subcommand]}\` instead.`);
1028
+ process.exit(1);
1064
1029
  }
1065
1030
  console.error(`Unknown package subcommand: ${subcommand}`);
1066
1031
  console.error("Run 'lotics package' for usage.");
@@ -1101,6 +1066,40 @@ async function main() {
1101
1066
  await appDeploy(client, { message });
1102
1067
  return;
1103
1068
  }
1069
+ if (subcommand === "publish") {
1070
+ // First-release a bespoke app AS a package. Positional app id (`.`/omitted →
1071
+ // the local app project manifest). Previews the minted aliases + findings,
1072
+ // then applies only with `--yes` (mirrors `app release`). `--rename old=new`
1073
+ // fixes an auto-minted alias before v1 freezes it; `-m` is an optional v1 changelog.
1074
+ await appPublish(client, {
1075
+ app_id: toolArgs,
1076
+ renames: flags.rename,
1077
+ changelog: flags.message,
1078
+ yes: flags.yes,
1079
+ });
1080
+ return;
1081
+ }
1082
+ if (subcommand === "release") {
1083
+ // -m <changelog> is REQUIRED — each release is a version row read back by
1084
+ // `lotics package show`, so a blank changelog loses the audit trail.
1085
+ if (!flags.message) {
1086
+ console.error("Usage: lotics app release [app_id|.] -m <changelog> [--yes]");
1087
+ console.error("Snapshots the origin app into its next package version (preview, then --yes to publish).");
1088
+ process.exit(1);
1089
+ }
1090
+ await appRelease(client, { app_id: toolArgs, changelog: flags.message, yes: flags.yes });
1091
+ return;
1092
+ }
1093
+ if (subcommand === "unpublish") {
1094
+ const id = toolArgs;
1095
+ if (!id) {
1096
+ console.error("Usage: lotics app unpublish <app_id|package_id> [--undo]");
1097
+ console.error("Takes a published package off the shelf; existing installations keep working.");
1098
+ process.exit(1);
1099
+ }
1100
+ await appUnpublish(client, { id, undo: flags.undo });
1101
+ return;
1102
+ }
1104
1103
  if (subcommand === "subdomain") {
1105
1104
  const newSubdomain = toolArgs;
1106
1105
  if (!newSubdomain) {