@lotics/cli 0.71.0 → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -12243,12 +12243,12 @@ var require_tokens = __commonJS({
12243
12243
  });
12244
12244
  }
12245
12245
  exports2.assignCategoriesMapProp = assignCategoriesMapProp;
12246
- function singleAssignCategoriesToksMap(path8, nextNode) {
12247
- utils_1.forEach(path8, function(pathNode) {
12246
+ function singleAssignCategoriesToksMap(path9, nextNode) {
12247
+ utils_1.forEach(path9, function(pathNode) {
12248
12248
  nextNode.categoryMatchesMap[pathNode.tokenTypeIdx] = true;
12249
12249
  });
12250
12250
  utils_1.forEach(nextNode.CATEGORIES, function(nextCategory) {
12251
- var newPath = path8.concat(nextNode);
12251
+ var newPath = path9.concat(nextNode);
12252
12252
  if (!utils_1.contains(newPath, nextCategory)) {
12253
12253
  singleAssignCategoriesToksMap(newPath, nextCategory);
12254
12254
  }
@@ -14011,10 +14011,10 @@ var require_interpreter = __commonJS({
14011
14011
  /** @class */
14012
14012
  (function(_super) {
14013
14013
  __extends(AbstractNextPossibleTokensWalker2, _super);
14014
- function AbstractNextPossibleTokensWalker2(topProd, path8) {
14014
+ function AbstractNextPossibleTokensWalker2(topProd, path9) {
14015
14015
  var _this = _super.call(this) || this;
14016
14016
  _this.topProd = topProd;
14017
- _this.path = path8;
14017
+ _this.path = path9;
14018
14018
  _this.possibleTokTypes = [];
14019
14019
  _this.nextProductionName = "";
14020
14020
  _this.nextProductionOccurrence = 0;
@@ -14068,9 +14068,9 @@ var require_interpreter = __commonJS({
14068
14068
  /** @class */
14069
14069
  (function(_super) {
14070
14070
  __extends(NextAfterTokenWalker2, _super);
14071
- function NextAfterTokenWalker2(topProd, path8) {
14072
- var _this = _super.call(this, topProd, path8) || this;
14073
- _this.path = path8;
14071
+ function NextAfterTokenWalker2(topProd, path9) {
14072
+ var _this = _super.call(this, topProd, path9) || this;
14073
+ _this.path = path9;
14074
14074
  _this.nextTerminalName = "";
14075
14075
  _this.nextTerminalOccurrence = 0;
14076
14076
  _this.nextTerminalName = _this.path.lastTok.name;
@@ -14767,10 +14767,10 @@ var require_lookahead = __commonJS({
14767
14767
  }
14768
14768
  return result;
14769
14769
  }
14770
- function pathToHashKeys(path8) {
14770
+ function pathToHashKeys(path9) {
14771
14771
  var keys2 = [""];
14772
- for (var i2 = 0; i2 < path8.length; i2++) {
14773
- var tokType = path8[i2];
14772
+ for (var i2 = 0; i2 < path9.length; i2++) {
14773
+ var tokType = path9[i2];
14774
14774
  var longerKeys = [];
14775
14775
  for (var j = 0; j < keys2.length; j++) {
14776
14776
  var currShorterKey = keys2[j];
@@ -15082,9 +15082,9 @@ var require_checks = __commonJS({
15082
15082
  return errors;
15083
15083
  }
15084
15084
  exports2.validateRuleIsOverridden = validateRuleIsOverridden;
15085
- function validateNoLeftRecursion(topRule, currRule, errMsgProvider, path8) {
15086
- if (path8 === void 0) {
15087
- path8 = [];
15085
+ function validateNoLeftRecursion(topRule, currRule, errMsgProvider, path9) {
15086
+ if (path9 === void 0) {
15087
+ path9 = [];
15088
15088
  }
15089
15089
  var errors = [];
15090
15090
  var nextNonTerminals = getFirstNoneTerminal(currRule.definition);
@@ -15097,15 +15097,15 @@ var require_checks = __commonJS({
15097
15097
  errors.push({
15098
15098
  message: errMsgProvider.buildLeftRecursionError({
15099
15099
  topLevelRule: topRule,
15100
- leftRecursionPath: path8
15100
+ leftRecursionPath: path9
15101
15101
  }),
15102
15102
  type: parser_1.ParserDefinitionErrorType.LEFT_RECURSION,
15103
15103
  ruleName
15104
15104
  });
15105
15105
  }
15106
- var validNextSteps = utils.difference(nextNonTerminals, path8.concat([topRule]));
15106
+ var validNextSteps = utils.difference(nextNonTerminals, path9.concat([topRule]));
15107
15107
  var errorsFromNextSteps = utils.map(validNextSteps, function(currRefRule) {
15108
- var newPath = utils.cloneArr(path8);
15108
+ var newPath = utils.cloneArr(path9);
15109
15109
  newPath.push(currRefRule);
15110
15110
  return validateNoLeftRecursion(topRule, currRefRule, errMsgProvider, newPath);
15111
15111
  });
@@ -22714,8 +22714,8 @@ var require_utils4 = __commonJS({
22714
22714
  var result = transform[inputType][outputType](input);
22715
22715
  return result;
22716
22716
  };
22717
- exports2.resolve = function(path8) {
22718
- var parts = path8.split("/");
22717
+ exports2.resolve = function(path9) {
22718
+ var parts = path9.split("/");
22719
22719
  var result = [];
22720
22720
  for (var index = 0; index < parts.length; index++) {
22721
22721
  var part = parts[index];
@@ -28568,18 +28568,18 @@ var require_object = __commonJS({
28568
28568
  var object = new ZipObject(name, zipObjectContent, o);
28569
28569
  this.files[name] = object;
28570
28570
  };
28571
- var parentFolder = function(path8) {
28572
- if (path8.slice(-1) === "/") {
28573
- path8 = path8.substring(0, path8.length - 1);
28571
+ var parentFolder = function(path9) {
28572
+ if (path9.slice(-1) === "/") {
28573
+ path9 = path9.substring(0, path9.length - 1);
28574
28574
  }
28575
- var lastSlash = path8.lastIndexOf("/");
28576
- return lastSlash > 0 ? path8.substring(0, lastSlash) : "";
28575
+ var lastSlash = path9.lastIndexOf("/");
28576
+ return lastSlash > 0 ? path9.substring(0, lastSlash) : "";
28577
28577
  };
28578
- var forceTrailingSlash = function(path8) {
28579
- if (path8.slice(-1) !== "/") {
28580
- path8 += "/";
28578
+ var forceTrailingSlash = function(path9) {
28579
+ if (path9.slice(-1) !== "/") {
28580
+ path9 += "/";
28581
28581
  }
28582
- return path8;
28582
+ return path9;
28583
28583
  };
28584
28584
  var folderAdd = function(name, createFolders) {
28585
28585
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults2.createFolders;
@@ -29581,8 +29581,8 @@ var require_lib3 = __commonJS({
29581
29581
  // src/cli.ts
29582
29582
  import dns from "node:dns";
29583
29583
  import net2 from "node:net";
29584
- import fs8 from "node:fs";
29585
- import path7 from "node:path";
29584
+ import fs9 from "node:fs";
29585
+ import path8 from "node:path";
29586
29586
  import readline from "node:readline";
29587
29587
 
29588
29588
  // src/client.ts
@@ -29680,8 +29680,8 @@ var LoticsClient = class {
29680
29680
  }
29681
29681
  return headers;
29682
29682
  }
29683
- async request(method, path8, body) {
29684
- const url = `${this.baseUrl}${path8}`;
29683
+ async request(method, path9, body) {
29684
+ const url = `${this.baseUrl}${path9}`;
29685
29685
  const headers = this.buildHeaders();
29686
29686
  const init = { method, headers };
29687
29687
  if (body !== void 0) {
@@ -29698,9 +29698,22 @@ var LoticsClient = class {
29698
29698
  setWorkspaceId(id) {
29699
29699
  this.workspaceId = id;
29700
29700
  }
29701
+ /** The workspace id the client targets (the `x-workspace-id` header), if resolved. */
29702
+ getWorkspaceId() {
29703
+ return this.workspaceId;
29704
+ }
29701
29705
  async listWorkspaces() {
29702
29706
  return this.request("GET", "/v1/workspaces");
29703
29707
  }
29708
+ /**
29709
+ * Resolve one workspace's info by id from the org's workspace list (the only
29710
+ * API-key-accessible source carrying `is_dev`). Returns null when the
29711
+ * workspace isn't visible to these credentials.
29712
+ */
29713
+ async getWorkspaceInfo(id) {
29714
+ const workspaces = await this.listWorkspaces();
29715
+ return workspaces.find((w) => w.id === id) ?? null;
29716
+ }
29704
29717
  async createWorkspace(body) {
29705
29718
  return this.request("POST", "/v1/workspaces", body);
29706
29719
  }
@@ -29749,6 +29762,175 @@ var LoticsClient = class {
29749
29762
  async createApp(body) {
29750
29763
  return this.request("POST", "/v1/apps", body);
29751
29764
  }
29765
+ /**
29766
+ * Install an app package version into the current workspace — scaffolds the
29767
+ * data model, deploys the package bundle, materializes its
29768
+ * queries/workflows/agents, and pins the installation. Returns the resulting
29769
+ * installation app. `version` omitted installs the latest published version.
29770
+ */
29771
+ async installAppPackage(package_id, body) {
29772
+ return this.request(
29773
+ "POST",
29774
+ `/v1/app-packages/${encodeURIComponent(package_id)}/installations`,
29775
+ body
29776
+ );
29777
+ }
29778
+ /**
29779
+ * Eject an installation from its package — re-deploy the pinned version's
29780
+ * source as a workspace-owned app version, then sever the package link
29781
+ * (clears `package_id`/`package_version`, stamps `ejected_at`). One-way: the
29782
+ * app becomes a normal bespoke app and can no longer be upgraded. Returns the
29783
+ * resulting app.
29784
+ */
29785
+ async ejectAppPackage(app_id) {
29786
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/eject`);
29787
+ }
29788
+ /**
29789
+ * Extract a DRAFT app package from an existing bespoke app — the promotion
29790
+ * read (docs/app_packages.md § Promotion). Pure: nothing is written. Returns
29791
+ * the alias-keyed draft `contract` (opaque to the CLI — the server is the
29792
+ * validating authority), the origin workspace's `binding` (which doubles as
29793
+ * the adopt binding), a findings `report` (any `error` ⇒ not publishable
29794
+ * as-is), and the file-backed `template_files` the CLI must stage into the
29795
+ * project at their `bytes_ref` paths. Backs `lotics package extract`.
29796
+ * Admin-only.
29797
+ */
29798
+ async extractAppPackage(app_id) {
29799
+ return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-extract`);
29800
+ }
29801
+ /**
29802
+ * Adopt a published package onto an EXISTING (bespoke or ejected) app — the
29803
+ * final promotion step. The workspace already holds the concrete objects, so
29804
+ * nothing is scaffolded or rewritten: the server verifies the `binding` is
29805
+ * complete, live, and FAITHFUL to the version's contract, then writes only the
29806
+ * installation pin (the app becomes installation #1, upgradeable again). A
29807
+ * ConflictError names the aliases that diverge. `version` omitted adopts the
29808
+ * latest. Backs `lotics package adopt`. Admin-only.
29809
+ */
29810
+ async adoptAppPackage(app_id, body) {
29811
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-adopt`, body);
29812
+ }
29813
+ /**
29814
+ * Fleet upgrade — bring every installation of a package across the CALLER'S
29815
+ * org to the target version (latest when omitted) in one call. Hands-off
29816
+ * applies only where the preview is clean; installations with breaking/
29817
+ * drift/modified-core findings are skipped and reported for the normal
29818
+ * per-installation consent flow. Backs `lotics package fleet-upgrade`.
29819
+ * Admin-only; org-scoped (no workspace header needed).
29820
+ */
29821
+ async fleetUpgradeAppPackage(package_id, body) {
29822
+ return this.request(
29823
+ "POST",
29824
+ `/v1/app-packages/${encodeURIComponent(package_id)}/fleet-upgrade`,
29825
+ body
29826
+ );
29827
+ }
29828
+ // --- App packages (registry authoring + dev harness) ---
29829
+ /**
29830
+ * Create a registry app package — the Lotics-owned, workspace-agnostic
29831
+ * blueprint. `lotics package publish` calls this on first publish (when the
29832
+ * local manifest has no `package_id`), then publishes version 1 against the
29833
+ * returned id. Admin-only.
29834
+ */
29835
+ async createAppPackage(body) {
29836
+ return this.request("POST", "/v1/app-packages", body);
29837
+ }
29838
+ /**
29839
+ * Fetch a registry app package's metadata (incl. `latest_version` and the
29840
+ * Lotics-backed `is_official` trust badge). Admin-only; cross-tenant by id.
29841
+ */
29842
+ async getAppPackage(package_id) {
29843
+ return this.request("GET", `/v1/app-packages/${encodeURIComponent(package_id)}`);
29844
+ }
29845
+ /**
29846
+ * Publish a new immutable package version — multipart upload of the alias-keyed
29847
+ * contract (JSON) + the prebuilt code bundle (a gzipped tarball carrying
29848
+ * `source.tar.gz` + `dist.tar.gz` members). The server validates the contract +
29849
+ * bundle, then allocates the next monotonic version. The `contract` is opaque
29850
+ * JSON to the transport (the server is the validating authority). Admin-only.
29851
+ */
29852
+ async publishAppPackageVersion(package_id, args) {
29853
+ const formData = new FormData();
29854
+ formData.append("contract", JSON.stringify(args.contract));
29855
+ formData.append(
29856
+ "bundle",
29857
+ new Blob([new Uint8Array(args.bundle)], { type: "application/gzip" }),
29858
+ "bundle.tar.gz"
29859
+ );
29860
+ if (args.changelog) formData.append("changelog", args.changelog);
29861
+ const url = `${this.baseUrl}/v1/app-packages/${encodeURIComponent(package_id)}/versions`;
29862
+ const response = await fetch(url, {
29863
+ method: "POST",
29864
+ headers: this.buildHeaders(),
29865
+ // no Content-Type — fetch sets the multipart boundary
29866
+ body: formData
29867
+ });
29868
+ if (!response.ok) await this.throwResponseError(response);
29869
+ return response.json();
29870
+ }
29871
+ /**
29872
+ * Upgrade a package installation to a newer published version — extends the
29873
+ * binding additively, re-materializes the target version's
29874
+ * queries/workflows/agents (preserving workspace overlay), prunes dropped
29875
+ * package artifacts, and bumps the pin. `version` omitted upgrades to the
29876
+ * latest. Returns the resulting app. Admin-only.
29877
+ */
29878
+ async upgradeAppPackage(app_id, body) {
29879
+ return this.request(
29880
+ "POST",
29881
+ `/v1/apps/${encodeURIComponent(app_id)}/package-upgrade`,
29882
+ body
29883
+ );
29884
+ }
29885
+ /**
29886
+ * Preview a package upgrade — the additive plan, informational removals,
29887
+ * breaking contract changes, the binding drift report, and the modified-core
29888
+ * report — with no writes. Admin-only.
29889
+ */
29890
+ async previewAppPackageUpgrade(app_id, opts = {}) {
29891
+ const query = opts.version !== void 0 ? `?version=${opts.version}` : "";
29892
+ return this.request(
29893
+ "GET",
29894
+ `/v1/apps/${encodeURIComponent(app_id)}/package-upgrade${query}`
29895
+ );
29896
+ }
29897
+ /**
29898
+ * Re-bind a package role to a different workspace group (the current group
29899
+ * still exists). Re-materializes at the pinned version; refuses over
29900
+ * modified-core findings. Admin-only.
29901
+ */
29902
+ async rebindAppPackageRole(app_id, body) {
29903
+ return this.request(
29904
+ "POST",
29905
+ `/v1/apps/${encodeURIComponent(app_id)}/package-rebind-role`,
29906
+ body
29907
+ );
29908
+ }
29909
+ /**
29910
+ * Workspace-wide dangling-reference sweep — active app/workflow artifacts
29911
+ * whose prefixed schema ids no longer resolve. Backs
29912
+ * `lotics workspace doctor`. Admin-only.
29913
+ */
29914
+ async getWorkspaceDanglingReferences() {
29915
+ return this.request("GET", "/v1/workspaces/dangling-references");
29916
+ }
29917
+ /**
29918
+ * Health check for a package installation — version pin vs. registry latest,
29919
+ * binding drift, and locally modified core artifacts. Read-only; backs
29920
+ * `lotics package doctor`. Admin-only.
29921
+ */
29922
+ async getAppPackageHealth(app_id) {
29923
+ return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-health`);
29924
+ }
29925
+ /**
29926
+ * Reset a package installation in a DEV workspace — drop the package-owned
29927
+ * scaffolded tables and re-scaffold clean. Hard-gated server-side to dev
29928
+ * workspaces (a non-dev workspace is refused). Returns the resulting app.
29929
+ * Admin-only.
29930
+ */
29931
+ async resetAppPackage(app_id) {
29932
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-reset`);
29933
+ }
29752
29934
  /**
29753
29935
  * Resolve the display name + fields (incl. select options) of the given tables
29754
29936
  * — the schema `lotics app codegen` turns into the runtime `.lotics/app_fields.ts`
@@ -31112,10 +31294,11 @@ async function dispatchRpc(client, body, opts) {
31112
31294
  }
31113
31295
  switch (body.op) {
31114
31296
  case "context": {
31115
- const who = await client.whoami();
31297
+ const [who, app] = await Promise.all([client.whoami(), client.getApp(body.app_id)]);
31116
31298
  return {
31117
31299
  member_id: who.member_id,
31118
- comments_enabled: opts?.commentsEnabled ?? false
31300
+ comments_enabled: opts?.commentsEnabled ?? false,
31301
+ config: app.config ?? {}
31119
31302
  };
31120
31303
  }
31121
31304
  case "query": {
@@ -31995,6 +32178,7 @@ function walk(node, visit) {
31995
32178
  case "sort":
31996
32179
  case "limit":
31997
32180
  case "unpivot":
32181
+ case "unnest":
31998
32182
  walk(node.from, visit);
31999
32183
  return;
32000
32184
  case "join":
@@ -33162,6 +33346,724 @@ function escapeRegExp(s) {
33162
33346
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33163
33347
  }
33164
33348
 
33349
+ // src/package_commands.ts
33350
+ import fs6 from "node:fs";
33351
+ import path7 from "node:path";
33352
+ import { tmpdir as tmpdir2 } from "node:os";
33353
+
33354
+ // src/file_command_io.ts
33355
+ import fs5 from "node:fs";
33356
+ import path6 from "node:path";
33357
+ var CliError = class extends Error {
33358
+ constructor(message) {
33359
+ super(message);
33360
+ this.name = "CliError";
33361
+ }
33362
+ };
33363
+ function fail(message) {
33364
+ throw new CliError(message);
33365
+ }
33366
+ function writeFileAtomic(filePath, bytes) {
33367
+ const dir = path6.dirname(path6.resolve(filePath));
33368
+ const tmp = path6.join(dir, `.${path6.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
33369
+ fs5.writeFileSync(tmp, bytes);
33370
+ try {
33371
+ fs5.renameSync(tmp, filePath);
33372
+ } catch (error) {
33373
+ try {
33374
+ fs5.unlinkSync(tmp);
33375
+ } catch {
33376
+ }
33377
+ throw error;
33378
+ }
33379
+ console.error(`Wrote ${filePath}`);
33380
+ }
33381
+
33382
+ // src/package_commands.ts
33383
+ var CONTRACT_FILE = "contract.json";
33384
+ var ADOPT_BINDING_FILE = "adopt_binding.json";
33385
+ function packageJsonPath(projectDir) {
33386
+ return path7.join(projectDir, "package.json");
33387
+ }
33388
+ function isPlainObject(value) {
33389
+ return typeof value === "object" && value !== null && !Array.isArray(value);
33390
+ }
33391
+ function readPackageProject(projectDir) {
33392
+ const pkgPath2 = packageJsonPath(projectDir);
33393
+ if (!fs6.existsSync(pkgPath2)) {
33394
+ throw new Error(
33395
+ `No package.json in ${projectDir}. Run this inside a package project (lotics package new <name>).`
33396
+ );
33397
+ }
33398
+ const parsed = JSON.parse(fs6.readFileSync(pkgPath2, "utf-8"));
33399
+ if (!isPlainObject(parsed)) throw new Error(`Malformed package.json in ${projectDir}.`);
33400
+ const lotics = parsed.lotics;
33401
+ const pkg2 = isPlainObject(lotics) ? lotics.package : void 0;
33402
+ if (!isPlainObject(pkg2) || typeof pkg2.name !== "string") {
33403
+ throw new Error(
33404
+ `${pkgPath2} has no lotics.package manifest \u2014 not a package project. Use "lotics package new <name>".`
33405
+ );
33406
+ }
33407
+ const dev = {};
33408
+ if (isPlainObject(pkg2.dev)) {
33409
+ for (const [ws, entry] of Object.entries(pkg2.dev)) {
33410
+ if (isPlainObject(entry) && typeof entry.app_id === "string" && typeof entry.version === "number") {
33411
+ dev[ws] = { app_id: entry.app_id, version: entry.version };
33412
+ }
33413
+ }
33414
+ }
33415
+ const manifest = {
33416
+ id: typeof pkg2.id === "string" ? pkg2.id : null,
33417
+ name: pkg2.name,
33418
+ description: typeof pkg2.description === "string" ? pkg2.description : null,
33419
+ version: typeof pkg2.version === "number" ? pkg2.version : null,
33420
+ dev
33421
+ };
33422
+ return { pkgJson: parsed, manifest };
33423
+ }
33424
+ function writePackageManifest(projectDir, project) {
33425
+ const next = {
33426
+ ...project.pkgJson,
33427
+ lotics: {
33428
+ ...isPlainObject(project.pkgJson.lotics) ? project.pkgJson.lotics : {},
33429
+ package: project.manifest
33430
+ }
33431
+ };
33432
+ writeFileAtomic(
33433
+ packageJsonPath(projectDir),
33434
+ new TextEncoder().encode(JSON.stringify(next, null, 2) + "\n")
33435
+ );
33436
+ }
33437
+ function sanitizePackageJsonForSource(pkgJson) {
33438
+ const lotics = isPlainObject(pkgJson.lotics) ? pkgJson.lotics : {};
33439
+ const pkg2 = isPlainObject(lotics.package) ? lotics.package : {};
33440
+ const { dev: _dev, ...pkgWithoutDev } = pkg2;
33441
+ return {
33442
+ ...pkgJson,
33443
+ lotics: { ...lotics, package: pkgWithoutDev }
33444
+ };
33445
+ }
33446
+ function draftPackageProjectFromApp(appPkgJson, args) {
33447
+ const { lotics: _appManifest, ...rest } = appPkgJson;
33448
+ return {
33449
+ pkgJson: rest,
33450
+ manifest: { id: null, name: args.name, description: args.description, version: null, dev: {} }
33451
+ };
33452
+ }
33453
+ function parseAdoptBindingFile(raw, expectedAppId) {
33454
+ if (!isPlainObject(raw) || typeof raw.app_id !== "string" || typeof raw.workspace_id !== "string" || !isPlainObject(raw.binding)) {
33455
+ throw new Error(
33456
+ "Malformed .lotics/adopt_binding.json \u2014 expected { app_id, workspace_id, binding }."
33457
+ );
33458
+ }
33459
+ if (raw.app_id !== expectedAppId) {
33460
+ throw new Error(
33461
+ `.lotics/adopt_binding.json records app ${raw.app_id}, but you are adopting ${expectedAppId}. A binding maps one app's concrete ids and must never be applied to another \u2014 run "lotics package extract ${expectedAppId}" to produce the right pin.`
33462
+ );
33463
+ }
33464
+ return {
33465
+ app_id: raw.app_id,
33466
+ workspace_id: raw.workspace_id,
33467
+ binding: raw.binding
33468
+ };
33469
+ }
33470
+ function formatExtractReport(report) {
33471
+ const order = ["error", "warning", "info"];
33472
+ const lines = order.flatMap(
33473
+ (severity) => report.filter((f) => f.severity === severity).map((f) => ` [${f.severity}] ${f.area}: ${f.message}`)
33474
+ );
33475
+ return { lines, hasError: report.some((f) => f.severity === "error") };
33476
+ }
33477
+ function readContract(projectDir) {
33478
+ const contractPath = path7.join(projectDir, CONTRACT_FILE);
33479
+ if (!fs6.existsSync(contractPath)) {
33480
+ throw new Error(
33481
+ `No ${CONTRACT_FILE} in ${projectDir}. A package project declares its data model there.`
33482
+ );
33483
+ }
33484
+ return JSON.parse(fs6.readFileSync(contractPath, "utf-8"));
33485
+ }
33486
+ var SOURCE_STAGE_EXCLUDES = /* @__PURE__ */ new Set([
33487
+ "node_modules",
33488
+ "dist",
33489
+ ".lotics",
33490
+ ".git",
33491
+ "bundle.tar.gz",
33492
+ "package.json"
33493
+ ]);
33494
+ function stagePackageSource(projectDir, sourceStage) {
33495
+ const project = readPackageProject(projectDir);
33496
+ fs6.mkdirSync(sourceStage, { recursive: true });
33497
+ for (const entry of fs6.readdirSync(projectDir)) {
33498
+ if (SOURCE_STAGE_EXCLUDES.has(entry) || entry.endsWith(".tsbuildinfo")) continue;
33499
+ fs6.cpSync(path7.join(projectDir, entry), path7.join(sourceStage, entry), { recursive: true });
33500
+ }
33501
+ fs6.writeFileSync(
33502
+ path7.join(sourceStage, "package.json"),
33503
+ JSON.stringify(sanitizePackageJsonForSource(project.pkgJson), null, 2) + "\n"
33504
+ );
33505
+ }
33506
+ async function buildPackageBundle(projectDir) {
33507
+ console.error("Building...");
33508
+ await runNpm(["run", "build"], projectDir);
33509
+ const distDir = path7.join(projectDir, "dist");
33510
+ if (!fs6.existsSync(distDir)) {
33511
+ throw new Error(
33512
+ `Build did not produce a dist/ directory in ${projectDir}. Check that 'npm run build' is configured correctly.`
33513
+ );
33514
+ }
33515
+ const stage = fs6.mkdtempSync(path7.join(tmpdir2(), "lotics-pkg-"));
33516
+ const bundlePath = path7.join(tmpdir2(), `lotics-bundle-${Date.now()}.tar.gz`);
33517
+ try {
33518
+ console.error("Packaging source...");
33519
+ const sourceStage = path7.join(stage, "source");
33520
+ stagePackageSource(projectDir, sourceStage);
33521
+ await runTar(["-czf", path7.join(stage, "source.tar.gz"), "-C", sourceStage, "."], projectDir);
33522
+ console.error("Packaging dist...");
33523
+ await runTar(["-czf", path7.join(stage, "dist.tar.gz"), "-C", distDir, "."], projectDir);
33524
+ console.error("Bundling...");
33525
+ await runTar(["-czf", bundlePath, "source.tar.gz", "dist.tar.gz"], stage);
33526
+ return fs6.readFileSync(bundlePath);
33527
+ } finally {
33528
+ fs6.rmSync(stage, { recursive: true, force: true });
33529
+ if (fs6.existsSync(bundlePath)) fs6.unlinkSync(bundlePath);
33530
+ }
33531
+ }
33532
+ function starterContract(name) {
33533
+ return {
33534
+ entities: [
33535
+ {
33536
+ alias: "item",
33537
+ label: name,
33538
+ description: `Records managed by the ${name} package.`,
33539
+ fields: [
33540
+ { alias: "name", label: "Name", type: "text", required: true },
33541
+ { alias: "notes", label: "Notes", type: "text" },
33542
+ {
33543
+ alias: "status",
33544
+ label: "Status",
33545
+ type: "select",
33546
+ options: [
33547
+ { alias: "open", label: "Open", color: "blue" },
33548
+ { alias: "done", label: "Done", color: "green" }
33549
+ ]
33550
+ }
33551
+ ]
33552
+ }
33553
+ ],
33554
+ roles: [],
33555
+ templates: [],
33556
+ queries: [
33557
+ {
33558
+ alias: "items",
33559
+ ast: {
33560
+ kind: "from_table",
33561
+ from_entity: "item",
33562
+ sort: [{ field_key: "name", order: "asc" }]
33563
+ }
33564
+ }
33565
+ ],
33566
+ workflows: [],
33567
+ agents: [],
33568
+ config: [
33569
+ { alias: "heading", label: "List heading", type: "text", default: name }
33570
+ ]
33571
+ };
33572
+ }
33573
+ async function packageNew(args) {
33574
+ const targetPath = path7.resolve(args.targetPath ?? appDirName(args.name));
33575
+ if (fs6.existsSync(targetPath) && fs6.readdirSync(targetPath).length > 0) {
33576
+ throw new Error(`Target directory ${targetPath} is not empty.`);
33577
+ }
33578
+ fs6.mkdirSync(targetPath, { recursive: true });
33579
+ const files = buildStarterTemplate({
33580
+ app_name: args.name,
33581
+ app_id: "",
33582
+ workspace_id: ""
33583
+ });
33584
+ for (const file of files) {
33585
+ const fullPath = path7.join(targetPath, file.path);
33586
+ if (file.path === "package.json") {
33587
+ const pkg2 = JSON.parse(file.content);
33588
+ const manifest = {
33589
+ id: null,
33590
+ name: args.name,
33591
+ description: null,
33592
+ version: null,
33593
+ dev: {}
33594
+ };
33595
+ pkg2.lotics = { package: manifest };
33596
+ fs6.mkdirSync(path7.dirname(fullPath), { recursive: true });
33597
+ fs6.writeFileSync(fullPath, JSON.stringify(pkg2, null, 2) + "\n");
33598
+ continue;
33599
+ }
33600
+ fs6.mkdirSync(path7.dirname(fullPath), { recursive: true });
33601
+ fs6.writeFileSync(fullPath, file.content);
33602
+ }
33603
+ fs6.writeFileSync(
33604
+ path7.join(targetPath, CONTRACT_FILE),
33605
+ JSON.stringify(starterContract(args.name), null, 2) + "\n"
33606
+ );
33607
+ console.error(`Scaffolded ${files.length + 1} files into ${targetPath}`);
33608
+ console.error("Installing npm dependencies...");
33609
+ await runNpm(["install"], targetPath);
33610
+ console.error(`
33611
+ Ready. Next steps:`);
33612
+ console.error(` cd ${path7.relative(process.cwd(), targetPath) || "."}`);
33613
+ console.error(` # edit contract.json + src/App.tsx, then run it against a dev workspace:`);
33614
+ console.error(` lotics workspace create "<name> dev" --dev`);
33615
+ console.error(` lotics package dev --workspace <dev_ws>`);
33616
+ }
33617
+ async function packageBuild(args) {
33618
+ const projectDir = path7.resolve(args.projectDir ?? process.cwd());
33619
+ readPackageProject(projectDir);
33620
+ const bundle = await buildPackageBundle(projectDir);
33621
+ const out = path7.join(projectDir, "bundle.tar.gz");
33622
+ fs6.writeFileSync(out, bundle);
33623
+ console.error(`Built ${out} (${(bundle.byteLength / 1024).toFixed(1)} KB)`);
33624
+ }
33625
+ async function publishVersion(client, projectDir, opts) {
33626
+ const project = readPackageProject(projectDir);
33627
+ const contract = readContract(projectDir);
33628
+ let packageId = project.manifest.id;
33629
+ if (packageId === null) {
33630
+ const pkg2 = await client.createAppPackage({
33631
+ name: project.manifest.name,
33632
+ description: project.manifest.description
33633
+ });
33634
+ packageId = pkg2.id;
33635
+ project.manifest.id = packageId;
33636
+ writePackageManifest(projectDir, project);
33637
+ console.error(`Created package ${pkg2.name} (${pkg2.id}).`);
33638
+ }
33639
+ const bundle = await buildPackageBundle(projectDir);
33640
+ console.error("Publishing version...");
33641
+ const version = await client.publishAppPackageVersion(packageId, {
33642
+ contract,
33643
+ bundle,
33644
+ changelog: opts.changelog ?? null
33645
+ });
33646
+ project.manifest.version = version.version;
33647
+ writePackageManifest(projectDir, project);
33648
+ return { package_id: packageId, version: version.version };
33649
+ }
33650
+ async function packagePublish(client, args) {
33651
+ const projectDir = path7.resolve(args.projectDir ?? process.cwd());
33652
+ const { package_id, version } = await publishVersion(client, projectDir, {
33653
+ changelog: args.changelog
33654
+ });
33655
+ console.error(`Published ${package_id} v${version}.`);
33656
+ console.error(` Install it: lotics package install ${package_id} --version ${version}`);
33657
+ }
33658
+ function assertDevWorkspace(workspace) {
33659
+ if (workspace.is_dev === true) return;
33660
+ throw new Error(
33661
+ `Workspace "${workspace.name}" (${workspace.id}) is not a dev workspace. "lotics package dev"/"sync" scaffold package tables into the target workspace, so it must be a throwaway dev workspace. Create one with "lotics workspace create <name> --dev" and pass --workspace <dev_ws>.`
33662
+ );
33663
+ }
33664
+ async function syncToDevWorkspace(client, projectDir) {
33665
+ const devWorkspaceId = client.getWorkspaceId();
33666
+ if (!devWorkspaceId) {
33667
+ throw new Error(
33668
+ "No dev workspace selected. Pass --workspace <dev_ws> (a workspace created with --dev)."
33669
+ );
33670
+ }
33671
+ const workspace = await client.getWorkspaceInfo(devWorkspaceId);
33672
+ if (!workspace) {
33673
+ throw new Error(
33674
+ `Workspace ${devWorkspaceId} is not accessible with these credentials. Pass --workspace <dev_ws> (a workspace created with --dev).`
33675
+ );
33676
+ }
33677
+ assertDevWorkspace(workspace);
33678
+ const { package_id, version } = await publishVersion(client, projectDir, {
33679
+ changelog: "dev sync"
33680
+ });
33681
+ const project = readPackageProject(projectDir);
33682
+ const existing = project.manifest.dev[devWorkspaceId];
33683
+ let appId;
33684
+ if (existing) {
33685
+ console.error(`Upgrading dev installation ${existing.app_id} \u2192 v${version}...`);
33686
+ const app = await client.upgradeAppPackage(existing.app_id, { version });
33687
+ appId = app.id;
33688
+ } else {
33689
+ console.error(`Installing ${package_id} v${version} into dev workspace ${devWorkspaceId}...`);
33690
+ const app = await client.installAppPackage(package_id, { version });
33691
+ appId = app.id;
33692
+ }
33693
+ project.manifest.dev[devWorkspaceId] = { app_id: appId, version };
33694
+ writePackageManifest(projectDir, project);
33695
+ const installed = await client.getApp(appId);
33696
+ return { app_id: appId, app_name: installed.name, workspace_id: devWorkspaceId, version };
33697
+ }
33698
+ async function packageSync(client, args) {
33699
+ const projectDir = path7.resolve(args.projectDir ?? process.cwd());
33700
+ const result = await syncToDevWorkspace(client, projectDir);
33701
+ console.error(
33702
+ `Synced ${result.app_name} v${result.version} \u2192 ${result.app_id} (workspace ${result.workspace_id}).`
33703
+ );
33704
+ }
33705
+ async function packageDev(client, args) {
33706
+ const projectDir = path7.resolve(args.projectDir ?? process.cwd());
33707
+ const synced = await syncToDevWorkspace(client, projectDir);
33708
+ const handle = await startDevServer({
33709
+ projectDir,
33710
+ app_id: synced.app_id,
33711
+ app_name: synced.app_name,
33712
+ workspace_id: synced.workspace_id,
33713
+ api_url: client.baseUrl,
33714
+ port: args.port,
33715
+ vitePort: args.vitePort,
33716
+ client
33717
+ });
33718
+ await handle.ready;
33719
+ const url = `http://localhost:${handle.port}`;
33720
+ console.error(`
33721
+ lotics package dev`);
33722
+ console.error(` package: ${synced.app_name} (dev installation ${synced.app_id} v${synced.version})`);
33723
+ console.error(` workspace: ${synced.workspace_id} (dev)`);
33724
+ if (client.viewAsMemberId) {
33725
+ console.error(` view as: ${client.viewAsMemberId}`);
33726
+ }
33727
+ console.error(` vite: http://localhost:${handle.vitePort}/`);
33728
+ console.error(` open: ${url}`);
33729
+ console.error(` rpc: ${client.baseUrl} (via Bearer API key)
33730
+ `);
33731
+ console.error(` Edit contract.json then re-run "lotics package dev" / "lotics package sync" to migrate.`);
33732
+ console.error(` Ctrl-C to stop.
33733
+ `);
33734
+ openBrowser(url);
33735
+ await new Promise((resolve2) => {
33736
+ const onSig = () => {
33737
+ process.off("SIGINT", onSig);
33738
+ process.off("SIGTERM", onSig);
33739
+ resolve2();
33740
+ };
33741
+ process.on("SIGINT", onSig);
33742
+ process.on("SIGTERM", onSig);
33743
+ });
33744
+ console.error("\nStopping\u2026");
33745
+ await handle.stop();
33746
+ }
33747
+ async function packageReset(client, args) {
33748
+ const projectDir = path7.resolve(args.projectDir ?? process.cwd());
33749
+ const devWorkspaceId = client.getWorkspaceId();
33750
+ if (!devWorkspaceId) {
33751
+ throw new Error(
33752
+ "No dev workspace selected. Pass --workspace <dev_ws> (a workspace created with --dev)."
33753
+ );
33754
+ }
33755
+ const { manifest } = readPackageProject(projectDir);
33756
+ const pin = manifest.dev[devWorkspaceId];
33757
+ if (!pin) {
33758
+ throw new Error(
33759
+ `No dev installation recorded for workspace ${devWorkspaceId}. Run "lotics package dev --workspace ${devWorkspaceId}" first.`
33760
+ );
33761
+ }
33762
+ console.error(`Resetting dev installation ${pin.app_id} in workspace ${devWorkspaceId}...`);
33763
+ const app = await client.resetAppPackage(pin.app_id);
33764
+ console.error(`Reset ${app.name} \u2192 ${app.id}. The scaffolded tables were dropped and re-created clean.`);
33765
+ }
33766
+ function resolveInstallationAppId(client, explicit) {
33767
+ if (explicit) return explicit;
33768
+ const workspaceId = client.getWorkspaceId();
33769
+ if (!workspaceId) {
33770
+ throw new Error("Pass an app id (lotics package doctor <app_id>) or select a workspace.");
33771
+ }
33772
+ let manifest;
33773
+ try {
33774
+ ({ manifest } = readPackageProject(path7.resolve(process.cwd())));
33775
+ } catch {
33776
+ throw new Error(
33777
+ "No app id given and the current directory is not a package project. Pass one explicitly: lotics package doctor <app_id>."
33778
+ );
33779
+ }
33780
+ const pin = manifest.dev[workspaceId];
33781
+ if (!pin) {
33782
+ throw new Error(
33783
+ `No app id given and no dev installation recorded for workspace ${workspaceId}. Pass one explicitly: lotics package doctor <app_id>.`
33784
+ );
33785
+ }
33786
+ return pin.app_id;
33787
+ }
33788
+ function parseResolveFlags(resolve2) {
33789
+ const resolutions = {};
33790
+ for (const entry of resolve2) {
33791
+ const eq = entry.indexOf("=");
33792
+ if (eq <= 0 || eq === entry.length - 1) {
33793
+ throw new Error(
33794
+ `Invalid --resolve "${entry}" \u2014 expected <key>=recreate|revert|keep or <key>=<existing_id>.`
33795
+ );
33796
+ }
33797
+ const key = entry.slice(0, eq);
33798
+ const value = entry.slice(eq + 1);
33799
+ resolutions[key] = value === "recreate" || value === "revert" || value === "keep" ? value : { bind_to: value };
33800
+ }
33801
+ return resolutions;
33802
+ }
33803
+ async function packageDoctor(client, args) {
33804
+ const app_id = resolveInstallationAppId(client, args.app_id);
33805
+ const health = await client.getAppPackageHealth(app_id);
33806
+ console.error(`${health.package_name} \u2014 installation ${health.app_id}`);
33807
+ console.error(
33808
+ ` Installed: v${health.installed_version} Latest: v${health.latest_version}` + (health.update_available ? " \u2192 update available" : "")
33809
+ );
33810
+ if (health.drift.length === 0) {
33811
+ console.error(" Binding: healthy \u2014 every bound alias resolves.");
33812
+ } else {
33813
+ console.error(` Binding drift (${health.drift.length}):`);
33814
+ for (const d of health.drift) {
33815
+ console.error(` - ${d.namespace}.${d.alias} \u2192 ${d.id} (missing from the workspace)`);
33816
+ }
33817
+ console.error(
33818
+ ` Resolve while upgrading:
33819
+ lotics package upgrade ${app_id} --resolve <namespace.alias>=recreate (or =<existing_id> to re-point)`
33820
+ );
33821
+ process.exitCode = 1;
33822
+ }
33823
+ if (health.modified.length === 0) {
33824
+ console.error(" Package artifacts: pristine \u2014 no local edits an upgrade would revert.");
33825
+ } else {
33826
+ console.error(` Locally modified package artifacts (${health.modified.length}):`);
33827
+ for (const m of health.modified) {
33828
+ console.error(` - ${m.kind}.${m.alias} (an upgrade overwrites this unless kept)`);
33829
+ }
33830
+ console.error(
33831
+ ` Consent while upgrading:
33832
+ lotics package upgrade ${app_id} --resolve <kind.alias>=revert (or =keep to retain the edit)`
33833
+ );
33834
+ process.exitCode = 1;
33835
+ }
33836
+ if (health.update_available) {
33837
+ console.error(` Upgrade: lotics package upgrade ${app_id}`);
33838
+ }
33839
+ }
33840
+ async function packageUpgrade(client, args) {
33841
+ const preview = await client.previewAppPackageUpgrade(args.app_id, {
33842
+ ...args.version !== void 0 ? { version: args.version } : {}
33843
+ });
33844
+ console.error(
33845
+ `Upgrade v${preview.from_version} \u2192 v${preview.to_version}` + (preview.changelog ? ` \u2014 ${preview.changelog}` : "")
33846
+ );
33847
+ const summarize = (sets) => Object.entries(sets).filter(([, entries]) => entries.length > 0).map(([kind, entries]) => `${entries.length} ${kind}`).join(", ");
33848
+ const added = summarize(preview.diff.added);
33849
+ const removed = summarize(preview.diff.removed);
33850
+ if (added) console.error(` Adds: ${added}`);
33851
+ if (removed) console.error(` Unbinds (workspace data kept): ${removed}`);
33852
+ if (preview.diff.breaking.length > 0) {
33853
+ console.error(
33854
+ " Breaking contract changes \u2014 NOT resolvable via --resolve (a bound alias's shape changed):"
33855
+ );
33856
+ for (const b of preview.diff.breaking) {
33857
+ console.error(` - ${b.entity}.${b.alias} (${b.kind}): ${b.from} \u2192 ${b.to}`);
33858
+ }
33859
+ console.error(
33860
+ ` There is no in-flow resolution for this class. The package author must publish a version
33861
+ that keeps these aliases' shape stable, or eject this app (severing the package link)
33862
+ before making the change: lotics package eject ${args.app_id}`
33863
+ );
33864
+ process.exit(1);
33865
+ }
33866
+ const unresolvedDrift = preview.drift.filter(
33867
+ (d) => args.resolutions[`${d.namespace}.${d.alias}`] === void 0
33868
+ );
33869
+ const unresolvedModified = preview.modified.filter(
33870
+ (m) => args.resolutions[`${m.kind}.${m.alias}`] === void 0
33871
+ );
33872
+ if (unresolvedDrift.length > 0 || unresolvedModified.length > 0) {
33873
+ if (unresolvedDrift.length > 0) {
33874
+ console.error(" Binding drift must be resolved before upgrading:");
33875
+ for (const d of unresolvedDrift) {
33876
+ console.error(
33877
+ ` --resolve ${d.namespace}.${d.alias}=recreate (or =<existing_id> to re-point; was ${d.id})`
33878
+ );
33879
+ }
33880
+ }
33881
+ if (unresolvedModified.length > 0) {
33882
+ console.error(" Locally modified package artifacts need consent before upgrading:");
33883
+ for (const m of unresolvedModified) {
33884
+ console.error(
33885
+ ` --resolve ${m.kind}.${m.alias}=revert (overwrite the local edit; or =keep to retain it)`
33886
+ );
33887
+ }
33888
+ }
33889
+ process.exit(1);
33890
+ }
33891
+ const app = await client.upgradeAppPackage(args.app_id, {
33892
+ ...args.version !== void 0 ? { version: args.version } : {},
33893
+ ...Object.keys(args.resolutions).length > 0 ? { resolutions: args.resolutions } : {}
33894
+ });
33895
+ console.error(`Upgraded ${app.name} \u2192 v${app.package_version} (${app.id}).`);
33896
+ }
33897
+ async function packageInstall(client, args) {
33898
+ const pkg2 = await client.getAppPackage(args.package_id);
33899
+ console.error(
33900
+ pkg2.is_official ? `Installing ${pkg2.name} \u2014 official Lotics package...` : `Installing ${pkg2.name} \u2014 third-party package (runs under your authority once installed)...`
33901
+ );
33902
+ const app = await client.installAppPackage(args.package_id, {
33903
+ ...args.version !== void 0 ? { version: args.version } : {}
33904
+ });
33905
+ const versionLabel = app.package_version !== null ? `v${app.package_version}` : "(unknown version)";
33906
+ console.error(
33907
+ `Installed ${app.name} ${versionLabel} \u2192 ${app.id} (workspace ${app.workspace_id}).`
33908
+ );
33909
+ console.error(" The data model, queries, workflows, and agents are live.");
33910
+ console.error(" Pull it for local editing: lotics app pull " + app.id);
33911
+ }
33912
+ async function packageEject(client, args) {
33913
+ const app = await client.ejectAppPackage(args.app_id);
33914
+ console.error(`Ejected ${app.name} \u2192 ${app.id} (workspace ${app.workspace_id}).`);
33915
+ console.error(" The package link is severed \u2014 it's now a normal bespoke app and can no longer be upgraded.");
33916
+ console.error(" Its data model, queries, workflows, and templates are unchanged.");
33917
+ console.error(" Pull the pinned source for local editing: lotics app pull " + app.id);
33918
+ }
33919
+ async function packageExtract(client, args) {
33920
+ const app = await client.getApp(args.app_id);
33921
+ if (!app.current_version_id) {
33922
+ throw new Error(
33923
+ `App ${app.id} has no deployed version \u2014 extract stages its source archive, so deploy it first.`
33924
+ );
33925
+ }
33926
+ const extracted = await client.extractAppPackage(args.app_id);
33927
+ const { lines, hasError } = formatExtractReport(extracted.report);
33928
+ if (lines.length > 0) {
33929
+ console.error(`Extraction report (${extracted.report.length}):`);
33930
+ for (const line of lines) console.error(line);
33931
+ } else {
33932
+ console.error("Extraction report: no findings.");
33933
+ }
33934
+ const targetPath = path7.resolve(args.targetPath ?? appDirName(app.name));
33935
+ if (fs6.existsSync(targetPath) && fs6.readdirSync(targetPath).length > 0) {
33936
+ throw new Error(`Target directory ${targetPath} is not empty.`);
33937
+ }
33938
+ fs6.mkdirSync(targetPath, { recursive: true });
33939
+ const version = await client.getAppVersion(app.id, app.current_version_id);
33940
+ const sourceUrl = await client.getAppVersionSourceUrl(app.id, version.id);
33941
+ const tmpFile = path7.join(tmpdir2(), `lotics-extract-${app.id}-${Date.now()}.tar.gz`);
33942
+ console.error("Downloading source archive...");
33943
+ await client.downloadFile(sourceUrl, tmpFile);
33944
+ try {
33945
+ console.error(`Extracting to ${targetPath}...`);
33946
+ await runTar(["-xzf", tmpFile, "-C", targetPath], targetPath);
33947
+ } finally {
33948
+ if (fs6.existsSync(tmpFile)) fs6.unlinkSync(tmpFile);
33949
+ }
33950
+ const appPkgJson = JSON.parse(
33951
+ fs6.readFileSync(packageJsonPath(targetPath), "utf-8")
33952
+ );
33953
+ writePackageManifest(
33954
+ targetPath,
33955
+ draftPackageProjectFromApp(appPkgJson, { name: app.name, description: null })
33956
+ );
33957
+ fs6.writeFileSync(
33958
+ path7.join(targetPath, CONTRACT_FILE),
33959
+ JSON.stringify(extracted.contract, null, 2) + "\n"
33960
+ );
33961
+ console.error(`Wrote ${CONTRACT_FILE}`);
33962
+ for (const tf of extracted.template_files) {
33963
+ const dest = path7.join(targetPath, tf.bytes_ref);
33964
+ fs6.mkdirSync(path7.dirname(dest), { recursive: true });
33965
+ const { path: downloaded } = await client.downloadFileById(tf.file_id, path7.dirname(dest));
33966
+ if (path7.resolve(downloaded) !== path7.resolve(dest)) fs6.renameSync(downloaded, dest);
33967
+ console.error(`Wrote ${tf.bytes_ref}`);
33968
+ }
33969
+ const dotLotics = path7.join(targetPath, ".lotics");
33970
+ fs6.mkdirSync(dotLotics, { recursive: true });
33971
+ fs6.writeFileSync(
33972
+ path7.join(dotLotics, ADOPT_BINDING_FILE),
33973
+ JSON.stringify(
33974
+ { app_id: app.id, workspace_id: app.workspace_id, binding: extracted.binding },
33975
+ null,
33976
+ 2
33977
+ ) + "\n"
33978
+ );
33979
+ console.error(`Wrote .lotics/${ADOPT_BINDING_FILE}`);
33980
+ console.error("Installing npm dependencies...");
33981
+ await runNpm(["install"], targetPath);
33982
+ if (hasError) {
33983
+ console.error(
33984
+ "\nExtraction produced error findings \u2014 the draft is written but NOT publishable as-is."
33985
+ );
33986
+ console.error(
33987
+ "Fix the reported items above, then publish (which re-validates the contract)."
33988
+ );
33989
+ process.exitCode = 1;
33990
+ return;
33991
+ }
33992
+ console.error("\nDraft package project written. Next steps:");
33993
+ console.error(` cd ${path7.relative(process.cwd(), targetPath) || "."}`);
33994
+ console.error(" # review the aliases in contract.json, then:");
33995
+ console.error(` lotics package publish -m "v1"`);
33996
+ console.error(` lotics package adopt ${app.id} (from this project, same workspace)`);
33997
+ }
33998
+ async function packageAdopt(client, args) {
33999
+ const projectDir = path7.resolve(args.projectDir ?? process.cwd());
34000
+ const { manifest } = readPackageProject(projectDir);
34001
+ if (manifest.id === null) {
34002
+ throw new Error(
34003
+ "This package project has never been published (no package id). Publish first:\n lotics package publish"
34004
+ );
34005
+ }
34006
+ const version = args.version ?? manifest.version;
34007
+ if (version === null) {
34008
+ throw new Error(
34009
+ "No version to adopt \u2014 publish this project first (lotics package publish) or pass --version N."
34010
+ );
34011
+ }
34012
+ const bindingPath = path7.join(projectDir, ".lotics", ADOPT_BINDING_FILE);
34013
+ if (!fs6.existsSync(bindingPath)) {
34014
+ throw new Error(
34015
+ `No .lotics/${ADOPT_BINDING_FILE} in ${projectDir}. Adopt binds the origin app that "lotics package extract" recorded \u2014 run extract to produce this project.`
34016
+ );
34017
+ }
34018
+ const pin = parseAdoptBindingFile(
34019
+ JSON.parse(fs6.readFileSync(bindingPath, "utf-8")),
34020
+ args.app_id
34021
+ );
34022
+ const app = await client.adoptAppPackage(args.app_id, {
34023
+ package_id: manifest.id,
34024
+ version,
34025
+ binding: pin.binding
34026
+ });
34027
+ console.error(
34028
+ `Adopted ${app.name} \u2192 installation of ${app.package_id} v${app.package_version}.`
34029
+ );
34030
+ console.error(` Verify the installation: lotics package doctor ${app.id}`);
34031
+ }
34032
+ async function packageFleetUpgrade(client, args) {
34033
+ const result = await client.fleetUpgradeAppPackage(args.package_id, {
34034
+ ...args.version !== void 0 ? { version: args.version } : {}
34035
+ });
34036
+ console.error(`Fleet upgrade of ${result.package_id} \u2192 v${result.target_version}:`);
34037
+ if (result.installations.length === 0) {
34038
+ console.error(" No installations of this package in your organization.");
34039
+ return;
34040
+ }
34041
+ const counts = { upgraded: 0, up_to_date: 0, skipped: 0, failed: 0 };
34042
+ for (const inst of result.installations) {
34043
+ counts[inst.outcome]++;
34044
+ const from = inst.from_version === null ? "?" : `v${inst.from_version}`;
34045
+ const line = ` [${inst.outcome}] ${inst.workspace_name} \u2014 ${inst.app_name} (${from} \u2192 v${result.target_version})`;
34046
+ if (inst.outcome === "skipped" && inst.blockers) {
34047
+ console.error(
34048
+ `${line}: breaking=${inst.blockers.breaking} drift=${inst.blockers.drift} modified=${inst.blockers.modified}`
34049
+ );
34050
+ console.error(
34051
+ ` resolve via: lotics package upgrade ${inst.app_id} --version ${result.target_version} ...`
34052
+ );
34053
+ } else if (inst.message) {
34054
+ console.error(`${line}: ${inst.message}`);
34055
+ } else {
34056
+ console.error(line);
34057
+ }
34058
+ }
34059
+ console.error(
34060
+ `Done: ${counts.upgraded} upgraded, ${counts.up_to_date} already current, ${counts.skipped} skipped, ${counts.failed} failed.`
34061
+ );
34062
+ if (counts.skipped > 0 || counts.failed > 0) {
34063
+ process.exitCode = 1;
34064
+ }
34065
+ }
34066
+
33165
34067
  // src/args.ts
33166
34068
  function parseArgs(argv) {
33167
34069
  const flags = {
@@ -33178,9 +34080,12 @@ function parseArgs(argv) {
33178
34080
  message: void 0,
33179
34081
  local: false,
33180
34082
  all: false,
34083
+ dev: false,
33181
34084
  yes: false,
33182
34085
  printCreated: false,
33183
34086
  cleanup: false,
34087
+ packageVersion: void 0,
34088
+ resolve: [],
33184
34089
  version: false,
33185
34090
  help: false
33186
34091
  };
@@ -33231,6 +34136,9 @@ function parseArgs(argv) {
33231
34136
  case "--local":
33232
34137
  flags.local = true;
33233
34138
  break;
34139
+ case "--dev":
34140
+ flags.dev = true;
34141
+ break;
33234
34142
  case "--all":
33235
34143
  flags.all = true;
33236
34144
  break;
@@ -33245,10 +34153,32 @@ function parseArgs(argv) {
33245
34153
  case "--cleanup":
33246
34154
  flags.cleanup = true;
33247
34155
  break;
34156
+ case "--resolve": {
34157
+ const value = argv[++i2];
34158
+ if (value === void 0 || value.startsWith("-")) {
34159
+ throw new Error(
34160
+ "--resolve requires a value: <key>=recreate|revert|keep or <key>=<existing_id> (drift: <namespace.alias>; modified core: <kind>.<alias>)."
34161
+ );
34162
+ }
34163
+ flags.resolve.push(value);
34164
+ break;
34165
+ }
33248
34166
  case "--version":
33249
- case "-v":
33250
- flags.version = true;
34167
+ case "-v": {
34168
+ const next = argv[i2 + 1];
34169
+ if (command === "package") {
34170
+ if (next === void 0 || next.startsWith("-")) {
34171
+ throw new Error(
34172
+ "--version requires a version number for package commands (e.g. --version 2)."
34173
+ );
34174
+ }
34175
+ flags.packageVersion = next;
34176
+ i2++;
34177
+ } else {
34178
+ flags.version = true;
34179
+ }
33251
34180
  break;
34181
+ }
33252
34182
  case "--help":
33253
34183
  case "-h":
33254
34184
  flags.help = true;
@@ -33299,35 +34229,7 @@ async function ingestJsonArgs(opts) {
33299
34229
  }
33300
34230
 
33301
34231
  // src/xlsx.ts
33302
- import fs6 from "node:fs";
33303
-
33304
- // src/file_command_io.ts
33305
- import fs5 from "node:fs";
33306
- import path6 from "node:path";
33307
- var CliError = class extends Error {
33308
- constructor(message) {
33309
- super(message);
33310
- this.name = "CliError";
33311
- }
33312
- };
33313
- function fail(message) {
33314
- throw new CliError(message);
33315
- }
33316
- function writeFileAtomic(filePath, bytes) {
33317
- const dir = path6.dirname(path6.resolve(filePath));
33318
- const tmp = path6.join(dir, `.${path6.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
33319
- fs5.writeFileSync(tmp, bytes);
33320
- try {
33321
- fs5.renameSync(tmp, filePath);
33322
- } catch (error) {
33323
- try {
33324
- fs5.unlinkSync(tmp);
33325
- } catch {
33326
- }
33327
- throw error;
33328
- }
33329
- console.error(`Wrote ${filePath}`);
33330
- }
34232
+ import fs7 from "node:fs";
33331
34233
 
33332
34234
  // ../xlsx/node_modules/fast-xml-parser/src/util.js
33333
34235
  var nameStartChar = ":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
@@ -36104,16 +37006,16 @@ var MatcherView = class {
36104
37006
  * @returns {string|undefined}
36105
37007
  */
36106
37008
  getCurrentTag() {
36107
- const path8 = this._matcher.path;
36108
- return path8.length > 0 ? path8[path8.length - 1].tag : void 0;
37009
+ const path9 = this._matcher.path;
37010
+ return path9.length > 0 ? path9[path9.length - 1].tag : void 0;
36109
37011
  }
36110
37012
  /**
36111
37013
  * Get current namespace.
36112
37014
  * @returns {string|undefined}
36113
37015
  */
36114
37016
  getCurrentNamespace() {
36115
- const path8 = this._matcher.path;
36116
- return path8.length > 0 ? path8[path8.length - 1].namespace : void 0;
37017
+ const path9 = this._matcher.path;
37018
+ return path9.length > 0 ? path9[path9.length - 1].namespace : void 0;
36117
37019
  }
36118
37020
  /**
36119
37021
  * Get current node's attribute value.
@@ -36121,9 +37023,9 @@ var MatcherView = class {
36121
37023
  * @returns {*}
36122
37024
  */
36123
37025
  getAttrValue(attrName) {
36124
- const path8 = this._matcher.path;
36125
- if (path8.length === 0) return void 0;
36126
- return path8[path8.length - 1].values?.[attrName];
37026
+ const path9 = this._matcher.path;
37027
+ if (path9.length === 0) return void 0;
37028
+ return path9[path9.length - 1].values?.[attrName];
36127
37029
  }
36128
37030
  /**
36129
37031
  * Check if current node has an attribute.
@@ -36131,9 +37033,9 @@ var MatcherView = class {
36131
37033
  * @returns {boolean}
36132
37034
  */
36133
37035
  hasAttr(attrName) {
36134
- const path8 = this._matcher.path;
36135
- if (path8.length === 0) return false;
36136
- const current = path8[path8.length - 1];
37036
+ const path9 = this._matcher.path;
37037
+ if (path9.length === 0) return false;
37038
+ const current = path9[path9.length - 1];
36137
37039
  return current.values !== void 0 && attrName in current.values;
36138
37040
  }
36139
37041
  /**
@@ -36141,18 +37043,18 @@ var MatcherView = class {
36141
37043
  * @returns {number}
36142
37044
  */
36143
37045
  getPosition() {
36144
- const path8 = this._matcher.path;
36145
- if (path8.length === 0) return -1;
36146
- return path8[path8.length - 1].position ?? 0;
37046
+ const path9 = this._matcher.path;
37047
+ if (path9.length === 0) return -1;
37048
+ return path9[path9.length - 1].position ?? 0;
36147
37049
  }
36148
37050
  /**
36149
37051
  * Get current node's repeat counter (occurrence count of this tag name).
36150
37052
  * @returns {number}
36151
37053
  */
36152
37054
  getCounter() {
36153
- const path8 = this._matcher.path;
36154
- if (path8.length === 0) return -1;
36155
- return path8[path8.length - 1].counter ?? 0;
37055
+ const path9 = this._matcher.path;
37056
+ if (path9.length === 0) return -1;
37057
+ return path9[path9.length - 1].counter ?? 0;
36156
37058
  }
36157
37059
  /**
36158
37060
  * Get current node's sibling index (alias for getPosition).
@@ -41608,10 +42510,10 @@ function resolveRelativePath(basePath, relativePath) {
41608
42510
  }
41609
42511
  return resolved.join("/");
41610
42512
  }
41611
- function findZipEntry(zipEntries, path8) {
41612
- if (zipEntries[path8]) return zipEntries[path8];
41613
- if (zipEntries[`xl/${path8}`]) return zipEntries[`xl/${path8}`];
41614
- const noSlash = path8.replace(/^\//, "");
42513
+ function findZipEntry(zipEntries, path9) {
42514
+ if (zipEntries[path9]) return zipEntries[path9];
42515
+ if (zipEntries[`xl/${path9}`]) return zipEntries[`xl/${path9}`];
42516
+ const noSlash = path9.replace(/^\//, "");
41615
42517
  if (zipEntries[noSlash]) return zipEntries[noSlash];
41616
42518
  return void 0;
41617
42519
  }
@@ -43334,9 +44236,9 @@ function parseExcelFromZip(zip, options) {
43334
44236
  }
43335
44237
  return result;
43336
44238
  }
43337
- function findEntry(zip, path8) {
43338
- if (zip[path8]) return zip[path8];
43339
- const lower = path8.toLowerCase();
44239
+ function findEntry(zip, path9) {
44240
+ if (zip[path9]) return zip[path9];
44241
+ const lower = path9.toLowerCase();
43340
44242
  for (const key of Object.keys(zip)) {
43341
44243
  if (key.toLowerCase() === lower) return zip[key];
43342
44244
  }
@@ -43947,12 +44849,12 @@ function extractPivotRoundTripInfo(originalZip) {
43947
44849
  if (paths.length > 0) pivotTablesBySheetIndex.set(i2, paths);
43948
44850
  }
43949
44851
  const pivotXmlPaths = [];
43950
- for (const path8 of Object.keys(originalZip)) {
43951
- if (path8.startsWith("xl/pivotTables/") && path8.endsWith(".xml")) {
43952
- pivotXmlPaths.push(path8);
44852
+ for (const path9 of Object.keys(originalZip)) {
44853
+ if (path9.startsWith("xl/pivotTables/") && path9.endsWith(".xml")) {
44854
+ pivotXmlPaths.push(path9);
43953
44855
  }
43954
- if (path8.startsWith("xl/pivotCache/") && path8.endsWith(".xml")) {
43955
- pivotXmlPaths.push(path8);
44856
+ if (path9.startsWith("xl/pivotCache/") && path9.endsWith(".xml")) {
44857
+ pivotXmlPaths.push(path9);
43956
44858
  }
43957
44859
  }
43958
44860
  return { cachesByWorkbook, pivotTablesBySheetIndex, pivotXmlPaths };
@@ -43960,15 +44862,15 @@ function extractPivotRoundTripInfo(originalZip) {
43960
44862
  function decode(bytes) {
43961
44863
  return new TextDecoder().decode(bytes);
43962
44864
  }
43963
- function pivotContentTypeFor(path8) {
43964
- if (path8.startsWith("xl/pivotTables/") && path8.endsWith(".xml")) {
43965
- return `<Override PartName="/${path8}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"/>`;
44865
+ function pivotContentTypeFor(path9) {
44866
+ if (path9.startsWith("xl/pivotTables/") && path9.endsWith(".xml")) {
44867
+ return `<Override PartName="/${path9}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"/>`;
43966
44868
  }
43967
- if (path8.includes("/pivotCacheDefinition") && path8.endsWith(".xml")) {
43968
- return `<Override PartName="/${path8}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"/>`;
44869
+ if (path9.includes("/pivotCacheDefinition") && path9.endsWith(".xml")) {
44870
+ return `<Override PartName="/${path9}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"/>`;
43969
44871
  }
43970
- if (path8.includes("/pivotCacheRecords") && path8.endsWith(".xml")) {
43971
- return `<Override PartName="/${path8}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"/>`;
44872
+ if (path9.includes("/pivotCacheRecords") && path9.endsWith(".xml")) {
44873
+ return `<Override PartName="/${path9}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"/>`;
43972
44874
  }
43973
44875
  return void 0;
43974
44876
  }
@@ -44040,13 +44942,13 @@ function exportWorkbook(workbook, originalZip) {
44040
44942
  regeneratedPaths.add(`xl/worksheets/sheet${i2 + 1}.xml`);
44041
44943
  regeneratedPaths.add(`xl/worksheets/_rels/sheet${i2 + 1}.xml.rels`);
44042
44944
  }
44043
- for (const path8 of Object.keys(originalZip)) {
44044
- if (path8.startsWith("xl/drawings/") || path8.startsWith("xl/charts/") || path8.startsWith("xl/tables/") || path8.startsWith("xl/media/")) {
44045
- regeneratedPaths.add(path8);
44945
+ for (const path9 of Object.keys(originalZip)) {
44946
+ if (path9.startsWith("xl/drawings/") || path9.startsWith("xl/charts/") || path9.startsWith("xl/tables/") || path9.startsWith("xl/media/")) {
44947
+ regeneratedPaths.add(path9);
44046
44948
  }
44047
44949
  }
44048
- for (const [path8, data] of Object.entries(originalZip)) {
44049
- if (!regeneratedPaths.has(path8)) entries[path8] = data;
44950
+ for (const [path9, data] of Object.entries(originalZip)) {
44951
+ if (!regeneratedPaths.has(path9)) entries[path9] = data;
44050
44952
  }
44051
44953
  }
44052
44954
  const sharedStrings = buildSharedStrings(workbook);
@@ -44061,8 +44963,8 @@ function exportWorkbook(workbook, originalZip) {
44061
44963
  buildWorkbookRels(workbook, pivotInfo)
44062
44964
  );
44063
44965
  const extraContentTypes = [];
44064
- for (const path8 of pivotInfo.pivotXmlPaths) {
44065
- const ct = pivotContentTypeFor(path8);
44966
+ for (const path9 of pivotInfo.pivotXmlPaths) {
44967
+ const ct = pivotContentTypeFor(path9);
44066
44968
  if (ct) extraContentTypes.push(ct);
44067
44969
  }
44068
44970
  let globalChartIndex = 1;
@@ -45823,14 +46725,14 @@ var DependencyGraph = class {
45823
46725
  stack.push({ cell: p, path: [cell, p] });
45824
46726
  }
45825
46727
  while (stack.length > 0) {
45826
- const { cell: current, path: path8 } = stack.pop();
45827
- if (current === cell) return path8;
46728
+ const { cell: current, path: path9 } = stack.pop();
46729
+ if (current === cell) return path9;
45828
46730
  if (visited.has(current)) continue;
45829
46731
  visited.add(current);
45830
46732
  const nextPrec = this.precedents.get(current);
45831
46733
  if (!nextPrec) continue;
45832
46734
  for (const p of nextPrec) {
45833
- stack.push({ cell: p, path: [...path8, p] });
46735
+ stack.push({ cell: p, path: [...path9, p] });
45834
46736
  }
45835
46737
  }
45836
46738
  return null;
@@ -46821,8 +47723,8 @@ function adjustMergedCellsForColDelete(sheet, atCol, count) {
46821
47723
 
46822
47724
  // src/xlsx.ts
46823
47725
  function loadFile(filePath) {
46824
- if (!fs6.existsSync(filePath)) fail(`File not found: ${filePath}`);
46825
- const buffer = fs6.readFileSync(filePath);
47726
+ if (!fs7.existsSync(filePath)) fail(`File not found: ${filePath}`);
47727
+ const buffer = fs7.readFileSync(filePath);
46826
47728
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
46827
47729
  const originalZip = unzipXlsx(arrayBuffer);
46828
47730
  const parsed = parseExcelBuffer(arrayBuffer);
@@ -46872,7 +47774,7 @@ function lazyEngine(workbook) {
46872
47774
  };
46873
47775
  }
46874
47776
  function readToJson(filePath) {
46875
- const buffer = fs6.readFileSync(filePath);
47777
+ const buffer = fs7.readFileSync(filePath);
46876
47778
  const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
46877
47779
  const parsed = parseExcelBuffer(arrayBuffer);
46878
47780
  return {
@@ -47262,7 +48164,7 @@ async function runXlsxCommand(subcommand, toolArgs, restArgs) {
47262
48164
  }
47263
48165
 
47264
48166
  // src/docx.ts
47265
- import fs7 from "node:fs";
48167
+ import fs8 from "node:fs";
47266
48168
 
47267
48169
  // ../docx/src/parse/parser.ts
47268
48170
  var import_jszip = __toESM(require_lib3(), 1);
@@ -49033,11 +49935,11 @@ async function parseDocx(buffer) {
49033
49935
  const zip = await import_jszip.default.loadAsync(buffer);
49034
49936
  const parts = /* @__PURE__ */ new Map();
49035
49937
  const filePromises = [];
49036
- zip.forEach((path8, file) => {
49938
+ zip.forEach((path9, file) => {
49037
49939
  if (file.dir) return;
49038
49940
  filePromises.push(
49039
49941
  file.async("uint8array").then((bytes) => {
49040
- parts.set(path8, bytes);
49942
+ parts.set(path9, bytes);
49041
49943
  })
49042
49944
  );
49043
49945
  });
@@ -49266,9 +50168,9 @@ var DEFAULT_PARTS = [
49266
50168
  ["word/theme/theme1.xml", () => encoder.encode(THEME_XML)]
49267
50169
  ];
49268
50170
  function fillDefaultParts(parts) {
49269
- for (const [path8, makeBytes] of DEFAULT_PARTS) {
49270
- if (!parts.has(path8)) {
49271
- parts.set(path8, makeBytes());
50171
+ for (const [path9, makeBytes] of DEFAULT_PARTS) {
50172
+ if (!parts.has(path9)) {
50173
+ parts.set(path9, makeBytes());
49272
50174
  }
49273
50175
  }
49274
50176
  return parts;
@@ -49278,9 +50180,9 @@ function fillDefaultParts(parts) {
49278
50180
  async function serializeDocx(doc) {
49279
50181
  const zip = new import_jszip2.default();
49280
50182
  const parts = fillDefaultParts(new Map(doc.parts));
49281
- for (const [path8, bytes] of parts) {
49282
- if (path8 === "word/document.xml") continue;
49283
- zip.file(path8, bytes);
50183
+ for (const [path9, bytes] of parts) {
50184
+ if (path9 === "word/document.xml") continue;
50185
+ zip.file(path9, bytes);
49284
50186
  }
49285
50187
  const documentXml = serializeDocumentXml(doc);
49286
50188
  zip.file("word/document.xml", documentXml);
@@ -49366,8 +50268,8 @@ var DEFAULT_DOC_ATTRS = {
49366
50268
  "@_xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
49367
50269
  };
49368
50270
  async function loadFile2(filePath) {
49369
- if (!fs7.existsSync(filePath)) fail(`File not found: ${filePath}`);
49370
- const bytes = fs7.readFileSync(filePath);
50271
+ if (!fs8.existsSync(filePath)) fail(`File not found: ${filePath}`);
50272
+ const bytes = fs8.readFileSync(filePath);
49371
50273
  return parseDocx(new Uint8Array(bytes));
49372
50274
  }
49373
50275
  async function writeDoc(filePath, doc) {
@@ -49659,7 +50561,7 @@ async function runDocxCommand(subcommand, toolArgs, restArgs) {
49659
50561
  import { spawn as spawn3 } from "node:child_process";
49660
50562
  import { createServer } from "node:http";
49661
50563
  import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdtempSync, rmSync, readdirSync } from "node:fs";
49662
- import { tmpdir as tmpdir2 } from "node:os";
50564
+ import { tmpdir as tmpdir3 } from "node:os";
49663
50565
  import { join, dirname, resolve, extname, basename } from "node:path";
49664
50566
  import { fileURLToPath as fileURLToPath2 } from "node:url";
49665
50567
  import { setTimeout as sleep } from "node:timers/promises";
@@ -49752,7 +50654,7 @@ async function runPreviewCommand(filePath, flags) {
49752
50654
  });
49753
50655
  await new Promise((r) => server.listen(0, "127.0.0.1", () => r()));
49754
50656
  const httpPort = server.address().port;
49755
- const udd = mkdtempSync(join(tmpdir2(), "lotics-render-"));
50657
+ const udd = mkdtempSync(join(tmpdir3(), "lotics-render-"));
49756
50658
  const child = spawn3(chrome, [
49757
50659
  "--headless=new",
49758
50660
  "--disable-gpu",
@@ -49893,6 +50795,7 @@ COMMANDS
49893
50795
  lotics workspace select <id> Switch to a different workspace
49894
50796
  lotics workspace create <name> Create a new workspace (admin only)
49895
50797
  lotics workspace delete <id> --yes Delete a workspace (admin only; soft delete, recoverable)
50798
+ lotics workspace doctor Report dangling schema references (admin only)
49896
50799
  lotics tools List all available tools
49897
50800
  lotics tools <name> Show tool description and input schema
49898
50801
  lotics run <tool> '<json>' Execute a tool
@@ -49925,6 +50828,37 @@ COMMANDS
49925
50828
  lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
49926
50829
  lotics app rename "<new name>" Rename the app's display name (launcher title)
49927
50830
  lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
50831
+ lotics package new <name> [path] Scaffold a package project (contract + app source)
50832
+ lotics package build [path] Build the publishable bundle (source + dist)
50833
+ lotics package publish [path] Publish a new immutable package version
50834
+ lotics package dev [path] Sync the package into a dev workspace + run the app
50835
+ dev server (--workspace <dev_ws> selects it)
50836
+ lotics package sync [path] Re-sync (additive migrate + materialize) into a dev ws
50837
+ lotics package reset [path] DEV-ONLY: drop scaffolded tables + re-scaffold clean
50838
+ lotics package install <package> [--version N]
50839
+ Install an app package into this workspace
50840
+ (scaffolds the data model + deploys + materializes)
50841
+ lotics package upgrade <app_id> [--version N] [--resolve <key>=recreate|revert|keep|<id> ...]
50842
+ Preview-then-apply a package upgrade; refuses while
50843
+ any drift/modified finding lacks a --resolve, and
50844
+ hard-stops on breaking contract changes
50845
+ lotics package doctor [app_id] Installation health: version pin vs latest, binding
50846
+ drift, locally modified core (exit 1 on findings)
50847
+ lotics package rebind-role <app_id> <alias> <grp_id>
50848
+ Re-point a package role at a different group
50849
+ (re-materializes at the pinned version)
50850
+ lotics package eject <app_id> Sever an installation's package link
50851
+ (re-deploys the pinned source as a bespoke app)
50852
+ lotics package extract <app_id> [path] Promote a bespoke app to a draft package
50853
+ project (contract + binding + templates); always
50854
+ writes the draft, exits 1 on error findings
50855
+ lotics package adopt <app_id> [path] Bind the published package project onto the
50856
+ origin app (reads .lotics/adopt_binding.json;
50857
+ --version N pins a specific version)
50858
+ lotics package fleet-upgrade <package_id> [--version N]
50859
+ Upgrade EVERY installation of the package across
50860
+ your org: applies where the preview is clean,
50861
+ skips + reports findings (exit 1 unless all current)
49928
50862
  lotics ui link <component> [--ui-src <path>] [--remove]
49929
50863
  Dev-link @lotics/ui to packages/ui/src (Vite alias
49930
50864
  + tsc paths) for live HMR + typecheck. Monorepo apps
@@ -49953,6 +50887,8 @@ FLAGS
49953
50887
  is_current_member / row-scoping resolve to them (also
49954
50888
  LOTICS_VIEW_AS env; admin key only; writes stay yours)
49955
50889
  --local Pin the current directory (lotics org use / auth api-key)
50890
+ --dev (lotics workspace create) Mark a throwaway app-package dev
50891
+ workspace \u2014 required for "lotics package reset"
49956
50892
  --all (lotics auth logout) Remove every saved credential
49957
50893
  --version Show version
49958
50894
 
@@ -50037,8 +50973,8 @@ function prompt(question) {
50037
50973
  });
50038
50974
  });
50039
50975
  }
50040
- async function publicPost(path8, body) {
50041
- const response = await fetch(`${API_BASE_URL}${path8}`, {
50976
+ async function publicPost(path9, body) {
50977
+ const response = await fetch(`${API_BASE_URL}${path9}`, {
50042
50978
  method: "POST",
50043
50979
  headers: { "Content-Type": "application/json" },
50044
50980
  body: JSON.stringify(body)
@@ -50184,13 +51120,13 @@ async function resolveWorkspace(client, ctx) {
50184
51120
  function resolveUploadPaths(rawPaths) {
50185
51121
  const result = [];
50186
51122
  for (const p of rawPaths) {
50187
- const resolved = path7.resolve(p);
50188
- const stat = fs8.statSync(resolved);
51123
+ const resolved = path8.resolve(p);
51124
+ const stat = fs9.statSync(resolved);
50189
51125
  if (stat.isDirectory()) {
50190
- const entries = fs8.readdirSync(resolved, { withFileTypes: true });
51126
+ const entries = fs9.readdirSync(resolved, { withFileTypes: true });
50191
51127
  for (const entry of entries) {
50192
51128
  if (entry.isFile()) {
50193
- result.push(path7.join(resolved, entry.name));
51129
+ result.push(path8.join(resolved, entry.name));
50194
51130
  }
50195
51131
  }
50196
51132
  } else {
@@ -50325,6 +51261,19 @@ async function main() {
50325
51261
  console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
50326
51262
  process.exit(1);
50327
51263
  }
51264
+ if (command === "package" && subcommand === "new") {
51265
+ const name = toolArgs;
51266
+ if (!name) {
51267
+ console.error("Usage: lotics package new <name> [path]");
51268
+ process.exit(1);
51269
+ }
51270
+ await packageNew({ name, targetPath: restArgs[0] });
51271
+ return;
51272
+ }
51273
+ if (command === "package" && subcommand === "build") {
51274
+ await packageBuild({ projectDir: toolArgs });
51275
+ return;
51276
+ }
50328
51277
  if (command === "app" && subcommand === "workflow" && toolArgs === "check") {
50329
51278
  await appWorkflowCheck({ alias: restArgs[0] });
50330
51279
  return;
@@ -50399,7 +51348,7 @@ async function main() {
50399
51348
  }
50400
51349
  return;
50401
51350
  }
50402
- if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app") {
51351
+ if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "package") {
50403
51352
  console.error(`Unknown command: ${command}`);
50404
51353
  console.error('Run "lotics --help" for usage.');
50405
51354
  process.exit(1);
@@ -50421,6 +51370,24 @@ async function main() {
50421
51370
  console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
50422
51371
  process.exit(1);
50423
51372
  }
51373
+ if (command === "package" && !subcommand) {
51374
+ console.error("Usage:");
51375
+ console.error(" lotics package new <name> [path] Scaffold a package project (contract + app source)");
51376
+ console.error(" lotics package build [path] Build the publishable bundle (source + dist)");
51377
+ console.error(" lotics package publish [path] [-m <changelog>] Publish a new immutable package version");
51378
+ console.error(" lotics package dev [path] [--workspace <ws>] Sync into a dev workspace + run the app dev server");
51379
+ console.error(" lotics package sync [path] [--workspace <ws>] Re-sync (additive migrate + materialize) into a dev workspace");
51380
+ console.error(" lotics package reset [path] [--workspace <ws>] DEV-ONLY: drop scaffolded tables + re-scaffold clean");
51381
+ console.error(" lotics package install <package> [--version N] Install a package into this workspace");
51382
+ console.error(" lotics package upgrade <app_id> [--version N] [--resolve ns.alias=recreate|<id>] Preview + apply an upgrade");
51383
+ console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding drift");
51384
+ console.error(" lotics package rebind-role <app_id> <alias> <grp_id> Re-point a package role at another group");
51385
+ console.error(" lotics package eject <app_id> Sever an installation's package link");
51386
+ console.error(" lotics package extract <app_id> [path] Promote a bespoke app to a draft package project");
51387
+ console.error(" lotics package adopt <app_id> [path] Bind the published project onto the origin app");
51388
+ console.error(" lotics package fleet-upgrade <package_id> [--version N] Upgrade every org installation (clean ones apply; findings skip)");
51389
+ process.exit(1);
51390
+ }
50424
51391
  if (command === "run" && !subcommand) {
50425
51392
  console.error(
50426
51393
  "Usage: lotics run <tool> '<json_args>'\n lotics run <tool> @args.json (read args from a file \u2014 for large payloads)\n cat args.json | lotics run <tool> (read args from stdin)"
@@ -50447,6 +51414,25 @@ async function main() {
50447
51414
  }
50448
51415
  const { client, ctx } = requireClient(flags);
50449
51416
  if (command === "workspace") {
51417
+ if (subcommand === "doctor") {
51418
+ await resolveWorkspace(client, ctx);
51419
+ const dangling = await client.getWorkspaceDanglingReferences();
51420
+ if (dangling.length === 0) {
51421
+ console.error("Workspace references: healthy \u2014 every referenced schema id resolves.");
51422
+ return;
51423
+ }
51424
+ console.error(`Dangling schema references (${dangling.length}):`);
51425
+ for (const d of dangling) {
51426
+ console.error(
51427
+ ` - ${d.referent.kind} "${d.referent.name}" (${d.referent.id}) \u2192 ${d.namespace} ${d.id} (missing)`
51428
+ );
51429
+ }
51430
+ console.error(
51431
+ "Each referent points at schema that no longer exists \u2014 edit or archive it, or recreate the missing schema."
51432
+ );
51433
+ process.exitCode = 1;
51434
+ return;
51435
+ }
50450
51436
  const workspaces = await client.listWorkspaces();
50451
51437
  const currentWorkspaceId = ctx.workspaceId;
50452
51438
  if (subcommand === "select") {
@@ -50475,11 +51461,11 @@ Available workspaces:`);
50475
51461
  if (subcommand === "create") {
50476
51462
  const name = toolArgs;
50477
51463
  if (!name) {
50478
- console.error("Usage: lotics workspace create <name> [--timezone <tz>]");
51464
+ console.error("Usage: lotics workspace create <name> [--timezone <tz>] [--dev]");
50479
51465
  process.exit(1);
50480
51466
  }
50481
51467
  const timezone = flags.timezone;
50482
- const created = await client.createWorkspace({ name, timezone });
51468
+ const created = await client.createWorkspace({ name, timezone, is_dev: flags.dev || void 0 });
50483
51469
  setSelectedWorkspace(created.id);
50484
51470
  client.setWorkspaceId(created.id);
50485
51471
  if (flags.json) {
@@ -50541,6 +51527,150 @@ Available workspaces:`);
50541
51527
  return;
50542
51528
  }
50543
51529
  await resolveWorkspace(client, ctx);
51530
+ if (command === "package") {
51531
+ if (subcommand === "install") {
51532
+ const packageId = toolArgs;
51533
+ if (!packageId) {
51534
+ console.error("Usage: lotics package install <package> [--version N]");
51535
+ process.exit(1);
51536
+ }
51537
+ let version;
51538
+ if (flags.packageVersion !== void 0) {
51539
+ version = Number(flags.packageVersion);
51540
+ if (!Number.isInteger(version) || version <= 0) {
51541
+ console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
51542
+ process.exit(1);
51543
+ }
51544
+ }
51545
+ await packageInstall(client, { package_id: packageId, version });
51546
+ return;
51547
+ }
51548
+ if (subcommand === "eject") {
51549
+ const appId = toolArgs;
51550
+ if (!appId) {
51551
+ console.error("Usage: lotics package eject <app_id>");
51552
+ process.exit(1);
51553
+ }
51554
+ await packageEject(client, { app_id: appId });
51555
+ return;
51556
+ }
51557
+ if (subcommand === "extract") {
51558
+ const appId = toolArgs;
51559
+ if (!appId) {
51560
+ console.error("Usage: lotics package extract <app_id> [path]");
51561
+ console.error("Promotes a bespoke app to a draft package project (contract + binding + templates).");
51562
+ process.exit(1);
51563
+ }
51564
+ await packageExtract(client, { app_id: appId, targetPath: restArgs[0] });
51565
+ return;
51566
+ }
51567
+ if (subcommand === "adopt") {
51568
+ const appId = toolArgs;
51569
+ if (!appId) {
51570
+ console.error("Usage: lotics package adopt <app_id> [path]");
51571
+ console.error("Binds the published package project (this dir, or [path]) onto the origin app.");
51572
+ process.exit(1);
51573
+ }
51574
+ let version;
51575
+ if (flags.packageVersion !== void 0) {
51576
+ version = Number(flags.packageVersion);
51577
+ if (!Number.isInteger(version) || version <= 0) {
51578
+ console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
51579
+ process.exit(1);
51580
+ }
51581
+ }
51582
+ await packageAdopt(client, { app_id: appId, version, projectDir: restArgs[0] });
51583
+ return;
51584
+ }
51585
+ if (subcommand === "fleet-upgrade") {
51586
+ const packageId = toolArgs;
51587
+ if (!packageId) {
51588
+ console.error("Usage: lotics package fleet-upgrade <package_id> [--version N]");
51589
+ process.exit(1);
51590
+ }
51591
+ let version;
51592
+ if (flags.packageVersion !== void 0) {
51593
+ version = Number(flags.packageVersion);
51594
+ if (!Number.isInteger(version) || version <= 0) {
51595
+ console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
51596
+ process.exit(1);
51597
+ }
51598
+ }
51599
+ await packageFleetUpgrade(client, { package_id: packageId, version });
51600
+ return;
51601
+ }
51602
+ if (subcommand === "doctor") {
51603
+ await packageDoctor(client, { app_id: toolArgs });
51604
+ return;
51605
+ }
51606
+ if (subcommand === "rebind-role") {
51607
+ const appId = toolArgs;
51608
+ const [roleAlias, groupId] = restArgs;
51609
+ if (!appId || !roleAlias || !groupId) {
51610
+ console.error("Usage: lotics package rebind-role <app_id> <role_alias> <grp_id>");
51611
+ process.exit(1);
51612
+ }
51613
+ const result = await client.rebindAppPackageRole(appId, {
51614
+ role_alias: roleAlias,
51615
+ group_id: groupId
51616
+ });
51617
+ console.error(
51618
+ `Rebound role "${result.role_alias}" \u2192 ${result.group_id}` + (result.previous_group_id ? ` (was ${result.previous_group_id})` : "") + ". Package workflows/agents were re-materialized against the new group."
51619
+ );
51620
+ return;
51621
+ }
51622
+ if (subcommand === "upgrade") {
51623
+ const appId = toolArgs;
51624
+ if (!appId) {
51625
+ console.error(
51626
+ "Usage: lotics package upgrade <app_id> [--version N] [--resolve <namespace.alias>=recreate|<id> ...]"
51627
+ );
51628
+ process.exit(1);
51629
+ }
51630
+ let version;
51631
+ if (flags.packageVersion !== void 0) {
51632
+ version = Number(flags.packageVersion);
51633
+ if (!Number.isInteger(version) || version <= 0) {
51634
+ console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
51635
+ process.exit(1);
51636
+ }
51637
+ }
51638
+ await packageUpgrade(client, {
51639
+ app_id: appId,
51640
+ version,
51641
+ resolutions: parseResolveFlags(flags.resolve)
51642
+ });
51643
+ return;
51644
+ }
51645
+ if (subcommand === "publish") {
51646
+ await packagePublish(client, { projectDir: toolArgs, changelog: flags.message });
51647
+ return;
51648
+ }
51649
+ if (subcommand === "sync") {
51650
+ await packageSync(client, { projectDir: toolArgs });
51651
+ return;
51652
+ }
51653
+ if (subcommand === "reset") {
51654
+ await packageReset(client, { projectDir: toolArgs });
51655
+ return;
51656
+ }
51657
+ if (subcommand === "dev") {
51658
+ const projectDir = toolArgs;
51659
+ let port;
51660
+ let vitePort;
51661
+ for (const a of restArgs) {
51662
+ const portMatch = /^--port=(\d+)$/.exec(a);
51663
+ const vitePortMatch = /^--vite-port=(\d+)$/.exec(a);
51664
+ if (portMatch) port = Number(portMatch[1]);
51665
+ else if (vitePortMatch) vitePort = Number(vitePortMatch[1]);
51666
+ }
51667
+ await packageDev(client, { projectDir, port, vitePort });
51668
+ return;
51669
+ }
51670
+ console.error(`Unknown package subcommand: ${subcommand}`);
51671
+ console.error("Run 'lotics package' for usage.");
51672
+ process.exit(1);
51673
+ }
50544
51674
  if (command === "app") {
50545
51675
  if (subcommand === "create") {
50546
51676
  const name = toolArgs;
@@ -50620,7 +51750,7 @@ Available workspaces:`);
50620
51750
  const ingested = await ingestJsonArgs({
50621
51751
  rawArg: restArgs[1],
50622
51752
  stdinIsTTY: process.stdin.isTTY ?? false,
50623
- readFile: (p) => fs8.readFileSync(p, "utf-8"),
51753
+ readFile: (p) => fs9.readFileSync(p, "utf-8"),
50624
51754
  readStdin
50625
51755
  });
50626
51756
  if (ingested.kind === "error") {
@@ -50772,7 +51902,7 @@ ${JSON.stringify(info.input_schema, null, 2)}`);
50772
51902
  const ingested = await ingestJsonArgs({
50773
51903
  rawArg: toolArgs,
50774
51904
  stdinIsTTY: process.stdin.isTTY ?? false,
50775
- readFile: (p) => fs8.readFileSync(p, "utf-8"),
51905
+ readFile: (p) => fs9.readFileSync(p, "utf-8"),
50776
51906
  readStdin
50777
51907
  });
50778
51908
  if (ingested.kind === "error") {