@lotics/cli 0.76.1 → 0.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -29709,15 +29709,6 @@ var LoticsClient = class {
29709
29709
  async listWorkspaces() {
29710
29710
  return this.request("GET", "/v1/workspaces");
29711
29711
  }
29712
- /**
29713
- * Resolve one workspace's info by id from the org's workspace list (the only
29714
- * API-key-accessible source carrying `is_dev`). Returns null when the
29715
- * workspace isn't visible to these credentials.
29716
- */
29717
- async getWorkspaceInfo(id) {
29718
- const workspaces = await this.listWorkspaces();
29719
- return workspaces.find((w) => w.id === id) ?? null;
29720
- }
29721
29712
  async createWorkspace(body) {
29722
29713
  return this.request("POST", "/v1/workspaces", body);
29723
29714
  }
@@ -29785,7 +29776,7 @@ var LoticsClient = class {
29785
29776
  /**
29786
29777
  * Uninstall a standalone content package — delete the installation row. By
29787
29778
  * default the package-bound docs are ARCHIVED; `keep_content` retains them as
29788
- * ordinary workspace docs. Admin-only. Backs `lotics package uninstall`.
29779
+ * ordinary workspace docs. Admin-only. Backs `lotics uninstall`.
29789
29780
  */
29790
29781
  async uninstallContentPackage(installation_id, opts = {}) {
29791
29782
  const qs = opts.keep_content ? "?keep_content=true" : "";
@@ -29833,7 +29824,7 @@ var LoticsClient = class {
29833
29824
  );
29834
29825
  }
29835
29826
  /**
29836
- * Uninstall a package installation (backs `lotics package uninstall`). Does
29827
+ * Uninstall a package installation (backs `lotics uninstall`). Does
29837
29828
  * everything DELETE does plus archives the installation's lifecycle
29838
29829
  * artifacts; with `archive_tables` it also archives the scaffolded entity
29839
29830
  * tables — refused server-side unless this installation created them
@@ -29851,10 +29842,11 @@ var LoticsClient = class {
29851
29842
  return this.request("PATCH", `/v1/apps/${encodeURIComponent(app_id)}/package-config`, body);
29852
29843
  }
29853
29844
  /**
29854
- * Retire (or `undo` un-retire) a registry package (backs `lotics package
29855
- * retire`). Retiring refuses NEW installs and hides the package from
29856
- * non-owning orgs; existing installations keep working and may still upgrade.
29857
- * Owner-org admin-only.
29845
+ * Retire (or `undo` un-retire) a registry package (backs `lotics app
29846
+ * unpublish` the endpoint/audit action keep the `retire` name to avoid API
29847
+ * churn). Retiring refuses NEW installs and hides the package from non-owning
29848
+ * orgs; existing installations keep working and may still upgrade. Owner-org
29849
+ * admin-only.
29858
29850
  */
29859
29851
  async retirePackage(package_id, body) {
29860
29852
  return this.request("POST", `/v1/packages/${encodeURIComponent(package_id)}/retire`, body);
@@ -29869,38 +29861,12 @@ var LoticsClient = class {
29869
29861
  async ejectPackage(app_id) {
29870
29862
  return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/eject`);
29871
29863
  }
29872
- /**
29873
- * Extract a DRAFT app package from an existing bespoke app — the promotion
29874
- * read (docs/packages.md § Promotion). Pure: nothing is written. Returns
29875
- * the alias-keyed draft `contract` (opaque to the CLI — the server is the
29876
- * validating authority), the origin workspace's `binding` (which doubles as
29877
- * the adopt binding), a findings `report` (any `error` ⇒ not publishable
29878
- * as-is), and the file-backed `template_files` the CLI must stage into the
29879
- * project at their `bytes_ref` paths. Backs `lotics package extract`.
29880
- * Admin-only.
29881
- */
29882
- async extractPackage(app_id, opts = {}) {
29883
- const qs = opts.knowledge && opts.knowledge.length > 0 ? `?knowledge=${encodeURIComponent(JSON.stringify(opts.knowledge))}` : "";
29884
- return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-extract${qs}`);
29885
- }
29886
- /**
29887
- * Adopt a published package onto an EXISTING (bespoke or ejected) app — the
29888
- * final promotion step. The workspace already holds the concrete objects, so
29889
- * nothing is scaffolded or rewritten: the server verifies the `binding` is
29890
- * complete, live, and FAITHFUL to the version's contract, then writes only the
29891
- * installation pin (the app becomes installation #1, upgradeable again). A
29892
- * ConflictError names the aliases that diverge. `version` omitted adopts the
29893
- * latest. Backs `lotics package adopt`. Admin-only.
29894
- */
29895
- async adoptPackage(app_id, body) {
29896
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-adopt`, body);
29897
- }
29898
29864
  /**
29899
29865
  * Fleet upgrade — bring every installation of a package across the CALLER'S
29900
29866
  * org to the target version (latest when omitted) in one call. Hands-off
29901
29867
  * applies only where the preview is clean; installations with breaking/
29902
29868
  * drift/modified-core findings are skipped and reported for the normal
29903
- * per-installation consent flow. Backs `lotics package fleet-upgrade`.
29869
+ * per-installation consent flow. Backs `lotics upgrade <package_id>` (fleet).
29904
29870
  * Admin-only; org-scoped (no workspace header needed).
29905
29871
  */
29906
29872
  /**
@@ -29922,16 +29888,10 @@ var LoticsClient = class {
29922
29888
  body
29923
29889
  );
29924
29890
  }
29925
- // --- App packages (registry authoring + dev harness) ---
29926
- /**
29927
- * Create a registry app package the Lotics-owned, workspace-agnostic
29928
- * blueprint. `lotics package publish` calls this on first publish (when the
29929
- * local manifest has no `package_id`), then publishes version 1 against the
29930
- * returned id. Admin-only.
29931
- */
29932
- async createPackage(body) {
29933
- return this.request("POST", "/v1/packages", body);
29934
- }
29891
+ // --- Packages (registry reads + installations) ---
29892
+ // Authoring is server-side: apps via `POST /v1/apps/{id}/package-publish|release`,
29893
+ // content packages via the `publish_content`/`release_content` tools. There is no
29894
+ // client-side create-package / upload-bundle path.
29935
29895
  /**
29936
29896
  * Fetch a registry app package's metadata (incl. `kind`, `latest_version` and
29937
29897
  * the Lotics-backed `is_official` trust badge). Admin-only; cross-tenant by id.
@@ -29943,33 +29903,6 @@ var LoticsClient = class {
29943
29903
  async listPackageVersions(package_id) {
29944
29904
  return this.request("GET", `/v1/packages/${encodeURIComponent(package_id)}/versions`);
29945
29905
  }
29946
- /**
29947
- * Publish a new immutable package version — multipart upload of the alias-keyed
29948
- * contract (JSON) + the prebuilt code bundle (a gzipped tarball carrying
29949
- * `source.tar.gz` + `dist.tar.gz` members). The server validates the contract +
29950
- * bundle, then allocates the next monotonic version. The `contract` is opaque
29951
- * JSON to the transport (the server is the validating authority). Admin-only.
29952
- */
29953
- async publishPackageVersion(package_id, args) {
29954
- const formData = new FormData();
29955
- formData.append("contract", JSON.stringify(args.contract));
29956
- formData.append(
29957
- "bundle",
29958
- new Blob([new Uint8Array(args.bundle)], { type: "application/gzip" }),
29959
- "bundle.tar.gz"
29960
- );
29961
- if (args.changelog) formData.append("changelog", args.changelog);
29962
- if (args.channel) formData.append("channel", args.channel);
29963
- const url2 = `${this.baseUrl}/v1/packages/${encodeURIComponent(package_id)}/versions`;
29964
- const response = await fetch(url2, {
29965
- method: "POST",
29966
- headers: this.buildHeaders(),
29967
- // no Content-Type — fetch sets the multipart boundary
29968
- body: formData
29969
- });
29970
- if (!response.ok) await this.throwResponseError(response);
29971
- return response.json();
29972
- }
29973
29906
  /**
29974
29907
  * Upgrade a package installation to a newer published version — extends the
29975
29908
  * binding additively, re-materializes the target version's
@@ -29996,18 +29929,6 @@ var LoticsClient = class {
29996
29929
  `/v1/apps/${encodeURIComponent(app_id)}/package-upgrade${query}`
29997
29930
  );
29998
29931
  }
29999
- /**
30000
- * Re-bind a package role to a different workspace group (the current group
30001
- * still exists). Re-materializes at the pinned version; refuses over
30002
- * modified-core findings. Admin-only.
30003
- */
30004
- async rebindPackageRole(app_id, body) {
30005
- return this.request(
30006
- "POST",
30007
- `/v1/apps/${encodeURIComponent(app_id)}/package-rebind-role`,
30008
- body
30009
- );
30010
- }
30011
29932
  /**
30012
29933
  * Workspace-wide dangling-reference sweep — active app/workflow artifacts
30013
29934
  * whose prefixed schema ids no longer resolve. Backs
@@ -30025,13 +29946,59 @@ var LoticsClient = class {
30025
29946
  return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-health`);
30026
29947
  }
30027
29948
  /**
30028
- * Reset a package installation in a DEV workspace drop the package-owned
30029
- * scaffolded tables and re-scaffold clean. Hard-gated server-side to dev
30030
- * workspaces (a non-dev workspace is refused). Returns the resulting app.
30031
- * Admin-only.
29949
+ * Preview a release the dry run behind `lotics app release`. Runs the
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,
29953
+ * owning-org only.
30032
29954
  */
30033
- async resetPackage(app_id) {
30034
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-reset`);
29955
+ async previewPackageRelease(app_id) {
29956
+ return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/package-release`);
29957
+ }
29958
+ /**
29959
+ * Release — snapshot the origin app into the next registry version. The server
29960
+ * binding-aware-extracts it, repackages its deployed source + dist as the
29961
+ * 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,
29963
+ * owning-org only. Backs `lotics app release --yes`.
29964
+ */
29965
+ async releasePackage(app_id, body) {
29966
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-release`, body);
29967
+ }
29968
+ /**
29969
+ * Dry-run preview of a first-release — the `GET` behind `lotics app publish`
29970
+ * (no `--yes`), the publish-side analogue of `previewPackageRelease`. The
29971
+ * server runs the same fresh-alias extract + `src/` scan the apply runs
29972
+ * (through any `renames`) and returns the package name it would mint, the
29973
+ * auto-minted RENAMABLE aliases (the exact `--rename` keys), and the extract
29974
+ * findings (an `error` blocks the apply). No writes. Admin-only.
29975
+ */
29976
+ async previewPublishAppPackage(app_id, opts = {}) {
29977
+ const params = new URLSearchParams();
29978
+ if (opts.knowledge !== void 0 && opts.knowledge.length > 0) {
29979
+ params.set("knowledge", JSON.stringify(opts.knowledge));
29980
+ }
29981
+ if (opts.renames !== void 0 && opts.renames.length > 0) {
29982
+ params.set("renames", JSON.stringify(opts.renames));
29983
+ }
29984
+ const query = params.toString();
29985
+ return this.request(
29986
+ "GET",
29987
+ `/v1/apps/${encodeURIComponent(app_id)}/package-publish${query ? `?${query}` : ""}`
29988
+ );
29989
+ }
29990
+ /**
29991
+ * First-release apply — mint a package from a BESPOKE app and publish v1 in one
29992
+ * call (the `POST` behind `lotics app publish --yes`). The server extracts an
29993
+ * alias-keyed contract from the app (fresh aliases; `renames` fixes them before
29994
+ * v1 freezes), creates the registry package (name/description from the app),
29995
+ * publishes v1 from the app's deployed source + dist, and pins the origin as
29996
+ * installation #1. Error findings from extract surface as a 409. An
29997
+ * already-linked app must use `releasePackage` instead. Admin-only. Backs
29998
+ * `lotics app publish <app_id>`.
29999
+ */
30000
+ async publishAppAsPackage(app_id, body) {
30001
+ return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-publish`, body);
30035
30002
  }
30036
30003
  /**
30037
30004
  * Resolve the display name + fields (incl. select options) of the given tables
@@ -31360,379 +31327,6 @@ See https://lotics.ai/docs/app-sdk for the SDK reference.
31360
31327
  }
31361
31328
  ];
31362
31329
  }
31363
- function buildPackageStarterOverrides(args) {
31364
- const nameLit = JSON.stringify(args.app_name);
31365
- return [
31366
- {
31367
- // The package App.tsx imports `../.lotics/app_fields` — a DETERMINISTIC
31368
- // pure function of contract.json (generate_package_fields.ts) — so unlike
31369
- // the app starter's fully-ignored `.lotics`, it must be committed or the
31370
- // scaffold's own shipped CI (npm ci → typecheck/test/build on a clean
31371
- // clone) fails on the missing module. The sync-written `.d.ts` companions
31372
- // stay ignored: they need a live workspace and their absence only degrades
31373
- // hooks to untyped overloads, never breaks the build.
31374
- path: ".gitignore",
31375
- content: `node_modules
31376
- dist
31377
- *.tsbuildinfo
31378
- .DS_Store
31379
- .lotics/*
31380
- !.lotics/app_fields.ts
31381
- coverage
31382
- `
31383
- },
31384
- {
31385
- path: "src/App.tsx",
31386
- content: `import { useMemo, type ReactNode } from "react";
31387
- import { ScrollView, View } from "react-native";
31388
- import { useConfig, useQuery, readSelect, row } from "@lotics/app-sdk";
31389
- import { AppRouter } from "@lotics/app-sdk/router";
31390
- import { Text } from "@lotics/ui/text";
31391
- import { Card } from "@lotics/ui/card";
31392
- import { Button } from "@lotics/ui/button";
31393
- import { OptionBadge } from "@lotics/ui/option_badge";
31394
- import { Skeleton } from "@lotics/ui/skeleton";
31395
- import { EmptyState } from "@lotics/ui/empty_state";
31396
- import { Callout, CalloutTitle, CalloutText, CalloutActions } from "@lotics/ui/callout";
31397
- import { F, OPT } from "../.lotics/app_fields";
31398
-
31399
- // This is a PACKAGE starter \u2014 a workspace-agnostic blueprint installed into many
31400
- // workspaces, each binding the contract's aliases to DIFFERENT concrete ids. So a
31401
- // screen never hardcodes a \`fld_\u2026\`/\`opt_\u2026\` id: it addresses a field by contract
31402
- // alias through \`F\` and a select option through \`OPT\`, both from the generated
31403
- // \`.lotics/app_fields.ts\` (which resolves every alias to THIS installation's id at
31404
- // module load from the binding). The aliases come from contract.json \u2014 entity
31405
- // \`item\`, fields name/notes/status, options open/done, query \`items\`, config knob
31406
- // \`heading\`. Edit contract.json, then \`lotics package sync\` to regenerate F/OPT.
31407
-
31408
- interface Item {
31409
- id: string;
31410
- name: string;
31411
- notes: string;
31412
- status: ReturnType<typeof readSelect>[number] | null;
31413
- done: boolean;
31414
- }
31415
-
31416
- // The \`items\` query is a bare from_entity, so a row is keyed by FIELD id: read a
31417
- // cell as \`r[F.ITEM.<field>]\` and decode it with the SDK's pure readers (\`row.text\`,
31418
- // \`readSelect\`). \`OPT.ITEM.status.done\` is this install's \`opt_\u2026\` id, so comparing
31419
- // the cell's stored option key to it marks a done row.
31420
- function decode(r: Record<string, unknown>): Item {
31421
- const status = readSelect(r[F.ITEM.status])[0] ?? null;
31422
- return {
31423
- id: row.text(r.__source_record_id),
31424
- name: row.text(r[F.ITEM.name]),
31425
- notes: row.text(r[F.ITEM.notes]),
31426
- status,
31427
- done: status?.key === OPT.ITEM.status.done,
31428
- };
31429
- }
31430
-
31431
- // Outer <View flex:1> claims the iframe height (index.html sets html/body/#root to
31432
- // 100% + #root is a flex column); the list scrolls beneath the heading. Keep this
31433
- // flex chain plain (not @lotics/ui/stack) so a fill-remaining-space child gets height.
31434
- function Screen({ children }: { children: ReactNode }) {
31435
- return (
31436
- <View style={{ flex: 1 }}>
31437
- <ScrollView contentContainerStyle={{ padding: 24, alignItems: "center" }}>
31438
- <View style={{ maxWidth: 640, width: "100%", gap: 16 }}>{children}</View>
31439
- </ScrollView>
31440
- </View>
31441
- );
31442
- }
31443
-
31444
- function ItemsScreen() {
31445
- // \`heading\` is a contract config knob \u2014 an installation overrides it and
31446
- // useConfig() renders the customized value; the literal here is only the fallback.
31447
- const { config } = useConfig({ heading: ${nameLit} });
31448
- const itemsQ = useQuery("items");
31449
- const items = useMemo(() => itemsQ.rows.map(decode), [itemsQ.rows]);
31450
- // First-load only: a skeleton while the very first fetch is in flight with no rows
31451
- // yet \u2014 never blank already-loaded rows to a spinner on a background refetch.
31452
- const firstLoad = itemsQ.loading && items.length === 0;
31453
-
31454
- return (
31455
- <Screen>
31456
- <Text size="xxl" weight="semibold" level={1}>
31457
- {config.heading}
31458
- </Text>
31459
-
31460
- {itemsQ.error ? (
31461
- <Callout tone="error">
31462
- <CalloutTitle>Couldn't load items</CalloutTitle>
31463
- <CalloutText>{itemsQ.error}</CalloutText>
31464
- <CalloutActions>
31465
- <Button title="Try again" onPress={() => itemsQ.refetch()} />
31466
- </CalloutActions>
31467
- </Callout>
31468
- ) : firstLoad ? (
31469
- <View style={{ gap: 10 }}>
31470
- {[0, 1, 2].map((i) => (
31471
- <Skeleton key={i} height={64} radius={12} />
31472
- ))}
31473
- </View>
31474
- ) : items.length > 0 ? (
31475
- <View style={{ gap: 10 }}>
31476
- {items.map((item) => (
31477
- <Card key={item.id}>
31478
- <View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
31479
- <View style={{ flex: 1, gap: 2 }}>
31480
- <Text weight="medium" color={item.done ? "muted" : undefined}>
31481
- {item.name}
31482
- </Text>
31483
- {item.notes.length > 0 ? (
31484
- <Text size="sm" color="muted" numberOfLines={1}>
31485
- {item.notes}
31486
- </Text>
31487
- ) : null}
31488
- </View>
31489
- <OptionBadge value={item.status} />
31490
- </View>
31491
- </Card>
31492
- ))}
31493
- </View>
31494
- ) : (
31495
- <EmptyState
31496
- icon="list-checks"
31497
- message="No items yet"
31498
- hint="Rows in this package's table appear here."
31499
- />
31500
- )}
31501
- </Screen>
31502
- );
31503
- }
31504
-
31505
- const routes = [{ path: "/", element: <ItemsScreen /> }];
31506
-
31507
- export default function App() {
31508
- return <AppRouter routes={routes} />;
31509
- }
31510
- `
31511
- },
31512
- {
31513
- path: "src/App.test.tsx",
31514
- content: `import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
31515
- import { render, screen, cleanup } from "@testing-library/react";
31516
- import App from "./App";
31517
-
31518
- // Mock @lotics/app-sdk \u2014 the app's one external boundary (data + RPC + the package
31519
- // binding). \`importOriginal\` keeps the PURE cell readers (\`row\`, \`readSelect\`) real,
31520
- // so mock rows decode exactly like wire rows; only the hooks and getAppBinding are
31521
- // stubbed. The binding ids below are what F.ITEM.* / OPT.ITEM.status.* resolve to;
31522
- // the query rows are keyed by the SAME ids.
31523
- const h = vi.hoisted(() => ({
31524
- state: {
31525
- rows: [] as Array<Record<string, unknown>>,
31526
- loading: false,
31527
- error: null as string | null,
31528
- },
31529
- binding: {
31530
- fields: { "item.name": "fld_name", "item.notes": "fld_notes", "item.status": "fld_status" },
31531
- options: { "item.status:open": "opt_open", "item.status:done": "opt_done" },
31532
- roles: {},
31533
- },
31534
- }));
31535
-
31536
- vi.mock("@lotics/app-sdk", async (importOriginal) => {
31537
- const actual = await importOriginal<typeof import("@lotics/app-sdk")>();
31538
- return {
31539
- ...actual,
31540
- getAppBinding: async () => h.binding,
31541
- useConfig: (defaults: Record<string, unknown>) => ({ config: defaults, loading: false }),
31542
- useQuery: () => ({
31543
- rows: h.state.rows,
31544
- loading: h.state.loading,
31545
- isValidating: false,
31546
- error: h.state.error,
31547
- refetch: () => {},
31548
- }),
31549
- };
31550
- });
31551
-
31552
- const rowOf = (id: string, name: string, notes: string, statusKey: string) => ({
31553
- __source_record_id: id,
31554
- fld_name: name,
31555
- fld_notes: notes,
31556
- fld_status: [{ key: statusKey, label: statusKey === "opt_done" ? "Done" : "Open" }],
31557
- });
31558
-
31559
- beforeEach(() => {
31560
- h.state.rows = [];
31561
- h.state.loading = false;
31562
- h.state.error = null;
31563
- });
31564
-
31565
- // vitest globals are off, so @testing-library/react's automatic afterEach cleanup is
31566
- // never registered \u2014 clean up explicitly or renders accumulate across tests and a
31567
- // second render's duplicate matches fail getByText.
31568
- afterEach(cleanup);
31569
-
31570
- describe("App", () => {
31571
- test("renders the heading and the loaded items with status badges", () => {
31572
- h.state.rows = [
31573
- rowOf("1", "First item", "with a note", "opt_open"),
31574
- rowOf("2", "Second item", "", "opt_done"),
31575
- ];
31576
- render(<App />);
31577
-
31578
- expect(screen.getByText(${nameLit})).toBeTruthy();
31579
- expect(screen.getByText("First item")).toBeTruthy();
31580
- expect(screen.getByText("Second item")).toBeTruthy();
31581
- expect(screen.getByText("with a note")).toBeTruthy();
31582
- expect(screen.getByText("Open")).toBeTruthy();
31583
- expect(screen.getByText("Done")).toBeTruthy();
31584
- expect(screen.queryByText("No items yet")).toBeNull();
31585
- });
31586
-
31587
- test("shows the empty state when there are no items", () => {
31588
- render(<App />);
31589
- expect(screen.getByText("No items yet")).toBeTruthy();
31590
- });
31591
-
31592
- test("does not flash the list or empty state on first load", () => {
31593
- h.state.loading = true;
31594
- render(<App />);
31595
- expect(screen.queryByText("No items yet")).toBeNull();
31596
- });
31597
-
31598
- test("surfaces a load error loudly", () => {
31599
- h.state.error = "Network unreachable";
31600
- render(<App />);
31601
- expect(screen.getByText("Couldn't load items")).toBeTruthy();
31602
- expect(screen.getByText("Network unreachable")).toBeTruthy();
31603
- });
31604
- });
31605
- `
31606
- },
31607
- {
31608
- path: "README.md",
31609
- content: `# ${escapeHtml(args.app_name)}
31610
-
31611
- A Lotics **app package** \u2014 a versioned, installable blueprint (a \`contract.json\`
31612
- data model + app source) that installs into many workspaces. Authored locally,
31613
- published and run through the \`lotics package\` CLI. See \`docs/app_packages.md\`.
31614
-
31615
- ## Dev loop
31616
-
31617
- \`\`\`bash
31618
- lotics workspace create "${escapeHtml(args.app_name)} dev" --dev # a throwaway dev workspace
31619
- lotics package dev --workspace <dev_ws> # sync into it + run the dev server
31620
- # edit contract.json \u2192 re-run \`lotics package sync\` to migrate + re-materialize
31621
- # edit src/* \u2192 hot reload
31622
- \`\`\`
31623
-
31624
- \`sync\` / \`dev\` regenerate \`.lotics/app_fields.ts\` (the runtime \`F\` / \`OPT\` / \`ROLE\`
31625
- surface) from \`contract.json\`, plus the typed \`.lotics/app_{queries,workflows,agents}.d.ts\`
31626
- companions from the live installation \u2014 so \`useQuery\` / \`useWorkflow\` stay typed in the
31627
- dev loop.
31628
-
31629
- ## Quality
31630
-
31631
- \`\`\`bash
31632
- npm run typecheck
31633
- npm run lint
31634
- npm test
31635
- \`\`\`
31636
-
31637
- ## Publish
31638
-
31639
- \`\`\`bash
31640
- lotics package publish -m "v1" # build + publish an immutable version
31641
- lotics package install <package_id> # install into any workspace
31642
- \`\`\`
31643
-
31644
- ## The app screen
31645
-
31646
- \`src/App.tsx\` reads the contract BY ALIAS through \`F\` / \`OPT\` (from the generated
31647
- \`.lotics/app_fields.ts\`) \u2014 never a raw \`fld_\u2026\` / \`opt_\u2026\` id, since every install binds
31648
- different concrete ids. It lists the \`items\` query and renders the \`heading\` config knob.
31649
- \`@lotics/ui\` primitives render via react-native-web (aliased in \`vite.config.ts\`).
31650
-
31651
- ## Contract reference
31652
-
31653
- \`contract.json\` is the package's alias-keyed data model
31654
- (schema: \`@lotics/shared/schemas/app_packages\`). Every cross-reference is by **alias**;
31655
- the materializer resolves alias \u2192 this workspace's concrete id at install.
31656
-
31657
- ### Aliases
31658
-
31659
- - Entity / field / option / role / template / config aliases are lowercase slugs
31660
- matching \`^[a-z][a-z0-9_]*$\`. A field's fully-qualified key is \`<entity>.<field>\`
31661
- (e.g. \`item.status\`); a select option's is \`<entity>.<field>:<option>\`
31662
- (e.g. \`item.status:done\`).
31663
- - Query / workflow / agent aliases are runtime lookup keys (1\u2013200 chars) \u2014 the app
31664
- source invokes them verbatim (\`useQuery("items")\`), so renaming one severs the call.
31665
-
31666
- ### Entities & fields
31667
-
31668
- \`\`\`jsonc
31669
- { "alias": "item", "label": "Item", "fields": [
31670
- { "alias": "name", "label": "Name", "type": "text", "required": true },
31671
- { "alias": "status", "label": "Status", "type": "select",
31672
- "options": [ { "alias": "open", "label": "Open", "color": "blue" },
31673
- { "alias": "done", "label": "Done", "color": "green" } ] } ] }
31674
- \`\`\`
31675
-
31676
- Field \`type\`: \`text\`, \`number\`, \`date\`, \`boolean\`, \`select\` (+ \`options\`),
31677
- \`select_member\`, \`select_record_link\` (\`target_entity\` = an entity alias), \`files\`,
31678
- \`formula\` (expression references same-entity fields as \`{alias}\`), \`rollup\`, \`lookup\`,
31679
- \`autonumber\`. \`required\` is advisory (app / workflow-layer UX only \u2014 the table model
31680
- has no required constraint). Select \`options\` are \`{ alias, label, color }\`.
31681
-
31682
- ### Queries
31683
-
31684
- Alias-form AST \u2014 the same node kinds as the runtime query engine, except a
31685
- \`from_table\` node carries \`from_entity\` (an entity alias) instead of a \`table_id\`:
31686
-
31687
- \`\`\`jsonc
31688
- { "alias": "items", "ast": {
31689
- "kind": "from_table", "from_entity": "item",
31690
- "sort": [ { "field_key": "name", "order": "asc" } ] } }
31691
- \`\`\`
31692
-
31693
- ### Workflows
31694
-
31695
- The sole app-side mutation path. Each workflow has a \`trigger\`, typed \`inputs\` /
31696
- \`outputs\`, and a JS-subset \`body\`.
31697
-
31698
- - Trigger \u2014 app-invoked or table-lifecycle:
31699
- - \`{ "type": "app" }\` \u2014 called by alias from the app (\`useWorkflow("<alias>")\`).
31700
- - \`{ "type": "entity_lifecycle", "entity": "<entity>", "event": "<event>" }\` \u2014 fires
31701
- on the bound table. \`event\` \u2208 \`before_create\`, \`after_create\`, \`before_update\`,
31702
- \`after_update\`, \`before_delete\`, \`after_delete\`.
31703
- - Typed \`inputs\` (\`text\` / \`number\` / \`date\` / \`member\` / \`select\` / \`record_link\`,
31704
- plus \`object\` / \`array\`):
31705
- - \`record_link\` \u2014 its \`table_id\` names an **entity alias**.
31706
- - \`member\` \u2014 its \`group\` names a **role alias**.
31707
- - \`select\` \u2014 an option \`value\` in \`entity.field:option\` form binds to that option's
31708
- id (a plain value is a literal, left as-is).
31709
- - Body **sentinel tokens** \u2014 a body addresses package objects by reserved tokens the
31710
- materializer rewrites to concrete ids at install. Always inside a **string literal**:
31711
- - \`@@entity:<entity>@@\`
31712
- - \`@@field:<entity>.<field>@@\`
31713
- - \`@@option:<entity>.<field>:<option>@@\`
31714
- - \`@@role:<role>@@\`
31715
- - \`@@template:<template>@@\`
31716
-
31717
- ### Config knobs
31718
-
31719
- Typed customization knobs the app reads via \`useConfig()\`. Each has an \`alias\`,
31720
- \`label\`, \`type\`, and \`default\`:
31721
-
31722
- - \`text\` \u2192 a string default; \`boolean\` \u2192 a boolean; \`number\` \u2192 a number;
31723
- \`color\` \u2192 a palette color token; \`select\` \u2192 \`options: [{ value, label }]\` with the
31724
- \`default\` being one of those \`value\`s.
31725
-
31726
- \`\`\`jsonc
31727
- { "alias": "heading", "label": "List heading", "type": "text", "default": "Items" }
31728
- \`\`\`
31729
-
31730
- Publish validates the whole contract (alias uniqueness, every cross-reference
31731
- resolves, sentinel tokens name declared objects) before storing the version.
31732
- `
31733
- }
31734
- ];
31735
- }
31736
31330
  function escapeHtml(s) {
31737
31331
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
31738
31332
  }
@@ -32802,6 +32396,123 @@ export type AppOptions = typeof OPT;
32802
32396
  `;
32803
32397
  }
32804
32398
 
32399
+ // src/generate_package_fields.ts
32400
+ var HEADER5 = `// Auto-generated by 'lotics app codegen' (linked/published app; also app pull/dev/deploy).
32401
+ // DO NOT EDIT \u2014 regenerated from the installation's live binding.
32402
+ //
32403
+ // A package installation resolves F/OPT/ROLE at MODULE LOAD from its binding
32404
+ // (contract alias \u2192 THIS workspace's concrete id) via the SDK's \`binding\` RPC.
32405
+ // Top-level await: the module graph waits for the binding before any importer
32406
+ // evaluates, so every entry is a plain string.
32407
+ import { getAppBinding } from "@lotics/app-sdk";
32408
+
32409
+ const binding = await getAppBinding();
32410
+
32411
+ function bound(map: Record<string, string>, key: string, kind: string): string {
32412
+ const id = map[key];
32413
+ if (id === undefined) {
32414
+ throw new Error(
32415
+ \`app_fields: \${kind} "\${key}" is not in this installation's binding \u2014 \` +
32416
+ \`the generated app_fields.ts is stale relative to the installed contract version.\`,
32417
+ );
32418
+ }
32419
+ return id;
32420
+ }
32421
+ `;
32422
+ function parseBinding(binding) {
32423
+ const entities = [];
32424
+ const entityByAlias = /* @__PURE__ */ new Map();
32425
+ const fieldByKey = /* @__PURE__ */ new Map();
32426
+ for (const key of Object.keys(binding.fields)) {
32427
+ const dot = key.indexOf(".");
32428
+ if (dot <= 0 || dot >= key.length - 1) continue;
32429
+ const entityAlias = key.slice(0, dot);
32430
+ const fieldAlias = key.slice(dot + 1);
32431
+ let entity = entityByAlias.get(entityAlias);
32432
+ if (entity === void 0) {
32433
+ entity = { alias: entityAlias, fields: [] };
32434
+ entityByAlias.set(entityAlias, entity);
32435
+ entities.push(entity);
32436
+ }
32437
+ const field = { alias: fieldAlias, key, options: [] };
32438
+ entity.fields.push(field);
32439
+ fieldByKey.set(key, field);
32440
+ }
32441
+ for (const key of Object.keys(binding.options)) {
32442
+ const colon = key.indexOf(":");
32443
+ if (colon <= 0 || colon >= key.length - 1) continue;
32444
+ const fieldKey = key.slice(0, colon);
32445
+ const optionAlias = key.slice(colon + 1);
32446
+ const field = fieldByKey.get(fieldKey);
32447
+ if (field === void 0) continue;
32448
+ field.options.push({ alias: optionAlias, key });
32449
+ }
32450
+ return { entities, roles: Object.keys(binding.roles) };
32451
+ }
32452
+ function emitFieldMap2(entities) {
32453
+ if (entities.length === 0) return `export const F = {} as const;`;
32454
+ const blocks = entities.map((entity) => {
32455
+ const lines = entity.fields.map(
32456
+ (field) => ` ${propKey(field.alias)}: bound(binding.fields, ${JSON.stringify(field.key)}, "field"),`
32457
+ );
32458
+ return ` ${propKey(entity.alias.toUpperCase())}: {
32459
+ ${lines.join("\n")}
32460
+ },`;
32461
+ });
32462
+ return `export const F = {
32463
+ ${blocks.join("\n")}
32464
+ } as const;`;
32465
+ }
32466
+ function emitOptionMap2(entities) {
32467
+ const entityBlocks = [];
32468
+ for (const entity of entities) {
32469
+ const fieldBlocks = [];
32470
+ for (const field of entity.fields) {
32471
+ if (field.options.length === 0) continue;
32472
+ const lines = field.options.map(
32473
+ (option) => ` ${propKey(option.alias)}: bound(binding.options, ${JSON.stringify(option.key)}, "option"),`
32474
+ );
32475
+ fieldBlocks.push(` ${propKey(field.alias)}: {
32476
+ ${lines.join("\n")}
32477
+ },`);
32478
+ }
32479
+ if (fieldBlocks.length === 0) continue;
32480
+ entityBlocks.push(` ${propKey(entity.alias.toUpperCase())}: {
32481
+ ${fieldBlocks.join("\n")}
32482
+ },`);
32483
+ }
32484
+ if (entityBlocks.length === 0) return `export const OPT = {} as const;`;
32485
+ return `export const OPT = {
32486
+ ${entityBlocks.join("\n")}
32487
+ } as const;`;
32488
+ }
32489
+ function emitRoleMap(roles) {
32490
+ if (roles.length === 0) return `export const ROLE = {} as const;`;
32491
+ const lines = roles.map(
32492
+ (role) => ` ${propKey(role)}: bound(binding.roles, ${JSON.stringify(role)}, "role"),`
32493
+ );
32494
+ return `export const ROLE = {
32495
+ ${lines.join("\n")}
32496
+ } as const;`;
32497
+ }
32498
+ function generatePackageAppFields(binding) {
32499
+ const { entities, roles } = parseBinding(binding);
32500
+ return `${HEADER5}
32501
+ ${emitFieldMap2(entities)}
32502
+
32503
+ ${emitOptionMap2(entities)}
32504
+
32505
+ ${emitRoleMap(roles)}
32506
+
32507
+ /** Field-id alias map: \`F[<ENTITY>][<field>]\` is this installation's \`fld_\u2026\` id. */
32508
+ export type AppFields = typeof F;
32509
+ /** Select-option alias map: \`OPT[<ENTITY>][<field>][<option>]\` is this installation's \`opt_\u2026\` id. */
32510
+ export type AppOptions = typeof OPT;
32511
+ /** Role alias map: \`ROLE[<role>]\` is this installation's \`grp_\u2026\` id. */
32512
+ export type AppRoles = typeof ROLE;
32513
+ `;
32514
+ }
32515
+
32805
32516
  // src/app_workflow_check.ts
32806
32517
  import fs3 from "node:fs";
32807
32518
  import path4 from "node:path";
@@ -33128,6 +32839,13 @@ function writeAppFields(projectDir, tables) {
33128
32839
  fs4.writeFileSync(file2, generateAppFields(tables));
33129
32840
  return file2;
33130
32841
  }
32842
+ function writeBindingAppFields(projectDir, binding) {
32843
+ const dotLotics = path5.join(projectDir, ".lotics");
32844
+ fs4.mkdirSync(dotLotics, { recursive: true });
32845
+ const file2 = path5.join(dotLotics, "app_fields.ts");
32846
+ fs4.writeFileSync(file2, generatePackageAppFields(binding));
32847
+ return file2;
32848
+ }
33131
32849
  async function appCodegen(args) {
33132
32850
  const projectDir = path5.resolve(args.projectDir ?? process.cwd());
33133
32851
  const meta3 = readAppMeta(projectDir);
@@ -33143,14 +32861,24 @@ async function appCodegen(args) {
33143
32861
  );
33144
32862
  return;
33145
32863
  }
33146
- const tableIds = resolveCodegenTableIds(projectDir, meta3.queries ?? {});
33147
32864
  try {
33148
- const tables = await args.client.getWorkspaceSchema(tableIds);
33149
- const fieldsPath = writeAppFields(projectDir, tables);
33150
- console.error(`Regenerated ${fieldsPath} (${tables.length} table${tables.length === 1 ? "" : "s"})`);
32865
+ const app = await args.client.getApp(meta3.app_id);
32866
+ if (app.package_id) {
32867
+ const binding = await args.client.appBinding(meta3.app_id);
32868
+ const fieldsPath = writeBindingAppFields(projectDir, binding);
32869
+ const count = Object.keys(binding.fields).length;
32870
+ console.error(
32871
+ `Regenerated ${fieldsPath} (binding form \u2014 ${count} field alias${count === 1 ? "" : "es"} resolved per-install)`
32872
+ );
32873
+ } else {
32874
+ const tableIds = resolveCodegenTableIds(projectDir, meta3.queries ?? {});
32875
+ const tables = await args.client.getWorkspaceSchema(tableIds);
32876
+ const fieldsPath = writeAppFields(projectDir, tables);
32877
+ console.error(`Regenerated ${fieldsPath} (${tables.length} table${tables.length === 1 ? "" : "s"})`);
32878
+ }
33151
32879
  } catch (err2) {
33152
32880
  console.error(
33153
- `\u26A0 Could not fetch the workspace schema (${err2 instanceof Error ? err2.message : String(err2)}). Kept the existing .lotics/app_fields.ts.`
32881
+ `\u26A0 Could not regenerate .lotics/app_fields.ts (${err2 instanceof Error ? err2.message : String(err2)}). Kept the existing file.`
33154
32882
  );
33155
32883
  }
33156
32884
  await refreshWorkflowGlobals(args.client, projectDir, meta3.app_id, Object.keys(meta3.workflows ?? {}));
@@ -33834,10 +33562,8 @@ function escapeRegExp(s) {
33834
33562
  }
33835
33563
 
33836
33564
  // src/package_commands.ts
33837
- import fs6 from "node:fs";
33838
- import path7 from "node:path";
33839
- import { tmpdir as tmpdir2 } from "node:os";
33840
- import { createHash } from "node:crypto";
33565
+ import fs5 from "node:fs";
33566
+ import path6 from "node:path";
33841
33567
 
33842
33568
  // ../../node_modules/zod/v4/classic/external.js
33843
33569
  var external_exports = {};
@@ -49725,7 +49451,7 @@ var appSchema = zod_default.object({
49725
49451
  // avoid a circular import; the shapes mirror Binding / AppInstallationConfig.
49726
49452
  package_id: zod_default.string().nullable().optional().describe("App-package registry id this app was installed from; null for a bespoke app or after eject."),
49727
49453
  package_version: zod_default.number().int().nullable().optional().describe("Installed package version (the upgrade pin); null for a bespoke app or after eject."),
49728
- binding: zod_default.record(zod_default.string(), zod_default.record(zod_default.string(), zod_default.string())).nullable().optional().describe("App-package install join: per-namespace (entities/fields/options/templates/roles) alias \u2192 concrete-id maps. Null for a bespoke app."),
49454
+ binding: zod_default.record(zod_default.string(), zod_default.record(zod_default.string(), zod_default.string())).nullable().optional().describe("App-package install join: per-namespace (entities/fields/options/templates/roles/workflows/knowledge) alias \u2192 concrete-id maps. Null for a bespoke app."),
49729
49455
  config: zod_default.record(zod_default.string(), zod_default.union([zod_default.string(), zod_default.number(), zod_default.boolean()])).nullable().optional().describe("Installation-level customization config values (the customization-ladder first rung). Null for a bespoke app."),
49730
49456
  ejected_at: zod_default.string().nullable().optional().describe("When the app was ejected from its package (severing upgrades); null if never ejected."),
49731
49457
  created_at: zod_default.string().describe("Timestamp when app was created"),
@@ -49768,7 +49494,6 @@ var workspaceSchema = zod_default.object({
49768
49494
  default_currency: zod_default.string().describe("Default currency code for the workspace"),
49769
49495
  timezone: zod_default.string().describe("IANA timezone name (e.g., 'America/New_York', 'Asia/Tokyo')"),
49770
49496
  organization_id: zod_default.string().describe("Organization ID this workspace belongs to"),
49771
- is_dev: zod_default.boolean().optional().describe("Marks a throwaway app-package dev workspace \u2014 gates `lotics package dev/sync/reset`."),
49772
49497
  created_at: zod_default.string().describe("Timestamp when workspace was created")
49773
49498
  });
49774
49499
  var memberGroupSchema = zod_default.object({
@@ -50315,7 +50040,14 @@ var bindingSchema = zod_default.object({
50315
50040
  // it, and hashes look rows up through it. Excluded from BINDING_NAMESPACES:
50316
50041
  // a deleted row is a modified-core finding (fingerprints), never binding
50317
50042
  // drift, so validateBinding/applyDriftResolutions ignore it.
50318
- workflows: bindingMapSchema.default({})
50043
+ workflows: bindingMapSchema.default({}),
50044
+ // The app's BUNDLED knowledge corpus: contract knowledge alias → the
50045
+ // workspace's concrete `kdc_` id. One row, one pin — the app installation IS
50046
+ // the anchor for its bundled docs (`package_content_installations` rows are
50047
+ // standalone content packages only). Excluded from BINDING_NAMESPACES: a
50048
+ // gone/edited doc is detected by the knowledge upgrade preview (live doc +
50049
+ // content-sha reads), never by validateBinding's live-id sweep.
50050
+ knowledge: bindingMapSchema.default({})
50319
50051
  });
50320
50052
  var BINDING_NAMESPACES = ["entities", "fields", "options", "templates", "roles"];
50321
50053
  var contentBindingMapSchema = zod_default.record(zod_default.string().min(1), zod_default.string().min(1));
@@ -50349,10 +50081,6 @@ var packageArtifactHashesSchema = zod_default.object({
50349
50081
  agents: zod_default.record(zod_default.string(), zod_default.string())
50350
50082
  });
50351
50083
  var modificationResolutionSchema = zod_default.union([zod_default.literal("revert"), zod_default.literal("keep")]);
50352
- var upgradeResolutionsSchema = zod_default.record(
50353
- zod_default.string(),
50354
- zod_default.union([driftResolutionSchema, modificationResolutionSchema])
50355
- );
50356
50084
  var KNOWLEDGE_UPGRADE_CHANGES = ["added", "changed", "removed", "drifted"];
50357
50085
  var knowledgeUpgradeEntrySchema = zod_default.object({
50358
50086
  alias: zod_default.string(),
@@ -50381,15 +50109,15 @@ function validKnowledgeResolutions(change) {
50381
50109
  return [];
50382
50110
  }
50383
50111
  }
50112
+ var upgradeResolutionsSchema = zod_default.record(
50113
+ zod_default.string(),
50114
+ zod_default.union([driftResolutionSchema, modificationResolutionSchema, knowledgeResolutionSchema])
50115
+ );
50384
50116
  var knowledgeBindConsentSchema = zod_default.record(zod_default.string(), zod_default.string());
50385
50117
  var knowledgeUpgradeResolutionsSchema = zod_default.object({
50386
50118
  resolutions: zod_default.record(zod_default.string(), knowledgeResolutionSchema).default({}),
50387
50119
  bind_to: knowledgeBindConsentSchema.default({})
50388
50120
  });
50389
- var contentUpgradeResolutionsSchema = zod_default.object({
50390
- knowledge: knowledgeUpgradeResolutionsSchema.default({ resolutions: {}, bind_to: {} }),
50391
- templates: zod_default.record(zod_default.string(), modificationResolutionSchema).default({})
50392
- });
50393
50121
  var knowledgeBoundRefSchema = zod_default.object({
50394
50122
  alias: zod_default.string(),
50395
50123
  name: zod_default.string(),
@@ -50403,261 +50131,38 @@ var appInstallationConfigSchema = zod_default.record(
50403
50131
  );
50404
50132
  var appInstallationProvenanceSchema = zod_default.enum(["install", "adopt"]);
50405
50133
 
50406
- // src/generate_package_fields.ts
50407
- var HEADER5 = `// Auto-generated by 'lotics package new/extract/dev/sync' from contract.json.
50408
- // DO NOT EDIT \u2014 regenerated whenever the contract changes.
50409
- //
50410
- // Package apps resolve F/OPT/ROLE at MODULE LOAD from the installation's
50411
- // binding (contract alias \u2192 THIS workspace's concrete id) via the SDK's
50412
- // \`binding\` RPC. Top-level await: the module graph waits for the binding
50413
- // before any importer evaluates, so every entry is a plain string.
50414
- import { getAppBinding } from "@lotics/app-sdk";
50415
-
50416
- const binding = await getAppBinding();
50417
-
50418
- function bound(map: Record<string, string>, key: string, kind: string): string {
50419
- const id = map[key];
50420
- if (id === undefined) {
50421
- throw new Error(
50422
- \`app_fields: \${kind} "\${key}" is not in this installation's binding \u2014 \` +
50423
- \`the generated app_fields.ts is stale relative to the installed contract version.\`,
50424
- );
50425
- }
50426
- return id;
50427
- }
50428
- `;
50429
- function generatePackageAppFields(contract) {
50430
- const entities = contract.entities ?? [];
50431
- const roles = contract.roles ?? [];
50432
- const fieldBlocks = [];
50433
- for (const entity of entities) {
50434
- const lines = (entity.fields ?? []).map(
50435
- (field) => ` ${propKey(field.alias)}: bound(binding.fields, ${JSON.stringify(`${entity.alias}.${field.alias}`)}, "field"),`
50436
- );
50437
- if (lines.length === 0) continue;
50438
- fieldBlocks.push(` ${propKey(entity.alias.toUpperCase())}: {
50439
- ${lines.join("\n")}
50440
- },`);
50441
- }
50442
- const optionBlocks = [];
50443
- for (const entity of entities) {
50444
- const perField = [];
50445
- for (const field of entity.fields ?? []) {
50446
- const options = field.options ?? [];
50447
- if (options.length === 0) continue;
50448
- const lines = options.map(
50449
- (option) => ` ${propKey(option.alias)}: bound(binding.options, ${JSON.stringify(`${entity.alias}.${field.alias}:${option.alias}`)}, "option"),`
50450
- );
50451
- perField.push(` ${propKey(field.alias)}: {
50452
- ${lines.join("\n")}
50453
- },`);
50454
- }
50455
- if (perField.length > 0) {
50456
- optionBlocks.push(` ${propKey(entity.alias.toUpperCase())}: {
50457
- ${perField.join("\n")}
50458
- },`);
50459
- }
50460
- }
50461
- const fMap = fieldBlocks.length > 0 ? `export const F = {
50462
- ${fieldBlocks.join("\n")}
50463
- } as const;` : `export const F = {} as const;`;
50464
- const optMap = optionBlocks.length > 0 ? `export const OPT = {
50465
- ${optionBlocks.join("\n")}
50466
- } as const;` : `export const OPT = {} as const;`;
50467
- const roleMap = roles.length > 0 ? `export const ROLE = {
50468
- ${roles.map((role) => ` ${propKey(role.alias)}: bound(binding.roles, ${JSON.stringify(role.alias)}, "role"),`).join("\n")}
50469
- } as const;` : `export const ROLE = {} as const;`;
50470
- return `${HEADER5}
50471
- ${fMap}
50472
-
50473
- ${optMap}
50474
-
50475
- ${roleMap}
50476
-
50477
- /** Field-id alias map: \`F[<ENTITY>][<field>]\` is this installation's \`fld_\u2026\` id. */
50478
- export type AppFields = typeof F;
50479
- /** Select-option alias map: \`OPT[<ENTITY>][<field>][<option>]\` is this installation's \`opt_\u2026\` id. */
50480
- export type AppOptions = typeof OPT;
50481
- /** Role alias map: \`ROLE[<role>]\` is this installation's \`grp_\u2026\` id. */
50482
- export type AppRoles = typeof ROLE;
50483
- `;
50484
- }
50485
-
50486
- // src/file_command_io.ts
50487
- import fs5 from "node:fs";
50488
- import path6 from "node:path";
50489
- var CliError = class extends Error {
50490
- constructor(message) {
50491
- super(message);
50492
- this.name = "CliError";
50493
- }
50494
- };
50495
- function fail(message) {
50496
- throw new CliError(message);
50497
- }
50498
- function writeFileAtomic(filePath, bytes) {
50499
- const dir = path6.dirname(path6.resolve(filePath));
50500
- const tmp = path6.join(dir, `.${path6.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
50501
- fs5.writeFileSync(tmp, bytes);
50502
- try {
50503
- fs5.renameSync(tmp, filePath);
50504
- } catch (error51) {
50505
- try {
50506
- fs5.unlinkSync(tmp);
50507
- } catch {
50508
- }
50509
- throw error51;
50510
- }
50511
- console.error(`Wrote ${filePath}`);
50512
- }
50513
-
50514
50134
  // src/package_commands.ts
50515
- var CONTRACT_FILE = "contract.json";
50516
- var ADOPT_BINDING_FILE = "adopt_binding.json";
50517
- var PACKAGE_TEMPLATE_KINDS = /* @__PURE__ */ new Set([
50518
- "html",
50519
- "email",
50520
- "excel",
50521
- "word",
50522
- "pdf-form"
50523
- ]);
50524
50135
  function packageJsonPath(projectDir) {
50525
- return path7.join(projectDir, "package.json");
50136
+ return path6.join(projectDir, "package.json");
50526
50137
  }
50527
50138
  function isPlainObject2(value) {
50528
50139
  return typeof value === "object" && value !== null && !Array.isArray(value);
50529
50140
  }
50530
- function readPackageProject(projectDir) {
50141
+ function readLocalAppManifest(projectDir) {
50531
50142
  const pkgPath2 = packageJsonPath(projectDir);
50532
- if (!fs6.existsSync(pkgPath2)) {
50533
- throw new Error(
50534
- `No package.json in ${projectDir}. Run this inside a package project (lotics package new <name>).`
50535
- );
50536
- }
50537
- const parsed = JSON.parse(fs6.readFileSync(pkgPath2, "utf-8"));
50538
- if (!isPlainObject2(parsed)) throw new Error(`Malformed package.json in ${projectDir}.`);
50539
- const lotics = parsed.lotics;
50540
- const pkg2 = isPlainObject2(lotics) ? lotics.package : void 0;
50541
- if (!isPlainObject2(pkg2) || typeof pkg2.name !== "string") {
50542
- throw new Error(
50543
- `${pkgPath2} has no lotics.package manifest \u2014 not a package project. Use "lotics package new <name>".`
50544
- );
50545
- }
50546
- const dev = {};
50547
- if (isPlainObject2(pkg2.dev)) {
50548
- for (const [ws, entry] of Object.entries(pkg2.dev)) {
50549
- if (isPlainObject2(entry) && typeof entry.app_id === "string" && typeof entry.version === "number") {
50550
- dev[ws] = { app_id: entry.app_id, version: entry.version };
50551
- }
50552
- }
50553
- }
50554
- const knowledge = {};
50555
- if (isPlainObject2(pkg2.knowledge)) {
50556
- for (const [alias, entry] of Object.entries(pkg2.knowledge)) {
50557
- if (!isPlainObject2(entry) || typeof entry.name !== "string" || entry.name.length === 0) {
50558
- throw new Error(
50559
- `${pkgPath2}: lotics.package.knowledge.${alias} must be an object with a non-empty "name".`
50560
- );
50561
- }
50562
- knowledge[alias] = {
50563
- name: entry.name,
50564
- description: typeof entry.description === "string" ? entry.description : null,
50565
- active_by_default: typeof entry.active_by_default === "boolean" ? entry.active_by_default : true
50566
- };
50567
- }
50568
- }
50569
- const templates = {};
50570
- if (isPlainObject2(pkg2.templates)) {
50571
- for (const [alias, entry] of Object.entries(pkg2.templates)) {
50572
- if (!isPlainObject2(entry) || typeof entry.name !== "string" || entry.name.length === 0 || typeof entry.type !== "string" || !PACKAGE_TEMPLATE_KINDS.has(entry.type) || typeof entry.file !== "string" || entry.file.length === 0) {
50573
- throw new Error(
50574
- `${pkgPath2}: lotics.package.templates.${alias} must be an object with a non-empty "name", a "type" of html|email|excel|word|pdf-form, and a non-empty "file" (a name in templates/).`
50575
- );
50143
+ if (!fs5.existsSync(pkgPath2)) return null;
50144
+ const parsed = JSON.parse(fs5.readFileSync(pkgPath2, "utf-8"));
50145
+ if (!isPlainObject2(parsed)) return null;
50146
+ const lotics = isPlainObject2(parsed.lotics) ? parsed.lotics : {};
50147
+ const app_id = typeof lotics.app_id === "string" ? lotics.app_id : null;
50148
+ const knowledge = [];
50149
+ if (Array.isArray(lotics.knowledge)) {
50150
+ for (const entry of lotics.knowledge) {
50151
+ if (isPlainObject2(entry) && typeof entry.alias === "string" && typeof entry.doc_id === "string") {
50152
+ knowledge.push({ alias: entry.alias, doc_id: entry.doc_id });
50576
50153
  }
50577
- templates[alias] = {
50578
- name: entry.name,
50579
- type: entry.type,
50580
- file: entry.file
50581
- };
50582
50154
  }
50583
50155
  }
50584
- const knowledge_expects = Array.isArray(pkg2.knowledge_expects) ? pkg2.knowledge_expects.filter((v) => typeof v === "string") : [];
50585
- const manifest = {
50586
- id: typeof pkg2.id === "string" ? pkg2.id : null,
50587
- name: pkg2.name,
50588
- description: typeof pkg2.description === "string" ? pkg2.description : null,
50589
- kind: pkg2.kind === "content" ? "content" : "app",
50590
- version: typeof pkg2.version === "number" ? pkg2.version : null,
50591
- knowledge,
50592
- templates,
50593
- knowledge_expects,
50594
- dev
50595
- };
50596
- return { pkgJson: parsed, manifest };
50597
- }
50598
- function writePackageManifest(projectDir, project) {
50599
- const next = {
50600
- ...project.pkgJson,
50601
- lotics: {
50602
- ...isPlainObject2(project.pkgJson.lotics) ? project.pkgJson.lotics : {},
50603
- package: project.manifest
50604
- }
50605
- };
50606
- writeFileAtomic(
50607
- packageJsonPath(projectDir),
50608
- new TextEncoder().encode(JSON.stringify(next, null, 2) + "\n")
50609
- );
50156
+ return { app_id, knowledge };
50610
50157
  }
50611
- function sanitizePackageJsonForSource(pkgJson) {
50612
- const lotics = isPlainObject2(pkgJson.lotics) ? pkgJson.lotics : {};
50613
- const pkg2 = isPlainObject2(lotics.package) ? lotics.package : {};
50614
- const { dev: _dev, ...pkgWithoutDev } = pkg2;
50615
- return {
50616
- ...pkgJson,
50617
- lotics: { ...lotics, package: pkgWithoutDev }
50618
- };
50619
- }
50620
- function draftPackageProjectFromApp(appPkgJson, args) {
50621
- const { lotics: _appManifest, ...rest } = appPkgJson;
50622
- return {
50623
- pkgJson: rest,
50624
- manifest: {
50625
- id: null,
50626
- name: args.name,
50627
- description: args.description,
50628
- // Extraction promotes a bespoke APP to a package — always app kind.
50629
- kind: "app",
50630
- version: null,
50631
- knowledge: {},
50632
- templates: {},
50633
- knowledge_expects: [],
50634
- dev: {}
50635
- }
50636
- };
50637
- }
50638
- function parseAdoptBindingFile(raw, expectedAppId) {
50639
- if (!isPlainObject2(raw) || typeof raw.app_id !== "string" || typeof raw.workspace_id !== "string" || !isPlainObject2(raw.binding)) {
50640
- throw new Error(
50641
- "Malformed .lotics/adopt_binding.json \u2014 expected { app_id, workspace_id, binding }."
50642
- );
50643
- }
50644
- if (raw.app_id !== expectedAppId) {
50645
- throw new Error(
50646
- `.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.`
50647
- );
50648
- }
50649
- const knowledge_binding = {};
50650
- if (isPlainObject2(raw.knowledge_binding)) {
50651
- for (const [alias, id] of Object.entries(raw.knowledge_binding)) {
50652
- if (typeof id === "string") knowledge_binding[alias] = id;
50158
+ function parseRenameFlags(renames) {
50159
+ return renames.map((entry) => {
50160
+ const eq = entry.indexOf("=");
50161
+ if (eq <= 0 || eq === entry.length - 1) {
50162
+ throw new Error(`Invalid --rename "${entry}" \u2014 expected old=new (an alias to rename before v1 freezes it).`);
50653
50163
  }
50654
- }
50655
- return {
50656
- app_id: raw.app_id,
50657
- workspace_id: raw.workspace_id,
50658
- binding: raw.binding,
50659
- knowledge_binding
50660
- };
50164
+ return { from: entry.slice(0, eq), to: entry.slice(eq + 1) };
50165
+ });
50661
50166
  }
50662
50167
  function formatExtractReport(report) {
50663
50168
  const order = ["error", "warning", "info"];
@@ -50666,573 +50171,32 @@ function formatExtractReport(report) {
50666
50171
  );
50667
50172
  return { lines, hasError: report.some((f) => f.severity === "error") };
50668
50173
  }
50669
- function dotLoticsDirEnsured(projectDir) {
50670
- const dir = path7.join(projectDir, ".lotics");
50671
- fs6.mkdirSync(dir, { recursive: true });
50672
- return dir;
50673
- }
50674
- function cmpVersions(a, b) {
50675
- const pa = a.split(".").map(Number);
50676
- const pb = b.split(".").map(Number);
50677
- for (let i2 = 0; i2 < 3; i2++) {
50678
- const d = (pa[i2] || 0) - (pb[i2] || 0);
50679
- if (d !== 0) return d;
50680
- }
50681
- return 0;
50682
- }
50683
- function packageSdkRange(sdkLatest) {
50684
- const version2 = sdkLatest !== null && cmpVersions(sdkLatest, STARTER_FALLBACK_SDK_VERSION) > 0 ? sdkLatest : STARTER_FALLBACK_SDK_VERSION;
50685
- return `^${version2}`;
50686
- }
50687
- function readContract(projectDir) {
50688
- const contractPath = path7.join(projectDir, CONTRACT_FILE);
50689
- if (!fs6.existsSync(contractPath)) {
50690
- throw new Error(
50691
- `No ${CONTRACT_FILE} in ${projectDir}. A package project declares its data model there.`
50692
- );
50693
- }
50694
- return JSON.parse(fs6.readFileSync(contractPath, "utf-8"));
50695
- }
50696
- function foldKnowledgeIntoContract(projectDir, project, contract) {
50697
- const aliases = Object.keys(project.manifest.knowledge);
50698
- const expects = project.manifest.knowledge_expects;
50699
- if (aliases.length === 0 && expects.length === 0) return contract;
50700
- const knowledge = {};
50701
- for (const alias of aliases) {
50702
- const entry = project.manifest.knowledge[alias];
50703
- const contentRef = `knowledge/${alias}.md`;
50704
- const filePath = path7.join(projectDir, "knowledge", `${alias}.md`);
50705
- if (!fs6.existsSync(filePath)) {
50706
- throw new Error(
50707
- `Declared knowledge doc "${alias}" (lotics.package.knowledge) has no ${contentRef} in the project. Create the file, or remove the manifest entry.`
50708
- );
50709
- }
50710
- const bytes = fs6.readFileSync(filePath);
50711
- knowledge[alias] = {
50712
- name: entry.name,
50713
- description: entry.description ?? "",
50714
- content_ref: contentRef,
50715
- content_sha256: createHash("sha256").update(bytes).digest("hex"),
50716
- active_by_default: entry.active_by_default
50717
- };
50718
- }
50719
- return { ...contract, knowledge, knowledge_expects: expects };
50720
- }
50721
- function foldTemplatesIntoContract(projectDir, project, contract) {
50722
- if (!isPlainObject2(contract)) {
50723
- throw new Error(`${CONTRACT_FILE} in ${projectDir} must be a JSON object.`);
50724
- }
50725
- const aliases = Object.keys(project.manifest.templates);
50726
- if (aliases.length === 0) return contract;
50727
- const existing = Array.isArray(contract.templates) ? contract.templates : [];
50728
- const existingAliases = new Set(
50729
- existing.filter((t) => isPlainObject2(t)).map((t) => t.alias).filter((a) => typeof a === "string")
50730
- );
50731
- const folded = aliases.map((alias) => {
50732
- if (existingAliases.has(alias)) {
50733
- throw new Error(
50734
- `Template alias "${alias}" is declared in both lotics.package.templates and ${CONTRACT_FILE} \u2014 declare each template once.`
50735
- );
50736
- }
50737
- const entry = project.manifest.templates[alias];
50738
- const filePath = path7.join(projectDir, "templates", entry.file);
50739
- if (!fs6.existsSync(filePath)) {
50740
- throw new Error(
50741
- `Declared template "${alias}" (lotics.package.templates) has no templates/${entry.file} in the project. Stage the file, then rebuild.`
50742
- );
50743
- }
50744
- if (entry.type === "html" || entry.type === "email") {
50745
- return { alias, label: entry.name, type: entry.type, content: fs6.readFileSync(filePath, "utf-8") };
50746
- }
50747
- return { alias, label: entry.name, type: entry.type, bytes_ref: `templates/${entry.file}` };
50748
- });
50749
- return { ...contract, templates: [...existing, ...folded] };
50750
- }
50751
- function foldTemplateShasIntoContract(projectDir, contract) {
50752
- const templates = contract.templates;
50753
- if (!Array.isArray(templates) || templates.length === 0) return contract;
50754
- const withShas = templates.map((template) => {
50755
- if (!isPlainObject2(template) || typeof template.type !== "string" || typeof template.alias !== "string") {
50756
- return template;
50757
- }
50758
- if (template.type === "html" || template.type === "email") {
50759
- if (typeof template.content !== "string") {
50760
- throw new Error(`Inline template "${template.alias}" must carry a string "content".`);
50761
- }
50762
- return {
50763
- ...template,
50764
- content_sha256: createHash("sha256").update(Buffer.from(template.content, "utf-8")).digest("hex")
50765
- };
50766
- }
50767
- if (typeof template.bytes_ref !== "string") {
50768
- throw new Error(`File-backed template "${template.alias}" must carry a string "bytes_ref".`);
50769
- }
50770
- const filePath = path7.join(projectDir, template.bytes_ref);
50771
- if (!fs6.existsSync(filePath)) {
50772
- throw new Error(
50773
- `Template "${template.alias}" references "${template.bytes_ref}", which is not in the project. Stage the file, then rebuild.`
50774
- );
50775
- }
50776
- const bytes = fs6.readFileSync(filePath);
50777
- return { ...template, content_sha256: createHash("sha256").update(bytes).digest("hex") };
50778
- });
50779
- return { ...contract, templates: withShas };
50780
- }
50781
- function writePackageAppFields(projectDir) {
50782
- const contract = readContract(projectDir);
50783
- const dotLotics = path7.join(projectDir, ".lotics");
50784
- fs6.mkdirSync(dotLotics, { recursive: true });
50785
- fs6.writeFileSync(path7.join(dotLotics, "app_fields.ts"), generatePackageAppFields(contract));
50786
- console.error("Wrote .lotics/app_fields.ts (contract-derived, runtime-resolved)");
50787
- }
50788
- var SOURCE_STAGE_EXCLUDES = /* @__PURE__ */ new Set([
50789
- "node_modules",
50790
- "dist",
50791
- ".lotics",
50792
- ".git",
50793
- "bundle.tar.gz",
50794
- "package.json"
50795
- ]);
50796
- function stagePackageSource(projectDir, sourceStage) {
50797
- const project = readPackageProject(projectDir);
50798
- fs6.mkdirSync(sourceStage, { recursive: true });
50799
- for (const entry of fs6.readdirSync(projectDir)) {
50800
- if (SOURCE_STAGE_EXCLUDES.has(entry) || entry.endsWith(".tsbuildinfo")) continue;
50801
- fs6.cpSync(path7.join(projectDir, entry), path7.join(sourceStage, entry), { recursive: true });
50802
- }
50803
- fs6.writeFileSync(
50804
- path7.join(sourceStage, "package.json"),
50805
- JSON.stringify(sanitizePackageJsonForSource(project.pkgJson), null, 2) + "\n"
50806
- );
50807
- }
50808
- async function buildPackageBundle(projectDir) {
50809
- const { manifest } = readPackageProject(projectDir);
50810
- const stage = fs6.mkdtempSync(path7.join(tmpdir2(), "lotics-pkg-"));
50811
- const bundlePath = path7.join(tmpdir2(), `lotics-bundle-${Date.now()}.tar.gz`);
50812
- try {
50813
- let distDir;
50814
- if (manifest.kind === "content") {
50815
- console.error("Content package \u2014 skipping app build.");
50816
- distDir = path7.join(stage, "empty-dist");
50817
- fs6.mkdirSync(distDir, { recursive: true });
50818
- fs6.writeFileSync(
50819
- path7.join(distDir, ".content-package"),
50820
- "Content package \u2014 no frontend bundle. Content ships in source.tar.gz under knowledge/.\n"
50821
- );
50822
- } else {
50823
- console.error("Building...");
50824
- await runNpm(["run", "build"], projectDir);
50825
- distDir = path7.join(projectDir, "dist");
50826
- if (!fs6.existsSync(distDir)) {
50827
- throw new Error(
50828
- `Build did not produce a dist/ directory in ${projectDir}. Check that 'npm run build' is configured correctly.`
50829
- );
50830
- }
50831
- }
50832
- console.error("Packaging source...");
50833
- const sourceStage = path7.join(stage, "source");
50834
- stagePackageSource(projectDir, sourceStage);
50835
- await runTar(["-czf", path7.join(stage, "source.tar.gz"), "-C", sourceStage, "."], projectDir);
50836
- console.error("Packaging dist...");
50837
- await runTar(["-czf", path7.join(stage, "dist.tar.gz"), "-C", distDir, "."], projectDir);
50838
- console.error("Bundling...");
50839
- await runTar(["-czf", bundlePath, "source.tar.gz", "dist.tar.gz"], stage);
50840
- return fs6.readFileSync(bundlePath);
50841
- } finally {
50842
- fs6.rmSync(stage, { recursive: true, force: true });
50843
- if (fs6.existsSync(bundlePath)) fs6.unlinkSync(bundlePath);
50844
- }
50845
- }
50846
- function starterContract(name) {
50847
- return {
50848
- entities: [
50849
- {
50850
- alias: "item",
50851
- label: name,
50852
- description: `Records managed by the ${name} package.`,
50853
- fields: [
50854
- { alias: "name", label: "Name", type: "text", required: true },
50855
- { alias: "notes", label: "Notes", type: "text" },
50856
- {
50857
- alias: "status",
50858
- label: "Status",
50859
- type: "select",
50860
- options: [
50861
- { alias: "open", label: "Open", color: "blue" },
50862
- { alias: "done", label: "Done", color: "green" }
50863
- ]
50864
- }
50865
- ]
50866
- }
50867
- ],
50868
- roles: [],
50869
- templates: [],
50870
- queries: [
50871
- {
50872
- alias: "items",
50873
- ast: {
50874
- kind: "from_table",
50875
- from_entity: "item",
50876
- sort: [{ field_key: "name", order: "asc" }]
50877
- }
50878
- }
50879
- ],
50880
- workflows: [],
50881
- agents: [],
50882
- config: [
50883
- { alias: "heading", label: "List heading", type: "text", default: name }
50884
- ]
50885
- };
50886
- }
50887
- function emptyContract() {
50888
- return {
50889
- entities: [],
50890
- roles: [],
50891
- templates: [],
50892
- queries: [],
50893
- workflows: [],
50894
- agents: [],
50895
- config: []
50896
- };
50897
- }
50898
- function scaffoldContentPackage(name, targetPath) {
50899
- const exampleAlias = "overview";
50900
- const manifest = {
50901
- id: null,
50902
- name,
50903
- description: null,
50904
- kind: "content",
50905
- version: null,
50906
- knowledge: {
50907
- [exampleAlias]: {
50908
- name: `${name} Overview`,
50909
- description: "What this corpus covers \u2014 replace with your doc's summary.",
50910
- active_by_default: true
50911
- }
50912
- },
50913
- // A content package can also ship standalone document templates — declare them
50914
- // under `lotics.package.templates` with the files in a `templates/` dir. See
50915
- // the commented example printed by the scaffold + docs/packages.md § Content.
50916
- templates: {},
50917
- knowledge_expects: [],
50918
- dev: {}
50919
- };
50920
- const pkgJson = {
50921
- name: appDirName(name),
50922
- private: true,
50923
- version: "0.0.0",
50924
- lotics: { package: manifest }
50925
- };
50926
- fs6.writeFileSync(
50927
- packageJsonPath(targetPath),
50928
- JSON.stringify(pkgJson, null, 2) + "\n"
50929
- );
50930
- fs6.writeFileSync(
50931
- path7.join(targetPath, CONTRACT_FILE),
50932
- JSON.stringify(emptyContract(), null, 2) + "\n"
50933
- );
50934
- const knowledgeDir = path7.join(targetPath, "knowledge");
50935
- fs6.mkdirSync(knowledgeDir, { recursive: true });
50936
- fs6.writeFileSync(
50937
- path7.join(knowledgeDir, `${exampleAlias}.md`),
50938
- `# ${name}
50939
-
50940
- Replace this file with the corpus content. Structure it under clear
50941
- Markdown headers so an agent can outline and read it by section.
50942
- `
50943
- );
50944
- console.error(`Scaffolded a content package into ${targetPath}`);
50945
- console.error(`
50946
- Ready. Next steps:`);
50947
- console.error(` cd ${path7.relative(process.cwd(), targetPath) || "."}`);
50948
- console.error(` # add docs: create knowledge/<alias>.md + a lotics.package.knowledge.<alias> entry`);
50949
- console.error(` # (name/description/active_by_default), then publish:`);
50950
- console.error(` # add templates: create templates/<file> + a lotics.package.templates.<alias> entry:`);
50951
- console.error(` # "templates": { "quote": { "name": "Quote", "type": "html", "file": "quote.html" } }`);
50952
- console.error(` # inline kinds html|email read templates/<file> as content; file-backed`);
50953
- console.error(` # excel|word|pdf-form pack the bytes into the bundle (bytes_ref = templates/<file>).`);
50954
- console.error(` lotics package publish -m "v1"`);
50955
- console.error(` lotics package install <package_id>`);
50956
- }
50957
- async function packageNew(args) {
50958
- const targetPath = path7.resolve(args.targetPath ?? appDirName(args.name));
50959
- if (fs6.existsSync(targetPath) && fs6.readdirSync(targetPath).length > 0) {
50960
- throw new Error(`Target directory ${targetPath} is not empty.`);
50961
- }
50962
- fs6.mkdirSync(targetPath, { recursive: true });
50963
- if ((args.kind ?? "app") === "content") {
50964
- scaffoldContentPackage(args.name, targetPath);
50965
- return;
50966
- }
50967
- const [uiLatest, sdkLatest] = await Promise.all([
50968
- fetchLatestNpmVersion("@lotics/ui"),
50969
- fetchLatestNpmVersion("@lotics/app-sdk")
50970
- ]);
50971
- const files = buildStarterTemplate({
50972
- app_name: args.name,
50973
- app_id: "",
50974
- workspace_id: "",
50975
- ui_version: uiLatest ? `^${uiLatest}` : void 0,
50976
- sdk_version: packageSdkRange(sdkLatest)
50977
- });
50978
- const overrides = new Map(
50979
- buildPackageStarterOverrides({ app_name: args.name }).map((f) => [f.path, f.content])
50980
- );
50981
- for (const file2 of files) {
50982
- const fullPath = path7.join(targetPath, file2.path);
50983
- if (file2.path === "package.json") {
50984
- const pkg2 = JSON.parse(file2.content);
50985
- const manifest = {
50986
- id: null,
50987
- name: args.name,
50988
- description: null,
50989
- kind: "app",
50990
- version: null,
50991
- knowledge: {},
50992
- templates: {},
50993
- knowledge_expects: [],
50994
- dev: {}
50995
- };
50996
- pkg2.lotics = { package: manifest };
50997
- fs6.mkdirSync(path7.dirname(fullPath), { recursive: true });
50998
- fs6.writeFileSync(fullPath, JSON.stringify(pkg2, null, 2) + "\n");
50999
- continue;
51000
- }
51001
- fs6.mkdirSync(path7.dirname(fullPath), { recursive: true });
51002
- fs6.writeFileSync(fullPath, overrides.get(file2.path) ?? file2.content);
51003
- }
51004
- fs6.writeFileSync(
51005
- path7.join(targetPath, CONTRACT_FILE),
51006
- JSON.stringify(starterContract(args.name), null, 2) + "\n"
51007
- );
51008
- writePackageAppFields(targetPath);
51009
- console.error(`Scaffolded ${files.length + 1} files into ${targetPath}`);
51010
- console.error("Installing npm dependencies...");
51011
- await runNpm(["install"], targetPath);
51012
- console.error(`
51013
- Ready. Next steps:`);
51014
- console.error(` cd ${path7.relative(process.cwd(), targetPath) || "."}`);
51015
- console.error(` # edit contract.json + src/App.tsx, then run it against a dev workspace:`);
51016
- console.error(` lotics workspace create "<name> dev" --dev`);
51017
- console.error(` lotics package dev --workspace <dev_ws>`);
51018
- }
51019
- async function packageBuild(args) {
51020
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
51021
- readPackageProject(projectDir);
51022
- const bundle = await buildPackageBundle(projectDir);
51023
- const out = path7.join(projectDir, "bundle.tar.gz");
51024
- fs6.writeFileSync(out, bundle);
51025
- console.error(`Built ${out} (${(bundle.byteLength / 1024).toFixed(1)} KB)`);
51026
- }
51027
- async function publishVersion(client, projectDir, opts) {
51028
- const project = readPackageProject(projectDir);
51029
- const rawContract = readContract(projectDir);
51030
- if (!isPlainObject2(rawContract)) {
51031
- throw new Error(`${CONTRACT_FILE} in ${projectDir} must be a JSON object.`);
51032
- }
51033
- const contract = foldTemplateShasIntoContract(
51034
- projectDir,
51035
- foldKnowledgeIntoContract(
51036
- projectDir,
51037
- project,
51038
- foldTemplatesIntoContract(projectDir, project, rawContract)
51039
- )
51040
- );
51041
- let packageId = project.manifest.id;
51042
- if (packageId === null) {
51043
- const pkg2 = await client.createPackage({
51044
- name: project.manifest.name,
51045
- description: project.manifest.description,
51046
- // Kind is fixed at package creation and immutable after — a knowledge
51047
- // package must be created as such so publish enforces its constraint.
51048
- kind: project.manifest.kind
51049
- });
51050
- packageId = pkg2.id;
51051
- project.manifest.id = packageId;
51052
- writePackageManifest(projectDir, project);
51053
- console.error(`Created package ${pkg2.name} (${pkg2.id}).`);
51054
- }
51055
- const bundle = await buildPackageBundle(projectDir);
51056
- console.error("Publishing version...");
51057
- const version2 = await client.publishPackageVersion(packageId, {
51058
- contract,
51059
- bundle,
51060
- changelog: opts.changelog ?? null,
51061
- channel: opts.channel ?? "release"
51062
- });
51063
- project.manifest.version = version2.version;
51064
- writePackageManifest(projectDir, project);
51065
- return { package_id: packageId, version: version2.version };
51066
- }
51067
- async function packagePublish(client, args) {
51068
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
51069
- const { package_id, version: version2 } = await publishVersion(client, projectDir, {
51070
- changelog: args.changelog
51071
- });
51072
- console.error(`Published ${package_id} v${version2}.`);
51073
- console.error(` Install it: lotics package install ${package_id} --version ${version2}`);
51074
- }
51075
- function assertDevWorkspace(workspace) {
51076
- if (workspace.is_dev === true) return;
51077
- throw new Error(
51078
- `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>.`
51079
- );
51080
- }
51081
- async function syncToDevWorkspace(client, projectDir) {
51082
- const devWorkspaceId = client.getWorkspaceId();
51083
- if (!devWorkspaceId) {
51084
- throw new Error(
51085
- "No dev workspace selected. Pass --workspace <dev_ws> (a workspace created with --dev)."
51086
- );
51087
- }
51088
- const workspace = await client.getWorkspaceInfo(devWorkspaceId);
51089
- if (!workspace) {
51090
- throw new Error(
51091
- `Workspace ${devWorkspaceId} is not accessible with these credentials. Pass --workspace <dev_ws> (a workspace created with --dev).`
51092
- );
51093
- }
51094
- assertDevWorkspace(workspace);
51095
- writePackageAppFields(projectDir);
51096
- const { package_id, version: version2 } = await publishVersion(client, projectDir, {
51097
- changelog: "dev sync",
51098
- channel: "dev"
51099
- });
51100
- const project = readPackageProject(projectDir);
51101
- const existing = project.manifest.dev[devWorkspaceId];
51102
- let appId;
51103
- if (existing) {
51104
- console.error(`Upgrading dev installation ${existing.app_id} \u2192 v${version2}...`);
51105
- const app = await client.upgradePackage(existing.app_id, { version: version2 });
51106
- appId = app.id;
51107
- } else {
51108
- console.error(`Installing ${package_id} v${version2} into dev workspace ${devWorkspaceId}...`);
51109
- const result = await client.installPackage(package_id, { version: version2 });
51110
- if (result.kind !== "app") {
51111
- throw new Error(
51112
- `Package ${package_id} is a content package \u2014 "lotics package dev/sync" run an app installation. Install a content package with "lotics package install ${package_id}".`
51113
- );
51114
- }
51115
- appId = result.app.id;
51116
- }
51117
- project.manifest.dev[devWorkspaceId] = { app_id: appId, version: version2 };
51118
- writePackageManifest(projectDir, project);
51119
- const installed = await client.getApp(appId);
51120
- writeAppDts(projectDir, {
51121
- workflows: installed.workflows ?? void 0,
51122
- queries: installed.queries ?? void 0,
51123
- agents: installed.agents ?? void 0
51124
- });
51125
- return { app_id: appId, app_name: installed.name, workspace_id: devWorkspaceId, version: version2 };
51126
- }
51127
- async function packageSync(client, args) {
51128
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
51129
- const result = await syncToDevWorkspace(client, projectDir);
51130
- console.error(
51131
- `Synced ${result.app_name} v${result.version} \u2192 ${result.app_id} (workspace ${result.workspace_id}).`
51132
- );
51133
- }
51134
- async function packageDev(client, args) {
51135
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
51136
- const synced = await syncToDevWorkspace(client, projectDir);
51137
- const handle = await startDevServer({
51138
- projectDir,
51139
- app_id: synced.app_id,
51140
- app_name: synced.app_name,
51141
- workspace_id: synced.workspace_id,
51142
- api_url: client.baseUrl,
51143
- port: args.port,
51144
- vitePort: args.vitePort,
51145
- client
51146
- });
51147
- await handle.ready;
51148
- const url2 = `http://localhost:${handle.port}`;
51149
- console.error(`
51150
- lotics package dev`);
51151
- console.error(` package: ${synced.app_name} (dev installation ${synced.app_id} v${synced.version})`);
51152
- console.error(` workspace: ${synced.workspace_id} (dev)`);
51153
- if (client.viewAsMemberId) {
51154
- console.error(` view as: ${client.viewAsMemberId}`);
51155
- }
51156
- console.error(` vite: http://localhost:${handle.vitePort}/`);
51157
- console.error(` open: ${url2}`);
51158
- console.error(` rpc: ${client.baseUrl} (via Bearer API key)
51159
- `);
51160
- console.error(` Edit contract.json then re-run "lotics package dev" / "lotics package sync" to migrate.`);
51161
- console.error(` Ctrl-C to stop.
51162
- `);
51163
- openBrowser(url2);
51164
- await new Promise((resolve2) => {
51165
- const onSig = () => {
51166
- process.off("SIGINT", onSig);
51167
- process.off("SIGTERM", onSig);
51168
- resolve2();
51169
- };
51170
- process.on("SIGINT", onSig);
51171
- process.on("SIGTERM", onSig);
51172
- });
51173
- console.error("\nStopping\u2026");
51174
- await handle.stop();
51175
- }
51176
- async function packageReset(client, args) {
51177
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
51178
- const devWorkspaceId = client.getWorkspaceId();
51179
- if (!devWorkspaceId) {
51180
- throw new Error(
51181
- "No dev workspace selected. Pass --workspace <dev_ws> (a workspace created with --dev)."
51182
- );
51183
- }
51184
- const { manifest } = readPackageProject(projectDir);
51185
- const pin = manifest.dev[devWorkspaceId];
51186
- if (!pin) {
51187
- throw new Error(
51188
- `No dev installation recorded for workspace ${devWorkspaceId}. Run "lotics package dev --workspace ${devWorkspaceId}" first.`
51189
- );
51190
- }
51191
- console.error(`Resetting dev installation ${pin.app_id} in workspace ${devWorkspaceId}...`);
51192
- const app = await client.resetPackage(pin.app_id);
51193
- console.error(`Reset ${app.name} \u2192 ${app.id}. The scaffolded tables were dropped and re-created clean.`);
51194
- }
51195
- function resolveInstallationAppId(client, explicit) {
50174
+ function resolveInstallationAppId(explicit) {
51196
50175
  if (explicit) return explicit;
51197
- const workspaceId = client.getWorkspaceId();
51198
- if (!workspaceId) {
51199
- throw new Error("Pass an app id (lotics package doctor <app_id>) or select a workspace.");
51200
- }
51201
- let manifest;
51202
- try {
51203
- ({ manifest } = readPackageProject(path7.resolve(process.cwd())));
51204
- } catch {
51205
- throw new Error(
51206
- "No app id given and the current directory is not a package project. Pass one explicitly: lotics package doctor <app_id>."
51207
- );
51208
- }
51209
- const pin = manifest.dev[workspaceId];
51210
- if (!pin) {
51211
- throw new Error(
51212
- `No app id given and no dev installation recorded for workspace ${workspaceId}. Pass one explicitly: lotics package doctor <app_id>.`
51213
- );
51214
- }
51215
- return pin.app_id;
50176
+ throw new Error("Pass an app id \u2014 e.g. lotics package doctor <app_id>.");
51216
50177
  }
50178
+ var RESOLVE_VERBS = /* @__PURE__ */ new Set(["recreate", "revert", "keep", "apply", "archive", "unbind"]);
51217
50179
  function parseResolveFlags(resolve2) {
51218
50180
  const resolutions = {};
51219
50181
  for (const entry of resolve2) {
51220
50182
  const eq = entry.indexOf("=");
51221
50183
  if (eq <= 0 || eq === entry.length - 1) {
51222
50184
  throw new Error(
51223
- `Invalid --resolve "${entry}" \u2014 expected <key>=recreate|revert|keep or <key>=<existing_id>.`
50185
+ `Invalid --resolve "${entry}" \u2014 expected <key>=<verb> (recreate|revert|keep|apply|archive|unbind) or <key>=<existing_id>.`
51224
50186
  );
51225
50187
  }
51226
50188
  const key = entry.slice(0, eq);
51227
50189
  const value = entry.slice(eq + 1);
51228
- resolutions[key] = value === "recreate" || value === "revert" || value === "keep" ? value : { bind_to: value };
50190
+ resolutions[key] = RESOLVE_VERBS.has(value) ? value : { bind_to: value };
51229
50191
  }
51230
50192
  return resolutions;
51231
50193
  }
51232
50194
  async function packageDoctor(client, args) {
51233
- const app_id = resolveInstallationAppId(client, args.app_id);
50195
+ const app_id = resolveInstallationAppId(args.app_id);
51234
50196
  const health = await client.getPackageHealth(app_id);
51235
- console.error(`${health.package_name} \u2014 installation ${health.app_id}`);
50197
+ console.error(
50198
+ `${health.package_name} \u2014 installation ${health.app_id}` + (health.is_origin ? " (origin \u2014 this app IS installation #1, the release working copy)" : "")
50199
+ );
51236
50200
  console.error(
51237
50201
  ` Installed: v${health.installed_version} Latest: v${health.latest_version}` + (health.update_available ? " \u2192 update available" : "")
51238
50202
  );
@@ -51245,12 +50209,22 @@ async function packageDoctor(client, args) {
51245
50209
  }
51246
50210
  console.error(
51247
50211
  ` Resolve while upgrading:
51248
- lotics package upgrade ${app_id} --resolve <namespace.alias>=recreate (or =<existing_id> to re-point)`
50212
+ lotics upgrade ${app_id} --resolve <namespace.alias>=recreate (or =<existing_id> to re-point)`
51249
50213
  );
51250
50214
  process.exitCode = 1;
51251
50215
  }
51252
50216
  if (health.modified.length === 0) {
51253
- console.error(" Package artifacts: pristine \u2014 no local edits an upgrade would revert.");
50217
+ 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."
50219
+ );
50220
+ } else if (health.is_origin) {
50221
+ console.error(
50222
+ ` Changed since v${health.installed_version} (${health.modified.length}) \u2014 a release will publish these:`
50223
+ );
50224
+ for (const m of health.modified) {
50225
+ console.error(` - ${m.kind}.${m.alias}`);
50226
+ }
50227
+ console.error(` Cut the next version: lotics app release ${app_id} -m "<what changed>"`);
51254
50228
  } else {
51255
50229
  console.error(` Locally modified package artifacts (${health.modified.length}):`);
51256
50230
  for (const m of health.modified) {
@@ -51258,7 +50232,7 @@ async function packageDoctor(client, args) {
51258
50232
  }
51259
50233
  console.error(
51260
50234
  ` Consent while upgrading:
51261
- lotics package upgrade ${app_id} --resolve <kind.alias>=revert (or =keep to retain the edit)`
50235
+ lotics upgrade ${app_id} --resolve <kind.alias>=revert (or =keep to retain the edit)`
51262
50236
  );
51263
50237
  process.exitCode = 1;
51264
50238
  }
@@ -51269,9 +50243,18 @@ async function packageDoctor(client, args) {
51269
50243
  }
51270
50244
  }
51271
50245
  if (health.knowledge_modified.length > 0) {
51272
- console.error(` Locally edited package knowledge (${health.knowledge_modified.length}):`);
51273
- for (const m of health.knowledge_modified) {
51274
- console.error(` - ${m.alias} "${m.name}" (an upgrade overwrites this unless kept)`);
50246
+ if (health.is_origin) {
50247
+ console.error(
50248
+ ` Knowledge changed since v${health.installed_version} (${health.knowledge_modified.length}) \u2014 a release will re-snapshot these:`
50249
+ );
50250
+ for (const m of health.knowledge_modified) {
50251
+ console.error(` - ${m.alias} "${m.name}"`);
50252
+ }
50253
+ } else {
50254
+ console.error(` Locally edited package knowledge (${health.knowledge_modified.length}):`);
50255
+ for (const m of health.knowledge_modified) {
50256
+ console.error(` - ${m.alias} "${m.name}" (an upgrade overwrites this unless kept)`);
50257
+ }
51275
50258
  }
51276
50259
  }
51277
50260
  if (health.missing_expected_docs.length > 0) {
@@ -51280,14 +50263,14 @@ async function packageDoctor(client, args) {
51280
50263
  console.error(` - "${name}" (the package's agents route to this name; no matching doc exists)`);
51281
50264
  }
51282
50265
  }
51283
- if (health.knowledge_drift.length > 0 || health.knowledge_modified.length > 0) {
50266
+ if (health.knowledge_drift.length > 0 || health.knowledge_modified.length > 0 && !health.is_origin) {
51284
50267
  process.exitCode = 1;
51285
50268
  }
51286
50269
  if (health.knowledge_drift.length === 0 && health.knowledge_modified.length === 0 && health.missing_expected_docs.length === 0) {
51287
50270
  console.error(" Knowledge: healthy \u2014 bound docs resolve, none locally edited, expects met.");
51288
50271
  }
51289
50272
  if (health.update_available) {
51290
- console.error(` Upgrade: lotics package upgrade ${app_id}`);
50273
+ console.error(` Upgrade: lotics upgrade ${app_id}`);
51291
50274
  }
51292
50275
  }
51293
50276
  async function packageUpgrade(client, args) {
@@ -51322,27 +50305,17 @@ async function packageUpgrade(client, args) {
51322
50305
  );
51323
50306
  process.exit(1);
51324
50307
  }
51325
- const coreKeys = /* @__PURE__ */ new Set([
51326
- ...preview.drift.map((d) => `${d.namespace}.${d.alias}`),
51327
- ...preview.modified.map((m) => `${m.kind}.${m.alias}`)
51328
- ]);
51329
- const { coreResolve, knowledgeResolutions } = routeAppUpgradeResolve(
51330
- args.resolve,
51331
- preview.knowledge,
51332
- coreKeys
51333
- );
51334
- const resolutions = parseResolveFlags(coreResolve);
50308
+ const resolutions = parseResolveFlags(args.resolve);
50309
+ for (const [alias, id] of Object.entries(parseBindToFlags(args.bindTo))) {
50310
+ resolutions[`knowledge.${alias}`] = { bind_to: id };
50311
+ }
51335
50312
  if (args.applyAll) {
51336
50313
  for (const entry of preview.knowledge) {
51337
- if (knowledgeEntryNeedsConsent(entry) && knowledgeResolutions[entry.alias] === void 0) {
51338
- knowledgeResolutions[entry.alias] = knowledgeAcceptResolution(entry.change);
50314
+ if (knowledgeEntryNeedsConsent(entry) && resolutions[`knowledge.${entry.alias}`] === void 0) {
50315
+ resolutions[`knowledge.${entry.alias}`] = knowledgeAcceptResolution(entry.change);
51339
50316
  }
51340
50317
  }
51341
50318
  }
51342
- const knowledge_resolutions = {
51343
- resolutions: knowledgeResolutions,
51344
- bind_to: parseBindToFlags(args.bindTo)
51345
- };
51346
50319
  const unresolvedDrift = preview.drift.filter(
51347
50320
  (d) => resolutions[`${d.namespace}.${d.alias}`] === void 0
51348
50321
  );
@@ -51350,7 +50323,7 @@ async function packageUpgrade(client, args) {
51350
50323
  (m) => resolutions[`${m.kind}.${m.alias}`] === void 0
51351
50324
  );
51352
50325
  const unresolvedKnowledge = preview.knowledge.filter(
51353
- (e) => knowledgeEntryNeedsConsent(e) && knowledgeResolutions[e.alias] === void 0
50326
+ (e) => knowledgeEntryNeedsConsent(e) && resolutions[`knowledge.${e.alias}`] === void 0
51354
50327
  );
51355
50328
  if (unresolvedDrift.length > 0 || unresolvedModified.length > 0 || unresolvedKnowledge.length > 0) {
51356
50329
  if (unresolvedDrift.length > 0) {
@@ -51379,11 +50352,9 @@ async function packageUpgrade(client, args) {
51379
50352
  }
51380
50353
  process.exit(1);
51381
50354
  }
51382
- const hasKnowledgeResolutions = Object.keys(knowledge_resolutions.resolutions).length > 0 || Object.keys(knowledge_resolutions.bind_to).length > 0;
51383
50355
  const app = await client.upgradePackage(args.app_id, {
51384
50356
  ...args.version !== void 0 ? { version: args.version } : {},
51385
- ...Object.keys(resolutions).length > 0 ? { resolutions } : {},
51386
- ...hasKnowledgeResolutions ? { knowledge_resolutions } : {}
50357
+ ...Object.keys(resolutions).length > 0 ? { resolutions } : {}
51387
50358
  });
51388
50359
  console.error(`Upgraded ${app.name} \u2192 v${app.package_version} (${app.id}).`);
51389
50360
  }
@@ -51433,82 +50404,11 @@ function formatKnowledgeEntryLine(entry) {
51433
50404
  }
51434
50405
  function formatKnowledgeResolveHint(entry) {
51435
50406
  const note = entry.change === "changed" || entry.change === "removed" ? " (a local edit \u2014 apply/archive overwrites it; keep retains it)" : " (bound doc is gone; recreate from the package, or unbind)";
51436
- return ` --resolve ${entry.alias}=${validKnowledgeResolutions(entry.change).join("|")}${note}`;
51437
- }
51438
- function routeAppUpgradeResolve(resolve2, knowledgeEntries, coreKeys) {
51439
- const byAlias = new Map(knowledgeEntries.map((e) => [e.alias, e]));
51440
- const coreResolve = [];
51441
- const knowledgeResolutions = {};
51442
- for (const entry of resolve2) {
51443
- const eq = entry.indexOf("=");
51444
- if (eq <= 0 || eq === entry.length - 1) {
51445
- throw new Error(`Invalid --resolve "${entry}" \u2014 expected <key>=<value>.`);
51446
- }
51447
- const key = entry.slice(0, eq);
51448
- const value = entry.slice(eq + 1);
51449
- const known = byAlias.get(key);
51450
- if (known && coreKeys.has(key)) {
51451
- throw new Error(
51452
- `--resolve "${key}" is ambiguous \u2014 it names both a bundled knowledge doc and a core artifact in this upgrade. Rename one alias so it is unambiguous; refusing to guess.`
51453
- );
51454
- }
51455
- if (known) {
51456
- const valid = validKnowledgeResolutions(known.change);
51457
- const match = valid.find((v) => v === value);
51458
- if (match === void 0) {
51459
- throw new Error(
51460
- `Invalid --resolve value "${value}" for knowledge doc "${key}" (${known.change}) \u2014 expected ${valid.join("|")}.`
51461
- );
51462
- }
51463
- knowledgeResolutions[key] = match;
51464
- } else {
51465
- coreResolve.push(entry);
51466
- }
51467
- }
51468
- return { coreResolve, knowledgeResolutions };
51469
- }
51470
- function routeContentResolveFlags(resolve2, knowledgeAliases, templateAliases) {
51471
- const knowledge = {};
51472
- const templates = {};
51473
- const knowledgeValid = /* @__PURE__ */ new Set(["apply", "keep", "archive", "recreate", "unbind"]);
51474
- const templateValid = /* @__PURE__ */ new Set(["revert", "keep"]);
51475
- for (const entry of resolve2) {
51476
- const eq = entry.indexOf("=");
51477
- if (eq <= 0 || eq === entry.length - 1) {
51478
- throw new Error(`Invalid --resolve "${entry}" \u2014 expected <alias>=<value>.`);
51479
- }
51480
- const key = entry.slice(0, eq);
51481
- const value = entry.slice(eq + 1);
51482
- const isKnowledge = knowledgeAliases.has(key);
51483
- const isTemplate = templateAliases.has(key);
51484
- if (isKnowledge && isTemplate) {
51485
- throw new Error(
51486
- `--resolve "${key}" is ambiguous \u2014 it names both a knowledge doc and a template in this upgrade. Rename one alias in the package so the two content namespaces are disjoint.`
51487
- );
51488
- }
51489
- if (isTemplate) {
51490
- if (!templateValid.has(value)) {
51491
- throw new Error(`Invalid --resolve value "${value}" for template "${key}" \u2014 expected revert|keep.`);
51492
- }
51493
- templates[key] = value;
51494
- continue;
51495
- }
51496
- if (isKnowledge) {
51497
- if (!knowledgeValid.has(value)) {
51498
- throw new Error(
51499
- `Invalid --resolve value "${value}" for knowledge alias "${key}" \u2014 expected apply|keep|archive|recreate|unbind.`
51500
- );
51501
- }
51502
- knowledge[key] = value;
51503
- continue;
51504
- }
51505
- throw new Error(`--resolve "${key}" does not name a knowledge doc or template in this upgrade.`);
51506
- }
51507
- return { knowledge, templates };
50407
+ return ` --resolve knowledge.${entry.alias}=${validKnowledgeResolutions(entry.change).join("|")}${note}`;
51508
50408
  }
51509
50409
  function formatTemplateResolveHint(entry) {
51510
50410
  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)";
51511
- return ` --resolve ${entry.alias}=revert|keep${note}`;
50411
+ return ` --resolve template.${entry.alias}=revert|keep${note}`;
51512
50412
  }
51513
50413
  async function packageUpgradeKnowledge(client, args) {
51514
50414
  const preview = await client.previewContentInstallationUpgrade(args.installation_id, {
@@ -51532,7 +50432,7 @@ async function packageUpgradeKnowledge(client, args) {
51532
50432
  console.error(
51533
50433
  " No changes needing consent \u2014 advancing the version pin; clean template updates apply automatically."
51534
50434
  );
51535
- await apply({ knowledge: { resolutions: {}, bind_to: {} }, templates: {} });
50435
+ await apply({});
51536
50436
  return;
51537
50437
  }
51538
50438
  for (const entry of preview.entries) {
@@ -51541,30 +50441,27 @@ async function packageUpgradeKnowledge(client, args) {
51541
50441
  for (const entry of preview.templates) {
51542
50442
  console.error(` [template] ${entry.alias} (modified \u2014 needs consent)`);
51543
50443
  }
51544
- const routed = routeContentResolveFlags(
51545
- args.resolve,
51546
- new Set(preview.entries.map((e) => e.alias)),
51547
- new Set(preview.templates.map((t) => t.alias))
51548
- );
51549
- const resolutions = {
51550
- knowledge: { resolutions: { ...routed.knowledge }, bind_to: { ...args.bind_to } },
51551
- templates: { ...routed.templates }
51552
- };
50444
+ const resolutions = parseResolveFlags(args.resolve);
50445
+ for (const [alias, id] of Object.entries(args.bind_to)) {
50446
+ resolutions[`knowledge.${alias}`] = { bind_to: id };
50447
+ }
51553
50448
  if (args.applyAll) {
51554
50449
  for (const entry of preview.entries) {
51555
- if (knowledgeEntryNeedsConsent(entry) && resolutions.knowledge.resolutions[entry.alias] === void 0) {
51556
- resolutions.knowledge.resolutions[entry.alias] = knowledgeAcceptResolution(entry.change);
50450
+ if (knowledgeEntryNeedsConsent(entry) && resolutions[`knowledge.${entry.alias}`] === void 0) {
50451
+ resolutions[`knowledge.${entry.alias}`] = knowledgeAcceptResolution(entry.change);
51557
50452
  }
51558
50453
  }
51559
50454
  for (const entry of preview.templates) {
51560
- if (resolutions.templates[entry.alias] === void 0) resolutions.templates[entry.alias] = "revert";
50455
+ if (resolutions[`template.${entry.alias}`] === void 0) {
50456
+ resolutions[`template.${entry.alias}`] = "revert";
50457
+ }
51561
50458
  }
51562
50459
  }
51563
50460
  const unresolvedKnowledge = preview.entries.filter(
51564
- (entry) => knowledgeEntryNeedsConsent(entry) && resolutions.knowledge.resolutions[entry.alias] === void 0
50461
+ (entry) => knowledgeEntryNeedsConsent(entry) && resolutions[`knowledge.${entry.alias}`] === void 0
51565
50462
  );
51566
50463
  const unresolvedTemplates = preview.templates.filter(
51567
- (entry) => resolutions.templates[entry.alias] === void 0
50464
+ (entry) => resolutions[`template.${entry.alias}`] === void 0
51568
50465
  );
51569
50466
  if (unresolvedKnowledge.length > 0 || unresolvedTemplates.length > 0) {
51570
50467
  console.error(
@@ -51624,8 +50521,8 @@ async function packageInstall(client, args) {
51624
50521
  );
51625
50522
  }
51626
50523
  warnMissingExpectedDocs(warnings.missing_expected_docs);
51627
- console.error(` Upgrade later: lotics package upgrade ${installation.id}`);
51628
- console.error(` Uninstall: lotics package uninstall ${installation.id} [--keep-content]`);
50524
+ console.error(` Upgrade later: lotics upgrade ${installation.id}`);
50525
+ console.error(` Uninstall: lotics uninstall ${installation.id} [--keep-content]`);
51629
50526
  return;
51630
50527
  }
51631
50528
  const { app, knowledge_warnings } = result;
@@ -51633,7 +50530,9 @@ async function packageInstall(client, args) {
51633
50530
  console.error(`Installed ${app.name} ${versionLabel} \u2192 ${app.id} (workspace ${app.workspace_id}).`);
51634
50531
  console.error(" The data model, queries, workflows, and agents are live.");
51635
50532
  warnMissingExpectedDocs(knowledge_warnings.missing_expected_docs);
51636
- console.error(" Pull it for local editing: lotics app pull " + app.id);
50533
+ console.error(` Pull it for local editing: lotics app pull ${app.id}`);
50534
+ console.error(` Upgrade later: lotics upgrade ${app.id}`);
50535
+ console.error(` Health / uninstall: lotics package doctor ${app.id} \xB7 lotics uninstall ${app.id} [--archive-tables]`);
51637
50536
  }
51638
50537
  async function packageUninstall(client, args) {
51639
50538
  if (args.id.startsWith("pci_")) {
@@ -51702,10 +50601,9 @@ async function packageListContent(client) {
51702
50601
  const name = inst.package_registry?.name ?? "(unknown package)";
51703
50602
  const latest = inst.package_registry?.latest_version;
51704
50603
  const updateAvailable = inst.package_registry?.update_available ?? false;
51705
- const source = inst.app_id === null ? "standalone" : `app-bundled (${inst.app_id})`;
51706
50604
  const versionLabel = latest !== void 0 && latest !== inst.package_version ? `v${inst.package_version} \u2192 latest v${latest}` : `v${inst.package_version}`;
51707
50605
  console.error(
51708
- ` ${inst.id} ${name} ${versionLabel} ${source}` + (updateAvailable ? " \u2192 update available" : "")
50606
+ ` ${inst.id} ${name} ${versionLabel}` + (updateAvailable ? " \u2192 update available" : "")
51709
50607
  );
51710
50608
  }
51711
50609
  }
@@ -51781,200 +50679,134 @@ async function packageConfig(client, args) {
51781
50679
  console.log(` ${key} = ${JSON.stringify(config2[key])}${marker}`);
51782
50680
  }
51783
50681
  }
51784
- async function packageRetire(client, args) {
51785
- const pkg2 = await client.retirePackage(args.package_id, { undo: args.undo });
50682
+ async function appUnpublish(client, args) {
50683
+ let packageId = args.id;
50684
+ if (args.id.startsWith("app_")) {
50685
+ const app = await client.getApp(args.id);
50686
+ if (!app.package_id) {
50687
+ throw new Error(
50688
+ `App ${args.id} is not a package installation \u2014 it has no package to unpublish. Pass the package id directly.`
50689
+ );
50690
+ }
50691
+ packageId = app.package_id;
50692
+ }
50693
+ const pkg2 = await client.retirePackage(packageId, { undo: args.undo });
51786
50694
  if (pkg2.retired_at !== null) {
51787
50695
  console.error(
51788
- `Retired ${pkg2.name} (${pkg2.id}). New installs refuse it and it is hidden from other orgs; existing installations keep working and may still upgrade.`
50696
+ `Unpublished ${pkg2.name} (${pkg2.id}). New installs refuse it and it is hidden from other orgs; existing installations keep working and may still upgrade. Undo: lotics app unpublish ${pkg2.id} --undo`
51789
50697
  );
51790
50698
  } else {
51791
- console.error(`Un-retired ${pkg2.name} (${pkg2.id}) \u2014 installable again.`);
50699
+ console.error(`Re-published ${pkg2.name} (${pkg2.id}) \u2014 installable again.`);
51792
50700
  }
51793
50701
  }
51794
- function readAppKnowledgeDeclaration(appPkgJson) {
51795
- const lotics = isPlainObject2(appPkgJson.lotics) ? appPkgJson.lotics : {};
51796
- const knowledge = [];
51797
- if (Array.isArray(lotics.knowledge)) {
51798
- for (const entry of lotics.knowledge) {
51799
- if (isPlainObject2(entry) && typeof entry.alias === "string" && typeof entry.doc_id === "string") {
51800
- knowledge.push({ alias: entry.alias, doc_id: entry.doc_id });
51801
- }
51802
- }
51803
- }
51804
- const knowledge_expects = Array.isArray(lotics.knowledge_expects) ? lotics.knowledge_expects.filter((v) => typeof v === "string") : [];
51805
- return { knowledge, knowledge_expects };
51806
- }
51807
- function knowledgeManifestFromContract(contract) {
51808
- const out = {};
51809
- if (!isPlainObject2(contract) || !isPlainObject2(contract.knowledge)) return out;
51810
- for (const [alias, entry] of Object.entries(contract.knowledge)) {
51811
- if (!isPlainObject2(entry) || typeof entry.name !== "string") continue;
51812
- out[alias] = {
51813
- name: entry.name,
51814
- description: typeof entry.description === "string" ? entry.description : null,
51815
- active_by_default: typeof entry.active_by_default === "boolean" ? entry.active_by_default : true
51816
- };
51817
- }
51818
- return out;
51819
- }
51820
- async function packageExtract(client, args) {
51821
- const app = await client.getApp(args.app_id);
51822
- if (!app.current_version_id) {
50702
+ async function appPublish(client, args) {
50703
+ const projectDir = path6.resolve(args.projectDir ?? process.cwd());
50704
+ const local = readLocalAppManifest(projectDir);
50705
+ const explicit = args.app_id !== void 0 && args.app_id !== "." ? args.app_id : void 0;
50706
+ const appId = explicit ?? local?.app_id ?? null;
50707
+ if (appId === null) {
51823
50708
  throw new Error(
51824
- `App ${app.id} has no deployed version \u2014 extract stages its source archive, so deploy it first.`
50709
+ "No app id. Run `lotics app publish` from a pulled app project (lotics app pull <app_id>), or pass one: lotics app publish <app_id>."
51825
50710
  );
51826
50711
  }
51827
- const targetPath = path7.resolve(args.targetPath ?? appDirName(app.name));
51828
- if (fs6.existsSync(targetPath) && fs6.readdirSync(targetPath).length > 0) {
51829
- throw new Error(`Target directory ${targetPath} is not empty.`);
51830
- }
51831
- fs6.mkdirSync(targetPath, { recursive: true });
51832
- const version2 = await client.getAppVersion(app.id, app.current_version_id);
51833
- const sourceUrl = await client.getAppVersionSourceUrl(app.id, version2.id);
51834
- const tmpFile = path7.join(tmpdir2(), `lotics-extract-${app.id}-${Date.now()}.tar.gz`);
51835
- console.error("Downloading source archive...");
51836
- await client.downloadFile(sourceUrl, tmpFile);
51837
- try {
51838
- console.error(`Extracting to ${targetPath}...`);
51839
- await runTar(["-xzf", tmpFile, "-C", targetPath], targetPath);
51840
- } finally {
51841
- if (fs6.existsSync(tmpFile)) fs6.unlinkSync(tmpFile);
51842
- }
51843
- const appPkgJson = JSON.parse(
51844
- fs6.readFileSync(packageJsonPath(targetPath), "utf-8")
51845
- );
51846
- const appKnowledge = readAppKnowledgeDeclaration(appPkgJson);
51847
- const extracted = await client.extractPackage(args.app_id, {
51848
- knowledge: appKnowledge.knowledge
51849
- });
51850
- const { lines, hasError } = formatExtractReport(extracted.report);
51851
- if (lines.length > 0) {
51852
- console.error(`Extraction report (${extracted.report.length}):`);
51853
- for (const line of lines) console.error(line);
51854
- } else {
51855
- console.error("Extraction report: no findings.");
51856
- }
51857
- const draft = draftPackageProjectFromApp(appPkgJson, { name: app.name, description: null });
51858
- draft.manifest.knowledge = knowledgeManifestFromContract(extracted.contract);
51859
- draft.manifest.knowledge_expects = appKnowledge.knowledge_expects;
51860
- const deps = isPlainObject2(draft.pkgJson.dependencies) ? draft.pkgJson.dependencies : {};
51861
- const originRange = typeof deps["@lotics/app-sdk"] === "string" ? deps["@lotics/app-sdk"] : null;
51862
- const originFloor = originRange?.replace(/^[\^~]/, "") ?? null;
51863
- if (originFloor === null || cmpVersions(originFloor, STARTER_FALLBACK_SDK_VERSION) < 0) {
51864
- const raised = packageSdkRange(await fetchLatestNpmVersion("@lotics/app-sdk"));
51865
- draft.pkgJson.dependencies = { ...deps, "@lotics/app-sdk": raised };
51866
- console.error(`Raised @lotics/app-sdk to ${raised} (generated app_fields needs getAppBinding)`);
51867
- }
51868
- writePackageManifest(targetPath, draft);
51869
- fs6.writeFileSync(
51870
- path7.join(targetPath, CONTRACT_FILE),
51871
- JSON.stringify(extracted.contract, null, 2) + "\n"
51872
- );
51873
- console.error(`Wrote ${CONTRACT_FILE}`);
51874
- writePackageAppFields(targetPath);
51875
- const starterViteConfig = buildStarterTemplate({
51876
- app_name: app.name,
51877
- app_id: "",
51878
- workspace_id: ""
51879
- }).find((f) => f.path === "vite.config.ts");
51880
- if (starterViteConfig === void 0) {
51881
- throw new Error("starter template is missing vite.config.ts \u2014 cannot refresh the package project");
51882
- }
51883
- const viteConfigPath = path7.join(targetPath, "vite.config.ts");
51884
- const originViteConfig = fs6.existsSync(viteConfigPath) ? fs6.readFileSync(viteConfigPath, "utf-8") : null;
51885
- fs6.writeFileSync(viteConfigPath, starterViteConfig.content);
51886
- if (originViteConfig !== null && originViteConfig !== starterViteConfig.content) {
51887
- const stash = path7.join(dotLoticsDirEnsured(targetPath), "vite.config.origin.ts");
51888
- fs6.writeFileSync(stash, originViteConfig);
50712
+ const knowledge = local && local.app_id === appId ? local.knowledge : [];
50713
+ const renames = parseRenameFlags(args.renames);
50714
+ const preview = await client.previewPublishAppPackage(appId, { renames, knowledge });
50715
+ const { lines, hasError } = formatExtractReport(preview.findings);
50716
+ console.error(`Publish preview \u2014 ${appId} as new package "${preview.package_name}" (v1):`);
50717
+ const groups = [
50718
+ ["entities", preview.renamable_aliases.entities],
50719
+ ["fields", preview.renamable_aliases.fields],
50720
+ ["options", preview.renamable_aliases.options],
50721
+ ["roles", preview.renamable_aliases.roles],
50722
+ ["templates", preview.renamable_aliases.templates],
50723
+ ["workflows", preview.renamable_aliases.workflows]
50724
+ ];
50725
+ if (groups.some(([, vals]) => vals.length > 0)) {
50726
+ console.error(" Auto-minted aliases \u2014 rename any with --rename <alias>=<new> before v1 freezes them:");
50727
+ for (const [label, vals] of groups) {
50728
+ if (vals.length > 0) console.error(` ${`${label}:`.padEnd(11)} ${vals.join(", ")}`);
50729
+ }
51889
50730
  console.error(
51890
- "Refreshed vite.config.ts from the starter (build.target es2022). The origin app's config differed \u2014 its original was saved to .lotics/vite.config.origin.ts; re-apply any custom optimizeDeps/plugins entries you still need (never dev-link aliases)."
50731
+ " (query / app-workflow / agent runtime aliases are fixed \u2014 the shipped source calls them verbatim.)"
51891
50732
  );
51892
50733
  } else {
51893
- console.error("Refreshed vite.config.ts from the starter (build.target es2022)");
51894
- }
51895
- for (const tf of extracted.template_files) {
51896
- const dest = path7.join(targetPath, tf.bytes_ref);
51897
- fs6.mkdirSync(path7.dirname(dest), { recursive: true });
51898
- const { path: downloaded } = await client.downloadFileById(tf.file_id, path7.dirname(dest));
51899
- if (path7.resolve(downloaded) !== path7.resolve(dest)) fs6.renameSync(downloaded, dest);
51900
- console.error(`Wrote ${tf.bytes_ref}`);
51901
- }
51902
- for (const kf of extracted.knowledge_files) {
51903
- const dest = path7.join(targetPath, kf.content_ref);
51904
- fs6.mkdirSync(path7.dirname(dest), { recursive: true });
51905
- fs6.writeFileSync(dest, kf.content);
51906
- console.error(`Wrote ${kf.content_ref}`);
51907
- }
51908
- const dotLotics = path7.join(targetPath, ".lotics");
51909
- fs6.mkdirSync(dotLotics, { recursive: true });
51910
- fs6.writeFileSync(
51911
- path7.join(dotLotics, ADOPT_BINDING_FILE),
51912
- JSON.stringify(
51913
- {
51914
- app_id: app.id,
51915
- workspace_id: app.workspace_id,
51916
- binding: extracted.binding,
51917
- knowledge_binding: extracted.knowledge_binding
51918
- },
51919
- null,
51920
- 2
51921
- ) + "\n"
51922
- );
51923
- console.error(`Wrote .lotics/${ADOPT_BINDING_FILE}`);
51924
- console.error("Installing npm dependencies...");
51925
- await runNpm(["install"], targetPath);
50734
+ console.error(" No renamable aliases.");
50735
+ }
50736
+ if (lines.length > 0) {
50737
+ console.error(` Findings (${preview.findings.length}):`);
50738
+ for (const line of lines) console.error(line);
50739
+ }
51926
50740
  if (hasError) {
51927
50741
  console.error(
51928
- "\nExtraction produced error findings \u2014 the draft is written but NOT publishable as-is."
51929
- );
51930
- console.error(
51931
- "Fix the reported items above, then publish (which re-validates the contract)."
50742
+ "\nExtract found error findings \u2014 the app cannot be published as-is. Fix them in the app, redeploy, and retry."
51932
50743
  );
51933
50744
  process.exitCode = 1;
51934
50745
  return;
51935
50746
  }
51936
- console.error("\nDraft package project written. Next steps:");
51937
- console.error(` cd ${path7.relative(process.cwd(), targetPath) || "."}`);
51938
- console.error(" # review the aliases in contract.json, then:");
51939
- console.error(` lotics package publish -m "v1"`);
51940
- console.error(` lotics package adopt ${app.id} (from this project, same workspace)`);
50747
+ if (!args.yes) {
50748
+ const idArg = explicit ?? ".";
50749
+ const renameArgs = args.renames.map((r) => ` --rename ${r}`).join("");
50750
+ const mArg = args.changelog ? ` -m ${JSON.stringify(args.changelog)}` : "";
50751
+ console.error(`
50752
+ Re-run with --yes to publish v1:`);
50753
+ console.error(` lotics app publish ${idArg}${renameArgs}${mArg} --yes`);
50754
+ process.exitCode = 1;
50755
+ return;
50756
+ }
50757
+ const result = await client.publishAppAsPackage(appId, {
50758
+ renames,
50759
+ changelog: args.changelog ?? null,
50760
+ knowledge
50761
+ });
50762
+ console.error(`Published ${result.package_id} v${result.version} from app ${appId}.`);
50763
+ console.error(` The app is now installation #1 \u2014 develop it in place, then release the next version:`);
50764
+ console.error(` lotics app pull ${appId} # edit, then lotics app deploy`);
50765
+ console.error(` lotics app release ${appId} -m "<what changed>"`);
50766
+ console.error(` Install it elsewhere: lotics install ${result.package_id}`);
50767
+ }
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
+ );
51941
50775
  }
51942
- async function packageAdopt(client, args) {
51943
- const projectDir = path7.resolve(args.projectDir ?? process.cwd());
51944
- const { manifest } = readPackageProject(projectDir);
51945
- if (manifest.id === null) {
51946
- throw new Error(
51947
- "This package project has never been published (no package id). Publish first:\n lotics package publish"
51948
- );
50776
+ async function appRelease(client, args) {
50777
+ const projectDir = path6.resolve(args.projectDir ?? process.cwd());
50778
+ const appId = resolveOriginAppId(projectDir, args.app_id);
50779
+ const preview = await client.previewPackageRelease(appId);
50780
+ const { lines, hasError } = formatExtractReport(preview.findings);
50781
+ console.error(`Release preview \u2014 ${appId} \u2192 ${preview.package_id} v${preview.version}:`);
50782
+ if (preview.added_aliases.length > 0) {
50783
+ console.error(` New (${preview.added_aliases.length}): ${preview.added_aliases.join(", ")}`);
51949
50784
  }
51950
- const version2 = args.version ?? manifest.version;
51951
- if (version2 === null) {
51952
- throw new Error(
51953
- "No version to adopt \u2014 publish this project first (lotics package publish) or pass --version N."
51954
- );
50785
+ if (preview.changed_artifacts.length > 0) {
50786
+ console.error(` Changed (${preview.changed_artifacts.length}): ${preview.changed_artifacts.join(", ")}`);
51955
50787
  }
51956
- const bindingPath = path7.join(projectDir, ".lotics", ADOPT_BINDING_FILE);
51957
- if (!fs6.existsSync(bindingPath)) {
51958
- throw new Error(
51959
- `No .lotics/${ADOPT_BINDING_FILE} in ${projectDir}. Adopt binds the origin app that "lotics package extract" recorded \u2014 run extract to produce this project.`
51960
- );
50788
+ if (preview.added_aliases.length === 0 && preview.changed_artifacts.length === 0) {
50789
+ console.error(" No contract changes since the current version (a fresh code/dist snapshot still ships).");
51961
50790
  }
51962
- const pin = parseAdoptBindingFile(
51963
- JSON.parse(fs6.readFileSync(bindingPath, "utf-8")),
51964
- args.app_id
51965
- );
51966
- const app = await client.adoptPackage(args.app_id, {
51967
- package_id: manifest.id,
51968
- version: version2,
51969
- binding: pin.binding,
51970
- // The origin knowledge binding (empty when the app owns no package-managed
51971
- // docs) — the server verifies it maps the contract's knowledge aliases faithfully.
51972
- ...Object.keys(pin.knowledge_binding).length > 0 ? { knowledge_binding: pin.knowledge_binding } : {}
51973
- });
51974
- console.error(
51975
- `Adopted ${app.name} \u2192 installation of ${app.package_id} v${app.package_version}.`
51976
- );
51977
- console.error(` Verify the installation: lotics package doctor ${app.id}`);
50791
+ if (lines.length > 0) {
50792
+ console.error(` Findings (${preview.findings.length}):`);
50793
+ for (const line of lines) console.error(line);
50794
+ }
50795
+ if (hasError) {
50796
+ console.error("\nExtract found error findings \u2014 the origin cannot be released as-is. Fix them in the app and retry.");
50797
+ process.exitCode = 1;
50798
+ return;
50799
+ }
50800
+ if (!args.yes) {
50801
+ console.error(`
50802
+ Re-run with --yes to publish v${preview.version}:`);
50803
+ console.error(` lotics app release ${args.app_id ?? "."} -m ${JSON.stringify(args.changelog)} --yes`);
50804
+ process.exitCode = 1;
50805
+ return;
50806
+ }
50807
+ const result = await client.releasePackage(appId, { changelog: args.changelog });
50808
+ console.error(`Released ${result.package_id} v${result.version}.`);
50809
+ console.error(` The origin was re-pinned to v${result.version} \u2014 verify: lotics package doctor ${appId}`);
51978
50810
  }
51979
50811
  async function packageYank(client, args) {
51980
50812
  const result = await client.yankPackageVersion(args.package_id, args.version, !args.undo);
@@ -52006,7 +50838,7 @@ async function packageFleetUpgrade(client, args) {
52006
50838
  `${line}: breaking=${inst.blockers.breaking} drift=${inst.blockers.drift} modified=${inst.blockers.modified}`
52007
50839
  );
52008
50840
  console.error(
52009
- ` resolve via: lotics package upgrade ${inst.app_id} --version ${result.target_version} ...`
50841
+ ` resolve via: lotics upgrade ${inst.app_id} --version ${result.target_version} ...`
52010
50842
  );
52011
50843
  } else if (inst.message) {
52012
50844
  console.error(`${line}: ${inst.message}`);
@@ -52038,7 +50870,6 @@ function parseArgs(argv) {
52038
50870
  message: void 0,
52039
50871
  local: false,
52040
50872
  all: false,
52041
- dev: false,
52042
50873
  yes: false,
52043
50874
  printCreated: false,
52044
50875
  cleanup: false,
@@ -52047,7 +50878,7 @@ function parseArgs(argv) {
52047
50878
  bindTo: [],
52048
50879
  keepContent: false,
52049
50880
  applyAll: false,
52050
- kind: void 0,
50881
+ rename: [],
52051
50882
  config: [],
52052
50883
  set: [],
52053
50884
  archiveTables: false,
@@ -52102,9 +50933,6 @@ function parseArgs(argv) {
52102
50933
  case "--local":
52103
50934
  flags.local = true;
52104
50935
  break;
52105
- case "--dev":
52106
- flags.dev = true;
52107
- break;
52108
50936
  case "--all":
52109
50937
  flags.all = true;
52110
50938
  break;
@@ -52143,9 +50971,14 @@ function parseArgs(argv) {
52143
50971
  case "--apply-all":
52144
50972
  flags.applyAll = true;
52145
50973
  break;
52146
- case "--kind":
52147
- flags.kind = argv[++i2];
50974
+ case "--rename": {
50975
+ const value = argv[++i2];
50976
+ if (value === void 0 || value.startsWith("-")) {
50977
+ throw new Error("--rename requires a value: old=new (an alias to rename before v1 freezes it).");
50978
+ }
50979
+ flags.rename.push(value);
52148
50980
  break;
50981
+ }
52149
50982
  case "--config": {
52150
50983
  const value = argv[++i2];
52151
50984
  if (value === void 0 || value.startsWith("-")) {
@@ -52171,10 +51004,10 @@ function parseArgs(argv) {
52171
51004
  case "--version":
52172
51005
  case "-v": {
52173
51006
  const next = argv[i2 + 1];
52174
- if (command === "package") {
51007
+ if (command === "package" || command === "install" || command === "upgrade") {
52175
51008
  if (next === void 0 || next.startsWith("-")) {
52176
51009
  throw new Error(
52177
- "--version requires a version number for package commands (e.g. --version 2)."
51010
+ "--version requires a version number for install/upgrade/package commands (e.g. --version 2)."
52178
51011
  );
52179
51012
  }
52180
51013
  flags.packageVersion = next;
@@ -52236,6 +51069,34 @@ async function ingestJsonArgs(opts) {
52236
51069
  // src/xlsx.ts
52237
51070
  import fs7 from "node:fs";
52238
51071
 
51072
+ // src/file_command_io.ts
51073
+ import fs6 from "node:fs";
51074
+ import path7 from "node:path";
51075
+ var CliError = class extends Error {
51076
+ constructor(message) {
51077
+ super(message);
51078
+ this.name = "CliError";
51079
+ }
51080
+ };
51081
+ function fail(message) {
51082
+ throw new CliError(message);
51083
+ }
51084
+ function writeFileAtomic(filePath, bytes) {
51085
+ const dir = path7.dirname(path7.resolve(filePath));
51086
+ const tmp = path7.join(dir, `.${path7.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
51087
+ fs6.writeFileSync(tmp, bytes);
51088
+ try {
51089
+ fs6.renameSync(tmp, filePath);
51090
+ } catch (error51) {
51091
+ try {
51092
+ fs6.unlinkSync(tmp);
51093
+ } catch {
51094
+ }
51095
+ throw error51;
51096
+ }
51097
+ console.error(`Wrote ${filePath}`);
51098
+ }
51099
+
52239
51100
  // ../xlsx/node_modules/fast-xml-parser/src/util.js
52240
51101
  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";
52241
51102
  var nameChar = nameStartChar + "\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
@@ -68566,7 +67427,7 @@ async function runDocxCommand(subcommand, toolArgs, restArgs) {
68566
67427
  import { spawn as spawn3 } from "node:child_process";
68567
67428
  import { createServer } from "node:http";
68568
67429
  import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdtempSync, rmSync, readdirSync } from "node:fs";
68569
- import { tmpdir as tmpdir3 } from "node:os";
67430
+ import { tmpdir as tmpdir2 } from "node:os";
68570
67431
  import { join, dirname, resolve, extname, basename } from "node:path";
68571
67432
  import { fileURLToPath as fileURLToPath2 } from "node:url";
68572
67433
  import { setTimeout as sleep } from "node:timers/promises";
@@ -68659,7 +67520,7 @@ async function runPreviewCommand(filePath, flags) {
68659
67520
  });
68660
67521
  await new Promise((r) => server.listen(0, "127.0.0.1", () => r()));
68661
67522
  const httpPort = server.address().port;
68662
- const udd = mkdtempSync(join(tmpdir3(), "lotics-render-"));
67523
+ const udd = mkdtempSync(join(tmpdir2(), "lotics-render-"));
68663
67524
  const child = spawn3(chrome, [
68664
67525
  "--headless=new",
68665
67526
  "--disable-gpu",
@@ -68808,10 +67669,22 @@ COMMANDS
68808
67669
  cat args.json | lotics run <tool> Read JSON args from stdin (large payloads)
68809
67670
  lotics app create <name> [path] Create a new custom-code app + scaffold locally
68810
67671
  lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)
68811
- lotics app deploy -m <message> Build + upload current dir as a new version
68812
- (-m is required \u2014 it's the version's audit trail)
68813
- (code + queries only \u2014 workflow bindings are
67672
+ lotics app deploy -m <message> Build + upload current dir as a new version \u2014 COMMIT
67673
+ (-m is required \u2014 it's the version's audit trail;
67674
+ carries code + queries only \u2014 workflow bindings are
68814
67675
  managed by set_app_workflow / remove_app_workflow)
67676
+ lotics app publish [app_id|.] [--rename old=new ...] [-m <changelog>] [--yes]
67677
+ Make the app DISTRIBUTABLE \u2014 first-release it (v1) as a
67678
+ package; later versions ship via "lotics app release".
67679
+ Previews the auto-minted aliases + findings; --yes applies
67680
+ (extract \u2192 create \u2192 publish v1 \u2192 pin origin). --rename fixes
67681
+ an alias before v1 freezes; -m is an optional v1 changelog
67682
+ lotics app release [app_id|.] -m <changelog> [--yes]
67683
+ RELEASE the next package version from the origin app you
67684
+ are happy with (preview; --yes publishes + re-pins)
67685
+ lotics app unpublish <app_id|package_id> [--undo]
67686
+ Take the published package off the shelf (installations
67687
+ keep working); --undo restores it
68815
67688
  lotics app versions [app_id] Show deploy history newest-first (version,
68816
67689
  timestamp, deployer, build status, -m message;
68817
67690
  * marks the currently served version)
@@ -68833,57 +67706,40 @@ COMMANDS
68833
67706
  lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
68834
67707
  lotics app rename "<new name>" Rename the app's display name (launcher title)
68835
67708
  lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
68836
- lotics package new <name> [path] [--kind app|content]
68837
- Scaffold a package project \u2014 an app (contract + source)
68838
- or a content corpus (docs-only, no app source)
68839
- lotics package build [path] Build the publishable bundle (source + dist)
68840
- lotics package publish [path] Publish a new immutable package version
68841
- lotics package dev [path] Sync the package into a dev workspace + run the app
68842
- dev server (--workspace <dev_ws> selects it)
68843
- lotics package sync [path] Re-sync (additive migrate + materialize) into a dev ws
68844
- lotics package reset [path] DEV-ONLY: drop scaffolded tables + re-scaffold clean
68845
- lotics package install <package> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]
67709
+ # PACKAGES \u2014 consumer verbs are top-level; low-traffic ops live on "lotics package"
67710
+ # (author verbs live on "lotics app")
67711
+ lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]
68846
67712
  Install a package (app: scaffolds/deploys/materializes,
68847
67713
  --config sets its knobs; content: installs the doc corpus,
68848
67714
  --bind-to consents to adopt a same-named doc on a collision)
68849
- lotics package uninstall <app_id|pci_id> [--archive-tables] [--keep-content]
67715
+ lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content]
68850
67716
  Uninstall \u2014 dispatched by id. App (app_id): archives
68851
67717
  artifacts (+ --archive-tables also archives scaffolded
68852
67718
  tables). Content (pci_): archives its package-bound docs
68853
67719
  unless --keep-content
68854
- lotics package config <app_id> [--set key=value ...]
68855
- Show or edit an installation's config knobs
68856
- lotics package show <package_id> Registry metadata + version history
68857
- lotics package retire <package_id> [--undo]
68858
- Retire a package (refuse new installs, hide from other
68859
- orgs); existing installations keep working + upgrade
68860
- lotics package list-content List the workspace's content installations
68861
- (standalone pci_ + app-bundled) \u2014 source for a pci_ id
68862
- lotics package upgrade <app_id|pci_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all]
68863
- Preview-then-apply an upgrade. App: refuses while any
68864
- drift/modified/bundled-knowledge finding lacks a --resolve
68865
- (a knowledge-alias --resolve routes to the doc). Content
68866
- (pci_): --resolve <alias>=apply|keep|archive|recreate|unbind;
68867
- --apply-all accepts the package's version for every doc
67720
+ lotics upgrade <app_id|pci_id|package_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all]
67721
+ Preview-then-apply an upgrade, dispatched by id. App
67722
+ (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
68868
67727
  lotics package doctor [app_id] Installation health: version pin vs latest, binding
68869
67728
  drift, locally modified core, knowledge drift/edits
68870
67729
  (exit 1 on findings)
68871
- lotics package rebind-role <app_id> <alias> <grp_id>
68872
- Re-point a package role at a different group
68873
- (re-materializes at the pinned version)
67730
+ lotics package config <app_id> [--set key=value ...]
67731
+ Show or edit an installation's config knobs
68874
67732
  lotics package eject <app_id> Sever an installation's package link
68875
67733
  (re-deploys the pinned source as a bespoke app)
68876
- lotics package extract <app_id> [path] Promote a bespoke app to a draft package
68877
- project (contract + binding + templates); always
68878
- writes the draft, exits 1 on error findings
68879
- lotics package adopt <app_id> [path] Bind the published package project onto the
68880
- origin app (reads .lotics/adopt_binding.json;
68881
- --version N pins a specific version)
68882
- lotics package fleet-upgrade <package_id> [--version N]
67734
+ lotics package show <package_id> Registry metadata + version history
67735
+ lotics package list-content List the workspace's content installations
67736
+ (standalone content installs) \u2014 source for a pci_ id
68883
67737
  lotics package yank <package_id> <version> [--undo]
68884
- Upgrade EVERY installation of the package across
68885
- your org: applies where the preview is clean,
68886
- skips + reports findings (exit 1 unless all current)
67738
+ Refuse new installs/upgrades of a broken published
67739
+ version (pinned installations keep running)
67740
+ lotics run publish_content '{"name":"\u2026","knowledge_doc_ids":[\u2026],"template_ids":[\u2026]}'
67741
+ Publish a SET of live docs + templates as a content
67742
+ package (v1 + origin pin); release_content cuts the next
68887
67743
  lotics ui link <component> [--ui-src <path>] [--remove]
68888
67744
  Dev-link @lotics/ui to packages/ui/src (Vite alias
68889
67745
  + tsc paths) for live HMR + typecheck. Monorepo apps
@@ -68912,13 +67768,12 @@ FLAGS
68912
67768
  is_current_member / row-scoping resolve to them (also
68913
67769
  LOTICS_VIEW_AS env; admin key only; writes stay yours)
68914
67770
  --local Pin the current directory (lotics org use / auth api-key)
68915
- --dev (lotics workspace create) Mark a throwaway app-package dev
68916
- workspace \u2014 required for "lotics package reset"
68917
- --kind <k> (lotics package new) Package kind: app (default) or content
68918
- --bind-to <a>=<id> (lotics package install/upgrade, repeatable) Adopt an existing
67771
+ --rename <old>=<new> (lotics app publish, repeatable) Fix an auto-minted alias before
67772
+ the package's v1 contract freezes it
67773
+ --bind-to <a>=<id> (lotics install/upgrade, repeatable) Adopt an existing
68919
67774
  same-named doc as package-managed (resolves a knowledge collision)
68920
- --keep-content (lotics package uninstall) Keep the docs instead of archiving them
68921
- --apply-all (lotics package upgrade, knowledge) Accept the package's version
67775
+ --keep-content (lotics uninstall) Keep the docs instead of archiving them
67776
+ --apply-all (lotics upgrade, knowledge) Accept the package's version
68922
67777
  for every doc (overwrites local edits)
68923
67778
  --all (lotics auth logout) Remove every saved credential
68924
67779
  --version Show version
@@ -69292,27 +68147,6 @@ async function main() {
69292
68147
  console.error("Usage: lotics ui link <component> [--ui-src <abs path>] [--remove]");
69293
68148
  process.exit(1);
69294
68149
  }
69295
- if (command === "package" && subcommand === "new") {
69296
- const name = toolArgs;
69297
- if (!name) {
69298
- console.error("Usage: lotics package new <name> [path] [--kind app|content]");
69299
- process.exit(1);
69300
- }
69301
- if (flags.kind !== void 0 && flags.kind !== "app" && flags.kind !== "content") {
69302
- console.error(`Invalid --kind "${flags.kind}" \u2014 expected "app" or "content".`);
69303
- process.exit(1);
69304
- }
69305
- await packageNew({
69306
- name,
69307
- targetPath: restArgs[0],
69308
- kind: flags.kind === "content" ? "content" : "app"
69309
- });
69310
- return;
69311
- }
69312
- if (command === "package" && subcommand === "build") {
69313
- await packageBuild({ projectDir: toolArgs });
69314
- return;
69315
- }
69316
68150
  if (command === "app" && subcommand === "workflow" && toolArgs === "check") {
69317
68151
  await appWorkflowCheck({ alias: restArgs[0] });
69318
68152
  return;
@@ -69387,7 +68221,7 @@ async function main() {
69387
68221
  }
69388
68222
  return;
69389
68223
  }
69390
- if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "package") {
68224
+ if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace" && command !== "app" && command !== "package" && command !== "install" && command !== "uninstall" && command !== "upgrade") {
69391
68225
  console.error(`Unknown command: ${command}`);
69392
68226
  console.error('Run "lotics --help" for usage.');
69393
68227
  process.exit(1);
@@ -69407,30 +68241,27 @@ async function main() {
69407
68241
  console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
69408
68242
  console.error(` lotics app rename "<new name>" Rename the app's display name (launcher title)`);
69409
68243
  console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
68244
+ console.error(" lotics app publish [app_id|.] [--rename old=new ...] [-m <changelog>] [--yes] First-release the app as a package (v1; preview, --yes applies)");
68245
+ console.error(" lotics app release [app_id|.] -m <changelog> [--yes] Release the next package version from the origin");
68246
+ console.error(" lotics app unpublish <app_id|package_id> [--undo] Take a published package off the shelf (any kind \u2014 app or content)");
69410
68247
  process.exit(1);
69411
68248
  }
69412
68249
  if (command === "package" && !subcommand) {
69413
- console.error("Usage:");
69414
- console.error(" lotics package new <name> [path] [--kind app|content] Scaffold a package project (app source, or docs-only)");
69415
- console.error(" lotics package build [path] Build the publishable bundle (source + dist)");
69416
- console.error(" lotics package publish [path] [-m <changelog>] Publish a new immutable package version");
69417
- console.error(" lotics package dev [path] [--workspace <ws>] Sync into a dev workspace + run the app dev server");
69418
- console.error(" lotics package sync [path] [--workspace <ws>] Re-sync (additive migrate + materialize) into a dev workspace");
69419
- console.error(" lotics package reset [path] [--workspace <ws>] DEV-ONLY: drop scaffolded tables + re-scaffold clean");
69420
- console.error(" lotics package install <package> [--version N] [--bind-to <alias>=<kdc_id>] [--config key=value ...] Install a package (app or content)");
69421
- console.error(" lotics package uninstall <app_id|pci_id> [--archive-tables] [--keep-content] Uninstall \u2014 dispatched by id (app vs content)");
69422
- console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
69423
- console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
69424
- console.error(" lotics package retire <package_id> [--undo] Retire a package (refuse new installs; installs keep working)");
69425
- console.error(" lotics package list-content List the workspace's content installations (standalone + app-bundled)");
69426
- console.error(" lotics package upgrade <app_id|pci_id> [--version N] [--resolve <key>=... ] [--bind-to ...] [--apply-all] Preview + apply an upgrade");
68250
+ console.error("Usage (low-traffic consumer / registry ops \u2014 author verbs live on `lotics app`):");
68251
+ console.error(" The high-traffic consumer verbs are top-level:");
68252
+ 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_)");
69427
68255
  console.error(" lotics package doctor [app_id] Health: version pin vs latest + binding/knowledge drift");
69428
- console.error(" lotics package rebind-role <app_id> <alias> <grp_id> Re-point a package role at another group");
68256
+ console.error(" lotics package config <app_id> [--set key=value ...] Show or edit an installation's config knobs");
69429
68257
  console.error(" lotics package eject <app_id> Sever an installation's package link");
69430
- console.error(" lotics package extract <app_id> [path] Promote a bespoke app to a draft package project");
69431
- console.error(" lotics package adopt <app_id> [path] Bind the published project onto the origin app");
69432
- console.error(" lotics package fleet-upgrade <package_id> [--version N] Upgrade every org installation (clean ones apply; findings skip)");
68258
+ console.error(" lotics package show <package_id> Registry metadata + version history (channel, yank, changelog)");
68259
+ console.error(" lotics package list-content List the workspace's content installations (standalone content installs)");
69433
68260
  console.error(" lotics package yank <package_id> <version> [--undo] Refuse new installs/upgrades of a broken published version (pinned installations keep running)");
68261
+ console.error(" A content package (a SET of live docs + templates) \u2014 publish + release with the tools, retire on `lotics app`:");
68262
+ console.error(` lotics run publish_content '{"name":"\u2026","knowledge_doc_ids":[\u2026],"template_ids":[\u2026]}' Publish v1 (a fixed set)`);
68263
+ console.error(` lotics run release_content '{"package_id":"apg_\u2026","changelog":"\u2026"}' Cut the next version (add "dry_run":true to preview)`);
68264
+ console.error(" lotics app unpublish <package_id> Take the whole package off the shelf (kind-agnostic \u2014 retires content too)");
69434
68265
  process.exit(1);
69435
68266
  }
69436
68267
  if (command === "run" && !subcommand) {
@@ -69506,11 +68337,11 @@ Available workspaces:`);
69506
68337
  if (subcommand === "create") {
69507
68338
  const name = toolArgs;
69508
68339
  if (!name) {
69509
- console.error("Usage: lotics workspace create <name> [--timezone <tz>] [--dev]");
68340
+ console.error("Usage: lotics workspace create <name> [--timezone <tz>]");
69510
68341
  process.exit(1);
69511
68342
  }
69512
68343
  const timezone = flags.timezone;
69513
- const created = await client.createWorkspace({ name, timezone, is_dev: flags.dev || void 0 });
68344
+ const created = await client.createWorkspace({ name, timezone });
69514
68345
  setSelectedWorkspace(created.id);
69515
68346
  client.setWorkspaceId(created.id);
69516
68347
  if (flags.json) {
@@ -69572,48 +68403,88 @@ Available workspaces:`);
69572
68403
  return;
69573
68404
  }
69574
68405
  await resolveWorkspace(client, ctx);
69575
- if (command === "package") {
69576
- if (subcommand === "install") {
69577
- const packageId = toolArgs;
69578
- if (!packageId) {
69579
- console.error(
69580
- "Usage: lotics package install <package> [--version N] [--bind-to <alias>=<kdc_id> ...]"
69581
- );
68406
+ if (command === "install") {
68407
+ const packageId = subcommand;
68408
+ if (!packageId) {
68409
+ console.error("Usage: lotics install <package_id> [--version N] [--bind-to <alias>=<kdc_id> ...] [--config key=value ...]");
68410
+ process.exit(1);
68411
+ }
68412
+ let version2;
68413
+ if (flags.packageVersion !== void 0) {
68414
+ version2 = Number(flags.packageVersion);
68415
+ if (!Number.isInteger(version2) || version2 <= 0) {
68416
+ console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
69582
68417
  process.exit(1);
69583
68418
  }
69584
- let version2;
69585
- if (flags.packageVersion !== void 0) {
69586
- version2 = Number(flags.packageVersion);
69587
- if (!Number.isInteger(version2) || version2 <= 0) {
69588
- console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
69589
- process.exit(1);
69590
- }
69591
- }
69592
- const config2 = flags.config.length > 0 ? parseInstallConfigFlags(flags.config) : void 0;
69593
- await packageInstall(client, {
69594
- package_id: packageId,
69595
- version: version2,
69596
- bind_to: parseBindToFlags(flags.bindTo),
69597
- ...config2 !== void 0 ? { config: config2 } : {}
69598
- });
69599
- return;
69600
68419
  }
69601
- if (subcommand === "uninstall") {
69602
- const id = toolArgs;
69603
- if (!id) {
69604
- console.error("Usage: lotics package uninstall <app_id|pci_id> [--archive-tables] [--keep-content]");
69605
- console.error(
69606
- "Dispatched by id: an app installation (app_id; --archive-tables to also archive its scaffolded tables) or a standalone content installation (pci_ id from install / lotics package list-content; --keep-content to retain its docs)."
69607
- );
68420
+ const config2 = flags.config.length > 0 ? parseInstallConfigFlags(flags.config) : void 0;
68421
+ await packageInstall(client, {
68422
+ package_id: packageId,
68423
+ version: version2,
68424
+ bind_to: parseBindToFlags(flags.bindTo),
68425
+ ...config2 !== void 0 ? { config: config2 } : {}
68426
+ });
68427
+ return;
68428
+ }
68429
+ if (command === "uninstall") {
68430
+ const id = subcommand;
68431
+ if (!id) {
68432
+ console.error("Usage: lotics uninstall <app_id|pci_id> [--archive-tables] [--keep-content]");
68433
+ 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)."
68435
+ );
68436
+ process.exit(1);
68437
+ }
68438
+ await packageUninstall(client, {
68439
+ id,
68440
+ keep_content: flags.keepContent,
68441
+ archive_tables: flags.archiveTables
68442
+ });
68443
+ return;
68444
+ }
68445
+ if (command === "upgrade") {
68446
+ const target = subcommand;
68447
+ if (!target) {
68448
+ console.error(
68449
+ "Usage: lotics upgrade <app_id | pci_id | package_id> [--version N] [--resolve <key>=... ...] [--bind-to ...] [--apply-all]"
68450
+ );
68451
+ 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)."
68453
+ );
68454
+ process.exit(1);
68455
+ }
68456
+ let version2;
68457
+ if (flags.packageVersion !== void 0) {
68458
+ version2 = Number(flags.packageVersion);
68459
+ if (!Number.isInteger(version2) || version2 <= 0) {
68460
+ console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
69608
68461
  process.exit(1);
69609
68462
  }
69610
- await packageUninstall(client, {
69611
- id,
69612
- keep_content: flags.keepContent,
69613
- archive_tables: flags.archiveTables
68463
+ }
68464
+ if (target.startsWith("apg_")) {
68465
+ await packageFleetUpgrade(client, { package_id: target, version: version2 });
68466
+ return;
68467
+ }
68468
+ if (target.startsWith("pci_")) {
68469
+ await packageUpgradeKnowledge(client, {
68470
+ installation_id: target,
68471
+ version: version2,
68472
+ resolve: flags.resolve,
68473
+ bind_to: parseBindToFlags(flags.bindTo),
68474
+ applyAll: flags.applyAll
69614
68475
  });
69615
68476
  return;
69616
68477
  }
68478
+ await packageUpgrade(client, {
68479
+ app_id: target,
68480
+ version: version2,
68481
+ resolve: flags.resolve,
68482
+ bindTo: flags.bindTo,
68483
+ applyAll: flags.applyAll
68484
+ });
68485
+ return;
68486
+ }
68487
+ if (command === "package") {
69617
68488
  if (subcommand === "list-content") {
69618
68489
  await packageListContent(client);
69619
68490
  return;
@@ -69627,15 +68498,6 @@ Available workspaces:`);
69627
68498
  await packageConfig(client, { app_id: appId, sets: flags.set });
69628
68499
  return;
69629
68500
  }
69630
- if (subcommand === "retire") {
69631
- const packageId = toolArgs;
69632
- if (!packageId) {
69633
- console.error("Usage: lotics package retire <package_id> [--undo]");
69634
- process.exit(1);
69635
- }
69636
- await packageRetire(client, { package_id: packageId, undo: flags.undo });
69637
- return;
69638
- }
69639
68501
  if (subcommand === "show") {
69640
68502
  const packageId = toolArgs;
69641
68503
  if (!packageId) {
@@ -69654,34 +68516,6 @@ Available workspaces:`);
69654
68516
  await packageEject(client, { app_id: appId });
69655
68517
  return;
69656
68518
  }
69657
- if (subcommand === "extract") {
69658
- const appId = toolArgs;
69659
- if (!appId) {
69660
- console.error("Usage: lotics package extract <app_id> [path]");
69661
- console.error("Promotes a bespoke app to a draft package project (contract + binding + templates).");
69662
- process.exit(1);
69663
- }
69664
- await packageExtract(client, { app_id: appId, targetPath: restArgs[0] });
69665
- return;
69666
- }
69667
- if (subcommand === "adopt") {
69668
- const appId = toolArgs;
69669
- if (!appId) {
69670
- console.error("Usage: lotics package adopt <app_id> [path]");
69671
- console.error("Binds the published package project (this dir, or [path]) onto the origin app.");
69672
- process.exit(1);
69673
- }
69674
- let version2;
69675
- if (flags.packageVersion !== void 0) {
69676
- version2 = Number(flags.packageVersion);
69677
- if (!Number.isInteger(version2) || version2 <= 0) {
69678
- console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
69679
- process.exit(1);
69680
- }
69681
- }
69682
- await packageAdopt(client, { app_id: appId, version: version2, projectDir: restArgs[0] });
69683
- return;
69684
- }
69685
68519
  if (subcommand === "yank") {
69686
68520
  const packageId = toolArgs;
69687
68521
  const version2 = Number(restArgs[0]);
@@ -69692,23 +68526,6 @@ Available workspaces:`);
69692
68526
  await packageYank(client, { package_id: packageId, version: version2, undo: flags.undo });
69693
68527
  return;
69694
68528
  }
69695
- if (subcommand === "fleet-upgrade") {
69696
- const packageId = toolArgs;
69697
- if (!packageId) {
69698
- console.error("Usage: lotics package fleet-upgrade <package_id> [--version N]");
69699
- process.exit(1);
69700
- }
69701
- let version2;
69702
- if (flags.packageVersion !== void 0) {
69703
- version2 = Number(flags.packageVersion);
69704
- if (!Number.isInteger(version2) || version2 <= 0) {
69705
- console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
69706
- process.exit(1);
69707
- }
69708
- }
69709
- await packageFleetUpgrade(client, { package_id: packageId, version: version2 });
69710
- return;
69711
- }
69712
68529
  if (subcommand === "doctor") {
69713
68530
  await packageDoctor(client, { app_id: toolArgs });
69714
68531
  return;
@@ -69716,78 +68533,23 @@ Available workspaces:`);
69716
68533
  if (subcommand === "rebind-role") {
69717
68534
  const appId = toolArgs;
69718
68535
  const [roleAlias, groupId] = restArgs;
69719
- if (!appId || !roleAlias || !groupId) {
69720
- console.error("Usage: lotics package rebind-role <app_id> <role_alias> <grp_id>");
69721
- process.exit(1);
69722
- }
69723
- const result = await client.rebindPackageRole(appId, {
69724
- role_alias: roleAlias,
69725
- group_id: groupId
69726
- });
69727
68536
  console.error(
69728
- `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."
68537
+ `\`lotics package rebind-role\` was folded into the upgrade grammar. Re-point a live role with:
68538
+ lotics upgrade ${appId ?? "<app_id>"} --resolve roles.${roleAlias ?? "<alias>"}=${groupId ?? "<grp_id>"}`
69729
68539
  );
69730
- return;
69731
- }
69732
- if (subcommand === "upgrade") {
69733
- const target = toolArgs;
69734
- if (!target) {
69735
- console.error(
69736
- "Usage: lotics package upgrade <app_id | pci_installation_id> [--version N] [--resolve <key>=... ...]"
69737
- );
69738
- process.exit(1);
69739
- }
69740
- let version2;
69741
- if (flags.packageVersion !== void 0) {
69742
- version2 = Number(flags.packageVersion);
69743
- if (!Number.isInteger(version2) || version2 <= 0) {
69744
- console.error(`Invalid --version "${flags.packageVersion}" \u2014 expected a positive integer.`);
69745
- process.exit(1);
69746
- }
69747
- }
69748
- if (target.startsWith("pci_")) {
69749
- await packageUpgradeKnowledge(client, {
69750
- installation_id: target,
69751
- version: version2,
69752
- resolve: flags.resolve,
69753
- bind_to: parseBindToFlags(flags.bindTo),
69754
- applyAll: flags.applyAll
69755
- });
69756
- return;
69757
- }
69758
- await packageUpgrade(client, {
69759
- app_id: target,
69760
- version: version2,
69761
- resolve: flags.resolve,
69762
- bindTo: flags.bindTo,
69763
- applyAll: flags.applyAll
69764
- });
69765
- return;
69766
- }
69767
- if (subcommand === "publish") {
69768
- await packagePublish(client, { projectDir: toolArgs, changelog: flags.message });
69769
- return;
69770
- }
69771
- if (subcommand === "sync") {
69772
- await packageSync(client, { projectDir: toolArgs });
69773
- return;
69774
- }
69775
- if (subcommand === "reset") {
69776
- await packageReset(client, { projectDir: toolArgs });
69777
- return;
68540
+ process.exit(1);
69778
68541
  }
69779
- if (subcommand === "dev") {
69780
- const projectDir = toolArgs;
69781
- let port;
69782
- let vitePort;
69783
- for (const a of restArgs) {
69784
- const portMatch = /^--port=(\d+)$/.exec(a);
69785
- const vitePortMatch = /^--vite-port=(\d+)$/.exec(a);
69786
- if (portMatch) port = Number(portMatch[1]);
69787
- else if (vitePortMatch) vitePort = Number(vitePortMatch[1]);
69788
- }
69789
- await packageDev(client, { projectDir, port, vitePort });
69790
- return;
68542
+ const movedVerbs = {
68543
+ install: "lotics install <package_id>",
68544
+ uninstall: "lotics uninstall <app_id|pci_id>",
68545
+ upgrade: "lotics upgrade <app_id|pci_id|package_id>",
68546
+ "fleet-upgrade": "lotics upgrade <package_id>"
68547
+ };
68548
+ if (subcommand !== void 0 && subcommand in movedVerbs) {
68549
+ console.error(
68550
+ `\`lotics package ${subcommand}\` moved to the top level \u2014 run \`${movedVerbs[subcommand]}\` instead.`
68551
+ );
68552
+ process.exit(1);
69791
68553
  }
69792
68554
  console.error(`Unknown package subcommand: ${subcommand}`);
69793
68555
  console.error("Run 'lotics package' for usage.");
@@ -69824,6 +68586,34 @@ Available workspaces:`);
69824
68586
  await appDeploy(client, { message });
69825
68587
  return;
69826
68588
  }
68589
+ if (subcommand === "publish") {
68590
+ await appPublish(client, {
68591
+ app_id: toolArgs,
68592
+ renames: flags.rename,
68593
+ changelog: flags.message,
68594
+ yes: flags.yes
68595
+ });
68596
+ return;
68597
+ }
68598
+ if (subcommand === "release") {
68599
+ if (!flags.message) {
68600
+ console.error("Usage: lotics app release [app_id|.] -m <changelog> [--yes]");
68601
+ console.error("Snapshots the origin app into its next package version (preview, then --yes to publish).");
68602
+ process.exit(1);
68603
+ }
68604
+ await appRelease(client, { app_id: toolArgs, changelog: flags.message, yes: flags.yes });
68605
+ return;
68606
+ }
68607
+ if (subcommand === "unpublish") {
68608
+ const id = toolArgs;
68609
+ if (!id) {
68610
+ console.error("Usage: lotics app unpublish <app_id|package_id> [--undo]");
68611
+ console.error("Takes a published package off the shelf; existing installations keep working.");
68612
+ process.exit(1);
68613
+ }
68614
+ await appUnpublish(client, { id, undo: flags.undo });
68615
+ return;
68616
+ }
69827
68617
  if (subcommand === "subdomain") {
69828
68618
  const newSubdomain = toolArgs;
69829
68619
  if (!newSubdomain) {