@ctxo/cli 0.8.2 → 0.9.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
@@ -11,8 +11,9 @@
11
11
  </picture>
12
12
 
13
13
  ```Shell
14
- npx @ctxo/cli init
15
- npx @ctxo/cli index
14
+ npm install -g @ctxo/cli # one-time global install (gives you the `ctxo` command)
15
+ ctxo init
16
+ ctxo index
16
17
  ```
17
18
 
18
19
  <a href="https://alperhankendi.github.io/Ctxo/docs/">
@@ -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";
@@ -2208,8 +2210,8 @@ var InitCommand = class {
2208
2210
  const hasWork = selectedTools.length > 0 || installGitHooks;
2209
2211
  if (hasWork) {
2210
2212
  const steps = [
2211
- `${pc.cyan("\u25B6")} npx ctxo index ${pc.dim("\u2500 build codebase index")}`,
2212
- `${pc.cyan("\u25B6")} npx ctxo doctor ${pc.dim("\u2500 verify everything works")}`
2213
+ `${pc.cyan("\u25B6")} ctxo index ${pc.dim("\u2500 build codebase index")}`,
2214
+ `${pc.cyan("\u25B6")} ctxo doctor ${pc.dim("\u2500 verify everything works")}`
2213
2215
  ];
2214
2216
  console.error("");
2215
2217
  console.error(box(steps, { title: pc.cyan("\u2192 Next steps"), border: pc.cyan }, pc));
@@ -3606,7 +3608,7 @@ var DoctorCommand = class {
3606
3608
  this.ctxoRoot = join22(projectRoot, ".ctxo");
3607
3609
  }
3608
3610
  async run(options = {}) {
3609
- const { getVersion: getVersion2 } = await import("./cli-router-3UHVNKWD.js");
3611
+ const { getVersion: getVersion2 } = await import("./cli-router-T4ZG2G2Z.js");
3610
3612
  console.error(`ctxo v${getVersion2()} \u2014 health check
3611
3613
  `);
3612
3614
  const checks = [
@@ -3867,20 +3869,363 @@ var VisualizeCommand = class {
3867
3869
  }
3868
3870
  };
3869
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
+
3870
4215
  // src/cli/cli-router.ts
3871
4216
  function getVersion() {
3872
4217
  let dir = import.meta.dirname;
3873
4218
  for (let i = 0; i < 10; i++) {
3874
- const pkg = join24(dir, "package.json");
3875
- if (existsSync22(pkg)) {
4219
+ const pkg = join25(dir, "package.json");
4220
+ if (existsSync23(pkg)) {
3876
4221
  try {
3877
- const json = JSON.parse(readFileSync14(pkg, "utf-8"));
4222
+ const json = JSON.parse(readFileSync15(pkg, "utf-8"));
3878
4223
  return json.version ?? "unknown";
3879
4224
  } catch {
3880
4225
  break;
3881
4226
  }
3882
4227
  }
3883
- const parent = join24(dir, "..");
4228
+ const parent = join25(dir, "..");
3884
4229
  if (parent === dir) break;
3885
4230
  dir = parent;
3886
4231
  }
@@ -3889,7 +4234,7 @@ function getVersion() {
3889
4234
  function locateGitRoot(startDir) {
3890
4235
  let dir = startDir;
3891
4236
  for (let i = 0; i < 12; i++) {
3892
- if (existsSync22(join24(dir, ".git"))) return dir;
4237
+ if (existsSync23(join25(dir, ".git"))) return dir;
3893
4238
  const parent = dirname8(dir);
3894
4239
  if (parent === dir) break;
3895
4240
  dir = parent;
@@ -4042,6 +4387,43 @@ var CliRouter = class {
4042
4387
  });
4043
4388
  break;
4044
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
+ }
4045
4427
  case "visualize": {
4046
4428
  const outputIdx = args.indexOf("--output");
4047
4429
  const outputArg = outputIdx !== -1 ? args[outputIdx + 1] : void 0;
@@ -4089,6 +4471,13 @@ Usage:
4089
4471
  ctxo install python -y Batch install, no prompt
4090
4472
  ctxo install ts --global Install globally
4091
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
4092
4481
  ctxo --version Print core version
4093
4482
  ctxo --version --verbose Print core + plugins + runtime
4094
4483
  ctxo --version --json Machine-readable version payload
@@ -4102,4 +4491,4 @@ export {
4102
4491
  getVersion,
4103
4492
  locateGitRoot
4104
4493
  };
4105
- //# sourceMappingURL=cli-router-3UHVNKWD.js.map
4494
+ //# sourceMappingURL=cli-router-T4ZG2G2Z.js.map