@dashai/cli 0.29.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -837,6 +837,30 @@ The CLI persists state at `$XDG_CONFIG_HOME/dashwise/auth.json` (typically `~/.c
837
837
 
838
838
  MIT
839
839
 
840
+ ### `dashwise module ship [slug]`
841
+
842
+ Release and install in one command, for a person. It releases the latest push to the linked repository's **default branch** (or `--commit-sha <sha>`), waits for the build, then installs exactly that version the way `module install --version` does: for an update it first prints what the move does to the copy's data, asks once, and installs with the preview's `drift_hash`.
843
+
844
+ ```bash
845
+ dashwise module ship # default-branch head of the module in module.json
846
+ dashwise module ship my-module --workspace acme --bump patch
847
+ dashwise module ship my-module --commit-sha <full-40-character-sha> --installation 156
848
+ ```
849
+
850
+ Before anything is queued it prints the revision it will release (`Releasing <sha> from main`, from the server's release plan) and names newer work on another branch that is not included. If the release is still building at `--timeout` (exit 4), it prints `dashwise module ship <slug> --build <id>`, which waits for that build and then installs it, without queueing another release. `--yes` skips the install question when nothing is lost; it never accepts data loss. A move that loses data is accepted only by answering in a terminal; without one, it is refused and the refusal prints the `module install --accept-data-loss <drift_hash>` re-run line. **Refused inside an AI agent session**: an agent may release (`module release`), but taking a module live is a person's act. Exit codes are `module release`'s while building (4: still building) and `module install`'s after.
851
+
852
+ ### `dashwise module build-log [slug] <buildId>`
853
+
854
+ Print a build's log as the server serves it: scrubbed (credentials, tokens, internal hosts and account ids are hidden; the count of hidden values is shown) and bounded to the newest part of the log. One positional is the build id, with the module from `module.json`.
855
+
856
+ ```bash
857
+ dashwise module build-log 51
858
+ dashwise module build-log my-module 51 --follow # new lines until the build ends
859
+ dashwise module build-log my-module 51 --json
860
+ ```
861
+
862
+ `--follow` re-reads the log while the build is queued or building and prints only what is new (every 5 s while it grows, backing off to 10 s while it does not); it exits 0 when the build succeeds, 1 when it fails, 4 at `--timeout` (default 900 s). Text written to the terminal is sanitized: escape sequences (cursor moves, window titles, OSC 8 links, OSC 52 clipboard writes) and control characters other than newline and tab are removed, keeping only colour on a terminal; `--json` prints the server's text unchanged. Needs the backend's author build logs switched on; until then it says so.
863
+
840
864
  ### `dashwise module release [slug]`
841
865
 
842
866
  Release code already pushed to the module's linked GitHub repository. This uses
package/dist/bin.js CHANGED
@@ -364,6 +364,14 @@ async function setOrigins(installationId, allowedOrigins, options = {}) {
364
364
  options
365
365
  );
366
366
  }
367
+ async function getModuleBuildLog(moduleId, buildId, options = {}) {
368
+ return apiRequest(
369
+ "GET",
370
+ `/api/modules/${moduleId}/builds/${buildId}/log`,
371
+ void 0,
372
+ options
373
+ );
374
+ }
367
375
  async function listInstallationSecrets(installationId, options = {}) {
368
376
  return apiRequest(
369
377
  "GET",
@@ -19217,8 +19225,16 @@ var MARKERS = [
19217
19225
  ["CODEX_SANDBOX_NETWORK_DISABLED", (v) => v.length > 0],
19218
19226
  ["CURSOR_AGENT", (v) => v.length > 0],
19219
19227
  ["DW_AGENT", (v) => v.length > 0],
19220
- ["DASHWISE_AGENT_SESSION", (v) => v === "1" || v === "true"]
19228
+ ["DASHWISE_AGENT_SESSION", (v) => v === "1" || v === "true"],
19229
+ ["CLAUDE_CODE_ENTRYPOINT", (v) => v.length > 0],
19230
+ ["GEMINI_CLI", (v) => v === "1"],
19231
+ ["COPILOT_AGENT_SESSION_ID", (v) => v.length > 0],
19232
+ ["COPILOT_AGENT_JOB_ID", (v) => v.length > 0],
19233
+ ["AI_AGENT", (v) => v.length > 0]
19221
19234
  ];
19235
+ MARKERS.map(
19236
+ ([name]) => name
19237
+ );
19222
19238
  function agentSessionMarker(env = process.env) {
19223
19239
  for (const [name, test] of MARKERS) {
19224
19240
  const value = env[name];
@@ -20787,7 +20803,10 @@ async function moduleReleaseCommand(slugArg, options) {
20787
20803
  if (resuming && (options.commitSha !== void 0 || options.bump !== void 0 || options.noWait)) {
20788
20804
  return usage2("--build only reads an existing release; do not combine it with --commit-sha, --bump or --no-wait.");
20789
20805
  }
20790
- if (!resuming && (!options.commitSha || !FULL_SHA.test(options.commitSha))) {
20806
+ if (!resuming && options.commitSha !== void 0 && !FULL_SHA.test(options.commitSha)) {
20807
+ return usage2("--commit-sha requires the full 40-character SHA already pushed to the linked repository.");
20808
+ }
20809
+ if (!resuming && options.commitSha === void 0 && !options.defaultBranchHead) {
20791
20810
  return usage2("--commit-sha requires the full 40-character SHA already pushed to the linked repository.");
20792
20811
  }
20793
20812
  const bump = options.bump ?? "minor";
@@ -20810,7 +20829,7 @@ async function moduleReleaseCommand(slugArg, options) {
20810
20829
  return usage2("Release creates a version. Pass --yes to confirm in non-interactive or --json mode.");
20811
20830
  }
20812
20831
  const answer = await select({
20813
- message: `Release ${slug} at ${options.commitSha} with a ${bump} bump?`,
20832
+ message: `Release ${slug} at ${options.commitSha ?? "the head of its default branch"} with a ${bump} bump?`,
20814
20833
  options: [{ value: "cancel", label: "Cancel" }, { value: "release", label: "Release this revision" }],
20815
20834
  initialValue: "cancel"
20816
20835
  });
@@ -20825,7 +20844,7 @@ async function moduleReleaseCommand(slugArg, options) {
20825
20844
  if (!resuming) {
20826
20845
  try {
20827
20846
  initial = await apiRequest("POST", `/api/modules/${moduleId}/builds`, {
20828
- commit_sha: pinnedSha,
20847
+ ...pinnedSha !== void 0 ? { commit_sha: pinnedSha } : {},
20829
20848
  version_bump: bump
20830
20849
  }, auth);
20831
20850
  } catch (err) {
@@ -20839,12 +20858,13 @@ async function moduleReleaseCommand(slugArg, options) {
20839
20858
  if (!json && isTransientPollError(err)) info("Check the module build history in Dashwise before retrying; the release may already be queued.");
20840
20859
  return reportRefusal(err, { module: slug }, { json, action: "Release" });
20841
20860
  }
20842
- if (!isRepositoryBuild(initial, moduleId) || initial.commit_sha.toLowerCase() !== pinnedSha) {
20861
+ if (!isRepositoryBuild(initial, moduleId) || pinnedSha !== void 0 && initial.commit_sha.toLowerCase() !== pinnedSha) {
20843
20862
  return reportCliError("Release response did not confirm the requested repository revision. Check the module build history before retrying.", { json });
20844
20863
  }
20845
20864
  buildId = initial.id;
20846
20865
  }
20847
20866
  const id = buildId;
20867
+ options.onBuildId?.(id);
20848
20868
  const resume = `dashwise module release ${slug} --build ${id}`;
20849
20869
  if (!json) info(`Build #${id}. Resume checking: ${code(resume)}`);
20850
20870
  if (options.noWait && isRepositoryBuild(initial, moduleId) && (initial.status === "queued" || initial.status === "building")) {
@@ -20883,6 +20903,13 @@ async function moduleReleaseCommand(slugArg, options) {
20883
20903
  }
20884
20904
  if (json) out(JSON.stringify(value));
20885
20905
  else success(`Released ${slug} v${value.version} (build #${id}, revision ${pinnedSha}).`);
20906
+ options.onReleased?.({
20907
+ id,
20908
+ module_id: moduleId,
20909
+ commit_sha: pinnedSha,
20910
+ version: value.version,
20911
+ module_version_id: value.module_version_id
20912
+ });
20886
20913
  return 0;
20887
20914
  }
20888
20915
  if (value.status === "failed") {
@@ -20924,6 +20951,248 @@ async function moduleReleaseCommand(slugArg, options) {
20924
20951
  }
20925
20952
  }
20926
20953
 
20954
+ // src/commands/module/build-log.ts
20955
+ init_api_client();
20956
+ init_config();
20957
+ init_manifest_io();
20958
+ init_output();
20959
+ init_permission_hint();
20960
+ var RUNNING = /* @__PURE__ */ new Set(["queued", "building"]);
20961
+ var FOLLOW_BASE_MS = 5e3;
20962
+ var FOLLOW_MAX_MS = 1e4;
20963
+ var MIN_OVERLAP = 64;
20964
+ function newLogText(printed, next) {
20965
+ if (printed === "") return { text: next, moved: false };
20966
+ if (next.startsWith(printed)) return { text: next.slice(printed.length), moved: false };
20967
+ const min = Math.min(MIN_OVERLAP, printed.length);
20968
+ const head = next.slice(0, min);
20969
+ for (let j = printed.indexOf(head); j !== -1; j = printed.indexOf(head, j + 1)) {
20970
+ const tail = printed.slice(j);
20971
+ if (tail.length >= min && next.startsWith(tail)) {
20972
+ return { text: next.slice(tail.length), moved: false };
20973
+ }
20974
+ }
20975
+ return { text: next, moved: true };
20976
+ }
20977
+ function sanitizeForTerminal(text4, keepColour = false) {
20978
+ const ESCAPE = /\u001b(?:\[[0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\u0007|\u001b\\|$)|[PX^_][\s\S]*?(?:\u001b\\|$)|[ -/]*[0-~]|$)/g;
20979
+ const SGR = /^\u001b\[[0-9;]*m$/;
20980
+ return text4.replace(/\r\n/g, "\n").replace(ESCAPE, (seq) => keepColour && SGR.test(seq) ? seq : "").replace(
20981
+ /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g,
20982
+ (ch, at, all) => (
20983
+ // Keep the ESC that opens a kept SGR sequence; drop every other control.
20984
+ ch === "\x1B" && keepColour && SGR.test((/^\u001b\[[0-9;]*m/.exec(all.slice(at)) ?? [""])[0]) ? ch : ""
20985
+ )
20986
+ );
20987
+ }
20988
+ async function moduleBuildLogCommand(slugArg, buildIdArg, options) {
20989
+ const json = options.json;
20990
+ const usage2 = (message) => reportCliError(message, { json, code: "USAGE" });
20991
+ let slug = slugArg;
20992
+ let rawId = buildIdArg;
20993
+ if (rawId === void 0 && slug !== void 0 && /^\d+$/.test(slug)) {
20994
+ rawId = slug;
20995
+ slug = void 0;
20996
+ }
20997
+ const buildId = rawId === void 0 ? Number.NaN : Number(rawId);
20998
+ if (!Number.isSafeInteger(buildId) || buildId <= 0) {
20999
+ return usage2("Pass the build id: `dashwise module build-log [slug] <buildId>` (ids are in `dashwise module status`).");
21000
+ }
21001
+ if (json && options.follow) return usage2("--follow streams text; drop --json.");
21002
+ if (!slug) {
21003
+ try {
21004
+ const local = readManifest(options.dir)?.module?.slug;
21005
+ if (typeof local === "string" && local) slug = local;
21006
+ } catch {
21007
+ }
21008
+ }
21009
+ if (!slug) return usage2("Pass a module slug or run from a directory with a valid module.json.");
21010
+ const timeoutMs = (options.timeoutSec ?? 900) * 1e3;
21011
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return usage2("--timeout expects a positive number of seconds.");
21012
+ const cfg = readConfig();
21013
+ if (!isLoggedIn() || !cfg) return reportCliError("Not logged in. Run `dashwise login` first.", { json });
21014
+ const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
21015
+ let moduleId;
21016
+ try {
21017
+ const mod = await getModuleBySlug(slug, auth);
21018
+ moduleId = mod.id;
21019
+ slug = mod.slug;
21020
+ } catch (err) {
21021
+ return reportRefusal(err, { module: slug }, { json, action: "Resolving the module" });
21022
+ }
21023
+ const read = async () => {
21024
+ try {
21025
+ return await getModuleBuildLog(moduleId, buildId, auth);
21026
+ } catch (err) {
21027
+ if (err instanceof ApiError && err.code === "FEATURE_DISABLED") {
21028
+ return reportCliError(
21029
+ "Build logs are not available on this Dashwise yet (the backend serves them once its build-log scrubber is switched on).",
21030
+ { json }
21031
+ );
21032
+ }
21033
+ return reportRefusal(err, { module: slug }, { json, action: `Reading the log of build #${buildId}` });
21034
+ }
21035
+ };
21036
+ const first = await read();
21037
+ if (typeof first === "number") return first;
21038
+ if (json) {
21039
+ out(JSON.stringify(first));
21040
+ return 0;
21041
+ }
21042
+ const printLog = (view, printed2) => {
21043
+ if (!view.available || view.text === null) return printed2;
21044
+ const delta = newLogText(printed2, view.text);
21045
+ if (delta.moved) info(dim("\u2026 (earlier lines scrolled out of the window the server keeps)"));
21046
+ if (delta.text) {
21047
+ const safe = sanitizeForTerminal(delta.text, Boolean(process.stdout.isTTY));
21048
+ process.stdout.write(safe.endsWith("\n") ? safe : `${safe}
21049
+ `);
21050
+ }
21051
+ return view.text;
21052
+ };
21053
+ if (!first.available && !(options.follow && RUNNING.has(first.status))) {
21054
+ info(`Build #${buildId} (${first.status}) has no log: it did not run on the build service, or the log expired.`);
21055
+ return 0;
21056
+ }
21057
+ if (first.truncated) info(dim("(showing the newest part of the log)"));
21058
+ let printed = printLog(first, "");
21059
+ const footer = (view) => {
21060
+ if (view.redactions > 0) info(dim(`${view.redactions} value(s) were hidden by the server's scrubber.`));
21061
+ };
21062
+ if (!options.follow || !RUNNING.has(first.status)) {
21063
+ footer(first);
21064
+ return 0;
21065
+ }
21066
+ const deadline = Date.now() + timeoutMs;
21067
+ let last = first;
21068
+ const base = options.pollIntervalMs ?? FOLLOW_BASE_MS;
21069
+ const cap = options.pollIntervalMs !== void 0 ? options.pollIntervalMs * 2 : FOLLOW_MAX_MS;
21070
+ let interval = base;
21071
+ while (Date.now() < deadline) {
21072
+ await new Promise(
21073
+ (resolve16) => setTimeout(resolve16, Math.min(interval, Math.max(0, deadline - Date.now())))
21074
+ );
21075
+ let view;
21076
+ try {
21077
+ view = await getModuleBuildLog(moduleId, buildId, auth);
21078
+ } catch (err) {
21079
+ if (isTransientPollError(err)) continue;
21080
+ return reportRefusal(err, { module: slug }, { json, action: `Reading the log of build #${buildId}` });
21081
+ }
21082
+ const grew = view.text !== null && view.text !== last.text;
21083
+ interval = grew ? base : Math.min(cap, Math.round(interval * 1.5));
21084
+ printed = printLog(view, printed);
21085
+ last = view;
21086
+ if (!RUNNING.has(view.status)) {
21087
+ footer(view);
21088
+ if (view.status === "failed") {
21089
+ fail(`Build #${buildId} failed.`);
21090
+ return 1;
21091
+ }
21092
+ info(`Build #${buildId}: ${view.status}.`);
21093
+ return 0;
21094
+ }
21095
+ }
21096
+ warn(`Stopped following build #${buildId} (still ${last.status}). Resume: ${code(`dashwise module build-log ${slug} ${buildId} --follow`)}`);
21097
+ return EXIT_TIMED_OUT;
21098
+ }
21099
+
21100
+ // src/commands/module/ship.ts
21101
+ init_api_client();
21102
+ init_config();
21103
+ init_manifest_io();
21104
+ init_output();
21105
+ init_permission_hint();
21106
+ async function moduleShipCommand(slugArg, options) {
21107
+ const marker = agentSessionMarker();
21108
+ if (marker !== null) {
21109
+ fail(
21110
+ `\`dashwise module ship\` takes a module live, which a person does (detected an AI agent session: ${marker}). An agent may release (${code("dashwise module release --commit-sha <sha> --yes")}); a person then installs it.`
21111
+ );
21112
+ return 2;
21113
+ }
21114
+ if (options.build !== void 0 && options.commitSha !== void 0) {
21115
+ fail("--build resumes an existing release; do not combine it with --commit-sha.");
21116
+ return 1;
21117
+ }
21118
+ const commitSha = options.build !== void 0 ? void 0 : options.commitSha ?? await defaultBranchHead(slugArg, options.dir);
21119
+ if (options.commitSha !== void 0) {
21120
+ info(`Releasing ${options.commitSha.slice(0, 12)} (--commit-sha).`);
21121
+ }
21122
+ let released = null;
21123
+ let buildId = options.build;
21124
+ const releaseExit = await moduleReleaseCommand(slugArg, {
21125
+ ...options.dir !== void 0 ? { dir: options.dir } : {},
21126
+ ...commitSha !== void 0 ? { commitSha } : {},
21127
+ ...options.build !== void 0 ? { build: options.build } : {},
21128
+ ...options.bump !== void 0 && options.build === void 0 ? { bump: options.bump } : {},
21129
+ ...options.timeoutSec !== void 0 ? { timeoutSec: options.timeoutSec } : {},
21130
+ ...options.pollIntervalMs !== void 0 ? { pollIntervalMs: options.pollIntervalMs } : {},
21131
+ // `ship` is the person's go for the release; the install below asks.
21132
+ yes: true,
21133
+ defaultBranchHead: commitSha === void 0 && options.build === void 0,
21134
+ onBuildId: (id) => {
21135
+ buildId = id;
21136
+ },
21137
+ onReleased: (build2) => {
21138
+ released = build2;
21139
+ }
21140
+ });
21141
+ if (releaseExit === EXIT_TIMED_OUT && buildId !== void 0) {
21142
+ info(
21143
+ `Resume the ship (wait for build #${buildId}, then install it): ${code(["dashwise module ship", slugArg, `--build ${buildId}`].filter(Boolean).join(" "))}`
21144
+ );
21145
+ }
21146
+ if (releaseExit !== 0) return releaseExit;
21147
+ const build = released;
21148
+ if (!build) {
21149
+ fail("The release finished without naming its version; nothing was installed.");
21150
+ return 1;
21151
+ }
21152
+ info(`Installing v${build.version} (build #${build.id}, revision ${build.commit_sha.slice(0, 12)}).`);
21153
+ return moduleInstallCommand(slugArg, {
21154
+ ...options.dir !== void 0 ? { dir: options.dir } : {},
21155
+ ...options.workspace !== void 0 ? { workspace: options.workspace } : {},
21156
+ ...options.installation !== void 0 ? { installation: options.installation } : {},
21157
+ ...options.keepDevInstall !== void 0 ? { keepDevInstall: options.keepDevInstall } : {},
21158
+ ...options.replaceDevInstall !== void 0 ? { replaceDevInstall: options.replaceDevInstall } : {},
21159
+ ...options.yes !== void 0 ? { yes: options.yes } : {},
21160
+ version: build.version
21161
+ });
21162
+ }
21163
+ async function defaultBranchHead(slugArg, dir) {
21164
+ const cfg = readConfig();
21165
+ let slug = slugArg;
21166
+ if (!slug) {
21167
+ try {
21168
+ slug = readManifest(dir)?.module?.slug;
21169
+ } catch {
21170
+ slug = void 0;
21171
+ }
21172
+ }
21173
+ if (!slug || !cfg || !isLoggedIn()) return void 0;
21174
+ const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
21175
+ try {
21176
+ const mod = await getModuleBySlug(slug, auth);
21177
+ const plan = await apiRequest("GET", `/api/modules/${mod.id}/builds/release-plan`, void 0, auth);
21178
+ const sha = plan.default_branch_sha ?? void 0;
21179
+ if (!sha || !/^[0-9a-f]{40}$/i.test(sha)) {
21180
+ info("Releasing the head of the default branch.");
21181
+ return void 0;
21182
+ }
21183
+ info(`Releasing ${sha.slice(0, 12)} from ${plan.default_branch ?? "the default branch"}.`);
21184
+ if (plan.pending_branch) {
21185
+ warn(
21186
+ `Newer work on ${plan.pending_branch.name}${plan.pending_branch.commits_ahead ? ` (${plan.pending_branch.commits_ahead} commit(s) ahead)` : ""} is not in this release: merge it to ${plan.default_branch ?? "the default branch"} first to ship it.`
21187
+ );
21188
+ }
21189
+ return sha.toLowerCase();
21190
+ } catch {
21191
+ info("Releasing the head of the default branch.");
21192
+ return void 0;
21193
+ }
21194
+ }
21195
+
20927
21196
  // src/commands/module/status.ts
20928
21197
  init_api_client();
20929
21198
  init_config();
@@ -26874,6 +27143,36 @@ moduleCmd.command("release [slug]").description("Release an exact pushed reposit
26874
27143
  timeoutSec: opts.timeout
26875
27144
  });
26876
27145
  });
27146
+ moduleCmd.command("build-log [slug] [buildId]").description(
27147
+ "Print a build's log, scrubbed by the server (credentials and internal hosts hidden) and bounded to its newest part. One positional is the build id (module from module.json). --follow prints new lines until the build ends."
27148
+ ).option("--dir <path>", "Project root for module.json slug lookup (default: current directory).").option("--follow", "Keep printing new lines while the build is queued or building.").option("--timeout <seconds>", "Max seconds to follow (default 900); exit 4 means still building.", Number).option("--json", "Emit the log response as JSON (not with --follow).").action(
27149
+ async (slug, buildId, opts) => {
27150
+ process.exitCode = await moduleBuildLogCommand(slug, buildId, {
27151
+ ...opts.dir !== void 0 ? { dir: opts.dir } : {},
27152
+ ...opts.follow !== void 0 ? { follow: opts.follow } : {},
27153
+ ...opts.timeout !== void 0 ? { timeoutSec: opts.timeout } : {},
27154
+ ...opts.json !== void 0 ? { json: opts.json } : {}
27155
+ });
27156
+ }
27157
+ );
27158
+ moduleCmd.command("ship [slug]").description(
27159
+ "Release and install in one command, for a person: releases the latest push to the default branch (or --commit-sha), waits for the build, then installs that version, showing what the update does to the data and asking once. Refused inside an AI agent session (an agent releases; a person takes it live)."
27160
+ ).option("--dir <path>", "Project root for module.json slug lookup (default: current directory).").option("--commit-sha <sha>", "Release this pushed revision (full 40-character SHA) instead of the default-branch head.").option("--build <id>", "Resume a ship whose release was still building: wait for that build, then install it. Never queues a release.", parsePositiveIdOption).option("--bump <direction>", "Semver bump: patch | minor | major (default minor).").option("--workspace <slug-or-id>", "Target workspace (as for module install).").option("--installation <id>", "Update exactly this production installation.", parsePositiveIdOption).option("--keep-dev-install", "Install alongside the workspace's dev install (as for module install).").option("--replace-dev-install", "Replace the workspace's dev install (as for module install).").option("-y, --yes", "Skip the install question when nothing is lost. Never accepts data loss.").option("--timeout <seconds>", "Max seconds to wait for the build (default 360); exit 4 means still building.", Number).action(
27161
+ async (slug, opts) => {
27162
+ process.exitCode = await moduleShipCommand(slug, {
27163
+ ...opts.dir !== void 0 ? { dir: opts.dir } : {},
27164
+ ...opts.commitSha !== void 0 ? { commitSha: opts.commitSha } : {},
27165
+ ...opts.build !== void 0 ? { build: opts.build } : {},
27166
+ ...opts.bump !== void 0 ? { bump: opts.bump } : {},
27167
+ ...opts.workspace !== void 0 ? { workspace: opts.workspace } : {},
27168
+ ...opts.installation !== void 0 ? { installation: opts.installation } : {},
27169
+ ...opts.keepDevInstall !== void 0 ? { keepDevInstall: opts.keepDevInstall } : {},
27170
+ ...opts.replaceDevInstall !== void 0 ? { replaceDevInstall: opts.replaceDevInstall } : {},
27171
+ ...opts.yes !== void 0 ? { yes: opts.yes } : {},
27172
+ ...opts.timeout !== void 0 ? { timeoutSec: opts.timeout } : {}
27173
+ });
27174
+ }
27175
+ );
26877
27176
  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(
26878
27177
  "--from-local",
26879
27178
  "Publish local files to a module that has a linked repository (the next repository build replaces them)."
@@ -27297,7 +27596,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
27297
27596
  }
27298
27597
  );
27299
27598
  function getVersion() {
27300
- return "0.29.0";
27599
+ return "0.30.1";
27301
27600
  }
27302
27601
  function parsePositiveIdOption(value) {
27303
27602
  if (!/^[1-9][0-9]*$/.test(value.trim())) {