@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 +1109 -275
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
17645
|
-
existsSync as
|
|
17646
|
-
mkdirSync as
|
|
17647
|
-
readFileSync as
|
|
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
|
|
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
|
|
18443
|
+
return join5(homedir4(), ".codex", "config.toml");
|
|
18544
18444
|
case "project":
|
|
18545
18445
|
case "local":
|
|
18546
|
-
return
|
|
18446
|
+
return join5(userCwd(), ".codex", "config.toml");
|
|
18547
18447
|
}
|
|
18548
18448
|
}
|
|
18549
18449
|
function readTomlOrEmpty(path) {
|
|
18550
|
-
if (!
|
|
18551
|
-
const raw =
|
|
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 =
|
|
18561
|
-
|
|
18460
|
+
const dir = dirname4(path);
|
|
18461
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
18562
18462
|
let mode;
|
|
18563
|
-
if (
|
|
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
|
-
|
|
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/
|
|
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
|
|
18591
|
-
var SCOPES = ["local", "project"];
|
|
18592
|
-
var configureCommand = defineCommand({
|
|
18583
|
+
var configSyncCommand = defineCommand({
|
|
18593
18584
|
meta: {
|
|
18594
|
-
name: "
|
|
18595
|
-
description: "
|
|
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
|
-
|
|
18598
|
-
|
|
18599
|
-
|
|
18600
|
-
|
|
18601
|
-
|
|
18602
|
-
|
|
18603
|
-
|
|
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
|
-
|
|
18614
|
-
const
|
|
18615
|
-
if (
|
|
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
|
|
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 "${
|
|
18613
|
+
`Active profile "${profiles.activeProfile}" is missing from credentials. Run \`codevector auth login\`.`
|
|
18623
18614
|
);
|
|
18624
18615
|
}
|
|
18625
|
-
|
|
18626
|
-
|
|
18627
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18722
|
-
|
|
18723
|
-
|
|
18724
|
-
|
|
18725
|
-
|
|
18726
|
-
|
|
18727
|
-
|
|
18728
|
-
|
|
18729
|
-
|
|
18730
|
-
|
|
18731
|
-
|
|
18732
|
-
|
|
18733
|
-
|
|
18734
|
-
|
|
18735
|
-
|
|
18736
|
-
|
|
18737
|
-
|
|
18738
|
-
|
|
18739
|
-
|
|
18740
|
-
|
|
18741
|
-
|
|
18742
|
-
|
|
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
|
|
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 {
|
|
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: "
|
|
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 =
|
|
19012
|
-
|
|
19013
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19022
|
-
|
|
19023
|
-
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19027
|
-
|
|
19028
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 ??
|
|
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 =
|
|
20389
|
+
const destDir = join11(BACKUP_ROOT, timestamp, tool);
|
|
19560
20390
|
mkdirSync6(destDir, { recursive: true, mode: 448 });
|
|
19561
|
-
const dest =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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.
|
|
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
|
|
19890
|
-
var INSTALL_PREF_FILE =
|
|
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
|
|
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 =
|
|
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
|