@lotics/cli 0.83.0 → 0.86.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -29948,18 +29948,32 @@ var LoticsClient = class {
29948
29948
  /**
29949
29949
  * Preview a release — the dry run behind `lotics app release`. Runs the
29950
29950
  * binding-aware extract of the origin (aliases stable through the app's current
29951
- * binding) and reports the next version number, the new + changed aliases, and
29952
- * any extract findings (an `error` blocks the apply). No writes. Admin,
29951
+ * binding) and reports the next version number, the new + changed aliases, the
29952
+ * bundled-knowledge delta, and any extract findings (an `error` blocks the
29953
+ * apply). An optional `knowledge` declaration (from the pulled app manifest)
29954
+ * re-declares the bundle set — added/dropped/changed docs surface in the delta;
29955
+ * omitted, the current corpus is reconstructed from the pin. No writes. Admin,
29953
29956
  * owning-org only.
29954
29957
  */
29955
- async previewPackageRelease(app_id) {
29956
- return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-release`);
29958
+ async previewPackageRelease(app_id, opts = {}) {
29959
+ const params = new URLSearchParams();
29960
+ if (opts.knowledge !== void 0 && opts.knowledge.length > 0) {
29961
+ params.set("knowledge", JSON.stringify(opts.knowledge));
29962
+ }
29963
+ const query = params.toString();
29964
+ return this.request(
29965
+ "GET",
29966
+ `/v1/apps/${encodeURIComponent(app_id)}/package-release${query ? `?${query}` : ""}`
29967
+ );
29957
29968
  }
29958
29969
  /**
29959
29970
  * Release — snapshot the origin app into the next registry version. The server
29960
29971
  * binding-aware-extracts it, repackages its deployed source + dist as the
29961
29972
  * bundle, publishes the next `release`-channel version with the changelog, and
29962
- * re-pins the origin. Error findings from extract surface as a 409. Admin,
29973
+ * re-pins the origin. An optional `knowledge` declaration re-declares the
29974
+ * bundled-knowledge set (added/dropped/re-snapshotted docs; omitted preserves
29975
+ * the current corpus). Error findings from extract surface as a 409; a
29976
+ * missing/archived declared doc is a 400, a foreign-package doc a 409. Admin,
29963
29977
  * owning-org only. Backs `lotics app release --yes`.
29964
29978
  */
29965
29979
  async releasePackage(app_id, body) {
@@ -31584,7 +31598,13 @@ function buildWrapperPage(args) {
31584
31598
 
31585
31599
  // The iframe SDK sends one "upload" op carrying a File. A File can't
31586
31600
  // cross the JSON /_rpc hop, so the upload runs here in the browser:
31587
- // mint a presigned URL, PUT the bytes to storage, then finalize.
31601
+ // mint an upload URL, PUT the bytes, then finalize.
31602
+ //
31603
+ // In dev the minted URL is same-origin (/_upload/<file_id>) \u2014 the dev
31604
+ // server relays the bytes to storage on our behalf, because the prod
31605
+ // bucket's CORS does not admit a localhost origin. Production PUTs the
31606
+ // presigned storage URL directly. Same three lines either way: the page
31607
+ // PUTs wherever the mint points.
31588
31608
  //
31589
31609
  // Retry policy parity with the production SDK
31590
31610
  // (packages/app-sdk/src/upload/transport.ts): 3 PUT attempts with
@@ -31789,6 +31809,85 @@ function escapeHtml2(s) {
31789
31809
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
31790
31810
  }
31791
31811
 
31812
+ // src/dev/upload_relay.ts
31813
+ var PRESIGN_TTL_MS = 15 * 60 * 1e3;
31814
+ function createUploadRelay(now = Date.now) {
31815
+ const minted = /* @__PURE__ */ new Map();
31816
+ const prune = (at) => {
31817
+ for (const [id, entry] of minted) {
31818
+ if (at - entry.mintedAt > PRESIGN_TTL_MS) minted.delete(id);
31819
+ }
31820
+ };
31821
+ return {
31822
+ rewriteMint(result) {
31823
+ const mint = result;
31824
+ if (typeof mint?.file_id !== "string" || typeof mint?.upload_url !== "string") return result;
31825
+ const at = now();
31826
+ prune(at);
31827
+ minted.set(mint.file_id, { url: mint.upload_url, mintedAt: at });
31828
+ return { ...mint, upload_url: `/_upload/${encodeURIComponent(mint.file_id)}` };
31829
+ },
31830
+ destinationFor(fileId) {
31831
+ const entry = minted.get(fileId);
31832
+ if (!entry) return null;
31833
+ if (now() - entry.mintedAt > PRESIGN_TTL_MS) {
31834
+ minted.delete(fileId);
31835
+ return null;
31836
+ }
31837
+ return entry.url;
31838
+ },
31839
+ settle(fileId) {
31840
+ minted.delete(fileId);
31841
+ },
31842
+ size: () => minted.size
31843
+ };
31844
+ }
31845
+
31846
+ // src/dev/file_relay.ts
31847
+ import { createHash } from "node:crypto";
31848
+ var TTL_MS = 24 * 60 * 60 * 1e3;
31849
+ var MAX_ENTRIES = 5e3;
31850
+ var PRESIGNED_KEYS = /* @__PURE__ */ new Set(["url", "thumbnail_url", "preview_url"]);
31851
+ var isFileObject = (o) => typeof o.filename === "string" && typeof o.mime_type === "string";
31852
+ var isRemoteUrl = (v) => typeof v === "string" && /^https?:\/\//i.test(v);
31853
+ function createFileRelay(wrapperOrigin, now = Date.now) {
31854
+ const seen = /* @__PURE__ */ new Map();
31855
+ const tokenFor = (url2) => createHash("sha256").update(url2).digest("hex").slice(0, 24);
31856
+ const remember = (url2) => {
31857
+ const token = tokenFor(url2);
31858
+ seen.set(token, { url: url2, at: now() });
31859
+ if (seen.size > MAX_ENTRIES) {
31860
+ const oldest = seen.keys().next();
31861
+ if (!oldest.done) seen.delete(oldest.value);
31862
+ }
31863
+ return token;
31864
+ };
31865
+ const walk2 = (node) => {
31866
+ if (Array.isArray(node)) return node.map(walk2);
31867
+ if (!node || typeof node !== "object") return node;
31868
+ const obj = node;
31869
+ const file2 = isFileObject(obj);
31870
+ const out = {};
31871
+ for (const [key, value] of Object.entries(obj)) {
31872
+ out[key] = file2 && PRESIGNED_KEYS.has(key) && isRemoteUrl(value) ? `${wrapperOrigin}/_file/${remember(value)}` : walk2(value);
31873
+ }
31874
+ return out;
31875
+ };
31876
+ return {
31877
+ rewrite: (result) => walk2(result),
31878
+ destinationFor(token) {
31879
+ const entry = seen.get(token);
31880
+ if (!entry) return null;
31881
+ if (now() - entry.at > TTL_MS) {
31882
+ seen.delete(token);
31883
+ return null;
31884
+ }
31885
+ return entry.url;
31886
+ },
31887
+ size: () => seen.size
31888
+ };
31889
+ }
31890
+
31792
31891
  // src/dev/server.ts
31793
31892
  var DEFAULT_PORT = 5174;
31794
31893
  var DEFAULT_VITE_PORT = 5173;
@@ -31835,6 +31934,24 @@ async function startDevServer(args) {
31835
31934
  }
31836
31935
  };
31837
31936
  process.once("exit", killViteOnExit);
31937
+ const uploads = createUploadRelay();
31938
+ const wrapperOrigin = `http://localhost:${wrapperPort}`;
31939
+ const viteOrigin = `http://localhost:${vitePort}`;
31940
+ const files = createFileRelay(wrapperOrigin);
31941
+ const fileCors = {
31942
+ "Access-Control-Allow-Origin": viteOrigin,
31943
+ "Access-Control-Allow-Headers": "range, content-type",
31944
+ "Access-Control-Expose-Headers": "content-length, content-range, accept-ranges, content-type, content-disposition, etag"
31945
+ };
31946
+ const PASS_THROUGH = [
31947
+ "content-type",
31948
+ "content-length",
31949
+ "content-range",
31950
+ "accept-ranges",
31951
+ "etag",
31952
+ "last-modified",
31953
+ "content-disposition"
31954
+ ];
31838
31955
  const server = http.createServer(async (req, res) => {
31839
31956
  const url2 = req.url ?? "/";
31840
31957
  const pathname = url2.split("?")[0];
@@ -31863,7 +31980,11 @@ async function startDevServer(args) {
31863
31980
  process.stderr.write(`[rpc] ${body.op} ${ms}ms
31864
31981
  `);
31865
31982
  res.writeHead(200, { "Content-Type": "application/json" });
31866
- res.end(serializeRpcResult(result));
31983
+ res.end(
31984
+ serializeRpcResult(
31985
+ body.op === "upload_url" ? uploads.rewriteMint(result) : files.rewrite(result)
31986
+ )
31987
+ );
31867
31988
  } catch (err2) {
31868
31989
  const message = err2 instanceof Error ? err2.message : String(err2);
31869
31990
  process.stderr.write(`[rpc] ERROR ${message}
@@ -31916,12 +32037,115 @@ async function startDevServer(args) {
31916
32037
  }
31917
32038
  return;
31918
32039
  }
32040
+ if (pathname.startsWith("/_file/")) {
32041
+ if (req.method === "OPTIONS") {
32042
+ res.writeHead(204, fileCors);
32043
+ res.end();
32044
+ return;
32045
+ }
32046
+ if (req.method !== "GET" && req.method !== "HEAD") {
32047
+ res.writeHead(405, { ...fileCors, Allow: "GET, HEAD, OPTIONS" });
32048
+ res.end();
32049
+ return;
32050
+ }
32051
+ const token = decodeURIComponent(pathname.slice("/_file/".length));
32052
+ const destination = files.destinationFor(token);
32053
+ if (!destination) {
32054
+ process.stderr.write(`[file] ERROR unknown or expired token ${token}
32055
+ `);
32056
+ res.writeHead(404, { ...fileCors, "Content-Type": "application/json" });
32057
+ res.end(JSON.stringify({ message: "No file for this token" }));
32058
+ return;
32059
+ }
32060
+ try {
32061
+ const range = req.headers.range;
32062
+ const upstream = await fetch(destination, {
32063
+ method: req.method,
32064
+ headers: range ? { Range: range } : void 0
32065
+ });
32066
+ const headers = { ...fileCors };
32067
+ for (const name of PASS_THROUGH) {
32068
+ const value = upstream.headers.get(name);
32069
+ if (value) headers[name] = value;
32070
+ }
32071
+ const size = headers["content-length"] ?? "?";
32072
+ process.stderr.write(`[file] ${token} ${upstream.status} ${size}B
32073
+ `);
32074
+ res.writeHead(upstream.status, headers);
32075
+ if (req.method === "HEAD" || !upstream.body) {
32076
+ res.end();
32077
+ return;
32078
+ }
32079
+ const reader = upstream.body.getReader();
32080
+ for (; ; ) {
32081
+ const { value, done } = await reader.read();
32082
+ if (done) break;
32083
+ res.write(Buffer.from(value));
32084
+ }
32085
+ res.end();
32086
+ } catch (err2) {
32087
+ const message = err2 instanceof Error ? err2.message : String(err2);
32088
+ process.stderr.write(`[file] ERROR ${token} ${message}
32089
+ `);
32090
+ if (!res.headersSent) {
32091
+ res.writeHead(502, { ...fileCors, "Content-Type": "application/json" });
32092
+ res.end(JSON.stringify({ message }));
32093
+ } else {
32094
+ res.end();
32095
+ }
32096
+ }
32097
+ return;
32098
+ }
32099
+ if (req.method === "PUT" && pathname.startsWith("/_upload/")) {
32100
+ const fileId = decodeURIComponent(pathname.slice("/_upload/".length));
32101
+ const destination = uploads.destinationFor(fileId);
32102
+ if (!destination) {
32103
+ process.stderr.write(`[upload] ERROR unknown or expired file_id ${fileId}
32104
+ `);
32105
+ res.writeHead(404, { "Content-Type": "application/json" });
32106
+ res.end(JSON.stringify({ message: `No presigned upload pending for ${fileId}` }));
32107
+ return;
32108
+ }
32109
+ try {
32110
+ const chunks = [];
32111
+ for await (const chunk of req) chunks.push(chunk);
32112
+ const bytes = Buffer.concat(chunks);
32113
+ const startedAt = Date.now();
32114
+ const upstream = await fetch(destination, {
32115
+ method: "PUT",
32116
+ body: bytes,
32117
+ headers: { "Content-Type": req.headers["content-type"] ?? "application/octet-stream" }
32118
+ });
32119
+ const ms = Date.now() - startedAt;
32120
+ if (upstream.ok) {
32121
+ uploads.settle(fileId);
32122
+ process.stderr.write(`[upload] ${fileId} ${bytes.length}B ${ms}ms
32123
+ `);
32124
+ } else {
32125
+ process.stderr.write(`[upload] ERROR ${fileId} storage returned ${upstream.status}
32126
+ `);
32127
+ }
32128
+ res.writeHead(upstream.status);
32129
+ res.end();
32130
+ } catch (err2) {
32131
+ const message = err2 instanceof Error ? err2.message : String(err2);
32132
+ process.stderr.write(`[upload] ERROR ${fileId} ${message}
32133
+ `);
32134
+ if (!res.headersSent) {
32135
+ res.writeHead(502, { "Content-Type": "application/json" });
32136
+ res.end(JSON.stringify({ message }));
32137
+ } else {
32138
+ res.end();
32139
+ }
32140
+ }
32141
+ return;
32142
+ }
31919
32143
  res.writeHead(404, { "Content-Type": "text/plain" });
31920
32144
  res.end("Not Found");
31921
32145
  });
31922
32146
  await new Promise((resolve2, reject2) => {
31923
32147
  server.once("error", reject2);
31924
- server.listen(wrapperPort, () => {
32148
+ server.listen(wrapperPort, "127.0.0.1", () => {
31925
32149
  server.off("error", reject2);
31926
32150
  resolve2();
31927
32151
  });
@@ -32085,7 +32309,7 @@ function inputDeclToTsType(decl) {
32085
32309
  }
32086
32310
  return null;
32087
32311
  }).filter((v) => v !== null);
32088
- const inner = literals.length > 0 ? literals.join(" | ") : "string";
32312
+ const inner = literals.length > 0 ? `${literals.join(" | ")} | (string & {})` : "string";
32089
32313
  return decl.multi === true ? `ReadonlyArray<${inner}>` : inner;
32090
32314
  }
32091
32315
  case "date_range":
@@ -32137,7 +32361,7 @@ function outputDeclToTsType(decl) {
32137
32361
  const literals = options.map(
32138
32362
  (o) => o !== null && typeof o === "object" && "value" in o && typeof o.value === "string" ? JSON.stringify(o.value) : null
32139
32363
  ).filter((v) => v !== null);
32140
- const inner = literals.length > 0 ? literals.join(" | ") : "string";
32364
+ const inner = literals.length > 0 ? `${literals.join(" | ")} | (string & {})` : "string";
32141
32365
  return decl.multi === true ? `ReadonlyArray<${inner}>` : inner;
32142
32366
  }
32143
32367
  case "object": {
@@ -50215,7 +50439,8 @@ async function packageDoctor(client, args) {
50215
50439
  }
50216
50440
  if (health.modified.length === 0) {
50217
50441
  console.error(
50218
- health.is_origin ? " Package artifacts: no changes since the last release." : " Package artifacts: pristine \u2014 no local edits an upgrade would revert."
50442
+ health.is_origin ? ` Package artifacts: no changes since the last release.
50443
+ (schema additions aren't fingerprinted \u2014 preview them with: lotics app release ${app_id})` : " Package artifacts: pristine \u2014 no local edits an upgrade would revert."
50219
50444
  );
50220
50445
  } else if (health.is_origin) {
50221
50446
  console.error(
@@ -50410,6 +50635,56 @@ function formatTemplateResolveHint(entry) {
50410
50635
  const note = entry.baseline_unknown ? " (can't verify the local edit \u2014 older package version; revert overwrites, keep retains)" : " (a local edit \u2014 revert overwrites it with the package's version; keep retains it)";
50411
50636
  return ` --resolve template.${entry.alias}=revert|keep${note}`;
50412
50637
  }
50638
+ async function resolveWorkspaceContentInstallation(client, package_id) {
50639
+ const workspaceId = client.getWorkspaceId();
50640
+ if (!workspaceId) {
50641
+ throw new Error(
50642
+ "No workspace selected. Pass --workspace <ws> (or select one) to operate on its content installation."
50643
+ );
50644
+ }
50645
+ const installations = await client.listContentInstallations(workspaceId);
50646
+ const match = installations.find((inst) => inst.package_id === package_id);
50647
+ if (!match) {
50648
+ throw new Error(
50649
+ `Package ${package_id} is not installed in this workspace \u2014 install it first: lotics install ${package_id}`
50650
+ );
50651
+ }
50652
+ return match;
50653
+ }
50654
+ async function redirectContentPciForm(client, pci_id, verb) {
50655
+ console.error(
50656
+ `Content installations are addressed by their PACKAGE id now \u2014 ${pci_id} is a server-internal resource id.`
50657
+ );
50658
+ const workspaceId = client.getWorkspaceId();
50659
+ const match = workspaceId ? (await client.listContentInstallations(workspaceId)).find((inst) => inst.id === pci_id) : void 0;
50660
+ if (match) {
50661
+ console.error(` Run: lotics ${verb} ${match.package_id}`);
50662
+ } else {
50663
+ console.error(" Find the package id: lotics package list-content");
50664
+ console.error(` Then run: lotics ${verb} <package_id>`);
50665
+ }
50666
+ process.exit(1);
50667
+ }
50668
+ async function packageUpgradeByPackageId(client, args) {
50669
+ const pkg2 = await client.getPackage(args.package_id);
50670
+ if (pkg2.kind === "content") {
50671
+ const installation = await resolveWorkspaceContentInstallation(client, args.package_id);
50672
+ await packageUpgradeKnowledge(client, {
50673
+ installation_id: installation.id,
50674
+ package_id: pkg2.id,
50675
+ package_name: pkg2.name,
50676
+ version: args.version,
50677
+ resolve: args.resolve,
50678
+ bind_to: parseBindToFlags(args.bindTo),
50679
+ applyAll: args.applyAll
50680
+ });
50681
+ return;
50682
+ }
50683
+ await packageFleetUpgrade(client, {
50684
+ package_id: args.package_id,
50685
+ ...args.version !== void 0 ? { version: args.version } : {}
50686
+ });
50687
+ }
50413
50688
  async function packageUpgradeKnowledge(client, args) {
50414
50689
  const preview = await client.previewContentInstallationUpgrade(args.installation_id, {
50415
50690
  ...args.version !== void 0 ? { version: args.version } : {}
@@ -50422,7 +50697,7 @@ async function packageUpgradeKnowledge(client, args) {
50422
50697
  ...args.version !== void 0 ? { version: args.version } : {},
50423
50698
  resolutions: resolutions2
50424
50699
  });
50425
- console.error(`Upgraded ${args.installation_id} \u2192 v${updated.package_version}.`);
50700
+ console.error(`Upgraded ${args.package_name} (${args.package_id}) \u2192 v${updated.package_version}.`);
50426
50701
  };
50427
50702
  if (preview.entries.length === 0 && preview.templates.length === 0) {
50428
50703
  if (preview.to_version === preview.from_version) {
@@ -50494,8 +50769,8 @@ function warnMissingExpectedDocs(missing) {
50494
50769
  }
50495
50770
  async function packageInstall(client, args) {
50496
50771
  const pkg2 = await client.getPackage(args.package_id);
50497
- const kindLabel = pkg2.kind === "content" ? "content package" : "package";
50498
- console.error(`Installing ${pkg2.name} \u2014 ${trustBadge(pkg2)} (${kindLabel})...`);
50772
+ const kindSuffix = pkg2.kind === "content" ? " (content \u2014 docs/templates, no app)" : "";
50773
+ console.error(`Installing ${pkg2.name} \u2014 ${trustBadge(pkg2)}${kindSuffix}...`);
50499
50774
  const result = await client.installPackage(args.package_id, {
50500
50775
  ...args.version !== void 0 ? { version: args.version } : {},
50501
50776
  ...args.bind_to && Object.keys(args.bind_to).length > 0 ? { bind_to: args.bind_to } : {},
@@ -50506,7 +50781,7 @@ async function packageInstall(client, args) {
50506
50781
  const docs = installation.binding.knowledge;
50507
50782
  const templates = installation.binding.templates;
50508
50783
  console.error(
50509
- `Installed ${pkg2.name} v${installation.package_version} \u2192 content installation ${installation.id} (workspace ${installation.workspace_id}).`
50784
+ `Installed ${pkg2.name} v${installation.package_version} (workspace ${installation.workspace_id}).`
50510
50785
  );
50511
50786
  const docAliases = Object.keys(docs);
50512
50787
  if (docAliases.length > 0) {
@@ -50521,8 +50796,8 @@ async function packageInstall(client, args) {
50521
50796
  );
50522
50797
  }
50523
50798
  warnMissingExpectedDocs(warnings.missing_expected_docs);
50524
- console.error(` Upgrade later: lotics upgrade ${installation.id}`);
50525
- console.error(` Uninstall: lotics uninstall ${installation.id} [--keep-content]`);
50799
+ console.error(` Upgrade later: lotics upgrade ${args.package_id}`);
50800
+ console.error(` Uninstall: lotics uninstall ${args.package_id} [--keep-content]`);
50526
50801
  return;
50527
50802
  }
50528
50803
  const { app, knowledge_warnings } = result;
@@ -50535,22 +50810,29 @@ async function packageInstall(client, args) {
50535
50810
  console.error(` Health / uninstall: lotics package doctor ${app.id} \xB7 lotics uninstall ${app.id} [--archive-tables]`);
50536
50811
  }
50537
50812
  async function packageUninstall(client, args) {
50538
- if (args.id.startsWith("pci_")) {
50813
+ if (args.id.startsWith("apg_")) {
50539
50814
  if (args.archive_tables) {
50540
50815
  throw new Error(
50541
- "--archive-tables applies only to an app installation (<app_id>). A content installation (pci_) has no scaffolded tables \u2014 use --keep-content to retain its docs/templates."
50816
+ "--archive-tables applies only to an app installation (<app_id>). A content package has no scaffolded tables \u2014 use --keep-content to retain its docs/templates."
50542
50817
  );
50543
50818
  }
50544
- const result2 = await client.uninstallContentPackage(args.id, {
50819
+ const pkg2 = await client.getPackage(args.id);
50820
+ if (pkg2.kind !== "content") {
50821
+ throw new Error(
50822
+ `Package ${args.id} is an app package \u2014 uninstall an app installation by its app id: lotics uninstall <app_id>.`
50823
+ );
50824
+ }
50825
+ const installation = await resolveWorkspaceContentInstallation(client, args.id);
50826
+ const result2 = await client.uninstallContentPackage(installation.id, {
50545
50827
  keep_content: args.keep_content
50546
50828
  });
50547
50829
  if (args.keep_content) {
50548
50830
  console.error(
50549
- `Uninstalled content installation ${result2.installation_id} \u2014 its docs and templates were kept as ordinary workspace content.`
50831
+ `Uninstalled ${pkg2.name} (${pkg2.id}) \u2014 its docs and templates were kept as ordinary workspace content.`
50550
50832
  );
50551
50833
  } else {
50552
50834
  console.error(
50553
- `Uninstalled content installation ${result2.installation_id} \u2014 archived ${result2.archived_doc_ids.length} doc(s) and ${result2.archived_template_ids.length} template(s).`
50835
+ `Uninstalled ${pkg2.name} (${pkg2.id}) \u2014 archived ${result2.archived_doc_ids.length} doc(s) and ${result2.archived_template_ids.length} template(s).`
50554
50836
  );
50555
50837
  for (const docId of result2.archived_doc_ids) console.error(` ${docId}`);
50556
50838
  for (const templateId of result2.archived_template_ids) console.error(` ${templateId}`);
@@ -50559,7 +50841,7 @@ async function packageUninstall(client, args) {
50559
50841
  }
50560
50842
  if (args.keep_content) {
50561
50843
  throw new Error(
50562
- "--keep-content applies only to a standalone content installation (pci_). An app installation (<app_id>) uses --archive-tables to also archive its scaffolded tables."
50844
+ "--keep-content applies only to a content package (apg_). An app installation (<app_id>) uses --archive-tables to also archive its scaffolded tables."
50563
50845
  );
50564
50846
  }
50565
50847
  const app = await client.getApp(args.id);
@@ -50603,7 +50885,7 @@ async function packageListContent(client) {
50603
50885
  const updateAvailable = inst.package_registry?.update_available ?? false;
50604
50886
  const versionLabel = latest !== void 0 && latest !== inst.package_version ? `v${inst.package_version} \u2192 latest v${latest}` : `v${inst.package_version}`;
50605
50887
  console.error(
50606
- ` ${inst.id} ${name} ${versionLabel}` + (updateAvailable ? " \u2192 update available" : "")
50888
+ ` ${inst.package_id} ${name} ${versionLabel}` + (updateAvailable ? " \u2192 update available" : "")
50607
50889
  );
50608
50890
  }
50609
50891
  }
@@ -50765,18 +51047,18 @@ Re-run with --yes to publish v1:`);
50765
51047
  console.error(` lotics app release ${appId} -m "<what changed>"`);
50766
51048
  console.error(` Install it elsewhere: lotics install ${result.package_id}`);
50767
51049
  }
50768
- function resolveOriginAppId(projectDir, explicit) {
50769
- if (explicit !== void 0 && explicit !== ".") return explicit;
50770
- const local = readLocalAppManifest(projectDir);
50771
- if (local?.app_id) return local.app_id;
50772
- throw new Error(
50773
- "No app id. Run this from a pulled app project (lotics app pull <app_id>), or pass an app id explicitly."
50774
- );
50775
- }
50776
51050
  async function appRelease(client, args) {
50777
51051
  const projectDir = path6.resolve(args.projectDir ?? process.cwd());
50778
- const appId = resolveOriginAppId(projectDir, args.app_id);
50779
- const preview = await client.previewPackageRelease(appId);
51052
+ const local = readLocalAppManifest(projectDir);
51053
+ const explicit = args.app_id !== void 0 && args.app_id !== "." ? args.app_id : void 0;
51054
+ const appId = explicit ?? local?.app_id ?? null;
51055
+ if (appId === null) {
51056
+ throw new Error(
51057
+ "No app id. Run this from a pulled app project (lotics app pull <app_id>), or pass an app id explicitly."
51058
+ );
51059
+ }
51060
+ const knowledge = local && local.app_id === appId && local.knowledge.length > 0 ? local.knowledge : void 0;
51061
+ const preview = await client.previewPackageRelease(appId, { knowledge });
50780
51062
  const { lines, hasError } = formatExtractReport(preview.findings);
50781
51063
  console.error(`Release preview \u2014 ${appId} \u2192 ${preview.package_id} v${preview.version}:`);
50782
51064
  if (preview.added_aliases.length > 0) {
@@ -50785,7 +51067,16 @@ async function appRelease(client, args) {
50785
51067
  if (preview.changed_artifacts.length > 0) {
50786
51068
  console.error(` Changed (${preview.changed_artifacts.length}): ${preview.changed_artifacts.join(", ")}`);
50787
51069
  }
50788
- if (preview.added_aliases.length === 0 && preview.changed_artifacts.length === 0) {
51070
+ const k = preview.knowledge ?? { added: [], removed: [], changed: [] };
51071
+ if (k.added.length > 0 || k.removed.length > 0 || k.changed.length > 0) {
51072
+ const parts = [
51073
+ k.added.length > 0 ? `+${k.added.join(", ")}` : null,
51074
+ k.removed.length > 0 ? `dropped ${k.removed.join(", ")}` : null,
51075
+ k.changed.length > 0 ? `changed ${k.changed.join(", ")}` : null
51076
+ ].filter((p) => p !== null);
51077
+ console.error(` Knowledge: ${parts.join("; ")}`);
51078
+ }
51079
+ if (preview.added_aliases.length === 0 && preview.changed_artifacts.length === 0 && k.added.length === 0 && k.removed.length === 0 && k.changed.length === 0) {
50789
51080
  console.error(" No contract changes since the current version (a fresh code/dist snapshot still ships).");
50790
51081
  }
50791
51082
  if (lines.length > 0) {
@@ -50804,7 +51095,7 @@ Re-run with --yes to publish v${preview.version}:`);
50804
51095
  process.exitCode = 1;
50805
51096
  return;
50806
51097
  }
50807
- const result = await client.releasePackage(appId, { changelog: args.changelog });
51098
+ const result = await client.releasePackage(appId, { changelog: args.changelog, knowledge });
50808
51099
  console.error(`Released ${result.package_id} v${result.version}.`);
50809
51100
  console.error(` The origin was re-pinned to v${result.version} \u2014 verify: lotics package doctor ${appId}`);
50810
51101
  }
@@ -67712,18 +68003,19 @@ COMMANDS
67712
68003
  Install a package (app: scaffolds/deploys/materializes,
67713
68004
  --config sets its knobs; content: installs the doc corpus,
67714
68005
  --bind-to consents to adopt a same-named doc on a collision)
67715
- lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content]
68006
+ lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content]
67716
68007
  Uninstall \u2014 dispatched by id. App (app_id): archives
67717
68008
  artifacts (+ --archive-tables also archives scaffolded
67718
- tables). Content (pci_): archives its package-bound docs
67719
- unless --keep-content
67720
- lotics upgrade <app_id|pci_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all]
68009
+ tables). Content package (apg_): archives its package-bound
68010
+ docs unless --keep-content
68011
+ lotics upgrade <app_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all]
67721
68012
  Preview-then-apply an upgrade, dispatched by id. App
67722
68013
  (app_id): refuses while any drift/modified/bundled-knowledge
67723
- finding lacks a --resolve. Content (pci_): --resolve
67724
- <alias>=apply|keep|archive|recreate|unbind. Package (apg_):
67725
- FLEET \u2014 every installation across your org (clean apply,
67726
- findings skip). --apply-all accepts the package's version
68014
+ finding lacks a --resolve. Package (apg_): a CONTENT package
68015
+ upgrades THIS workspace's install (--resolve
68016
+ <alias>=apply|keep|archive|recreate|unbind); an APP package
68017
+ FLEET-upgrades every installation across your org (clean
68018
+ apply, findings skip). --apply-all accepts the package's version
67727
68019
  lotics package doctor [app_id] Installation health: version pin vs latest, binding
67728
68020
  drift, locally modified core, knowledge drift/edits
67729
68021
  (exit 1 on findings)
@@ -67733,7 +68025,8 @@ COMMANDS
67733
68025
  (re-deploys the pinned source as a bespoke app)
67734
68026
  lotics package show <package_id> Registry metadata + version history
67735
68027
  lotics package list-content List the workspace's content installations
67736
- (standalone content installs) \u2014 source for a pci_ id
68028
+ (standalone content installs) \u2014 each row leads with
68029
+ the package id for upgrade/uninstall
67737
68030
  lotics package yank <package_id> <version> [--undo]
67738
68031
  Refuse new installs/upgrades of a broken published
67739
68032
  version (pinned installations keep running)
@@ -68250,8 +68543,8 @@ async function main() {
68250
68543
  console.error("Usage (low-traffic consumer / registry ops \u2014 author verbs live on `lotics app`):");
68251
68544
  console.error(" The high-traffic consumer verbs are top-level:");
68252
68545
  console.error(" lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
68253
- console.error(" lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content] Uninstall \u2014 dispatched by id (app vs content)");
68254
- console.error(" lotics upgrade <app_id|pci_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Upgrade \u2014 app / content / whole fleet (apg_)");
68546
+ console.error(" lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content] Uninstall \u2014 dispatched by id (app vs content)");
68547
+ console.error(" lotics upgrade <app_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Upgrade \u2014 app install / content install / whole app fleet (apg_)");
68255
68548
  console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding/knowledge drift");
68256
68549
  console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
68257
68550
  console.error(" lotics package eject <app_id> Sever an installation's package link");
@@ -68429,12 +68722,16 @@ Available workspaces:`);
68429
68722
  if (command === "uninstall") {
68430
68723
  const id = subcommand;
68431
68724
  if (!id) {
68432
- console.error("Usage: lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content]");
68725
+ console.error("Usage: lotics uninstall <app_id|package_id> [--archive-tables] [--keep-content]");
68433
68726
  console.error(
68434
- "Dispatched by id: an app installation (app_id; --archive-tables to also archive its scaffolded tables) or a standalone content installation (pci_ id from `lotics install` / lotics package list-content; --keep-content to retain its docs)."
68727
+ "Dispatched by id: an app installation (app_id; --archive-tables to also archive its scaffolded tables) or a content package (apg_ id from `lotics install` / lotics package list-content; --keep-content to retain its docs)."
68435
68728
  );
68436
68729
  process.exit(1);
68437
68730
  }
68731
+ if (id.startsWith("pci_")) {
68732
+ await redirectContentPciForm(client, id, "uninstall");
68733
+ return;
68734
+ }
68438
68735
  await packageUninstall(client, {
68439
68736
  id,
68440
68737
  keep_content: flags.keepContent,
@@ -68446,10 +68743,10 @@ Available workspaces:`);
68446
68743
  const target = subcommand;
68447
68744
  if (!target) {
68448
68745
  console.error(
68449
- "Usage: lotics upgrade <app_id | pci_id | package_id> [--version N] [--resolve <key>=... ...] [--bind-to ...] [--apply-all]"
68746
+ "Usage: lotics upgrade <app_id | package_id> [--version N] [--resolve <key>=... ...] [--bind-to ...] [--apply-all]"
68450
68747
  );
68451
68748
  console.error(
68452
- "Dispatched by id: an app installation (app_id), a standalone content installation (pci_ id), or a package id (apg_ \u2014 fleet-upgrades every installation across your org)."
68749
+ "Dispatched by id: an app installation (app_id), or a package id (apg_) \u2014 a CONTENT package upgrades this workspace's install; an APP package fleet-upgrades every installation across your org."
68453
68750
  );
68454
68751
  process.exit(1);
68455
68752
  }
@@ -68461,16 +68758,16 @@ Available workspaces:`);
68461
68758
  process.exit(1);
68462
68759
  }
68463
68760
  }
68464
- if (target.startsWith("apg_")) {
68465
- await packageFleetUpgrade(client, { package_id: target, version: version2 });
68761
+ if (target.startsWith("pci_")) {
68762
+ await redirectContentPciForm(client, target, "upgrade");
68466
68763
  return;
68467
68764
  }
68468
- if (target.startsWith("pci_")) {
68469
- await packageUpgradeKnowledge(client, {
68470
- installation_id: target,
68765
+ if (target.startsWith("apg_")) {
68766
+ await packageUpgradeByPackageId(client, {
68767
+ package_id: target,
68471
68768
  version: version2,
68472
68769
  resolve: flags.resolve,
68473
- bind_to: parseBindToFlags(flags.bindTo),
68770
+ bindTo: flags.bindTo,
68474
68771
  applyAll: flags.applyAll
68475
68772
  });
68476
68773
  return;
@@ -68541,8 +68838,8 @@ Available workspaces:`);
68541
68838
  }
68542
68839
  const movedVerbs = {
68543
68840
  install: "lotics install <package_id>",
68544
- uninstall: "lotics uninstall <app_id|pci_id>",
68545
- upgrade: "lotics upgrade <app_id|pci_id|package_id>",
68841
+ uninstall: "lotics uninstall <app_id|package_id>",
68842
+ upgrade: "lotics upgrade <app_id|package_id>",
68546
68843
  "fleet-upgrade": "lotics upgrade <package_id>"
68547
68844
  };
68548
68845
  if (subcommand !== void 0 && subcommand in movedVerbs) {
@@ -68829,8 +69126,6 @@ ${JSON.stringify(info.input_schema, null, 2)}`);
68829
69126
  console.log(JSON.stringify({ error: result.error }, null, 2));
68830
69127
  } else {
68831
69128
  console.error(result.error);
68832
- console.error(`
68833
- Hint: run "lotics tools ${toolName}" to see the expected input schema.`);
68834
69129
  }
68835
69130
  process.exit(1);
68836
69131
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.83.0",
3
+ "version": "0.86.1",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {