@codevector/cli 0.8.0 → 0.10.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 +1558 -446
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2084,7 +2084,72 @@ var init_client2 = __esm({
|
|
|
2084
2084
|
}
|
|
2085
2085
|
});
|
|
2086
2086
|
|
|
2087
|
+
// package.json
|
|
2088
|
+
var package_default;
|
|
2089
|
+
var init_package = __esm({
|
|
2090
|
+
"package.json"() {
|
|
2091
|
+
package_default = {
|
|
2092
|
+
name: "@codevector/cli",
|
|
2093
|
+
version: "0.10.0",
|
|
2094
|
+
description: "CodeVector CLI \u2014 installs and configures first-party coding-tool integrations.",
|
|
2095
|
+
license: "UNLICENSED",
|
|
2096
|
+
bin: {
|
|
2097
|
+
codevector: "./bin/codevector.mjs"
|
|
2098
|
+
},
|
|
2099
|
+
files: [
|
|
2100
|
+
"bin",
|
|
2101
|
+
"dist",
|
|
2102
|
+
"scripts",
|
|
2103
|
+
"src/hooks"
|
|
2104
|
+
],
|
|
2105
|
+
type: "module",
|
|
2106
|
+
publishConfig: {
|
|
2107
|
+
access: "public"
|
|
2108
|
+
},
|
|
2109
|
+
scripts: {
|
|
2110
|
+
dev: "tsx src/index.ts",
|
|
2111
|
+
build: "tsup",
|
|
2112
|
+
"type-check": "tsc --noEmit",
|
|
2113
|
+
test: "vitest run",
|
|
2114
|
+
"test:watch": "vitest",
|
|
2115
|
+
postinstall: "node scripts/postinstall.mjs"
|
|
2116
|
+
},
|
|
2117
|
+
dependencies: {
|
|
2118
|
+
"@clack/prompts": "1.4.0",
|
|
2119
|
+
citty: "^0.2.2",
|
|
2120
|
+
hono: "4.12.16",
|
|
2121
|
+
"smol-toml": "^1.6.1"
|
|
2122
|
+
},
|
|
2123
|
+
devDependencies: {
|
|
2124
|
+
"@codevector/api": "workspace:*",
|
|
2125
|
+
"@codevector/common": "workspace:*",
|
|
2126
|
+
"@types/node": "25.6.0",
|
|
2127
|
+
tsup: "^8.5.1",
|
|
2128
|
+
tsx: "4.21.0",
|
|
2129
|
+
typescript: "6.0.3",
|
|
2130
|
+
vitest: "4.1.5",
|
|
2131
|
+
zod: "4.4.2"
|
|
2132
|
+
},
|
|
2133
|
+
engines: {
|
|
2134
|
+
node: ">=20"
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
});
|
|
2139
|
+
|
|
2087
2140
|
// src/lib/api-client.ts
|
|
2141
|
+
function healthClient(baseUrl, timeoutMs = 5e3) {
|
|
2142
|
+
const client = hc(trimRightSlash(baseUrl), {
|
|
2143
|
+
fetch: (input, init) => {
|
|
2144
|
+
const controller = new AbortController();
|
|
2145
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2146
|
+
return fetch(input, { ...init, signal: controller.signal }).finally(
|
|
2147
|
+
() => clearTimeout(timer)
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
});
|
|
2151
|
+
return client.health;
|
|
2152
|
+
}
|
|
2088
2153
|
function gatewayClient(gatewayUrl, apiKey, timeoutMs = 15e3) {
|
|
2089
2154
|
const client = hc(trimRightSlash(gatewayUrl), {
|
|
2090
2155
|
fetch: (input, init) => {
|
|
@@ -2092,6 +2157,7 @@ function gatewayClient(gatewayUrl, apiKey, timeoutMs = 15e3) {
|
|
|
2092
2157
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2093
2158
|
const headers = new Headers(init?.headers);
|
|
2094
2159
|
headers.set("x-api-key", apiKey);
|
|
2160
|
+
headers.set("x-codevector-cli-version", package_default.version);
|
|
2095
2161
|
return fetch(input, { ...init, headers, signal: controller.signal }).finally(
|
|
2096
2162
|
() => clearTimeout(timer)
|
|
2097
2163
|
);
|
|
@@ -2108,7 +2174,10 @@ async function call(promise2) {
|
|
|
2108
2174
|
throw new ApiClientError(
|
|
2109
2175
|
err.statusCode,
|
|
2110
2176
|
errShape?.code ?? `HTTP_${err.statusCode}`,
|
|
2111
|
-
|
|
2177
|
+
// Prefer the server's human-facing description (e.g. the UPGRADE_REQUIRED
|
|
2178
|
+
// "run `codevector update`" text) over hono/client's generic
|
|
2179
|
+
// "<status> <statusText>" message.
|
|
2180
|
+
errShape?.description ?? err.message
|
|
2112
2181
|
);
|
|
2113
2182
|
}
|
|
2114
2183
|
const asError = err;
|
|
@@ -2130,6 +2199,7 @@ var init_api_client = __esm({
|
|
|
2130
2199
|
"src/lib/api-client.ts"() {
|
|
2131
2200
|
"use strict";
|
|
2132
2201
|
init_client2();
|
|
2202
|
+
init_package();
|
|
2133
2203
|
ApiClientError = class extends Error {
|
|
2134
2204
|
constructor(status, code, message) {
|
|
2135
2205
|
super(message);
|
|
@@ -17513,17 +17583,128 @@ var init_gitignore = __esm({
|
|
|
17513
17583
|
}
|
|
17514
17584
|
});
|
|
17515
17585
|
|
|
17516
|
-
// src/config-writers/
|
|
17517
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
17586
|
+
// src/config-writers/cline.ts
|
|
17587
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
17518
17588
|
import { homedir as homedir3 } from "os";
|
|
17519
|
-
import { join as join4 } from "path";
|
|
17589
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
17590
|
+
function clineDataDir() {
|
|
17591
|
+
const home = homedir3();
|
|
17592
|
+
return join4(home, ".cline", "data", "settings");
|
|
17593
|
+
}
|
|
17594
|
+
function clineSettingsPath(scope) {
|
|
17595
|
+
switch (scope) {
|
|
17596
|
+
case "user":
|
|
17597
|
+
return join4(clineDataDir(), PROVIDERS_FILE);
|
|
17598
|
+
case "project":
|
|
17599
|
+
return join4(userCwd(), CLINE_CONFIG_DIR, PROVIDERS_FILE);
|
|
17600
|
+
case "local":
|
|
17601
|
+
return join4(userCwd(), CLINE_CONFIG_DIR, PROVIDERS_FILE);
|
|
17602
|
+
}
|
|
17603
|
+
}
|
|
17604
|
+
function detectClineConfig() {
|
|
17605
|
+
const scopes = ["local", "project", "user"];
|
|
17606
|
+
for (const scope of scopes) {
|
|
17607
|
+
const path = clineSettingsPath(scope);
|
|
17608
|
+
if (!existsSync3(path)) continue;
|
|
17609
|
+
try {
|
|
17610
|
+
const raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
17611
|
+
const providers = raw.providers;
|
|
17612
|
+
if (Array.isArray(providers)) {
|
|
17613
|
+
for (const p2 of providers) {
|
|
17614
|
+
if (typeof p2.baseUrl === "string" && p2.baseUrl.includes("/gateway/")) {
|
|
17615
|
+
return { scope, modelSlug: p2.defaultModel };
|
|
17616
|
+
}
|
|
17617
|
+
}
|
|
17618
|
+
}
|
|
17619
|
+
} catch {
|
|
17620
|
+
}
|
|
17621
|
+
}
|
|
17622
|
+
return void 0;
|
|
17623
|
+
}
|
|
17624
|
+
function readProvidersFile(path) {
|
|
17625
|
+
try {
|
|
17626
|
+
const raw = readFileSync4(path, "utf8").trim();
|
|
17627
|
+
if (!raw) return {};
|
|
17628
|
+
const parsed = JSON.parse(raw);
|
|
17629
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
17630
|
+
return parsed;
|
|
17631
|
+
}
|
|
17632
|
+
return {};
|
|
17633
|
+
} catch {
|
|
17634
|
+
return {};
|
|
17635
|
+
}
|
|
17636
|
+
}
|
|
17637
|
+
function trimRightSlash3(url2) {
|
|
17638
|
+
return url2.endsWith("/") ? url2.slice(0, -1) : url2;
|
|
17639
|
+
}
|
|
17640
|
+
var CLINE_CONFIG_DIR, PROVIDERS_FILE, writeClineConfig;
|
|
17641
|
+
var init_cline = __esm({
|
|
17642
|
+
"src/config-writers/cline.ts"() {
|
|
17643
|
+
"use strict";
|
|
17644
|
+
init_gitignore();
|
|
17645
|
+
init_paths();
|
|
17646
|
+
CLINE_CONFIG_DIR = ".cline";
|
|
17647
|
+
PROVIDERS_FILE = "providers.json";
|
|
17648
|
+
writeClineConfig = ({
|
|
17649
|
+
gatewayUrl,
|
|
17650
|
+
apiKey,
|
|
17651
|
+
scope,
|
|
17652
|
+
project,
|
|
17653
|
+
model
|
|
17654
|
+
}) => {
|
|
17655
|
+
const path = clineSettingsPath(scope);
|
|
17656
|
+
const baseUrl = `${trimRightSlash3(gatewayUrl)}/gateway/openai/v1`;
|
|
17657
|
+
mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
|
|
17658
|
+
const existing = readProvidersFile(path);
|
|
17659
|
+
const providers = (existing.providers ?? []).filter(
|
|
17660
|
+
(p2) => p2.id !== "codevector" && p2.baseUrl !== baseUrl
|
|
17661
|
+
);
|
|
17662
|
+
providers.push({
|
|
17663
|
+
type: "openai-compatible",
|
|
17664
|
+
id: "codevector",
|
|
17665
|
+
apiKey,
|
|
17666
|
+
baseUrl,
|
|
17667
|
+
...model?.slug ? { defaultModel: model.slug } : {}
|
|
17668
|
+
});
|
|
17669
|
+
writeFileSync3(path, `${JSON.stringify({ ...existing, providers }, null, 2)}
|
|
17670
|
+
`);
|
|
17671
|
+
const notes = [];
|
|
17672
|
+
if (scope === "local") {
|
|
17673
|
+
if (ensureGitignored(join4(CLINE_CONFIG_DIR, PROVIDERS_FILE))) {
|
|
17674
|
+
notes.push(`Added \`${CLINE_CONFIG_DIR}/${PROVIDERS_FILE}\` to .gitignore.`);
|
|
17675
|
+
}
|
|
17676
|
+
}
|
|
17677
|
+
const authCmd = `cline auth --provider openai-compatible --apikey "${apiKey.slice(0, 8)}\u2026" --baseurl "${baseUrl}"${model?.slug ? ` --modelid "${model.slug}"` : ""}`;
|
|
17678
|
+
notes.push(
|
|
17679
|
+
`Alternatively, run \`${authCmd}\` (requires cline CLI: \`npm i -g cline\`).`
|
|
17680
|
+
);
|
|
17681
|
+
if (scope !== "user" && !project) {
|
|
17682
|
+
notes.push(
|
|
17683
|
+
"Project slug could not be derived (no git remote and no .codevector.json). Per-project usage attribution will be blank until you set one."
|
|
17684
|
+
);
|
|
17685
|
+
}
|
|
17686
|
+
return {
|
|
17687
|
+
tool: "cline",
|
|
17688
|
+
status: "configured",
|
|
17689
|
+
path,
|
|
17690
|
+
scope,
|
|
17691
|
+
notes: notes.length > 0 ? notes.join(" ") : void 0
|
|
17692
|
+
};
|
|
17693
|
+
};
|
|
17694
|
+
}
|
|
17695
|
+
});
|
|
17696
|
+
|
|
17697
|
+
// src/config-writers/opencode.ts
|
|
17698
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
17699
|
+
import { homedir as homedir4 } from "os";
|
|
17700
|
+
import { join as join5 } from "path";
|
|
17520
17701
|
function syncOpencodeModels(path, availableModels) {
|
|
17521
|
-
if (!
|
|
17702
|
+
if (!existsSync4(path)) {
|
|
17522
17703
|
throw new Error(
|
|
17523
17704
|
`No opencode config found at ${path}. Run \`codevector configure opencode\` first.`
|
|
17524
17705
|
);
|
|
17525
17706
|
}
|
|
17526
|
-
const raw =
|
|
17707
|
+
const raw = readFileSync5(path, "utf8");
|
|
17527
17708
|
let parsed;
|
|
17528
17709
|
try {
|
|
17529
17710
|
parsed = raw.trim().length === 0 ? {} : JSON.parse(raw);
|
|
@@ -17542,7 +17723,7 @@ function syncOpencodeModels(path, availableModels) {
|
|
|
17542
17723
|
}
|
|
17543
17724
|
const models = buildModelEntries(availableModels);
|
|
17544
17725
|
codevector.models = models;
|
|
17545
|
-
|
|
17726
|
+
writeFileSync4(path, `${JSON.stringify(parsed, null, 2)}
|
|
17546
17727
|
`);
|
|
17547
17728
|
return Object.keys(models).length;
|
|
17548
17729
|
}
|
|
@@ -17591,10 +17772,10 @@ function parsePrice(raw) {
|
|
|
17591
17772
|
return Number.isFinite(n) ? n : void 0;
|
|
17592
17773
|
}
|
|
17593
17774
|
function forceReplaceCgwModels(path, models) {
|
|
17594
|
-
if (!
|
|
17775
|
+
if (!existsSync4(path)) return;
|
|
17595
17776
|
let parsed;
|
|
17596
17777
|
try {
|
|
17597
|
-
parsed = JSON.parse(
|
|
17778
|
+
parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
17598
17779
|
} catch {
|
|
17599
17780
|
return;
|
|
17600
17781
|
}
|
|
@@ -17604,25 +17785,25 @@ function forceReplaceCgwModels(path, models) {
|
|
|
17604
17785
|
const codevector = provider[PROVIDER_PREFIX];
|
|
17605
17786
|
if (!isObject3(codevector)) return;
|
|
17606
17787
|
codevector.models = models;
|
|
17607
|
-
|
|
17788
|
+
writeFileSync4(path, `${JSON.stringify(parsed, null, 2)}
|
|
17608
17789
|
`);
|
|
17609
17790
|
}
|
|
17610
17791
|
function opencodeSettingsPath(scope) {
|
|
17611
17792
|
switch (scope) {
|
|
17612
17793
|
case "user":
|
|
17613
|
-
return
|
|
17794
|
+
return join5(homedir4(), ".config", "opencode", "opencode.json");
|
|
17614
17795
|
case "project":
|
|
17615
17796
|
case "local":
|
|
17616
|
-
return
|
|
17797
|
+
return join5(userCwd(), "opencode.json");
|
|
17617
17798
|
}
|
|
17618
17799
|
}
|
|
17619
17800
|
function detectOpencodeConfig() {
|
|
17620
17801
|
const scopes = ["local", "project", "user"];
|
|
17621
17802
|
for (const scope of scopes) {
|
|
17622
17803
|
const path = opencodeSettingsPath(scope);
|
|
17623
|
-
if (!
|
|
17804
|
+
if (!existsSync4(path)) continue;
|
|
17624
17805
|
try {
|
|
17625
|
-
const raw = JSON.parse(
|
|
17806
|
+
const raw = JSON.parse(readFileSync5(path, "utf8"));
|
|
17626
17807
|
if (raw.provider?.codevector?.options?.baseURL) {
|
|
17627
17808
|
const modelSlug = raw.model?.startsWith("codevector/") ? raw.model.slice("codevector/".length) : raw.model;
|
|
17628
17809
|
return { scope, modelSlug };
|
|
@@ -17633,10 +17814,10 @@ function detectOpencodeConfig() {
|
|
|
17633
17814
|
return void 0;
|
|
17634
17815
|
}
|
|
17635
17816
|
function removeStaleGatewayProviders(path, gateway) {
|
|
17636
|
-
if (!
|
|
17817
|
+
if (!existsSync4(path)) return;
|
|
17637
17818
|
let parsed;
|
|
17638
17819
|
try {
|
|
17639
|
-
const raw =
|
|
17820
|
+
const raw = readFileSync5(path, "utf8");
|
|
17640
17821
|
if (raw.trim().length === 0) return;
|
|
17641
17822
|
parsed = JSON.parse(raw);
|
|
17642
17823
|
} catch {
|
|
@@ -17659,7 +17840,7 @@ function removeStaleGatewayProviders(path, gateway) {
|
|
|
17659
17840
|
}
|
|
17660
17841
|
}
|
|
17661
17842
|
if (mutated) {
|
|
17662
|
-
|
|
17843
|
+
writeFileSync4(path, `${JSON.stringify(parsed, null, 2)}
|
|
17663
17844
|
`);
|
|
17664
17845
|
}
|
|
17665
17846
|
}
|
|
@@ -17679,7 +17860,7 @@ function buildCustomHeaders2(scope, project) {
|
|
|
17679
17860
|
}
|
|
17680
17861
|
return headers;
|
|
17681
17862
|
}
|
|
17682
|
-
function
|
|
17863
|
+
function trimRightSlash4(url2) {
|
|
17683
17864
|
return url2.endsWith("/") ? url2.slice(0, -1) : url2;
|
|
17684
17865
|
}
|
|
17685
17866
|
var ENV_VAR_NAME, PROVIDER_PREFIX, writeOpencodeConfig;
|
|
@@ -17700,7 +17881,7 @@ var init_opencode = __esm({
|
|
|
17700
17881
|
availableModels = []
|
|
17701
17882
|
}) => {
|
|
17702
17883
|
const path = opencodeSettingsPath(scope);
|
|
17703
|
-
const gateway =
|
|
17884
|
+
const gateway = trimRightSlash4(gatewayUrl);
|
|
17704
17885
|
const keyLiteral = scope === "project" ? `{env:${ENV_VAR_NAME}}` : apiKey;
|
|
17705
17886
|
removeStaleGatewayProviders(path, gateway);
|
|
17706
17887
|
const models = buildModelEntries(availableModels);
|
|
@@ -17749,8 +17930,8 @@ var init_opencode = __esm({
|
|
|
17749
17930
|
});
|
|
17750
17931
|
|
|
17751
17932
|
// src/lib/credentials-lock.ts
|
|
17752
|
-
import { closeSync, constants, mkdirSync as
|
|
17753
|
-
import { dirname as
|
|
17933
|
+
import { closeSync, constants, mkdirSync as mkdirSync3, openSync, statSync as statSync2, unlinkSync } from "fs";
|
|
17934
|
+
import { dirname as dirname3 } from "path";
|
|
17754
17935
|
function syncSleep(ms) {
|
|
17755
17936
|
Atomics.wait(sleepView, 0, 0, ms);
|
|
17756
17937
|
}
|
|
@@ -17773,7 +17954,7 @@ function withCredentialsLockSync(fn) {
|
|
|
17773
17954
|
}
|
|
17774
17955
|
}
|
|
17775
17956
|
function acquireLock() {
|
|
17776
|
-
|
|
17957
|
+
mkdirSync3(dirname3(LOCK_FILE), { recursive: true, mode: 448 });
|
|
17777
17958
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
17778
17959
|
try {
|
|
17779
17960
|
const fd = openSync(
|
|
@@ -17827,24 +18008,24 @@ var init_credentials_lock = __esm({
|
|
|
17827
18008
|
// src/lib/credentials.ts
|
|
17828
18009
|
import {
|
|
17829
18010
|
chmodSync as chmodSync2,
|
|
17830
|
-
mkdirSync as
|
|
17831
|
-
readFileSync as
|
|
18011
|
+
mkdirSync as mkdirSync4,
|
|
18012
|
+
readFileSync as readFileSync6,
|
|
17832
18013
|
renameSync as renameSync2,
|
|
17833
18014
|
rmSync,
|
|
17834
18015
|
statSync as statSync3,
|
|
17835
|
-
writeFileSync as
|
|
18016
|
+
writeFileSync as writeFileSync5
|
|
17836
18017
|
} from "fs";
|
|
17837
|
-
import { dirname as
|
|
18018
|
+
import { dirname as dirname4 } from "path";
|
|
17838
18019
|
function writeProfilesFile(payload) {
|
|
17839
18020
|
const parsed = ProfilesFileSchema.parse(payload);
|
|
17840
|
-
const dir =
|
|
17841
|
-
|
|
18021
|
+
const dir = dirname4(CREDENTIALS_FILE);
|
|
18022
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
17842
18023
|
try {
|
|
17843
18024
|
chmodSync2(dir, 448);
|
|
17844
18025
|
} catch {
|
|
17845
18026
|
}
|
|
17846
18027
|
const tmp = `${CREDENTIALS_FILE}.${process.pid}.tmp`;
|
|
17847
|
-
|
|
18028
|
+
writeFileSync5(tmp, JSON.stringify(parsed, null, 2), { mode: 384 });
|
|
17848
18029
|
try {
|
|
17849
18030
|
chmodSync2(tmp, 384);
|
|
17850
18031
|
} catch {
|
|
@@ -17854,7 +18035,7 @@ function writeProfilesFile(payload) {
|
|
|
17854
18035
|
function readProfilesSync() {
|
|
17855
18036
|
let raw;
|
|
17856
18037
|
try {
|
|
17857
|
-
raw =
|
|
18038
|
+
raw = readFileSync6(CREDENTIALS_FILE, "utf8");
|
|
17858
18039
|
} catch (err) {
|
|
17859
18040
|
if (err.code === "ENOENT") return null;
|
|
17860
18041
|
throw err;
|
|
@@ -17873,7 +18054,7 @@ function readProfilesSync() {
|
|
|
17873
18054
|
return withCredentialsLockSync(() => {
|
|
17874
18055
|
let lockedRaw;
|
|
17875
18056
|
try {
|
|
17876
|
-
lockedRaw =
|
|
18057
|
+
lockedRaw = readFileSync6(CREDENTIALS_FILE, "utf8");
|
|
17877
18058
|
} catch (err) {
|
|
17878
18059
|
if (err.code === "ENOENT") {
|
|
17879
18060
|
return null;
|
|
@@ -17885,7 +18066,7 @@ function readProfilesSync() {
|
|
|
17885
18066
|
return ProfilesFileSchema.parse(lockedParsed);
|
|
17886
18067
|
}
|
|
17887
18068
|
const oldProfile = ProfileSchema.parse(lockedParsed);
|
|
17888
|
-
const seeded =
|
|
18069
|
+
const seeded = detectOnDiskToolConfigs();
|
|
17889
18070
|
const defaultProfile = {
|
|
17890
18071
|
...oldProfile,
|
|
17891
18072
|
...seeded.length > 0 ? { toolConfigs: seeded } : {}
|
|
@@ -17901,7 +18082,7 @@ function readProfilesSync() {
|
|
|
17901
18082
|
return migrated;
|
|
17902
18083
|
});
|
|
17903
18084
|
}
|
|
17904
|
-
function
|
|
18085
|
+
function detectOnDiskToolConfigs() {
|
|
17905
18086
|
const out = [];
|
|
17906
18087
|
const claude = detectClaudeCodeConfig();
|
|
17907
18088
|
if (claude) {
|
|
@@ -17919,6 +18100,14 @@ function seedToolConfigsFromDisk() {
|
|
|
17919
18100
|
...opencode.modelSlug ? { modelSlug: opencode.modelSlug } : {}
|
|
17920
18101
|
});
|
|
17921
18102
|
}
|
|
18103
|
+
const cline = detectClineConfig();
|
|
18104
|
+
if (cline) {
|
|
18105
|
+
out.push({
|
|
18106
|
+
tool: "cline",
|
|
18107
|
+
scope: cline.scope,
|
|
18108
|
+
...cline.modelSlug ? { modelSlug: cline.modelSlug } : {}
|
|
18109
|
+
});
|
|
18110
|
+
}
|
|
17922
18111
|
return out;
|
|
17923
18112
|
}
|
|
17924
18113
|
async function readProfiles() {
|
|
@@ -18020,6 +18209,7 @@ var init_credentials = __esm({
|
|
|
18020
18209
|
"use strict";
|
|
18021
18210
|
init_zod();
|
|
18022
18211
|
init_claude_code();
|
|
18212
|
+
init_cline();
|
|
18023
18213
|
init_opencode();
|
|
18024
18214
|
init_credentials_lock();
|
|
18025
18215
|
init_paths();
|
|
@@ -18034,7 +18224,18 @@ var init_credentials = __esm({
|
|
|
18034
18224
|
userId: external_exports.uuid(),
|
|
18035
18225
|
email: external_exports.email(),
|
|
18036
18226
|
savedAt: external_exports.iso.datetime(),
|
|
18037
|
-
toolConfigs: external_exports.array(ToolConfigSchema).optional()
|
|
18227
|
+
toolConfigs: external_exports.array(ToolConfigSchema).optional(),
|
|
18228
|
+
// Group-scoped profile: the group this profile's key bills/records against.
|
|
18229
|
+
// Attribution is server-side (bound to the key); these are persisted only so
|
|
18230
|
+
// `profile list` / `status` / `doctor` can show the billed group without a
|
|
18231
|
+
// round-trip. `groupId` is the stable key (survives a rename); `groupName` is
|
|
18232
|
+
// display only. Absent for a personal profile.
|
|
18233
|
+
groupId: external_exports.uuid().optional(),
|
|
18234
|
+
groupName: external_exports.string().min(1).optional(),
|
|
18235
|
+
// Server-side id of the API key this profile holds. Stored on mint so
|
|
18236
|
+
// `sync` can match a local profile to a server key record exactly (prefix
|
|
18237
|
+
// is non-unique). Absent on legacy profiles (matched by prefix instead).
|
|
18238
|
+
keyId: external_exports.uuid().optional()
|
|
18038
18239
|
});
|
|
18039
18240
|
ProfilesFileSchema = external_exports.object({
|
|
18040
18241
|
activeProfile: external_exports.string().min(1),
|
|
@@ -18076,16 +18277,16 @@ var init_prompt = __esm({
|
|
|
18076
18277
|
});
|
|
18077
18278
|
|
|
18078
18279
|
// src/lib/project-config.ts
|
|
18079
|
-
import { existsSync as
|
|
18080
|
-
import { dirname as
|
|
18280
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
18281
|
+
import { dirname as dirname5, join as join6, parse as parsePath, resolve } from "path";
|
|
18081
18282
|
function findProjectConfigPath(startDir) {
|
|
18082
18283
|
let dir = resolve(startDir);
|
|
18083
18284
|
const root = parsePath(dir).root;
|
|
18084
18285
|
while (true) {
|
|
18085
|
-
const candidate =
|
|
18086
|
-
if (
|
|
18286
|
+
const candidate = join6(dir, PROJECT_CONFIG_FILENAME);
|
|
18287
|
+
if (existsSync5(candidate)) return candidate;
|
|
18087
18288
|
if (dir === root) return null;
|
|
18088
|
-
const parent =
|
|
18289
|
+
const parent = dirname5(dir);
|
|
18089
18290
|
if (parent === dir) return null;
|
|
18090
18291
|
dir = parent;
|
|
18091
18292
|
}
|
|
@@ -18093,7 +18294,7 @@ function findProjectConfigPath(startDir) {
|
|
|
18093
18294
|
function readProjectConfigAt(path) {
|
|
18094
18295
|
let raw;
|
|
18095
18296
|
try {
|
|
18096
|
-
raw =
|
|
18297
|
+
raw = readFileSync7(path, "utf8");
|
|
18097
18298
|
} catch {
|
|
18098
18299
|
return null;
|
|
18099
18300
|
}
|
|
@@ -18115,7 +18316,7 @@ function readProjectConfig(startDir) {
|
|
|
18115
18316
|
}
|
|
18116
18317
|
function writeProjectConfig(path, config2) {
|
|
18117
18318
|
const parsed = ProjectConfigSchema.parse(config2);
|
|
18118
|
-
|
|
18319
|
+
writeFileSync6(path, `${JSON.stringify(parsed, null, 2)}
|
|
18119
18320
|
`, { mode: 420 });
|
|
18120
18321
|
}
|
|
18121
18322
|
var PROJECT_CONFIG_FILENAME, ProjectConfigSchema;
|
|
@@ -18129,14 +18330,18 @@ var init_project_config = __esm({
|
|
|
18129
18330
|
projectName: external_exports.string().min(1).optional(),
|
|
18130
18331
|
ticketPattern: external_exports.string().min(1).optional(),
|
|
18131
18332
|
gateway: external_exports.url().optional(),
|
|
18132
|
-
tools: external_exports.array(ToolConfigSchema).optional()
|
|
18333
|
+
tools: external_exports.array(ToolConfigSchema).optional(),
|
|
18334
|
+
// Candidate groups this repo bills. The shell hook resolves them to the one
|
|
18335
|
+
// local key the developer holds (see resolveProfileForRepo). A list, not a
|
|
18336
|
+
// single pin: a repo shared by two teams lists both; each dev bills their own.
|
|
18337
|
+
groups: external_exports.array(external_exports.string().min(1)).optional()
|
|
18133
18338
|
});
|
|
18134
18339
|
}
|
|
18135
18340
|
});
|
|
18136
18341
|
|
|
18137
18342
|
// src/lib/project-context.ts
|
|
18138
18343
|
import { execFileSync } from "child_process";
|
|
18139
|
-
import { join as
|
|
18344
|
+
import { join as join7 } from "path";
|
|
18140
18345
|
function safeGit(args, cwd) {
|
|
18141
18346
|
try {
|
|
18142
18347
|
const out = execFileSync("git", args, {
|
|
@@ -18174,7 +18379,7 @@ function resolveProjectContext(cwd = process.cwd(), ticketPattern = DEFAULT_TICK
|
|
|
18174
18379
|
return { project, ticket };
|
|
18175
18380
|
}
|
|
18176
18381
|
function readLocalConfig(cwd) {
|
|
18177
|
-
const config2 = readProjectConfigAt(
|
|
18382
|
+
const config2 = readProjectConfigAt(join7(cwd, PROJECT_CONFIG_FILENAME));
|
|
18178
18383
|
if (!config2) return null;
|
|
18179
18384
|
const result = {};
|
|
18180
18385
|
if (config2.projectName) result.projectName = config2.projectName;
|
|
@@ -18216,9 +18421,9 @@ var init_shell = __esm({
|
|
|
18216
18421
|
});
|
|
18217
18422
|
|
|
18218
18423
|
// src/lib/shell-hook.ts
|
|
18219
|
-
import { existsSync as
|
|
18220
|
-
import { homedir as
|
|
18221
|
-
import { join as
|
|
18424
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
|
|
18425
|
+
import { homedir as homedir5 } from "os";
|
|
18426
|
+
import { join as join8 } from "path";
|
|
18222
18427
|
function shellHookRecipe(shell) {
|
|
18223
18428
|
return RECIPES[shell];
|
|
18224
18429
|
}
|
|
@@ -18251,19 +18456,19 @@ function shellHookOneLiner(shell = detectShell()) {
|
|
|
18251
18456
|
return `Optional: add \`${line}\` to ${rc} so credentials auto-activate on cd.`;
|
|
18252
18457
|
}
|
|
18253
18458
|
function rcCandidates(shell, platform = process.platform) {
|
|
18254
|
-
const home =
|
|
18459
|
+
const home = homedir5();
|
|
18255
18460
|
switch (shell) {
|
|
18256
18461
|
case "bash":
|
|
18257
|
-
return platform === "darwin" ? [
|
|
18462
|
+
return platform === "darwin" ? [join8(home, ".bash_profile"), join8(home, ".bashrc")] : [join8(home, ".bashrc")];
|
|
18258
18463
|
case "zsh":
|
|
18259
|
-
return [
|
|
18464
|
+
return [join8(home, ".zshrc")];
|
|
18260
18465
|
case "fish":
|
|
18261
|
-
return [
|
|
18466
|
+
return [join8(home, ".config", "fish", "config.fish")];
|
|
18262
18467
|
case "powershell": {
|
|
18263
|
-
const docs =
|
|
18468
|
+
const docs = join8(home, "Documents");
|
|
18264
18469
|
return [
|
|
18265
|
-
|
|
18266
|
-
|
|
18470
|
+
join8(docs, "PowerShell", "Microsoft.PowerShell_profile.ps1"),
|
|
18471
|
+
join8(docs, "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1")
|
|
18267
18472
|
];
|
|
18268
18473
|
}
|
|
18269
18474
|
}
|
|
@@ -18271,7 +18476,7 @@ function rcCandidates(shell, platform = process.platform) {
|
|
|
18271
18476
|
function isHookInstalled(shell, platform = process.platform) {
|
|
18272
18477
|
for (const path of rcCandidates(shell, platform)) {
|
|
18273
18478
|
try {
|
|
18274
|
-
if (
|
|
18479
|
+
if (existsSync6(path) && /codevector hook/.test(readFileSync8(path, "utf8"))) return true;
|
|
18275
18480
|
} catch {
|
|
18276
18481
|
}
|
|
18277
18482
|
}
|
|
@@ -18297,8 +18502,8 @@ __export(init_exports, {
|
|
|
18297
18502
|
initCommand: () => initCommand
|
|
18298
18503
|
});
|
|
18299
18504
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
18300
|
-
import { existsSync as
|
|
18301
|
-
import { join as
|
|
18505
|
+
import { existsSync as existsSync7 } from "fs";
|
|
18506
|
+
import { join as join9 } from "path";
|
|
18302
18507
|
function isInteractive(args) {
|
|
18303
18508
|
if (!process.stdout.isTTY) return false;
|
|
18304
18509
|
return !args.gateway;
|
|
@@ -18429,8 +18634,8 @@ var init_init = __esm({
|
|
|
18429
18634
|
},
|
|
18430
18635
|
async run({ args }) {
|
|
18431
18636
|
const cwd = userCwd();
|
|
18432
|
-
const target =
|
|
18433
|
-
const existing =
|
|
18637
|
+
const target = join9(cwd, PROJECT_CONFIG_FILENAME);
|
|
18638
|
+
const existing = existsSync7(target) ? readProjectConfigAt(target) : null;
|
|
18434
18639
|
const interactive = isInteractive(args);
|
|
18435
18640
|
if (existing && !args.force && !interactive) {
|
|
18436
18641
|
throw new Error(
|
|
@@ -18482,9 +18687,9 @@ var init_init = __esm({
|
|
|
18482
18687
|
});
|
|
18483
18688
|
|
|
18484
18689
|
// src/commands/configure.ts
|
|
18485
|
-
import { homedir as
|
|
18486
|
-
import { existsSync as
|
|
18487
|
-
import { join as
|
|
18690
|
+
import { homedir as homedir6 } from "os";
|
|
18691
|
+
import { existsSync as existsSync8 } from "fs";
|
|
18692
|
+
import { join as join10, sep } from "path";
|
|
18488
18693
|
async function resolveTools(args) {
|
|
18489
18694
|
if (args.all) return [...ALL_TOOLS];
|
|
18490
18695
|
if (args.tool) {
|
|
@@ -18545,12 +18750,14 @@ function pathHint(tools, scope) {
|
|
|
18545
18750
|
switch (tool) {
|
|
18546
18751
|
case "claude-code":
|
|
18547
18752
|
return relativizeHomeAndCwd(claudeSettingsPath(scope));
|
|
18753
|
+
case "cline":
|
|
18754
|
+
return relativizeHomeAndCwd(clineSettingsPath(scope));
|
|
18548
18755
|
case "opencode":
|
|
18549
18756
|
return relativizeHomeAndCwd(opencodeSettingsPath(scope));
|
|
18550
18757
|
}
|
|
18551
18758
|
}
|
|
18552
18759
|
function relativizeHomeAndCwd(absolutePath) {
|
|
18553
|
-
const home =
|
|
18760
|
+
const home = homedir6();
|
|
18554
18761
|
const cwd = userCwd();
|
|
18555
18762
|
if (home && absolutePath.startsWith(`${home}${sep}`)) {
|
|
18556
18763
|
return `~${absolutePath.slice(home.length)}`;
|
|
@@ -18564,7 +18771,7 @@ function isTool(value) {
|
|
|
18564
18771
|
return ALL_TOOLS.includes(value);
|
|
18565
18772
|
}
|
|
18566
18773
|
function mergeToolsIntoProjectConfig(cwd, tools, gatewayUrl) {
|
|
18567
|
-
const path =
|
|
18774
|
+
const path = join10(cwd, PROJECT_CONFIG_FILENAME);
|
|
18568
18775
|
const existing = readProjectConfigAt(path) ?? {};
|
|
18569
18776
|
const byTool = new Map((existing.tools ?? []).map((c) => [c.tool, c]));
|
|
18570
18777
|
for (const cfg of tools) byTool.set(cfg.tool, cfg);
|
|
@@ -18584,7 +18791,7 @@ async function fetchReachableChatModels2(creds) {
|
|
|
18584
18791
|
s.start("Loading reachable models\u2026");
|
|
18585
18792
|
try {
|
|
18586
18793
|
const res = await call(parseResponse(client.models.$get()));
|
|
18587
|
-
const models = res.data.filter((m2) => m2.kind === "chat").map((m2) => ({
|
|
18794
|
+
const models = res.data.filter((m2) => m2.kind === "chat" && m2.callable).map((m2) => ({
|
|
18588
18795
|
slug: m2.slug,
|
|
18589
18796
|
providerKind: m2.providerKind,
|
|
18590
18797
|
displayName: m2.displayName,
|
|
@@ -18658,12 +18865,14 @@ var init_configure = __esm({
|
|
|
18658
18865
|
init_project_config();
|
|
18659
18866
|
init_shell_hook();
|
|
18660
18867
|
init_claude_code();
|
|
18868
|
+
init_cline();
|
|
18661
18869
|
init_opencode();
|
|
18662
18870
|
WRITERS2 = {
|
|
18663
18871
|
"claude-code": writeClaudeCodeConfig,
|
|
18872
|
+
cline: writeClineConfig,
|
|
18664
18873
|
opencode: writeOpencodeConfig
|
|
18665
18874
|
};
|
|
18666
|
-
ALL_TOOLS = ["claude-code", "opencode"];
|
|
18875
|
+
ALL_TOOLS = ["claude-code", "cline", "opencode"];
|
|
18667
18876
|
SCOPES = ["local", "project"];
|
|
18668
18877
|
configureCommand = defineCommand({
|
|
18669
18878
|
meta: {
|
|
@@ -18699,7 +18908,7 @@ var init_configure = __esm({
|
|
|
18699
18908
|
);
|
|
18700
18909
|
}
|
|
18701
18910
|
const cwd = userCwd();
|
|
18702
|
-
if (process.stdout.isTTY && !
|
|
18911
|
+
if (process.stdout.isTTY && !existsSync8(join10(cwd, PROJECT_CONFIG_FILENAME))) {
|
|
18703
18912
|
const runInit = unwrap(
|
|
18704
18913
|
await ue({
|
|
18705
18914
|
message: `No ${PROJECT_CONFIG_FILENAME} in this repo yet. Run \`codevector init\` first to set it up (project name, ticket pattern, gateway)?`,
|
|
@@ -18907,12 +19116,15 @@ var authLoginCommand = defineCommand({
|
|
|
18907
19116
|
apiKey,
|
|
18908
19117
|
gatewayUrl: config2.gatewayPublicUrl,
|
|
18909
19118
|
userId: me2.user.id,
|
|
18910
|
-
email: me2.user.email
|
|
19119
|
+
email: me2.user.email,
|
|
19120
|
+
...me2.attributionGroupId ? { groupId: me2.attributionGroupId, groupName: me2.attributionGroupName ?? void 0 } : {}
|
|
18911
19121
|
});
|
|
19122
|
+
const billingNote = stored.groupName ? `Billing: group "${stored.groupName}"` : "Billing: personal";
|
|
18912
19123
|
s.stop(`Signed in as ${stored.email} (${profileName})`);
|
|
18913
19124
|
Se(
|
|
18914
19125
|
`Gateway: ${stored.gatewayUrl}
|
|
18915
19126
|
Profile: ${profileName}
|
|
19127
|
+
${billingNote}
|
|
18916
19128
|
Next: run \`codevector configure claude-code\`.`,
|
|
18917
19129
|
"Credentials saved"
|
|
18918
19130
|
);
|
|
@@ -19074,9 +19286,7 @@ var configSyncCommand = defineCommand({
|
|
|
19074
19286
|
ge("codevector sync");
|
|
19075
19287
|
const found = readProjectConfig(userCwd());
|
|
19076
19288
|
if (!found) {
|
|
19077
|
-
throw new Error(
|
|
19078
|
-
"No .codevector.json on the path to root. Run `codevector init` first."
|
|
19079
|
-
);
|
|
19289
|
+
throw new Error("No .codevector.json on the path to root. Run `codevector init` first.");
|
|
19080
19290
|
}
|
|
19081
19291
|
const { config: config2, path: manifestPath } = found;
|
|
19082
19292
|
R2.info(`Manifest: ${manifestPath}`);
|
|
@@ -19098,7 +19308,7 @@ var configSyncCommand = defineCommand({
|
|
|
19098
19308
|
`Active profile "${profiles.activeProfile}" is missing from credentials. Run \`codevector auth login\`.`
|
|
19099
19309
|
);
|
|
19100
19310
|
}
|
|
19101
|
-
if (config2.gateway &&
|
|
19311
|
+
if (config2.gateway && trimRightSlash5(creds.gatewayUrl) !== trimRightSlash5(config2.gateway)) {
|
|
19102
19312
|
R2.warn(
|
|
19103
19313
|
`Active profile points at ${creds.gatewayUrl} but manifest pins ${config2.gateway}. Sync will use the active profile.`
|
|
19104
19314
|
);
|
|
@@ -19183,7 +19393,7 @@ async function fetchReachableChatModels(creds) {
|
|
|
19183
19393
|
s.start("Loading reachable models\u2026");
|
|
19184
19394
|
try {
|
|
19185
19395
|
const res = await call(parseResponse(client.models.$get()));
|
|
19186
|
-
const models = res.data.filter((m2) => m2.kind === "chat").map((m2) => ({
|
|
19396
|
+
const models = res.data.filter((m2) => m2.kind === "chat" && m2.callable).map((m2) => ({
|
|
19187
19397
|
slug: m2.slug,
|
|
19188
19398
|
providerKind: m2.providerKind,
|
|
19189
19399
|
displayName: m2.displayName,
|
|
@@ -19214,7 +19424,7 @@ async function fetchReachableChatModels(creds) {
|
|
|
19214
19424
|
return [];
|
|
19215
19425
|
}
|
|
19216
19426
|
}
|
|
19217
|
-
function
|
|
19427
|
+
function trimRightSlash5(url2) {
|
|
19218
19428
|
return url2.endsWith("/") ? url2.slice(0, -1) : url2;
|
|
19219
19429
|
}
|
|
19220
19430
|
|
|
@@ -19237,25 +19447,26 @@ init_dist();
|
|
|
19237
19447
|
init_dist5();
|
|
19238
19448
|
init_api_client();
|
|
19239
19449
|
init_claude_code();
|
|
19450
|
+
init_cline();
|
|
19240
19451
|
init_opencode();
|
|
19241
19452
|
init_credentials();
|
|
19242
|
-
import { existsSync as
|
|
19243
|
-
import { join as
|
|
19453
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "fs";
|
|
19454
|
+
import { join as join13 } from "path";
|
|
19244
19455
|
|
|
19245
19456
|
// src/lib/install-pref.ts
|
|
19246
19457
|
init_paths();
|
|
19247
|
-
import { existsSync as
|
|
19248
|
-
import { join as
|
|
19458
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync9, realpathSync, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
19459
|
+
import { join as join11, sep as sep2 } from "path";
|
|
19249
19460
|
import { fileURLToPath } from "url";
|
|
19250
|
-
var INSTALL_PREF_FILE =
|
|
19461
|
+
var INSTALL_PREF_FILE = join11(CODEVECTOR_CONFIG_DIR, "install.json");
|
|
19251
19462
|
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn"];
|
|
19252
19463
|
function isPackageManager(v2) {
|
|
19253
19464
|
return typeof v2 === "string" && PACKAGE_MANAGERS.includes(v2);
|
|
19254
19465
|
}
|
|
19255
19466
|
function readInstallPref() {
|
|
19256
|
-
if (!
|
|
19467
|
+
if (!existsSync9(INSTALL_PREF_FILE)) return void 0;
|
|
19257
19468
|
try {
|
|
19258
|
-
const raw =
|
|
19469
|
+
const raw = readFileSync9(INSTALL_PREF_FILE, "utf8");
|
|
19259
19470
|
const parsed = JSON.parse(raw);
|
|
19260
19471
|
if (!isPackageManager(parsed.packageManager)) return void 0;
|
|
19261
19472
|
const source = parsed.source === "user" ? "user" : "auto";
|
|
@@ -19269,9 +19480,9 @@ function readInstallPref() {
|
|
|
19269
19480
|
}
|
|
19270
19481
|
}
|
|
19271
19482
|
function writeInstallPref(pref) {
|
|
19272
|
-
|
|
19483
|
+
mkdirSync5(CODEVECTOR_CONFIG_DIR, { recursive: true, mode: 448 });
|
|
19273
19484
|
const tmp = `${INSTALL_PREF_FILE}.${process.pid}.tmp`;
|
|
19274
|
-
|
|
19485
|
+
writeFileSync7(tmp, JSON.stringify(pref, null, 2));
|
|
19275
19486
|
renameSync3(tmp, INSTALL_PREF_FILE);
|
|
19276
19487
|
}
|
|
19277
19488
|
function packageManagerFromPath(installPath) {
|
|
@@ -19314,6 +19525,147 @@ init_project_config();
|
|
|
19314
19525
|
init_paths();
|
|
19315
19526
|
init_shell();
|
|
19316
19527
|
init_shell_hook();
|
|
19528
|
+
|
|
19529
|
+
// src/lib/version-check.ts
|
|
19530
|
+
init_api_client();
|
|
19531
|
+
|
|
19532
|
+
// src/lib/update-notifier.ts
|
|
19533
|
+
init_paths();
|
|
19534
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
19535
|
+
import { join as join12 } from "path";
|
|
19536
|
+
var PKG_NAME = "@codevector/cli";
|
|
19537
|
+
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
19538
|
+
var CHECK_CACHE_FILE = join12(CODEVECTOR_CONFIG_DIR, "update-check.json");
|
|
19539
|
+
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
19540
|
+
var FETCH_TIMEOUT_MS = 2e3;
|
|
19541
|
+
function readCache() {
|
|
19542
|
+
if (!existsSync10(CHECK_CACHE_FILE)) return void 0;
|
|
19543
|
+
try {
|
|
19544
|
+
const raw = readFileSync10(CHECK_CACHE_FILE, "utf8");
|
|
19545
|
+
const parsed = JSON.parse(raw);
|
|
19546
|
+
if (typeof parsed.checkedAt !== "number") return void 0;
|
|
19547
|
+
return {
|
|
19548
|
+
latest: typeof parsed.latest === "string" ? parsed.latest : null,
|
|
19549
|
+
checkedAt: parsed.checkedAt
|
|
19550
|
+
};
|
|
19551
|
+
} catch {
|
|
19552
|
+
return void 0;
|
|
19553
|
+
}
|
|
19554
|
+
}
|
|
19555
|
+
function readCachedLatestVersion() {
|
|
19556
|
+
return readCache()?.latest ?? null;
|
|
19557
|
+
}
|
|
19558
|
+
function writeCache(cache) {
|
|
19559
|
+
try {
|
|
19560
|
+
mkdirSync6(CODEVECTOR_CONFIG_DIR, { recursive: true, mode: 448 });
|
|
19561
|
+
writeFileSync8(CHECK_CACHE_FILE, JSON.stringify(cache));
|
|
19562
|
+
} catch {
|
|
19563
|
+
}
|
|
19564
|
+
}
|
|
19565
|
+
function isNewer(current, latest) {
|
|
19566
|
+
const a = current.split(".").map((p2) => Number.parseInt(p2, 10));
|
|
19567
|
+
const b2 = latest.split(".").map((p2) => Number.parseInt(p2, 10));
|
|
19568
|
+
for (let i = 0; i < Math.max(a.length, b2.length); i++) {
|
|
19569
|
+
const ai = a[i] ?? 0;
|
|
19570
|
+
const bi = b2[i] ?? 0;
|
|
19571
|
+
if (Number.isNaN(ai) || Number.isNaN(bi)) return latest > current;
|
|
19572
|
+
if (bi > ai) return true;
|
|
19573
|
+
if (bi < ai) return false;
|
|
19574
|
+
}
|
|
19575
|
+
return false;
|
|
19576
|
+
}
|
|
19577
|
+
function printUpdateNoticeIfCached(currentVersion) {
|
|
19578
|
+
if (process.env.CODEVECTOR_NO_UPDATE_CHECK === "1") return;
|
|
19579
|
+
try {
|
|
19580
|
+
const cache = readCache();
|
|
19581
|
+
if (!cache || !cache.latest) return;
|
|
19582
|
+
if (isNewer(currentVersion, cache.latest)) {
|
|
19583
|
+
process.stderr.write(
|
|
19584
|
+
`\x1B[33m\u203A\x1B[0m A new version of @codevector/cli is available: ${currentVersion} \u2192 ${cache.latest}. Run \`codevector update\` to install.
|
|
19585
|
+
`
|
|
19586
|
+
);
|
|
19587
|
+
}
|
|
19588
|
+
} catch {
|
|
19589
|
+
}
|
|
19590
|
+
}
|
|
19591
|
+
function scheduleUpdateCheck() {
|
|
19592
|
+
if (process.env.CODEVECTOR_NO_UPDATE_CHECK === "1") return;
|
|
19593
|
+
const cache = readCache();
|
|
19594
|
+
const now = Date.now();
|
|
19595
|
+
if (cache && now - cache.checkedAt < CHECK_TTL_MS) return;
|
|
19596
|
+
void fetchLatestVersion().then((latest) => {
|
|
19597
|
+
writeCache({ latest, checkedAt: Date.now() });
|
|
19598
|
+
}).catch(() => {
|
|
19599
|
+
writeCache({ latest: cache?.latest ?? null, checkedAt: Date.now() });
|
|
19600
|
+
});
|
|
19601
|
+
}
|
|
19602
|
+
async function fetchLatestVersion() {
|
|
19603
|
+
const controller = new AbortController();
|
|
19604
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
19605
|
+
timer.unref?.();
|
|
19606
|
+
try {
|
|
19607
|
+
const res = await fetch(REGISTRY_URL, {
|
|
19608
|
+
signal: controller.signal,
|
|
19609
|
+
headers: { accept: "application/json" }
|
|
19610
|
+
});
|
|
19611
|
+
if (!res.ok) return null;
|
|
19612
|
+
const body = await res.json();
|
|
19613
|
+
return typeof body.version === "string" ? body.version : null;
|
|
19614
|
+
} finally {
|
|
19615
|
+
clearTimeout(timer);
|
|
19616
|
+
}
|
|
19617
|
+
}
|
|
19618
|
+
|
|
19619
|
+
// src/lib/version-check.ts
|
|
19620
|
+
init_package();
|
|
19621
|
+
var MIN_SERVER_VERSION = "";
|
|
19622
|
+
var IncompatibleVersionError = class extends Error {
|
|
19623
|
+
constructor(message) {
|
|
19624
|
+
super(message);
|
|
19625
|
+
this.name = "IncompatibleVersionError";
|
|
19626
|
+
}
|
|
19627
|
+
};
|
|
19628
|
+
function evaluateCompatibility(input) {
|
|
19629
|
+
const { cliVersion, minServerVersion, health } = input;
|
|
19630
|
+
if (health.minCliVersion && isNewer(cliVersion, health.minCliVersion)) {
|
|
19631
|
+
return `This CLI (${cliVersion}) is too old for the gateway, which requires >= ${health.minCliVersion}. Run \`codevector update\` to upgrade.`;
|
|
19632
|
+
}
|
|
19633
|
+
if (minServerVersion) {
|
|
19634
|
+
if (!health.version) {
|
|
19635
|
+
return `This CLI requires a gateway >= ${minServerVersion}, but the gateway is too old to report its version. Ask your admin to upgrade the gateway.`;
|
|
19636
|
+
}
|
|
19637
|
+
if (isNewer(health.version, minServerVersion)) {
|
|
19638
|
+
return `This CLI requires a gateway >= ${minServerVersion}, but the gateway is ${health.version}. Ask your admin to upgrade the gateway.`;
|
|
19639
|
+
}
|
|
19640
|
+
}
|
|
19641
|
+
return null;
|
|
19642
|
+
}
|
|
19643
|
+
async function fetchHealthVersion(baseUrl) {
|
|
19644
|
+
try {
|
|
19645
|
+
const res = await healthClient(baseUrl).$get();
|
|
19646
|
+
if (!res.ok) return null;
|
|
19647
|
+
const body = await res.json();
|
|
19648
|
+
return {
|
|
19649
|
+
version: typeof body.version === "string" ? body.version : void 0,
|
|
19650
|
+
minCliVersion: typeof body.minCliVersion === "string" ? body.minCliVersion : void 0
|
|
19651
|
+
};
|
|
19652
|
+
} catch {
|
|
19653
|
+
return null;
|
|
19654
|
+
}
|
|
19655
|
+
}
|
|
19656
|
+
async function checkServerCompatibility(baseUrl) {
|
|
19657
|
+
const health = await fetchHealthVersion(baseUrl);
|
|
19658
|
+
if (!health) return void 0;
|
|
19659
|
+
const problem = evaluateCompatibility({
|
|
19660
|
+
cliVersion: package_default.version,
|
|
19661
|
+
minServerVersion: MIN_SERVER_VERSION,
|
|
19662
|
+
health
|
|
19663
|
+
});
|
|
19664
|
+
if (problem) throw new IncompatibleVersionError(problem);
|
|
19665
|
+
return health.version;
|
|
19666
|
+
}
|
|
19667
|
+
|
|
19668
|
+
// src/commands/doctor.ts
|
|
19317
19669
|
var CLAUDE_SCOPE_ORDER = ["local", "project", "user"];
|
|
19318
19670
|
var doctorCommand = defineCommand({
|
|
19319
19671
|
meta: {
|
|
@@ -19335,6 +19687,21 @@ var doctorCommand = defineCommand({
|
|
|
19335
19687
|
label: "credentials",
|
|
19336
19688
|
detail: `${creds.email} @ ${creds.gatewayUrl} (${maskApiKey(creds.apiKey)})`
|
|
19337
19689
|
});
|
|
19690
|
+
checks.push({
|
|
19691
|
+
level: "ok",
|
|
19692
|
+
label: "billing",
|
|
19693
|
+
detail: creds.groupName ? `group "${creds.groupName}"` : "personal"
|
|
19694
|
+
});
|
|
19695
|
+
const pinned = readProjectConfig(userCwd())?.config.groups ?? [];
|
|
19696
|
+
if (pinned.length > 0) {
|
|
19697
|
+
const activeGroup = creds.groupName ?? null;
|
|
19698
|
+
const matches = activeGroup != null && pinned.includes(activeGroup);
|
|
19699
|
+
checks.push({
|
|
19700
|
+
level: matches ? "ok" : "warn",
|
|
19701
|
+
label: "repo group binding",
|
|
19702
|
+
detail: matches ? `active key bills "${activeGroup}" (pinned: ${pinned.join(", ")})` : `pinned ${pinned.join(", ")} but active key bills ${activeGroup ?? "personal"} \u2014 run \`codevector use\``
|
|
19703
|
+
});
|
|
19704
|
+
}
|
|
19338
19705
|
if (process.platform !== "win32") {
|
|
19339
19706
|
checks.push(
|
|
19340
19707
|
credentialsFileModeOk() ? { level: "ok", label: "credentials permissions", detail: "chmod 600" } : {
|
|
@@ -19360,6 +19727,22 @@ var doctorCommand = defineCommand({
|
|
|
19360
19727
|
const message = err instanceof ApiClientError ? `${err.code}: ${err.message}` : String(err);
|
|
19361
19728
|
checks.push({ level: "fail", label: "gateway /me", detail: message });
|
|
19362
19729
|
}
|
|
19730
|
+
try {
|
|
19731
|
+
const serverVersion = await checkServerCompatibility(creds.gatewayUrl);
|
|
19732
|
+
checks.push(
|
|
19733
|
+
serverVersion ? { level: "ok", label: "gateway version", detail: serverVersion } : {
|
|
19734
|
+
level: "warn",
|
|
19735
|
+
label: "gateway version",
|
|
19736
|
+
detail: "gateway did not report a version \u2014 it predates version reporting"
|
|
19737
|
+
}
|
|
19738
|
+
);
|
|
19739
|
+
} catch (err) {
|
|
19740
|
+
checks.push({
|
|
19741
|
+
level: "fail",
|
|
19742
|
+
label: "gateway version",
|
|
19743
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
19744
|
+
});
|
|
19745
|
+
}
|
|
19363
19746
|
}
|
|
19364
19747
|
checks.push(inspectClaudeSettings());
|
|
19365
19748
|
checks.push(inspectShellHook());
|
|
@@ -19391,9 +19774,9 @@ function inspectUpdateManager() {
|
|
|
19391
19774
|
function inspectClaudeSettings() {
|
|
19392
19775
|
for (const scope of CLAUDE_SCOPE_ORDER) {
|
|
19393
19776
|
const path = claudeSettingsPath(scope);
|
|
19394
|
-
if (!
|
|
19777
|
+
if (!existsSync11(path)) continue;
|
|
19395
19778
|
try {
|
|
19396
|
-
const raw = JSON.parse(
|
|
19779
|
+
const raw = JSON.parse(readFileSync11(path, "utf8"));
|
|
19397
19780
|
if (typeof raw.env?.ANTHROPIC_BASE_URL === "string") {
|
|
19398
19781
|
return {
|
|
19399
19782
|
level: "ok",
|
|
@@ -19461,7 +19844,7 @@ function inspectManifestDrift() {
|
|
|
19461
19844
|
});
|
|
19462
19845
|
continue;
|
|
19463
19846
|
}
|
|
19464
|
-
if (!
|
|
19847
|
+
if (!existsSync11(path)) {
|
|
19465
19848
|
checks.push({
|
|
19466
19849
|
level: "fail",
|
|
19467
19850
|
label: `manifest drift: ${tool.tool}`,
|
|
@@ -19471,7 +19854,7 @@ function inspectManifestDrift() {
|
|
|
19471
19854
|
}
|
|
19472
19855
|
let raw;
|
|
19473
19856
|
try {
|
|
19474
|
-
raw =
|
|
19857
|
+
raw = readFileSync11(path, "utf8");
|
|
19475
19858
|
} catch (err) {
|
|
19476
19859
|
checks.push({
|
|
19477
19860
|
level: "fail",
|
|
@@ -19501,7 +19884,7 @@ function pruneAcceptanceHook() {
|
|
|
19501
19884
|
const cleaned = [];
|
|
19502
19885
|
for (const scope of CLAUDE_SCOPE_ORDER) {
|
|
19503
19886
|
const path = claudeSettingsPath(scope);
|
|
19504
|
-
if (!
|
|
19887
|
+
if (!existsSync11(path)) continue;
|
|
19505
19888
|
try {
|
|
19506
19889
|
if (removeAcceptanceHookFromSettings(path)) cleaned.push(scope);
|
|
19507
19890
|
} catch {
|
|
@@ -19514,8 +19897,8 @@ function pruneAcceptanceHook() {
|
|
|
19514
19897
|
detail: `cleared stale hook from claude-code settings [${cleaned.join(", ")}]`
|
|
19515
19898
|
});
|
|
19516
19899
|
}
|
|
19517
|
-
const legacyHooksDir =
|
|
19518
|
-
if (
|
|
19900
|
+
const legacyHooksDir = join13(CODEVECTOR_CONFIG_DIR, "hooks");
|
|
19901
|
+
if (existsSync11(legacyHooksDir)) {
|
|
19519
19902
|
try {
|
|
19520
19903
|
rmSync2(legacyHooksDir, { recursive: true, force: true });
|
|
19521
19904
|
checks.push({
|
|
@@ -19529,7 +19912,7 @@ function pruneAcceptanceHook() {
|
|
|
19529
19912
|
return checks;
|
|
19530
19913
|
}
|
|
19531
19914
|
function removeAcceptanceHookFromSettings(path) {
|
|
19532
|
-
const parsed = JSON.parse(
|
|
19915
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
19533
19916
|
const hooks = parsed.hooks;
|
|
19534
19917
|
if (!isPlainObject3(hooks)) return false;
|
|
19535
19918
|
let mutated = false;
|
|
@@ -19543,7 +19926,7 @@ function removeAcceptanceHookFromSettings(path) {
|
|
|
19543
19926
|
else hooks[event] = entries;
|
|
19544
19927
|
}
|
|
19545
19928
|
if (Object.keys(hooks).length === 0) delete parsed.hooks;
|
|
19546
|
-
if (mutated)
|
|
19929
|
+
if (mutated) writeFileSync9(path, `${JSON.stringify(parsed, null, 2)}
|
|
19547
19930
|
`);
|
|
19548
19931
|
return mutated;
|
|
19549
19932
|
}
|
|
@@ -19584,6 +19967,8 @@ function manifestToolPath(tool, scope) {
|
|
|
19584
19967
|
switch (tool) {
|
|
19585
19968
|
case "claude-code":
|
|
19586
19969
|
return claudeSettingsPath(scope);
|
|
19970
|
+
case "cline":
|
|
19971
|
+
return clineSettingsPath(scope);
|
|
19587
19972
|
case "opencode":
|
|
19588
19973
|
return opencodeSettingsPath(scope);
|
|
19589
19974
|
default:
|
|
@@ -19611,7 +19996,7 @@ init_credentials();
|
|
|
19611
19996
|
init_paths();
|
|
19612
19997
|
init_project_config();
|
|
19613
19998
|
init_shell();
|
|
19614
|
-
import { dirname as
|
|
19999
|
+
import { dirname as dirname6 } from "path";
|
|
19615
20000
|
var envCommand = defineCommand({
|
|
19616
20001
|
meta: {
|
|
19617
20002
|
name: "env",
|
|
@@ -19629,7 +20014,7 @@ var envCommand = defineCommand({
|
|
|
19629
20014
|
const cwd = userCwd();
|
|
19630
20015
|
const previousDir = process.env.CODEVECTOR_ACTIVE_DIR ?? null;
|
|
19631
20016
|
const found = readProjectConfig(cwd);
|
|
19632
|
-
const repoDir = found?.path ?
|
|
20017
|
+
const repoDir = found?.path ? dirname6(found.path) : null;
|
|
19633
20018
|
const gateway = found?.config.gateway ?? null;
|
|
19634
20019
|
const projectName = found?.config.projectName ?? null;
|
|
19635
20020
|
const lines = [];
|
|
@@ -19638,16 +20023,25 @@ var envCommand = defineCommand({
|
|
|
19638
20023
|
process.stdout.write(lines.join("\n") + (lines.length > 0 ? "\n" : ""));
|
|
19639
20024
|
return;
|
|
19640
20025
|
}
|
|
19641
|
-
if (previousDir === repoDir) {
|
|
19642
|
-
return;
|
|
19643
|
-
}
|
|
19644
20026
|
const profiles = await readProfiles();
|
|
19645
|
-
const
|
|
20027
|
+
const repoGroups = found?.config.groups ?? null;
|
|
20028
|
+
const resolution = resolveProfileForRepo(
|
|
19646
20029
|
profiles?.profiles ?? {},
|
|
19647
20030
|
profiles?.activeProfile ?? null,
|
|
19648
|
-
gateway
|
|
20031
|
+
gateway,
|
|
20032
|
+
repoGroups
|
|
19649
20033
|
);
|
|
19650
|
-
if (
|
|
20034
|
+
if (resolution.kind === "no-group-key") {
|
|
20035
|
+
if (previousDir) emitDeactivate(lines, shell, previousDir);
|
|
20036
|
+
emitEcho(
|
|
20037
|
+
lines,
|
|
20038
|
+
shell,
|
|
20039
|
+
`codevector: this repo bills group(s) "${resolution.groups.join(", ")}" but you have no key for any. Run \`codevector sync\` or \`codevector use\`.`
|
|
20040
|
+
);
|
|
20041
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
20042
|
+
return;
|
|
20043
|
+
}
|
|
20044
|
+
if (resolution.kind === "none") {
|
|
19651
20045
|
if (previousDir) emitDeactivate(lines, shell, previousDir);
|
|
19652
20046
|
emitEcho(
|
|
19653
20047
|
lines,
|
|
@@ -19657,12 +20051,26 @@ var envCommand = defineCommand({
|
|
|
19657
20051
|
process.stdout.write(lines.join("\n") + "\n");
|
|
19658
20052
|
return;
|
|
19659
20053
|
}
|
|
19660
|
-
const { name: profileName, profile } =
|
|
20054
|
+
const { name: profileName, profile } = resolution;
|
|
20055
|
+
const previousProfile = process.env.CODEVECTOR_ACTIVE_PROFILE ?? null;
|
|
20056
|
+
if (!shouldReexport(previousDir, repoDir, previousProfile, profileName)) {
|
|
20057
|
+
return;
|
|
20058
|
+
}
|
|
19661
20059
|
const headers = buildAnthropicCustomHeaders(projectName);
|
|
19662
|
-
emitExport(
|
|
20060
|
+
emitExport(
|
|
20061
|
+
lines,
|
|
20062
|
+
shell,
|
|
20063
|
+
"ANTHROPIC_BASE_URL",
|
|
20064
|
+
`${trimRightSlash6(profile.gatewayUrl)}/gateway/anthropic`
|
|
20065
|
+
);
|
|
19663
20066
|
emitExport(lines, shell, "ANTHROPIC_API_KEY", profile.apiKey);
|
|
19664
20067
|
if (headers) emitExport(lines, shell, "ANTHROPIC_CUSTOM_HEADERS", headers);
|
|
19665
|
-
emitExport(
|
|
20068
|
+
emitExport(
|
|
20069
|
+
lines,
|
|
20070
|
+
shell,
|
|
20071
|
+
"OPENAI_BASE_URL",
|
|
20072
|
+
`${trimRightSlash6(profile.gatewayUrl)}/gateway/openai/v1`
|
|
20073
|
+
);
|
|
19666
20074
|
emitExport(lines, shell, "OPENAI_API_KEY", profile.apiKey);
|
|
19667
20075
|
emitExport(lines, shell, "CODEVECTOR_ACTIVE_DIR", repoDir);
|
|
19668
20076
|
emitExport(lines, shell, "CODEVECTOR_ACTIVE_PROFILE", profileName);
|
|
@@ -19670,11 +20078,14 @@ var envCommand = defineCommand({
|
|
|
19670
20078
|
emitEcho(
|
|
19671
20079
|
lines,
|
|
19672
20080
|
shell,
|
|
19673
|
-
`codevector: ${profileName} -> ${
|
|
20081
|
+
`codevector: ${profileName} -> ${trimRightSlash6(profile.gatewayUrl)}${projectSuffix}`
|
|
19674
20082
|
);
|
|
19675
20083
|
process.stdout.write(lines.join("\n") + "\n");
|
|
19676
20084
|
}
|
|
19677
20085
|
});
|
|
20086
|
+
function shouldReexport(previousDir, repoDir, previousProfile, pickedProfile) {
|
|
20087
|
+
return !(previousDir === repoDir && previousProfile === pickedProfile);
|
|
20088
|
+
}
|
|
19678
20089
|
function resolveShell(raw) {
|
|
19679
20090
|
if (!raw) return detectShell() ?? "bash";
|
|
19680
20091
|
if (!SHELLS.includes(raw)) {
|
|
@@ -19728,7 +20139,7 @@ function quoteForShell(shell, value) {
|
|
|
19728
20139
|
}
|
|
19729
20140
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
19730
20141
|
}
|
|
19731
|
-
function
|
|
20142
|
+
function trimRightSlash6(url2) {
|
|
19732
20143
|
return url2.endsWith("/") ? url2.slice(0, -1) : url2;
|
|
19733
20144
|
}
|
|
19734
20145
|
function buildAnthropicCustomHeaders(projectName) {
|
|
@@ -19737,19 +20148,24 @@ function buildAnthropicCustomHeaders(projectName) {
|
|
|
19737
20148
|
if (!safe) return null;
|
|
19738
20149
|
return `x-project: ${safe}`;
|
|
19739
20150
|
}
|
|
19740
|
-
function
|
|
19741
|
-
const target =
|
|
19742
|
-
const
|
|
19743
|
-
([, p2]) =>
|
|
20151
|
+
function resolveProfileForRepo(profiles, activeProfile, gateway, repoGroups) {
|
|
20152
|
+
const target = trimRightSlash6(gateway);
|
|
20153
|
+
const gatewayMatches = Object.entries(profiles).filter(
|
|
20154
|
+
([, p2]) => trimRightSlash6(p2.gatewayUrl) === target
|
|
19744
20155
|
);
|
|
19745
|
-
if (
|
|
20156
|
+
if (gatewayMatches.length === 0) return { kind: "none" };
|
|
20157
|
+
const groups = repoGroups?.filter((g) => g.length > 0) ?? [];
|
|
20158
|
+
const pool = groups.length === 0 ? gatewayMatches : gatewayMatches.filter(([, p2]) => p2.groupName != null && groups.includes(p2.groupName));
|
|
20159
|
+
if (pool.length === 0) return { kind: "no-group-key", groups };
|
|
20160
|
+
const picked = preferActive(pool, activeProfile);
|
|
20161
|
+
return { kind: "match", name: picked[0], profile: picked[1] };
|
|
20162
|
+
}
|
|
20163
|
+
function preferActive(pool, activeProfile) {
|
|
19746
20164
|
if (activeProfile) {
|
|
19747
|
-
const active =
|
|
19748
|
-
if (active) return
|
|
20165
|
+
const active = pool.find(([name]) => name === activeProfile);
|
|
20166
|
+
if (active) return active;
|
|
19749
20167
|
}
|
|
19750
|
-
|
|
19751
|
-
const picked = sorted[0];
|
|
19752
|
-
return { name: picked[0], profile: picked[1] };
|
|
20168
|
+
return [...pool].sort((a, b2) => a[0].localeCompare(b2[0]))[0];
|
|
19753
20169
|
}
|
|
19754
20170
|
|
|
19755
20171
|
// src/commands/github/index.ts
|
|
@@ -19762,46 +20178,42 @@ init_api_client();
|
|
|
19762
20178
|
init_credentials();
|
|
19763
20179
|
init_paths();
|
|
19764
20180
|
init_prompt();
|
|
19765
|
-
import { existsSync as
|
|
19766
|
-
import { join as
|
|
20181
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
20182
|
+
import { join as join14 } from "path";
|
|
19767
20183
|
|
|
19768
20184
|
// src/commands/github/workflow-yaml.ts
|
|
19769
20185
|
function generateWorkflowYaml(opts) {
|
|
19770
20186
|
const baseUrl = opts.gatewayUrl.replace(/\/$/, "");
|
|
19771
20187
|
const sanitized = { ...opts, gatewayUrl: baseUrl };
|
|
19772
|
-
|
|
20188
|
+
switch (sanitized.tool) {
|
|
20189
|
+
case "claude-code":
|
|
20190
|
+
return claudeCodeWorkflow(sanitized);
|
|
20191
|
+
case "cline":
|
|
20192
|
+
return clineWorkflow(sanitized);
|
|
20193
|
+
case "opencode":
|
|
20194
|
+
return opencodeWorkflow(sanitized);
|
|
20195
|
+
}
|
|
20196
|
+
}
|
|
20197
|
+
function prNumberExpr() {
|
|
20198
|
+
return "${{ github.event.pull_request.number || github.event.issue.number }}";
|
|
20199
|
+
}
|
|
20200
|
+
function baseRefExpr() {
|
|
20201
|
+
return "${{ github.base_ref }}";
|
|
19773
20202
|
}
|
|
19774
20203
|
function claudeCodeWorkflow(opts) {
|
|
19775
|
-
const modelEnv =
|
|
19776
|
-
|
|
19777
|
-
|
|
19778
|
-
|
|
19779
|
-
`# Routes all PR review requests through the CodeVector gateway.`,
|
|
19780
|
-
`name: CodeVector PR Review (Claude Code)`,
|
|
19781
|
-
``,
|
|
19782
|
-
`on:`,
|
|
19783
|
-
` pull_request:`,
|
|
19784
|
-
` types: [opened, synchronize]`,
|
|
19785
|
-
` issue_comment:`,
|
|
19786
|
-
` types: [created]`,
|
|
19787
|
-
` pull_request_review_comment:`,
|
|
19788
|
-
` types: [created]`,
|
|
19789
|
-
``,
|
|
19790
|
-
`concurrency:`,
|
|
19791
|
-
` group: codevector-pr-review-claude-code-\${{ github.event.pull_request.number || github.event.issue.number }}`,
|
|
19792
|
-
` cancel-in-progress: true`,
|
|
19793
|
-
``,
|
|
19794
|
-
`permissions:`,
|
|
19795
|
-
` contents: read`,
|
|
19796
|
-
` pull-requests: write`,
|
|
19797
|
-
``,
|
|
19798
|
-
`jobs:`,
|
|
20204
|
+
const modelEnv = ` ANTHROPIC_MODEL: ${opts.model || "claude-sonnet-4-6"}
|
|
20205
|
+
`;
|
|
20206
|
+
const concurrencyGroup = `codevector-pr-review-claude-code-${prNumberExpr()}`;
|
|
20207
|
+
const reviewJob = [
|
|
19799
20208
|
` review:`,
|
|
19800
20209
|
` if: |`,
|
|
19801
20210
|
` github.event_name == 'pull_request' ||`,
|
|
19802
20211
|
` (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@claude')) ||`,
|
|
19803
20212
|
` (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude'))`,
|
|
19804
20213
|
` runs-on: ubuntu-latest`,
|
|
20214
|
+
` concurrency:`,
|
|
20215
|
+
` group: ${concurrencyGroup}`,
|
|
20216
|
+
` cancel-in-progress: true`,
|
|
19805
20217
|
` steps:`,
|
|
19806
20218
|
` - uses: actions/checkout@v4`,
|
|
19807
20219
|
` - uses: actions/setup-node@v4`,
|
|
@@ -19816,14 +20228,153 @@ function claudeCodeWorkflow(opts) {
|
|
|
19816
20228
|
` run: |`,
|
|
19817
20229
|
` claude --dangerously-skip-permissions -p "Review this PR and provide concise, actionable feedback. Focus on bugs, security issues, and code quality." \\`,
|
|
19818
20230
|
` --output-format text \\`,
|
|
19819
|
-
` --allowedTools "Bash(git diff origin
|
|
20231
|
+
` --allowedTools "Bash(git diff origin/${baseRefExpr()}...HEAD),Bash(git log origin/${baseRefExpr()}..HEAD),Read" \\`,
|
|
19820
20232
|
` > review.md`,
|
|
19821
20233
|
` - name: Post review comment`,
|
|
19822
20234
|
` env:`,
|
|
19823
20235
|
` GH_TOKEN: \${{ github.token }}`,
|
|
19824
20236
|
` run: |`,
|
|
19825
|
-
` gh pr comment
|
|
19826
|
-
].join("\n")
|
|
20237
|
+
` gh pr comment ${prNumberExpr()} --body-file review.md`
|
|
20238
|
+
].join("\n");
|
|
20239
|
+
const skillJobs = (opts.skills ?? []).map(
|
|
20240
|
+
(name) => claudeCodeSkillJob(opts, name)
|
|
20241
|
+
);
|
|
20242
|
+
return workflowHeader("Claude Code", [reviewJob, ...skillJobs]);
|
|
20243
|
+
}
|
|
20244
|
+
function claudeCodeSkillJob(opts, skillName) {
|
|
20245
|
+
const outputFile = `skill-${skillName}.md`;
|
|
20246
|
+
const modelEnv = ` ANTHROPIC_MODEL: ${opts.model || "claude-sonnet-4-6"}
|
|
20247
|
+
`;
|
|
20248
|
+
return [
|
|
20249
|
+
` skill-${skillName}:`,
|
|
20250
|
+
` if: |`,
|
|
20251
|
+
` github.event_name == 'pull_request' ||`,
|
|
20252
|
+
` (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@skill:${skillName}')) ||`,
|
|
20253
|
+
` (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@skill:${skillName}'))`,
|
|
20254
|
+
` runs-on: ubuntu-latest`,
|
|
20255
|
+
` concurrency:`,
|
|
20256
|
+
` group: codevector-skill-${skillName}-${prNumberExpr()}`,
|
|
20257
|
+
` cancel-in-progress: true`,
|
|
20258
|
+
` steps:`,
|
|
20259
|
+
` - uses: actions/checkout@v4`,
|
|
20260
|
+
` - uses: actions/setup-node@v4`,
|
|
20261
|
+
` with:`,
|
|
20262
|
+
` node-version: '24'`,
|
|
20263
|
+
` - run: npm install -g @anthropic-ai/claude-code @codevector/cli`,
|
|
20264
|
+
` - name: Authenticate with CodeVector`,
|
|
20265
|
+
` env:`,
|
|
20266
|
+
` CODEVECTOR_API_KEY: \${{ secrets.${opts.secretName} }}`,
|
|
20267
|
+
` run: |`,
|
|
20268
|
+
` codevector auth login \\`,
|
|
20269
|
+
` --gateway-url ${opts.gatewayUrl} \\`,
|
|
20270
|
+
` --api-key "$CODEVECTOR_API_KEY" \\`,
|
|
20271
|
+
` --profile ci`,
|
|
20272
|
+
` - name: Sync skill pack`,
|
|
20273
|
+
` run: codevector skills sync ${skillName}`,
|
|
20274
|
+
` - name: Review PR with skill`,
|
|
20275
|
+
` env:`,
|
|
20276
|
+
` ANTHROPIC_API_KEY: \${{ secrets.${opts.secretName} }}`,
|
|
20277
|
+
` ANTHROPIC_BASE_URL: ${opts.gatewayUrl}/gateway/anthropic`,
|
|
20278
|
+
`${modelEnv}`,
|
|
20279
|
+
` run: |`,
|
|
20280
|
+
` claude --dangerously-skip-permissions -p "Review this PR using the ${skillName} skill. Write your findings to ${outputFile}." \\`,
|
|
20281
|
+
` --output-format text \\`,
|
|
20282
|
+
` --allowedTools "Bash(git diff origin/${baseRefExpr()}...HEAD),Bash(git log origin/${baseRefExpr()}..HEAD),Read" \\`,
|
|
20283
|
+
` > ${outputFile}`,
|
|
20284
|
+
` - name: Post review comment`,
|
|
20285
|
+
` env:`,
|
|
20286
|
+
` GH_TOKEN: \${{ github.token }}`,
|
|
20287
|
+
` run: |`,
|
|
20288
|
+
` gh pr comment ${prNumberExpr()} --body-file ${outputFile}`
|
|
20289
|
+
].join("\n");
|
|
20290
|
+
}
|
|
20291
|
+
function clineWorkflow(opts) {
|
|
20292
|
+
const modelFlag = opts.model ? ` --modelid "${opts.model}"` : "";
|
|
20293
|
+
const concurrencyGroup = `codevector-pr-review-cline-${prNumberExpr()}`;
|
|
20294
|
+
const reviewJob = [
|
|
20295
|
+
` review:`,
|
|
20296
|
+
` if: |`,
|
|
20297
|
+
` github.event_name == 'pull_request' ||`,
|
|
20298
|
+
` (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@cline')) ||`,
|
|
20299
|
+
` (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@cline'))`,
|
|
20300
|
+
` runs-on: ubuntu-latest`,
|
|
20301
|
+
` concurrency:`,
|
|
20302
|
+
` group: ${concurrencyGroup}`,
|
|
20303
|
+
` cancel-in-progress: true`,
|
|
20304
|
+
` steps:`,
|
|
20305
|
+
` - uses: actions/checkout@v4`,
|
|
20306
|
+
` - uses: actions/setup-node@v4`,
|
|
20307
|
+
` with:`,
|
|
20308
|
+
` node-version: '24'`,
|
|
20309
|
+
` - run: npm install -g cline`,
|
|
20310
|
+
` - name: Configure Cline auth`,
|
|
20311
|
+
` run: |`,
|
|
20312
|
+
` cline auth --provider openai-compatible --apikey "\${{ secrets.${opts.secretName} }}" --baseurl "${opts.gatewayUrl}/gateway/openai/v1"${modelFlag}`,
|
|
20313
|
+
` - name: Review PR`,
|
|
20314
|
+
` env:`,
|
|
20315
|
+
` GH_TOKEN: \${{ github.token }}`,
|
|
20316
|
+
` CLINE_COMMAND_PERMISSIONS: |`,
|
|
20317
|
+
` {`,
|
|
20318
|
+
` "allow": ["gh pr diff *", "gh pr view *", "git log *"],`,
|
|
20319
|
+
` "deny": ["rm -rf *", "sudo *"]`,
|
|
20320
|
+
` }`,
|
|
20321
|
+
` run: |`,
|
|
20322
|
+
` cline --auto-approve true "Review this PR. Fetch the diff with \`gh pr diff \${{ github.event.pull_request.number }}\`, analyze the changes, and post a single comprehensive review comment using \`gh pr comment\`."`
|
|
20323
|
+
].join("\n");
|
|
20324
|
+
const skillJobs = (opts.skills ?? []).map(
|
|
20325
|
+
(name) => clineSkillJob(opts, name)
|
|
20326
|
+
);
|
|
20327
|
+
return workflowHeader("Cline", [reviewJob, ...skillJobs]);
|
|
20328
|
+
}
|
|
20329
|
+
function clineSkillJob(opts, skillName) {
|
|
20330
|
+
const modelFlag = opts.model ? ` --modelid "${opts.model}"` : "";
|
|
20331
|
+
const outputFile = `skill-${skillName}.md`;
|
|
20332
|
+
return [
|
|
20333
|
+
` skill-${skillName}:`,
|
|
20334
|
+
` if: |`,
|
|
20335
|
+
` github.event_name == 'pull_request' ||`,
|
|
20336
|
+
` (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@skill:${skillName}')) ||`,
|
|
20337
|
+
` (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@skill:${skillName}'))`,
|
|
20338
|
+
` runs-on: ubuntu-latest`,
|
|
20339
|
+
` concurrency:`,
|
|
20340
|
+
` group: codevector-skill-${skillName}-${prNumberExpr()}`,
|
|
20341
|
+
` cancel-in-progress: true`,
|
|
20342
|
+
` steps:`,
|
|
20343
|
+
` - uses: actions/checkout@v4`,
|
|
20344
|
+
` - uses: actions/setup-node@v4`,
|
|
20345
|
+
` with:`,
|
|
20346
|
+
` node-version: '24'`,
|
|
20347
|
+
` - run: npm install -g cline @codevector/cli`,
|
|
20348
|
+
` - name: Authenticate with CodeVector`,
|
|
20349
|
+
` env:`,
|
|
20350
|
+
` CODEVECTOR_API_KEY: \${{ secrets.${opts.secretName} }}`,
|
|
20351
|
+
` run: |`,
|
|
20352
|
+
` codevector auth login \\`,
|
|
20353
|
+
` --gateway-url ${opts.gatewayUrl} \\`,
|
|
20354
|
+
` --api-key "$CODEVECTOR_API_KEY" \\`,
|
|
20355
|
+
` --profile ci`,
|
|
20356
|
+
` - name: Sync skill pack`,
|
|
20357
|
+
` run: codevector skills sync ${skillName}`,
|
|
20358
|
+
` - name: Configure Cline auth`,
|
|
20359
|
+
` run: |`,
|
|
20360
|
+
` cline auth --provider openai-compatible --apikey "\${{ secrets.${opts.secretName} }}" --baseurl "${opts.gatewayUrl}/gateway/openai/v1"${modelFlag}`,
|
|
20361
|
+
` - name: Review PR with skill`,
|
|
20362
|
+
` env:`,
|
|
20363
|
+
` GH_TOKEN: \${{ github.token }}`,
|
|
20364
|
+
` CLINE_COMMAND_PERMISSIONS: |`,
|
|
20365
|
+
` {`,
|
|
20366
|
+
` "allow": ["gh pr diff *", "gh pr view *", "git log *"],`,
|
|
20367
|
+
` "deny": ["rm -rf *", "sudo *"]`,
|
|
20368
|
+
` }`,
|
|
20369
|
+
` run: |`,
|
|
20370
|
+
` cline --auto-approve true "Review this PR using the ${skillName} skill. Write your findings to ${outputFile}." \\`,
|
|
20371
|
+
` > ${outputFile}`,
|
|
20372
|
+
` - name: Post review comment`,
|
|
20373
|
+
` env:`,
|
|
20374
|
+
` GH_TOKEN: \${{ github.token }}`,
|
|
20375
|
+
` run: |`,
|
|
20376
|
+
` gh pr comment ${prNumberExpr()} --body-file ${outputFile}`
|
|
20377
|
+
].join("\n");
|
|
19827
20378
|
}
|
|
19828
20379
|
function opencodeModelEntryLines(model) {
|
|
19829
20380
|
const name = model.split("-").map((w3) => w3.charAt(0).toUpperCase() + w3.slice(1)).join(" ");
|
|
@@ -19837,34 +20388,17 @@ function opencodeModelEntryLines(model) {
|
|
|
19837
20388
|
}
|
|
19838
20389
|
function opencodeWorkflow(opts) {
|
|
19839
20390
|
const model = opts.model || "deepseek-pro";
|
|
19840
|
-
|
|
19841
|
-
|
|
19842
|
-
`# Routes all PR review requests through the CodeVector gateway via OpenCode CLI.`,
|
|
19843
|
-
`name: CodeVector PR Review (OpenCode)`,
|
|
19844
|
-
``,
|
|
19845
|
-
`on:`,
|
|
19846
|
-
` pull_request:`,
|
|
19847
|
-
` types: [opened, synchronize]`,
|
|
19848
|
-
` issue_comment:`,
|
|
19849
|
-
` types: [created]`,
|
|
19850
|
-
` pull_request_review_comment:`,
|
|
19851
|
-
` types: [created]`,
|
|
19852
|
-
``,
|
|
19853
|
-
`concurrency:`,
|
|
19854
|
-
` group: codevector-pr-review-opencode-\${{ github.event.pull_request.number || github.event.issue.number }}`,
|
|
19855
|
-
` cancel-in-progress: true`,
|
|
19856
|
-
``,
|
|
19857
|
-
`permissions:`,
|
|
19858
|
-
` contents: read`,
|
|
19859
|
-
` pull-requests: write`,
|
|
19860
|
-
``,
|
|
19861
|
-
`jobs:`,
|
|
20391
|
+
const concurrencyGroup = `codevector-pr-review-opencode-${prNumberExpr()}`;
|
|
20392
|
+
const reviewJob = [
|
|
19862
20393
|
` review:`,
|
|
19863
20394
|
` if: |`,
|
|
19864
20395
|
` github.event_name == 'pull_request' ||`,
|
|
19865
20396
|
` (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@opencode')) ||`,
|
|
19866
20397
|
` (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@opencode'))`,
|
|
19867
20398
|
` runs-on: ubuntu-latest`,
|
|
20399
|
+
` concurrency:`,
|
|
20400
|
+
` group: ${concurrencyGroup}`,
|
|
20401
|
+
` cancel-in-progress: true`,
|
|
19868
20402
|
` steps:`,
|
|
19869
20403
|
` - uses: actions/checkout@v4`,
|
|
19870
20404
|
` - name: Install OpenCode CLI`,
|
|
@@ -19894,7 +20428,7 @@ function opencodeWorkflow(opts) {
|
|
|
19894
20428
|
` env:`,
|
|
19895
20429
|
` GH_TOKEN: \${{ github.token }}`,
|
|
19896
20430
|
` run: |`,
|
|
19897
|
-
` PR_NUMBER
|
|
20431
|
+
` PR_NUMBER=${prNumberExpr()}`,
|
|
19898
20432
|
` gh pr diff $PR_NUMBER > pr_diff.txt`,
|
|
19899
20433
|
` - name: Fetch AI Review`,
|
|
19900
20434
|
` env:`,
|
|
@@ -19910,15 +20444,114 @@ function opencodeWorkflow(opts) {
|
|
|
19910
20444
|
` env:`,
|
|
19911
20445
|
` GH_TOKEN: \${{ github.token }}`,
|
|
19912
20446
|
` run: |`,
|
|
19913
|
-
` PR_NUMBER
|
|
20447
|
+
` PR_NUMBER=${prNumberExpr()}`,
|
|
19914
20448
|
` gh pr comment $PR_NUMBER --body-file review.md`
|
|
20449
|
+
].join("\n");
|
|
20450
|
+
const skillJobs = (opts.skills ?? []).map(
|
|
20451
|
+
(name) => opencodeSkillJob(opts, name, model)
|
|
20452
|
+
);
|
|
20453
|
+
return workflowHeader("OpenCode", [reviewJob, ...skillJobs]);
|
|
20454
|
+
}
|
|
20455
|
+
function opencodeSkillJob(opts, skillName, model) {
|
|
20456
|
+
const outputFile = `skill-${skillName}.md`;
|
|
20457
|
+
return [
|
|
20458
|
+
` skill-${skillName}:`,
|
|
20459
|
+
` if: |`,
|
|
20460
|
+
` github.event_name == 'pull_request' ||`,
|
|
20461
|
+
` (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@skill:${skillName}')) ||`,
|
|
20462
|
+
` (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@skill:${skillName}'))`,
|
|
20463
|
+
` runs-on: ubuntu-latest`,
|
|
20464
|
+
` concurrency:`,
|
|
20465
|
+
` group: codevector-skill-${skillName}-${prNumberExpr()}`,
|
|
20466
|
+
` cancel-in-progress: true`,
|
|
20467
|
+
` steps:`,
|
|
20468
|
+
` - uses: actions/checkout@v4`,
|
|
20469
|
+
` - name: Install OpenCode CLI`,
|
|
20470
|
+
` run: curl -fsSL https://opencode.ai/install | bash`,
|
|
20471
|
+
` - run: npm install -g @codevector/cli`,
|
|
20472
|
+
` - name: Authenticate with CodeVector`,
|
|
20473
|
+
` env:`,
|
|
20474
|
+
` CODEVECTOR_API_KEY: \${{ secrets.${opts.secretName} }}`,
|
|
20475
|
+
` run: |`,
|
|
20476
|
+
` codevector auth login \\`,
|
|
20477
|
+
` --gateway-url ${opts.gatewayUrl} \\`,
|
|
20478
|
+
` --api-key "$CODEVECTOR_API_KEY" \\`,
|
|
20479
|
+
` --profile ci`,
|
|
20480
|
+
` - name: Sync skill pack`,
|
|
20481
|
+
` run: codevector skills sync ${skillName}`,
|
|
20482
|
+
` - name: Configure OpenCode`,
|
|
20483
|
+
` run: |`,
|
|
20484
|
+
` cat > opencode.json << 'OPEOF'`,
|
|
20485
|
+
` {`,
|
|
20486
|
+
` "$schema": "https://opencode.ai/config.json",`,
|
|
20487
|
+
` "provider": {`,
|
|
20488
|
+
` "codevector": {`,
|
|
20489
|
+
` "options": {`,
|
|
20490
|
+
` "apiKey": "{env:CODEVECTOR_API_KEY}",`,
|
|
20491
|
+
` "baseURL": "${opts.gatewayUrl}/gateway/openai/v1",`,
|
|
20492
|
+
` "headers": {`,
|
|
20493
|
+
` "x-client-app": "opencode",`,
|
|
20494
|
+
` "x-project": "codevector"`,
|
|
20495
|
+
` }`,
|
|
20496
|
+
` },`,
|
|
20497
|
+
...opencodeModelEntryLines(model),
|
|
20498
|
+
` }`,
|
|
20499
|
+
` },`,
|
|
20500
|
+
` "model": "codevector/${model}"`,
|
|
20501
|
+
` }`,
|
|
20502
|
+
` OPEOF`,
|
|
20503
|
+
` - name: Generate PR Diff`,
|
|
20504
|
+
` env:`,
|
|
20505
|
+
` GH_TOKEN: \${{ github.token }}`,
|
|
20506
|
+
` run: |`,
|
|
20507
|
+
` PR_NUMBER=${prNumberExpr()}`,
|
|
20508
|
+
` gh pr diff $PR_NUMBER > pr_diff.txt`,
|
|
20509
|
+
` - name: Fetch AI Review with skill`,
|
|
20510
|
+
` env:`,
|
|
20511
|
+
` CODEVECTOR_API_KEY: \${{ secrets.${opts.secretName} }}`,
|
|
20512
|
+
` run: |`,
|
|
20513
|
+
` export PATH="$HOME/.opencode/bin:$PATH"`,
|
|
20514
|
+
` opencode run --model codevector/${model} \\`,
|
|
20515
|
+
` --dangerously-skip-permissions \\`,
|
|
20516
|
+
` "Review this PR using the ${skillName} skill. Write your findings to ${outputFile}." \\`,
|
|
20517
|
+
` -f pr_diff.txt \\`,
|
|
20518
|
+
` > ${outputFile}`,
|
|
20519
|
+
` - name: Post review comment`,
|
|
20520
|
+
` env:`,
|
|
20521
|
+
` GH_TOKEN: \${{ github.token }}`,
|
|
20522
|
+
` run: |`,
|
|
20523
|
+
` PR_NUMBER=${prNumberExpr()}`,
|
|
20524
|
+
` gh pr comment $PR_NUMBER --body-file ${outputFile}`
|
|
20525
|
+
].join("\n");
|
|
20526
|
+
}
|
|
20527
|
+
function workflowHeader(toolLabel, jobs) {
|
|
20528
|
+
return [
|
|
20529
|
+
`# Generated by codevector github init. Do not edit manually.`,
|
|
20530
|
+
`# Routes all PR review requests through the CodeVector gateway.`,
|
|
20531
|
+
`name: CodeVector PR Review (${toolLabel})`,
|
|
20532
|
+
``,
|
|
20533
|
+
`on:`,
|
|
20534
|
+
` pull_request:`,
|
|
20535
|
+
` types: [opened, synchronize]`,
|
|
20536
|
+
` issue_comment:`,
|
|
20537
|
+
` types: [created]`,
|
|
20538
|
+
` pull_request_review_comment:`,
|
|
20539
|
+
` types: [created]`,
|
|
20540
|
+
``,
|
|
20541
|
+
`permissions:`,
|
|
20542
|
+
` contents: read`,
|
|
20543
|
+
` pull-requests: write`,
|
|
20544
|
+
``,
|
|
20545
|
+
`jobs:`,
|
|
20546
|
+
...jobs
|
|
19915
20547
|
].join("\n").trimEnd() + "\n";
|
|
19916
20548
|
}
|
|
19917
20549
|
|
|
19918
20550
|
// src/commands/github/init.ts
|
|
19919
|
-
var ALL_TOOLS2 = ["claude-code", "opencode"];
|
|
20551
|
+
var ALL_TOOLS2 = ["claude-code", "cline", "opencode"];
|
|
19920
20552
|
var WORKFLOW_FILENAMES = {
|
|
19921
20553
|
"claude-code": "codevector-pr-review-claude-code.yml",
|
|
20554
|
+
cline: "codevector-pr-review-cline.yml",
|
|
19922
20555
|
opencode: "codevector-pr-review-opencode.yml"
|
|
19923
20556
|
};
|
|
19924
20557
|
var githubInitCommand = defineCommand({
|
|
@@ -19929,8 +20562,8 @@ var githubInitCommand = defineCommand({
|
|
|
19929
20562
|
args: {
|
|
19930
20563
|
tool: {
|
|
19931
20564
|
type: "string",
|
|
19932
|
-
description: "Tool: claude-code or opencode. Prompted if omitted.",
|
|
19933
|
-
valueHint: "claude-code|opencode"
|
|
20565
|
+
description: "Tool: claude-code, cline, or opencode. Prompted if omitted.",
|
|
20566
|
+
valueHint: "claude-code|cline|opencode"
|
|
19934
20567
|
},
|
|
19935
20568
|
model: {
|
|
19936
20569
|
type: "string",
|
|
@@ -19954,6 +20587,10 @@ var githubInitCommand = defineCommand({
|
|
|
19954
20587
|
type: "boolean",
|
|
19955
20588
|
default: false,
|
|
19956
20589
|
description: "Print the workflow to stdout instead of writing to disk."
|
|
20590
|
+
},
|
|
20591
|
+
skills: {
|
|
20592
|
+
type: "string",
|
|
20593
|
+
description: "Comma-separated list of skill pack names to generate CI jobs for."
|
|
19957
20594
|
}
|
|
19958
20595
|
},
|
|
19959
20596
|
async run({ args }) {
|
|
@@ -19964,11 +20601,13 @@ var githubInitCommand = defineCommand({
|
|
|
19964
20601
|
const tool = await resolveTool(args, interactive);
|
|
19965
20602
|
const model = await resolveModel(args, tool, gatewayUrl, profile, interactive);
|
|
19966
20603
|
const secretName = args["secret-name"] ?? "CODEVECTOR_API_KEY";
|
|
20604
|
+
const skills = args.skills ? args.skills.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
19967
20605
|
const yaml = generateWorkflowYaml({
|
|
19968
20606
|
tool,
|
|
19969
20607
|
gatewayUrl: gatewayUrl.replace(/\/$/, ""),
|
|
19970
20608
|
secretName,
|
|
19971
|
-
...model ? { model } : {}
|
|
20609
|
+
...model ? { model } : {},
|
|
20610
|
+
...skills ? { skills } : {}
|
|
19972
20611
|
});
|
|
19973
20612
|
if (args["dry-run"]) {
|
|
19974
20613
|
process.stdout.write(yaml);
|
|
@@ -19982,9 +20621,9 @@ var githubInitCommand = defineCommand({
|
|
|
19982
20621
|
return;
|
|
19983
20622
|
}
|
|
19984
20623
|
const cwd = userCwd();
|
|
19985
|
-
const workflowsDir =
|
|
19986
|
-
const filePath =
|
|
19987
|
-
if (
|
|
20624
|
+
const workflowsDir = join14(cwd, ".github", "workflows");
|
|
20625
|
+
const filePath = join14(workflowsDir, WORKFLOW_FILENAMES[tool]);
|
|
20626
|
+
if (existsSync12(filePath) && !args.force) {
|
|
19988
20627
|
if (!interactive) {
|
|
19989
20628
|
throw new Error(
|
|
19990
20629
|
`${filePath} already exists. Pass --force to overwrite.`
|
|
@@ -20001,8 +20640,8 @@ var githubInitCommand = defineCommand({
|
|
|
20001
20640
|
return;
|
|
20002
20641
|
}
|
|
20003
20642
|
}
|
|
20004
|
-
|
|
20005
|
-
|
|
20643
|
+
mkdirSync7(workflowsDir, { recursive: true });
|
|
20644
|
+
writeFileSync10(filePath, yaml, { mode: 420 });
|
|
20006
20645
|
if (interactive) {
|
|
20007
20646
|
R2.success(`Wrote ${filePath}`);
|
|
20008
20647
|
Se(
|
|
@@ -20018,7 +20657,7 @@ var githubInitCommand = defineCommand({
|
|
|
20018
20657
|
});
|
|
20019
20658
|
function isInteractive2(args) {
|
|
20020
20659
|
if (!process.stdout.isTTY) return false;
|
|
20021
|
-
return !args.tool || !args["gateway-url"];
|
|
20660
|
+
return !args.tool || !args["gateway-url"] || !args.model;
|
|
20022
20661
|
}
|
|
20023
20662
|
function resolveGatewayUrl(args, profile, interactive) {
|
|
20024
20663
|
if (args["gateway-url"]) {
|
|
@@ -20057,14 +20696,17 @@ async function resolveTool(args, interactive) {
|
|
|
20057
20696
|
message: "Which tool should the workflow use for PR review?",
|
|
20058
20697
|
options: [
|
|
20059
20698
|
{ value: "claude-code", label: "Claude Code", hint: "uses the @anthropic-ai/claude-code CLI" },
|
|
20699
|
+
{ value: "cline", label: "Cline", hint: "uses the cline CLI (npm i -g cline)" },
|
|
20060
20700
|
{ value: "opencode", label: "OpenCode", hint: "uses the OpenCode CLI" }
|
|
20061
20701
|
],
|
|
20062
20702
|
initialValue: "claude-code"
|
|
20063
20703
|
})
|
|
20064
20704
|
);
|
|
20065
20705
|
}
|
|
20066
|
-
async function resolveModel(args,
|
|
20706
|
+
async function resolveModel(args, tool, gatewayUrl, profile, interactive) {
|
|
20067
20707
|
if (args.model) return args.model;
|
|
20708
|
+
const configuredModel = profile?.toolConfigs?.find((c) => c.tool === tool)?.modelSlug;
|
|
20709
|
+
if (configuredModel) return configuredModel;
|
|
20068
20710
|
if (!interactive) return void 0;
|
|
20069
20711
|
const models = profile ? await fetchReachableChatModels3(gatewayUrl, profile.apiKey) : [];
|
|
20070
20712
|
if (models.length === 0) {
|
|
@@ -20094,7 +20736,7 @@ async function fetchReachableChatModels3(gatewayUrl, apiKey) {
|
|
|
20094
20736
|
s.start("Loading reachable models\u2026");
|
|
20095
20737
|
try {
|
|
20096
20738
|
const res = await call(parseResponse(client.models.$get()));
|
|
20097
|
-
const models = res.data.filter((m2) => m2.kind === "chat").map((m2) => ({
|
|
20739
|
+
const models = res.data.filter((m2) => m2.kind === "chat" && m2.callable).map((m2) => ({
|
|
20098
20740
|
slug: m2.slug,
|
|
20099
20741
|
providerKind: m2.providerKind,
|
|
20100
20742
|
displayName: m2.displayName
|
|
@@ -20124,6 +20766,82 @@ var githubCommand = defineCommand({
|
|
|
20124
20766
|
}
|
|
20125
20767
|
});
|
|
20126
20768
|
|
|
20769
|
+
// src/commands/groups.ts
|
|
20770
|
+
init_dist();
|
|
20771
|
+
init_dist5();
|
|
20772
|
+
init_api_client();
|
|
20773
|
+
init_credentials();
|
|
20774
|
+
|
|
20775
|
+
// src/lib/table.ts
|
|
20776
|
+
function renderTable(headers, rows) {
|
|
20777
|
+
const widths = headers.map(
|
|
20778
|
+
(h2, i) => Math.max(h2.length, ...rows.map((row) => (row[i] ?? "").length))
|
|
20779
|
+
);
|
|
20780
|
+
const pad = (s, i) => s.padEnd(widths[i] ?? 0);
|
|
20781
|
+
const divider = widths.map((w3) => "\u2500".repeat(w3)).join(" ");
|
|
20782
|
+
return [headers.map(pad).join(" "), divider, ...rows.map((row) => row.map(pad).join(" "))].join(
|
|
20783
|
+
"\n"
|
|
20784
|
+
);
|
|
20785
|
+
}
|
|
20786
|
+
|
|
20787
|
+
// src/commands/groups.ts
|
|
20788
|
+
var groupsListCommand = defineCommand({
|
|
20789
|
+
meta: {
|
|
20790
|
+
name: "list",
|
|
20791
|
+
description: "List groups you can mint a key for."
|
|
20792
|
+
},
|
|
20793
|
+
args: {
|
|
20794
|
+
json: {
|
|
20795
|
+
type: "boolean",
|
|
20796
|
+
description: "Emit raw JSON (for scripting)."
|
|
20797
|
+
}
|
|
20798
|
+
},
|
|
20799
|
+
async run({ args }) {
|
|
20800
|
+
const active = await getActiveProfile();
|
|
20801
|
+
if (!active) {
|
|
20802
|
+
R2.warn("Not signed in. Run `codevector auth login` to get started.");
|
|
20803
|
+
process.exitCode = 1;
|
|
20804
|
+
return;
|
|
20805
|
+
}
|
|
20806
|
+
const client = gatewayClient(active.gatewayUrl, active.apiKey, 1e4);
|
|
20807
|
+
const s = args.json ? null : ft();
|
|
20808
|
+
s?.start("Loading groups\u2026");
|
|
20809
|
+
try {
|
|
20810
|
+
const res = await call(parseResponse(client.groups.$get()));
|
|
20811
|
+
s?.stop(`${res.data.length} group${res.data.length === 1 ? "" : "s"}`);
|
|
20812
|
+
if (args.json) {
|
|
20813
|
+
process.stdout.write(`${JSON.stringify(res.data, null, 2)}
|
|
20814
|
+
`);
|
|
20815
|
+
return;
|
|
20816
|
+
}
|
|
20817
|
+
if (res.data.length === 0) {
|
|
20818
|
+
R2.info("You are not a member of any groups. Ask an admin to add you.");
|
|
20819
|
+
return;
|
|
20820
|
+
}
|
|
20821
|
+
Se(
|
|
20822
|
+
renderTable(
|
|
20823
|
+
["NAME", "ID"],
|
|
20824
|
+
res.data.map((g) => [g.name, g.id])
|
|
20825
|
+
),
|
|
20826
|
+
"Your groups"
|
|
20827
|
+
);
|
|
20828
|
+
} catch (err) {
|
|
20829
|
+
s?.stop("Could not load groups");
|
|
20830
|
+
R2.error(err instanceof ApiClientError ? err.message : String(err));
|
|
20831
|
+
process.exitCode = 1;
|
|
20832
|
+
}
|
|
20833
|
+
}
|
|
20834
|
+
});
|
|
20835
|
+
var groupsCommand = defineCommand({
|
|
20836
|
+
meta: {
|
|
20837
|
+
name: "groups",
|
|
20838
|
+
description: "List the groups you can mint keys for."
|
|
20839
|
+
},
|
|
20840
|
+
subCommands: {
|
|
20841
|
+
list: groupsListCommand
|
|
20842
|
+
}
|
|
20843
|
+
});
|
|
20844
|
+
|
|
20127
20845
|
// src/commands/hook.ts
|
|
20128
20846
|
init_dist();
|
|
20129
20847
|
init_shell();
|
|
@@ -20199,10 +20917,11 @@ _codevector_on_chpwd
|
|
|
20199
20917
|
`;
|
|
20200
20918
|
var FISH_SNIPPET = `# codevector shell hook (fish) \u2014 auto-activates credentials on cd
|
|
20201
20919
|
function _codevector_on_pwd --on-variable PWD
|
|
20202
|
-
|
|
20203
|
-
|
|
20204
|
-
|
|
20205
|
-
|
|
20920
|
+
# Pipe straight to \`source\`. Capturing into a variable and re-running it
|
|
20921
|
+
# breaks here: fish command substitution splits on newlines and re-joins
|
|
20922
|
+
# with spaces, collapsing the multi-line export script into one broken
|
|
20923
|
+
# \`set\` command.
|
|
20924
|
+
command codevector env --shell fish 2>/dev/null | source
|
|
20206
20925
|
end
|
|
20207
20926
|
# Run once for the current directory at shell startup.
|
|
20208
20927
|
_codevector_on_pwd
|
|
@@ -20227,23 +20946,132 @@ if (-not $global:_CODEVECTOR_PROMPT_WRAPPED) {
|
|
|
20227
20946
|
// src/index.ts
|
|
20228
20947
|
init_init();
|
|
20229
20948
|
|
|
20230
|
-
// src/commands/
|
|
20949
|
+
// src/commands/keys.ts
|
|
20231
20950
|
init_dist();
|
|
20232
20951
|
init_dist5();
|
|
20233
20952
|
init_api_client();
|
|
20234
20953
|
init_credentials();
|
|
20954
|
+
var sortProfileNames = (names) => names.sort((a, b2) => {
|
|
20955
|
+
if (a === "default") return -1;
|
|
20956
|
+
if (b2 === "default") return 1;
|
|
20957
|
+
return a.localeCompare(b2);
|
|
20958
|
+
});
|
|
20959
|
+
var keysCreateCommand = defineCommand({
|
|
20960
|
+
meta: {
|
|
20961
|
+
name: "create",
|
|
20962
|
+
description: "Mint a group-scoped key and save it as a profile."
|
|
20963
|
+
},
|
|
20964
|
+
args: {
|
|
20965
|
+
group: {
|
|
20966
|
+
type: "string",
|
|
20967
|
+
description: "Name of the group to bill (you must be a member).",
|
|
20968
|
+
required: true
|
|
20969
|
+
},
|
|
20970
|
+
name: {
|
|
20971
|
+
type: "string",
|
|
20972
|
+
description: "Optional label for the new key."
|
|
20973
|
+
}
|
|
20974
|
+
},
|
|
20975
|
+
async run({ args }) {
|
|
20976
|
+
const active = await getActiveProfile();
|
|
20977
|
+
if (!active) {
|
|
20978
|
+
R2.warn("Not signed in. Run `codevector auth login` to get started.");
|
|
20979
|
+
process.exitCode = 1;
|
|
20980
|
+
return;
|
|
20981
|
+
}
|
|
20982
|
+
const client = gatewayClient(active.gatewayUrl, active.apiKey, 1e4);
|
|
20983
|
+
const s = ft();
|
|
20984
|
+
s.start(`Minting a key for group "${args.group}"\u2026`);
|
|
20985
|
+
try {
|
|
20986
|
+
const groupsRes = await call(parseResponse(client.groups.$get()));
|
|
20987
|
+
const group = groupsRes.data.find((g) => g.name === args.group);
|
|
20988
|
+
if (!group) {
|
|
20989
|
+
s.stop("Group not found");
|
|
20990
|
+
R2.error(
|
|
20991
|
+
`You are not a member of a group named "${args.group}". Run \`codevector groups list\` to see yours.`
|
|
20992
|
+
);
|
|
20993
|
+
process.exitCode = 1;
|
|
20994
|
+
return;
|
|
20995
|
+
}
|
|
20996
|
+
const res = await call(
|
|
20997
|
+
parseResponse(
|
|
20998
|
+
client.keys.$post({
|
|
20999
|
+
json: { groupId: group.id, ...args.name ? { name: args.name } : {} }
|
|
21000
|
+
})
|
|
21001
|
+
)
|
|
21002
|
+
);
|
|
21003
|
+
const minted = res.key;
|
|
21004
|
+
const profiles = await readProfiles();
|
|
21005
|
+
const previousActive = profiles?.activeProfile;
|
|
21006
|
+
writeProfile(group.name, {
|
|
21007
|
+
apiKey: minted.plaintext,
|
|
21008
|
+
gatewayUrl: active.gatewayUrl,
|
|
21009
|
+
userId: active.userId,
|
|
21010
|
+
email: active.email,
|
|
21011
|
+
groupId: group.id,
|
|
21012
|
+
groupName: group.name,
|
|
21013
|
+
keyId: minted.id
|
|
21014
|
+
});
|
|
21015
|
+
if (previousActive && previousActive !== group.name) {
|
|
21016
|
+
await setActiveProfile(previousActive);
|
|
21017
|
+
}
|
|
21018
|
+
s.stop(`Minted a group key and saved profile "${group.name}".`);
|
|
21019
|
+
Se(
|
|
21020
|
+
`Profile "${group.name}" bills group "${group.name}".
|
|
21021
|
+
Run \`codevector profile switch\` and pick "${group.name}" to point your editors at it.`,
|
|
21022
|
+
"Group key created"
|
|
21023
|
+
);
|
|
21024
|
+
} catch (err) {
|
|
21025
|
+
s.stop("Could not mint group key");
|
|
21026
|
+
R2.error(err instanceof ApiClientError ? err.message : String(err));
|
|
21027
|
+
process.exitCode = 1;
|
|
21028
|
+
}
|
|
21029
|
+
}
|
|
21030
|
+
});
|
|
21031
|
+
var keysListCommand = defineCommand({
|
|
21032
|
+
meta: {
|
|
21033
|
+
name: "list",
|
|
21034
|
+
description: "List saved keys/profiles and the group each one bills."
|
|
21035
|
+
},
|
|
21036
|
+
async run() {
|
|
21037
|
+
const profiles = await readProfiles();
|
|
21038
|
+
if (!profiles || Object.keys(profiles.profiles).length === 0) {
|
|
21039
|
+
R2.info(
|
|
21040
|
+
"No keys saved. Run `codevector auth login`, or `codevector keys create --group <name>`."
|
|
21041
|
+
);
|
|
21042
|
+
return;
|
|
21043
|
+
}
|
|
21044
|
+
const names = sortProfileNames(Object.keys(profiles.profiles));
|
|
21045
|
+
const headers = ["", "PROFILE", "GROUP", "KEY", "GATEWAY"];
|
|
21046
|
+
const cells = names.map((name) => {
|
|
21047
|
+
const p2 = profiles.profiles[name];
|
|
21048
|
+
return [
|
|
21049
|
+
name === profiles.activeProfile ? "*" : "",
|
|
21050
|
+
name,
|
|
21051
|
+
p2.groupName ?? "personal",
|
|
21052
|
+
maskApiKey(p2.apiKey),
|
|
21053
|
+
p2.gatewayUrl
|
|
21054
|
+
];
|
|
21055
|
+
});
|
|
21056
|
+
Se(renderTable(headers, cells), "Keys");
|
|
21057
|
+
}
|
|
21058
|
+
});
|
|
21059
|
+
var keysCommand = defineCommand({
|
|
21060
|
+
meta: {
|
|
21061
|
+
name: "keys",
|
|
21062
|
+
description: "Mint and list group-scoped API keys."
|
|
21063
|
+
},
|
|
21064
|
+
subCommands: {
|
|
21065
|
+
create: keysCreateCommand,
|
|
21066
|
+
list: keysListCommand
|
|
21067
|
+
}
|
|
21068
|
+
});
|
|
20235
21069
|
|
|
20236
|
-
// src/
|
|
20237
|
-
|
|
20238
|
-
|
|
20239
|
-
|
|
20240
|
-
|
|
20241
|
-
const pad = (s, i) => s.padEnd(widths[i] ?? 0);
|
|
20242
|
-
const divider = widths.map((w3) => "\u2500".repeat(w3)).join(" ");
|
|
20243
|
-
return [headers.map(pad).join(" "), divider, ...rows.map((row) => row.map(pad).join(" "))].join(
|
|
20244
|
-
"\n"
|
|
20245
|
-
);
|
|
20246
|
-
}
|
|
21070
|
+
// src/commands/models.ts
|
|
21071
|
+
init_dist();
|
|
21072
|
+
init_dist5();
|
|
21073
|
+
init_api_client();
|
|
21074
|
+
init_credentials();
|
|
20247
21075
|
|
|
20248
21076
|
// src/commands/sync-models.ts
|
|
20249
21077
|
init_dist();
|
|
@@ -20251,12 +21079,14 @@ init_dist5();
|
|
|
20251
21079
|
init_api_client();
|
|
20252
21080
|
init_opencode();
|
|
20253
21081
|
init_credentials();
|
|
21082
|
+
init_paths();
|
|
21083
|
+
init_project_config();
|
|
20254
21084
|
init_prompt();
|
|
20255
21085
|
var SCOPES2 = ["local", "project", "user"];
|
|
20256
21086
|
var modelsSyncCommand = defineCommand({
|
|
20257
21087
|
meta: {
|
|
20258
21088
|
name: "sync",
|
|
20259
|
-
description: "Refresh the model list (
|
|
21089
|
+
description: "Refresh the configured group's model list (names, costs, context limits) in an existing opencode.json."
|
|
20260
21090
|
},
|
|
20261
21091
|
args: {
|
|
20262
21092
|
scope: {
|
|
@@ -20271,19 +21101,53 @@ var modelsSyncCommand = defineCommand({
|
|
|
20271
21101
|
}
|
|
20272
21102
|
},
|
|
20273
21103
|
async run({ args }) {
|
|
20274
|
-
const
|
|
20275
|
-
if (!
|
|
20276
|
-
|
|
21104
|
+
const found = readProjectConfig(userCwd());
|
|
21105
|
+
if (!found || !found.config.gateway) {
|
|
21106
|
+
R2.warn("No .codevector.json with a `gateway` here. Run `codevector init` first.");
|
|
21107
|
+
process.exitCode = 1;
|
|
21108
|
+
return;
|
|
21109
|
+
}
|
|
21110
|
+
const repoGroups = found.config.groups ?? [];
|
|
21111
|
+
if (repoGroups.length === 0) {
|
|
21112
|
+
R2.info(
|
|
21113
|
+
"Model sync is available only when a group is configured for this repo. Run `codevector use <group>` to bind one."
|
|
21114
|
+
);
|
|
21115
|
+
return;
|
|
21116
|
+
}
|
|
21117
|
+
const profiles = await readProfiles();
|
|
21118
|
+
if (!profiles) {
|
|
21119
|
+
R2.warn("Not signed in. Run `codevector auth login` first.");
|
|
21120
|
+
process.exitCode = 1;
|
|
21121
|
+
return;
|
|
21122
|
+
}
|
|
21123
|
+
const resolution = resolveProfileForRepo(
|
|
21124
|
+
profiles.profiles,
|
|
21125
|
+
profiles.activeProfile,
|
|
21126
|
+
found.config.gateway,
|
|
21127
|
+
repoGroups
|
|
21128
|
+
);
|
|
21129
|
+
if (resolution.kind === "no-group-key") {
|
|
21130
|
+
R2.warn(
|
|
21131
|
+
`No local key for this repo's group${resolution.groups.length === 1 ? "" : "s"} (${resolution.groups.join(", ")}). Run \`codevector use <group>\` first.`
|
|
21132
|
+
);
|
|
21133
|
+
process.exitCode = 1;
|
|
21134
|
+
return;
|
|
21135
|
+
}
|
|
21136
|
+
if (resolution.kind === "none") {
|
|
21137
|
+
R2.warn("No profile matches this repo's gateway. Run `codevector auth login` for it.");
|
|
21138
|
+
process.exitCode = 1;
|
|
21139
|
+
return;
|
|
20277
21140
|
}
|
|
20278
|
-
|
|
21141
|
+
const profile = resolution.profile;
|
|
21142
|
+
ge(`Sync models for group "${profile.groupName ?? "group"}"`);
|
|
20279
21143
|
const target = await resolveTarget(args);
|
|
20280
|
-
const client = gatewayClient(
|
|
21144
|
+
const client = gatewayClient(profile.gatewayUrl, profile.apiKey, 1e4);
|
|
20281
21145
|
const s = ft();
|
|
20282
|
-
s.start("Loading
|
|
21146
|
+
s.start("Loading group models\u2026");
|
|
20283
21147
|
let reachable;
|
|
20284
21148
|
try {
|
|
20285
21149
|
const res = await call(parseResponse(client.models.$get()));
|
|
20286
|
-
reachable = res.data.filter((m2) => m2.kind === "chat").map((m2) => ({
|
|
21150
|
+
reachable = res.data.filter((m2) => m2.kind === "chat" && m2.callable).map((m2) => ({
|
|
20287
21151
|
slug: m2.slug,
|
|
20288
21152
|
displayName: m2.displayName,
|
|
20289
21153
|
contextWindow: m2.contextWindow,
|
|
@@ -20303,7 +21167,7 @@ var modelsSyncCommand = defineCommand({
|
|
|
20303
21167
|
releaseDate: m2.releaseDate,
|
|
20304
21168
|
family: m2.family
|
|
20305
21169
|
}));
|
|
20306
|
-
s.stop(`${reachable.length} model${reachable.length === 1 ? "" : "s"}
|
|
21170
|
+
s.stop(`${reachable.length} model${reachable.length === 1 ? "" : "s"} in group`);
|
|
20307
21171
|
} catch (err) {
|
|
20308
21172
|
s.stop("Could not load models");
|
|
20309
21173
|
R2.error(err instanceof ApiClientError ? err.message : String(err));
|
|
@@ -20392,20 +21256,25 @@ var modelsListCommand = defineCommand({
|
|
|
20392
21256
|
);
|
|
20393
21257
|
return;
|
|
20394
21258
|
}
|
|
21259
|
+
const ordered = [...filtered].sort((a, b2) => Number(b2.callable) - Number(a.callable));
|
|
20395
21260
|
const headers = [
|
|
20396
21261
|
"SLUG",
|
|
20397
21262
|
"KIND",
|
|
20398
21263
|
"STATUS",
|
|
21264
|
+
"SOURCE",
|
|
21265
|
+
"USABLE",
|
|
20399
21266
|
"PROVIDER",
|
|
20400
21267
|
"CONTEXT",
|
|
20401
21268
|
"IN $/MTOK",
|
|
20402
21269
|
"OUT $/MTOK",
|
|
20403
21270
|
"NAME"
|
|
20404
21271
|
];
|
|
20405
|
-
const cells =
|
|
21272
|
+
const cells = ordered.map((m2) => [
|
|
20406
21273
|
m2.slug,
|
|
20407
21274
|
m2.kind,
|
|
20408
21275
|
m2.status,
|
|
21276
|
+
m2.sources.length ? m2.sources.join(", ") : "\u2014",
|
|
21277
|
+
m2.callable ? "yes" : "",
|
|
20409
21278
|
m2.providerKind ?? "\u2014",
|
|
20410
21279
|
m2.contextWindow ? m2.contextWindow.toLocaleString() : "\u2014",
|
|
20411
21280
|
m2.inputPricePerMtok ?? "\u2014",
|
|
@@ -20413,6 +21282,11 @@ var modelsListCommand = defineCommand({
|
|
|
20413
21282
|
m2.displayName
|
|
20414
21283
|
]);
|
|
20415
21284
|
Se(renderTable(headers, cells), "Reachable models");
|
|
21285
|
+
if (ordered.some((m2) => !m2.callable)) {
|
|
21286
|
+
R2.info(
|
|
21287
|
+
"Models without USABLE are reachable through another profile. Run `codevector use <group>` to switch."
|
|
21288
|
+
);
|
|
21289
|
+
}
|
|
20416
21290
|
} catch (err) {
|
|
20417
21291
|
s?.stop("Could not load models");
|
|
20418
21292
|
R2.error(err instanceof ApiClientError ? err.message : String(err));
|
|
@@ -20435,6 +21309,7 @@ var modelsCommand = defineCommand({
|
|
|
20435
21309
|
init_dist();
|
|
20436
21310
|
init_dist5();
|
|
20437
21311
|
init_claude_code();
|
|
21312
|
+
init_cline();
|
|
20438
21313
|
init_opencode();
|
|
20439
21314
|
init_api_client();
|
|
20440
21315
|
init_credentials();
|
|
@@ -20443,8 +21318,77 @@ init_prompt();
|
|
|
20443
21318
|
init_project_context();
|
|
20444
21319
|
var WRITERS3 = {
|
|
20445
21320
|
"claude-code": writeClaudeCodeConfig,
|
|
21321
|
+
cline: writeClineConfig,
|
|
20446
21322
|
opencode: writeOpencodeConfig
|
|
20447
21323
|
};
|
|
21324
|
+
async function applyProfileToolConfigs(profileName, active) {
|
|
21325
|
+
const toolConfigs = active.toolConfigs?.length ? active.toolConfigs : detectOnDiskToolConfigs();
|
|
21326
|
+
if (toolConfigs.length === 0) {
|
|
21327
|
+
return { kind: "no-tools" };
|
|
21328
|
+
}
|
|
21329
|
+
const fetched = await fetchReachableModels(active.gatewayUrl, active.apiKey);
|
|
21330
|
+
if (!fetched.ok) {
|
|
21331
|
+
return { kind: "unreachable", reason: fetched.reason };
|
|
21332
|
+
}
|
|
21333
|
+
const reachable = fetched.models;
|
|
21334
|
+
const project = resolveProjectContext(userCwd()).project ?? void 0;
|
|
21335
|
+
const results = [];
|
|
21336
|
+
const persisted = [];
|
|
21337
|
+
for (const tc of toolConfigs) {
|
|
21338
|
+
const tool = tc.tool;
|
|
21339
|
+
const writer = WRITERS3[tool];
|
|
21340
|
+
if (!writer) {
|
|
21341
|
+
results.push({
|
|
21342
|
+
tool: tc.tool,
|
|
21343
|
+
status: "skipped",
|
|
21344
|
+
path: "",
|
|
21345
|
+
scope: tc.scope,
|
|
21346
|
+
notes: `Unsupported tool "${tc.tool}".`
|
|
21347
|
+
});
|
|
21348
|
+
continue;
|
|
21349
|
+
}
|
|
21350
|
+
const storedModelSlug = tc.modelSlug;
|
|
21351
|
+
const reachableModel = storedModelSlug ? reachable.find((m2) => m2.slug === storedModelSlug) : void 0;
|
|
21352
|
+
const modelArg = reachableModel ? { slug: reachableModel.slug, providerKind: reachableModel.providerKind } : void 0;
|
|
21353
|
+
let extraNote;
|
|
21354
|
+
if (storedModelSlug && !reachableModel) {
|
|
21355
|
+
extraNote = `Pinned model "${tc.modelSlug}" is not reachable with this profile; model pin removed.`;
|
|
21356
|
+
}
|
|
21357
|
+
try {
|
|
21358
|
+
const result = writer({
|
|
21359
|
+
gatewayUrl: active.gatewayUrl,
|
|
21360
|
+
apiKey: active.apiKey,
|
|
21361
|
+
scope: tc.scope,
|
|
21362
|
+
...project ? { project } : {},
|
|
21363
|
+
...modelArg ? { model: modelArg } : {},
|
|
21364
|
+
availableModels: reachable
|
|
21365
|
+
});
|
|
21366
|
+
if (extraNote) {
|
|
21367
|
+
result.notes = result.notes ? `${result.notes} ${extraNote}` : extraNote;
|
|
21368
|
+
}
|
|
21369
|
+
results.push(result);
|
|
21370
|
+
if (result.status === "configured") {
|
|
21371
|
+
persisted.push({
|
|
21372
|
+
tool: result.tool,
|
|
21373
|
+
scope: result.scope,
|
|
21374
|
+
...modelArg ? { modelSlug: modelArg.slug } : {}
|
|
21375
|
+
});
|
|
21376
|
+
}
|
|
21377
|
+
} catch (err) {
|
|
21378
|
+
results.push({
|
|
21379
|
+
tool: tc.tool,
|
|
21380
|
+
status: "skipped",
|
|
21381
|
+
path: "",
|
|
21382
|
+
scope: tc.scope,
|
|
21383
|
+
notes: err instanceof Error ? err.message : String(err)
|
|
21384
|
+
});
|
|
21385
|
+
}
|
|
21386
|
+
}
|
|
21387
|
+
if (persisted.length > 0) {
|
|
21388
|
+
updateProfileToolConfigs(profileName, persisted);
|
|
21389
|
+
}
|
|
21390
|
+
return { kind: "applied", results };
|
|
21391
|
+
}
|
|
20448
21392
|
var profileListCommand = defineCommand({
|
|
20449
21393
|
meta: {
|
|
20450
21394
|
name: "list",
|
|
@@ -20500,7 +21444,9 @@ var profileSwitchCommand = defineCommand({
|
|
|
20500
21444
|
await setActiveProfile(selected);
|
|
20501
21445
|
const active = await getActiveProfile();
|
|
20502
21446
|
if (!active) {
|
|
20503
|
-
R2.error(
|
|
21447
|
+
R2.error(
|
|
21448
|
+
`Profile "${selected}" disappeared mid-switch. Re-run \`codevector profile switch\`.`
|
|
21449
|
+
);
|
|
20504
21450
|
process.exitCode = 1;
|
|
20505
21451
|
return;
|
|
20506
21452
|
}
|
|
@@ -20511,86 +21457,76 @@ Gateway: ${active.gatewayUrl}
|
|
|
20511
21457
|
Key: ${maskApiKey(active.apiKey)}`,
|
|
20512
21458
|
`Profile: ${selected}`
|
|
20513
21459
|
);
|
|
20514
|
-
const
|
|
20515
|
-
if (
|
|
20516
|
-
R2.info(
|
|
20517
|
-
|
|
20518
|
-
const fetched = await fetchReachableModels(active.gatewayUrl, active.apiKey);
|
|
20519
|
-
if (!fetched.ok) {
|
|
20520
|
-
R2.warn(
|
|
20521
|
-
`Could not reach the gateway to verify reachable models: ${fetched.reason}.`
|
|
21460
|
+
const outcome = await applyProfileToolConfigs(selected, active);
|
|
21461
|
+
if (outcome.kind === "no-tools") {
|
|
21462
|
+
R2.info(
|
|
21463
|
+
"No tool configurations found for this profile and none detected on disk; nothing to reapply."
|
|
20522
21464
|
);
|
|
21465
|
+
return;
|
|
21466
|
+
}
|
|
21467
|
+
if (outcome.kind === "unreachable") {
|
|
21468
|
+
R2.warn(`Could not reach the gateway to verify reachable models: ${outcome.reason}.`);
|
|
20523
21469
|
R2.info(
|
|
20524
21470
|
"Stored tool configurations were left unchanged. Re-run `codevector profile switch` once the gateway is reachable to reapply them."
|
|
20525
21471
|
);
|
|
20526
21472
|
return;
|
|
20527
21473
|
}
|
|
20528
|
-
const
|
|
20529
|
-
|
|
20530
|
-
|
|
20531
|
-
|
|
20532
|
-
|
|
20533
|
-
const tool = tc.tool;
|
|
20534
|
-
const writer = WRITERS3[tool];
|
|
20535
|
-
if (!writer) {
|
|
20536
|
-
results.push({
|
|
20537
|
-
tool: tc.tool,
|
|
20538
|
-
status: "skipped",
|
|
20539
|
-
path: "",
|
|
20540
|
-
scope: tc.scope,
|
|
20541
|
-
notes: `Unsupported tool "${tc.tool}".`
|
|
20542
|
-
});
|
|
20543
|
-
continue;
|
|
20544
|
-
}
|
|
20545
|
-
const storedModelSlug = tc.modelSlug;
|
|
20546
|
-
const reachableModel = storedModelSlug ? reachable.find((m2) => m2.slug === storedModelSlug) : void 0;
|
|
20547
|
-
const modelArg = reachableModel ? { slug: reachableModel.slug, providerKind: reachableModel.providerKind } : void 0;
|
|
20548
|
-
let extraNote;
|
|
20549
|
-
if (storedModelSlug && !reachableModel) {
|
|
20550
|
-
extraNote = `Pinned model "${tc.modelSlug}" is not reachable with this profile; model pin removed.`;
|
|
20551
|
-
}
|
|
20552
|
-
try {
|
|
20553
|
-
const result = writer({
|
|
20554
|
-
gatewayUrl: active.gatewayUrl,
|
|
20555
|
-
apiKey: active.apiKey,
|
|
20556
|
-
scope: tc.scope,
|
|
20557
|
-
...project ? { project } : {},
|
|
20558
|
-
...modelArg ? { model: modelArg } : {},
|
|
20559
|
-
availableModels: reachable
|
|
20560
|
-
});
|
|
20561
|
-
if (extraNote) {
|
|
20562
|
-
result.notes = result.notes ? `${result.notes} ${extraNote}` : extraNote;
|
|
20563
|
-
}
|
|
20564
|
-
results.push(result);
|
|
20565
|
-
if (result.status === "configured") {
|
|
20566
|
-
persisted.push({
|
|
20567
|
-
tool: result.tool,
|
|
20568
|
-
scope: result.scope,
|
|
20569
|
-
...modelArg ? { modelSlug: modelArg.slug } : {}
|
|
20570
|
-
});
|
|
20571
|
-
}
|
|
20572
|
-
} catch (err) {
|
|
20573
|
-
results.push({
|
|
20574
|
-
tool: tc.tool,
|
|
20575
|
-
status: "skipped",
|
|
20576
|
-
path: "",
|
|
20577
|
-
scope: tc.scope,
|
|
20578
|
-
notes: err instanceof Error ? err.message : String(err)
|
|
20579
|
-
});
|
|
21474
|
+
const lines = outcome.results.map((r) => {
|
|
21475
|
+
if (r.status === "configured") {
|
|
21476
|
+
const noteStr = r.notes ? `
|
|
21477
|
+
${r.notes}` : "";
|
|
21478
|
+
return ` \u2713 ${r.tool} \u2192 ${r.path} [${r.scope}]${noteStr}`;
|
|
20580
21479
|
}
|
|
21480
|
+
return ` ! ${r.tool}: skipped \u2014 ${r.notes ?? "unknown reason"}`;
|
|
21481
|
+
});
|
|
21482
|
+
Se(lines.join("\n"), "Tool configurations updated");
|
|
21483
|
+
}
|
|
21484
|
+
});
|
|
21485
|
+
var profileDeleteCommand = defineCommand({
|
|
21486
|
+
meta: {
|
|
21487
|
+
name: "delete",
|
|
21488
|
+
description: "Delete a saved profile."
|
|
21489
|
+
},
|
|
21490
|
+
args: {
|
|
21491
|
+
name: {
|
|
21492
|
+
type: "string",
|
|
21493
|
+
description: "Profile name to delete. Prompts with a picker if omitted."
|
|
21494
|
+
}
|
|
21495
|
+
},
|
|
21496
|
+
async run({ args }) {
|
|
21497
|
+
const profiles = await readProfiles();
|
|
21498
|
+
if (!profiles || Object.keys(profiles.profiles).length === 0) {
|
|
21499
|
+
R2.warn("No profiles configured. Nothing to delete.");
|
|
21500
|
+
process.exitCode = 1;
|
|
21501
|
+
return;
|
|
21502
|
+
}
|
|
21503
|
+
let target = args.name;
|
|
21504
|
+
if (!target) {
|
|
21505
|
+
const names = Object.keys(profiles.profiles).sort((a, b2) => {
|
|
21506
|
+
if (a === "default") return -1;
|
|
21507
|
+
if (b2 === "default") return 1;
|
|
21508
|
+
return a.localeCompare(b2);
|
|
21509
|
+
});
|
|
21510
|
+
const options = names.map((n) => ({
|
|
21511
|
+
value: n,
|
|
21512
|
+
label: n,
|
|
21513
|
+
hint: n === profiles.activeProfile ? "active" : void 0
|
|
21514
|
+
}));
|
|
21515
|
+
target = unwrap(
|
|
21516
|
+
await xe({
|
|
21517
|
+
message: "Select profile to delete",
|
|
21518
|
+
options,
|
|
21519
|
+
initialValue: profiles.activeProfile
|
|
21520
|
+
})
|
|
21521
|
+
);
|
|
20581
21522
|
}
|
|
20582
|
-
|
|
20583
|
-
|
|
21523
|
+
const removed = deleteProfile(target);
|
|
21524
|
+
if (removed) {
|
|
21525
|
+
R2.success(`Deleted profile "${target}".`);
|
|
21526
|
+
} else {
|
|
21527
|
+
R2.error(`Profile "${target}" does not exist.`);
|
|
21528
|
+
process.exitCode = 1;
|
|
20584
21529
|
}
|
|
20585
|
-
const lines = results.map((r) => {
|
|
20586
|
-
if (r.status === "configured") {
|
|
20587
|
-
const noteStr = r.notes ? `
|
|
20588
|
-
${r.notes}` : "";
|
|
20589
|
-
return ` \u2713 ${r.tool} \u2192 ${r.path} [${r.scope}]${noteStr}`;
|
|
20590
|
-
}
|
|
20591
|
-
return ` ! ${r.tool}: skipped \u2014 ${r.notes ?? "unknown reason"}`;
|
|
20592
|
-
});
|
|
20593
|
-
Se(lines.join("\n"), "Tool configurations updated");
|
|
20594
21530
|
}
|
|
20595
21531
|
});
|
|
20596
21532
|
var profileCommand = defineCommand({
|
|
@@ -20600,14 +21536,15 @@ var profileCommand = defineCommand({
|
|
|
20600
21536
|
},
|
|
20601
21537
|
subCommands: {
|
|
20602
21538
|
list: profileListCommand,
|
|
20603
|
-
switch: profileSwitchCommand
|
|
21539
|
+
switch: profileSwitchCommand,
|
|
21540
|
+
delete: profileDeleteCommand
|
|
20604
21541
|
}
|
|
20605
21542
|
});
|
|
20606
21543
|
async function fetchReachableModels(gatewayUrl, apiKey) {
|
|
20607
21544
|
const client = gatewayClient(gatewayUrl, apiKey, 1e4);
|
|
20608
21545
|
try {
|
|
20609
21546
|
const res = await call(parseResponse(client.models.$get()));
|
|
20610
|
-
const models = res.data.filter((m2) => m2.kind === "chat").map((m2) => ({
|
|
21547
|
+
const models = res.data.filter((m2) => m2.kind === "chat" && m2.callable).map((m2) => ({
|
|
20611
21548
|
slug: m2.slug,
|
|
20612
21549
|
providerKind: m2.providerKind,
|
|
20613
21550
|
displayName: m2.displayName,
|
|
@@ -20639,8 +21576,8 @@ init_dist();
|
|
|
20639
21576
|
init_dist5();
|
|
20640
21577
|
init_api_client();
|
|
20641
21578
|
init_credentials();
|
|
20642
|
-
import { readFile, writeFile } from "fs/promises";
|
|
20643
|
-
import { resolve as resolve2 } from "path";
|
|
21579
|
+
import { mkdir, readdir, readFile, symlink, writeFile } from "fs/promises";
|
|
21580
|
+
import { join as join15, resolve as resolve2 } from "path";
|
|
20644
21581
|
import { spawn } from "child_process";
|
|
20645
21582
|
async function getClient() {
|
|
20646
21583
|
const creds = await readCredentials();
|
|
@@ -20735,6 +21672,37 @@ var skillsUploadCommand = defineCommand({
|
|
|
20735
21672
|
}
|
|
20736
21673
|
}
|
|
20737
21674
|
});
|
|
21675
|
+
var skillsDeleteCommand = defineCommand({
|
|
21676
|
+
meta: {
|
|
21677
|
+
name: "delete",
|
|
21678
|
+
description: "Delete a skill pack from the gateway."
|
|
21679
|
+
},
|
|
21680
|
+
args: {
|
|
21681
|
+
name: {
|
|
21682
|
+
type: "positional",
|
|
21683
|
+
description: "Name of the skill pack to delete.",
|
|
21684
|
+
required: true
|
|
21685
|
+
}
|
|
21686
|
+
},
|
|
21687
|
+
async run({ args }) {
|
|
21688
|
+
const client = await getClient();
|
|
21689
|
+
if (!client) return;
|
|
21690
|
+
const s = ft();
|
|
21691
|
+
s.start(`Deleting skill pack "${args.name}"\u2026`);
|
|
21692
|
+
try {
|
|
21693
|
+
await call(
|
|
21694
|
+
parseResponse(
|
|
21695
|
+
client["skill-packs"][":name"].$delete({ param: { name: args.name } })
|
|
21696
|
+
)
|
|
21697
|
+
);
|
|
21698
|
+
s.stop(`Skill pack "${args.name}" deleted.`);
|
|
21699
|
+
} catch (err) {
|
|
21700
|
+
s.stop("Delete failed");
|
|
21701
|
+
R2.error(err instanceof ApiClientError ? err.message : String(err));
|
|
21702
|
+
process.exitCode = 1;
|
|
21703
|
+
}
|
|
21704
|
+
}
|
|
21705
|
+
});
|
|
20738
21706
|
var skillsSyncCommand = defineCommand({
|
|
20739
21707
|
meta: {
|
|
20740
21708
|
name: "sync",
|
|
@@ -20791,6 +21759,28 @@ var skillsSyncCommand = defineCommand({
|
|
|
20791
21759
|
child.on("error", reject);
|
|
20792
21760
|
});
|
|
20793
21761
|
s.stop("Skills installed");
|
|
21762
|
+
const agentsDir = resolve2(".agents/skills");
|
|
21763
|
+
let entries = [];
|
|
21764
|
+
try {
|
|
21765
|
+
entries = await readdir(agentsDir, { withFileTypes: true }).then((list) => list.filter((d) => d.isDirectory()).map((d) => d.name));
|
|
21766
|
+
} catch {
|
|
21767
|
+
}
|
|
21768
|
+
if (entries.length > 0) {
|
|
21769
|
+
for (const tool of ["claude", "opencode"]) {
|
|
21770
|
+
const toolDir = resolve2(`.${tool}/skills`);
|
|
21771
|
+
await mkdir(toolDir, { recursive: true });
|
|
21772
|
+
for (const name of entries) {
|
|
21773
|
+
const linkPath = join15(toolDir, name);
|
|
21774
|
+
try {
|
|
21775
|
+
await symlink(join15(agentsDir, name), linkPath, "dir");
|
|
21776
|
+
} catch (e2) {
|
|
21777
|
+
if (e2.code !== "EEXIST") {
|
|
21778
|
+
R2.warn(`Could not link ${name} into .${tool}/skills/: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
21779
|
+
}
|
|
21780
|
+
}
|
|
21781
|
+
}
|
|
21782
|
+
}
|
|
21783
|
+
}
|
|
20794
21784
|
} catch (err) {
|
|
20795
21785
|
s.stop("Install failed");
|
|
20796
21786
|
R2.error(err instanceof Error ? err.message : String(err));
|
|
@@ -20806,6 +21796,7 @@ var skillsCommand = defineCommand({
|
|
|
20806
21796
|
subCommands: {
|
|
20807
21797
|
list: skillsListCommand,
|
|
20808
21798
|
upload: skillsUploadCommand,
|
|
21799
|
+
delete: skillsDeleteCommand,
|
|
20809
21800
|
sync: skillsSyncCommand
|
|
20810
21801
|
}
|
|
20811
21802
|
});
|
|
@@ -20815,6 +21806,8 @@ init_dist();
|
|
|
20815
21806
|
init_dist5();
|
|
20816
21807
|
init_api_client();
|
|
20817
21808
|
init_credentials();
|
|
21809
|
+
init_paths();
|
|
21810
|
+
init_project_config();
|
|
20818
21811
|
var statusCommand = defineCommand({
|
|
20819
21812
|
meta: {
|
|
20820
21813
|
name: "status",
|
|
@@ -20831,20 +21824,151 @@ var statusCommand = defineCommand({
|
|
|
20831
21824
|
`Signed in as ${creds.email}
|
|
20832
21825
|
Gateway: ${creds.gatewayUrl}
|
|
20833
21826
|
API key: ${maskApiKey(creds.apiKey)}
|
|
21827
|
+
Billing: ${creds.groupName ? `group "${creds.groupName}"` : "personal"}
|
|
20834
21828
|
Last saved: ${creds.savedAt}`,
|
|
20835
21829
|
"Credentials"
|
|
20836
21830
|
);
|
|
21831
|
+
const pinned = readProjectConfig(userCwd())?.config.groups ?? [];
|
|
21832
|
+
if (pinned.length > 0) {
|
|
21833
|
+
R2.info(`This repo bills group(s): ${pinned.join(", ")}`);
|
|
21834
|
+
}
|
|
20837
21835
|
const client = gatewayClient(creds.gatewayUrl, creds.apiKey, 1e4);
|
|
20838
21836
|
const s = ft();
|
|
20839
21837
|
s.start("Reaching gateway\u2026");
|
|
20840
21838
|
try {
|
|
21839
|
+
const serverVersion = await checkServerCompatibility(creds.gatewayUrl);
|
|
20841
21840
|
const res = await call(parseResponse(client.models.$get()));
|
|
21841
|
+
const versionSuffix = serverVersion ? ` (server ${serverVersion})` : "";
|
|
20842
21842
|
s.stop(
|
|
20843
|
-
res.data.length === 0 ?
|
|
21843
|
+
res.data.length === 0 ? `Gateway reachable${versionSuffix} \u2014 no models granted. Ask your admin for access.` : `Gateway reachable${versionSuffix} \u2014 ${res.data.length} model${res.data.length === 1 ? "" : "s"} available. Run \`codevector models\` to list them.`
|
|
20844
21844
|
);
|
|
20845
21845
|
} catch (err) {
|
|
20846
|
-
s.stop("Gateway unreachable");
|
|
20847
|
-
R2.error(err instanceof
|
|
21846
|
+
s.stop(err instanceof ApiClientError ? "Gateway unreachable" : "Gateway incompatible");
|
|
21847
|
+
R2.error(err instanceof Error ? err.message : String(err));
|
|
21848
|
+
process.exitCode = 1;
|
|
21849
|
+
}
|
|
21850
|
+
}
|
|
21851
|
+
});
|
|
21852
|
+
|
|
21853
|
+
// src/commands/sync.ts
|
|
21854
|
+
init_dist();
|
|
21855
|
+
init_dist5();
|
|
21856
|
+
init_api_client();
|
|
21857
|
+
init_credentials();
|
|
21858
|
+
|
|
21859
|
+
// src/lib/sync-plan.ts
|
|
21860
|
+
function keyUsable(k2, now) {
|
|
21861
|
+
if (!k2.isActive) return false;
|
|
21862
|
+
if (k2.expiresAt && new Date(k2.expiresAt).getTime() <= now.getTime()) return false;
|
|
21863
|
+
return true;
|
|
21864
|
+
}
|
|
21865
|
+
function findServerKey(profile, serverKeys) {
|
|
21866
|
+
if (profile.keyId) {
|
|
21867
|
+
const byId = serverKeys.find((k2) => k2.id === profile.keyId);
|
|
21868
|
+
if (byId) return byId;
|
|
21869
|
+
}
|
|
21870
|
+
return serverKeys.find((k2) => profile.apiKey.startsWith(k2.prefix));
|
|
21871
|
+
}
|
|
21872
|
+
function computeSyncActions(memberships, serverKeys, localProfiles, now) {
|
|
21873
|
+
const mints = [];
|
|
21874
|
+
const reminds = [];
|
|
21875
|
+
const prunes = [];
|
|
21876
|
+
const memberIds = new Set(memberships.map((m2) => m2.id));
|
|
21877
|
+
for (const m2 of memberships) {
|
|
21878
|
+
const profile = localProfiles.find((p2) => p2.groupId === m2.id);
|
|
21879
|
+
if (!profile) {
|
|
21880
|
+
mints.push({ type: "mint", groupId: m2.id, groupName: m2.name });
|
|
21881
|
+
continue;
|
|
21882
|
+
}
|
|
21883
|
+
const key = findServerKey(profile, serverKeys);
|
|
21884
|
+
if (!key || !keyUsable(key, now)) {
|
|
21885
|
+
reminds.push({ type: "remint", profileName: profile.name, groupId: m2.id, groupName: m2.name });
|
|
21886
|
+
}
|
|
21887
|
+
}
|
|
21888
|
+
for (const p2 of localProfiles) {
|
|
21889
|
+
if (!memberIds.has(p2.groupId)) {
|
|
21890
|
+
prunes.push({ type: "prune", profileName: p2.name, groupName: p2.groupName ?? p2.name });
|
|
21891
|
+
}
|
|
21892
|
+
}
|
|
21893
|
+
return [...mints, ...reminds, ...prunes];
|
|
21894
|
+
}
|
|
21895
|
+
|
|
21896
|
+
// src/commands/sync.ts
|
|
21897
|
+
var syncCommand = defineCommand({
|
|
21898
|
+
meta: {
|
|
21899
|
+
name: "sync",
|
|
21900
|
+
description: "Reconcile local group keys with your server memberships + key status."
|
|
21901
|
+
},
|
|
21902
|
+
args: {
|
|
21903
|
+
prune: {
|
|
21904
|
+
type: "boolean",
|
|
21905
|
+
description: "Also delete local profiles for groups you've left (destructive)."
|
|
21906
|
+
}
|
|
21907
|
+
},
|
|
21908
|
+
async run({ args }) {
|
|
21909
|
+
const active = await getActiveProfile();
|
|
21910
|
+
if (!active) {
|
|
21911
|
+
R2.warn("Not signed in. Run `codevector auth login` first.");
|
|
21912
|
+
process.exitCode = 1;
|
|
21913
|
+
return;
|
|
21914
|
+
}
|
|
21915
|
+
const client = gatewayClient(active.gatewayUrl, active.apiKey, 1e4);
|
|
21916
|
+
const s = ft();
|
|
21917
|
+
s.start("Reconciling\u2026");
|
|
21918
|
+
try {
|
|
21919
|
+
const [groupsRes, keysRes] = await Promise.all([
|
|
21920
|
+
call(parseResponse(client.groups.$get())),
|
|
21921
|
+
call(parseResponse(client.keys.$get()))
|
|
21922
|
+
]);
|
|
21923
|
+
const profiles = await readProfiles();
|
|
21924
|
+
const previousActive = profiles?.activeProfile ?? null;
|
|
21925
|
+
const local = Object.entries(profiles?.profiles ?? {}).filter(([, p2]) => p2.groupId).map(([name, p2]) => ({
|
|
21926
|
+
name,
|
|
21927
|
+
groupId: p2.groupId,
|
|
21928
|
+
groupName: p2.groupName,
|
|
21929
|
+
keyId: p2.keyId,
|
|
21930
|
+
apiKey: p2.apiKey
|
|
21931
|
+
}));
|
|
21932
|
+
const actions = computeSyncActions(groupsRes.data, keysRes.data, local, /* @__PURE__ */ new Date());
|
|
21933
|
+
s.stop(`${actions.length} change${actions.length === 1 ? "" : "s"}`);
|
|
21934
|
+
const summary = [];
|
|
21935
|
+
for (const a of actions) {
|
|
21936
|
+
if (a.type === "mint" || a.type === "remint") {
|
|
21937
|
+
const res = await call(
|
|
21938
|
+
parseResponse(client.keys.$post({ json: { groupId: a.groupId } }))
|
|
21939
|
+
);
|
|
21940
|
+
writeProfile(a.groupName, {
|
|
21941
|
+
apiKey: res.key.plaintext,
|
|
21942
|
+
gatewayUrl: active.gatewayUrl,
|
|
21943
|
+
userId: active.userId,
|
|
21944
|
+
email: active.email,
|
|
21945
|
+
groupId: a.groupId,
|
|
21946
|
+
groupName: a.groupName,
|
|
21947
|
+
keyId: res.key.id
|
|
21948
|
+
});
|
|
21949
|
+
summary.push(`${a.type === "mint" ? "minted" : "re-minted"} "${a.groupName}"`);
|
|
21950
|
+
} else if (a.type === "prune") {
|
|
21951
|
+
if (args.prune) {
|
|
21952
|
+
deleteProfile(a.profileName);
|
|
21953
|
+
summary.push(`pruned "${a.profileName}"`);
|
|
21954
|
+
} else {
|
|
21955
|
+
summary.push(`stale "${a.profileName}" \u2014 run with --prune to delete`);
|
|
21956
|
+
}
|
|
21957
|
+
}
|
|
21958
|
+
}
|
|
21959
|
+
if (previousActive) {
|
|
21960
|
+
const stillExists = (await readProfiles())?.profiles[previousActive];
|
|
21961
|
+
if (stillExists) await setActiveProfile(previousActive);
|
|
21962
|
+
}
|
|
21963
|
+
Se(summary.length ? summary.join("\n") : "Everything is up to date.", "Sync");
|
|
21964
|
+
} catch (err) {
|
|
21965
|
+
s.stop("Sync failed");
|
|
21966
|
+
const e2 = err instanceof ApiClientError ? err : null;
|
|
21967
|
+
if (e2?.code === "FORBIDDEN") {
|
|
21968
|
+
R2.warn("Group budgets are not enabled on this gateway \u2014 nothing to sync.");
|
|
21969
|
+
return;
|
|
21970
|
+
}
|
|
21971
|
+
R2.error(e2 ? e2.message : String(err));
|
|
20848
21972
|
process.exitCode = 1;
|
|
20849
21973
|
}
|
|
20850
21974
|
}
|
|
@@ -20854,47 +21978,47 @@ Last saved: ${creds.savedAt}`,
|
|
|
20854
21978
|
init_dist();
|
|
20855
21979
|
init_dist5();
|
|
20856
21980
|
init_api_client();
|
|
20857
|
-
import { existsSync as
|
|
21981
|
+
import { existsSync as existsSync14 } from "fs";
|
|
20858
21982
|
|
|
20859
21983
|
// src/lib/backup.ts
|
|
20860
21984
|
import {
|
|
20861
21985
|
copyFileSync,
|
|
20862
|
-
existsSync as
|
|
20863
|
-
mkdirSync as
|
|
21986
|
+
existsSync as existsSync13,
|
|
21987
|
+
mkdirSync as mkdirSync8,
|
|
20864
21988
|
readdirSync,
|
|
20865
21989
|
statSync as statSync4
|
|
20866
21990
|
} from "fs";
|
|
20867
|
-
import { basename, dirname as
|
|
20868
|
-
import { homedir as
|
|
20869
|
-
var BACKUP_ROOT = process.env.CODEVECTOR_BACKUP_ROOT ??
|
|
21991
|
+
import { basename, dirname as dirname7, join as join16 } from "path";
|
|
21992
|
+
import { homedir as homedir7 } from "os";
|
|
21993
|
+
var BACKUP_ROOT = process.env.CODEVECTOR_BACKUP_ROOT ?? join16(homedir7(), ".codevector", "backups");
|
|
20870
21994
|
function backupTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
20871
21995
|
return d.toISOString().replace(/:/g, "-");
|
|
20872
21996
|
}
|
|
20873
21997
|
function backupFile(sourcePath, tool, timestamp) {
|
|
20874
|
-
if (!
|
|
20875
|
-
const destDir =
|
|
20876
|
-
|
|
20877
|
-
const dest =
|
|
21998
|
+
if (!existsSync13(sourcePath)) return void 0;
|
|
21999
|
+
const destDir = join16(BACKUP_ROOT, timestamp, tool);
|
|
22000
|
+
mkdirSync8(destDir, { recursive: true, mode: 448 });
|
|
22001
|
+
const dest = join16(destDir, basename(sourcePath));
|
|
20878
22002
|
copyFileSync(sourcePath, dest);
|
|
20879
22003
|
return dest;
|
|
20880
22004
|
}
|
|
20881
22005
|
function listBackupRuns() {
|
|
20882
|
-
if (!
|
|
22006
|
+
if (!existsSync13(BACKUP_ROOT)) return [];
|
|
20883
22007
|
const entries = readdirSync(BACKUP_ROOT, { withFileTypes: true }).filter((e2) => e2.isDirectory()).map((e2) => e2.name).sort().reverse();
|
|
20884
22008
|
const runs = [];
|
|
20885
22009
|
for (const ts of entries) {
|
|
20886
|
-
const dir =
|
|
22010
|
+
const dir = join16(BACKUP_ROOT, ts);
|
|
20887
22011
|
const tools = readdirSync(dir, { withFileTypes: true }).filter((e2) => e2.isDirectory());
|
|
20888
22012
|
const collected = [];
|
|
20889
22013
|
for (const toolDir of tools) {
|
|
20890
|
-
const toolPath =
|
|
22014
|
+
const toolPath = join16(dir, toolDir.name);
|
|
20891
22015
|
for (const file2 of readdirSync(toolPath, { withFileTypes: true })) {
|
|
20892
22016
|
if (!file2.isFile()) continue;
|
|
20893
22017
|
collected.push({
|
|
20894
22018
|
tool: toolDir.name,
|
|
20895
22019
|
original: "",
|
|
20896
22020
|
// resolved by caller per tool — see restoreBackup()
|
|
20897
|
-
backup:
|
|
22021
|
+
backup: join16(toolPath, file2.name)
|
|
20898
22022
|
});
|
|
20899
22023
|
}
|
|
20900
22024
|
}
|
|
@@ -20904,10 +22028,10 @@ function listBackupRuns() {
|
|
|
20904
22028
|
return runs;
|
|
20905
22029
|
}
|
|
20906
22030
|
function restoreBackup(backupPath, originalPath) {
|
|
20907
|
-
if (!
|
|
22031
|
+
if (!existsSync13(backupPath)) {
|
|
20908
22032
|
throw new Error(`Backup file missing: ${backupPath}`);
|
|
20909
22033
|
}
|
|
20910
|
-
|
|
22034
|
+
mkdirSync8(dirname7(originalPath), { recursive: true });
|
|
20911
22035
|
copyFileSync(backupPath, originalPath);
|
|
20912
22036
|
}
|
|
20913
22037
|
function backupRunMtime(dir) {
|
|
@@ -20919,16 +22043,20 @@ init_credentials();
|
|
|
20919
22043
|
init_prompt();
|
|
20920
22044
|
init_shell_hook();
|
|
20921
22045
|
init_claude_code();
|
|
22046
|
+
init_cline();
|
|
20922
22047
|
init_opencode();
|
|
20923
|
-
var TOOLS = ["claude-code", "opencode"];
|
|
22048
|
+
var TOOLS = ["claude-code", "cline", "opencode"];
|
|
20924
22049
|
var WRITERS4 = {
|
|
20925
22050
|
"claude-code": writeClaudeCodeConfig,
|
|
22051
|
+
cline: writeClineConfig,
|
|
20926
22052
|
opencode: writeOpencodeConfig
|
|
20927
22053
|
};
|
|
20928
22054
|
function userPathFor(tool) {
|
|
20929
22055
|
switch (tool) {
|
|
20930
22056
|
case "claude-code":
|
|
20931
22057
|
return claudeSettingsPath("user");
|
|
22058
|
+
case "cline":
|
|
22059
|
+
return clineSettingsPath("user");
|
|
20932
22060
|
case "opencode":
|
|
20933
22061
|
return opencodeSettingsPath("user");
|
|
20934
22062
|
}
|
|
@@ -20944,10 +22072,11 @@ var systemConfigureCommand = defineCommand({
|
|
|
20944
22072
|
if (!profiles) {
|
|
20945
22073
|
throw new Error("Not signed in. Run `codevector auth login` first.");
|
|
20946
22074
|
}
|
|
20947
|
-
const
|
|
22075
|
+
const activeProfileName = profiles.activeProfile;
|
|
22076
|
+
const creds = profiles.profiles[activeProfileName];
|
|
20948
22077
|
if (!creds) {
|
|
20949
22078
|
throw new Error(
|
|
20950
|
-
`Active profile "${
|
|
22079
|
+
`Active profile "${activeProfileName}" is missing from credentials. Run \`codevector auth login\` to recover.`
|
|
20951
22080
|
);
|
|
20952
22081
|
}
|
|
20953
22082
|
ge("codevector system configure");
|
|
@@ -20996,6 +22125,10 @@ var systemConfigureCommand = defineCommand({
|
|
|
20996
22125
|
R2.warn(`${r.tool}: skipped \u2014 ${r.notes ?? "unknown reason"}`);
|
|
20997
22126
|
}
|
|
20998
22127
|
}
|
|
22128
|
+
const persisted = results.filter((r) => r.status === "configured").map((r) => ({ tool: r.tool, scope: "user" }));
|
|
22129
|
+
if (persisted.length > 0) {
|
|
22130
|
+
updateProfileToolConfigs(activeProfileName, persisted);
|
|
22131
|
+
}
|
|
20999
22132
|
if (backups.length > 0) {
|
|
21000
22133
|
Se(
|
|
21001
22134
|
`To roll back: codevector system restore --timestamp ${timestamp}`,
|
|
@@ -21067,7 +22200,7 @@ var systemRestoreCommand = defineCommand({
|
|
|
21067
22200
|
}
|
|
21068
22201
|
const target = userPathFor(e2.tool);
|
|
21069
22202
|
plan.push({ tool: e2.tool, backup: e2.backup, target });
|
|
21070
|
-
R2.info(` ${e2.tool} \u2192 ${target}${
|
|
22203
|
+
R2.info(` ${e2.tool} \u2192 ${target}${existsSync14(target) ? " (will overwrite)" : " (new)"}`);
|
|
21071
22204
|
}
|
|
21072
22205
|
if (plan.length === 0) {
|
|
21073
22206
|
ye("Nothing to restore.");
|
|
@@ -21109,7 +22242,7 @@ async function fetchReachableChatModels4(creds) {
|
|
|
21109
22242
|
s.start("Loading reachable models\u2026");
|
|
21110
22243
|
try {
|
|
21111
22244
|
const res = await call(parseResponse(client.models.$get()));
|
|
21112
|
-
const models = res.data.filter((m2) => m2.kind === "chat").map((m2) => ({
|
|
22245
|
+
const models = res.data.filter((m2) => m2.kind === "chat" && m2.callable).map((m2) => ({
|
|
21113
22246
|
slug: m2.slug,
|
|
21114
22247
|
displayName: m2.displayName,
|
|
21115
22248
|
contextWindow: m2.contextWindow,
|
|
@@ -21153,147 +22286,9 @@ var systemCommand = defineCommand({
|
|
|
21153
22286
|
// src/commands/update.ts
|
|
21154
22287
|
init_dist();
|
|
21155
22288
|
init_dist5();
|
|
22289
|
+
init_package();
|
|
21156
22290
|
import { spawnSync } from "child_process";
|
|
21157
|
-
|
|
21158
|
-
// package.json
|
|
21159
|
-
var package_default = {
|
|
21160
|
-
name: "@codevector/cli",
|
|
21161
|
-
version: "0.8.0",
|
|
21162
|
-
description: "CodeVector CLI \u2014 installs and configures first-party coding-tool integrations.",
|
|
21163
|
-
license: "UNLICENSED",
|
|
21164
|
-
bin: {
|
|
21165
|
-
codevector: "./bin/codevector.mjs"
|
|
21166
|
-
},
|
|
21167
|
-
files: [
|
|
21168
|
-
"bin",
|
|
21169
|
-
"dist",
|
|
21170
|
-
"scripts",
|
|
21171
|
-
"src/hooks"
|
|
21172
|
-
],
|
|
21173
|
-
type: "module",
|
|
21174
|
-
publishConfig: {
|
|
21175
|
-
access: "public"
|
|
21176
|
-
},
|
|
21177
|
-
scripts: {
|
|
21178
|
-
dev: "tsx src/index.ts",
|
|
21179
|
-
build: "tsup",
|
|
21180
|
-
"type-check": "tsc --noEmit",
|
|
21181
|
-
test: "vitest run",
|
|
21182
|
-
"test:watch": "vitest",
|
|
21183
|
-
postinstall: "node scripts/postinstall.mjs"
|
|
21184
|
-
},
|
|
21185
|
-
dependencies: {
|
|
21186
|
-
"@clack/prompts": "1.4.0",
|
|
21187
|
-
citty: "^0.2.2",
|
|
21188
|
-
hono: "4.12.16",
|
|
21189
|
-
"smol-toml": "^1.6.1"
|
|
21190
|
-
},
|
|
21191
|
-
devDependencies: {
|
|
21192
|
-
"@codevector/api": "workspace:*",
|
|
21193
|
-
"@codevector/common": "workspace:*",
|
|
21194
|
-
"@types/node": "25.6.0",
|
|
21195
|
-
tsup: "^8.5.1",
|
|
21196
|
-
tsx: "4.21.0",
|
|
21197
|
-
typescript: "6.0.3",
|
|
21198
|
-
vitest: "4.1.5",
|
|
21199
|
-
zod: "4.4.2"
|
|
21200
|
-
},
|
|
21201
|
-
engines: {
|
|
21202
|
-
node: ">=20"
|
|
21203
|
-
}
|
|
21204
|
-
};
|
|
21205
|
-
|
|
21206
|
-
// src/commands/update.ts
|
|
21207
22291
|
init_prompt();
|
|
21208
|
-
|
|
21209
|
-
// src/lib/update-notifier.ts
|
|
21210
|
-
init_paths();
|
|
21211
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
21212
|
-
import { join as join14 } from "path";
|
|
21213
|
-
var PKG_NAME = "@codevector/cli";
|
|
21214
|
-
var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
21215
|
-
var CHECK_CACHE_FILE = join14(CODEVECTOR_CONFIG_DIR, "update-check.json");
|
|
21216
|
-
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
21217
|
-
var FETCH_TIMEOUT_MS = 2e3;
|
|
21218
|
-
function readCache() {
|
|
21219
|
-
if (!existsSync13(CHECK_CACHE_FILE)) return void 0;
|
|
21220
|
-
try {
|
|
21221
|
-
const raw = readFileSync10(CHECK_CACHE_FILE, "utf8");
|
|
21222
|
-
const parsed = JSON.parse(raw);
|
|
21223
|
-
if (typeof parsed.checkedAt !== "number") return void 0;
|
|
21224
|
-
return {
|
|
21225
|
-
latest: typeof parsed.latest === "string" ? parsed.latest : null,
|
|
21226
|
-
checkedAt: parsed.checkedAt
|
|
21227
|
-
};
|
|
21228
|
-
} catch {
|
|
21229
|
-
return void 0;
|
|
21230
|
-
}
|
|
21231
|
-
}
|
|
21232
|
-
function readCachedLatestVersion() {
|
|
21233
|
-
return readCache()?.latest ?? null;
|
|
21234
|
-
}
|
|
21235
|
-
function writeCache(cache) {
|
|
21236
|
-
try {
|
|
21237
|
-
mkdirSync7(CODEVECTOR_CONFIG_DIR, { recursive: true, mode: 448 });
|
|
21238
|
-
writeFileSync9(CHECK_CACHE_FILE, JSON.stringify(cache));
|
|
21239
|
-
} catch {
|
|
21240
|
-
}
|
|
21241
|
-
}
|
|
21242
|
-
function isNewer(current, latest) {
|
|
21243
|
-
const a = current.split(".").map((p2) => Number.parseInt(p2, 10));
|
|
21244
|
-
const b2 = latest.split(".").map((p2) => Number.parseInt(p2, 10));
|
|
21245
|
-
for (let i = 0; i < Math.max(a.length, b2.length); i++) {
|
|
21246
|
-
const ai = a[i] ?? 0;
|
|
21247
|
-
const bi = b2[i] ?? 0;
|
|
21248
|
-
if (Number.isNaN(ai) || Number.isNaN(bi)) return latest > current;
|
|
21249
|
-
if (bi > ai) return true;
|
|
21250
|
-
if (bi < ai) return false;
|
|
21251
|
-
}
|
|
21252
|
-
return false;
|
|
21253
|
-
}
|
|
21254
|
-
function printUpdateNoticeIfCached(currentVersion) {
|
|
21255
|
-
if (process.env.CODEVECTOR_NO_UPDATE_CHECK === "1") return;
|
|
21256
|
-
try {
|
|
21257
|
-
const cache = readCache();
|
|
21258
|
-
if (!cache || !cache.latest) return;
|
|
21259
|
-
if (isNewer(currentVersion, cache.latest)) {
|
|
21260
|
-
process.stderr.write(
|
|
21261
|
-
`\x1B[33m\u203A\x1B[0m A new version of @codevector/cli is available: ${currentVersion} \u2192 ${cache.latest}. Run \`codevector update\` to install.
|
|
21262
|
-
`
|
|
21263
|
-
);
|
|
21264
|
-
}
|
|
21265
|
-
} catch {
|
|
21266
|
-
}
|
|
21267
|
-
}
|
|
21268
|
-
function scheduleUpdateCheck() {
|
|
21269
|
-
if (process.env.CODEVECTOR_NO_UPDATE_CHECK === "1") return;
|
|
21270
|
-
const cache = readCache();
|
|
21271
|
-
const now = Date.now();
|
|
21272
|
-
if (cache && now - cache.checkedAt < CHECK_TTL_MS) return;
|
|
21273
|
-
void fetchLatestVersion().then((latest) => {
|
|
21274
|
-
writeCache({ latest, checkedAt: Date.now() });
|
|
21275
|
-
}).catch(() => {
|
|
21276
|
-
writeCache({ latest: cache?.latest ?? null, checkedAt: Date.now() });
|
|
21277
|
-
});
|
|
21278
|
-
}
|
|
21279
|
-
async function fetchLatestVersion() {
|
|
21280
|
-
const controller = new AbortController();
|
|
21281
|
-
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
21282
|
-
timer.unref?.();
|
|
21283
|
-
try {
|
|
21284
|
-
const res = await fetch(REGISTRY_URL, {
|
|
21285
|
-
signal: controller.signal,
|
|
21286
|
-
headers: { accept: "application/json" }
|
|
21287
|
-
});
|
|
21288
|
-
if (!res.ok) return null;
|
|
21289
|
-
const body = await res.json();
|
|
21290
|
-
return typeof body.version === "string" ? body.version : null;
|
|
21291
|
-
} finally {
|
|
21292
|
-
clearTimeout(timer);
|
|
21293
|
-
}
|
|
21294
|
-
}
|
|
21295
|
-
|
|
21296
|
-
// src/commands/update.ts
|
|
21297
22292
|
var PKG_NAME2 = "@codevector/cli";
|
|
21298
22293
|
var updateCommand = defineCommand({
|
|
21299
22294
|
meta: {
|
|
@@ -21413,6 +22408,117 @@ function installCommand(manager) {
|
|
|
21413
22408
|
}
|
|
21414
22409
|
}
|
|
21415
22410
|
|
|
22411
|
+
// src/commands/use.ts
|
|
22412
|
+
init_dist();
|
|
22413
|
+
init_dist5();
|
|
22414
|
+
init_api_client();
|
|
22415
|
+
init_credentials();
|
|
22416
|
+
init_paths();
|
|
22417
|
+
init_prompt();
|
|
22418
|
+
init_project_config();
|
|
22419
|
+
function addGroupToConfig(config2, groupName) {
|
|
22420
|
+
const existing = config2.groups ?? [];
|
|
22421
|
+
if (existing.includes(groupName)) return config2;
|
|
22422
|
+
return { ...config2, groups: [...existing, groupName] };
|
|
22423
|
+
}
|
|
22424
|
+
var useCommand = defineCommand({
|
|
22425
|
+
meta: {
|
|
22426
|
+
name: "use",
|
|
22427
|
+
description: "Bind this repo to a group: pin it in .codevector.json and activate its key."
|
|
22428
|
+
},
|
|
22429
|
+
args: {
|
|
22430
|
+
group: {
|
|
22431
|
+
type: "positional",
|
|
22432
|
+
required: false,
|
|
22433
|
+
description: "Group name to bill for this repo. Prompted if omitted."
|
|
22434
|
+
}
|
|
22435
|
+
},
|
|
22436
|
+
async run({ args }) {
|
|
22437
|
+
const active = await getActiveProfile();
|
|
22438
|
+
if (!active) {
|
|
22439
|
+
R2.warn("Not signed in. Run `codevector auth login` first.");
|
|
22440
|
+
process.exitCode = 1;
|
|
22441
|
+
return;
|
|
22442
|
+
}
|
|
22443
|
+
const found = readProjectConfig(userCwd());
|
|
22444
|
+
if (!found || !found.config.gateway) {
|
|
22445
|
+
R2.error("No .codevector.json with a `gateway` here. Run `codevector init` first.");
|
|
22446
|
+
process.exitCode = 1;
|
|
22447
|
+
return;
|
|
22448
|
+
}
|
|
22449
|
+
const client = gatewayClient(active.gatewayUrl, active.apiKey, 1e4);
|
|
22450
|
+
const s = ft();
|
|
22451
|
+
s.start("Loading your groups\u2026");
|
|
22452
|
+
try {
|
|
22453
|
+
const groupsRes = await call(parseResponse(client.groups.$get()));
|
|
22454
|
+
s.stop(`${groupsRes.data.length} group${groupsRes.data.length === 1 ? "" : "s"}`);
|
|
22455
|
+
if (groupsRes.data.length === 0) {
|
|
22456
|
+
R2.info("You are not a member of any groups. Ask an admin to add you.");
|
|
22457
|
+
return;
|
|
22458
|
+
}
|
|
22459
|
+
const chosenName = args.group ?? unwrap(
|
|
22460
|
+
await xe({
|
|
22461
|
+
message: "Which group should this repo bill?",
|
|
22462
|
+
options: groupsRes.data.map((g) => ({ value: g.name, label: g.name }))
|
|
22463
|
+
})
|
|
22464
|
+
);
|
|
22465
|
+
const group = groupsRes.data.find((g) => g.name === chosenName);
|
|
22466
|
+
if (!group) {
|
|
22467
|
+
R2.error(
|
|
22468
|
+
`You are not a member of a group named "${chosenName}". Run \`codevector groups list\` to see yours.`
|
|
22469
|
+
);
|
|
22470
|
+
process.exitCode = 1;
|
|
22471
|
+
return;
|
|
22472
|
+
}
|
|
22473
|
+
const [keysRes, profiles] = await Promise.all([
|
|
22474
|
+
call(parseResponse(client.keys.$get())),
|
|
22475
|
+
readProfiles()
|
|
22476
|
+
]);
|
|
22477
|
+
const localForGroup = Object.entries(profiles?.profiles ?? {}).filter(([, p2]) => p2.groupId === group.id).map(([name, p2]) => ({
|
|
22478
|
+
name,
|
|
22479
|
+
groupId: p2.groupId,
|
|
22480
|
+
groupName: p2.groupName,
|
|
22481
|
+
keyId: p2.keyId,
|
|
22482
|
+
apiKey: p2.apiKey
|
|
22483
|
+
}));
|
|
22484
|
+
const [action] = computeSyncActions(
|
|
22485
|
+
[{ id: group.id, name: group.name }],
|
|
22486
|
+
keysRes.data,
|
|
22487
|
+
localForGroup,
|
|
22488
|
+
/* @__PURE__ */ new Date()
|
|
22489
|
+
);
|
|
22490
|
+
if (action && (action.type === "mint" || action.type === "remint")) {
|
|
22491
|
+
const mintS = ft();
|
|
22492
|
+
mintS.start(`Minting a key for "${group.name}"\u2026`);
|
|
22493
|
+
const res = await call(parseResponse(client.keys.$post({ json: { groupId: group.id } })));
|
|
22494
|
+
writeProfile(group.name, {
|
|
22495
|
+
apiKey: res.key.plaintext,
|
|
22496
|
+
gatewayUrl: active.gatewayUrl,
|
|
22497
|
+
userId: active.userId,
|
|
22498
|
+
email: active.email,
|
|
22499
|
+
groupId: group.id,
|
|
22500
|
+
groupName: group.name,
|
|
22501
|
+
keyId: res.key.id
|
|
22502
|
+
});
|
|
22503
|
+
mintS.stop(`Minted profile "${group.name}".`);
|
|
22504
|
+
}
|
|
22505
|
+
writeProjectConfig(found.path, addGroupToConfig(found.config, group.name));
|
|
22506
|
+
await setActiveProfile(group.name);
|
|
22507
|
+
const nowActive = (await readProfiles())?.profiles[group.name];
|
|
22508
|
+
if (nowActive) await applyProfileToolConfigs(group.name, nowActive);
|
|
22509
|
+
Se(
|
|
22510
|
+
`This repo now bills group "${group.name}".
|
|
22511
|
+
The shell hook will export its key on the next prompt.`,
|
|
22512
|
+
"Repo bound"
|
|
22513
|
+
);
|
|
22514
|
+
} catch (err) {
|
|
22515
|
+
s.stop("Failed");
|
|
22516
|
+
R2.error(err instanceof ApiClientError ? err.message : String(err));
|
|
22517
|
+
process.exitCode = 1;
|
|
22518
|
+
}
|
|
22519
|
+
}
|
|
22520
|
+
});
|
|
22521
|
+
|
|
21416
22522
|
// src/commands/usage.ts
|
|
21417
22523
|
init_dist();
|
|
21418
22524
|
init_dist5();
|
|
@@ -21506,6 +22612,7 @@ function buildQuery(args) {
|
|
|
21506
22612
|
|
|
21507
22613
|
// src/commands/version.ts
|
|
21508
22614
|
init_dist();
|
|
22615
|
+
init_package();
|
|
21509
22616
|
var versionCommand = defineCommand({
|
|
21510
22617
|
meta: {
|
|
21511
22618
|
name: "version",
|
|
@@ -21517,6 +22624,7 @@ var versionCommand = defineCommand({
|
|
|
21517
22624
|
});
|
|
21518
22625
|
|
|
21519
22626
|
// src/index.ts
|
|
22627
|
+
init_package();
|
|
21520
22628
|
var main = defineCommand({
|
|
21521
22629
|
meta: {
|
|
21522
22630
|
name: "codevector",
|
|
@@ -21531,13 +22639,17 @@ var main = defineCommand({
|
|
|
21531
22639
|
github: githubCommand,
|
|
21532
22640
|
doctor: doctorCommand,
|
|
21533
22641
|
env: envCommand,
|
|
22642
|
+
groups: groupsCommand,
|
|
21534
22643
|
hook: hookCommand,
|
|
22644
|
+
keys: keysCommand,
|
|
21535
22645
|
status: statusCommand,
|
|
22646
|
+
sync: syncCommand,
|
|
21536
22647
|
system: systemCommand,
|
|
21537
22648
|
models: modelsCommand,
|
|
21538
22649
|
profile: profileCommand,
|
|
21539
22650
|
skills: skillsCommand,
|
|
21540
22651
|
update: updateCommand,
|
|
22652
|
+
use: useCommand,
|
|
21541
22653
|
usage: usageCommand,
|
|
21542
22654
|
version: versionCommand
|
|
21543
22655
|
}
|