@codevector/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
@@ -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");
@@ -18918,6 +19158,7 @@ var doctorCommand = defineCommand({
18918
19158
  checks.push({ level: "fail", label: "gateway /me", detail: message });
18919
19159
  }
18920
19160
  checks.push(inspectClaudeSettings());
19161
+ checks.push(...inspectManifestDrift());
18921
19162
  if (!existsSync7(ACCEPTANCE_HOOK_FILE)) {
18922
19163
  checks.push({
18923
19164
  level: "warn",
@@ -18967,6 +19208,80 @@ function inspectClaudeSettings() {
18967
19208
  detail: "ANTHROPIC_BASE_URL not set at any scope \u2014 run `codevector configure claude-code`"
18968
19209
  };
18969
19210
  }
19211
+ function inspectManifestDrift() {
19212
+ const found = readProjectConfig(userCwd());
19213
+ if (!found) return [];
19214
+ const { config: config2 } = found;
19215
+ const tools = config2.tools ?? [];
19216
+ if (tools.length === 0) return [];
19217
+ const gateway = config2.gateway?.replace(/\/$/, "") ?? null;
19218
+ if (!gateway) {
19219
+ return [
19220
+ {
19221
+ level: "warn",
19222
+ label: "manifest drift",
19223
+ detail: ".codevector.json lists tools but has no `gateway` \u2014 run `codevector init`"
19224
+ }
19225
+ ];
19226
+ }
19227
+ const checks = [];
19228
+ for (const tool of tools) {
19229
+ const path = manifestToolPath(tool.tool, tool.scope);
19230
+ if (!path) {
19231
+ checks.push({
19232
+ level: "warn",
19233
+ label: `manifest drift: ${tool.tool}`,
19234
+ detail: `unknown tool "${tool.tool}"`
19235
+ });
19236
+ continue;
19237
+ }
19238
+ if (!existsSync7(path)) {
19239
+ checks.push({
19240
+ level: "fail",
19241
+ label: `manifest drift: ${tool.tool}`,
19242
+ detail: `${path} missing \u2014 run \`codevector config sync\``
19243
+ });
19244
+ continue;
19245
+ }
19246
+ let raw;
19247
+ try {
19248
+ raw = readFileSync8(path, "utf8");
19249
+ } catch (err) {
19250
+ checks.push({
19251
+ level: "fail",
19252
+ label: `manifest drift: ${tool.tool}`,
19253
+ detail: `cannot read ${path}: ${err instanceof Error ? err.message : String(err)}`
19254
+ });
19255
+ continue;
19256
+ }
19257
+ if (!raw.includes(gateway)) {
19258
+ checks.push({
19259
+ level: "fail",
19260
+ label: `manifest drift: ${tool.tool}`,
19261
+ detail: `${path} does not reference ${gateway} \u2014 run \`codevector config sync\``
19262
+ });
19263
+ continue;
19264
+ }
19265
+ checks.push({
19266
+ level: "ok",
19267
+ label: `manifest: ${tool.tool}`,
19268
+ detail: `[${tool.scope}] ${path}`
19269
+ });
19270
+ }
19271
+ return checks;
19272
+ }
19273
+ function manifestToolPath(tool, scope) {
19274
+ switch (tool) {
19275
+ case "claude-code":
19276
+ return claudeSettingsPath(scope);
19277
+ case "opencode":
19278
+ return opencodeSettingsPath(scope);
19279
+ case "codex":
19280
+ return codexConfigPath(scope);
19281
+ default:
19282
+ return null;
19283
+ }
19284
+ }
18970
19285
  function emit(checks) {
18971
19286
  Se(
18972
19287
  checks.map((c) => {
@@ -18982,15 +19297,244 @@ function emit(checks) {
18982
19297
  }
18983
19298
  }
18984
19299
 
19300
+ // src/commands/env.ts
19301
+ var SHELLS = ["bash", "zsh", "fish"];
19302
+ var envCommand = defineCommand({
19303
+ meta: {
19304
+ name: "env",
19305
+ 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`)."
19306
+ },
19307
+ args: {
19308
+ shell: {
19309
+ type: "string",
19310
+ description: "Output dialect: bash, zsh, or fish.",
19311
+ valueHint: "bash|zsh|fish"
19312
+ }
19313
+ },
19314
+ async run({ args }) {
19315
+ const shell = resolveShell(args.shell);
19316
+ const cwd = userCwd();
19317
+ const previousDir = process.env.CODEVECTOR_ACTIVE_DIR ?? null;
19318
+ const found = readProjectConfig(cwd);
19319
+ const repoDir = found?.path ? dirOf(found.path) : null;
19320
+ const gateway = found?.config.gateway ?? null;
19321
+ const projectName = found?.config.projectName ?? null;
19322
+ const lines = [];
19323
+ if (!gateway || !repoDir) {
19324
+ if (previousDir) emitDeactivate(lines, shell, previousDir);
19325
+ process.stdout.write(lines.join("\n") + (lines.length > 0 ? "\n" : ""));
19326
+ return;
19327
+ }
19328
+ if (previousDir === repoDir) {
19329
+ return;
19330
+ }
19331
+ const profiles = await readProfiles();
19332
+ const match = pickProfileForGateway(
19333
+ profiles?.profiles ?? {},
19334
+ profiles?.activeProfile ?? null,
19335
+ gateway
19336
+ );
19337
+ if (!match) {
19338
+ if (previousDir) emitDeactivate(lines, shell, previousDir);
19339
+ emitEcho(
19340
+ lines,
19341
+ shell,
19342
+ `codevector: no matching profile for gateway ${gateway}. Run \`codevector auth login --gateway ${gateway}\`.`
19343
+ );
19344
+ process.stdout.write(lines.join("\n") + "\n");
19345
+ return;
19346
+ }
19347
+ const { name: profileName, profile } = match;
19348
+ const headers = buildAnthropicCustomHeaders(projectName);
19349
+ emitExport(lines, shell, "ANTHROPIC_BASE_URL", `${trimRightSlash6(profile.gatewayUrl)}/gateway/anthropic`);
19350
+ emitExport(lines, shell, "ANTHROPIC_API_KEY", profile.apiKey);
19351
+ if (headers) emitExport(lines, shell, "ANTHROPIC_CUSTOM_HEADERS", headers);
19352
+ emitExport(lines, shell, "OPENAI_BASE_URL", `${trimRightSlash6(profile.gatewayUrl)}/gateway/openai/v1`);
19353
+ emitExport(lines, shell, "OPENAI_API_KEY", profile.apiKey);
19354
+ emitExport(lines, shell, "CODEVECTOR_ACTIVE_DIR", repoDir);
19355
+ emitExport(lines, shell, "CODEVECTOR_ACTIVE_PROFILE", profileName);
19356
+ const projectSuffix = projectName ? ` (project: ${projectName})` : "";
19357
+ emitEcho(
19358
+ lines,
19359
+ shell,
19360
+ `codevector: ${profileName} -> ${trimRightSlash6(profile.gatewayUrl)}${projectSuffix}`
19361
+ );
19362
+ process.stdout.write(lines.join("\n") + "\n");
19363
+ }
19364
+ });
19365
+ function resolveShell(raw) {
19366
+ if (!raw) return detectShell();
19367
+ if (!SHELLS.includes(raw)) {
19368
+ throw new Error(`Invalid --shell "${raw}". Use one of: ${SHELLS.join(", ")}.`);
19369
+ }
19370
+ return raw;
19371
+ }
19372
+ function detectShell() {
19373
+ const shellPath = process.env.SHELL ?? "";
19374
+ if (shellPath.endsWith("/fish")) return "fish";
19375
+ if (shellPath.endsWith("/zsh")) return "zsh";
19376
+ return "bash";
19377
+ }
19378
+ function emitExport(lines, shell, name, value) {
19379
+ const quoted = shellQuote(value);
19380
+ if (shell === "fish") {
19381
+ lines.push(`set -gx ${name} ${quoted}`);
19382
+ } else {
19383
+ lines.push(`export ${name}=${quoted}`);
19384
+ }
19385
+ }
19386
+ function emitUnset(lines, shell, name) {
19387
+ if (shell === "fish") {
19388
+ lines.push(`set -e ${name}`);
19389
+ } else {
19390
+ lines.push(`unset ${name}`);
19391
+ }
19392
+ }
19393
+ function emitEcho(lines, _shell, message) {
19394
+ lines.push(`echo ${shellQuote(message)} 1>&2`);
19395
+ }
19396
+ function emitDeactivate(lines, shell, previousDir) {
19397
+ for (const name of [
19398
+ "ANTHROPIC_BASE_URL",
19399
+ "ANTHROPIC_API_KEY",
19400
+ "ANTHROPIC_CUSTOM_HEADERS",
19401
+ "OPENAI_BASE_URL",
19402
+ "OPENAI_API_KEY",
19403
+ "CODEVECTOR_ACTIVE_DIR",
19404
+ "CODEVECTOR_ACTIVE_PROFILE"
19405
+ ]) {
19406
+ emitUnset(lines, shell, name);
19407
+ }
19408
+ emitEcho(lines, shell, `codevector: left ${previousDir}, credentials cleared.`);
19409
+ }
19410
+ function shellQuote(value) {
19411
+ return `'${value.replace(/'/g, `'\\''`)}'`;
19412
+ }
19413
+ function trimRightSlash6(url2) {
19414
+ return url2.endsWith("/") ? url2.slice(0, -1) : url2;
19415
+ }
19416
+ function dirOf(filePath) {
19417
+ const idx = filePath.lastIndexOf("/");
19418
+ return idx === -1 ? filePath : filePath.slice(0, idx);
19419
+ }
19420
+ function buildAnthropicCustomHeaders(projectName) {
19421
+ if (!projectName) return null;
19422
+ const safe = projectName.replace(/[\r\n]/g, "").trim();
19423
+ if (!safe) return null;
19424
+ return `x-project: ${safe}`;
19425
+ }
19426
+ function pickProfileForGateway(profiles, activeProfile, gateway) {
19427
+ const target = trimRightSlash6(gateway);
19428
+ const matches = Object.entries(profiles).filter(
19429
+ ([, p2]) => trimRightSlash6(p2.gatewayUrl) === target
19430
+ );
19431
+ if (matches.length === 0) return null;
19432
+ if (activeProfile) {
19433
+ const active = matches.find(([name]) => name === activeProfile);
19434
+ if (active) return { name: active[0], profile: active[1] };
19435
+ }
19436
+ const sorted = [...matches].sort((a, b2) => a[0].localeCompare(b2[0]));
19437
+ const picked = sorted[0];
19438
+ return { name: picked[0], profile: picked[1] };
19439
+ }
19440
+
19441
+ // src/commands/hook.ts
19442
+ var SHELLS2 = ["bash", "zsh", "fish"];
19443
+ var hookCommand = defineCommand({
19444
+ meta: {
19445
+ name: "hook",
19446
+ 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.'
19447
+ },
19448
+ args: {
19449
+ shell: {
19450
+ type: "positional",
19451
+ required: false,
19452
+ description: "Target shell: bash, zsh, or fish.",
19453
+ valueHint: "bash|zsh|fish"
19454
+ }
19455
+ },
19456
+ run({ args }) {
19457
+ const shell = resolveShell2(args.shell);
19458
+ process.stdout.write(snippetFor(shell));
19459
+ }
19460
+ });
19461
+ function resolveShell2(raw) {
19462
+ if (!raw) {
19463
+ const detected = detectShell2();
19464
+ if (!detected) {
19465
+ throw new Error(
19466
+ `Could not detect shell from $SHELL. Pass one explicitly: ${SHELLS2.join(", ")}.`
19467
+ );
19468
+ }
19469
+ return detected;
19470
+ }
19471
+ if (!SHELLS2.includes(raw)) {
19472
+ throw new Error(`Invalid shell "${raw}". Use one of: ${SHELLS2.join(", ")}.`);
19473
+ }
19474
+ return raw;
19475
+ }
19476
+ function detectShell2() {
19477
+ const shellPath = process.env.SHELL ?? "";
19478
+ if (shellPath.endsWith("/fish")) return "fish";
19479
+ if (shellPath.endsWith("/zsh")) return "zsh";
19480
+ if (shellPath.endsWith("/bash")) return "bash";
19481
+ return null;
19482
+ }
19483
+ function snippetFor(shell) {
19484
+ switch (shell) {
19485
+ case "bash":
19486
+ return BASH_SNIPPET;
19487
+ case "zsh":
19488
+ return ZSH_SNIPPET;
19489
+ case "fish":
19490
+ return FISH_SNIPPET;
19491
+ }
19492
+ }
19493
+ var BASH_SNIPPET = `# codevector shell hook (bash) \u2014 auto-activates credentials on cd
19494
+ _codevector_on_prompt() {
19495
+ if [ "$PWD" = "\${_CODEVECTOR_LAST_PWD:-}" ]; then return; fi
19496
+ _CODEVECTOR_LAST_PWD="$PWD"
19497
+ local _codevector_out
19498
+ _codevector_out="$(command codevector env --shell bash 2>/dev/null)"
19499
+ if [ -n "$_codevector_out" ]; then eval "$_codevector_out"; fi
19500
+ }
19501
+ case "\${PROMPT_COMMAND:-}" in
19502
+ *_codevector_on_prompt*) ;;
19503
+ "") PROMPT_COMMAND="_codevector_on_prompt" ;;
19504
+ *) PROMPT_COMMAND="_codevector_on_prompt;$PROMPT_COMMAND" ;;
19505
+ esac
19506
+ `;
19507
+ var ZSH_SNIPPET = `# codevector shell hook (zsh) \u2014 auto-activates credentials on cd
19508
+ _codevector_on_chpwd() {
19509
+ local _codevector_out
19510
+ _codevector_out="$(command codevector env --shell zsh 2>/dev/null)"
19511
+ if [ -n "$_codevector_out" ]; then eval "$_codevector_out"; fi
19512
+ }
19513
+ autoload -Uz add-zsh-hook
19514
+ add-zsh-hook chpwd _codevector_on_chpwd
19515
+ # Run once for the current directory at shell startup.
19516
+ _codevector_on_chpwd
19517
+ `;
19518
+ var FISH_SNIPPET = `# codevector shell hook (fish) \u2014 auto-activates credentials on cd
19519
+ function _codevector_on_pwd --on-variable PWD
19520
+ set -l out (command codevector env --shell fish 2>/dev/null)
19521
+ if test -n "$out"
19522
+ eval $out
19523
+ end
19524
+ end
19525
+ # Run once for the current directory at shell startup.
19526
+ _codevector_on_pwd
19527
+ `;
19528
+
18985
19529
  // src/commands/init.ts
18986
- import { existsSync as existsSync8, writeFileSync as writeFileSync6 } from "fs";
18987
19530
  import { execFileSync as execFileSync2 } from "child_process";
18988
- import { join as join8 } from "path";
19531
+ import { existsSync as existsSync8 } from "fs";
19532
+ import { join as join10 } from "path";
18989
19533
  var DEFAULT_TICKET_PATTERN2 = "[A-Z]+-\\d+";
18990
19534
  var initCommand = defineCommand({
18991
19535
  meta: {
18992
19536
  name: "init",
18993
- description: "Write .codevector.json in the current repo."
19537
+ description: "Set this repo up for codevector: write .codevector.json (gateway + project) and optionally configure coding tools."
18994
19538
  },
18995
19539
  args: {
18996
19540
  project: {
@@ -19001,33 +19545,147 @@ var initCommand = defineCommand({
19001
19545
  type: "string",
19002
19546
  description: "Override the ticket-extraction regex."
19003
19547
  },
19548
+ gateway: {
19549
+ type: "string",
19550
+ description: "Gateway URL to pin for this repo (e.g. https://gateway.acme.com)."
19551
+ },
19004
19552
  force: {
19005
19553
  type: "boolean",
19006
- description: "Overwrite an existing .codevector.json."
19554
+ description: "Overwrite an existing .codevector.json without prompting."
19555
+ },
19556
+ "skip-configure": {
19557
+ type: "boolean",
19558
+ description: "Don't run `codevector configure` after writing the file."
19007
19559
  }
19008
19560
  },
19009
- run({ args }) {
19561
+ async run({ args }) {
19010
19562
  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) {
19563
+ const target = join10(cwd, PROJECT_CONFIG_FILENAME);
19564
+ const existing = existsSync8(target) ? readProjectConfigAt(target) : null;
19565
+ const interactive = isInteractive(args);
19566
+ if (existing && !args.force && !interactive) {
19017
19567
  throw new Error(
19018
- "Could not derive projectName from git remote. Pass --project <name> explicitly."
19568
+ `${PROJECT_CONFIG_FILENAME} already exists in ${cwd}. Pass --force to overwrite, or run without flags for the interactive update flow.`
19019
19569
  );
19020
19570
  }
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}`);
19571
+ if (interactive) ge("codevector init");
19572
+ if (existing && interactive && !args.force) {
19573
+ R2.info(`Found existing ${PROJECT_CONFIG_FILENAME} \u2014 updating in place.`);
19574
+ }
19575
+ const projectName = await resolveProjectName(args.project, existing, cwd, interactive);
19576
+ const gateway = await resolveGateway(args.gateway, existing, interactive);
19577
+ const ticketPattern = args["ticket-pattern"] ?? existing?.ticketPattern ?? DEFAULT_TICKET_PATTERN2;
19578
+ const next = {
19579
+ ...projectName ? { projectName } : {},
19580
+ ticketPattern,
19581
+ ...gateway ? { gateway } : {},
19582
+ ...existing?.tools ? { tools: existing.tools } : {}
19583
+ };
19584
+ writeProjectConfig(target, next);
19585
+ if (interactive) {
19586
+ R2.success(`Wrote ${target}`);
19587
+ R2.info(`projectName: ${projectName ?? "(unset)"}`);
19588
+ R2.info(`gateway: ${gateway ?? "(unset)"}`);
19589
+ R2.info(`ticketPattern: ${ticketPattern}`);
19590
+ } else {
19591
+ R2.success(`Wrote ${target}`);
19592
+ }
19593
+ if (!args["skip-configure"] && interactive && gateway) {
19594
+ const run = unwrap(
19595
+ await ue({
19596
+ message: "Configure coding tools (Claude Code, Codex, OpenCode) for this repo now?",
19597
+ initialValue: true
19598
+ })
19599
+ );
19600
+ if (run) {
19601
+ await runCommand(configureCommand, { rawArgs: [] });
19602
+ } else {
19603
+ Se("Run `codevector configure` when you're ready.", "Skipped");
19604
+ }
19605
+ }
19606
+ if (interactive) {
19607
+ Se(
19608
+ 'Add `eval "$(codevector hook bash)"` (or zsh / fish) to your shell rc so credentials auto-activate on cd.',
19609
+ "Shell hook"
19610
+ );
19611
+ ye("Done.");
19612
+ }
19029
19613
  }
19030
19614
  });
19615
+ function isInteractive(args) {
19616
+ if (!process.stdout.isTTY) return false;
19617
+ return !args.gateway;
19618
+ }
19619
+ async function resolveProjectName(fromArgs, existing, cwd, interactive) {
19620
+ if (fromArgs) return fromArgs;
19621
+ const derived = deriveProjectName(cwd) ?? existing?.projectName ?? null;
19622
+ if (!interactive) return derived;
19623
+ const initial = derived ?? "";
19624
+ const entered = unwrap(
19625
+ await Pe({
19626
+ message: "Project name (used for x-project attribution header):",
19627
+ placeholder: initial || "my-project",
19628
+ defaultValue: initial
19629
+ })
19630
+ );
19631
+ const trimmed = entered.trim();
19632
+ return trimmed.length > 0 ? trimmed : null;
19633
+ }
19634
+ async function resolveGateway(fromArgs, existing, interactive) {
19635
+ if (fromArgs) return fromArgs;
19636
+ if (!interactive) return existing?.gateway ?? null;
19637
+ const profiles = await readProfiles();
19638
+ const knownUrls = uniqueGatewayUrls(profiles?.profiles ?? {});
19639
+ const initialUrl = existing?.gateway ?? profiles?.profiles[profiles.activeProfile]?.gatewayUrl;
19640
+ const CUSTOM = "__custom__";
19641
+ const options = [
19642
+ ...knownUrls.map((url2) => ({ value: url2, label: url2 })),
19643
+ { value: CUSTOM, label: "Enter a different URL\u2026" }
19644
+ ];
19645
+ if (knownUrls.length === 0) {
19646
+ const entered2 = unwrap(
19647
+ await Pe({
19648
+ message: "Gateway URL (e.g. https://gateway.acme.com):",
19649
+ placeholder: "https://gateway.acme.com",
19650
+ validate: validateUrl
19651
+ })
19652
+ );
19653
+ return entered2.trim();
19654
+ }
19655
+ const picked = unwrap(
19656
+ await xe({
19657
+ message: "Pin a gateway for this repo (auto-activates on cd):",
19658
+ options,
19659
+ ...initialUrl && knownUrls.includes(initialUrl) ? { initialValue: initialUrl } : {}
19660
+ })
19661
+ );
19662
+ if (picked !== CUSTOM) return picked;
19663
+ const entered = unwrap(
19664
+ await Pe({
19665
+ message: "Gateway URL:",
19666
+ placeholder: "https://gateway.acme.com",
19667
+ validate: validateUrl
19668
+ })
19669
+ );
19670
+ return entered.trim();
19671
+ }
19672
+ function uniqueGatewayUrls(profiles) {
19673
+ const seen = /* @__PURE__ */ new Set();
19674
+ for (const p2 of Object.values(profiles)) {
19675
+ seen.add(p2.gatewayUrl);
19676
+ }
19677
+ return [...seen].sort();
19678
+ }
19679
+ function validateUrl(value) {
19680
+ const trimmed = (value ?? "").trim();
19681
+ if (!trimmed) return "Required.";
19682
+ try {
19683
+ new URL(trimmed);
19684
+ } catch {
19685
+ return "Must be a valid URL (e.g. https://gateway.acme.com).";
19686
+ }
19687
+ return void 0;
19688
+ }
19031
19689
  function deriveProjectName(cwd) {
19032
19690
  try {
19033
19691
  const out = execFileSync2("git", ["remote", "get-url", "origin"], {
@@ -19236,7 +19894,7 @@ var modelsCommand = defineCommand({
19236
19894
 
19237
19895
  // src/commands/profile.ts
19238
19896
  import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
19239
- var WRITERS2 = {
19897
+ var WRITERS3 = {
19240
19898
  "claude-code": writeClaudeCodeConfig,
19241
19899
  opencode: writeOpencodeConfig,
19242
19900
  codex: writeCodexConfig
@@ -19333,7 +19991,7 @@ Key: ${maskApiKey(active.apiKey)}`,
19333
19991
  const persisted = [];
19334
19992
  for (const tc of toolConfigs) {
19335
19993
  const tool = tc.tool;
19336
- const writer = WRITERS2[tool];
19994
+ const writer = WRITERS3[tool];
19337
19995
  if (!writer) {
19338
19996
  results.push({
19339
19997
  tool: tc.tool,
@@ -19501,6 +20159,178 @@ function asObject(value) {
19501
20159
  return isObject5(value) ? value : void 0;
19502
20160
  }
19503
20161
 
20162
+ // src/commands/skills.ts
20163
+ import { readFile, writeFile } from "fs/promises";
20164
+ import { resolve as resolve2 } from "path";
20165
+ import { spawn } from "child_process";
20166
+ async function getClient() {
20167
+ const creds = await readCredentials();
20168
+ if (!creds) {
20169
+ R2.warn("Not signed in. Run `codevector auth login` to get started.");
20170
+ process.exitCode = 1;
20171
+ return null;
20172
+ }
20173
+ return gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
20174
+ }
20175
+ var skillsListCommand = defineCommand({
20176
+ meta: {
20177
+ name: "list",
20178
+ description: "List skill packs available on the gateway."
20179
+ },
20180
+ args: {
20181
+ json: {
20182
+ type: "boolean",
20183
+ description: "Emit raw JSON (for scripting)."
20184
+ }
20185
+ },
20186
+ async run({ args }) {
20187
+ const client = await getClient();
20188
+ if (!client) return;
20189
+ const s = args.json ? null : ft();
20190
+ s?.start("Loading skill packs\u2026");
20191
+ try {
20192
+ const res = await call(parseResponse(client["skill-packs"].$get()));
20193
+ s?.stop(`${res.data.length} skill pack${res.data.length === 1 ? "" : "s"}`);
20194
+ if (args.json) {
20195
+ process.stdout.write(`${JSON.stringify(res.data, null, 2)}
20196
+ `);
20197
+ return;
20198
+ }
20199
+ if (res.data.length === 0) {
20200
+ R2.info("No skill packs found. Ask an admin to upload one with `codevector skills upload <name>`.");
20201
+ return;
20202
+ }
20203
+ const headers = ["NAME", "UPDATED"];
20204
+ const cells = res.data.map((p2) => [
20205
+ p2.name,
20206
+ new Date(p2.updatedAt).toLocaleString()
20207
+ ]);
20208
+ Se(renderTable(headers, cells), "Skill packs");
20209
+ } catch (err) {
20210
+ s?.stop("Could not load skill packs");
20211
+ R2.error(err instanceof ApiClientError ? err.message : String(err));
20212
+ process.exitCode = 1;
20213
+ }
20214
+ }
20215
+ });
20216
+ var skillsUploadCommand = defineCommand({
20217
+ meta: {
20218
+ name: "upload",
20219
+ description: "Upload the local skills-lock.json as a named skill pack."
20220
+ },
20221
+ args: {
20222
+ name: {
20223
+ type: "positional",
20224
+ description: "Name for the skill pack.",
20225
+ required: true
20226
+ }
20227
+ },
20228
+ async run({ args }) {
20229
+ const client = await getClient();
20230
+ if (!client) return;
20231
+ const lockfilePath = resolve2("skills-lock.json");
20232
+ let lockfile;
20233
+ try {
20234
+ const raw = await readFile(lockfilePath, "utf-8");
20235
+ lockfile = JSON.parse(raw);
20236
+ } catch {
20237
+ R2.error(`No lockfile found at ${lockfilePath}. Run \`npx skills install\` first.`);
20238
+ process.exitCode = 1;
20239
+ return;
20240
+ }
20241
+ const s = ft();
20242
+ s.start("Uploading skill pack\u2026");
20243
+ try {
20244
+ await call(
20245
+ parseResponse(
20246
+ client["skill-packs"].$post({
20247
+ json: { name: args.name, lockfile }
20248
+ })
20249
+ )
20250
+ );
20251
+ s.stop(`Skill pack "${args.name}" uploaded.`);
20252
+ } catch (err) {
20253
+ s.stop("Upload failed");
20254
+ R2.error(err instanceof ApiClientError ? err.message : String(err));
20255
+ process.exitCode = 1;
20256
+ }
20257
+ }
20258
+ });
20259
+ var skillsSyncCommand = defineCommand({
20260
+ meta: {
20261
+ name: "sync",
20262
+ description: "Fetch a skill pack's lockfile, write skills-lock.json, and run npx skills install."
20263
+ },
20264
+ args: {
20265
+ name: {
20266
+ type: "positional",
20267
+ description: "Name of the skill pack to sync.",
20268
+ required: true
20269
+ }
20270
+ },
20271
+ async run({ args }) {
20272
+ const client = await getClient();
20273
+ if (!client) return;
20274
+ const s = ft();
20275
+ s.start(`Fetching skill pack "${args.name}"\u2026`);
20276
+ let pack;
20277
+ try {
20278
+ pack = await call(
20279
+ parseResponse(
20280
+ client["skill-packs"][":name"].$get({ param: { name: args.name } })
20281
+ )
20282
+ );
20283
+ } catch (err) {
20284
+ s.stop("Fetch failed");
20285
+ R2.error(err instanceof ApiClientError ? err.message : String(err));
20286
+ process.exitCode = 1;
20287
+ return;
20288
+ }
20289
+ s.stop("Fetched");
20290
+ const lockfilePath = resolve2("skills-lock.json");
20291
+ s.start("Writing skills-lock.json\u2026");
20292
+ try {
20293
+ await writeFile(lockfilePath, `${JSON.stringify(pack.lockfile, null, 2)}
20294
+ `, "utf-8");
20295
+ } catch (err) {
20296
+ s.stop("Write failed");
20297
+ R2.error(err instanceof Error ? err.message : String(err));
20298
+ process.exitCode = 1;
20299
+ return;
20300
+ }
20301
+ s.stop("skills-lock.json written");
20302
+ s.start("Running npx skills install\u2026");
20303
+ try {
20304
+ await new Promise((resolve3, reject) => {
20305
+ const child = spawn("npx", ["skills", "experimental_install"], {
20306
+ stdio: "inherit"
20307
+ });
20308
+ child.on("close", (code) => {
20309
+ if (code === 0) resolve3();
20310
+ else reject(new Error(`npx skills experimental_install exited with code ${code}`));
20311
+ });
20312
+ child.on("error", reject);
20313
+ });
20314
+ s.stop("Skills installed");
20315
+ } catch (err) {
20316
+ s.stop("Install failed");
20317
+ R2.error(err instanceof Error ? err.message : String(err));
20318
+ process.exitCode = 1;
20319
+ }
20320
+ }
20321
+ });
20322
+ var skillsCommand = defineCommand({
20323
+ meta: {
20324
+ name: "skills",
20325
+ description: "Upload and sync skill packs."
20326
+ },
20327
+ subCommands: {
20328
+ list: skillsListCommand,
20329
+ upload: skillsUploadCommand,
20330
+ sync: skillsSyncCommand
20331
+ }
20332
+ });
20333
+
19504
20334
  // src/commands/status.ts
19505
20335
  var statusCommand = defineCommand({
19506
20336
  meta: {
@@ -19548,17 +20378,17 @@ import {
19548
20378
  readdirSync,
19549
20379
  statSync as statSync6
19550
20380
  } from "fs";
19551
- import { basename, dirname as dirname6, join as join9 } from "path";
20381
+ import { basename, dirname as dirname7, join as join11 } from "path";
19552
20382
  import { homedir as homedir6 } from "os";
19553
- var BACKUP_ROOT = process.env.CODEVECTOR_BACKUP_ROOT ?? join9(homedir6(), ".codevector", "backups");
20383
+ var BACKUP_ROOT = process.env.CODEVECTOR_BACKUP_ROOT ?? join11(homedir6(), ".codevector", "backups");
19554
20384
  function backupTimestamp(d = /* @__PURE__ */ new Date()) {
19555
20385
  return d.toISOString().replace(/:/g, "-");
19556
20386
  }
19557
20387
  function backupFile(sourcePath, tool, timestamp) {
19558
20388
  if (!existsSync10(sourcePath)) return void 0;
19559
- const destDir = join9(BACKUP_ROOT, timestamp, tool);
20389
+ const destDir = join11(BACKUP_ROOT, timestamp, tool);
19560
20390
  mkdirSync6(destDir, { recursive: true, mode: 448 });
19561
- const dest = join9(destDir, basename(sourcePath));
20391
+ const dest = join11(destDir, basename(sourcePath));
19562
20392
  copyFileSync2(sourcePath, dest);
19563
20393
  return dest;
19564
20394
  }
@@ -19567,18 +20397,18 @@ function listBackupRuns() {
19567
20397
  const entries = readdirSync(BACKUP_ROOT, { withFileTypes: true }).filter((e2) => e2.isDirectory()).map((e2) => e2.name).sort().reverse();
19568
20398
  const runs = [];
19569
20399
  for (const ts of entries) {
19570
- const dir = join9(BACKUP_ROOT, ts);
20400
+ const dir = join11(BACKUP_ROOT, ts);
19571
20401
  const tools = readdirSync(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory());
19572
20402
  const collected = [];
19573
20403
  for (const toolDir of tools) {
19574
- const toolPath = join9(dir, toolDir.name);
20404
+ const toolPath = join11(dir, toolDir.name);
19575
20405
  for (const file2 of readdirSync(toolPath, { withFileTypes: true })) {
19576
20406
  if (!file2.isFile()) continue;
19577
20407
  collected.push({
19578
20408
  tool: toolDir.name,
19579
20409
  original: "",
19580
20410
  // resolved by caller per tool — see restoreBackup()
19581
- backup: join9(toolPath, file2.name)
20411
+ backup: join11(toolPath, file2.name)
19582
20412
  });
19583
20413
  }
19584
20414
  }
@@ -19591,7 +20421,7 @@ function restoreBackup(backupPath, originalPath) {
19591
20421
  if (!existsSync10(backupPath)) {
19592
20422
  throw new Error(`Backup file missing: ${backupPath}`);
19593
20423
  }
19594
- mkdirSync6(dirname6(originalPath), { recursive: true });
20424
+ mkdirSync6(dirname7(originalPath), { recursive: true });
19595
20425
  copyFileSync2(backupPath, originalPath);
19596
20426
  }
19597
20427
  function backupRunMtime(dir) {
@@ -19600,7 +20430,7 @@ function backupRunMtime(dir) {
19600
20430
 
19601
20431
  // src/commands/system.ts
19602
20432
  var TOOLS = ["claude-code", "opencode", "codex"];
19603
- var WRITERS3 = {
20433
+ var WRITERS4 = {
19604
20434
  "claude-code": writeClaudeCodeConfig,
19605
20435
  opencode: writeOpencodeConfig,
19606
20436
  codex: writeCodexConfig
@@ -19647,11 +20477,11 @@ var systemConfigureCommand = defineCommand({
19647
20477
  } else {
19648
20478
  R2.info("No pre-existing user-scope config files to back up.");
19649
20479
  }
19650
- const reachable = await fetchReachableChatModels2(creds);
20480
+ const reachable = await fetchReachableChatModels3(creds);
19651
20481
  const hookPath = installAcceptanceHook();
19652
20482
  const results = [];
19653
20483
  for (const tool of TOOLS) {
19654
- const writer = WRITERS3[tool];
20484
+ const writer = WRITERS4[tool];
19655
20485
  try {
19656
20486
  results.push(
19657
20487
  writer({
@@ -19786,7 +20616,7 @@ async function confirmRestore() {
19786
20616
  function isTool2(value) {
19787
20617
  return TOOLS.includes(value);
19788
20618
  }
19789
- async function fetchReachableChatModels2(creds) {
20619
+ async function fetchReachableChatModels3(creds) {
19790
20620
  const client = gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
19791
20621
  const s = ft();
19792
20622
  s.start("Loading reachable models\u2026");
@@ -19839,7 +20669,7 @@ import { spawnSync } from "child_process";
19839
20669
  // package.json
19840
20670
  var package_default = {
19841
20671
  name: "@codevector/cli",
19842
- version: "0.4.0",
20672
+ version: "0.5.0",
19843
20673
  description: "CodeVector CLI \u2014 installs and configures first-party coding-tool integrations.",
19844
20674
  license: "UNLICENSED",
19845
20675
  bin: {
@@ -19886,8 +20716,8 @@ var package_default = {
19886
20716
 
19887
20717
  // src/lib/install-pref.ts
19888
20718
  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");
20719
+ import { join as join12 } from "path";
20720
+ var INSTALL_PREF_FILE = join12(CODEVECTOR_CONFIG_DIR, "install.json");
19891
20721
  var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn"];
19892
20722
  function isPackageManager(v2) {
19893
20723
  return typeof v2 === "string" && PACKAGE_MANAGERS.includes(v2);
@@ -20104,10 +20934,10 @@ var versionCommand = defineCommand({
20104
20934
 
20105
20935
  // src/lib/update-notifier.ts
20106
20936
  import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
20107
- import { join as join11 } from "path";
20937
+ import { join as join13 } from "path";
20108
20938
  var PKG_NAME2 = "@codevector/cli";
20109
20939
  var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME2}/latest`;
20110
- var CHECK_CACHE_FILE = join11(CODEVECTOR_CONFIG_DIR, "update-check.json");
20940
+ var CHECK_CACHE_FILE = join13(CODEVECTOR_CONFIG_DIR, "update-check.json");
20111
20941
  var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
20112
20942
  var FETCH_TIMEOUT_MS = 2e3;
20113
20943
  function readCache() {
@@ -20194,13 +21024,17 @@ var main = defineCommand({
20194
21024
  },
20195
21025
  subCommands: {
20196
21026
  auth: authCommand,
21027
+ config: configCommand,
20197
21028
  configure: configureCommand,
20198
21029
  init: initCommand,
20199
21030
  doctor: doctorCommand,
21031
+ env: envCommand,
21032
+ hook: hookCommand,
20200
21033
  status: statusCommand,
20201
21034
  system: systemCommand,
20202
21035
  models: modelsCommand,
20203
21036
  profile: profileCommand,
21037
+ skills: skillsCommand,
20204
21038
  update: updateCommand,
20205
21039
  usage: usageCommand,
20206
21040
  version: versionCommand