@codevector/cli 0.7.0 → 0.8.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
@@ -4,11 +4,13 @@ Wire your AI coding tools through a corporate gateway in one command. One API ke
4
4
 
5
5
  ```bash
6
6
  npm i -g @codevector/cli # or: pnpm add -g @codevector/cli
7
- codevector auth login
8
- codevector configure
7
+ codevector auth login # once, to store your gateway + API key
8
+ codevector init # per repo: pin the gateway, name the project, wire your tools
9
9
  ```
10
10
 
11
- That's the whole onboarding. Pick a tool, pick a scope, done.
11
+ That's the whole onboarding. `init` is the per-repo entry point - it pins a gateway,
12
+ writes a `.codevector.json`, and offers to run `configure` for you. Add the shell hook
13
+ (below) and your credentials auto-activate as you `cd` between repos.
12
14
 
13
15
  ---
14
16
 
@@ -21,19 +23,20 @@ That's the whole onboarding. Pick a tool, pick a scope, done.
21
23
  ## What it does
22
24
 
23
25
  - **Signs you in** with a per-seat API key issued by your gateway admin.
26
+ - **Initializes a repo** with `codevector init`: pins a gateway, names the project for attribution, and offers to wire your tools in one pass.
24
27
  - **Writes the right config file** for each supported coding tool so its model traffic routes through the gateway instead of calling upstream providers directly.
25
28
  - **Picks the right scope** (global, project-wide, or per-machine) based on what you want and what the tool supports.
26
- - **Installs an acceptance-tracking hook** that fires at session end for tools that expose lifecycle events. Feeds the gateway's usage reporting.
29
+ - **Auto-activates credentials on cd** via an optional shell hook, so each repo gets the gateway env vars for its pinned gateway without a per-tool config file.
27
30
  - **Stays out of your way.** Idempotent writes. Deep-merges into existing settings files — won't clobber your theme, your other hooks, your existing env vars.
28
31
 
29
32
  ---
30
33
 
31
34
  ## Supported tools
32
35
 
33
- | Tool | Config path (user scope) | Scopes (`configure` writes project/local; `system configure` writes user) | Key storage | Hooks wired |
34
- | -------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------- | --------------------- |
35
- | **Claude Code** | `~/.claude/settings.json` | user / project / local | literal in JSON (`ANTHROPIC_AUTH_TOKEN`) | `Stop` + `SessionEnd` |
36
- | **opencode** (sst/opencode) | `~/.config/opencode/opencode.json` | user / project / local | literal (user + local); env-var reference in project scope | — |
36
+ | Tool | Config path (user scope) | Scopes (`configure` writes project/local; `system configure` writes user) | Key storage |
37
+ | -------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------- |
38
+ | **Claude Code** | `~/.claude/settings.json` | user / project / local | literal in JSON (`ANTHROPIC_AUTH_TOKEN`) |
39
+ | **opencode** (sst/opencode) | `~/.config/opencode/opencode.json` | user / project / local | literal (user + local); env-var reference in project scope |
37
40
 
38
41
  Each writer emits both Anthropic and OpenAI-compatible provider entries where the tool supports both wire formats.
39
42
 
@@ -85,9 +88,14 @@ codevector status # who am I, where am I pointed, wh
85
88
  codevector doctor # health checks: creds, network, hook install, settings
86
89
  codevector version # print CLI version
87
90
  codevector update # update @codevector/cli from npm (use --with pnpm|yarn if needed)
88
- codevector init # writes .codevector.json (projectName + ticket pattern)
89
- codevector init --project my-app # with explicit project name
90
- codevector init --ticket-pattern "JIRA-\d+" # with custom ticket extraction regex
91
+ codevector init # per-repo: pin gateway, name project, offer to wire tools
92
+ codevector init --project my-app # explicit project name
93
+ codevector init --gateway https://gw.corp.com # pin a gateway non-interactively
94
+ codevector init --ticket-pattern "JIRA-\d+" # custom ticket extraction regex
95
+ codevector init --skip-configure # write .codevector.json, don't wire tools
96
+ codevector init --force # overwrite an existing .codevector.json
97
+ codevector hook bash # print the shell-hook snippet (bash|zsh|fish)
98
+ codevector env --shell bash # print env exports for the current repo (used by the hook)
91
99
  ```
92
100
 
93
101
  Every command supports `--help`. Commands that need input will prompt interactively when flags are missing, but every flag still works for scripting.
@@ -109,10 +117,18 @@ codevector auth login --gateway-url https://gateway.corp.com --api-key "$KEY" #
109
117
 
110
118
  ```bash
111
119
  codevector auth login # paste the gateway URL + API key your admin emailed you
112
- codevector configure claude-code # default scope is local safe per-seat, gitignored
120
+ codevector init # in the repo: pins the gateway, names the project, wires your tools
113
121
  codevector doctor # confirm everything's wired
114
122
  ```
115
123
 
124
+ **Auto-activate credentials as you move between repos:**
125
+
126
+ ```bash
127
+ # add to ~/.zshrc (or ~/.bashrc; fish: codevector hook fish | source)
128
+ eval "$(codevector hook zsh)"
129
+ # then: cd into any repo with a .codevector.json and its gateway env vars load automatically
130
+ ```
131
+
116
132
  **Configuring multiple tools in one go:**
117
133
 
118
134
  ```bash
@@ -155,6 +171,32 @@ codevector models sync --scope local # refresh model list after admin adds mod
155
171
 
156
172
  ---
157
173
 
174
+ ## Shell hook
175
+
176
+ `codevector init` pins a gateway to a repo in `.codevector.json`. The shell hook reads
177
+ that pin and exports the matching profile's gateway env vars whenever you `cd` into the
178
+ repo, then clears them when you leave. Install it once:
179
+
180
+ | Shell | rc file | Line to add |
181
+ | ----- | ---------------------------- | -------------------------------- |
182
+ | bash | `~/.bashrc` | `eval "$(codevector hook bash)"` |
183
+ | zsh | `~/.zshrc` | `eval "$(codevector hook zsh)"` |
184
+ | fish | `~/.config/fish/config.fish` | `codevector hook fish \| source` |
185
+
186
+ On macOS, login shells read `~/.bash_profile` instead of `~/.bashrc`.
187
+
188
+ After restarting your shell, entering a repo with a pinned gateway exports
189
+ `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_API_KEY`, and
190
+ `ANTHROPIC_CUSTOM_HEADERS` (the `x-project` attribution header). The CLI walks up the
191
+ directory tree, so a subdirectory of the repo resolves the same pin. If no saved profile
192
+ matches the pinned gateway, the hook prints a hint to run `codevector auth login`.
193
+ `codevector doctor` reports whether the hook is installed.
194
+
195
+ `codevector env --shell <shell>` is the command the hook evaluates on each `cd` — you
196
+ don't run it directly.
197
+
198
+ ---
199
+
158
200
  ## Safe config merging
159
201
 
160
202
  `codevector configure` adds the gateway settings to your existing config files without overwriting what's already there:
@@ -189,4 +231,4 @@ Override the config root with `CODEVECTOR_CONFIG_DIR`.
189
231
  | Claude Code | `~/.claude/settings.json` / `.claude/settings.local.json` | JSON |
190
232
  | opencode | `~/.config/opencode/opencode.json` / `./opencode.json` | JSON (JSONC tolerated) |
191
233
  | codevector credentials | `~/.config/codevector/credentials.json` | JSON (chmod 0600) |
192
- | codevector hook script | `~/.config/codevector/hooks/acceptance.sh` | shell |
234
+ | codevector project pin | `<repo>/.codevector.json` | JSON (no secrets) |
package/dist/index.js CHANGED
@@ -18199,18 +18199,19 @@ var init_project_context = __esm({
18199
18199
  });
18200
18200
 
18201
18201
  // src/lib/shell.ts
18202
- function detectShell() {
18202
+ function detectShell(platform = process.platform) {
18203
18203
  const shellPath = process.env.SHELL ?? "";
18204
18204
  if (shellPath.endsWith("/fish")) return "fish";
18205
18205
  if (shellPath.endsWith("/zsh")) return "zsh";
18206
18206
  if (shellPath.endsWith("/bash")) return "bash";
18207
+ if (shellPath === "" && platform === "win32") return "powershell";
18207
18208
  return null;
18208
18209
  }
18209
18210
  var SHELLS;
18210
18211
  var init_shell = __esm({
18211
18212
  "src/lib/shell.ts"() {
18212
18213
  "use strict";
18213
- SHELLS = ["bash", "zsh", "fish"];
18214
+ SHELLS = ["bash", "zsh", "fish", "powershell"];
18214
18215
  }
18215
18216
  });
18216
18217
 
@@ -18223,7 +18224,7 @@ function shellHookRecipe(shell) {
18223
18224
  }
18224
18225
  function shellHookInstructions(shell = detectShell(), platform = process.platform) {
18225
18226
  if (!shell) {
18226
- const rows = Object.keys(RECIPES).map((s) => ` ${s.padEnd(5)} ${RECIPES[s].rc.padEnd(26)} ${RECIPES[s].line}`).join("\n");
18227
+ const rows = Object.keys(RECIPES).map((s) => ` ${s.padEnd(10)} ${RECIPES[s].rc.padEnd(26)} ${RECIPES[s].line}`).join("\n");
18227
18228
  return `Add the line for your shell to its rc file, then restart your shell (credentials auto-activate on cd):
18228
18229
 
18229
18230
  ${rows}`;
@@ -18237,6 +18238,9 @@ ${rows}`;
18237
18238
  if (shell === "bash" && platform === "darwin") {
18238
18239
  lines.push("", "On macOS, login shells read ~/.bash_profile instead of ~/.bashrc.");
18239
18240
  }
18241
+ if (shell === "powershell") {
18242
+ lines.push("", "If $PROFILE doesn't exist yet, create it: New-Item -ItemType File -Path $PROFILE -Force.");
18243
+ }
18240
18244
  return lines.join("\n");
18241
18245
  }
18242
18246
  function shellHookOneLiner(shell = detectShell()) {
@@ -18255,6 +18259,13 @@ function rcCandidates(shell, platform = process.platform) {
18255
18259
  return [join7(home, ".zshrc")];
18256
18260
  case "fish":
18257
18261
  return [join7(home, ".config", "fish", "config.fish")];
18262
+ case "powershell": {
18263
+ const docs = join7(home, "Documents");
18264
+ return [
18265
+ join7(docs, "PowerShell", "Microsoft.PowerShell_profile.ps1"),
18266
+ join7(docs, "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1")
18267
+ ];
18268
+ }
18258
18269
  }
18259
18270
  }
18260
18271
  function isHookInstalled(shell, platform = process.platform) {
@@ -18274,7 +18285,8 @@ var init_shell_hook = __esm({
18274
18285
  RECIPES = {
18275
18286
  bash: { rc: "~/.bashrc", line: `eval "$(codevector hook bash)"` },
18276
18287
  zsh: { rc: "~/.zshrc", line: `eval "$(codevector hook zsh)"` },
18277
- fish: { rc: "~/.config/fish/config.fish", line: "codevector hook fish | source" }
18288
+ fish: { rc: "~/.config/fish/config.fish", line: "codevector hook fish | source" },
18289
+ powershell: { rc: "$PROFILE", line: "codevector hook powershell | Out-String | Invoke-Expression" }
18278
18290
  };
18279
18291
  }
18280
18292
  });
@@ -18472,7 +18484,7 @@ var init_init = __esm({
18472
18484
  // src/commands/configure.ts
18473
18485
  import { homedir as homedir5 } from "os";
18474
18486
  import { existsSync as existsSync7 } from "fs";
18475
- import { join as join9 } from "path";
18487
+ import { join as join9, sep } from "path";
18476
18488
  async function resolveTools(args) {
18477
18489
  if (args.all) return [...ALL_TOOLS];
18478
18490
  if (args.tool) {
@@ -18540,11 +18552,11 @@ function pathHint(tools, scope) {
18540
18552
  function relativizeHomeAndCwd(absolutePath) {
18541
18553
  const home = homedir5();
18542
18554
  const cwd = userCwd();
18543
- if (home && absolutePath.startsWith(`${home}/`)) {
18555
+ if (home && absolutePath.startsWith(`${home}${sep}`)) {
18544
18556
  return `~${absolutePath.slice(home.length)}`;
18545
18557
  }
18546
- if (absolutePath.startsWith(`${cwd}/`)) {
18547
- return `./${absolutePath.slice(cwd.length + 1)}`;
18558
+ if (absolutePath.startsWith(`${cwd}${sep}`)) {
18559
+ return `.${absolutePath.slice(cwd.length)}`;
18548
18560
  }
18549
18561
  return absolutePath;
18550
18562
  }
@@ -19233,7 +19245,7 @@ import { join as join11 } from "path";
19233
19245
  // src/lib/install-pref.ts
19234
19246
  init_paths();
19235
19247
  import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync8, realpathSync, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "fs";
19236
- import { join as join10, sep } from "path";
19248
+ import { join as join10, sep as sep2 } from "path";
19237
19249
  import { fileURLToPath } from "url";
19238
19250
  var INSTALL_PREF_FILE = join10(CODEVECTOR_CONFIG_DIR, "install.json");
19239
19251
  var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn"];
@@ -19263,11 +19275,11 @@ function writeInstallPref(pref) {
19263
19275
  renameSync3(tmp, INSTALL_PREF_FILE);
19264
19276
  }
19265
19277
  function packageManagerFromPath(installPath) {
19266
- if (installPath.includes(`${sep}.pnpm${sep}`)) return "pnpm";
19267
- if (installPath.includes(`${sep}.config${sep}yarn${sep}global${sep}`) || installPath.includes(`${sep}.yarn${sep}`)) {
19278
+ if (installPath.includes(`${sep2}.pnpm${sep2}`)) return "pnpm";
19279
+ if (installPath.includes(`${sep2}.config${sep2}yarn${sep2}global${sep2}`) || installPath.includes(`${sep2}.yarn${sep2}`)) {
19268
19280
  return "yarn";
19269
19281
  }
19270
- if (installPath.includes(`${sep}lib${sep}node_modules${sep}`)) return "npm";
19282
+ if (installPath.includes(`${sep2}lib${sep2}node_modules${sep2}`)) return "npm";
19271
19283
  return void 0;
19272
19284
  }
19273
19285
  function detectPackageManagerFromInstallPath() {
@@ -19323,13 +19335,15 @@ var doctorCommand = defineCommand({
19323
19335
  label: "credentials",
19324
19336
  detail: `${creds.email} @ ${creds.gatewayUrl} (${maskApiKey(creds.apiKey)})`
19325
19337
  });
19326
- checks.push(
19327
- credentialsFileModeOk() ? { level: "ok", label: "credentials permissions", detail: "chmod 600" } : {
19328
- level: "warn",
19329
- label: "credentials permissions",
19330
- detail: `expected 0600 on ${CREDENTIALS_FILE}`
19331
- }
19332
- );
19338
+ if (process.platform !== "win32") {
19339
+ checks.push(
19340
+ credentialsFileModeOk() ? { level: "ok", label: "credentials permissions", detail: "chmod 600" } : {
19341
+ level: "warn",
19342
+ label: "credentials permissions",
19343
+ detail: `expected 0600 on ${CREDENTIALS_FILE}`
19344
+ }
19345
+ );
19346
+ }
19333
19347
  const client = gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
19334
19348
  try {
19335
19349
  const me2 = await call(parseResponse(client.me.$get()));
@@ -19597,6 +19611,7 @@ init_credentials();
19597
19611
  init_paths();
19598
19612
  init_project_config();
19599
19613
  init_shell();
19614
+ import { dirname as dirname5 } from "path";
19600
19615
  var envCommand = defineCommand({
19601
19616
  meta: {
19602
19617
  name: "env",
@@ -19605,8 +19620,8 @@ var envCommand = defineCommand({
19605
19620
  args: {
19606
19621
  shell: {
19607
19622
  type: "string",
19608
- description: "Output dialect: bash, zsh, or fish.",
19609
- valueHint: "bash|zsh|fish"
19623
+ description: "Output dialect: bash, zsh, fish, or powershell.",
19624
+ valueHint: "bash|zsh|fish|powershell"
19610
19625
  }
19611
19626
  },
19612
19627
  async run({ args }) {
@@ -19614,7 +19629,7 @@ var envCommand = defineCommand({
19614
19629
  const cwd = userCwd();
19615
19630
  const previousDir = process.env.CODEVECTOR_ACTIVE_DIR ?? null;
19616
19631
  const found = readProjectConfig(cwd);
19617
- const repoDir = found?.path ? dirOf(found.path) : null;
19632
+ const repoDir = found?.path ? dirname5(found.path) : null;
19618
19633
  const gateway = found?.config.gateway ?? null;
19619
19634
  const projectName = found?.config.projectName ?? null;
19620
19635
  const lines = [];
@@ -19668,22 +19683,30 @@ function resolveShell(raw) {
19668
19683
  return raw;
19669
19684
  }
19670
19685
  function emitExport(lines, shell, name, value) {
19671
- const quoted = shellQuote(value);
19672
- if (shell === "fish") {
19686
+ const quoted = quoteForShell(shell, value);
19687
+ if (shell === "powershell") {
19688
+ lines.push(`$env:${name} = ${quoted}`);
19689
+ } else if (shell === "fish") {
19673
19690
  lines.push(`set -gx ${name} ${quoted}`);
19674
19691
  } else {
19675
19692
  lines.push(`export ${name}=${quoted}`);
19676
19693
  }
19677
19694
  }
19678
19695
  function emitUnset(lines, shell, name) {
19679
- if (shell === "fish") {
19696
+ if (shell === "powershell") {
19697
+ lines.push(`Remove-Item -ErrorAction SilentlyContinue Env:\\${name}`);
19698
+ } else if (shell === "fish") {
19680
19699
  lines.push(`set -e ${name}`);
19681
19700
  } else {
19682
19701
  lines.push(`unset ${name}`);
19683
19702
  }
19684
19703
  }
19685
- function emitEcho(lines, _shell, message) {
19686
- lines.push(`echo ${shellQuote(message)} 1>&2`);
19704
+ function emitEcho(lines, shell, message) {
19705
+ if (shell === "powershell") {
19706
+ lines.push(`[Console]::Error.WriteLine(${quoteForShell(shell, message)})`);
19707
+ } else {
19708
+ lines.push(`echo ${quoteForShell(shell, message)} 1>&2`);
19709
+ }
19687
19710
  }
19688
19711
  function emitDeactivate(lines, shell, previousDir) {
19689
19712
  for (const name of [
@@ -19699,16 +19722,15 @@ function emitDeactivate(lines, shell, previousDir) {
19699
19722
  }
19700
19723
  emitEcho(lines, shell, `codevector: left ${previousDir}, credentials cleared.`);
19701
19724
  }
19702
- function shellQuote(value) {
19725
+ function quoteForShell(shell, value) {
19726
+ if (shell === "powershell") {
19727
+ return `'${value.replace(/'/g, "''")}'`;
19728
+ }
19703
19729
  return `'${value.replace(/'/g, `'\\''`)}'`;
19704
19730
  }
19705
19731
  function trimRightSlash5(url2) {
19706
19732
  return url2.endsWith("/") ? url2.slice(0, -1) : url2;
19707
19733
  }
19708
- function dirOf(filePath) {
19709
- const idx = filePath.lastIndexOf("/");
19710
- return idx === -1 ? filePath : filePath.slice(0, idx);
19711
- }
19712
19734
  function buildAnthropicCustomHeaders(projectName) {
19713
19735
  if (!projectName) return null;
19714
19736
  const safe = projectName.replace(/[\r\n]/g, "").trim();
@@ -20114,8 +20136,8 @@ var hookCommand = defineCommand({
20114
20136
  shell: {
20115
20137
  type: "positional",
20116
20138
  required: false,
20117
- description: "Target shell: bash, zsh, or fish.",
20118
- valueHint: "bash|zsh|fish"
20139
+ description: "Target shell: bash, zsh, fish, or powershell.",
20140
+ valueHint: "bash|zsh|fish|powershell"
20119
20141
  }
20120
20142
  },
20121
20143
  run({ args }) {
@@ -20146,6 +20168,8 @@ function snippetFor(shell) {
20146
20168
  return ZSH_SNIPPET;
20147
20169
  case "fish":
20148
20170
  return FISH_SNIPPET;
20171
+ case "powershell":
20172
+ return POWERSHELL_SNIPPET;
20149
20173
  }
20150
20174
  }
20151
20175
  var BASH_SNIPPET = `# codevector shell hook (bash) \u2014 auto-activates credentials on cd
@@ -20183,6 +20207,22 @@ end
20183
20207
  # Run once for the current directory at shell startup.
20184
20208
  _codevector_on_pwd
20185
20209
  `;
20210
+ var POWERSHELL_SNIPPET = `# codevector shell hook (powershell) \u2014 auto-activates credentials on cd
20211
+ function global:_codevector_on_prompt {
20212
+ if ($PWD.Path -eq $global:_CODEVECTOR_LAST_PWD) { return }
20213
+ $global:_CODEVECTOR_LAST_PWD = $PWD.Path
20214
+ $out = (& codevector env --shell powershell 2>$null | Out-String)
20215
+ if ($out.Trim().Length -gt 0) { Invoke-Expression $out }
20216
+ }
20217
+ if (-not $global:_CODEVECTOR_PROMPT_WRAPPED) {
20218
+ $global:_CODEVECTOR_PROMPT_WRAPPED = $true
20219
+ $global:_CODEVECTOR_ORIG_PROMPT = $function:prompt
20220
+ function global:prompt {
20221
+ _codevector_on_prompt
20222
+ if ($global:_CODEVECTOR_ORIG_PROMPT) { & $global:_CODEVECTOR_ORIG_PROMPT } else { "PS $($PWD.Path)> " }
20223
+ }
20224
+ }
20225
+ `;
20186
20226
 
20187
20227
  // src/index.ts
20188
20228
  init_init();
@@ -20824,7 +20864,7 @@ import {
20824
20864
  readdirSync,
20825
20865
  statSync as statSync4
20826
20866
  } from "fs";
20827
- import { basename, dirname as dirname5, join as join13 } from "path";
20867
+ import { basename, dirname as dirname6, join as join13 } from "path";
20828
20868
  import { homedir as homedir6 } from "os";
20829
20869
  var BACKUP_ROOT = process.env.CODEVECTOR_BACKUP_ROOT ?? join13(homedir6(), ".codevector", "backups");
20830
20870
  function backupTimestamp(d = /* @__PURE__ */ new Date()) {
@@ -20867,7 +20907,7 @@ function restoreBackup(backupPath, originalPath) {
20867
20907
  if (!existsSync11(backupPath)) {
20868
20908
  throw new Error(`Backup file missing: ${backupPath}`);
20869
20909
  }
20870
- mkdirSync6(dirname5(originalPath), { recursive: true });
20910
+ mkdirSync6(dirname6(originalPath), { recursive: true });
20871
20911
  copyFileSync(backupPath, originalPath);
20872
20912
  }
20873
20913
  function backupRunMtime(dir) {
@@ -21118,7 +21158,7 @@ import { spawnSync } from "child_process";
21118
21158
  // package.json
21119
21159
  var package_default = {
21120
21160
  name: "@codevector/cli",
21121
- version: "0.7.0",
21161
+ version: "0.8.0",
21122
21162
  description: "CodeVector CLI \u2014 installs and configures first-party coding-tool integrations.",
21123
21163
  license: "UNLICENSED",
21124
21164
  bin: {
@@ -21273,7 +21313,10 @@ var updateCommand = defineCommand({
21273
21313
  const manager = await resolvePackageManager(args.with);
21274
21314
  const command = installCommand(manager);
21275
21315
  R2.info(`Using ${manager}: ${command.cmd} ${command.args.join(" ")}`);
21276
- const result = spawnSync(command.cmd, command.args, { stdio: "inherit" });
21316
+ const result = spawnSync(command.cmd, command.args, {
21317
+ stdio: "inherit",
21318
+ shell: process.platform === "win32"
21319
+ });
21277
21320
  if (result.error) {
21278
21321
  throw new Error(
21279
21322
  `Failed to invoke ${command.cmd}: ${result.error.message}. Is ${command.cmd} on your PATH?`
@@ -21308,7 +21351,10 @@ function verifyOnPathVersion(previousVersion, manager) {
21308
21351
  R2.success(`Already on the latest version (${previousVersion}).`);
21309
21352
  }
21310
21353
  function readOnPathVersion() {
21311
- const probe = spawnSync("codevector", ["version"], { encoding: "utf8" });
21354
+ const probe = spawnSync("codevector", ["version"], {
21355
+ encoding: "utf8",
21356
+ shell: process.platform === "win32"
21357
+ });
21312
21358
  if (probe.error || probe.status !== 0) return void 0;
21313
21359
  const out = probe.stdout?.trim();
21314
21360
  return out && /^\d+\.\d+\.\d+/.test(out) ? out : void 0;