@codevector/cli 0.4.0 → 0.5.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/dist/index.js CHANGED
@@ -1995,15 +1995,12 @@ var ApiClientError = class extends Error {
1995
1995
  };
1996
1996
  function gatewayClient(gatewayUrl, apiKey, timeoutMs = 15e3) {
1997
1997
  const client = hc(trimRightSlash(gatewayUrl), {
1998
- init: {
1999
- headers: {
2000
- "x-api-key": apiKey
2001
- }
2002
- },
2003
1998
  fetch: (input, init) => {
2004
1999
  const controller = new AbortController();
2005
2000
  const timer = setTimeout(() => controller.abort(), timeoutMs);
2006
- return fetch(input, { ...init, signal: controller.signal }).finally(
2001
+ const headers = new Headers(init?.headers);
2002
+ headers.set("x-api-key", apiKey);
2003
+ return fetch(input, { ...init, headers, signal: controller.signal }).finally(
2007
2004
  () => clearTimeout(timer)
2008
2005
  );
2009
2006
  }
@@ -17532,124 +17529,25 @@ function assertHttpsUrl(urlString) {
17532
17529
  } catch {
17533
17530
  throw new Error(`Invalid gateway URL: "${urlString}"`);
17534
17531
  }
17535
- if (parsed.protocol !== "https:" && parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1") {
17532
+ if (process.env.CODEVECTOR_SANDBOX === "1") return;
17533
+ if (parsed.protocol !== "https:" && parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1" && parsed.hostname !== "host.docker.internal") {
17536
17534
  throw new Error(
17537
- `Gateway URL must be https:// (got ${parsed.protocol}). Only localhost is allowed over http.`
17535
+ `Gateway URL must be https:// (got ${parsed.protocol}). Only localhost / 127.0.0.1 / host.docker.internal are allowed over http.`
17538
17536
  );
17539
17537
  }
17540
17538
  }
17541
17539
 
17542
- // src/commands/configure.ts
17543
- import { homedir as homedir5 } from "os";
17544
-
17545
- // src/lib/hooks.ts
17546
- import { chmodSync as chmodSync3, copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
17547
- import { dirname as dirname4, join as join5 } from "path";
17548
- import { fileURLToPath } from "url";
17549
- function bundledHookSource() {
17550
- const here = dirname4(fileURLToPath(import.meta.url));
17551
- const candidates = [
17552
- join5(here, "hooks", "acceptance.sh"),
17553
- // production — dist/hooks/…
17554
- join5(here, "..", "hooks", "acceptance.sh")
17555
- // dev — src/hooks/…
17556
- ];
17557
- for (const candidate of candidates) {
17558
- if (existsSync4(candidate)) return candidate;
17559
- }
17560
- throw new Error(
17561
- "Could not locate the bundled acceptance hook script (acceptance.sh). Reinstall @codevector/cli to repair the installation."
17562
- );
17563
- }
17564
- function installAcceptanceHook() {
17565
- mkdirSync4(HOOKS_DIR, { recursive: true, mode: 448 });
17566
- copyFileSync(bundledHookSource(), ACCEPTANCE_HOOK_FILE);
17567
- try {
17568
- chmodSync3(ACCEPTANCE_HOOK_FILE, 493);
17569
- } catch {
17570
- }
17571
- return ACCEPTANCE_HOOK_FILE;
17572
- }
17573
-
17574
- // src/lib/project-context.ts
17575
- import { execFileSync } from "child_process";
17576
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
17577
- import { join as join6 } from "path";
17578
- var DEFAULT_TICKET_PATTERN = /[A-Z]+-\d+/;
17579
- function safeGit(args, cwd) {
17580
- try {
17581
- const out = execFileSync("git", args, {
17582
- encoding: "utf8",
17583
- stdio: ["ignore", "pipe", "ignore"],
17584
- cwd,
17585
- timeout: 2e3
17586
- });
17587
- return out.trim() || null;
17588
- } catch {
17589
- return null;
17590
- }
17591
- }
17592
- function repoSlugFromRemote(remote) {
17593
- const trimmed = remote.trim().replace(/\.git$/i, "");
17594
- const lastSegment = trimmed.split(/[:/]/).pop();
17595
- if (!lastSegment) return null;
17596
- return lastSegment.length > 0 ? lastSegment : null;
17597
- }
17598
- function resolveProjectContext(cwd = process.cwd(), ticketPattern = DEFAULT_TICKET_PATTERN) {
17599
- let project = null;
17600
- let ticket = null;
17601
- const override = readLocalConfig(cwd);
17602
- if (override?.projectName) project = override.projectName;
17603
- const effectivePattern = override?.ticketPattern ? safeCompileTicketPattern(override.ticketPattern) ?? ticketPattern : ticketPattern;
17604
- if (!project) {
17605
- const remote = safeGit(["remote", "get-url", "origin"], cwd);
17606
- if (remote) project = repoSlugFromRemote(remote);
17607
- }
17608
- const branch = safeGit(["branch", "--show-current"], cwd);
17609
- if (branch) {
17610
- const match = effectivePattern.exec(branch);
17611
- if (match) ticket = match[0];
17612
- }
17613
- return { project, ticket };
17614
- }
17615
- function readLocalConfig(cwd) {
17616
- const path = join6(cwd, ".codevector.json");
17617
- if (!existsSync5(path)) return null;
17618
- try {
17619
- const parsed = JSON.parse(readFileSync6(path, "utf8"));
17620
- if (typeof parsed !== "object" || parsed === null) return null;
17621
- const p2 = parsed;
17622
- const result = {};
17623
- if (typeof p2.projectName === "string" && p2.projectName.length > 0) {
17624
- result.projectName = p2.projectName;
17625
- }
17626
- if (typeof p2.ticketPattern === "string" && p2.ticketPattern.length > 0) {
17627
- result.ticketPattern = p2.ticketPattern;
17628
- }
17629
- return result;
17630
- } catch {
17631
- return null;
17632
- }
17633
- }
17634
- function safeCompileTicketPattern(pattern) {
17635
- try {
17636
- return new RegExp(pattern);
17637
- } catch {
17638
- return null;
17639
- }
17640
- }
17641
-
17642
17540
  // src/config-writers/codex.ts
17643
17541
  import {
17644
- chmodSync as chmodSync4,
17645
- existsSync as existsSync6,
17646
- mkdirSync as mkdirSync5,
17647
- readFileSync as readFileSync7,
17542
+ chmodSync as chmodSync3,
17543
+ existsSync as existsSync4,
17544
+ mkdirSync as mkdirSync4,
17545
+ readFileSync as readFileSync6,
17648
17546
  renameSync as renameSync3,
17649
17547
  statSync as statSync4,
17650
17548
  writeFileSync as writeFileSync5
17651
17549
  } from "fs";
17652
- import { dirname as dirname5, join as join7 } from "path";
17550
+ import { dirname as dirname4, join as join5 } from "path";
17653
17551
  import { homedir as homedir4 } from "os";
17654
17552
 
17655
17553
  // ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/error.js
@@ -17732,7 +17630,7 @@ function skipVoid(str, ptr, banNewLines, banComments) {
17732
17630
  }
17733
17631
  return ptr;
17734
17632
  }
17735
- function skipUntil(str, ptr, sep, end, banNewLines = false) {
17633
+ function skipUntil(str, ptr, sep2, end, banNewLines = false) {
17736
17634
  if (!end) {
17737
17635
  ptr = indexOfNewline(str, ptr);
17738
17636
  return ptr < 0 ? str.length : ptr;
@@ -17741,7 +17639,7 @@ function skipUntil(str, ptr, sep, end, banNewLines = false) {
17741
17639
  let c = str[i];
17742
17640
  if (c === "#") {
17743
17641
  i = indexOfNewline(str, i);
17744
- } else if (c === sep) {
17642
+ } else if (c === sep2) {
17745
17643
  return i + 1;
17746
17644
  } else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
17747
17645
  return i;
@@ -18489,6 +18387,7 @@ var PROVIDER_ID = "codevector";
18489
18387
  var writeCodexConfig = ({
18490
18388
  gatewayUrl,
18491
18389
  scope,
18390
+ project,
18492
18391
  model,
18493
18392
  availableModels = []
18494
18393
  }) => {
@@ -18502,7 +18401,8 @@ var writeCodexConfig = ({
18502
18401
  name: "CodeVector Gateway",
18503
18402
  base_url: `${gateway}/gateway/openai/v1`,
18504
18403
  env_key: ENV_VAR_NAME2,
18505
- wire_api: "responses"
18404
+ wire_api: "responses",
18405
+ http_headers: buildHttpHeaders(scope, project)
18506
18406
  };
18507
18407
  merged.model_providers = providers;
18508
18408
  if (model) {
@@ -18540,15 +18440,15 @@ var writeCodexConfig = ({
18540
18440
  function codexConfigPath(scope = "user") {
18541
18441
  switch (scope) {
18542
18442
  case "user":
18543
- return join7(homedir4(), ".codex", "config.toml");
18443
+ return join5(homedir4(), ".codex", "config.toml");
18544
18444
  case "project":
18545
18445
  case "local":
18546
- return join7(userCwd(), ".codex", "config.toml");
18446
+ return join5(userCwd(), ".codex", "config.toml");
18547
18447
  }
18548
18448
  }
18549
18449
  function readTomlOrEmpty(path) {
18550
- if (!existsSync6(path)) return {};
18551
- const raw = readFileSync7(path, "utf8");
18450
+ if (!existsSync4(path)) return {};
18451
+ const raw = readFileSync6(path, "utf8");
18552
18452
  if (raw.trim().length === 0) return {};
18553
18453
  const parsed = parse3(raw);
18554
18454
  if (!isObject4(parsed)) {
@@ -18557,10 +18457,10 @@ function readTomlOrEmpty(path) {
18557
18457
  return parsed;
18558
18458
  }
18559
18459
  function writeTomlAtomic(path, value) {
18560
- const dir = dirname5(path);
18561
- mkdirSync5(dir, { recursive: true, mode: 448 });
18460
+ const dir = dirname4(path);
18461
+ mkdirSync4(dir, { recursive: true, mode: 448 });
18562
18462
  let mode;
18563
- if (existsSync6(path)) {
18463
+ if (existsSync4(path)) {
18564
18464
  mode = statSync4(path).mode & 511;
18565
18465
  }
18566
18466
  const tmp = `${path}.${process.pid}.tmp`;
@@ -18568,7 +18468,7 @@ function writeTomlAtomic(path, value) {
18568
18468
  `);
18569
18469
  if (mode !== void 0) {
18570
18470
  try {
18571
- chmodSync4(tmp, mode);
18471
+ chmodSync3(tmp, mode);
18572
18472
  } catch {
18573
18473
  }
18574
18474
  }
@@ -18580,127 +18480,172 @@ function isObject4(v2) {
18580
18480
  function trimRightSlash4(url2) {
18581
18481
  return url2.endsWith("/") ? url2.slice(0, -1) : url2;
18582
18482
  }
18483
+ function buildHttpHeaders(scope, project) {
18484
+ const safe = (v2) => v2.replace(/[\r\n]/g, "").trim();
18485
+ const headers = {
18486
+ "x-client-app": "codex"
18487
+ };
18488
+ if (scope !== "user" && project) {
18489
+ const slug = safe(project);
18490
+ if (slug) {
18491
+ headers["x-project"] = slug;
18492
+ }
18493
+ }
18494
+ return headers;
18495
+ }
18583
18496
 
18584
- // src/commands/configure.ts
18497
+ // src/lib/hooks.ts
18498
+ import { chmodSync as chmodSync4, copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync5 } from "fs";
18499
+ import { dirname as dirname5, join as join6 } from "path";
18500
+ import { fileURLToPath } from "url";
18501
+ function bundledHookSource() {
18502
+ const here = dirname5(fileURLToPath(import.meta.url));
18503
+ const candidates = [
18504
+ join6(here, "hooks", "acceptance.sh"),
18505
+ // production — dist/hooks/…
18506
+ join6(here, "..", "hooks", "acceptance.sh")
18507
+ // dev — src/hooks/…
18508
+ ];
18509
+ for (const candidate of candidates) {
18510
+ if (existsSync5(candidate)) return candidate;
18511
+ }
18512
+ throw new Error(
18513
+ "Could not locate the bundled acceptance hook script (acceptance.sh). Reinstall @codevector/cli to repair the installation."
18514
+ );
18515
+ }
18516
+ function installAcceptanceHook() {
18517
+ mkdirSync5(HOOKS_DIR, { recursive: true, mode: 448 });
18518
+ copyFileSync(bundledHookSource(), ACCEPTANCE_HOOK_FILE);
18519
+ try {
18520
+ chmodSync4(ACCEPTANCE_HOOK_FILE, 493);
18521
+ } catch {
18522
+ }
18523
+ return ACCEPTANCE_HOOK_FILE;
18524
+ }
18525
+
18526
+ // src/lib/project-config.ts
18527
+ import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
18528
+ import { dirname as dirname6, join as join7, parse as parsePath, resolve } from "path";
18529
+ var PROJECT_CONFIG_FILENAME = ".codevector.json";
18530
+ var ProjectConfigSchema = external_exports.object({
18531
+ projectName: external_exports.string().min(1).optional(),
18532
+ ticketPattern: external_exports.string().min(1).optional(),
18533
+ gateway: external_exports.url().optional(),
18534
+ tools: external_exports.array(ToolConfigSchema).optional()
18535
+ });
18536
+ function findProjectConfigPath(startDir) {
18537
+ let dir = resolve(startDir);
18538
+ const root = parsePath(dir).root;
18539
+ while (true) {
18540
+ const candidate = join7(dir, PROJECT_CONFIG_FILENAME);
18541
+ if (existsSync6(candidate)) return candidate;
18542
+ if (dir === root) return null;
18543
+ const parent = dirname6(dir);
18544
+ if (parent === dir) return null;
18545
+ dir = parent;
18546
+ }
18547
+ }
18548
+ function readProjectConfigAt(path) {
18549
+ let raw;
18550
+ try {
18551
+ raw = readFileSync7(path, "utf8");
18552
+ } catch {
18553
+ return null;
18554
+ }
18555
+ let parsed;
18556
+ try {
18557
+ parsed = JSON.parse(raw);
18558
+ } catch {
18559
+ return null;
18560
+ }
18561
+ const result = ProjectConfigSchema.safeParse(parsed);
18562
+ return result.success ? result.data : null;
18563
+ }
18564
+ function readProjectConfig(startDir) {
18565
+ const path = findProjectConfigPath(startDir);
18566
+ if (!path) return null;
18567
+ const config2 = readProjectConfigAt(path);
18568
+ if (!config2) return null;
18569
+ return { config: config2, path };
18570
+ }
18571
+ function writeProjectConfig(path, config2) {
18572
+ const parsed = ProjectConfigSchema.parse(config2);
18573
+ writeFileSync6(path, `${JSON.stringify(parsed, null, 2)}
18574
+ `, { mode: 420 });
18575
+ }
18576
+
18577
+ // src/commands/config/sync.ts
18585
18578
  var WRITERS = {
18586
18579
  "claude-code": writeClaudeCodeConfig,
18587
18580
  opencode: writeOpencodeConfig,
18588
18581
  codex: writeCodexConfig
18589
18582
  };
18590
- var ALL_TOOLS = ["claude-code", "opencode", "codex"];
18591
- var SCOPES = ["local", "project"];
18592
- var configureCommand = defineCommand({
18583
+ var configSyncCommand = defineCommand({
18593
18584
  meta: {
18594
- name: "configure",
18595
- description: "Configure coding tools to route through the gateway."
18585
+ name: "sync",
18586
+ description: "Re-apply the tool configuration recorded in .codevector.json. Use this when on-disk IDE config files have drifted from the manifest."
18596
18587
  },
18597
- args: {
18598
- tool: {
18599
- type: "positional",
18600
- required: false,
18601
- description: "Tool to configure. Prompted if omitted. One of: claude-code, opencode, codex, all."
18602
- },
18603
- scope: {
18604
- type: "string",
18605
- description: "Settings scope: local (default, per-repo, gitignored) or project (committed). For user-scope (global) setup, use `codevector system configure`.",
18606
- valueHint: "local|project"
18607
- },
18608
- all: {
18609
- type: "boolean",
18610
- description: "Configure every supported tool."
18588
+ async run() {
18589
+ ge("codevector sync");
18590
+ const found = readProjectConfig(userCwd());
18591
+ if (!found) {
18592
+ throw new Error(
18593
+ "No .codevector.json on the path to root. Run `codevector init` first."
18594
+ );
18611
18595
  }
18612
- },
18613
- async run({ args }) {
18614
- const initialProfiles = await readProfiles();
18615
- if (!initialProfiles) {
18596
+ const { config: config2, path: manifestPath } = found;
18597
+ R2.info(`Manifest: ${manifestPath}`);
18598
+ const tools = config2.tools ?? [];
18599
+ if (tools.length === 0) {
18600
+ R2.warn(
18601
+ "Manifest has no `tools` to sync. Run `codevector configure` to set up tools for this repo."
18602
+ );
18603
+ ye("Nothing to do.");
18604
+ return;
18605
+ }
18606
+ const profiles = await readProfiles();
18607
+ if (!profiles) {
18616
18608
  throw new Error("Not signed in. Run `codevector auth login` first.");
18617
18609
  }
18618
- const activeProfileName = initialProfiles.activeProfile;
18619
- const creds = initialProfiles.profiles[activeProfileName];
18610
+ const creds = profiles.profiles[profiles.activeProfile];
18620
18611
  if (!creds) {
18621
18612
  throw new Error(
18622
- `Active profile "${activeProfileName}" is missing from credentials. Run \`codevector auth login\` to recover.`
18613
+ `Active profile "${profiles.activeProfile}" is missing from credentials. Run \`codevector auth login\`.`
18623
18614
  );
18624
18615
  }
18625
- ge("codevector configure");
18626
- const tools = await resolveTools(args);
18627
- const scope = await resolveScope(args.scope, tools);
18628
- const project = scope === "user" ? void 0 : resolveProjectContext(userCwd()).project ?? void 0;
18629
- if (scope !== "user") {
18630
- R2.info(
18631
- project ? `Project: ${project} (used for x-project attribution header)` : "No project detected (no git remote and no .codevector.json)."
18616
+ if (config2.gateway && trimRightSlash5(creds.gatewayUrl) !== trimRightSlash5(config2.gateway)) {
18617
+ R2.warn(
18618
+ `Active profile points at ${creds.gatewayUrl} but manifest pins ${config2.gateway}. Sync will use the active profile.`
18632
18619
  );
18633
18620
  }
18634
18621
  const reachable = await fetchReachableChatModels(creds);
18635
- const model = await pickPinnedModel(reachable);
18636
18622
  const hookPath = installAcceptanceHook();
18623
+ const project = config2.projectName;
18637
18624
  const results = [];
18638
18625
  for (const tool of tools) {
18639
- const writer = WRITERS[tool];
18626
+ const writer = WRITERS[tool.tool];
18640
18627
  if (!writer) {
18641
18628
  results.push({
18642
- tool,
18629
+ tool: tool.tool,
18643
18630
  status: "skipped",
18644
18631
  path: "",
18645
- scope,
18646
- notes: `Unsupported tool "${tool}". Known: ${Object.keys(WRITERS).join(", ")}.`
18632
+ scope: tool.scope,
18633
+ notes: `Unsupported tool "${tool.tool}". Known: ${Object.keys(WRITERS).join(", ")}.`
18647
18634
  });
18648
18635
  continue;
18649
18636
  }
18650
18637
  try {
18651
- results.push(
18652
- writer({
18653
- gatewayUrl: creds.gatewayUrl,
18654
- apiKey: creds.apiKey,
18655
- hookScriptPath: hookPath,
18656
- scope,
18657
- ...project ? { project } : {},
18658
- ...model ? { model } : {},
18659
- availableModels: reachable.map((m2) => ({
18660
- slug: m2.slug,
18661
- displayName: m2.displayName,
18662
- contextWindow: m2.contextWindow,
18663
- maxOutputTokens: m2.maxOutputTokens,
18664
- maxInputTokens: m2.maxInputTokens,
18665
- inputPricePerMtok: m2.inputPricePerMtok,
18666
- cachedInputPricePerMtok: m2.cachedInputPricePerMtok,
18667
- cacheWritePricePerMtok: m2.cacheWritePricePerMtok,
18668
- reasoningPricePerMtok: m2.reasoningPricePerMtok,
18669
- outputPricePerMtok: m2.outputPricePerMtok,
18670
- supportsReasoning: m2.supportsReasoning,
18671
- supportsAttachment: m2.supportsAttachment,
18672
- supportsToolCall: m2.supportsToolCall,
18673
- supportsTemperature: m2.supportsTemperature,
18674
- inputModalities: m2.inputModalities,
18675
- outputModalities: m2.outputModalities,
18676
- releaseDate: m2.releaseDate,
18677
- family: m2.family
18678
- }))
18679
- })
18680
- );
18638
+ results.push(writer(buildWriterInput(tool, creds, hookPath, project, reachable)));
18681
18639
  } catch (err) {
18682
18640
  results.push({
18683
- tool,
18641
+ tool: tool.tool,
18684
18642
  status: "skipped",
18685
18643
  path: "",
18686
- scope,
18644
+ scope: tool.scope,
18687
18645
  notes: err instanceof Error ? err.message : String(err)
18688
18646
  });
18689
18647
  }
18690
18648
  }
18691
- const persisted = [];
18692
- for (const r of results) {
18693
- if (r.status === "configured") {
18694
- persisted.push({
18695
- tool: r.tool,
18696
- scope: r.scope,
18697
- ...model ? { modelSlug: model.slug } : {}
18698
- });
18699
- }
18700
- }
18701
- if (persisted.length > 0) {
18702
- updateProfileToolConfigs(activeProfileName, persisted);
18703
- }
18704
18649
  for (const r of results) {
18705
18650
  if (r.status === "configured") {
18706
18651
  R2.success(`${r.tool} \u2192 ${r.path} [${r.scope}]`);
@@ -18709,37 +18654,320 @@ var configureCommand = defineCommand({
18709
18654
  R2.warn(`${r.tool}: skipped \u2014 ${r.notes ?? "unknown reason"}`);
18710
18655
  }
18711
18656
  }
18712
- if (scope === "local" && tools.includes("claude-code")) {
18713
- Se(
18714
- "Claude Code auto-ignores .claude/settings.local.json in git; your API key stays on this machine.",
18715
- "Local scope"
18716
- );
18717
- }
18718
- ye("Done.");
18657
+ ye("Sync complete.");
18719
18658
  }
18720
18659
  });
18721
- async function resolveTools(args) {
18722
- if (args.all) return [...ALL_TOOLS];
18723
- if (args.tool) {
18724
- if (args.tool === "all") return [...ALL_TOOLS];
18725
- if (isTool(args.tool)) return [args.tool];
18726
- throw new Error(
18727
- `Unsupported tool "${args.tool}". Known: ${ALL_TOOLS.join(", ")}, or pass --all.`
18728
- );
18729
- }
18730
- Se(
18731
- "Use arrow keys to move, space to toggle, a to toggle all, enter to confirm.",
18732
- "Controls"
18733
- );
18734
- const picked = unwrap(
18735
- await ve({
18736
- message: "Which tools do you want to configure? (space to select/deselect, enter to confirm)",
18737
- options: ALL_TOOLS.map((t) => ({ value: t, label: t })),
18738
- initialValues: ["claude-code"],
18739
- required: true
18740
- })
18741
- );
18742
- return picked;
18660
+ function buildWriterInput(tool, creds, hookPath, project, reachable) {
18661
+ const model = pickModel(tool.modelSlug, reachable);
18662
+ return {
18663
+ gatewayUrl: creds.gatewayUrl,
18664
+ apiKey: creds.apiKey,
18665
+ hookScriptPath: hookPath,
18666
+ scope: tool.scope,
18667
+ ...project ? { project } : {},
18668
+ ...model ? { model } : {},
18669
+ availableModels: reachable.map((m2) => ({
18670
+ slug: m2.slug,
18671
+ displayName: m2.displayName,
18672
+ contextWindow: m2.contextWindow,
18673
+ maxOutputTokens: m2.maxOutputTokens,
18674
+ maxInputTokens: m2.maxInputTokens,
18675
+ inputPricePerMtok: m2.inputPricePerMtok,
18676
+ cachedInputPricePerMtok: m2.cachedInputPricePerMtok,
18677
+ cacheWritePricePerMtok: m2.cacheWritePricePerMtok,
18678
+ reasoningPricePerMtok: m2.reasoningPricePerMtok,
18679
+ outputPricePerMtok: m2.outputPricePerMtok,
18680
+ supportsReasoning: m2.supportsReasoning,
18681
+ supportsAttachment: m2.supportsAttachment,
18682
+ supportsToolCall: m2.supportsToolCall,
18683
+ supportsTemperature: m2.supportsTemperature,
18684
+ inputModalities: m2.inputModalities,
18685
+ outputModalities: m2.outputModalities,
18686
+ releaseDate: m2.releaseDate,
18687
+ family: m2.family
18688
+ }))
18689
+ };
18690
+ }
18691
+ function pickModel(modelSlug, reachable) {
18692
+ if (!modelSlug) return void 0;
18693
+ const found = reachable.find((m2) => m2.slug === modelSlug);
18694
+ if (!found) return void 0;
18695
+ return { slug: found.slug, providerKind: found.providerKind };
18696
+ }
18697
+ async function fetchReachableChatModels(creds) {
18698
+ const client = gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
18699
+ const s = ft();
18700
+ s.start("Loading reachable models\u2026");
18701
+ try {
18702
+ const res = await call(parseResponse(client.models.$get()));
18703
+ const models = res.data.filter((m2) => m2.kind === "chat").map((m2) => ({
18704
+ slug: m2.slug,
18705
+ providerKind: m2.providerKind,
18706
+ displayName: m2.displayName,
18707
+ contextWindow: m2.contextWindow,
18708
+ maxOutputTokens: m2.maxOutputTokens,
18709
+ maxInputTokens: m2.maxInputTokens,
18710
+ inputPricePerMtok: m2.inputPricePerMtok,
18711
+ cachedInputPricePerMtok: m2.cachedInputPricePerMtok,
18712
+ cacheWritePricePerMtok: m2.cacheWritePricePerMtok,
18713
+ reasoningPricePerMtok: m2.reasoningPricePerMtok,
18714
+ outputPricePerMtok: m2.outputPricePerMtok,
18715
+ supportsReasoning: m2.supportsReasoning,
18716
+ supportsAttachment: m2.supportsAttachment,
18717
+ supportsToolCall: m2.supportsToolCall,
18718
+ supportsTemperature: m2.supportsTemperature,
18719
+ inputModalities: m2.inputModalities,
18720
+ outputModalities: m2.outputModalities,
18721
+ releaseDate: m2.releaseDate,
18722
+ family: m2.family
18723
+ }));
18724
+ s.stop(`${models.length} model${models.length === 1 ? "" : "s"} reachable`);
18725
+ return models;
18726
+ } catch (err) {
18727
+ s.stop("Could not load models");
18728
+ R2.warn(
18729
+ `Continuing without model list \u2014 ${err instanceof ApiClientError ? err.message : String(err)}.`
18730
+ );
18731
+ return [];
18732
+ }
18733
+ }
18734
+ function trimRightSlash5(url2) {
18735
+ return url2.endsWith("/") ? url2.slice(0, -1) : url2;
18736
+ }
18737
+
18738
+ // src/commands/config/index.ts
18739
+ var configCommand = defineCommand({
18740
+ meta: {
18741
+ name: "config",
18742
+ description: "Inspect and re-apply this repo's .codevector.json configuration."
18743
+ },
18744
+ subCommands: {
18745
+ sync: configSyncCommand
18746
+ }
18747
+ });
18748
+
18749
+ // src/commands/configure.ts
18750
+ import { homedir as homedir5 } from "os";
18751
+
18752
+ // src/lib/project-context.ts
18753
+ import { execFileSync } from "child_process";
18754
+ import { join as join8 } from "path";
18755
+ var DEFAULT_TICKET_PATTERN = /[A-Z]+-\d+/;
18756
+ function safeGit(args, cwd) {
18757
+ try {
18758
+ const out = execFileSync("git", args, {
18759
+ encoding: "utf8",
18760
+ stdio: ["ignore", "pipe", "ignore"],
18761
+ cwd,
18762
+ timeout: 2e3
18763
+ });
18764
+ return out.trim() || null;
18765
+ } catch {
18766
+ return null;
18767
+ }
18768
+ }
18769
+ function repoSlugFromRemote(remote) {
18770
+ const trimmed = remote.trim().replace(/\.git$/i, "");
18771
+ const lastSegment = trimmed.split(/[:/]/).pop();
18772
+ if (!lastSegment) return null;
18773
+ return lastSegment.length > 0 ? lastSegment : null;
18774
+ }
18775
+ function resolveProjectContext(cwd = process.cwd(), ticketPattern = DEFAULT_TICKET_PATTERN) {
18776
+ let project = null;
18777
+ let ticket = null;
18778
+ const override = readLocalConfig(cwd);
18779
+ if (override?.projectName) project = override.projectName;
18780
+ const effectivePattern = override?.ticketPattern ? safeCompileTicketPattern(override.ticketPattern) ?? ticketPattern : ticketPattern;
18781
+ if (!project) {
18782
+ const remote = safeGit(["remote", "get-url", "origin"], cwd);
18783
+ if (remote) project = repoSlugFromRemote(remote);
18784
+ }
18785
+ const branch = safeGit(["branch", "--show-current"], cwd);
18786
+ if (branch) {
18787
+ const match = effectivePattern.exec(branch);
18788
+ if (match) ticket = match[0];
18789
+ }
18790
+ return { project, ticket };
18791
+ }
18792
+ function readLocalConfig(cwd) {
18793
+ const config2 = readProjectConfigAt(join8(cwd, PROJECT_CONFIG_FILENAME));
18794
+ if (!config2) return null;
18795
+ const result = {};
18796
+ if (config2.projectName) result.projectName = config2.projectName;
18797
+ if (config2.ticketPattern) result.ticketPattern = config2.ticketPattern;
18798
+ return result;
18799
+ }
18800
+ function safeCompileTicketPattern(pattern) {
18801
+ try {
18802
+ return new RegExp(pattern);
18803
+ } catch {
18804
+ return null;
18805
+ }
18806
+ }
18807
+
18808
+ // src/commands/configure.ts
18809
+ import { join as join9 } from "path";
18810
+ var WRITERS2 = {
18811
+ "claude-code": writeClaudeCodeConfig,
18812
+ opencode: writeOpencodeConfig,
18813
+ codex: writeCodexConfig
18814
+ };
18815
+ var ALL_TOOLS = ["claude-code", "opencode", "codex"];
18816
+ var SCOPES = ["local", "project"];
18817
+ var configureCommand = defineCommand({
18818
+ meta: {
18819
+ name: "configure",
18820
+ description: "Configure coding tools to route through the gateway."
18821
+ },
18822
+ args: {
18823
+ tool: {
18824
+ type: "positional",
18825
+ required: false,
18826
+ description: "Tool to configure. Prompted if omitted. One of: claude-code, opencode, codex, all."
18827
+ },
18828
+ scope: {
18829
+ type: "string",
18830
+ description: "Settings scope: local (default, per-repo, gitignored) or project (committed). For user-scope (global) setup, use `codevector system configure`.",
18831
+ valueHint: "local|project"
18832
+ },
18833
+ all: {
18834
+ type: "boolean",
18835
+ description: "Configure every supported tool."
18836
+ }
18837
+ },
18838
+ async run({ args }) {
18839
+ const initialProfiles = await readProfiles();
18840
+ if (!initialProfiles) {
18841
+ throw new Error("Not signed in. Run `codevector auth login` first.");
18842
+ }
18843
+ const activeProfileName = initialProfiles.activeProfile;
18844
+ const creds = initialProfiles.profiles[activeProfileName];
18845
+ if (!creds) {
18846
+ throw new Error(
18847
+ `Active profile "${activeProfileName}" is missing from credentials. Run \`codevector auth login\` to recover.`
18848
+ );
18849
+ }
18850
+ ge("codevector configure");
18851
+ const tools = await resolveTools(args);
18852
+ const scope = await resolveScope(args.scope, tools);
18853
+ const project = scope === "user" ? void 0 : resolveProjectContext(userCwd()).project ?? void 0;
18854
+ if (scope !== "user") {
18855
+ R2.info(
18856
+ project ? `Project: ${project} (used for x-project attribution header)` : "No project detected (no git remote and no .codevector.json)."
18857
+ );
18858
+ }
18859
+ const reachable = await fetchReachableChatModels2(creds);
18860
+ const model = await pickPinnedModel(reachable);
18861
+ const hookPath = installAcceptanceHook();
18862
+ const results = [];
18863
+ for (const tool of tools) {
18864
+ const writer = WRITERS2[tool];
18865
+ if (!writer) {
18866
+ results.push({
18867
+ tool,
18868
+ status: "skipped",
18869
+ path: "",
18870
+ scope,
18871
+ notes: `Unsupported tool "${tool}". Known: ${Object.keys(WRITERS2).join(", ")}.`
18872
+ });
18873
+ continue;
18874
+ }
18875
+ try {
18876
+ results.push(
18877
+ writer({
18878
+ gatewayUrl: creds.gatewayUrl,
18879
+ apiKey: creds.apiKey,
18880
+ hookScriptPath: hookPath,
18881
+ scope,
18882
+ ...project ? { project } : {},
18883
+ ...model ? { model } : {},
18884
+ availableModels: reachable.map((m2) => ({
18885
+ slug: m2.slug,
18886
+ displayName: m2.displayName,
18887
+ contextWindow: m2.contextWindow,
18888
+ maxOutputTokens: m2.maxOutputTokens,
18889
+ maxInputTokens: m2.maxInputTokens,
18890
+ inputPricePerMtok: m2.inputPricePerMtok,
18891
+ cachedInputPricePerMtok: m2.cachedInputPricePerMtok,
18892
+ cacheWritePricePerMtok: m2.cacheWritePricePerMtok,
18893
+ reasoningPricePerMtok: m2.reasoningPricePerMtok,
18894
+ outputPricePerMtok: m2.outputPricePerMtok,
18895
+ supportsReasoning: m2.supportsReasoning,
18896
+ supportsAttachment: m2.supportsAttachment,
18897
+ supportsToolCall: m2.supportsToolCall,
18898
+ supportsTemperature: m2.supportsTemperature,
18899
+ inputModalities: m2.inputModalities,
18900
+ outputModalities: m2.outputModalities,
18901
+ releaseDate: m2.releaseDate,
18902
+ family: m2.family
18903
+ }))
18904
+ })
18905
+ );
18906
+ } catch (err) {
18907
+ results.push({
18908
+ tool,
18909
+ status: "skipped",
18910
+ path: "",
18911
+ scope,
18912
+ notes: err instanceof Error ? err.message : String(err)
18913
+ });
18914
+ }
18915
+ }
18916
+ const persisted = [];
18917
+ for (const r of results) {
18918
+ if (r.status === "configured") {
18919
+ persisted.push({
18920
+ tool: r.tool,
18921
+ scope: r.scope,
18922
+ ...model ? { modelSlug: model.slug } : {}
18923
+ });
18924
+ }
18925
+ }
18926
+ if (persisted.length > 0) {
18927
+ updateProfileToolConfigs(activeProfileName, persisted);
18928
+ if (scope !== "user") {
18929
+ mergeToolsIntoProjectConfig(userCwd(), persisted, creds.gatewayUrl);
18930
+ }
18931
+ }
18932
+ for (const r of results) {
18933
+ if (r.status === "configured") {
18934
+ R2.success(`${r.tool} \u2192 ${r.path} [${r.scope}]`);
18935
+ if (r.notes) R2.info(r.notes);
18936
+ } else {
18937
+ R2.warn(`${r.tool}: skipped \u2014 ${r.notes ?? "unknown reason"}`);
18938
+ }
18939
+ }
18940
+ if (scope === "local" && tools.includes("claude-code")) {
18941
+ Se(
18942
+ "Claude Code auto-ignores .claude/settings.local.json in git; your API key stays on this machine.",
18943
+ "Local scope"
18944
+ );
18945
+ }
18946
+ ye("Done.");
18947
+ }
18948
+ });
18949
+ async function resolveTools(args) {
18950
+ if (args.all) return [...ALL_TOOLS];
18951
+ if (args.tool) {
18952
+ if (args.tool === "all") return [...ALL_TOOLS];
18953
+ if (isTool(args.tool)) return [args.tool];
18954
+ throw new Error(
18955
+ `Unsupported tool "${args.tool}". Known: ${ALL_TOOLS.join(", ")}, or pass --all.`
18956
+ );
18957
+ }
18958
+ Se(
18959
+ "Use arrow keys to move, space to toggle, a to toggle all, enter to confirm.",
18960
+ "Controls"
18961
+ );
18962
+ const picked = unwrap(
18963
+ await ve({
18964
+ message: "Which tools do you want to configure? (space to select/deselect, enter to confirm)",
18965
+ options: ALL_TOOLS.map((t) => ({ value: t, label: t })),
18966
+ initialValues: ["claude-code"],
18967
+ required: true
18968
+ })
18969
+ );
18970
+ return picked;
18743
18971
  }
18744
18972
  async function resolveScope(raw, tools) {
18745
18973
  if (raw === "user") {
@@ -18798,10 +19026,22 @@ function relativizeHomeAndCwd(absolutePath) {
18798
19026
  function isTool(value) {
18799
19027
  return ALL_TOOLS.includes(value);
18800
19028
  }
19029
+ function mergeToolsIntoProjectConfig(cwd, tools, gatewayUrl) {
19030
+ const path = join9(cwd, PROJECT_CONFIG_FILENAME);
19031
+ const existing = readProjectConfigAt(path) ?? {};
19032
+ const byTool = new Map((existing.tools ?? []).map((c) => [c.tool, c]));
19033
+ for (const cfg of tools) byTool.set(cfg.tool, cfg);
19034
+ const next = {
19035
+ ...existing,
19036
+ ...existing.gateway ? {} : { gateway: gatewayUrl },
19037
+ tools: [...byTool.values()]
19038
+ };
19039
+ writeProjectConfig(path, next);
19040
+ }
18801
19041
  function isScope(value) {
18802
19042
  return SCOPES.includes(value);
18803
19043
  }
18804
- async function fetchReachableChatModels(creds) {
19044
+ async function fetchReachableChatModels2(creds) {
18805
19045
  const client = gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
18806
19046
  const s = ft();
18807
19047
  s.start("Loading reachable models\u2026");
@@ -18869,7 +19109,74 @@ async function pickPinnedModel(models) {
18869
19109
  }
18870
19110
 
18871
19111
  // src/commands/doctor.ts
18872
- import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
19112
+ import { existsSync as existsSync8, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
19113
+
19114
+ // src/lib/install-pref.ts
19115
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync, renameSync as renameSync4, writeFileSync as writeFileSync7 } from "fs";
19116
+ import { join as join10, sep } from "path";
19117
+ import { fileURLToPath as fileURLToPath2 } from "url";
19118
+ var INSTALL_PREF_FILE = join10(CODEVECTOR_CONFIG_DIR, "install.json");
19119
+ var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn"];
19120
+ function isPackageManager(v2) {
19121
+ return typeof v2 === "string" && PACKAGE_MANAGERS.includes(v2);
19122
+ }
19123
+ function readInstallPref() {
19124
+ if (!existsSync7(INSTALL_PREF_FILE)) return void 0;
19125
+ try {
19126
+ const raw = readFileSync8(INSTALL_PREF_FILE, "utf8");
19127
+ const parsed = JSON.parse(raw);
19128
+ if (!isPackageManager(parsed.packageManager)) return void 0;
19129
+ const source = parsed.source === "user" ? "user" : "auto";
19130
+ return {
19131
+ packageManager: parsed.packageManager,
19132
+ source,
19133
+ detectedAt: typeof parsed.detectedAt === "string" ? parsed.detectedAt : (/* @__PURE__ */ new Date()).toISOString()
19134
+ };
19135
+ } catch {
19136
+ return void 0;
19137
+ }
19138
+ }
19139
+ function writeInstallPref(pref) {
19140
+ mkdirSync6(CODEVECTOR_CONFIG_DIR, { recursive: true, mode: 448 });
19141
+ const tmp = `${INSTALL_PREF_FILE}.${process.pid}.tmp`;
19142
+ writeFileSync7(tmp, JSON.stringify(pref, null, 2));
19143
+ renameSync4(tmp, INSTALL_PREF_FILE);
19144
+ }
19145
+ function packageManagerFromPath(installPath) {
19146
+ if (installPath.includes(`${sep}.pnpm${sep}`)) return "pnpm";
19147
+ if (installPath.includes(`${sep}.config${sep}yarn${sep}global${sep}`) || installPath.includes(`${sep}.yarn${sep}`)) {
19148
+ return "yarn";
19149
+ }
19150
+ if (installPath.includes(`${sep}lib${sep}node_modules${sep}`)) return "npm";
19151
+ return void 0;
19152
+ }
19153
+ function detectPackageManagerFromInstallPath() {
19154
+ try {
19155
+ const real = realpathSync(fileURLToPath2(import.meta.url));
19156
+ return packageManagerFromPath(real);
19157
+ } catch {
19158
+ return void 0;
19159
+ }
19160
+ }
19161
+ function resolveInstallManager() {
19162
+ const pref = readInstallPref();
19163
+ if (pref?.source === "user") return { packageManager: pref.packageManager, source: "user" };
19164
+ const fromPath = detectPackageManagerFromInstallPath();
19165
+ if (fromPath) {
19166
+ if (pref?.packageManager !== fromPath) {
19167
+ writeInstallPref({
19168
+ packageManager: fromPath,
19169
+ source: "auto",
19170
+ detectedAt: (/* @__PURE__ */ new Date()).toISOString()
19171
+ });
19172
+ }
19173
+ return { packageManager: fromPath, source: "path" };
19174
+ }
19175
+ if (pref) return { packageManager: pref.packageManager, source: "auto" };
19176
+ return { packageManager: void 0, source: "unknown" };
19177
+ }
19178
+
19179
+ // src/commands/doctor.ts
18873
19180
  var CLAUDE_SCOPE_ORDER = ["local", "project", "user"];
18874
19181
  var doctorCommand = defineCommand({
18875
19182
  meta: {
@@ -18881,44 +19188,43 @@ var doctorCommand = defineCommand({
18881
19188
  const creds = await readCredentials();
18882
19189
  if (!creds) {
18883
19190
  checks.push({
18884
- level: "fail",
19191
+ level: "warn",
18885
19192
  label: "credentials",
18886
- detail: `no credentials at ${CREDENTIALS_FILE} \u2014 run \`codevector auth login\``
19193
+ detail: `not logged in \u2014 no credentials at ${CREDENTIALS_FILE}. Run \`codevector auth login\`. Local checks still run.`
18887
19194
  });
18888
- emit(checks);
18889
- process.exitCode = 1;
18890
- return;
18891
- }
18892
- checks.push({
18893
- level: "ok",
18894
- label: "credentials",
18895
- detail: `${creds.email} @ ${creds.gatewayUrl} (${maskApiKey(creds.apiKey)})`
18896
- });
18897
- checks.push(
18898
- credentialsFileModeOk() ? { level: "ok", label: "credentials permissions", detail: "chmod 600" } : {
18899
- level: "warn",
18900
- label: "credentials permissions",
18901
- detail: `expected 0600 on ${CREDENTIALS_FILE}`
18902
- }
18903
- );
18904
- const client = gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
18905
- try {
18906
- const me2 = await call(parseResponse(client.me.$get()));
18907
- if (me2.user.id !== creds.userId) {
18908
- checks.push({
19195
+ } else {
19196
+ checks.push({
19197
+ level: "ok",
19198
+ label: "credentials",
19199
+ detail: `${creds.email} @ ${creds.gatewayUrl} (${maskApiKey(creds.apiKey)})`
19200
+ });
19201
+ checks.push(
19202
+ credentialsFileModeOk() ? { level: "ok", label: "credentials permissions", detail: "chmod 600" } : {
18909
19203
  level: "warn",
18910
- label: "gateway /me",
18911
- detail: "user id mismatch \u2014 credentials may be stale. Re-run `codevector auth login`."
18912
- });
18913
- } else {
18914
- checks.push({ level: "ok", label: "gateway /me", detail: "200 OK" });
19204
+ label: "credentials permissions",
19205
+ detail: `expected 0600 on ${CREDENTIALS_FILE}`
19206
+ }
19207
+ );
19208
+ const client = gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
19209
+ try {
19210
+ const me2 = await call(parseResponse(client.me.$get()));
19211
+ if (me2.user.id !== creds.userId) {
19212
+ checks.push({
19213
+ level: "warn",
19214
+ label: "gateway /me",
19215
+ detail: "user id mismatch \u2014 credentials may be stale. Re-run `codevector auth login`."
19216
+ });
19217
+ } else {
19218
+ checks.push({ level: "ok", label: "gateway /me", detail: "200 OK" });
19219
+ }
19220
+ } catch (err) {
19221
+ const message = err instanceof ApiClientError ? `${err.code}: ${err.message}` : String(err);
19222
+ checks.push({ level: "fail", label: "gateway /me", detail: message });
18915
19223
  }
18916
- } catch (err) {
18917
- const message = err instanceof ApiClientError ? `${err.code}: ${err.message}` : String(err);
18918
- checks.push({ level: "fail", label: "gateway /me", detail: message });
18919
19224
  }
18920
19225
  checks.push(inspectClaudeSettings());
18921
- if (!existsSync7(ACCEPTANCE_HOOK_FILE)) {
19226
+ checks.push(...inspectManifestDrift());
19227
+ if (!existsSync8(ACCEPTANCE_HOOK_FILE)) {
18922
19228
  checks.push({
18923
19229
  level: "warn",
18924
19230
  label: "acceptance hook",
@@ -18934,18 +19240,35 @@ var doctorCommand = defineCommand({
18934
19240
  }
18935
19241
  );
18936
19242
  }
19243
+ checks.push(inspectUpdateManager());
18937
19244
  emit(checks);
18938
19245
  if (checks.some((c) => c.level === "fail")) {
18939
19246
  process.exitCode = 1;
18940
19247
  }
18941
19248
  }
18942
19249
  });
19250
+ function inspectUpdateManager() {
19251
+ const resolved = resolveInstallManager();
19252
+ if (!resolved.packageManager) {
19253
+ return {
19254
+ level: "warn",
19255
+ label: "update manager",
19256
+ detail: "couldn't determine which package manager installed the CLI \u2014 `codevector update` will have to guess. Pin it with `codevector update --with <npm|pnpm|yarn>`."
19257
+ };
19258
+ }
19259
+ const how = resolved.source === "user" ? "pinned via --with" : resolved.source === "path" ? "detected from install path" : "auto-detected at install";
19260
+ return {
19261
+ level: "ok",
19262
+ label: "update manager",
19263
+ detail: `${resolved.packageManager} (${how})`
19264
+ };
19265
+ }
18943
19266
  function inspectClaudeSettings() {
18944
19267
  for (const scope of CLAUDE_SCOPE_ORDER) {
18945
19268
  const path = claudeSettingsPath(scope);
18946
- if (!existsSync7(path)) continue;
19269
+ if (!existsSync8(path)) continue;
18947
19270
  try {
18948
- const raw = JSON.parse(readFileSync8(path, "utf8"));
19271
+ const raw = JSON.parse(readFileSync9(path, "utf8"));
18949
19272
  if (typeof raw.env?.ANTHROPIC_BASE_URL === "string") {
18950
19273
  return {
18951
19274
  level: "ok",
@@ -18961,36 +19284,339 @@ function inspectClaudeSettings() {
18961
19284
  };
18962
19285
  }
18963
19286
  }
18964
- return {
18965
- level: "warn",
18966
- label: "claude-code settings",
18967
- detail: "ANTHROPIC_BASE_URL not set at any scope \u2014 run `codevector configure claude-code`"
18968
- };
19287
+ return {
19288
+ level: "warn",
19289
+ label: "claude-code settings",
19290
+ detail: "ANTHROPIC_BASE_URL not set at any scope \u2014 run `codevector configure claude-code`"
19291
+ };
19292
+ }
19293
+ function inspectManifestDrift() {
19294
+ const found = readProjectConfig(userCwd());
19295
+ if (!found) return [];
19296
+ const { config: config2 } = found;
19297
+ const tools = config2.tools ?? [];
19298
+ if (tools.length === 0) return [];
19299
+ const gateway = config2.gateway?.replace(/\/$/, "") ?? null;
19300
+ if (!gateway) {
19301
+ return [
19302
+ {
19303
+ level: "warn",
19304
+ label: "manifest drift",
19305
+ detail: ".codevector.json lists tools but has no `gateway` \u2014 run `codevector init`"
19306
+ }
19307
+ ];
19308
+ }
19309
+ const checks = [];
19310
+ for (const tool of tools) {
19311
+ const path = manifestToolPath(tool.tool, tool.scope);
19312
+ if (!path) {
19313
+ checks.push({
19314
+ level: "warn",
19315
+ label: `manifest drift: ${tool.tool}`,
19316
+ detail: `unknown tool "${tool.tool}"`
19317
+ });
19318
+ continue;
19319
+ }
19320
+ if (!existsSync8(path)) {
19321
+ checks.push({
19322
+ level: "fail",
19323
+ label: `manifest drift: ${tool.tool}`,
19324
+ detail: `${path} missing \u2014 run \`codevector config sync\``
19325
+ });
19326
+ continue;
19327
+ }
19328
+ let raw;
19329
+ try {
19330
+ raw = readFileSync9(path, "utf8");
19331
+ } catch (err) {
19332
+ checks.push({
19333
+ level: "fail",
19334
+ label: `manifest drift: ${tool.tool}`,
19335
+ detail: `cannot read ${path}: ${err instanceof Error ? err.message : String(err)}`
19336
+ });
19337
+ continue;
19338
+ }
19339
+ if (!raw.includes(gateway)) {
19340
+ checks.push({
19341
+ level: "fail",
19342
+ label: `manifest drift: ${tool.tool}`,
19343
+ detail: `${path} does not reference ${gateway} \u2014 run \`codevector config sync\``
19344
+ });
19345
+ continue;
19346
+ }
19347
+ checks.push({
19348
+ level: "ok",
19349
+ label: `manifest: ${tool.tool}`,
19350
+ detail: `[${tool.scope}] ${path}`
19351
+ });
19352
+ }
19353
+ return checks;
19354
+ }
19355
+ function manifestToolPath(tool, scope) {
19356
+ switch (tool) {
19357
+ case "claude-code":
19358
+ return claudeSettingsPath(scope);
19359
+ case "opencode":
19360
+ return opencodeSettingsPath(scope);
19361
+ case "codex":
19362
+ return codexConfigPath(scope);
19363
+ default:
19364
+ return null;
19365
+ }
19366
+ }
19367
+ function emit(checks) {
19368
+ Se(
19369
+ checks.map((c) => {
19370
+ const icon = c.level === "ok" ? "\u2713" : c.level === "warn" ? "!" : "\u2717";
19371
+ const detail = c.detail ? ` \u2014 ${c.detail}` : "";
19372
+ return `${icon} ${c.label}${detail}`;
19373
+ }).join("\n"),
19374
+ "Doctor"
19375
+ );
19376
+ for (const c of checks) {
19377
+ if (c.level === "fail") R2.error(c.label);
19378
+ else if (c.level === "warn") R2.warn(c.label);
19379
+ }
19380
+ }
19381
+
19382
+ // src/commands/env.ts
19383
+ var SHELLS = ["bash", "zsh", "fish"];
19384
+ var envCommand = defineCommand({
19385
+ meta: {
19386
+ name: "env",
19387
+ description: "Print shell exports to activate / deactivate codevector credentials for the current directory. Intended to be eval'd by the shell hook (see `codevector hook`)."
19388
+ },
19389
+ args: {
19390
+ shell: {
19391
+ type: "string",
19392
+ description: "Output dialect: bash, zsh, or fish.",
19393
+ valueHint: "bash|zsh|fish"
19394
+ }
19395
+ },
19396
+ async run({ args }) {
19397
+ const shell = resolveShell(args.shell);
19398
+ const cwd = userCwd();
19399
+ const previousDir = process.env.CODEVECTOR_ACTIVE_DIR ?? null;
19400
+ const found = readProjectConfig(cwd);
19401
+ const repoDir = found?.path ? dirOf(found.path) : null;
19402
+ const gateway = found?.config.gateway ?? null;
19403
+ const projectName = found?.config.projectName ?? null;
19404
+ const lines = [];
19405
+ if (!gateway || !repoDir) {
19406
+ if (previousDir) emitDeactivate(lines, shell, previousDir);
19407
+ process.stdout.write(lines.join("\n") + (lines.length > 0 ? "\n" : ""));
19408
+ return;
19409
+ }
19410
+ if (previousDir === repoDir) {
19411
+ return;
19412
+ }
19413
+ const profiles = await readProfiles();
19414
+ const match = pickProfileForGateway(
19415
+ profiles?.profiles ?? {},
19416
+ profiles?.activeProfile ?? null,
19417
+ gateway
19418
+ );
19419
+ if (!match) {
19420
+ if (previousDir) emitDeactivate(lines, shell, previousDir);
19421
+ emitEcho(
19422
+ lines,
19423
+ shell,
19424
+ `codevector: no matching profile for gateway ${gateway}. Run \`codevector auth login --gateway ${gateway}\`.`
19425
+ );
19426
+ process.stdout.write(lines.join("\n") + "\n");
19427
+ return;
19428
+ }
19429
+ const { name: profileName, profile } = match;
19430
+ const headers = buildAnthropicCustomHeaders(projectName);
19431
+ emitExport(lines, shell, "ANTHROPIC_BASE_URL", `${trimRightSlash6(profile.gatewayUrl)}/gateway/anthropic`);
19432
+ emitExport(lines, shell, "ANTHROPIC_API_KEY", profile.apiKey);
19433
+ if (headers) emitExport(lines, shell, "ANTHROPIC_CUSTOM_HEADERS", headers);
19434
+ emitExport(lines, shell, "OPENAI_BASE_URL", `${trimRightSlash6(profile.gatewayUrl)}/gateway/openai/v1`);
19435
+ emitExport(lines, shell, "OPENAI_API_KEY", profile.apiKey);
19436
+ emitExport(lines, shell, "CODEVECTOR_ACTIVE_DIR", repoDir);
19437
+ emitExport(lines, shell, "CODEVECTOR_ACTIVE_PROFILE", profileName);
19438
+ const projectSuffix = projectName ? ` (project: ${projectName})` : "";
19439
+ emitEcho(
19440
+ lines,
19441
+ shell,
19442
+ `codevector: ${profileName} -> ${trimRightSlash6(profile.gatewayUrl)}${projectSuffix}`
19443
+ );
19444
+ process.stdout.write(lines.join("\n") + "\n");
19445
+ }
19446
+ });
19447
+ function resolveShell(raw) {
19448
+ if (!raw) return detectShell();
19449
+ if (!SHELLS.includes(raw)) {
19450
+ throw new Error(`Invalid --shell "${raw}". Use one of: ${SHELLS.join(", ")}.`);
19451
+ }
19452
+ return raw;
19453
+ }
19454
+ function detectShell() {
19455
+ const shellPath = process.env.SHELL ?? "";
19456
+ if (shellPath.endsWith("/fish")) return "fish";
19457
+ if (shellPath.endsWith("/zsh")) return "zsh";
19458
+ return "bash";
19459
+ }
19460
+ function emitExport(lines, shell, name, value) {
19461
+ const quoted = shellQuote(value);
19462
+ if (shell === "fish") {
19463
+ lines.push(`set -gx ${name} ${quoted}`);
19464
+ } else {
19465
+ lines.push(`export ${name}=${quoted}`);
19466
+ }
18969
19467
  }
18970
- function emit(checks) {
18971
- Se(
18972
- checks.map((c) => {
18973
- const icon = c.level === "ok" ? "\u2713" : c.level === "warn" ? "!" : "\u2717";
18974
- const detail = c.detail ? ` \u2014 ${c.detail}` : "";
18975
- return `${icon} ${c.label}${detail}`;
18976
- }).join("\n"),
18977
- "Doctor"
19468
+ function emitUnset(lines, shell, name) {
19469
+ if (shell === "fish") {
19470
+ lines.push(`set -e ${name}`);
19471
+ } else {
19472
+ lines.push(`unset ${name}`);
19473
+ }
19474
+ }
19475
+ function emitEcho(lines, _shell, message) {
19476
+ lines.push(`echo ${shellQuote(message)} 1>&2`);
19477
+ }
19478
+ function emitDeactivate(lines, shell, previousDir) {
19479
+ for (const name of [
19480
+ "ANTHROPIC_BASE_URL",
19481
+ "ANTHROPIC_API_KEY",
19482
+ "ANTHROPIC_CUSTOM_HEADERS",
19483
+ "OPENAI_BASE_URL",
19484
+ "OPENAI_API_KEY",
19485
+ "CODEVECTOR_ACTIVE_DIR",
19486
+ "CODEVECTOR_ACTIVE_PROFILE"
19487
+ ]) {
19488
+ emitUnset(lines, shell, name);
19489
+ }
19490
+ emitEcho(lines, shell, `codevector: left ${previousDir}, credentials cleared.`);
19491
+ }
19492
+ function shellQuote(value) {
19493
+ return `'${value.replace(/'/g, `'\\''`)}'`;
19494
+ }
19495
+ function trimRightSlash6(url2) {
19496
+ return url2.endsWith("/") ? url2.slice(0, -1) : url2;
19497
+ }
19498
+ function dirOf(filePath) {
19499
+ const idx = filePath.lastIndexOf("/");
19500
+ return idx === -1 ? filePath : filePath.slice(0, idx);
19501
+ }
19502
+ function buildAnthropicCustomHeaders(projectName) {
19503
+ if (!projectName) return null;
19504
+ const safe = projectName.replace(/[\r\n]/g, "").trim();
19505
+ if (!safe) return null;
19506
+ return `x-project: ${safe}`;
19507
+ }
19508
+ function pickProfileForGateway(profiles, activeProfile, gateway) {
19509
+ const target = trimRightSlash6(gateway);
19510
+ const matches = Object.entries(profiles).filter(
19511
+ ([, p2]) => trimRightSlash6(p2.gatewayUrl) === target
18978
19512
  );
18979
- for (const c of checks) {
18980
- if (c.level === "fail") R2.error(c.label);
18981
- else if (c.level === "warn") R2.warn(c.label);
19513
+ if (matches.length === 0) return null;
19514
+ if (activeProfile) {
19515
+ const active = matches.find(([name]) => name === activeProfile);
19516
+ if (active) return { name: active[0], profile: active[1] };
18982
19517
  }
19518
+ const sorted = [...matches].sort((a, b2) => a[0].localeCompare(b2[0]));
19519
+ const picked = sorted[0];
19520
+ return { name: picked[0], profile: picked[1] };
18983
19521
  }
18984
19522
 
19523
+ // src/commands/hook.ts
19524
+ var SHELLS2 = ["bash", "zsh", "fish"];
19525
+ var hookCommand = defineCommand({
19526
+ meta: {
19527
+ name: "hook",
19528
+ description: 'Print the shell snippet that auto-activates codevector credentials on cd. Add `eval "$(codevector hook bash)"` (or zsh / fish) to your shell\'s rc file.'
19529
+ },
19530
+ args: {
19531
+ shell: {
19532
+ type: "positional",
19533
+ required: false,
19534
+ description: "Target shell: bash, zsh, or fish.",
19535
+ valueHint: "bash|zsh|fish"
19536
+ }
19537
+ },
19538
+ run({ args }) {
19539
+ const shell = resolveShell2(args.shell);
19540
+ process.stdout.write(snippetFor(shell));
19541
+ }
19542
+ });
19543
+ function resolveShell2(raw) {
19544
+ if (!raw) {
19545
+ const detected = detectShell2();
19546
+ if (!detected) {
19547
+ throw new Error(
19548
+ `Could not detect shell from $SHELL. Pass one explicitly: ${SHELLS2.join(", ")}.`
19549
+ );
19550
+ }
19551
+ return detected;
19552
+ }
19553
+ if (!SHELLS2.includes(raw)) {
19554
+ throw new Error(`Invalid shell "${raw}". Use one of: ${SHELLS2.join(", ")}.`);
19555
+ }
19556
+ return raw;
19557
+ }
19558
+ function detectShell2() {
19559
+ const shellPath = process.env.SHELL ?? "";
19560
+ if (shellPath.endsWith("/fish")) return "fish";
19561
+ if (shellPath.endsWith("/zsh")) return "zsh";
19562
+ if (shellPath.endsWith("/bash")) return "bash";
19563
+ return null;
19564
+ }
19565
+ function snippetFor(shell) {
19566
+ switch (shell) {
19567
+ case "bash":
19568
+ return BASH_SNIPPET;
19569
+ case "zsh":
19570
+ return ZSH_SNIPPET;
19571
+ case "fish":
19572
+ return FISH_SNIPPET;
19573
+ }
19574
+ }
19575
+ var BASH_SNIPPET = `# codevector shell hook (bash) \u2014 auto-activates credentials on cd
19576
+ _codevector_on_prompt() {
19577
+ if [ "$PWD" = "\${_CODEVECTOR_LAST_PWD:-}" ]; then return; fi
19578
+ _CODEVECTOR_LAST_PWD="$PWD"
19579
+ local _codevector_out
19580
+ _codevector_out="$(command codevector env --shell bash 2>/dev/null)"
19581
+ if [ -n "$_codevector_out" ]; then eval "$_codevector_out"; fi
19582
+ }
19583
+ case "\${PROMPT_COMMAND:-}" in
19584
+ *_codevector_on_prompt*) ;;
19585
+ "") PROMPT_COMMAND="_codevector_on_prompt" ;;
19586
+ *) PROMPT_COMMAND="_codevector_on_prompt;$PROMPT_COMMAND" ;;
19587
+ esac
19588
+ `;
19589
+ var ZSH_SNIPPET = `# codevector shell hook (zsh) \u2014 auto-activates credentials on cd
19590
+ _codevector_on_chpwd() {
19591
+ local _codevector_out
19592
+ _codevector_out="$(command codevector env --shell zsh 2>/dev/null)"
19593
+ if [ -n "$_codevector_out" ]; then eval "$_codevector_out"; fi
19594
+ }
19595
+ autoload -Uz add-zsh-hook
19596
+ add-zsh-hook chpwd _codevector_on_chpwd
19597
+ # Run once for the current directory at shell startup.
19598
+ _codevector_on_chpwd
19599
+ `;
19600
+ var FISH_SNIPPET = `# codevector shell hook (fish) \u2014 auto-activates credentials on cd
19601
+ function _codevector_on_pwd --on-variable PWD
19602
+ set -l out (command codevector env --shell fish 2>/dev/null)
19603
+ if test -n "$out"
19604
+ eval $out
19605
+ end
19606
+ end
19607
+ # Run once for the current directory at shell startup.
19608
+ _codevector_on_pwd
19609
+ `;
19610
+
18985
19611
  // src/commands/init.ts
18986
- import { existsSync as existsSync8, writeFileSync as writeFileSync6 } from "fs";
18987
19612
  import { execFileSync as execFileSync2 } from "child_process";
18988
- import { join as join8 } from "path";
19613
+ import { existsSync as existsSync9 } from "fs";
19614
+ import { join as join11 } from "path";
18989
19615
  var DEFAULT_TICKET_PATTERN2 = "[A-Z]+-\\d+";
18990
19616
  var initCommand = defineCommand({
18991
19617
  meta: {
18992
19618
  name: "init",
18993
- description: "Write .codevector.json in the current repo."
19619
+ description: "Set this repo up for codevector: write .codevector.json (gateway + project) and optionally configure coding tools."
18994
19620
  },
18995
19621
  args: {
18996
19622
  project: {
@@ -19001,33 +19627,147 @@ var initCommand = defineCommand({
19001
19627
  type: "string",
19002
19628
  description: "Override the ticket-extraction regex."
19003
19629
  },
19630
+ gateway: {
19631
+ type: "string",
19632
+ description: "Gateway URL to pin for this repo (e.g. https://gateway.acme.com)."
19633
+ },
19004
19634
  force: {
19005
19635
  type: "boolean",
19006
- description: "Overwrite an existing .codevector.json."
19636
+ description: "Overwrite an existing .codevector.json without prompting."
19637
+ },
19638
+ "skip-configure": {
19639
+ type: "boolean",
19640
+ description: "Don't run `codevector configure` after writing the file."
19007
19641
  }
19008
19642
  },
19009
- run({ args }) {
19643
+ async run({ args }) {
19010
19644
  const cwd = userCwd();
19011
- const target = join8(cwd, ".codevector.json");
19012
- if (existsSync8(target) && !args.force) {
19013
- throw new Error(`.codevector.json already exists in ${cwd}. Pass --force to overwrite.`);
19014
- }
19015
- const projectName = args.project ?? deriveProjectName(cwd);
19016
- if (!projectName) {
19645
+ const target = join11(cwd, PROJECT_CONFIG_FILENAME);
19646
+ const existing = existsSync9(target) ? readProjectConfigAt(target) : null;
19647
+ const interactive = isInteractive(args);
19648
+ if (existing && !args.force && !interactive) {
19017
19649
  throw new Error(
19018
- "Could not derive projectName from git remote. Pass --project <name> explicitly."
19650
+ `${PROJECT_CONFIG_FILENAME} already exists in ${cwd}. Pass --force to overwrite, or run without flags for the interactive update flow.`
19019
19651
  );
19020
19652
  }
19021
- const ticketPattern = args["ticket-pattern"] ?? DEFAULT_TICKET_PATTERN2;
19022
- writeFileSync6(target, `${JSON.stringify({ projectName, ticketPattern }, null, 2)}
19023
- `, {
19024
- mode: 420
19025
- });
19026
- R2.success(`Wrote ${target}`);
19027
- R2.info(`projectName: ${projectName}`);
19028
- R2.info(`ticketPattern: ${ticketPattern}`);
19653
+ if (interactive) ge("codevector init");
19654
+ if (existing && interactive && !args.force) {
19655
+ R2.info(`Found existing ${PROJECT_CONFIG_FILENAME} \u2014 updating in place.`);
19656
+ }
19657
+ const projectName = await resolveProjectName(args.project, existing, cwd, interactive);
19658
+ const gateway = await resolveGateway(args.gateway, existing, interactive);
19659
+ const ticketPattern = args["ticket-pattern"] ?? existing?.ticketPattern ?? DEFAULT_TICKET_PATTERN2;
19660
+ const next = {
19661
+ ...projectName ? { projectName } : {},
19662
+ ticketPattern,
19663
+ ...gateway ? { gateway } : {},
19664
+ ...existing?.tools ? { tools: existing.tools } : {}
19665
+ };
19666
+ writeProjectConfig(target, next);
19667
+ if (interactive) {
19668
+ R2.success(`Wrote ${target}`);
19669
+ R2.info(`projectName: ${projectName ?? "(unset)"}`);
19670
+ R2.info(`gateway: ${gateway ?? "(unset)"}`);
19671
+ R2.info(`ticketPattern: ${ticketPattern}`);
19672
+ } else {
19673
+ R2.success(`Wrote ${target}`);
19674
+ }
19675
+ if (!args["skip-configure"] && interactive && gateway) {
19676
+ const run = unwrap(
19677
+ await ue({
19678
+ message: "Configure coding tools (Claude Code, Codex, OpenCode) for this repo now?",
19679
+ initialValue: true
19680
+ })
19681
+ );
19682
+ if (run) {
19683
+ await runCommand(configureCommand, { rawArgs: [] });
19684
+ } else {
19685
+ Se("Run `codevector configure` when you're ready.", "Skipped");
19686
+ }
19687
+ }
19688
+ if (interactive) {
19689
+ Se(
19690
+ 'Add `eval "$(codevector hook bash)"` (or zsh / fish) to your shell rc so credentials auto-activate on cd.',
19691
+ "Shell hook"
19692
+ );
19693
+ ye("Done.");
19694
+ }
19029
19695
  }
19030
19696
  });
19697
+ function isInteractive(args) {
19698
+ if (!process.stdout.isTTY) return false;
19699
+ return !args.gateway;
19700
+ }
19701
+ async function resolveProjectName(fromArgs, existing, cwd, interactive) {
19702
+ if (fromArgs) return fromArgs;
19703
+ const derived = deriveProjectName(cwd) ?? existing?.projectName ?? null;
19704
+ if (!interactive) return derived;
19705
+ const initial = derived ?? "";
19706
+ const entered = unwrap(
19707
+ await Pe({
19708
+ message: "Project name (used for x-project attribution header):",
19709
+ placeholder: initial || "my-project",
19710
+ defaultValue: initial
19711
+ })
19712
+ );
19713
+ const trimmed = entered.trim();
19714
+ return trimmed.length > 0 ? trimmed : null;
19715
+ }
19716
+ async function resolveGateway(fromArgs, existing, interactive) {
19717
+ if (fromArgs) return fromArgs;
19718
+ if (!interactive) return existing?.gateway ?? null;
19719
+ const profiles = await readProfiles();
19720
+ const knownUrls = uniqueGatewayUrls(profiles?.profiles ?? {});
19721
+ const initialUrl = existing?.gateway ?? profiles?.profiles[profiles.activeProfile]?.gatewayUrl;
19722
+ const CUSTOM = "__custom__";
19723
+ const options = [
19724
+ ...knownUrls.map((url2) => ({ value: url2, label: url2 })),
19725
+ { value: CUSTOM, label: "Enter a different URL\u2026" }
19726
+ ];
19727
+ if (knownUrls.length === 0) {
19728
+ const entered2 = unwrap(
19729
+ await Pe({
19730
+ message: "Gateway URL (e.g. https://gateway.acme.com):",
19731
+ placeholder: "https://gateway.acme.com",
19732
+ validate: validateUrl
19733
+ })
19734
+ );
19735
+ return entered2.trim();
19736
+ }
19737
+ const picked = unwrap(
19738
+ await xe({
19739
+ message: "Pin a gateway for this repo (auto-activates on cd):",
19740
+ options,
19741
+ ...initialUrl && knownUrls.includes(initialUrl) ? { initialValue: initialUrl } : {}
19742
+ })
19743
+ );
19744
+ if (picked !== CUSTOM) return picked;
19745
+ const entered = unwrap(
19746
+ await Pe({
19747
+ message: "Gateway URL:",
19748
+ placeholder: "https://gateway.acme.com",
19749
+ validate: validateUrl
19750
+ })
19751
+ );
19752
+ return entered.trim();
19753
+ }
19754
+ function uniqueGatewayUrls(profiles) {
19755
+ const seen = /* @__PURE__ */ new Set();
19756
+ for (const p2 of Object.values(profiles)) {
19757
+ seen.add(p2.gatewayUrl);
19758
+ }
19759
+ return [...seen].sort();
19760
+ }
19761
+ function validateUrl(value) {
19762
+ const trimmed = (value ?? "").trim();
19763
+ if (!trimmed) return "Required.";
19764
+ try {
19765
+ new URL(trimmed);
19766
+ } catch {
19767
+ return "Must be a valid URL (e.g. https://gateway.acme.com).";
19768
+ }
19769
+ return void 0;
19770
+ }
19031
19771
  function deriveProjectName(cwd) {
19032
19772
  try {
19033
19773
  const out = execFileSync2("git", ["remote", "get-url", "origin"], {
@@ -19235,8 +19975,8 @@ var modelsCommand = defineCommand({
19235
19975
  });
19236
19976
 
19237
19977
  // src/commands/profile.ts
19238
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
19239
- var WRITERS2 = {
19978
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
19979
+ var WRITERS3 = {
19240
19980
  "claude-code": writeClaudeCodeConfig,
19241
19981
  opencode: writeOpencodeConfig,
19242
19982
  codex: writeCodexConfig
@@ -19333,7 +20073,7 @@ Key: ${maskApiKey(active.apiKey)}`,
19333
20073
  const persisted = [];
19334
20074
  for (const tc of toolConfigs) {
19335
20075
  const tool = tc.tool;
19336
- const writer = WRITERS2[tool];
20076
+ const writer = WRITERS3[tool];
19337
20077
  if (!writer) {
19338
20078
  results.push({
19339
20079
  tool: tc.tool,
@@ -19424,82 +20164,254 @@ Or add it to your shell rc to persist across sessions.`,
19424
20164
  }
19425
20165
  }
19426
20166
  });
19427
- var profileCommand = defineCommand({
20167
+ var profileCommand = defineCommand({
20168
+ meta: {
20169
+ name: "profile",
20170
+ description: "Manage CLI profiles."
20171
+ },
20172
+ subCommands: {
20173
+ list: profileListCommand,
20174
+ switch: profileSwitchCommand
20175
+ }
20176
+ });
20177
+ async function fetchReachableModels(gatewayUrl, apiKey) {
20178
+ const client = gatewayClient(gatewayUrl, apiKey, 1e4);
20179
+ try {
20180
+ const res = await call(parseResponse(client.models.$get()));
20181
+ const models = res.data.filter((m2) => m2.kind === "chat").map((m2) => ({
20182
+ slug: m2.slug,
20183
+ providerKind: m2.providerKind,
20184
+ displayName: m2.displayName,
20185
+ contextWindow: m2.contextWindow,
20186
+ maxOutputTokens: m2.maxOutputTokens,
20187
+ maxInputTokens: m2.maxInputTokens,
20188
+ inputPricePerMtok: m2.inputPricePerMtok,
20189
+ cachedInputPricePerMtok: m2.cachedInputPricePerMtok,
20190
+ cacheWritePricePerMtok: m2.cacheWritePricePerMtok,
20191
+ reasoningPricePerMtok: m2.reasoningPricePerMtok,
20192
+ outputPricePerMtok: m2.outputPricePerMtok,
20193
+ supportsReasoning: m2.supportsReasoning,
20194
+ supportsAttachment: m2.supportsAttachment,
20195
+ supportsToolCall: m2.supportsToolCall,
20196
+ supportsTemperature: m2.supportsTemperature,
20197
+ inputModalities: m2.inputModalities,
20198
+ outputModalities: m2.outputModalities,
20199
+ releaseDate: m2.releaseDate,
20200
+ family: m2.family
20201
+ }));
20202
+ return { ok: true, models };
20203
+ } catch (err) {
20204
+ return { ok: false, reason: err instanceof ApiClientError ? err.message : String(err) };
20205
+ }
20206
+ }
20207
+ function isResponsesCompatibleProviderKind(providerKind) {
20208
+ return providerKind === "openai" || providerKind === "openai_compatible";
20209
+ }
20210
+ function pickCodexFallbackModel(reachable) {
20211
+ const candidates = reachable.filter((m2) => isResponsesCompatibleProviderKind(m2.providerKind));
20212
+ if (candidates.length === 0) {
20213
+ return void 0;
20214
+ }
20215
+ const ranked = readCodexNuxRankedModels();
20216
+ for (const slug of ranked) {
20217
+ const match = candidates.find((m2) => m2.slug === slug);
20218
+ if (match) return match;
20219
+ }
20220
+ return candidates[0];
20221
+ }
20222
+ function readCodexNuxRankedModels() {
20223
+ const path = codexConfigPath();
20224
+ if (!existsSync10(path)) return [];
20225
+ let parsed;
20226
+ try {
20227
+ parsed = parse3(readFileSync10(path, "utf8"));
20228
+ } catch {
20229
+ return [];
20230
+ }
20231
+ if (!isObject5(parsed)) return [];
20232
+ const tui = asObject(parsed.tui);
20233
+ const availability = asObject(tui?.model_availability_nux);
20234
+ if (!availability) return [];
20235
+ return Object.entries(availability).filter((entry) => typeof entry[1] === "number" && Number.isFinite(entry[1])).sort((a, b2) => b2[1] - a[1] || a[0].localeCompare(b2[0])).map(([slug]) => slug);
20236
+ }
20237
+ function isObject5(value) {
20238
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20239
+ }
20240
+ function asObject(value) {
20241
+ return isObject5(value) ? value : void 0;
20242
+ }
20243
+
20244
+ // src/commands/skills.ts
20245
+ import { readFile, writeFile } from "fs/promises";
20246
+ import { resolve as resolve2 } from "path";
20247
+ import { spawn } from "child_process";
20248
+ async function getClient() {
20249
+ const creds = await readCredentials();
20250
+ if (!creds) {
20251
+ R2.warn("Not signed in. Run `codevector auth login` to get started.");
20252
+ process.exitCode = 1;
20253
+ return null;
20254
+ }
20255
+ return gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
20256
+ }
20257
+ var skillsListCommand = defineCommand({
20258
+ meta: {
20259
+ name: "list",
20260
+ description: "List skill packs available on the gateway."
20261
+ },
20262
+ args: {
20263
+ json: {
20264
+ type: "boolean",
20265
+ description: "Emit raw JSON (for scripting)."
20266
+ }
20267
+ },
20268
+ async run({ args }) {
20269
+ const client = await getClient();
20270
+ if (!client) return;
20271
+ const s = args.json ? null : ft();
20272
+ s?.start("Loading skill packs\u2026");
20273
+ try {
20274
+ const res = await call(parseResponse(client["skill-packs"].$get()));
20275
+ s?.stop(`${res.data.length} skill pack${res.data.length === 1 ? "" : "s"}`);
20276
+ if (args.json) {
20277
+ process.stdout.write(`${JSON.stringify(res.data, null, 2)}
20278
+ `);
20279
+ return;
20280
+ }
20281
+ if (res.data.length === 0) {
20282
+ R2.info("No skill packs found. Ask an admin to upload one with `codevector skills upload <name>`.");
20283
+ return;
20284
+ }
20285
+ const headers = ["NAME", "UPDATED"];
20286
+ const cells = res.data.map((p2) => [
20287
+ p2.name,
20288
+ new Date(p2.updatedAt).toLocaleString()
20289
+ ]);
20290
+ Se(renderTable(headers, cells), "Skill packs");
20291
+ } catch (err) {
20292
+ s?.stop("Could not load skill packs");
20293
+ R2.error(err instanceof ApiClientError ? err.message : String(err));
20294
+ process.exitCode = 1;
20295
+ }
20296
+ }
20297
+ });
20298
+ var skillsUploadCommand = defineCommand({
20299
+ meta: {
20300
+ name: "upload",
20301
+ description: "Upload the local skills-lock.json as a named skill pack."
20302
+ },
20303
+ args: {
20304
+ name: {
20305
+ type: "positional",
20306
+ description: "Name for the skill pack.",
20307
+ required: true
20308
+ }
20309
+ },
20310
+ async run({ args }) {
20311
+ const client = await getClient();
20312
+ if (!client) return;
20313
+ const lockfilePath = resolve2("skills-lock.json");
20314
+ let lockfile;
20315
+ try {
20316
+ const raw = await readFile(lockfilePath, "utf-8");
20317
+ lockfile = JSON.parse(raw);
20318
+ } catch {
20319
+ R2.error(`No lockfile found at ${lockfilePath}. Run \`npx skills install\` first.`);
20320
+ process.exitCode = 1;
20321
+ return;
20322
+ }
20323
+ const s = ft();
20324
+ s.start("Uploading skill pack\u2026");
20325
+ try {
20326
+ await call(
20327
+ parseResponse(
20328
+ client["skill-packs"].$post({
20329
+ json: { name: args.name, lockfile }
20330
+ })
20331
+ )
20332
+ );
20333
+ s.stop(`Skill pack "${args.name}" uploaded.`);
20334
+ } catch (err) {
20335
+ s.stop("Upload failed");
20336
+ R2.error(err instanceof ApiClientError ? err.message : String(err));
20337
+ process.exitCode = 1;
20338
+ }
20339
+ }
20340
+ });
20341
+ var skillsSyncCommand = defineCommand({
20342
+ meta: {
20343
+ name: "sync",
20344
+ description: "Fetch a skill pack's lockfile, write skills-lock.json, and run npx skills install."
20345
+ },
20346
+ args: {
20347
+ name: {
20348
+ type: "positional",
20349
+ description: "Name of the skill pack to sync.",
20350
+ required: true
20351
+ }
20352
+ },
20353
+ async run({ args }) {
20354
+ const client = await getClient();
20355
+ if (!client) return;
20356
+ const s = ft();
20357
+ s.start(`Fetching skill pack "${args.name}"\u2026`);
20358
+ let pack;
20359
+ try {
20360
+ pack = await call(
20361
+ parseResponse(
20362
+ client["skill-packs"][":name"].$get({ param: { name: args.name } })
20363
+ )
20364
+ );
20365
+ } catch (err) {
20366
+ s.stop("Fetch failed");
20367
+ R2.error(err instanceof ApiClientError ? err.message : String(err));
20368
+ process.exitCode = 1;
20369
+ return;
20370
+ }
20371
+ s.stop("Fetched");
20372
+ const lockfilePath = resolve2("skills-lock.json");
20373
+ s.start("Writing skills-lock.json\u2026");
20374
+ try {
20375
+ await writeFile(lockfilePath, `${JSON.stringify(pack.lockfile, null, 2)}
20376
+ `, "utf-8");
20377
+ } catch (err) {
20378
+ s.stop("Write failed");
20379
+ R2.error(err instanceof Error ? err.message : String(err));
20380
+ process.exitCode = 1;
20381
+ return;
20382
+ }
20383
+ s.stop("skills-lock.json written");
20384
+ s.start("Running npx skills install\u2026");
20385
+ try {
20386
+ await new Promise((resolve3, reject) => {
20387
+ const child = spawn("npx", ["skills", "experimental_install"], {
20388
+ stdio: "inherit"
20389
+ });
20390
+ child.on("close", (code) => {
20391
+ if (code === 0) resolve3();
20392
+ else reject(new Error(`npx skills experimental_install exited with code ${code}`));
20393
+ });
20394
+ child.on("error", reject);
20395
+ });
20396
+ s.stop("Skills installed");
20397
+ } catch (err) {
20398
+ s.stop("Install failed");
20399
+ R2.error(err instanceof Error ? err.message : String(err));
20400
+ process.exitCode = 1;
20401
+ }
20402
+ }
20403
+ });
20404
+ var skillsCommand = defineCommand({
19428
20405
  meta: {
19429
- name: "profile",
19430
- description: "Manage CLI profiles."
20406
+ name: "skills",
20407
+ description: "Upload and sync skill packs."
19431
20408
  },
19432
20409
  subCommands: {
19433
- list: profileListCommand,
19434
- switch: profileSwitchCommand
20410
+ list: skillsListCommand,
20411
+ upload: skillsUploadCommand,
20412
+ sync: skillsSyncCommand
19435
20413
  }
19436
20414
  });
19437
- async function fetchReachableModels(gatewayUrl, apiKey) {
19438
- const client = gatewayClient(gatewayUrl, apiKey, 1e4);
19439
- try {
19440
- const res = await call(parseResponse(client.models.$get()));
19441
- const models = res.data.filter((m2) => m2.kind === "chat").map((m2) => ({
19442
- slug: m2.slug,
19443
- providerKind: m2.providerKind,
19444
- displayName: m2.displayName,
19445
- contextWindow: m2.contextWindow,
19446
- maxOutputTokens: m2.maxOutputTokens,
19447
- maxInputTokens: m2.maxInputTokens,
19448
- inputPricePerMtok: m2.inputPricePerMtok,
19449
- cachedInputPricePerMtok: m2.cachedInputPricePerMtok,
19450
- cacheWritePricePerMtok: m2.cacheWritePricePerMtok,
19451
- reasoningPricePerMtok: m2.reasoningPricePerMtok,
19452
- outputPricePerMtok: m2.outputPricePerMtok,
19453
- supportsReasoning: m2.supportsReasoning,
19454
- supportsAttachment: m2.supportsAttachment,
19455
- supportsToolCall: m2.supportsToolCall,
19456
- supportsTemperature: m2.supportsTemperature,
19457
- inputModalities: m2.inputModalities,
19458
- outputModalities: m2.outputModalities,
19459
- releaseDate: m2.releaseDate,
19460
- family: m2.family
19461
- }));
19462
- return { ok: true, models };
19463
- } catch (err) {
19464
- return { ok: false, reason: err instanceof ApiClientError ? err.message : String(err) };
19465
- }
19466
- }
19467
- function isResponsesCompatibleProviderKind(providerKind) {
19468
- return providerKind === "openai" || providerKind === "openai_compatible";
19469
- }
19470
- function pickCodexFallbackModel(reachable) {
19471
- const candidates = reachable.filter((m2) => isResponsesCompatibleProviderKind(m2.providerKind));
19472
- if (candidates.length === 0) {
19473
- return void 0;
19474
- }
19475
- const ranked = readCodexNuxRankedModels();
19476
- for (const slug of ranked) {
19477
- const match = candidates.find((m2) => m2.slug === slug);
19478
- if (match) return match;
19479
- }
19480
- return candidates[0];
19481
- }
19482
- function readCodexNuxRankedModels() {
19483
- const path = codexConfigPath();
19484
- if (!existsSync9(path)) return [];
19485
- let parsed;
19486
- try {
19487
- parsed = parse3(readFileSync9(path, "utf8"));
19488
- } catch {
19489
- return [];
19490
- }
19491
- if (!isObject5(parsed)) return [];
19492
- const tui = asObject(parsed.tui);
19493
- const availability = asObject(tui?.model_availability_nux);
19494
- if (!availability) return [];
19495
- return Object.entries(availability).filter((entry) => typeof entry[1] === "number" && Number.isFinite(entry[1])).sort((a, b2) => b2[1] - a[1] || a[0].localeCompare(b2[0])).map(([slug]) => slug);
19496
- }
19497
- function isObject5(value) {
19498
- return typeof value === "object" && value !== null && !Array.isArray(value);
19499
- }
19500
- function asObject(value) {
19501
- return isObject5(value) ? value : void 0;
19502
- }
19503
20415
 
19504
20416
  // src/commands/status.ts
19505
20417
  var statusCommand = defineCommand({
@@ -19538,47 +20450,47 @@ Last saved: ${creds.savedAt}`,
19538
20450
  });
19539
20451
 
19540
20452
  // src/commands/system.ts
19541
- import { existsSync as existsSync11 } from "fs";
20453
+ import { existsSync as existsSync12 } from "fs";
19542
20454
 
19543
20455
  // src/lib/backup.ts
19544
20456
  import {
19545
20457
  copyFileSync as copyFileSync2,
19546
- existsSync as existsSync10,
19547
- mkdirSync as mkdirSync6,
20458
+ existsSync as existsSync11,
20459
+ mkdirSync as mkdirSync7,
19548
20460
  readdirSync,
19549
20461
  statSync as statSync6
19550
20462
  } from "fs";
19551
- import { basename, dirname as dirname6, join as join9 } from "path";
20463
+ import { basename, dirname as dirname7, join as join12 } from "path";
19552
20464
  import { homedir as homedir6 } from "os";
19553
- var BACKUP_ROOT = process.env.CODEVECTOR_BACKUP_ROOT ?? join9(homedir6(), ".codevector", "backups");
20465
+ var BACKUP_ROOT = process.env.CODEVECTOR_BACKUP_ROOT ?? join12(homedir6(), ".codevector", "backups");
19554
20466
  function backupTimestamp(d = /* @__PURE__ */ new Date()) {
19555
20467
  return d.toISOString().replace(/:/g, "-");
19556
20468
  }
19557
20469
  function backupFile(sourcePath, tool, timestamp) {
19558
- if (!existsSync10(sourcePath)) return void 0;
19559
- const destDir = join9(BACKUP_ROOT, timestamp, tool);
19560
- mkdirSync6(destDir, { recursive: true, mode: 448 });
19561
- const dest = join9(destDir, basename(sourcePath));
20470
+ if (!existsSync11(sourcePath)) return void 0;
20471
+ const destDir = join12(BACKUP_ROOT, timestamp, tool);
20472
+ mkdirSync7(destDir, { recursive: true, mode: 448 });
20473
+ const dest = join12(destDir, basename(sourcePath));
19562
20474
  copyFileSync2(sourcePath, dest);
19563
20475
  return dest;
19564
20476
  }
19565
20477
  function listBackupRuns() {
19566
- if (!existsSync10(BACKUP_ROOT)) return [];
20478
+ if (!existsSync11(BACKUP_ROOT)) return [];
19567
20479
  const entries = readdirSync(BACKUP_ROOT, { withFileTypes: true }).filter((e2) => e2.isDirectory()).map((e2) => e2.name).sort().reverse();
19568
20480
  const runs = [];
19569
20481
  for (const ts of entries) {
19570
- const dir = join9(BACKUP_ROOT, ts);
20482
+ const dir = join12(BACKUP_ROOT, ts);
19571
20483
  const tools = readdirSync(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory());
19572
20484
  const collected = [];
19573
20485
  for (const toolDir of tools) {
19574
- const toolPath = join9(dir, toolDir.name);
20486
+ const toolPath = join12(dir, toolDir.name);
19575
20487
  for (const file2 of readdirSync(toolPath, { withFileTypes: true })) {
19576
20488
  if (!file2.isFile()) continue;
19577
20489
  collected.push({
19578
20490
  tool: toolDir.name,
19579
20491
  original: "",
19580
20492
  // resolved by caller per tool — see restoreBackup()
19581
- backup: join9(toolPath, file2.name)
20493
+ backup: join12(toolPath, file2.name)
19582
20494
  });
19583
20495
  }
19584
20496
  }
@@ -19588,10 +20500,10 @@ function listBackupRuns() {
19588
20500
  return runs;
19589
20501
  }
19590
20502
  function restoreBackup(backupPath, originalPath) {
19591
- if (!existsSync10(backupPath)) {
20503
+ if (!existsSync11(backupPath)) {
19592
20504
  throw new Error(`Backup file missing: ${backupPath}`);
19593
20505
  }
19594
- mkdirSync6(dirname6(originalPath), { recursive: true });
20506
+ mkdirSync7(dirname7(originalPath), { recursive: true });
19595
20507
  copyFileSync2(backupPath, originalPath);
19596
20508
  }
19597
20509
  function backupRunMtime(dir) {
@@ -19600,7 +20512,7 @@ function backupRunMtime(dir) {
19600
20512
 
19601
20513
  // src/commands/system.ts
19602
20514
  var TOOLS = ["claude-code", "opencode", "codex"];
19603
- var WRITERS3 = {
20515
+ var WRITERS4 = {
19604
20516
  "claude-code": writeClaudeCodeConfig,
19605
20517
  opencode: writeOpencodeConfig,
19606
20518
  codex: writeCodexConfig
@@ -19647,11 +20559,11 @@ var systemConfigureCommand = defineCommand({
19647
20559
  } else {
19648
20560
  R2.info("No pre-existing user-scope config files to back up.");
19649
20561
  }
19650
- const reachable = await fetchReachableChatModels2(creds);
20562
+ const reachable = await fetchReachableChatModels3(creds);
19651
20563
  const hookPath = installAcceptanceHook();
19652
20564
  const results = [];
19653
20565
  for (const tool of TOOLS) {
19654
- const writer = WRITERS3[tool];
20566
+ const writer = WRITERS4[tool];
19655
20567
  try {
19656
20568
  results.push(
19657
20569
  writer({
@@ -19750,7 +20662,7 @@ var systemRestoreCommand = defineCommand({
19750
20662
  }
19751
20663
  const target = userPathFor(e2.tool);
19752
20664
  plan.push({ tool: e2.tool, backup: e2.backup, target });
19753
- R2.info(` ${e2.tool} \u2192 ${target}${existsSync11(target) ? " (will overwrite)" : " (new)"}`);
20665
+ R2.info(` ${e2.tool} \u2192 ${target}${existsSync12(target) ? " (will overwrite)" : " (new)"}`);
19754
20666
  }
19755
20667
  if (plan.length === 0) {
19756
20668
  ye("Nothing to restore.");
@@ -19786,7 +20698,7 @@ async function confirmRestore() {
19786
20698
  function isTool2(value) {
19787
20699
  return TOOLS.includes(value);
19788
20700
  }
19789
- async function fetchReachableChatModels2(creds) {
20701
+ async function fetchReachableChatModels3(creds) {
19790
20702
  const client = gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
19791
20703
  const s = ft();
19792
20704
  s.start("Loading reachable models\u2026");
@@ -19839,7 +20751,7 @@ import { spawnSync } from "child_process";
19839
20751
  // package.json
19840
20752
  var package_default = {
19841
20753
  name: "@codevector/cli",
19842
- version: "0.4.0",
20754
+ version: "0.5.1",
19843
20755
  description: "CodeVector CLI \u2014 installs and configures first-party coding-tool integrations.",
19844
20756
  license: "UNLICENSED",
19845
20757
  bin: {
@@ -19884,43 +20796,98 @@ var package_default = {
19884
20796
  }
19885
20797
  };
19886
20798
 
19887
- // src/lib/install-pref.ts
19888
- import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync4, writeFileSync as writeFileSync7 } from "fs";
19889
- import { join as join10 } from "path";
19890
- var INSTALL_PREF_FILE = join10(CODEVECTOR_CONFIG_DIR, "install.json");
19891
- var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn"];
19892
- function isPackageManager(v2) {
19893
- return typeof v2 === "string" && PACKAGE_MANAGERS.includes(v2);
19894
- }
19895
- function readInstallPref() {
19896
- if (!existsSync12(INSTALL_PREF_FILE)) return void 0;
20799
+ // src/lib/update-notifier.ts
20800
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
20801
+ import { join as join13 } from "path";
20802
+ var PKG_NAME = "@codevector/cli";
20803
+ var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
20804
+ var CHECK_CACHE_FILE = join13(CODEVECTOR_CONFIG_DIR, "update-check.json");
20805
+ var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
20806
+ var FETCH_TIMEOUT_MS = 2e3;
20807
+ function readCache() {
20808
+ if (!existsSync13(CHECK_CACHE_FILE)) return void 0;
19897
20809
  try {
19898
- const raw = readFileSync10(INSTALL_PREF_FILE, "utf8");
20810
+ const raw = readFileSync11(CHECK_CACHE_FILE, "utf8");
19899
20811
  const parsed = JSON.parse(raw);
19900
- if (!isPackageManager(parsed.packageManager)) return void 0;
19901
- const source = parsed.source === "user" ? "user" : "auto";
20812
+ if (typeof parsed.checkedAt !== "number") return void 0;
19902
20813
  return {
19903
- packageManager: parsed.packageManager,
19904
- source,
19905
- detectedAt: typeof parsed.detectedAt === "string" ? parsed.detectedAt : (/* @__PURE__ */ new Date()).toISOString()
20814
+ latest: typeof parsed.latest === "string" ? parsed.latest : null,
20815
+ checkedAt: parsed.checkedAt
19906
20816
  };
19907
20817
  } catch {
19908
20818
  return void 0;
19909
20819
  }
19910
20820
  }
19911
- function writeInstallPref(pref) {
19912
- mkdirSync7(CODEVECTOR_CONFIG_DIR, { recursive: true, mode: 448 });
19913
- const tmp = `${INSTALL_PREF_FILE}.${process.pid}.tmp`;
19914
- writeFileSync7(tmp, JSON.stringify(pref, null, 2));
19915
- renameSync4(tmp, INSTALL_PREF_FILE);
20821
+ function readCachedLatestVersion() {
20822
+ return readCache()?.latest ?? null;
20823
+ }
20824
+ function writeCache(cache) {
20825
+ try {
20826
+ mkdirSync8(CODEVECTOR_CONFIG_DIR, { recursive: true, mode: 448 });
20827
+ writeFileSync8(CHECK_CACHE_FILE, JSON.stringify(cache));
20828
+ } catch {
20829
+ }
20830
+ }
20831
+ function isNewer(current, latest) {
20832
+ const a = current.split(".").map((p2) => Number.parseInt(p2, 10));
20833
+ const b2 = latest.split(".").map((p2) => Number.parseInt(p2, 10));
20834
+ for (let i = 0; i < Math.max(a.length, b2.length); i++) {
20835
+ const ai = a[i] ?? 0;
20836
+ const bi = b2[i] ?? 0;
20837
+ if (Number.isNaN(ai) || Number.isNaN(bi)) return latest > current;
20838
+ if (bi > ai) return true;
20839
+ if (bi < ai) return false;
20840
+ }
20841
+ return false;
20842
+ }
20843
+ function printUpdateNoticeIfCached(currentVersion) {
20844
+ if (process.env.CODEVECTOR_NO_UPDATE_CHECK === "1") return;
20845
+ try {
20846
+ const cache = readCache();
20847
+ if (!cache || !cache.latest) return;
20848
+ if (isNewer(currentVersion, cache.latest)) {
20849
+ process.stderr.write(
20850
+ `\x1B[33m\u203A\x1B[0m A new version of @codevector/cli is available: ${currentVersion} \u2192 ${cache.latest}. Run \`codevector update\` to install.
20851
+ `
20852
+ );
20853
+ }
20854
+ } catch {
20855
+ }
20856
+ }
20857
+ function scheduleUpdateCheck() {
20858
+ if (process.env.CODEVECTOR_NO_UPDATE_CHECK === "1") return;
20859
+ const cache = readCache();
20860
+ const now = Date.now();
20861
+ if (cache && now - cache.checkedAt < CHECK_TTL_MS) return;
20862
+ void fetchLatestVersion().then((latest) => {
20863
+ writeCache({ latest, checkedAt: Date.now() });
20864
+ }).catch(() => {
20865
+ writeCache({ latest: cache?.latest ?? null, checkedAt: Date.now() });
20866
+ });
20867
+ }
20868
+ async function fetchLatestVersion() {
20869
+ const controller = new AbortController();
20870
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
20871
+ timer.unref?.();
20872
+ try {
20873
+ const res = await fetch(REGISTRY_URL, {
20874
+ signal: controller.signal,
20875
+ headers: { accept: "application/json" }
20876
+ });
20877
+ if (!res.ok) return null;
20878
+ const body = await res.json();
20879
+ return typeof body.version === "string" ? body.version : null;
20880
+ } finally {
20881
+ clearTimeout(timer);
20882
+ }
19916
20883
  }
19917
20884
 
19918
20885
  // src/commands/update.ts
19919
- var PKG_NAME = "@codevector/cli";
20886
+ var PKG_NAME2 = "@codevector/cli";
19920
20887
  var updateCommand = defineCommand({
19921
20888
  meta: {
19922
20889
  name: "update",
19923
- description: `Update ${PKG_NAME} to the latest version on npm. Uses the package manager that installed it (auto-detected on install) unless --with overrides.`
20890
+ description: `Update ${PKG_NAME2} to the latest version on npm. Uses the package manager that installed it (auto-detected on install) unless --with overrides.`
19924
20891
  },
19925
20892
  args: {
19926
20893
  with: {
@@ -19946,10 +20913,35 @@ var updateCommand = defineCommand({
19946
20913
  `${command.cmd} exited with status ${result.status}. The new version was not installed.`
19947
20914
  );
19948
20915
  }
19949
- R2.success("Update complete. Run `codevector --version` to confirm.");
20916
+ verifyOnPathVersion(package_default.version, manager);
19950
20917
  ye("Done.");
19951
20918
  }
19952
20919
  });
20920
+ function verifyOnPathVersion(previousVersion, manager) {
20921
+ const after = readOnPathVersion();
20922
+ if (!after) {
20923
+ R2.success("Update complete. Run `codevector --version` to confirm.");
20924
+ return;
20925
+ }
20926
+ if (after !== previousVersion) {
20927
+ R2.success(`Update complete: ${previousVersion} \u2192 ${after}.`);
20928
+ return;
20929
+ }
20930
+ const latest = readCachedLatestVersion();
20931
+ if (latest && isNewer(previousVersion, latest)) {
20932
+ R2.warn(
20933
+ `The \`codevector\` on your PATH is still ${previousVersion}, but ${latest} is available. ${manager} likely installed into a different global location than the binary on your PATH. Try \`codevector update --with <the manager that owns the binary on your PATH>\`, or reinstall manually.`
20934
+ );
20935
+ return;
20936
+ }
20937
+ R2.success(`Already on the latest version (${previousVersion}).`);
20938
+ }
20939
+ function readOnPathVersion() {
20940
+ const probe = spawnSync("codevector", ["version"], { encoding: "utf8" });
20941
+ if (probe.error || probe.status !== 0) return void 0;
20942
+ const out = probe.stdout?.trim();
20943
+ return out && /^\d+\.\d+\.\d+/.test(out) ? out : void 0;
20944
+ }
19953
20945
  async function resolvePackageManager(raw) {
19954
20946
  if (raw) {
19955
20947
  const pm = parsePackageManager(raw);
@@ -19959,10 +20951,10 @@ async function resolvePackageManager(raw) {
19959
20951
  writeInstallPref({ packageManager: pm, source: "user", detectedAt: (/* @__PURE__ */ new Date()).toISOString() });
19960
20952
  return pm;
19961
20953
  }
19962
- const pref = readInstallPref();
19963
- if (pref) return pref.packageManager;
19964
- R2.info(
19965
- `Couldn't detect which package manager installed ${PKG_NAME} (no record at ${INSTALL_PREF_FILE}).`
20954
+ const resolved = resolveInstallManager();
20955
+ if (resolved.packageManager) return resolved.packageManager;
20956
+ R2.warn(
20957
+ `Couldn't determine which package manager installed ${PKG_NAME2} (no record at ${INSTALL_PREF_FILE}, and the install path didn't reveal it). Picking the wrong one installs into a location that isn't on your PATH.`
19966
20958
  );
19967
20959
  const picked = unwrap(
19968
20960
  await xe({
@@ -19993,7 +20985,7 @@ function parsePackageManager(value) {
19993
20985
  }
19994
20986
  }
19995
20987
  function installCommand(manager) {
19996
- const target = `${PKG_NAME}@latest`;
20988
+ const target = `${PKG_NAME2}@latest`;
19997
20989
  switch (manager) {
19998
20990
  case "npm":
19999
20991
  return { cmd: "npm", args: ["install", "-g", target] };
@@ -20102,89 +21094,6 @@ var versionCommand = defineCommand({
20102
21094
  }
20103
21095
  });
20104
21096
 
20105
- // src/lib/update-notifier.ts
20106
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
20107
- import { join as join11 } from "path";
20108
- var PKG_NAME2 = "@codevector/cli";
20109
- var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME2}/latest`;
20110
- var CHECK_CACHE_FILE = join11(CODEVECTOR_CONFIG_DIR, "update-check.json");
20111
- var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
20112
- var FETCH_TIMEOUT_MS = 2e3;
20113
- function readCache() {
20114
- if (!existsSync13(CHECK_CACHE_FILE)) return void 0;
20115
- try {
20116
- const raw = readFileSync11(CHECK_CACHE_FILE, "utf8");
20117
- const parsed = JSON.parse(raw);
20118
- if (typeof parsed.checkedAt !== "number") return void 0;
20119
- return {
20120
- latest: typeof parsed.latest === "string" ? parsed.latest : null,
20121
- checkedAt: parsed.checkedAt
20122
- };
20123
- } catch {
20124
- return void 0;
20125
- }
20126
- }
20127
- function writeCache(cache) {
20128
- try {
20129
- mkdirSync8(CODEVECTOR_CONFIG_DIR, { recursive: true, mode: 448 });
20130
- writeFileSync8(CHECK_CACHE_FILE, JSON.stringify(cache));
20131
- } catch {
20132
- }
20133
- }
20134
- function isNewer(current, latest) {
20135
- const a = current.split(".").map((p2) => Number.parseInt(p2, 10));
20136
- const b2 = latest.split(".").map((p2) => Number.parseInt(p2, 10));
20137
- for (let i = 0; i < Math.max(a.length, b2.length); i++) {
20138
- const ai = a[i] ?? 0;
20139
- const bi = b2[i] ?? 0;
20140
- if (Number.isNaN(ai) || Number.isNaN(bi)) return latest > current;
20141
- if (bi > ai) return true;
20142
- if (bi < ai) return false;
20143
- }
20144
- return false;
20145
- }
20146
- function printUpdateNoticeIfCached(currentVersion) {
20147
- if (process.env.CODEVECTOR_NO_UPDATE_CHECK === "1") return;
20148
- try {
20149
- const cache = readCache();
20150
- if (!cache || !cache.latest) return;
20151
- if (isNewer(currentVersion, cache.latest)) {
20152
- process.stderr.write(
20153
- `\x1B[33m\u203A\x1B[0m A new version of @codevector/cli is available: ${currentVersion} \u2192 ${cache.latest}. Run \`codevector update\` to install.
20154
- `
20155
- );
20156
- }
20157
- } catch {
20158
- }
20159
- }
20160
- function scheduleUpdateCheck() {
20161
- if (process.env.CODEVECTOR_NO_UPDATE_CHECK === "1") return;
20162
- const cache = readCache();
20163
- const now = Date.now();
20164
- if (cache && now - cache.checkedAt < CHECK_TTL_MS) return;
20165
- void fetchLatestVersion().then((latest) => {
20166
- writeCache({ latest, checkedAt: Date.now() });
20167
- }).catch(() => {
20168
- writeCache({ latest: cache?.latest ?? null, checkedAt: Date.now() });
20169
- });
20170
- }
20171
- async function fetchLatestVersion() {
20172
- const controller = new AbortController();
20173
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
20174
- timer.unref?.();
20175
- try {
20176
- const res = await fetch(REGISTRY_URL, {
20177
- signal: controller.signal,
20178
- headers: { accept: "application/json" }
20179
- });
20180
- if (!res.ok) return null;
20181
- const body = await res.json();
20182
- return typeof body.version === "string" ? body.version : null;
20183
- } finally {
20184
- clearTimeout(timer);
20185
- }
20186
- }
20187
-
20188
21097
  // src/index.ts
20189
21098
  var main = defineCommand({
20190
21099
  meta: {
@@ -20194,13 +21103,17 @@ var main = defineCommand({
20194
21103
  },
20195
21104
  subCommands: {
20196
21105
  auth: authCommand,
21106
+ config: configCommand,
20197
21107
  configure: configureCommand,
20198
21108
  init: initCommand,
20199
21109
  doctor: doctorCommand,
21110
+ env: envCommand,
21111
+ hook: hookCommand,
20200
21112
  status: statusCommand,
20201
21113
  system: systemCommand,
20202
21114
  models: modelsCommand,
20203
21115
  profile: profileCommand,
21116
+ skills: skillsCommand,
20204
21117
  update: updateCommand,
20205
21118
  usage: usageCommand,
20206
21119
  version: versionCommand