@ctxo/cli 0.8.1 → 0.9.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/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  </picture>
12
12
 
13
13
  ```Shell
14
- npx @ctxo/init
14
+ npx @ctxo/cli init
15
15
  npx @ctxo/cli index
16
16
  ```
17
17
 
@@ -31,8 +31,8 @@ import {
31
31
  } from "./chunk-PSKDDLSF.js";
32
32
 
33
33
  // src/cli/cli-router.ts
34
- import { readFileSync as readFileSync14, existsSync as existsSync22 } from "fs";
35
- import { dirname as dirname8, join as join24 } from "path";
34
+ import { readFileSync as readFileSync15, existsSync as existsSync23 } from "fs";
35
+ import { dirname as dirname8, join as join25 } from "path";
36
36
 
37
37
  // src/cli/index-command.ts
38
38
  import { execFileSync as execFileSync2 } from "child_process";
@@ -264,7 +264,6 @@ async function loadPlugins(projectRoot, options = {}) {
264
264
 
265
265
  // src/cli/install-command.ts
266
266
  import { existsSync as existsSync5 } from "fs";
267
- import { spawn } from "child_process";
268
267
  import { createRequire as createRequire2 } from "module";
269
268
  import { join as join4 } from "path";
270
269
 
@@ -523,6 +522,16 @@ function buildInstallCommand(manager, packages, options = {}) {
523
522
  }
524
523
  }
525
524
 
525
+ // src/core/install/run-package-manager.ts
526
+ import { spawn } from "child_process";
527
+ function runPackageManager(command, args, cwd) {
528
+ return new Promise((resolve4, reject) => {
529
+ const child = spawn(command, [...args], { cwd, stdio: "inherit", shell: process.platform === "win32" });
530
+ child.on("exit", (code) => resolve4(code ?? 0));
531
+ child.on("error", (err) => reject(err));
532
+ });
533
+ }
534
+
526
535
  // src/cli/install-command.ts
527
536
  var InstallCommand = class {
528
537
  constructor(projectRoot) {
@@ -579,7 +588,7 @@ var InstallCommand = class {
579
588
  console.error("[ctxo] --dry-run set; not executing.");
580
589
  return;
581
590
  }
582
- const code = await spawnInstall(plan.command, plan.args, this.projectRoot);
591
+ const code = await runPackageManager(plan.command, plan.args, this.projectRoot);
583
592
  if (code !== 0) {
584
593
  console.error(`[ctxo] ${plan.command} exited with code ${code}`);
585
594
  process.exitCode = code;
@@ -648,13 +657,6 @@ function isLockedCI(env) {
648
657
  if (env["CI"] !== "true" && env["CI"] !== "1") return false;
649
658
  return true;
650
659
  }
651
- function spawnInstall(command, args, cwd) {
652
- return new Promise((resolve4, reject) => {
653
- const child = spawn(command, [...args], { cwd, stdio: "inherit", shell: process.platform === "win32" });
654
- child.on("exit", (code) => resolve4(code ?? 0));
655
- child.on("error", (err) => reject(err));
656
- });
657
- }
658
660
 
659
661
  // src/adapters/storage/json-index-writer.ts
660
662
  import { mkdirSync, unlinkSync, existsSync as existsSync6 } from "fs";
@@ -1734,7 +1736,9 @@ var PLATFORMS = [
1734
1736
  { id: "windsurf", name: "Windsurf", file: ".windsurfrules", mode: "create", detectPaths: [".windsurfrules", ".windsurf"], starred: false },
1735
1737
  { id: "antigravity", name: "Google Antigravity", file: "AGENTS.md", mode: "create", detectPaths: ["AGENTS.md", "GEMINI.md", ".gemini"], starred: false },
1736
1738
  { id: "augment", name: "Augment Code", file: "augment-guidelines.md", mode: "create", detectPaths: ["augment-guidelines.md", ".augment"], starred: false },
1737
- { id: "amazonq", name: "Amazon Q", file: ".amazonq/rules/ctxo.md", mode: "create", detectPaths: [".amazonq"], starred: false }
1739
+ { id: "amazonq", name: "Amazon Q", file: ".amazonq/rules/ctxo.md", mode: "create", detectPaths: [".amazonq"], starred: false },
1740
+ { id: "gemini-cli", name: "Gemini CLI", file: "GEMINI.md", mode: "append", detectPaths: [".gemini/settings.json", "GEMINI.md"], starred: false },
1741
+ { id: "continue", name: "Continue", file: ".continue/rules/ctxo.md", mode: "create", detectPaths: [".continue"], starred: false }
1738
1742
  ];
1739
1743
  function detectPlatforms(projectRoot) {
1740
1744
  const detected = /* @__PURE__ */ new Set();
@@ -1898,6 +1902,12 @@ function getMcpConfigTargets(selectedPlatformIds) {
1898
1902
  case "amazonq":
1899
1903
  targets.set(".amazonq/mcp.json", { file: ".amazonq/mcp.json", serverKey: "mcpServers" });
1900
1904
  break;
1905
+ case "gemini-cli":
1906
+ targets.set(".gemini/settings.json", { file: ".gemini/settings.json", serverKey: "mcpServers" });
1907
+ break;
1908
+ case "continue":
1909
+ targets.set(".continue/mcpServers/ctxo.json", { file: ".continue/mcpServers/ctxo.json", serverKey: "mcpServers" });
1910
+ break;
1901
1911
  }
1902
1912
  }
1903
1913
  return [...targets.values()];
@@ -3598,7 +3608,7 @@ var DoctorCommand = class {
3598
3608
  this.ctxoRoot = join22(projectRoot, ".ctxo");
3599
3609
  }
3600
3610
  async run(options = {}) {
3601
- const { getVersion: getVersion2 } = await import("./cli-router-HEZER4G7.js");
3611
+ const { getVersion: getVersion2 } = await import("./cli-router-L34BRMXQ.js");
3602
3612
  console.error(`ctxo v${getVersion2()} \u2014 health check
3603
3613
  `);
3604
3614
  const checks = [
@@ -3859,20 +3869,363 @@ var VisualizeCommand = class {
3859
3869
  }
3860
3870
  };
3861
3871
 
3872
+ // src/cli/update-command.ts
3873
+ import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
3874
+ import { join as join24 } from "path";
3875
+
3876
+ // src/core/update/registry-client.ts
3877
+ import { request } from "https";
3878
+ var log5 = createLogger("ctxo:update:registry");
3879
+ var DEFAULT_TIMEOUT_MS = 5e3;
3880
+ async function fetchDistTags(packageName, options = {}) {
3881
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3882
+ const fetcher = options.fetcher ?? defaultHttpsFetcher;
3883
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
3884
+ let response;
3885
+ try {
3886
+ response = await fetcher(url, timeoutMs);
3887
+ } catch (err) {
3888
+ return classifyFetchError(packageName, err);
3889
+ }
3890
+ if (response.status === 404) {
3891
+ return { name: packageName, reason: "registry-404", status: 404 };
3892
+ }
3893
+ if (response.status < 200 || response.status >= 300) {
3894
+ return { name: packageName, reason: "registry-error", status: response.status };
3895
+ }
3896
+ let parsed;
3897
+ try {
3898
+ parsed = JSON.parse(response.body);
3899
+ } catch {
3900
+ return { name: packageName, reason: "registry-error", status: response.status, message: "invalid JSON" };
3901
+ }
3902
+ const distTags = parsed["dist-tags"];
3903
+ if (!distTags || typeof distTags !== "object") {
3904
+ return { name: packageName, reason: "registry-error", status: response.status, message: "missing dist-tags" };
3905
+ }
3906
+ return { name: packageName, distTags };
3907
+ }
3908
+ async function fetchDistTagsBatch(packageNames, options = {}) {
3909
+ return Promise.all(packageNames.map((name) => fetchDistTags(name, options)));
3910
+ }
3911
+ function classifyFetchError(name, err) {
3912
+ const code = err?.code;
3913
+ const message = err?.message ?? String(err);
3914
+ if (code === "ETIMEDOUT" || /timeout/i.test(message)) {
3915
+ return { name, reason: "timeout", message };
3916
+ }
3917
+ if (code === "ECONNREFUSED" || code === "ECONNRESET" || code === "ENOTFOUND" || code === "EAI_AGAIN") {
3918
+ return { name, reason: "network", message };
3919
+ }
3920
+ log5.error(`unexpected fetch error for ${name}: ${message}`);
3921
+ return { name, reason: "network", message };
3922
+ }
3923
+ var defaultHttpsFetcher = (url, timeoutMs) => new Promise((resolve4, reject) => {
3924
+ const req = request(url, { method: "GET", headers: { "user-agent": "ctxo-update", accept: "application/json" } }, (res) => {
3925
+ const chunks = [];
3926
+ res.on("data", (chunk) => chunks.push(chunk));
3927
+ res.on("end", () => resolve4({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf-8") }));
3928
+ res.on("error", reject);
3929
+ });
3930
+ req.setTimeout(timeoutMs, () => {
3931
+ req.destroy(Object.assign(new Error(`timeout after ${timeoutMs}ms`), { code: "ETIMEDOUT" }));
3932
+ });
3933
+ req.on("error", reject);
3934
+ req.end();
3935
+ });
3936
+
3937
+ // src/core/update/update-plan.ts
3938
+ var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
3939
+ function parseSemver(version) {
3940
+ const match = SEMVER_RE.exec(version.trim());
3941
+ if (!match) return null;
3942
+ const prereleaseRaw = match[4] ?? "";
3943
+ return {
3944
+ major: Number(match[1]),
3945
+ minor: Number(match[2]),
3946
+ patch: Number(match[3]),
3947
+ prerelease: prereleaseRaw ? prereleaseRaw.split(".") : []
3948
+ };
3949
+ }
3950
+ function detectChannel(version) {
3951
+ const parsed = parseSemver(version);
3952
+ if (!parsed || parsed.prerelease.length === 0) return "latest";
3953
+ const first = parsed.prerelease[0];
3954
+ if (/^\d+$/.test(first)) return "latest";
3955
+ return first;
3956
+ }
3957
+ function compareSemver(a, b) {
3958
+ const pa = parseSemver(a);
3959
+ const pb = parseSemver(b);
3960
+ if (!pa && !pb) return 0;
3961
+ if (!pa) return -1;
3962
+ if (!pb) return 1;
3963
+ if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1;
3964
+ if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1;
3965
+ if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1;
3966
+ if (pa.prerelease.length === 0 && pb.prerelease.length === 0) return 0;
3967
+ if (pa.prerelease.length === 0) return 1;
3968
+ if (pb.prerelease.length === 0) return -1;
3969
+ const len = Math.max(pa.prerelease.length, pb.prerelease.length);
3970
+ for (let i = 0; i < len; i++) {
3971
+ const ai = pa.prerelease[i];
3972
+ const bi = pb.prerelease[i];
3973
+ if (ai === void 0) return -1;
3974
+ if (bi === void 0) return 1;
3975
+ const aNum = /^\d+$/.test(ai);
3976
+ const bNum = /^\d+$/.test(bi);
3977
+ if (aNum && bNum) {
3978
+ const an = Number(ai);
3979
+ const bn = Number(bi);
3980
+ if (an !== bn) return an < bn ? -1 : 1;
3981
+ } else if (aNum && !bNum) {
3982
+ return -1;
3983
+ } else if (!aNum && bNum) {
3984
+ return 1;
3985
+ } else if (ai !== bi) {
3986
+ return ai < bi ? -1 : 1;
3987
+ }
3988
+ }
3989
+ return 0;
3990
+ }
3991
+ function computePackageStates(installed, results) {
3992
+ const byName = /* @__PURE__ */ new Map();
3993
+ for (const r of results) byName.set(r.name, r);
3994
+ return installed.map((pkg) => {
3995
+ const installedChannel = detectChannel(pkg.version);
3996
+ const result = byName.get(pkg.name);
3997
+ if (!result) {
3998
+ return { name: pkg.name, current: pkg.version, latest: null, channel: installedChannel, status: "unknown" };
3999
+ }
4000
+ if (!("distTags" in result)) {
4001
+ return { name: pkg.name, current: pkg.version, latest: null, channel: installedChannel, status: "unknown", reason: result.reason };
4002
+ }
4003
+ const hasInstalledChannel = typeof result.distTags[installedChannel] === "string";
4004
+ const targetChannel = hasInstalledChannel ? installedChannel : "latest";
4005
+ const target = result.distTags[targetChannel] ?? null;
4006
+ if (!target) {
4007
+ return { name: pkg.name, current: pkg.version, latest: null, channel: installedChannel, status: "unknown" };
4008
+ }
4009
+ const cmp = compareSemver(pkg.version, target);
4010
+ const status = cmp === 0 ? "current" : cmp < 0 ? "update" : "ahead";
4011
+ return { name: pkg.name, current: pkg.version, latest: target, channel: targetChannel, status };
4012
+ });
4013
+ }
4014
+ function selectInstallTargets(states) {
4015
+ return states.filter((s) => s.status === "update" && typeof s.latest === "string").filter((s) => isSafeVersionSpecifier(s.latest)).map((s) => ({ name: s.name, version: s.latest }));
4016
+ }
4017
+ var SAFE_SPECIFIER_RE = /^[0-9A-Za-z.+-]+$/;
4018
+ function isSafeVersionSpecifier(version) {
4019
+ return SAFE_SPECIFIER_RE.test(version);
4020
+ }
4021
+
4022
+ // src/cli/update-command.ts
4023
+ var log6 = createLogger("ctxo:update");
4024
+ var CTXO_CLI_PACKAGE = "@ctxo/cli";
4025
+ function formatText(report) {
4026
+ const lines = [];
4027
+ lines.push("ctxo update \u2014 checking registry for updates...");
4028
+ lines.push("");
4029
+ const updatesExist = report.packages.some((p) => p.status === "update");
4030
+ if (!updatesExist && report.strategy !== "print") {
4031
+ const total = report.packages.length;
4032
+ lines.push(`All ${total} package${total === 1 ? "" : "s"} are up to date.`);
4033
+ return lines.join("\n");
4034
+ }
4035
+ const nameCol = Math.max(7, ...report.packages.map((p) => p.name.length));
4036
+ const curCol = Math.max(7, ...report.packages.map((p) => p.current.length));
4037
+ const latestCol = Math.max(15, ...report.packages.map((p) => (p.latest ?? "(not found)").length));
4038
+ lines.push(
4039
+ `${"PACKAGE".padEnd(nameCol)} ${"CURRENT".padEnd(curCol)} ${`LATEST (${report.channel})`.padEnd(latestCol)} STATUS`
4040
+ );
4041
+ for (const pkg of report.packages) {
4042
+ const latestText = pkg.latest ?? "(not found)";
4043
+ const statusText = renderStatus(pkg, report.channel);
4044
+ lines.push(
4045
+ `${pkg.name.padEnd(nameCol)} ${pkg.current.padEnd(curCol)} ${latestText.padEnd(latestCol)} ${statusText}`
4046
+ );
4047
+ }
4048
+ if (report.plan && report.strategy === "execute") {
4049
+ lines.push("");
4050
+ lines.push(`Plan: ${report.plan.command} ${report.plan.args.join(" ")}`);
4051
+ lines.push(`Using ${report.plan.manager} (${formatManagerSource(report.plan)})`);
4052
+ lines.push("");
4053
+ lines.push("Running...");
4054
+ } else if (report.plan && report.strategy === "print") {
4055
+ lines.push("");
4056
+ lines.push("To update, run:");
4057
+ lines.push(` ${report.plan.command} ${report.plan.args.join(" ")}`);
4058
+ }
4059
+ return lines.join("\n");
4060
+ }
4061
+ function renderStatus(pkg, reportChannel) {
4062
+ switch (pkg.status) {
4063
+ case "current":
4064
+ return "up to date";
4065
+ case "ahead":
4066
+ return "ahead of registry";
4067
+ case "unknown":
4068
+ return pkg.reason === "registry-404" ? "skipped" : `error${pkg.reason ? ` (${pkg.reason})` : ""}`;
4069
+ case "update":
4070
+ return pkg.channel === reportChannel ? "update" : `update (${pkg.channel})`;
4071
+ }
4072
+ }
4073
+ function formatManagerSource(plan) {
4074
+ return plan.managerDetail ? `${plan.managerSource}: ${plan.managerDetail}` : plan.managerSource;
4075
+ }
4076
+ function formatJson2(report) {
4077
+ return JSON.stringify(
4078
+ {
4079
+ ctxo: report.ctxo,
4080
+ channel: report.channel,
4081
+ packages: report.packages,
4082
+ plan: report.plan,
4083
+ executed: report.executed,
4084
+ ...report.exitCode !== void 0 ? { exitCode: report.exitCode } : {}
4085
+ },
4086
+ null,
4087
+ 2
4088
+ );
4089
+ }
4090
+ var UpdateCommand = class {
4091
+ constructor(projectRoot, deps = {}) {
4092
+ this.projectRoot = projectRoot;
4093
+ this.deps = deps;
4094
+ }
4095
+ projectRoot;
4096
+ deps;
4097
+ async run(options = {}) {
4098
+ if (options.pm && !isPackageManager(options.pm)) {
4099
+ this.emitError(`Unknown --pm value "${options.pm}".`);
4100
+ this.deps.setExitCode?.(1);
4101
+ return;
4102
+ }
4103
+ const discover = this.deps.discoverInstalled ?? defaultDiscoverInstalled;
4104
+ const installed = await discover(this.projectRoot);
4105
+ if (installed.length === 0) {
4106
+ this.emit(options.json ? "{}\n" : "ctxo update \u2014 no @ctxo/* packages discovered.\n");
4107
+ return;
4108
+ }
4109
+ const names = installed.map((p) => p.name);
4110
+ const results = await fetchDistTagsBatch(names, { fetcher: this.deps.fetcher });
4111
+ const allMissed = results.every((r) => !("distTags" in r));
4112
+ if (allMissed) {
4113
+ this.emitError("registry unreachable for every package; aborting.");
4114
+ this.deps.setExitCode?.(1);
4115
+ return;
4116
+ }
4117
+ const states = computePackageStates(installed, results);
4118
+ const targets = selectInstallTargets(states);
4119
+ const cliVersion = getVersion();
4120
+ const channel = detectChannel(cliVersion);
4121
+ const plan = targets.length === 0 ? null : this.buildPlan(targets, options);
4122
+ const strategy = this.pickStrategy(targets, options);
4123
+ const baseReport = {
4124
+ ctxo: cliVersion,
4125
+ channel,
4126
+ packages: states,
4127
+ plan,
4128
+ strategy,
4129
+ executed: false
4130
+ };
4131
+ if (options.check) {
4132
+ const checkStrategy = plan ? "print" : strategy;
4133
+ this.emitReport({ ...baseReport, strategy: checkStrategy }, options.json ?? false);
4134
+ return;
4135
+ }
4136
+ if (strategy === "none" || strategy === "print" || !plan) {
4137
+ this.emitReport(baseReport, options.json ?? false);
4138
+ return;
4139
+ }
4140
+ if (!options.force && !options.global && isLockedCI2(this.deps.env ?? process.env)) {
4141
+ this.emitReport({ ...baseReport, strategy: "print" }, options.json ?? false);
4142
+ this.emitError("CI environment with frozen lockfile detected. Refusing to mutate. Use --force or --global.");
4143
+ this.deps.setExitCode?.(1);
4144
+ return;
4145
+ }
4146
+ if (!options.json) this.emitReport(baseReport, false);
4147
+ const run2 = this.deps.runner ?? runPackageManager;
4148
+ const code = await run2(plan.command, plan.args, this.projectRoot);
4149
+ const finalReport = { ...baseReport, executed: true, exitCode: code };
4150
+ if (options.json) this.emit(formatJson2(finalReport) + "\n");
4151
+ if (code !== 0) this.deps.setExitCode?.(code);
4152
+ }
4153
+ buildPlan(targets, options) {
4154
+ const resolution = resolvePackageManager({
4155
+ flag: options.pm,
4156
+ projectRoot: this.projectRoot,
4157
+ env: this.deps.env
4158
+ });
4159
+ const specifiers = targets.map((t) => `${t.name}@${t.version}`);
4160
+ const useGlobal = options.global || !this.projectHasCtxoDep();
4161
+ const invocation = buildInstallCommand(resolution.manager, specifiers, { global: useGlobal });
4162
+ return {
4163
+ manager: resolution.manager,
4164
+ managerSource: resolution.source,
4165
+ managerDetail: resolution.detail,
4166
+ global: useGlobal,
4167
+ command: invocation.command,
4168
+ args: invocation.args
4169
+ };
4170
+ }
4171
+ pickStrategy(targets, options) {
4172
+ if (targets.length === 0) return "none";
4173
+ if (options.print) return "print";
4174
+ if (options.global) return "execute";
4175
+ return this.projectHasCtxoDep() ? "execute" : "print";
4176
+ }
4177
+ projectHasCtxoDep() {
4178
+ const pkg = join24(this.projectRoot, "package.json");
4179
+ if (!existsSync22(pkg)) return false;
4180
+ try {
4181
+ const json = JSON.parse(readFileSync14(pkg, "utf-8"));
4182
+ const merged = { ...json.dependencies ?? {}, ...json.devDependencies ?? {} };
4183
+ return Object.keys(merged).some(
4184
+ (name) => name === CTXO_CLI_PACKAGE || name.startsWith("@ctxo/lang-") || name.startsWith("ctxo-lang-")
4185
+ );
4186
+ } catch (err) {
4187
+ log6.error(`failed to read project package.json: ${err.message}`);
4188
+ return false;
4189
+ }
4190
+ }
4191
+ emit(text) {
4192
+ process.stdout.write(text);
4193
+ }
4194
+ emitError(text) {
4195
+ process.stderr.write(`[ctxo] ${text}
4196
+ `);
4197
+ }
4198
+ emitReport(report, json) {
4199
+ if (json) this.emit(formatJson2(report) + "\n");
4200
+ else this.emit(formatText(report) + "\n");
4201
+ }
4202
+ };
4203
+ async function defaultDiscoverInstalled(projectRoot) {
4204
+ const out = [{ name: CTXO_CLI_PACKAGE, version: getVersion() }];
4205
+ const manifestPath = loadManifestPath(projectRoot);
4206
+ if (!manifestPath) return out;
4207
+ const { plugins } = await discoverPlugins({ manifestPath });
4208
+ for (const p of plugins) out.push({ name: p.specifier, version: p.plugin.version });
4209
+ return out;
4210
+ }
4211
+ function isLockedCI2(env) {
4212
+ return env["CI"] === "true" || env["CI"] === "1";
4213
+ }
4214
+
3862
4215
  // src/cli/cli-router.ts
3863
4216
  function getVersion() {
3864
4217
  let dir = import.meta.dirname;
3865
4218
  for (let i = 0; i < 10; i++) {
3866
- const pkg = join24(dir, "package.json");
3867
- if (existsSync22(pkg)) {
4219
+ const pkg = join25(dir, "package.json");
4220
+ if (existsSync23(pkg)) {
3868
4221
  try {
3869
- const json = JSON.parse(readFileSync14(pkg, "utf-8"));
4222
+ const json = JSON.parse(readFileSync15(pkg, "utf-8"));
3870
4223
  return json.version ?? "unknown";
3871
4224
  } catch {
3872
4225
  break;
3873
4226
  }
3874
4227
  }
3875
- const parent = join24(dir, "..");
4228
+ const parent = join25(dir, "..");
3876
4229
  if (parent === dir) break;
3877
4230
  dir = parent;
3878
4231
  }
@@ -3881,7 +4234,7 @@ function getVersion() {
3881
4234
  function locateGitRoot(startDir) {
3882
4235
  let dir = startDir;
3883
4236
  for (let i = 0; i < 12; i++) {
3884
- if (existsSync22(join24(dir, ".git"))) return dir;
4237
+ if (existsSync23(join25(dir, ".git"))) return dir;
3885
4238
  const parent = dirname8(dir);
3886
4239
  if (parent === dir) break;
3887
4240
  dir = parent;
@@ -4034,6 +4387,43 @@ var CliRouter = class {
4034
4387
  });
4035
4388
  break;
4036
4389
  }
4390
+ case "update": {
4391
+ const flagValues = {};
4392
+ for (let i = 1; i < args.length; i++) {
4393
+ const a = args[i];
4394
+ if (a === "--check") flagValues["check"] = true;
4395
+ else if (a === "--print") flagValues["print"] = true;
4396
+ else if (a === "--global" || a === "-g") flagValues["global"] = true;
4397
+ else if (a === "--json") flagValues["json"] = true;
4398
+ else if (a === "--force") flagValues["force"] = true;
4399
+ else if (a === "--pm") {
4400
+ const next = args[++i];
4401
+ if (!next) {
4402
+ console.error("[ctxo] --pm requires a value");
4403
+ process.exit(1);
4404
+ return;
4405
+ }
4406
+ flagValues["pm"] = next;
4407
+ } else {
4408
+ console.error(`[ctxo] Unknown update flag: ${a}`);
4409
+ process.exit(1);
4410
+ return;
4411
+ }
4412
+ }
4413
+ await new UpdateCommand(this.projectRoot, {
4414
+ setExitCode: (code) => {
4415
+ process.exitCode = code;
4416
+ }
4417
+ }).run({
4418
+ check: flagValues["check"] === true,
4419
+ print: flagValues["print"] === true,
4420
+ global: flagValues["global"] === true,
4421
+ json: flagValues["json"] === true,
4422
+ force: flagValues["force"] === true,
4423
+ pm: typeof flagValues["pm"] === "string" ? flagValues["pm"] : void 0
4424
+ });
4425
+ break;
4426
+ }
4037
4427
  case "visualize": {
4038
4428
  const outputIdx = args.indexOf("--output");
4039
4429
  const outputArg = outputIdx !== -1 ? args[outputIdx + 1] : void 0;
@@ -4081,6 +4471,13 @@ Usage:
4081
4471
  ctxo install python -y Batch install, no prompt
4082
4472
  ctxo install ts --global Install globally
4083
4473
  ctxo install py --dry-run Show command without running
4474
+ ctxo update Check + apply updates for ctxo + plugins
4475
+ ctxo update --check Check only, no install
4476
+ ctxo update --print Print install command, never execute
4477
+ ctxo update --global Force a global install
4478
+ ctxo update --json Machine-readable output
4479
+ ctxo update --pm <pm> Override package manager (npm|pnpm|yarn|bun)
4480
+ ctxo update --force Bypass CI lockfile guard
4084
4481
  ctxo --version Print core version
4085
4482
  ctxo --version --verbose Print core + plugins + runtime
4086
4483
  ctxo --version --json Machine-readable version payload
@@ -4094,4 +4491,4 @@ export {
4094
4491
  getVersion,
4095
4492
  locateGitRoot
4096
4493
  };
4097
- //# sourceMappingURL=cli-router-HEZER4G7.js.map
4494
+ //# sourceMappingURL=cli-router-L34BRMXQ.js.map