@dashai/cli 0.23.0 → 0.24.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/bin.js CHANGED
@@ -3,7 +3,7 @@ import pc from 'picocolors';
3
3
  import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, watch, readdirSync, chmodSync, renameSync, lstatSync, mkdtempSync, statSync } from 'fs';
4
4
  import { hostname, tmpdir, platform, homedir } from 'os';
5
5
  import { resolve, join, relative, dirname, basename, win32, sep } from 'path';
6
- import { spawn, spawnSync } from 'child_process';
6
+ import { execFileSync, spawn, spawnSync } from 'child_process';
7
7
  import { intro, log, select, isCancel, cancel, spinner, outro, text, confirm, multiselect } from '@clack/prompts';
8
8
  import { Command, Option } from 'commander';
9
9
  import open from 'open';
@@ -1406,13 +1406,13 @@ async function resolveDevPort(requested) {
1406
1406
  }
1407
1407
  async function isPortFree(port) {
1408
1408
  const { createServer } = await import('net');
1409
- return new Promise((resolve14) => {
1409
+ return new Promise((resolve15) => {
1410
1410
  const server = createServer();
1411
1411
  server.once("error", () => {
1412
- resolve14(false);
1412
+ resolve15(false);
1413
1413
  });
1414
1414
  server.once("listening", () => {
1415
- server.close(() => resolve14(true));
1415
+ server.close(() => resolve15(true));
1416
1416
  });
1417
1417
  server.listen(port, "0.0.0.0");
1418
1418
  });
@@ -1493,6 +1493,62 @@ var init_dev = __esm({
1493
1493
  PORT_PROBE_LIMIT = 10;
1494
1494
  }
1495
1495
  });
1496
+ function resolveSdkPackageJson(projectRoot) {
1497
+ let dir = projectRoot;
1498
+ for (; ; ) {
1499
+ const candidate = join(dir, "node_modules", "@dashai", "sdk", "package.json");
1500
+ if (existsSync(candidate)) return candidate;
1501
+ const parent = dirname(dir);
1502
+ if (parent === dir) return null;
1503
+ dir = parent;
1504
+ }
1505
+ }
1506
+ function readInstalledSdk(projectRoot) {
1507
+ const pkgPath = resolveSdkPackageJson(projectRoot);
1508
+ if (pkgPath === null) return null;
1509
+ try {
1510
+ const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
1511
+ if (typeof parsed.version !== "string" || parsed.version.length === 0) return null;
1512
+ return { version: parsed.version };
1513
+ } catch {
1514
+ return null;
1515
+ }
1516
+ }
1517
+ function compareVersions(a, b) {
1518
+ const parse = (v) => {
1519
+ const [core = "", pre] = v.trim().replace(/^[^0-9]*/, "").split("-", 2);
1520
+ const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
1521
+ while (nums.length < 3) nums.push(0);
1522
+ return [nums.slice(0, 3), pre !== void 0 && pre.length > 0];
1523
+ };
1524
+ const [an, apre] = parse(a);
1525
+ const [bn, bpre] = parse(b);
1526
+ for (let i = 0; i < 3; i++) {
1527
+ const d = (an[i] ?? 0) - (bn[i] ?? 0);
1528
+ if (d !== 0) return d;
1529
+ }
1530
+ if (apre === bpre) return 0;
1531
+ return apre ? -1 : 1;
1532
+ }
1533
+ function satisfiesFloor(version, floor) {
1534
+ return compareVersions(version, floor) >= 0;
1535
+ }
1536
+ function checkSdkFloor(projectRoot, floor) {
1537
+ const installed = readInstalledSdk(projectRoot);
1538
+ if (installed === null) return { ok: true, installed: null, floor };
1539
+ return {
1540
+ ok: satisfiesFloor(installed.version, floor),
1541
+ installed: installed.version,
1542
+ floor
1543
+ };
1544
+ }
1545
+ var USES_SDK_FLOOR, SCAFFOLD_SDK_RANGE;
1546
+ var init_sdk_version = __esm({
1547
+ "src/lib/sdk-version.ts"() {
1548
+ USES_SDK_FLOOR = "2.5.0";
1549
+ SCAFFOLD_SDK_RANGE = "^2.5.0";
1550
+ }
1551
+ });
1496
1552
  async function selectWorkspace(opts) {
1497
1553
  const workspaces = await apiRequest(
1498
1554
  "GET",
@@ -1551,62 +1607,6 @@ var init_workspaces = __esm({
1551
1607
  init_api_client();
1552
1608
  }
1553
1609
  });
1554
- function resolveSdkPackageJson(projectRoot) {
1555
- let dir = projectRoot;
1556
- for (; ; ) {
1557
- const candidate = join(dir, "node_modules", "@dashai", "sdk", "package.json");
1558
- if (existsSync(candidate)) return candidate;
1559
- const parent = dirname(dir);
1560
- if (parent === dir) return null;
1561
- dir = parent;
1562
- }
1563
- }
1564
- function readInstalledSdk(projectRoot) {
1565
- const pkgPath = resolveSdkPackageJson(projectRoot);
1566
- if (pkgPath === null) return null;
1567
- try {
1568
- const parsed = JSON.parse(readFileSync(pkgPath, "utf-8"));
1569
- if (typeof parsed.version !== "string" || parsed.version.length === 0) return null;
1570
- return { version: parsed.version };
1571
- } catch {
1572
- return null;
1573
- }
1574
- }
1575
- function compareVersions(a, b) {
1576
- const parse = (v) => {
1577
- const [core = "", pre] = v.trim().replace(/^[^0-9]*/, "").split("-", 2);
1578
- const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
1579
- while (nums.length < 3) nums.push(0);
1580
- return [nums.slice(0, 3), pre !== void 0 && pre.length > 0];
1581
- };
1582
- const [an, apre] = parse(a);
1583
- const [bn, bpre] = parse(b);
1584
- for (let i = 0; i < 3; i++) {
1585
- const d = (an[i] ?? 0) - (bn[i] ?? 0);
1586
- if (d !== 0) return d;
1587
- }
1588
- if (apre === bpre) return 0;
1589
- return apre ? -1 : 1;
1590
- }
1591
- function satisfiesFloor(version, floor) {
1592
- return compareVersions(version, floor) >= 0;
1593
- }
1594
- function checkSdkFloor(projectRoot, floor) {
1595
- const installed = readInstalledSdk(projectRoot);
1596
- if (installed === null) return { ok: true, installed: null, floor };
1597
- return {
1598
- ok: satisfiesFloor(installed.version, floor),
1599
- installed: installed.version,
1600
- floor
1601
- };
1602
- }
1603
- var USES_SDK_FLOOR, SCAFFOLD_SDK_RANGE;
1604
- var init_sdk_version = __esm({
1605
- "src/lib/sdk-version.ts"() {
1606
- USES_SDK_FLOOR = "2.5.0";
1607
- SCAFFOLD_SDK_RANGE = "^2.5.0";
1608
- }
1609
- });
1610
1610
 
1611
1611
  // src/lib/scaffold-locks/simple.json
1612
1612
  var simple_default;
@@ -9990,7 +9990,7 @@ function manifestJson(opts) {
9990
9990
  // everything on the default — a warning on every column just trains
9991
9991
  // people to click through warnings.
9992
9992
  fields: [
9993
- { slug: "title", type: "text", human_edit: "open" },
9993
+ { slug: "title", type: "text", primary: true, human_edit: "open" },
9994
9994
  { slug: "done", type: "boolean", human_edit: "guarded" }
9995
9995
  ]
9996
9996
  }
@@ -12206,6 +12206,8 @@ function claudeMd(opts) {
12206
12206
  Instructions for AI coding agents (Claude Code, Cursor, etc.) working
12207
12207
  in this Dashwise module. Read this before making changes.
12208
12208
 
12209
+ Every table declares exactly one primary field (the row's name everywhere in Dashwise).
12210
+
12209
12211
  ${opts.cloudReady === false ? "" : cloudAgentInstructions()}
12210
12212
  ## What this module is
12211
12213
 
@@ -12350,17 +12352,18 @@ which calls the schema endpoints and rewrites the local
12350
12352
  Common commands:
12351
12353
 
12352
12354
  \`\`\`
12353
- dashwise table create <slug> --field <slug:type[:required]>
12355
+ dashwise table create <slug> --field title:text:primary
12354
12356
  dashwise table delete <slug>
12355
12357
  dashwise field add <table> <slug:type[:required]>
12356
12358
  dashwise field remove <table> <slug>
12357
12359
  dashwise field set-type <table> <slug> <type>
12358
12360
  \`\`\`
12359
12361
 
12360
- Allowed field types (18): \`text\`, \`long_text\`, \`number\`, \`boolean\`,
12362
+ Allowed field types (21): \`text\`, \`long_text\`, \`number\`, \`boolean\`,
12361
12363
  \`date\`, \`datetime\`, \`single_select\`, \`multi_select\`,
12362
12364
  \`link_row\`, \`file\`, \`multi_file\`, \`email\`, \`url\`,
12363
- \`phone\`, \`formula\`, \`ai\`, \`uuid\`, \`jsonb\`.
12365
+ \`phone\`, \`formula\`, \`ai\`, \`uuid\`, \`jsonb\`,
12366
+ \`rating\`, \`duration\`, \`autonumber\`.
12364
12367
 
12365
12368
  ### \`human_edit\` \u2014 who may hand-edit each column
12366
12369
 
@@ -12614,7 +12617,7 @@ what tables it owns.
12614
12617
 
12615
12618
  | Table | Columns | Notes |
12616
12619
  |---|---|---|
12617
- | \`items\` | \`title:text\`, \`done:boolean\` | seeded example \u2014 edit or replace; \`dashwise table create <slug> --field <slug:type>\` adds more |
12620
+ | \`items\` | \`title:text:primary\`, \`done:boolean\` | seeded example \u2014 edit or replace; \`dashwise table create <slug> --field title:text:primary\` adds more |
12618
12621
 
12619
12622
  ## API / contract
12620
12623
 
@@ -12756,6 +12759,8 @@ function cloudAgentInstructions() {
12756
12759
  function agentsMd(opts) {
12757
12760
  return `# ${opts.name}: agent instructions
12758
12761
 
12762
+ Every table declares exactly one primary field (the row's name everywhere in Dashwise).
12763
+
12759
12764
  ` + cloudAgentInstructions();
12760
12765
  }
12761
12766
  function nodeVersion() {
@@ -14023,6 +14028,16 @@ function printApplySummary(applied) {
14023
14028
  const updated = asArray(applied.tables_updated);
14024
14029
  const removed = asArray(applied.tables_removed);
14025
14030
  success(`Manifest applied to the dev install.`);
14031
+ for (const warning of asArray(applied.warnings)) {
14032
+ const message = strProp(warning, "message");
14033
+ if (!message) continue;
14034
+ const warningCode = strProp(warning, "code");
14035
+ const table = strProp(warning, "table");
14036
+ const field = strProp(warning, "field");
14037
+ const location = table ? `${table}${field ? `.${field}` : ""}` : field;
14038
+ const prefix = [warningCode, location].filter(Boolean).join(" ");
14039
+ warn(`${prefix ? `${code(prefix)}: ` : ""}${message}`);
14040
+ }
14026
14041
  if (created.length === 0 && updated.length === 0 && removed.length === 0) {
14027
14042
  info(dim("No table changes (metadata-only or already in sync)."));
14028
14043
  return;
@@ -14132,6 +14147,76 @@ var init_push = __esm({
14132
14147
  }
14133
14148
  });
14134
14149
 
14150
+ // ../sdk/dist/index.js
14151
+ function primaryDeclarationIssues(table) {
14152
+ const invalid = table.fields.findIndex(
14153
+ (field2) => field2.primary !== void 0 && field2.primary !== true
14154
+ );
14155
+ if (invalid !== -1) {
14156
+ return [
14157
+ { path: ["fields", invalid, "primary"], message: "primary must be literal true or omitted" }
14158
+ ];
14159
+ }
14160
+ const marked = table.fields.filter((field2) => field2.primary === true);
14161
+ if (marked.length !== 1) {
14162
+ return [
14163
+ {
14164
+ path: ["fields"],
14165
+ message: `Table ${table.slug}: mark exactly one field as primary: true (the row's label in links, pickers, titles)`
14166
+ }
14167
+ ];
14168
+ }
14169
+ const field = marked[0];
14170
+ const path = ["fields", table.fields.indexOf(field), "primary"];
14171
+ if (field.type === "formula") {
14172
+ return [{ path, message: "formula fields cannot be the primary of a module table yet" }];
14173
+ }
14174
+ if (!PRIMARY_TYPES.has(field.type)) {
14175
+ return [
14176
+ {
14177
+ path,
14178
+ message: `Table ${table.slug}: mark a field that can be the primary with primary: true (${field.slug} of type ${field.type} cannot be primary)`
14179
+ }
14180
+ ];
14181
+ }
14182
+ return [];
14183
+ }
14184
+ function weakPrimaryWarning(table) {
14185
+ const field = table.fields.find((field2) => field2.primary === true);
14186
+ if (!field || !WEAK_PRIMARY_TYPES.has(field.type)) return void 0;
14187
+ return `${field.slug} is a weak row label; prefer a short text field such as a name or title`;
14188
+ }
14189
+ var PRIMARY_TYPES, WEAK_PRIMARY_TYPES;
14190
+ var init_dist = __esm({
14191
+ "../sdk/dist/index.js"() {
14192
+ PRIMARY_TYPES = /* @__PURE__ */ new Set([
14193
+ "text",
14194
+ "long_text",
14195
+ "number",
14196
+ "date",
14197
+ "datetime",
14198
+ "single_select",
14199
+ "multi_select",
14200
+ "email",
14201
+ "url",
14202
+ "phone",
14203
+ "rating",
14204
+ "duration",
14205
+ "autonumber"
14206
+ ]);
14207
+ WEAK_PRIMARY_TYPES = /* @__PURE__ */ new Set([
14208
+ "single_select",
14209
+ "multi_select",
14210
+ "number",
14211
+ "rating",
14212
+ "duration",
14213
+ "date",
14214
+ "datetime",
14215
+ "autonumber"
14216
+ ]);
14217
+ }
14218
+ });
14219
+
14135
14220
  // src/lib/field-spec.ts
14136
14221
  function parseFieldSpec(spec) {
14137
14222
  const parts = spec.split(":");
@@ -14186,6 +14271,7 @@ function parseFieldSpec(spec) {
14186
14271
  );
14187
14272
  }
14188
14273
  if (flag === "required") result.required = true;
14274
+ if (flag === "primary") result.primary = true;
14189
14275
  }
14190
14276
  }
14191
14277
  if ((type === "single_select" || type === "multi_select") && result.options === void 0) {
@@ -14228,6 +14314,9 @@ function parseFieldJson(json) {
14228
14314
  `--field-json missing or invalid ${quote("type")}. Allowed: ${FIELD_TYPES.join(", ")}.`
14229
14315
  );
14230
14316
  }
14317
+ if (o.primary !== void 0 && o.primary !== true) {
14318
+ throw new FieldSpecError("primary must be literal true or omitted");
14319
+ }
14231
14320
  return o;
14232
14321
  }
14233
14322
  function quote(s) {
@@ -14240,6 +14329,9 @@ var init_field_spec = __esm({
14240
14329
  "text",
14241
14330
  "long_text",
14242
14331
  "number",
14332
+ "rating",
14333
+ "duration",
14334
+ "autonumber",
14243
14335
  "boolean",
14244
14336
  "date",
14245
14337
  "datetime",
@@ -14255,7 +14347,7 @@ var init_field_spec = __esm({
14255
14347
  "ai"
14256
14348
  ];
14257
14349
  SLUG_REGEX = /^[a-z][a-z0-9_]*[a-z0-9]$/;
14258
- KNOWN_FLAGS = /* @__PURE__ */ new Set(["required"]);
14350
+ KNOWN_FLAGS = /* @__PURE__ */ new Set(["required", "primary"]);
14259
14351
  DECIMALS_FLAG_REGEX = /^\d+$/;
14260
14352
  MAX_DECIMAL_PLACES = 10;
14261
14353
  FieldSpecError = class extends Error {
@@ -14276,7 +14368,7 @@ __export(create_exports, {
14276
14368
  });
14277
14369
  async function tableCreateCommand(slug, options) {
14278
14370
  if (!slug) {
14279
- fail("Usage: `dashwise table create <slug> --field <slug>:<type>[:required] [...]`");
14371
+ fail("Usage: `dashwise table create <slug> --field <slug>:<type>[:primary,required] [...]`");
14280
14372
  return 1;
14281
14373
  }
14282
14374
  const fields = [];
@@ -14308,6 +14400,15 @@ async function tableCreateCommand(slug, options) {
14308
14400
  }
14309
14401
  seen.add(f.slug);
14310
14402
  }
14403
+ const table = { slug, fields };
14404
+ const issues = primaryDeclarationIssues(table);
14405
+ if (issues.length > 0) {
14406
+ for (const issue of issues) fail(issue.message);
14407
+ info("Mark the row name explicitly, e.g. --field title:text:primary.");
14408
+ return 1;
14409
+ }
14410
+ const primaryWarning = weakPrimaryWarning(table);
14411
+ if (primaryWarning) warn(primaryWarning);
14311
14412
  let ctx;
14312
14413
  try {
14313
14414
  ctx = loadSchemaCommandContext({
@@ -14400,6 +14501,7 @@ function surfaceFailure(err) {
14400
14501
  }
14401
14502
  var init_create = __esm({
14402
14503
  "src/commands/table/create.ts"() {
14504
+ init_dist();
14403
14505
  init_manifest_io();
14404
14506
  init_api_client();
14405
14507
  init_manifest_merge();
@@ -15021,6 +15123,433 @@ function toMe(cached) {
15021
15123
  };
15022
15124
  }
15023
15125
 
15126
+ // src/commands/doctor.ts
15127
+ init_api_client();
15128
+ init_config();
15129
+ init_output();
15130
+ init_sdk_version();
15131
+ var MIN_NODE_MAJOR = 20;
15132
+ var CLOUD_FILES = [
15133
+ "scripts/cloud-setup.sh",
15134
+ ".claude/settings.json",
15135
+ "AGENTS.md",
15136
+ "CLAUDE.md"
15137
+ ];
15138
+ async function doctorCommand(options, deps) {
15139
+ const checks = await runDoctorChecks(resolve(options.dir ?? process.cwd()), deps);
15140
+ const ok = !checks.some((c2) => c2.status === "fail");
15141
+ if (options.json) {
15142
+ out(JSON.stringify({ ok, checks }, null, 2));
15143
+ } else {
15144
+ for (const c2 of checks) {
15145
+ out(`${badge(c2.status)} ${c2.label}: ${c2.detail}`);
15146
+ if (c2.fix) out(` ${dim("\u2192")} ${c2.fix}`);
15147
+ }
15148
+ const fails = checks.filter((c2) => c2.status === "fail").length;
15149
+ const warns = checks.filter((c2) => c2.status === "warn").length;
15150
+ out("");
15151
+ out(
15152
+ fails === 0 && warns === 0 ? pc.green("Everything looks good.") : `${fails} problem${fails === 1 ? "" : "s"}, ${warns} warning${warns === 1 ? "" : "s"}.`
15153
+ );
15154
+ }
15155
+ return ok ? 0 : 1;
15156
+ }
15157
+ async function runDoctorChecks(dir, deps) {
15158
+ const checks = [];
15159
+ const apiUrl = resolveApiUrl();
15160
+ checks.push(targetCheck(apiUrl));
15161
+ const cfg = safeReadConfig2();
15162
+ let signedIn = false;
15163
+ if (!cfg?.token) {
15164
+ checks.push({
15165
+ id: "login",
15166
+ label: "Login",
15167
+ status: "fail",
15168
+ detail: "not signed in",
15169
+ fix: `Run ${code("dashwise login")}.`
15170
+ });
15171
+ checks.push(await connectionOnly(apiUrl));
15172
+ } else {
15173
+ try {
15174
+ const me = await apiRequest("GET", "/api/auth/me");
15175
+ signedIn = true;
15176
+ checks.push({
15177
+ id: "login",
15178
+ label: "Login",
15179
+ status: "ok",
15180
+ detail: `signed in as ${me.email}`
15181
+ });
15182
+ checks.push({
15183
+ id: "connection",
15184
+ label: "Connection",
15185
+ status: "ok",
15186
+ detail: `${apiUrl} answers`
15187
+ });
15188
+ } catch (err) {
15189
+ if (err instanceof ApiError && err.code === "NETWORK_ERROR") {
15190
+ checks.push({
15191
+ id: "login",
15192
+ label: "Login",
15193
+ status: "skip",
15194
+ detail: "not checked, Dashwise is not reachable"
15195
+ });
15196
+ checks.push(unreachable(apiUrl));
15197
+ } else if (err instanceof ApiError && err.status === 401) {
15198
+ checks.push({
15199
+ id: "login",
15200
+ label: "Login",
15201
+ status: "fail",
15202
+ detail: "the saved login is expired or was revoked",
15203
+ fix: `Run ${code("dashwise login")} again.`
15204
+ });
15205
+ checks.push({
15206
+ id: "connection",
15207
+ label: "Connection",
15208
+ status: "ok",
15209
+ detail: `${apiUrl} answers`
15210
+ });
15211
+ } else {
15212
+ checks.push({
15213
+ id: "login",
15214
+ label: "Login",
15215
+ status: "fail",
15216
+ detail: `could not check the login (${err.message})`,
15217
+ fix: `Try again; if it stays, run ${code("dashwise login")}.`
15218
+ });
15219
+ checks.push({
15220
+ id: "connection",
15221
+ label: "Connection",
15222
+ status: "warn",
15223
+ detail: `${apiUrl} answered with an error`
15224
+ });
15225
+ }
15226
+ }
15227
+ }
15228
+ checks.push(nodeCheck(deps.nodeVersion));
15229
+ checks.push(
15230
+ await versionCheck(
15231
+ "cli",
15232
+ "Dashwise CLI",
15233
+ deps.cliVersion,
15234
+ await deps.latestVersion("@dashai/cli"),
15235
+ `Run ${code("npx @dashai/cli@latest <command>")} or ${code("npm install -g @dashai/cli@latest")}.`
15236
+ )
15237
+ );
15238
+ const manifestPath2 = join(dir, "module.json");
15239
+ if (!existsSync(manifestPath2)) {
15240
+ checks.push({
15241
+ id: "module",
15242
+ label: "Module",
15243
+ status: "skip",
15244
+ detail: `no ${code("module.json")} here, so the module checks were skipped`,
15245
+ fix: `Run ${code("dashwise doctor")} inside a module folder to check it too.`
15246
+ });
15247
+ return checks;
15248
+ }
15249
+ let parsed = false;
15250
+ let manifest = null;
15251
+ try {
15252
+ manifest = JSON.parse(readFileSync(manifestPath2, "utf-8"));
15253
+ parsed = true;
15254
+ } catch (err) {
15255
+ checks.push({
15256
+ id: "module",
15257
+ label: "module.json",
15258
+ status: "fail",
15259
+ detail: `cannot be read: ${err.message}`,
15260
+ fix: `Fix the JSON in ${code("module.json")}.`
15261
+ });
15262
+ }
15263
+ const header = manifest !== null && typeof manifest === "object" ? manifest.module : void 0;
15264
+ const slug = typeof header?.slug === "string" ? header.slug : null;
15265
+ if (parsed) checks.push(await manifestCheck(manifest, signedIn));
15266
+ const sdk = readInstalledSdk(dir);
15267
+ checks.push(
15268
+ sdk ? await versionCheck(
15269
+ "sdk",
15270
+ "Dashwise SDK",
15271
+ sdk.version,
15272
+ await deps.latestVersion("@dashai/sdk"),
15273
+ `Run ${code("npm install @dashai/sdk@latest")}.`
15274
+ ) : {
15275
+ id: "sdk",
15276
+ label: "Dashwise SDK",
15277
+ status: "warn",
15278
+ detail: "not installed in this folder",
15279
+ fix: `Run ${code("npm install")}.`
15280
+ }
15281
+ );
15282
+ checks.push(await repoCheck(dir, slug, signedIn, deps));
15283
+ const missing = CLOUD_FILES.filter((f) => !existsSync(join(dir, f)));
15284
+ checks.push(
15285
+ missing.length === 0 ? {
15286
+ id: "cloud-setup",
15287
+ label: "Cloud setup files",
15288
+ status: "ok",
15289
+ detail: "present (cloud agents can set up this module)"
15290
+ } : {
15291
+ id: "cloud-setup",
15292
+ label: "Cloud setup files",
15293
+ status: "warn",
15294
+ detail: `missing: ${missing.join(", ")}`,
15295
+ fix: `Copy them from a fresh ${code("dashwise init")} of the template, so Claude Code web and Codex cloud can set this module up.`
15296
+ }
15297
+ );
15298
+ return checks;
15299
+ }
15300
+ function targetCheck(apiUrl) {
15301
+ const source = process.env.DASHWISE_API_URL?.trim() ? "from DASHWISE_API_URL" : "from your saved login";
15302
+ if (sameOrigin(apiUrl, KNOWN_API_URLS.prod)) {
15303
+ return {
15304
+ id: "target",
15305
+ label: "Target",
15306
+ status: "warn",
15307
+ detail: `PRODUCTION (${apiUrl}, ${source}). Commands here change real company data.`,
15308
+ fix: `To build against dev, run ${code(`dashwise login --api-url ${KNOWN_API_URLS.dev}`)} or set ${code(`DASHWISE_API_URL=${KNOWN_API_URLS.dev}`)}.`
15309
+ };
15310
+ }
15311
+ if (sameOrigin(apiUrl, KNOWN_API_URLS.dev)) {
15312
+ return {
15313
+ id: "target",
15314
+ label: "Target",
15315
+ status: "ok",
15316
+ detail: `dev (${apiUrl}, ${source})`
15317
+ };
15318
+ }
15319
+ return {
15320
+ id: "target",
15321
+ label: "Target",
15322
+ status: "ok",
15323
+ detail: `custom (${apiUrl}, ${source})`
15324
+ };
15325
+ }
15326
+ async function connectionOnly(apiUrl) {
15327
+ try {
15328
+ await apiRequest("GET", "/api/auth/me", void 0, { auth: "public" });
15329
+ } catch (err) {
15330
+ if (err instanceof ApiError && err.code === "NETWORK_ERROR") {
15331
+ return unreachable(apiUrl);
15332
+ }
15333
+ }
15334
+ return {
15335
+ id: "connection",
15336
+ label: "Connection",
15337
+ status: "ok",
15338
+ detail: `${apiUrl} answers`
15339
+ };
15340
+ }
15341
+ function unreachable(apiUrl) {
15342
+ return {
15343
+ id: "connection",
15344
+ label: "Connection",
15345
+ status: "fail",
15346
+ detail: `cannot reach ${apiUrl}`,
15347
+ fix: "Check your network, VPN or proxy, and that the address is right."
15348
+ };
15349
+ }
15350
+ function nodeCheck(version) {
15351
+ const major = Number.parseInt(version.replace(/^v/, ""), 10);
15352
+ return Number.isFinite(major) && major >= MIN_NODE_MAJOR ? { id: "node", label: "Node", status: "ok", detail: version } : {
15353
+ id: "node",
15354
+ label: "Node",
15355
+ status: "fail",
15356
+ detail: `${version} is too old`,
15357
+ fix: `Install Node ${MIN_NODE_MAJOR} or newer (the template pins Node 22).`
15358
+ };
15359
+ }
15360
+ async function versionCheck(id, label, installed, latest, fix) {
15361
+ if (!latest) {
15362
+ return {
15363
+ id,
15364
+ label,
15365
+ status: "ok",
15366
+ detail: `${installed} (could not look up the newest version)`
15367
+ };
15368
+ }
15369
+ return compareVersions(installed, latest) < 0 ? {
15370
+ id,
15371
+ label,
15372
+ status: "warn",
15373
+ detail: `${installed}, newest is ${latest}`,
15374
+ fix
15375
+ } : { id, label, status: "ok", detail: `${installed} (newest)` };
15376
+ }
15377
+ async function manifestCheck(manifest, signedIn) {
15378
+ if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest)) {
15379
+ return {
15380
+ id: "module",
15381
+ label: "module.json",
15382
+ status: "fail",
15383
+ detail: "is not a JSON object",
15384
+ fix: `Run ${code("dashwise module validate")} and fix ${code("module.json")}.`
15385
+ };
15386
+ }
15387
+ if (!signedIn) {
15388
+ return {
15389
+ id: "module",
15390
+ label: "module.json",
15391
+ status: "ok",
15392
+ detail: "readable (not validated, you are not signed in)"
15393
+ };
15394
+ }
15395
+ try {
15396
+ await apiRequest("POST", "/api/modules/manifest/validate", manifest);
15397
+ return {
15398
+ id: "module",
15399
+ label: "module.json",
15400
+ status: "ok",
15401
+ detail: "valid"
15402
+ };
15403
+ } catch (err) {
15404
+ if (err instanceof ApiError && err.status === 400) {
15405
+ return {
15406
+ id: "module",
15407
+ label: "module.json",
15408
+ status: "fail",
15409
+ detail: `invalid: ${err.message}`,
15410
+ fix: `Run ${code("dashwise module validate")} for the full list.`
15411
+ };
15412
+ }
15413
+ return {
15414
+ id: "module",
15415
+ label: "module.json",
15416
+ status: "warn",
15417
+ detail: `not validated (${err.message})`
15418
+ };
15419
+ }
15420
+ }
15421
+ async function repoCheck(dir, slug, signedIn, deps) {
15422
+ const origin = deps.gitOrigin(dir);
15423
+ if (!slug || !signedIn) {
15424
+ return {
15425
+ id: "repository",
15426
+ label: "Repository",
15427
+ status: "skip",
15428
+ detail: !slug ? `no module slug in ${code("module.json")}` : "not checked, you are not signed in"
15429
+ };
15430
+ }
15431
+ let linked = null;
15432
+ try {
15433
+ const mod = await apiRequest(
15434
+ "GET",
15435
+ `/api/modules/by-slug/${encodeURIComponent(slug)}`
15436
+ );
15437
+ if (!("repository_url" in mod)) {
15438
+ return {
15439
+ id: "repository",
15440
+ label: "Repository",
15441
+ status: "warn",
15442
+ code: "REPOSITORY_NOT_VISIBLE",
15443
+ detail: `you cannot see the repository of ${code(slug)}: only its builders and owners in its company can`,
15444
+ fix: "Ask one of the module owners to add you as a builder."
15445
+ };
15446
+ }
15447
+ linked = mod.repository_url ?? null;
15448
+ } catch (err) {
15449
+ return {
15450
+ id: "repository",
15451
+ label: "Repository",
15452
+ status: "warn",
15453
+ detail: err instanceof ApiError && err.status === 404 ? `module ${code(slug)} is not found in this Dashwise` : `could not read the module (${err.message})`,
15454
+ fix: "Check the target above and that you can build this module."
15455
+ };
15456
+ }
15457
+ if (!linked) {
15458
+ return {
15459
+ id: "repository",
15460
+ label: "Repository",
15461
+ status: "warn",
15462
+ code: "REPOSITORY_NOT_LINKED",
15463
+ detail: "the module has no repository linked in Dashwise",
15464
+ fix: "Link one on the module's Build tab in Dashwise."
15465
+ };
15466
+ }
15467
+ if (!origin) {
15468
+ return {
15469
+ id: "repository",
15470
+ label: "Repository",
15471
+ status: "warn",
15472
+ detail: `linked to ${linked}, but this folder has no git origin`,
15473
+ fix: `Run ${code(`git remote add origin ${linked}.git`)}.`
15474
+ };
15475
+ }
15476
+ return sameRepo(origin, linked) ? {
15477
+ id: "repository",
15478
+ label: "Repository",
15479
+ status: "ok",
15480
+ detail: `${linked} (matches git origin)`
15481
+ } : {
15482
+ id: "repository",
15483
+ label: "Repository",
15484
+ status: "warn",
15485
+ detail: `git origin is ${origin}, Dashwise builds from ${linked}`,
15486
+ fix: `Push to ${linked}, or link this repository on the module's Build tab.`
15487
+ };
15488
+ }
15489
+ function repoKey(url) {
15490
+ const m = /github\.com[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/i.exec(url.trim());
15491
+ return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
15492
+ }
15493
+ function sameRepo(a, b) {
15494
+ const ka = repoKey(a);
15495
+ return ka !== null && ka === repoKey(b);
15496
+ }
15497
+ function sameOrigin(a, b) {
15498
+ try {
15499
+ return new URL(a).origin === new URL(b).origin;
15500
+ } catch {
15501
+ return false;
15502
+ }
15503
+ }
15504
+ function safeReadConfig2() {
15505
+ try {
15506
+ return readConfig();
15507
+ } catch {
15508
+ return null;
15509
+ }
15510
+ }
15511
+ function badge(status) {
15512
+ switch (status) {
15513
+ case "ok":
15514
+ return pc.green("\u2714");
15515
+ case "warn":
15516
+ return pc.yellow("!");
15517
+ case "fail":
15518
+ return pc.red("\u2716");
15519
+ default:
15520
+ return dim("-");
15521
+ }
15522
+ }
15523
+ function defaultDoctorDeps(cliVersion) {
15524
+ return {
15525
+ cliVersion,
15526
+ nodeVersion: process.version,
15527
+ latestVersion: async (pkg) => {
15528
+ try {
15529
+ const res = await fetch(
15530
+ `https://registry.npmjs.org/${pkg.replace("/", "%2f")}/latest`,
15531
+ { signal: AbortSignal.timeout(4e3) }
15532
+ );
15533
+ if (!res.ok) return null;
15534
+ const body = await res.json();
15535
+ return typeof body.version === "string" ? body.version : null;
15536
+ } catch {
15537
+ return null;
15538
+ }
15539
+ },
15540
+ gitOrigin: (dir) => {
15541
+ try {
15542
+ return execFileSync("git", ["remote", "get-url", "origin"], {
15543
+ cwd: dir,
15544
+ stdio: ["ignore", "pipe", "ignore"]
15545
+ }).toString().trim() || null;
15546
+ } catch {
15547
+ return null;
15548
+ }
15549
+ }
15550
+ };
15551
+ }
15552
+
15024
15553
  // src/commands/mcp.ts
15025
15554
  init_api_client();
15026
15555
  init_config();
@@ -15145,8 +15674,8 @@ function startMcpProxy(opts) {
15145
15674
  let streamUnavailable = false;
15146
15675
  let streamStarted = false;
15147
15676
  let resolveDone;
15148
- const done = new Promise((resolve14) => {
15149
- resolveDone = resolve14;
15677
+ const done = new Promise((resolve15) => {
15678
+ resolveDone = resolve15;
15150
15679
  });
15151
15680
  const trace = (line) => {
15152
15681
  if (verbose) log2(line);
@@ -15632,7 +16161,7 @@ async function revokeSession(endpointId) {
15632
16161
  () => false
15633
16162
  );
15634
16163
  const timeout = new Promise(
15635
- (resolve14) => setTimeout(() => resolve14(false), REVOKE_TIMEOUT_MS).unref()
16164
+ (resolve15) => setTimeout(() => resolve15(false), REVOKE_TIMEOUT_MS).unref()
15636
16165
  );
15637
16166
  await Promise.race([attempt, timeout]);
15638
16167
  }
@@ -15810,7 +16339,7 @@ async function mcpAddCommand(options = {}) {
15810
16339
  fail(`Could not run \`claude\`: ${target.refused} Run the command above yourself.`);
15811
16340
  return 1;
15812
16341
  }
15813
- return await new Promise((resolve14) => {
16342
+ return await new Promise((resolve15) => {
15814
16343
  const child = spawn(target.command, target.args, { shell: target.shell, stdio: "inherit" });
15815
16344
  let settled = false;
15816
16345
  child.on("error", (err) => {
@@ -15820,16 +16349,16 @@ async function mcpAddCommand(options = {}) {
15820
16349
  fail(
15821
16350
  `Claude Code is not on your PATH, so nothing was registered. Run the command above once ${code("claude")} is installed.`
15822
16351
  );
15823
- resolve14(1);
16352
+ resolve15(1);
15824
16353
  return;
15825
16354
  }
15826
16355
  fail(`Could not run \`claude\`: ${err.message}`);
15827
- resolve14(1);
16356
+ resolve15(1);
15828
16357
  });
15829
16358
  child.on("close", (codeOut) => {
15830
16359
  if (settled) return;
15831
16360
  settled = true;
15832
- resolve14(codeOut ?? 1);
16361
+ resolve15(codeOut ?? 1);
15833
16362
  });
15834
16363
  });
15835
16364
  }
@@ -15948,10 +16477,10 @@ function upgradeCommand(plugin, marketplace) {
15948
16477
  var PLUGIN_NAME = "dashwise";
15949
16478
  var MARKETPLACE_NAME = "dashwise";
15950
16479
  var DEFAULT_MARKETPLACE_SOURCE = "Intuize/dashwise-cli";
15951
- var defaultRunner = (cmd, args, opts) => new Promise((resolve14) => {
16480
+ var defaultRunner = (cmd, args, opts) => new Promise((resolve15) => {
15952
16481
  const target = spawnTarget(cmd, args);
15953
16482
  if ("refused" in target) {
15954
- resolve14({ code: null, stdout: "", stderr: target.refused, missing: false });
16483
+ resolve15({ code: null, stdout: "", stderr: target.refused, missing: false });
15955
16484
  return;
15956
16485
  }
15957
16486
  const child = spawn(target.command, target.args, {
@@ -15966,7 +16495,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve14) => {
15966
16495
  child.on("error", (err) => {
15967
16496
  if (settled) return;
15968
16497
  settled = true;
15969
- resolve14({
16498
+ resolve15({
15970
16499
  code: null,
15971
16500
  stdout,
15972
16501
  stderr: stderr || err.message,
@@ -15976,7 +16505,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolve14) => {
15976
16505
  child.on("close", (exitCode) => {
15977
16506
  if (settled) return;
15978
16507
  settled = true;
15979
- resolve14({ code: exitCode, stdout, stderr, missing: false });
16508
+ resolve15({ code: exitCode, stdout, stderr, missing: false });
15980
16509
  });
15981
16510
  });
15982
16511
  function readSkillsOnDisk() {
@@ -17670,7 +18199,14 @@ async function moduleInstallCommand(slugArg, options) {
17670
18199
  try {
17671
18200
  const installs = await listWorkspaceInstallations(workspace2.id, auth);
17672
18201
  const byKind = selectInstallsByKind(installs, mod.id);
17673
- existing = byKind.production;
18202
+ const production = byKind.all.filter((row2) => !isDevInstallKind(row2.kind));
18203
+ if (production.length > 1) {
18204
+ fail(
18205
+ "Several production installations exist for this module in this company. Select the installation to update in the app. Nothing was changed."
18206
+ );
18207
+ return 1;
18208
+ }
18209
+ existing = production[0];
17674
18210
  devInstall = byKind.dev;
17675
18211
  } catch (err) {
17676
18212
  fail(
@@ -17753,7 +18289,8 @@ async function moduleInstallCommand(slugArg, options) {
17753
18289
  mod.id,
17754
18290
  {
17755
18291
  workspaceId: workspace2.id,
17756
- ...options.version !== void 0 ? { version: options.version } : {},
18292
+ version: targetVersion,
18293
+ ...existing ? { installation_id: existing.id } : {},
17757
18294
  ...options.force !== void 0 ? { force: options.force } : {}
17758
18295
  },
17759
18296
  auth
@@ -17761,6 +18298,12 @@ async function moduleInstallCommand(slugArg, options) {
17761
18298
  } catch (err) {
17762
18299
  return surfaceInstallError(err, mod.slug, workspace2.slug, devInstallReplaced);
17763
18300
  }
18301
+ if (existing && (isInstallGated(result) || result.installation.id !== existing.id)) {
18302
+ fail(
18303
+ `The backend did not confirm an in-place update of installation #${existing.id}. Check its state in the app before retrying; no fallback install will be attempted.`
18304
+ );
18305
+ return 1;
18306
+ }
17764
18307
  if (isInstallGated(result)) {
17765
18308
  if (options.json) {
17766
18309
  out(JSON.stringify(result));
@@ -17780,7 +18323,9 @@ async function moduleInstallCommand(slugArg, options) {
17780
18323
  let verified = false;
17781
18324
  try {
17782
18325
  const after = await listWorkspaceInstallations(workspace2.id, auth);
17783
- row = selectInstallsByKind(after, mod.id).production;
18326
+ row = after.find(
18327
+ (candidate) => candidate.id === result.installation.id && candidate.module_id === mod.id && !isDevInstallKind(candidate.kind)
18328
+ );
17784
18329
  verified = row !== void 0;
17785
18330
  } catch {
17786
18331
  }
@@ -18390,6 +18935,16 @@ function packedFileLines(files, cap = PACKED_FILE_LIST_CAP) {
18390
18935
  if (files.length <= cap) return [...files];
18391
18936
  return [...files.slice(0, cap), `\u2026 and ${files.length - cap} more`];
18392
18937
  }
18938
+ function repositoryLinkedDecision(input) {
18939
+ if (!input.repositoryUrl || input.fromLocal || input.dryRun) return "proceed";
18940
+ return input.interactive ? "confirm" : "refuse";
18941
+ }
18942
+ function canPrompt(stdin, stdout) {
18943
+ return stdin.isTTY === true && stdout.isTTY === true;
18944
+ }
18945
+ function repositoryLinkedWarning(repositoryUrl) {
18946
+ return `These files are not in ${repositoryUrl}; the next repository build replaces them.`;
18947
+ }
18393
18948
  async function modulePublishCommand(options) {
18394
18949
  const projectRoot = resolve(options.dir ?? process.cwd());
18395
18950
  const manifestPath2 = join(projectRoot, "module.json");
@@ -18486,6 +19041,35 @@ async function modulePublishCommand(options) {
18486
19041
  );
18487
19042
  }
18488
19043
  for (const w of tarball.warnings) warn(w);
19044
+ if (mod.repository_url) {
19045
+ const decision = repositoryLinkedDecision({
19046
+ repositoryUrl: mod.repository_url,
19047
+ fromLocal: options.fromLocal === true,
19048
+ dryRun: options.dryRun === true,
19049
+ interactive: canPrompt(process.stdin, process.stdout)
19050
+ });
19051
+ warn(repositoryLinkedWarning(mod.repository_url));
19052
+ if (decision === "refuse") {
19053
+ fail(
19054
+ `${code(slug)} is linked to a repository. Re-run with ${code("--from-local")} to publish these local files anyway.`
19055
+ );
19056
+ return 1;
19057
+ }
19058
+ if (decision === "confirm") {
19059
+ const answer = await select({
19060
+ message: "Publish these local files anyway?",
19061
+ options: [
19062
+ { value: "go", label: "Yes, publish from local files" },
19063
+ { value: "cancel", label: "No, abort" }
19064
+ ],
19065
+ initialValue: "cancel"
19066
+ });
19067
+ if (isCancel(answer) || answer === "cancel") {
19068
+ fail("Aborted.");
19069
+ return 130;
19070
+ }
19071
+ }
19072
+ }
18489
19073
  if (!options.yes && process.stdout.isTTY && !options.dryRun) {
18490
19074
  const answer = await select({
18491
19075
  message: `Upload + publish ${code(`bump:${bump}`)}?`,
@@ -19022,7 +19606,7 @@ async function moduleReleaseCommand(slugArg, options) {
19022
19606
  }
19023
19607
  async function wait() {
19024
19608
  const remaining = Math.max(0, deadline - Date.now());
19025
- await new Promise((resolve14) => setTimeout(resolve14, Math.min(options.pollIntervalMs ?? 4e3, remaining)));
19609
+ await new Promise((resolve15) => setTimeout(resolve15, Math.min(options.pollIntervalMs ?? 4e3, remaining)));
19026
19610
  }
19027
19611
  }
19028
19612
 
@@ -22430,7 +23014,7 @@ function formatTimestamp(iso) {
22430
23014
  return iso.slice(0, 19).replace("T", " ");
22431
23015
  }
22432
23016
  function sleep2(ms) {
22433
- return new Promise((resolve14) => setTimeout(resolve14, ms));
23017
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
22434
23018
  }
22435
23019
 
22436
23020
  // src/commands/module/action-log.ts
@@ -22532,7 +23116,7 @@ async function runTail2(installationId, options) {
22532
23116
  }
22533
23117
  dim(`(tail: transient error \u2014 ${err.message})`);
22534
23118
  }
22535
- await new Promise((resolve14) => setTimeout(resolve14, POLL_INTERVAL_MS2));
23119
+ await new Promise((resolve15) => setTimeout(resolve15, POLL_INTERVAL_MS2));
22536
23120
  }
22537
23121
  }
22538
23122
  function printTable(rows) {
@@ -22917,7 +23501,7 @@ async function sleep3(ms, aborted) {
22917
23501
  const slice = 100;
22918
23502
  let waited = 0;
22919
23503
  while (waited < ms && !aborted()) {
22920
- await new Promise((resolve14) => setTimeout(resolve14, Math.min(slice, ms - waited)));
23504
+ await new Promise((resolve15) => setTimeout(resolve15, Math.min(slice, ms - waited)));
22921
23505
  waited += slice;
22922
23506
  }
22923
23507
  }
@@ -23467,6 +24051,17 @@ program.command("login").description("Sign in via the device-code flow.").option
23467
24051
  });
23468
24052
  process.exitCode = exit;
23469
24053
  });
24054
+ program.command("doctor").description(
24055
+ "Check your setup: which Dashwise you target (dev or prod), login, connection, Node, CLI and SDK versions, and inside a module folder module.json, the repository link and the cloud setup files. Each item says what to do. Exits 1 when something fails."
24056
+ ).option("--dir <path>", "Module folder to check (default: current directory).").option("--json", "Print one JSON object { ok, checks } instead of the report.").action(async (cmdOpts) => {
24057
+ process.exitCode = await doctorCommand(
24058
+ {
24059
+ ...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
24060
+ ...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
24061
+ },
24062
+ defaultDoctorDeps(getVersion())
24063
+ );
24064
+ });
23470
24065
  program.command("logout").description("Clear the local credentials and revoke the server-side token.").action(async () => {
23471
24066
  process.exitCode = await logoutCommand();
23472
24067
  });
@@ -24057,6 +24652,9 @@ moduleCmd.command("release [slug]").description("Release an exact pushed reposit
24057
24652
  });
24058
24653
  });
24059
24654
  moduleCmd.command("publish").description("Pack the current directory + upload it as a new module version.").option("--dir <path>", "Project root (default: current directory).").option("--bump <direction>", "Semver bump: patch | minor | major.").option("--dry-run", "Validate + upload but do not persist the version row.").option("-y, --yes", "Skip the confirmation prompt (for CI / scripted use).").option(
24655
+ "--from-local",
24656
+ "Publish local files to a module that has a linked repository (the next repository build replaces them)."
24657
+ ).option(
24060
24658
  "--exclude <pattern...>",
24061
24659
  "Extra exclude patterns (segment-matched) appended to the defaults."
24062
24660
  ).option(
@@ -24076,6 +24674,7 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
24076
24674
  ...cmdOpts.bump !== void 0 ? { bump: cmdOpts.bump } : {},
24077
24675
  ...cmdOpts.dryRun !== void 0 ? { dryRun: cmdOpts.dryRun } : {},
24078
24676
  ...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
24677
+ ...cmdOpts.fromLocal !== void 0 ? { fromLocal: cmdOpts.fromLocal } : {},
24079
24678
  ...cmdOpts.exclude !== void 0 ? { exclude: cmdOpts.exclude } : {},
24080
24679
  // commander maps `--no-respect-gitignore` to
24081
24680
  // `respectGitignore === false`.
@@ -24332,12 +24931,12 @@ tableCmd.command("create <slug>").description(
24332
24931
  "Create a new table. Pass --field flags to define columns. Calls POST /api/installations/:id/schema/tables and syncs the updated manifest back to local module.json."
24333
24932
  ).option("--dir <path>", "Project root (default: current directory).").option(
24334
24933
  "-f, --field <spec>",
24335
- "Field spec \u2014 `<slug>:<type>[:required]`. Repeat for multiple fields. For options/target/default, use --field-json instead. Example: `--field title:text:required --field done:boolean`.",
24934
+ "Field spec: `<slug>:<type>[:primary,required]`. Exactly one eligible field must be primary. Repeat for multiple fields. For options/target/default, use --field-json instead. Example: `--field title:text:primary,required --field done:boolean`.",
24336
24935
  (val, prev = []) => [...prev, val],
24337
24936
  []
24338
24937
  ).option(
24339
24938
  "--field-json <json>",
24340
- "Field as a JSON object \u2014 `{slug,type,required?,options?,target?,default?}`. Repeatable. Use for fields where the simple syntax can't express the shape.",
24939
+ "Field as a JSON object \u2014 `{slug,type,primary?:true,required?,options?,target?,default?}`. Repeatable. Use for fields where the simple syntax can't express the shape.",
24341
24940
  (val, prev = []) => [...prev, val],
24342
24941
  []
24343
24942
  ).action(
@@ -24401,7 +25000,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
24401
25000
  }
24402
25001
  );
24403
25002
  function getVersion() {
24404
- return "0.23.0";
25003
+ return "0.24.0";
24405
25004
  }
24406
25005
  function parseIntOption(value) {
24407
25006
  const n = Number.parseInt(value, 10);