@lotics/cli 0.181.1 → 0.183.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
@@ -44516,14 +44516,13 @@ var LoticsClient = class {
44516
44516
  return this.request("POST", "/v1/apps", body);
44517
44517
  }
44518
44518
  /**
44519
- * Retire (or `undo` un-retire) a registry package (backs `lotics app
44520
- * unpublish` — the endpoint/audit action keep the `retire` name to avoid API
44521
- * churn). Retiring refuses NEW installs and hides the package from non-owning
44522
- * orgs; existing installations keep working and may still upgrade. Owner-org
44523
- * admin-only.
44519
+ * Take a starter off the shelf, or `undo` to put it back (backs `opctl
44520
+ * starter unpublish`). It hides from non-owning orgs and can no longer be
44521
+ * copied; copies already made are unaffected — they never linked back.
44522
+ * Owner-org admin-only.
44524
44523
  */
44525
- async retirePackage(package_id, body) {
44526
- return this.request("POST", `/v1/packages/${encodeURIComponent(package_id)}/retire`, body);
44524
+ async unpublishStarter(starter_id, body) {
44525
+ return this.request("POST", `/v1/starters/${encodeURIComponent(starter_id)}/unpublish`, body);
44527
44526
  }
44528
44527
  /**
44529
44528
  * Edit a starter's registry listing — the name and description a stranger
@@ -44532,13 +44531,12 @@ var LoticsClient = class {
44532
44531
  * A version is an immutable snapshot; the listing is not. Omit a field to
44533
44532
  * leave it, pass `description: null` to clear it. Owner-org admin-only.
44534
44533
  */
44535
- async editPackageListing(package_id, body) {
44536
- return this.request("POST", `/v1/packages/${encodeURIComponent(package_id)}/listing`, body);
44534
+ async editStarterListing(starter_id, body) {
44535
+ return this.request("POST", `/v1/starters/${encodeURIComponent(starter_id)}/listing`, body);
44537
44536
  }
44538
- // --- Packages (registry reads + installations) ---
44539
- // Authoring is server-side: apps via `POST /v1/apps/{id}/package-publish|release`,
44540
- // content packages via the `publish_content`/`release_content` tools. There is no
44541
- // client-side create-package / upload-bundle path.
44537
+ // --- Starters (registry reads + copies) ---
44538
+ // Authoring is server-side, through the publish job (`requestStarterPublish`).
44539
+ // There is no client-side create-starter / upload-bundle path.
44542
44540
  /**
44543
44541
  * The starters this organization can copy — Lotics-reviewed ones plus its own,
44544
44542
  * never a catalogue of everything published. The server returns exactly what
@@ -44551,10 +44549,9 @@ var LoticsClient = class {
44551
44549
  * Copy a starter into the current workspace.
44552
44550
  *
44553
44551
  * Server-side this scaffolds the schema, creates the templates, docs and
44554
- * sample records, creates a BESPOKE app and materializes onto it — then stops.
44555
- * It deploys nothing: a starter ships source only, and the source has to be
44556
- * built where codegen can bake THIS workspace's field ids. `bundle_url` is a
44557
- * presigned GET of that source, and finishing the job is the caller's half.
44552
+ * sample records, creates every app the starter carries and deploys each
44553
+ * from its prebuilt dist — no build anywhere. `apps` reports each deploy;
44554
+ * one that failed carries its `error` and the copy is complete around it.
44558
44555
  * Admin-only.
44559
44556
  */
44560
44557
  async instantiateStarter(starter_id, body) {
@@ -44598,12 +44595,12 @@ var LoticsClient = class {
44598
44595
  * Fetch a registry starter's metadata (`latest_version` and the Lotics-backed
44599
44596
  * `is_official` trust badge). Admin-only; cross-tenant by id.
44600
44597
  */
44601
- async getPackage(package_id) {
44602
- return this.request("GET", `/v1/packages/${encodeURIComponent(package_id)}`);
44598
+ async getStarter(starter_id) {
44599
+ return this.request("GET", `/v1/starters/${encodeURIComponent(starter_id)}`);
44603
44600
  }
44604
- /** Version history newest-first (no contract payloads) — backs `opctl package show`. Admin-only. */
44605
- async listPackageVersions(package_id) {
44606
- return this.request("GET", `/v1/packages/${encodeURIComponent(package_id)}/versions`);
44601
+ /** Version history newest-first (no contract payloads) — backs `opctl starter show`. Admin-only. */
44602
+ async listStarterVersions(starter_id) {
44603
+ return this.request("GET", `/v1/starters/${encodeURIComponent(starter_id)}/versions`);
44607
44604
  }
44608
44605
  /**
44609
44606
  * Workspace-wide dangling-reference sweep — active app/workflow artifacts
@@ -44613,74 +44610,45 @@ var LoticsClient = class {
44613
44610
  async getWorkspaceDanglingReferences() {
44614
44611
  return this.request("GET", "/v1/workspaces/dangling-references");
44615
44612
  }
44613
+ // --- Starter publishing (the authoring verbs; copying is `instantiateStarter`) ---
44616
44614
  /**
44617
- * Preview a release — the dry run behind `opctl app release`. Runs the
44618
- * binding-aware extract of the origin (aliases stable through the app's current
44619
- * binding) and reports the next version number, the new + changed aliases, the
44620
- * bundled-knowledge delta, and any extract findings (an `error` blocks the
44621
- * apply). An optional `knowledge` declaration (from the pulled app manifest)
44622
- * re-declares the bundle set — added/dropped/changed docs surface in the delta;
44623
- * omitted, the current corpus is reconstructed from the pin. No writes. Admin,
44624
- * owning-org only.
44615
+ * Preview publishing a set of this workspace's apps as one starter version —
44616
+ * the GET behind `opctl starter publish` (no `--yes`). The server runs the
44617
+ * same extraction the publish runs and reports which starter it would
44618
+ * release into (null: it would mint one), the next version, the aliases a
44619
+ * first publish can still rename, the diff against the current version, the
44620
+ * knowledge delta, and the findings (an `error` blocks the publish). No
44621
+ * writes. Admin-only.
44625
44622
  */
44626
- async previewPackageRelease(app_id, opts = {}) {
44623
+ async previewStarterPublish(opts) {
44627
44624
  const params = new URLSearchParams();
44628
- if (opts.knowledge !== void 0 && opts.knowledge.length > 0) {
44629
- params.set("knowledge", JSON.stringify(opts.knowledge));
44630
- }
44631
- const query = params.toString();
44632
- return this.request(
44633
- "GET",
44634
- `/v1/apps/${encodeURIComponent(app_id)}/package-release${query ? `?${query}` : ""}`
44635
- );
44625
+ params.set("app_ids", opts.app_ids.join(","));
44626
+ if (opts.knowledge_doc_ids !== void 0) params.set("knowledge_doc_ids", opts.knowledge_doc_ids.join(","));
44627
+ if (opts.renames !== void 0 && opts.renames.length > 0) params.set("renames", JSON.stringify(opts.renames));
44628
+ if (opts.name !== void 0) params.set("name", opts.name);
44629
+ if (opts.description !== void 0) params.set("description", opts.description);
44630
+ if (opts.icon !== void 0) params.set("icon", opts.icon);
44631
+ if (opts.color !== void 0) params.set("color", opts.color);
44632
+ return this.request("GET", `/v1/starters/publish-preview?${params.toString()}`);
44636
44633
  }
44637
44634
  /**
44638
- * Release — snapshot the origin app into the next registry version. The server
44639
- * binding-aware-extracts it, repackages its deployed source + dist as the
44640
- * bundle, publishes the next `release`-channel version with the changelog, and
44641
- * re-pins the origin. An optional `knowledge` declaration re-declares the
44642
- * bundled-knowledge set (added/dropped/re-snapshotted docs; omitted preserves
44643
- * the current corpus). Error findings from extract surface as a 409; a
44644
- * missing/archived declared doc is a 400, a foreign-package doc a 409. Admin,
44645
- * owning-org only. Backs `opctl app release --yes`.
44635
+ * Publish this workspace's apps as a starter version — a JOB, because every
44636
+ * app is built once against sentinel field keys and eleven builds outlast a
44637
+ * request. Everything a request can refuse is refused here with nothing
44638
+ * written: a blocking finding or another publish still running for this
44639
+ * org (409), a missing deploy or a bad declaration (400). The response is
44640
+ * the job to poll with `getStarterPublish`. Admin-only.
44646
44641
  */
44647
- async releasePackage(app_id, body) {
44648
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-release`, body);
44649
- }
44650
- /**
44651
- * Dry-run preview of a first-release — the `GET` behind `opctl app publish`
44652
- * (no `--yes`), the publish-side analogue of `previewPackageRelease`. The
44653
- * server runs the same fresh-alias extract + `src/` scan the apply runs
44654
- * (through any `renames`) and returns the package name it would mint, the
44655
- * auto-minted RENAMABLE aliases (the exact `--rename` keys), and the extract
44656
- * findings (an `error` blocks the apply). No writes. Admin-only.
44657
- */
44658
- async previewPublishAppPackage(app_id, opts = {}) {
44659
- const params = new URLSearchParams();
44660
- if (opts.knowledge !== void 0 && opts.knowledge.length > 0) {
44661
- params.set("knowledge", JSON.stringify(opts.knowledge));
44662
- }
44663
- if (opts.renames !== void 0 && opts.renames.length > 0) {
44664
- params.set("renames", JSON.stringify(opts.renames));
44665
- }
44666
- const query = params.toString();
44667
- return this.request(
44668
- "GET",
44669
- `/v1/apps/${encodeURIComponent(app_id)}/package-publish${query ? `?${query}` : ""}`
44670
- );
44642
+ async requestStarterPublish(body) {
44643
+ const { color, ...rest2 } = body;
44644
+ return this.request("POST", "/v1/starters/publishes", {
44645
+ ...rest2,
44646
+ ...color !== void 0 ? { theme: { color } } : {}
44647
+ });
44671
44648
  }
44672
- /**
44673
- * First-release apply — mint a package from a BESPOKE app and publish v1 in one
44674
- * call (the `POST` behind `opctl app publish --yes`). The server extracts an
44675
- * alias-keyed contract from the app (fresh aliases; `renames` fixes them before
44676
- * v1 freezes), creates the registry package (name/description from the app),
44677
- * publishes v1 from the app's deployed source + dist, and pins the origin as
44678
- * installation #1. Error findings from extract surface as a 409. An
44679
- * already-linked app must use `releasePackage` instead. Admin-only. Backs
44680
- * `opctl app publish <app_id>`.
44681
- */
44682
- async publishAppAsPackage(app_id, body) {
44683
- return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/package-publish`, body);
44649
+ /** The state of a publish: which app is building, and the version once every dist is in. */
44650
+ async getStarterPublish(publish_id) {
44651
+ return this.request("GET", `/v1/starters/publishes/${encodeURIComponent(publish_id)}`);
44684
44652
  }
44685
44653
  /**
44686
44654
  * Resolve the display name + fields (incl. select options) of the given tables
@@ -45711,7 +45679,7 @@ function resultSideEffects(result) {
45711
45679
  }
45712
45680
 
45713
45681
  // src/version.ts
45714
- var VERSION = "0.181.1";
45682
+ var VERSION = "0.183.0";
45715
45683
 
45716
45684
  // src/timezone.ts
45717
45685
  function machineTimezone() {
@@ -45881,11 +45849,11 @@ var COMMANDS = [
45881
45849
  {
45882
45850
  verbs: ["setup"],
45883
45851
  help: [
45884
- " lotics setup <starter_id> [path] --email <you@co.com>",
45852
+ " lotics setup <starter_id> --email <you@co.com>",
45885
45853
  " First run, one command: create an account if this",
45886
45854
  " machine has none, then copy the starter into its",
45887
45855
  " workspace \u2014 schema, templates, knowledge docs,",
45888
- " sample records, a deployed app \u2014 and print a",
45856
+ " sample records, its apps deployed \u2014 and print a",
45889
45857
  " one-time sign-in link. Drop --email to copy into",
45890
45858
  " the account you already have. --json prints one",
45891
45859
  " object and nothing else"
@@ -45922,13 +45890,13 @@ var COMMANDS = [
45922
45890
  " lotics starter list Starters you can copy into this workspace. Works with",
45923
45891
  " NO account \u2014 it then lists the published shelf, so you",
45924
45892
  " can decide whether to copy one or build before signing up",
45925
- " lotics starter show <starter_id> What one carries: tables, docs, templates, sample data",
45926
- " lotics starter init <starter_id> [path]",
45927
- " Copy it in \u2014 schema, templates, knowledge docs,",
45928
- " sample records and an editable app project. A COPY:",
45929
- " everything it creates is yours outright, with no",
45930
- " link back and nothing to upgrade. Builds and deploys",
45931
- " locally, so it needs node + a few minutes.",
45893
+ " lotics starter show <starter_id> Its name, description, current version, tile and trust",
45894
+ " standing \u2014 read it before copying one you did not publish",
45895
+ " lotics starter init <starter_id> Copy it in \u2014 schema, templates, knowledge docs,",
45896
+ " sample records and its apps, deployed on Lotics. A",
45897
+ " COPY: everything it creates is yours outright, with",
45898
+ " no link back and nothing to upgrade. Nothing lands",
45899
+ " on this machine; lotics app pull <app_id> edits one.",
45932
45900
  " --no-sample-data skips the sample records;",
45933
45901
  " --adopt allows a workspace that is not empty",
45934
45902
  " lotics starter fixtures capture --entity <alias> --limit <n>",
@@ -45946,7 +45914,7 @@ var COMMANDS = [
45946
45914
  {
45947
45915
  verbs: ["docs"],
45948
45916
  help: [
45949
- " lotics docs List the reference docs of the INSTALLED packages",
45917
+ " lotics docs List the reference docs the npm packages in node_modules ship",
45950
45918
  " lotics docs <area> Print one (e.g. 'lotics docs ai')"
45951
45919
  ]
45952
45920
  },
@@ -46495,10 +46463,10 @@ export default defineConfig({
46495
46463
  // openly at the asset layer \u2014 the map would expose the very source the gate
46496
46464
  // exists to protect. Source maps aren't needed to run the app.
46497
46465
  sourcemap: false,
46498
- // Top-level await (package projects' generated .lotics/app_fields.ts
46499
- // resolves the installation binding at module load) needs es2022 \u2014 Vite's
46500
- // default 'modules' baseline is es2020 and esbuild hard-fails TLA there.
46501
- // Dev already transforms at esnext, so this only aligns the prod build.
46466
+ // Vite's default 'modules' baseline is es2020, which is older than the
46467
+ // browsers a Lotics app is served to and older than what dev already
46468
+ // transforms at (esnext). Pinned so the prod build does not silently
46469
+ // down-level past a feature the dev server accepted.
46502
46470
  target: "es2022",
46503
46471
  },
46504
46472
  server: {
@@ -46521,10 +46489,10 @@ export default defineConfig({
46521
46489
  },
46522
46490
  test: {
46523
46491
  environment: "jsdom",
46524
- // Global test setup, run before every test file (empty until an author fills it)
46525
- // collects. A package-linked app's generated .lotics/app_fields.ts awaits it
46526
- // at module load; without the stub that network call throws under jsdom and
46527
- // every test that imports the app graph fails to collect. See vitest.setup.ts.
46492
+ // Global test setup, run before every test file. Ships empty \u2014 the slot is
46493
+ // the author's (custom matchers, a fetch stub, timezone pinning). It must
46494
+ // EXIST either way: setupFiles naming a missing file makes vitest fail to
46495
+ // load every test. See vitest.setup.ts.
46528
46496
  setupFiles: ["./${VITEST_SETUP_FILENAME}"],
46529
46497
  // RN packages ship Flow (\`import typeof\`) in their native source, reached
46530
46498
  // transitively by RN-Web components (pickers, calendars, anything touching
@@ -46892,18 +46860,12 @@ declare module "*.css";
46892
46860
  {
46893
46861
  path: "src/harness.test.ts",
46894
46862
  content: `import { describe, test, expect } from "vitest";
46895
- import { getAppBinding } from "@lotics/app-sdk";
46896
46863
 
46897
46864
  /**
46898
46865
  * The one test the scaffold seeds, and it is about the TEST SETUP rather than
46899
46866
  * about your app \u2014 so editing src/App.tsx can never break it, and \`npm test\`
46900
- * never has to be green over zero tests.
46901
- *
46902
- * What it pins: tests run under jsdom, and vitest.setup.ts's echo stub answers
46903
- * for \`getAppBinding\`. That second one is the whole reason the setup file
46904
- * exists: a package-linked app's generated .lotics/app_fields.ts awaits it while
46905
- * a test file is still COLLECTING, so without the stub every test fails before
46906
- * any of them runs \u2014 including pure logic tests that never touch a field id.
46867
+ * never has to be green over zero tests. It imports nothing of yours and
46868
+ * nothing generated, so it passes on a fresh clone before any codegen has run.
46907
46869
  *
46908
46870
  * Delete it once you have tests of your own, or keep it; it costs nothing.
46909
46871
  */
@@ -46911,13 +46873,6 @@ describe("test harness", () => {
46911
46873
  test("runs in a DOM", () => {
46912
46874
  expect(typeof document).toBe("object");
46913
46875
  });
46914
-
46915
- test("resolves the app binding from the setup stub, so the app graph collects", async () => {
46916
- const binding = await getAppBinding();
46917
- // The echo stub answers any alias with a self-identifying ':test:' id \u2014 one
46918
- // that can never be mistaken for a real fld_\u2026 from the workspace.
46919
- expect(binding.fields["anything.at.all"]).toContain(":test:");
46920
- });
46921
46876
  });
46922
46877
  `
46923
46878
  },
@@ -74054,7 +74009,6 @@ function parseArgs(argv) {
74054
74009
  // src/starter_commands.ts
74055
74010
  import fs9 from "node:fs";
74056
74011
  import path10 from "node:path";
74057
- import { tmpdir as tmpdir2 } from "node:os";
74058
74012
  function printStarterRows(rows) {
74059
74013
  const width = Math.max(...rows.map((row) => row.id.length));
74060
74014
  for (const row of rows) {
@@ -74117,7 +74071,7 @@ async function starterList(client) {
74117
74071
  description: s.description,
74118
74072
  tags: [
74119
74073
  s.is_official ? "official" : null,
74120
- s.owned_by_caller === true ? "yours" : null
74074
+ s.owned_by_caller ? "yours" : null
74121
74075
  ].filter((t) => t !== null)
74122
74076
  }))
74123
74077
  );
@@ -74125,19 +74079,17 @@ async function starterList(client) {
74125
74079
  lotics starter init <starter_id> Copy one into this workspace`);
74126
74080
  }
74127
74081
  async function starterShow(client, starter_id) {
74128
- const starter = await client.getPackage(starter_id);
74082
+ const starter = await client.getStarter(starter_id);
74129
74083
  console.log(`${starter.name} (${starter.id})`);
74130
74084
  if (starter.description) console.log(starter.description);
74131
74085
  console.log(
74132
74086
  `
74133
- version ${starter.latest_version}
74134
- trust ${starter.is_official ? "official \u2014 reviewed by Lotics" : starter.owned_by_caller === true ? "your organization's own" : "not copyable from this organization"}`
74087
+ version ${starter.latest_version}
74088
+ trust ${starter.is_official ? "official \u2014 reviewed by Lotics" : starter.owned_by_caller ? "your organization's own" : "not copyable from this organization"}`
74135
74089
  );
74136
- if (starter.icon !== void 0) {
74137
- console.log(`icon ${starter.icon ?? "none \u2014 copies show a generic tile"}`);
74138
- }
74090
+ console.log(`icon ${starter.icon ?? "none \u2014 the shelf shows a generic tile"}`);
74139
74091
  if (starter.retired_at !== null) {
74140
- console.log(`retired ${starter.retired_at} \u2014 can no longer be copied`);
74092
+ console.log(`unpublished ${starter.retired_at} \u2014 can no longer be copied`);
74141
74093
  }
74142
74094
  }
74143
74095
  function reportKnowledgeWarnings(warnings) {
@@ -74151,130 +74103,58 @@ function reportKnowledgeWarnings(warnings) {
74151
74103
  answering from nowhere. Create them with those names, or edit the agent.`
74152
74104
  );
74153
74105
  }
74154
- function instantiateBody(args) {
74155
- return {
74156
- ...args.noSampleData === true ? { no_sample_data: true } : {},
74157
- ...args.adopt === true ? { adopt: true } : {},
74158
- build_on_server: true
74159
- };
74160
- }
74161
74106
  async function starterInit(client, args) {
74162
- const starter = await client.getPackage(args.starter_id);
74163
- const targetPath = path10.resolve(args.targetPath ?? appDirName(starter.name));
74164
- if (fs9.existsSync(targetPath) && fs9.readdirSync(targetPath).length > 0) {
74165
- throw new Error(`Target directory ${targetPath} is not empty. Pass an empty path and re-run.`);
74166
- }
74107
+ const starter = await client.getStarter(args.starter_id);
74167
74108
  note(
74168
74109
  `Copying ${starter.name}${starter.is_official ? " (official)" : ""} into this workspace\u2026`
74169
74110
  );
74170
- const result = await client.instantiateStarter(args.starter_id, instantiateBody(args));
74111
+ const result = await client.instantiateStarter(args.starter_id, {
74112
+ ...args.noSampleData === true ? { no_sample_data: true } : {},
74113
+ ...args.adopt === true ? { adopt: true } : {}
74114
+ });
74171
74115
  const tables = Object.keys(result.binding.entities ?? {}).sort();
74172
74116
  const knowledgeDocs = Object.keys(result.binding.knowledge ?? {}).sort();
74173
74117
  const templates = Object.keys(result.binding.templates ?? {}).sort();
74174
74118
  const records = Object.values(result.sample_record_ids).reduce((n, ids) => n + ids.length, 0);
74175
- const created = {
74176
- tables,
74177
- templates,
74178
- knowledge_docs: knowledgeDocs,
74179
- sample_records: records
74180
- };
74119
+ const created = { tables, templates, knowledge_docs: knowledgeDocs, sample_records: records };
74120
+ const apps = result.apps.map((app) => ({
74121
+ alias: app.alias,
74122
+ app_id: app.app_id,
74123
+ name: app.name,
74124
+ version_number: app.deployed?.version_number ?? null,
74125
+ error: app.error
74126
+ }));
74181
74127
  note(
74182
- ` Created ${tables.length} table${tables.length === 1 ? "" : "s"}, ${templates.length} template${templates.length === 1 ? "" : "s"}, ${knowledgeDocs.length} knowledge doc${knowledgeDocs.length === 1 ? "" : "s"}, ${records} sample record${records === 1 ? "" : "s"}.`
74128
+ ` Created ${tables.length} table${tables.length === 1 ? "" : "s"}, ${templates.length} template${templates.length === 1 ? "" : "s"}, ${knowledgeDocs.length} knowledge doc${knowledgeDocs.length === 1 ? "" : "s"}, ${records} sample record${records === 1 ? "" : "s"}, ${apps.length} app${apps.length === 1 ? "" : "s"}.`
74183
74129
  );
74184
74130
  reportKnowledgeWarnings(result.knowledge_warnings);
74185
- if (result.app_id === null || result.bundle_url === null) {
74186
- note(`
74187
- Done \u2014 these are yours now, with no link back to the starter.`);
74188
- noteCopiedContent(templates, knowledgeDocs);
74189
- return {
74190
- starter_id: args.starter_id,
74191
- starter_name: starter.name,
74192
- version: result.version,
74193
- app_id: null,
74194
- project_dir: null,
74195
- created,
74196
- signin_url: null
74197
- };
74198
- }
74199
- fs9.mkdirSync(targetPath, { recursive: true });
74200
- note(`Downloading source\u2026`);
74201
- const bundleFile = path10.join(tmpdir2(), `lotics-starter-${args.starter_id}-${process.pid}.tar.gz`);
74202
- const wrapperDir = fs9.mkdtempSync(path10.join(tmpdir2(), `lotics-starter-`));
74203
- try {
74204
- await downloadToFile(result.bundle_url, bundleFile);
74205
- await runTar(["-xzf", bundleFile, "-C", wrapperDir], wrapperDir);
74206
- const sourceArchive = path10.join(wrapperDir, "source.tar.gz");
74207
- if (!fs9.existsSync(sourceArchive)) {
74208
- throw new Error(
74209
- `Starter bundle carried no source.tar.gz \u2014 the app (${result.app_id}) exists but has no code. Report this to the starter's publisher.`
74210
- );
74211
- }
74212
- await runTar(["-xzf", sourceArchive, "-C", targetPath], targetPath);
74213
- } finally {
74214
- if (fs9.existsSync(bundleFile)) fs9.unlinkSync(bundleFile);
74215
- fs9.rmSync(wrapperDir, { recursive: true, force: true });
74216
- }
74217
- for (const dir of ["knowledge", "fixtures"]) {
74218
- fs9.rmSync(path10.join(targetPath, dir), { recursive: true, force: true });
74219
- }
74220
- const app = await client.getApp(result.app_id);
74221
- const kept = [];
74222
- await hydrateAppProject(client, targetPath, app, {
74223
- // Nothing is deployed yet; the build below is what mints v1.
74224
- stamp: { id: null, number: null },
74225
- kept
74226
- });
74227
- const serverDeployed = result.deployed ?? null;
74228
- if (starter.owned_by_caller !== true) {
74131
+ if (!starter.owned_by_caller) {
74229
74132
  warn(
74230
- serverDeployed !== null ? `
74231
- Built ${starter.name} on Lotics \u2014 its package scripts and vite config are the
74232
- publisher's code, run in an isolated container rather than on your machine.${starter.is_official ? " Lotics reviewed this starter." : ""}` : `
74233
- Building ${starter.name} \u2014 this runs its build on your machine (its package scripts
74234
- and vite config are the publisher's code, executed as you).${starter.is_official ? " Lotics reviewed this starter." : ""}`
74133
+ `
74134
+ ${starter.name} is the publisher's code \u2014 its apps, workflows and agents \u2014 and runs in
74135
+ this workspace as you.${starter.is_official ? " Lotics reviewed this starter." : ""}`
74235
74136
  );
74236
- } else if (serverDeployed === null) {
74237
- note(`Building and deploying\u2026`);
74238
74137
  }
74239
- if (serverDeployed === null && result.build_error) {
74240
- warn(
74241
- `
74242
- Lotics could not build ${starter.name} \u2014 building here instead.
74243
- ${result.build_error.split("\n")[0]}
74244
- The copy is fine: the tables, the records and the app all landed. Do NOT
74245
- copy again, and do not write a replacement app \u2014 only the build is missing.`
74138
+ for (const app of apps) {
74139
+ note(
74140
+ app.error === null ? ` \u2713 ${app.name} (${app.app_id}, v${app.version_number})` : ` \u2717 ${app.name} (${app.app_id}): ${app.error.split("\n")[0]}`
74246
74141
  );
74247
74142
  }
74248
- const resumeDir = path10.relative(process.cwd(), targetPath) || ".";
74249
- try {
74250
- if (serverDeployed !== null) {
74251
- note(`Deployed v${serverDeployed.version_number} \u2014 built on Lotics, no Node needed here.`);
74252
- } else {
74253
- await prepareProjectToolchain(targetPath);
74254
- await appDeploy(client, {
74255
- projectDir: targetPath,
74256
- message: `Copied from starter ${starter.name}`
74257
- });
74258
- }
74259
- } catch (error52) {
74260
- const reason = error52 instanceof Error ? error52.message : String(error52);
74261
- throw new Error(
74262
- `${reason}
74263
-
74264
- The copy itself is DONE and nothing needs repeating: the tables, the sample
74265
- records and the project all landed. Only the deploy is left.
74266
-
74267
- Finish it: cd ${resumeDir} && lotics app deploy -m "Copied from starter ${starter.name}"
74268
-
74269
- Do NOT re-run the copy \u2014 it would adopt the tables this one just created and
74270
- insert the sample records again.`
74143
+ const failed = apps.filter((app) => app.error !== null);
74144
+ if (failed.length > 0) {
74145
+ warn(
74146
+ `
74147
+ ${failed.length} app${failed.length === 1 ? "" : "s"} landed without a version: ${failed.map((app) => app.name).join(", ")}.
74148
+ The copy is otherwise complete \u2014 do NOT copy again, and do not write a replacement
74149
+ app. Report this to the starter's publisher; a fixed starter is copied into a fresh workspace.`
74271
74150
  );
74151
+ process.exitCode = 1;
74272
74152
  }
74273
74153
  let signInUrl = null;
74274
74154
  try {
74275
74155
  const link = await client.login({
74276
74156
  return_link: true,
74277
- redirect_path: `/apps/${result.app_id}`
74157
+ redirect_path: apps.length === 1 ? `/apps/${apps[0].app_id}` : "/apps"
74278
74158
  });
74279
74159
  signInUrl = link.url ?? null;
74280
74160
  } catch (error52) {
@@ -74286,17 +74166,15 @@ insert the sample records again.`
74286
74166
  signInUrl = null;
74287
74167
  }
74288
74168
  note(
74289
- `
74169
+ (apps.length > 0 && failed.length === apps.length ? `
74170
+ ${starter.name} landed, but none of its ${apps.length} app${apps.length === 1 ? "" : "s"} is live.
74171
+ ` : `
74290
74172
  Done \u2014 ${starter.name} is live and yours.
74291
- App: ${result.app_id}
74292
- Project: ${targetPath}
74293
-
74294
- Edit anything: the tables, the app source, the templates, the docs. There is no
74295
- link back to the starter and nothing to upgrade \u2014 this is your app now.
74296
- Ship a change: cd ${path10.relative(process.cwd(), targetPath) || "."} && lotics app deploy -m "<what changed>"
74297
-
74298
- How to change it: lotics docs building_an_app (then \`lotics docs\` for the rest \u2014
74299
- the SDK and UI references are installed in this project, so they match its versions)`
74173
+ `) + `
74174
+ Edit anything: the tables, the apps, the templates, the docs. There is no link
74175
+ back to the starter and nothing to upgrade \u2014 this is your workspace now.
74176
+ Change an app's code: lotics app pull <app_id> (then lotics app deploy -m "<what changed>")
74177
+ How to change it: lotics docs building_an_app (then \`lotics docs\` for the rest)`
74300
74178
  );
74301
74179
  if (records > 0) {
74302
74180
  note(
@@ -74307,18 +74185,16 @@ Done \u2014 ${starter.name} is live and yours.
74307
74185
  }
74308
74186
  noteCopiedContent(templates, knowledgeDocs);
74309
74187
  if (signInUrl !== null) {
74310
- note(
74311
- `
74188
+ note(`
74312
74189
  Open it \u2014 one-time sign-in link, expires in 15 minutes:
74313
- ${signInUrl}`
74314
- );
74190
+ ${signInUrl}`);
74315
74191
  }
74316
74192
  return {
74317
74193
  starter_id: args.starter_id,
74318
74194
  starter_name: starter.name,
74319
74195
  version: result.version,
74320
- app_id: result.app_id,
74321
- project_dir: targetPath,
74196
+ app_ids: Object.fromEntries(apps.map((app) => [app.alias, app.app_id])),
74197
+ apps,
74322
74198
  created,
74323
74199
  signin_url: signInUrl
74324
74200
  };
@@ -103064,7 +102940,7 @@ async function runKnowledgeCommand(client, subcommand, toolArgs, flags, restArgs
103064
102940
  import { spawn as spawn3 } from "node:child_process";
103065
102941
  import { createServer } from "node:http";
103066
102942
  import { readFileSync, writeFileSync, existsSync as existsSync2, mkdtempSync, rmSync, readdirSync } from "node:fs";
103067
- import { tmpdir as tmpdir3 } from "node:os";
102943
+ import { tmpdir as tmpdir2 } from "node:os";
103068
102944
  import { join as join2, dirname, resolve, extname, basename } from "node:path";
103069
102945
  import { fileURLToPath as fileURLToPath2 } from "node:url";
103070
102946
  import { setTimeout as sleep } from "node:timers/promises";
@@ -103163,7 +103039,7 @@ async function runPreviewCommand(filePath, flags) {
103163
103039
  });
103164
103040
  await new Promise((r) => server.listen(0, "127.0.0.1", () => r()));
103165
103041
  const httpPort = server.address().port;
103166
- const udd = mkdtempSync(join2(tmpdir3(), "lotics-render-"));
103042
+ const udd = mkdtempSync(join2(tmpdir2(), "lotics-render-"));
103167
103043
  const child = spawn3(chrome, [
103168
103044
  "--headless=new",
103169
103045
  "--disable-gpu",
@@ -103675,7 +103551,7 @@ async function main() {
103675
103551
  console.error(
103676
103552
  `--version prints this CLI's version and takes no argument; it cannot be combined with a command.
103677
103553
  The CLI version: lotics --version
103678
- A package version to take: operator-only for now \u2014 \`lotics upgrade\` takes the next release.`
103554
+ A starter's version: a copy always takes the latest; there is no flag for an older one.`
103679
103555
  );
103680
103556
  process.exit(1);
103681
103557
  }
@@ -103686,10 +103562,10 @@ async function main() {
103686
103562
  if (command === "setup") {
103687
103563
  const starterId = subcommand;
103688
103564
  if (!starterId) {
103689
- console.error("Usage: lotics setup <starter_id> [path] [--email <you@co.com>] [--json]");
103565
+ console.error("Usage: lotics setup <starter_id> [--email <you@co.com>] [--json]");
103690
103566
  console.error(" Creates an account if this machine has none, then copies the starter");
103691
103567
  console.error(" into its workspace: schema, templates, knowledge docs, sample records");
103692
- console.error(" and a deployed app. Ends with a one-time sign-in link.");
103568
+ console.error(" and its apps, deployed. Ends with a one-time sign-in link.");
103693
103569
  console.error(" --json prints one object and nothing else.");
103694
103570
  process.exit(1);
103695
103571
  }
@@ -103699,7 +103575,7 @@ async function main() {
103699
103575
  const email3 = flags.email ?? (process.stdin.isTTY ? await prompt("Email: ") : "");
103700
103576
  if (!email3) {
103701
103577
  console.error(
103702
- "This machine has no Lotics credential yet, so `start` needs an email to create one:\n lotics setup " + starterId + " --email you@company.com"
103578
+ "This machine has no Lotics credential yet, so `setup` needs an email to create one:\n lotics setup " + starterId + " --email you@company.com"
103703
103579
  );
103704
103580
  process.exit(1);
103705
103581
  }
@@ -103725,9 +103601,9 @@ async function main() {
103725
103601
  }
103726
103602
  const { client: client2, ctx: ctx2 } = requireClient(flags);
103727
103603
  await resolveWorkspace(client2, ctx2);
103604
+ if (toolArgs !== void 0) warn(`A path is no longer needed \u2014 a copy writes nothing to disk. Ignoring "${toolArgs}".`);
103728
103605
  const result = await starterInit(client2, {
103729
103606
  starter_id: starterId,
103730
- ...toolArgs !== void 0 ? { targetPath: toolArgs } : {},
103731
103607
  ...flags.noSampleData === true ? { noSampleData: true } : {},
103732
103608
  ...flags.adopt === true ? { adopt: true } : {}
103733
103609
  });
@@ -103759,18 +103635,18 @@ async function main() {
103759
103635
  }
103760
103636
  if (subcommand === "init") {
103761
103637
  if (!toolArgs) {
103762
- console.error("Usage: lotics starter init <starter_id> [path] [--no-sample-data] [--adopt]");
103638
+ console.error("Usage: lotics starter init <starter_id> [--no-sample-data] [--adopt]");
103763
103639
  console.error(" Copies a starter into this workspace: schema, templates, knowledge");
103764
- console.error(" docs, sample records and an editable app project. Builds and deploys");
103765
- console.error(" locally, so it needs node and a few minutes.");
103640
+ console.error(" docs, sample records and its apps, deployed on Lotics. Nothing is");
103641
+ console.error(" written here; edit an app afterwards with lotics app pull <app_id>.");
103766
103642
  process.exit(1);
103767
103643
  }
103768
103644
  if (flags.json) setMachineOutput(true);
103769
103645
  const { client: client2, ctx: ctx2 } = requireClient(flags);
103770
103646
  await resolveWorkspace(client2, ctx2);
103647
+ if (restArgs[0] !== void 0) warn(`A path is no longer needed \u2014 a copy writes nothing to disk. Ignoring "${restArgs[0]}".`);
103771
103648
  const result = await starterInit(client2, {
103772
103649
  starter_id: toolArgs,
103773
- ...restArgs[0] !== void 0 ? { targetPath: restArgs[0] } : {},
103774
103650
  ...flags.noSampleData === true ? { noSampleData: true } : {},
103775
103651
  ...flags.adopt === true ? { adopt: true } : {}
103776
103652
  });
@@ -103804,7 +103680,7 @@ async function main() {
103804
103680
  return;
103805
103681
  }
103806
103682
  console.error(
103807
- "Usage: lotics starter list | show <starter_id> | init <starter_id> [path] | fixtures capture"
103683
+ "Usage: lotics starter list | show <starter_id> | init <starter_id> | fixtures capture"
103808
103684
  );
103809
103685
  process.exit(1);
103810
103686
  }