@codevector/cli 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,7 +34,6 @@ That's the whole onboarding. Pick a tool, pick a scope, done.
34
34
  | -------------------------------- | ---------------------------------------- | ---------------------- | ---------------------------------------------------------- | --------------------- |
35
35
  | **Claude Code** | `~/.claude/settings.json` | user / project / local | literal in JSON (`ANTHROPIC_AUTH_TOKEN`) | `Stop` + `SessionEnd` |
36
36
  | **opencode** (sst/opencode) | `~/.config/opencode/opencode.json` | user / project / local | literal (user + local); env-var reference in project scope | — |
37
- | **qwen-code** (Alibaba Qwen CLI) | `~/.qwen/settings.json` + `~/.qwen/.env` | user / project / local | `.env` file alongside settings (`envKey` reference) | `Stop` + `SessionEnd` |
38
37
  | **codex** (OpenAI codex CLI) | `~/.codex/config.toml` | **user only** | shell env var (codex does not auto-load `.env`) | not wired |
39
38
 
40
39
  Each writer emits both Anthropic and OpenAI-compatible provider entries where the tool supports both wire formats. codex is OpenAI-only.
@@ -49,7 +48,7 @@ Three scopes, same names across every supported tool that allows more than one:
49
48
  | --------- | ------------------------------------------------------------------------------------------------ | ------------------------------- | --------------------------------------------------------- |
50
49
  | `user` | your home directory (`~/.claude/…`, `~/.codex/…`, etc.) | no (outside the repo) | your personal default across every project |
51
50
  | `project` | this repo's config dir (`./.claude/settings.json`, etc.) | **yes** — shared with teammates | base URL only; we never embed the API key here |
52
- | `local` | this repo's local-override file (`./.claude/settings.local.json`, `./opencode.json`, `./.qwen/`) | no — auto-gitignored | the usual default: per-seat API key stays on your machine |
51
+ | `local` | this repo's local-override file (`./.claude/settings.local.json`, `./opencode.json`) | no — auto-gitignored | the usual default: per-seat API key stays on your machine |
53
52
 
54
53
  `local` is the default when you don't pass `--scope`. The CLI adds gitignore entries automatically for tools that don't natively ignore their local file.
55
54
 
@@ -175,8 +174,6 @@ Override the config root with `CODEVECTOR_CONFIG_DIR`.
175
174
  | ---------------------- | --------------------------------------------------------- | ---------------------- |
176
175
  | Claude Code | `~/.claude/settings.json` / `.claude/settings.local.json` | JSON |
177
176
  | opencode | `~/.config/opencode/opencode.json` / `./opencode.json` | JSON (JSONC tolerated) |
178
- | qwen-code | `~/.qwen/settings.json` / `.qwen/settings.json` | JSON |
179
- | qwen-code secrets | `~/.qwen/.env` / `.qwen/.env` | dotenv |
180
177
  | codex | `~/.codex/config.toml` | TOML |
181
178
  | codevector credentials | `~/.config/codevector/credentials.json` | JSON (chmod 0600) |
182
179
  | codevector hook script | `~/.config/codevector/hooks/acceptance.sh` | shell |
package/dist/index.js CHANGED
@@ -16753,7 +16753,7 @@ function assertHttpsUrl(urlString) {
16753
16753
  }
16754
16754
 
16755
16755
  // src/commands/configure.ts
16756
- import { homedir as homedir6 } from "os";
16756
+ import { homedir as homedir5 } from "os";
16757
16757
 
16758
16758
  // src/lib/hooks.ts
16759
16759
  import { chmodSync as chmodSync2, copyFileSync, existsSync, mkdirSync as mkdirSync2 } from "fs";
@@ -18001,6 +18001,7 @@ var writeOpencodeConfig = ({
18001
18001
  gatewayUrl,
18002
18002
  apiKey,
18003
18003
  scope,
18004
+ project,
18004
18005
  model,
18005
18006
  availableModels = []
18006
18007
  }) => {
@@ -18009,14 +18010,16 @@ var writeOpencodeConfig = ({
18009
18010
  const keyLiteral = scope === "project" ? `{env:${ENV_VAR_NAME2}}` : apiKey;
18010
18011
  removeStaleGatewayProviders(path, gateway);
18011
18012
  const models = buildModelEntries(availableModels);
18013
+ const options = {
18014
+ apiKey: keyLiteral,
18015
+ baseURL: `${gateway}/gateway/openai/v1`,
18016
+ headers: buildCustomHeaders2(scope, project)
18017
+ };
18012
18018
  const patch = {
18013
18019
  $schema: "https://opencode.ai/config.json",
18014
18020
  provider: {
18015
18021
  [PROVIDER_PREFIX]: {
18016
- options: {
18017
- apiKey: keyLiteral,
18018
- baseURL: `${gateway}/gateway/openai/v1`
18019
- },
18022
+ options,
18020
18023
  models
18021
18024
  }
18022
18025
  }
@@ -18037,6 +18040,11 @@ var writeOpencodeConfig = ({
18037
18040
  `Project scope avoids embedding secrets. Export ${ENV_VAR_NAME2}=<your key> before running opencode, or switch to local scope to embed the key.`
18038
18041
  );
18039
18042
  }
18043
+ if (scope !== "user" && !project) {
18044
+ notes.push(
18045
+ "Project slug could not be derived (no git remote and no .codevector.json). Per-project usage attribution will be blank until you set one."
18046
+ );
18047
+ }
18040
18048
  return {
18041
18049
  tool: "opencode",
18042
18050
  status: "configured",
@@ -18178,126 +18186,20 @@ function removeStaleGatewayProviders(path, gateway) {
18178
18186
  function isObject4(v2) {
18179
18187
  return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
18180
18188
  }
18181
- function trimRightSlash4(url2) {
18182
- return url2.endsWith("/") ? url2.slice(0, -1) : url2;
18183
- }
18184
-
18185
- // src/config-writers/qwen-code.ts
18186
- import { homedir as homedir5 } from "os";
18187
- import { join as join8 } from "path";
18188
-
18189
- // src/lib/env-file.ts
18190
- import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
18191
- import { dirname as dirname5 } from "path";
18192
- function mergeEnvFile(path, entries) {
18193
- mkdirSync5(dirname5(path), { recursive: true, mode: 448 });
18194
- const existing = existsSync6(path) ? readFileSync6(path, "utf8") : "";
18195
- const lines = existing.split("\n");
18196
- const seen = /* @__PURE__ */ new Set();
18197
- const updated = lines.map((line) => {
18198
- const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
18199
- if (!match) return line;
18200
- const key = match[1];
18201
- if (key === void 0 || !(key in entries)) return line;
18202
- seen.add(key);
18203
- return `${key}=${quote(entries[key] ?? "")}`;
18204
- });
18205
- for (const [key, value] of Object.entries(entries)) {
18206
- if (!seen.has(key)) updated.push(`${key}=${quote(value)}`);
18207
- }
18208
- while (updated.length > 0 && updated[updated.length - 1] === "") updated.pop();
18209
- writeFileSync6(path, `${updated.join("\n")}
18210
- `);
18211
- try {
18212
- chmodSync5(path, 384);
18213
- } catch {
18214
- }
18215
- }
18216
- function quote(v2) {
18217
- const escaped = v2.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
18218
- return `"${escaped}"`;
18219
- }
18220
-
18221
- // src/config-writers/qwen-code.ts
18222
- var ENV_VAR_NAME3 = "CODEVECTOR_GATEWAY_KEY";
18223
- var writeQwenCodeConfig = ({
18224
- gatewayUrl,
18225
- apiKey,
18226
- hookScriptPath,
18227
- scope,
18228
- model
18229
- }) => {
18230
- const path = qwenSettingsPath(scope);
18231
- const gateway = trimRightSlash5(gatewayUrl);
18232
- const patch = {
18233
- modelProviders: {
18234
- anthropic: [
18235
- {
18236
- id: "codevector-anthropic",
18237
- name: "CodeVector Gateway (Anthropic)",
18238
- envKey: ENV_VAR_NAME3,
18239
- baseUrl: `${gateway}/gateway/anthropic`
18240
- }
18241
- ],
18242
- openai: [
18243
- {
18244
- id: "codevector-openai",
18245
- name: "CodeVector Gateway (OpenAI-compat)",
18246
- envKey: ENV_VAR_NAME3,
18247
- baseUrl: `${gateway}/gateway/openai/v1`
18248
- }
18249
- ]
18250
- },
18251
- hooks: {
18252
- Stop: [{ matcher: "*", hooks: [{ type: "command", command: hookScriptPath, async: true }] }],
18253
- SessionEnd: [
18254
- { matcher: "*", hooks: [{ type: "command", command: hookScriptPath, async: true }] }
18255
- ]
18256
- }
18189
+ function buildCustomHeaders2(scope, project) {
18190
+ const safe = (v2) => v2.replace(/[\r\n]/g, "").trim();
18191
+ const headers = {
18192
+ "x-client-app": "opencode"
18257
18193
  };
18258
- if (model) patch.model = { name: model.slug };
18259
- mergeJsonFile(path, patch, {
18260
- hookArrayPaths: ["hooks.Stop", "hooks.SessionEnd"]
18261
- });
18262
- const notes = [];
18263
- if (scope === "project") {
18264
- notes.push(
18265
- `Project scope avoids writing the API key. Export ${ENV_VAR_NAME3}=<your key> in your shell, or switch to local scope to drop it into .qwen/.env.`
18266
- );
18267
- } else {
18268
- const envPath = qwenEnvPath(scope);
18269
- mergeEnvFile(envPath, { [ENV_VAR_NAME3]: apiKey });
18270
- notes.push(`API key written to ${envPath} (chmod 600).`);
18271
- }
18272
- if (scope === "local") {
18273
- if (ensureGitignored(".qwen/")) {
18274
- notes.push("Added `.qwen/` to .gitignore.");
18194
+ if (scope !== "user" && project) {
18195
+ const slug = safe(project);
18196
+ if (slug) {
18197
+ headers["x-project"] = slug;
18275
18198
  }
18276
18199
  }
18277
- return {
18278
- tool: "qwen-code",
18279
- status: "configured",
18280
- path,
18281
- scope,
18282
- notes: notes.length > 0 ? notes.join(" ") : void 0
18283
- };
18284
- };
18285
- function qwenSettingsPath(scope) {
18286
- return join8(qwenRootForScope(scope), "settings.json");
18287
- }
18288
- function qwenEnvPath(scope) {
18289
- return join8(qwenRootForScope(scope), ".env");
18290
- }
18291
- function qwenRootForScope(scope) {
18292
- switch (scope) {
18293
- case "user":
18294
- return join8(homedir5(), ".qwen");
18295
- case "project":
18296
- case "local":
18297
- return join8(userCwd(), ".qwen");
18298
- }
18200
+ return headers;
18299
18201
  }
18300
- function trimRightSlash5(url2) {
18202
+ function trimRightSlash4(url2) {
18301
18203
  return url2.endsWith("/") ? url2.slice(0, -1) : url2;
18302
18204
  }
18303
18205
 
@@ -18305,10 +18207,9 @@ function trimRightSlash5(url2) {
18305
18207
  var WRITERS = {
18306
18208
  "claude-code": writeClaudeCodeConfig,
18307
18209
  opencode: writeOpencodeConfig,
18308
- "qwen-code": writeQwenCodeConfig,
18309
18210
  codex: writeCodexConfig
18310
18211
  };
18311
- var ALL_TOOLS = ["claude-code", "opencode", "qwen-code", "codex"];
18212
+ var ALL_TOOLS = ["claude-code", "opencode", "codex"];
18312
18213
  var SCOPES = ["local", "project", "user"];
18313
18214
  var configureCommand = defineCommand({
18314
18215
  meta: {
@@ -18319,7 +18220,7 @@ var configureCommand = defineCommand({
18319
18220
  tool: {
18320
18221
  type: "positional",
18321
18222
  required: false,
18322
- description: "Tool to configure. Prompted if omitted. One of: claude-code, opencode, qwen-code, codex, all."
18223
+ description: "Tool to configure. Prompted if omitted. One of: claude-code, opencode, codex, all."
18323
18224
  },
18324
18225
  scope: {
18325
18226
  type: "string",
@@ -18428,9 +18329,13 @@ async function resolveTools(args) {
18428
18329
  `Unsupported tool "${args.tool}". Known: ${ALL_TOOLS.join(", ")}, or pass --all.`
18429
18330
  );
18430
18331
  }
18332
+ Se(
18333
+ "Use arrow keys to move, space to toggle, a to toggle all, enter to confirm.",
18334
+ "Controls"
18335
+ );
18431
18336
  const picked = unwrap(
18432
18337
  await ve({
18433
- message: "Which tools do you want to configure?",
18338
+ message: "Which tools do you want to configure? (space to select/deselect, enter to confirm)",
18434
18339
  options: ALL_TOOLS.map((t) => ({ value: t, label: t })),
18435
18340
  initialValues: ["claude-code"],
18436
18341
  required: true
@@ -18447,7 +18352,7 @@ async function resolveScope(raw, tools) {
18447
18352
  }
18448
18353
  return unwrap(
18449
18354
  await Ee({
18450
- message: "Where should these settings be written?",
18355
+ message: "Where should these settings be written? (arrow keys to move, enter to select)",
18451
18356
  options: [
18452
18357
  {
18453
18358
  value: "local",
@@ -18477,14 +18382,12 @@ function pathHint(tools, scope) {
18477
18382
  return relativizeHomeAndCwd(claudeSettingsPath(scope));
18478
18383
  case "opencode":
18479
18384
  return relativizeHomeAndCwd(opencodeSettingsPath(scope));
18480
- case "qwen-code":
18481
- return relativizeHomeAndCwd(qwenSettingsPath(scope));
18482
18385
  case "codex":
18483
18386
  return scope === "user" ? relativizeHomeAndCwd(codexConfigPath()) : `${relativizeHomeAndCwd(codexConfigPath())} (codex is user-scope only)`;
18484
18387
  }
18485
18388
  }
18486
18389
  function relativizeHomeAndCwd(absolutePath) {
18487
- const home = homedir6();
18390
+ const home = homedir5();
18488
18391
  const cwd = userCwd();
18489
18392
  if (home && absolutePath.startsWith(`${home}/`)) {
18490
18393
  return `~${absolutePath.slice(home.length)}`;
@@ -18545,7 +18448,7 @@ async function pickPinnedModel(models) {
18545
18448
  const SKIP = "__skip__";
18546
18449
  const picked = unwrap(
18547
18450
  await Ee({
18548
- message: "Pin a default model for these tools?",
18451
+ message: "Pin a default model for these tools? (arrow keys to move, enter to select)",
18549
18452
  options: [
18550
18453
  {
18551
18454
  value: SKIP,
@@ -18568,7 +18471,7 @@ async function pickPinnedModel(models) {
18568
18471
  }
18569
18472
 
18570
18473
  // src/commands/doctor.ts
18571
- import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
18474
+ import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
18572
18475
  var CLAUDE_SCOPE_ORDER = ["local", "project", "user"];
18573
18476
  var doctorCommand = defineCommand({
18574
18477
  meta: {
@@ -18617,7 +18520,7 @@ var doctorCommand = defineCommand({
18617
18520
  checks.push({ level: "fail", label: "gateway /me", detail: message });
18618
18521
  }
18619
18522
  checks.push(inspectClaudeSettings());
18620
- if (!existsSync7(ACCEPTANCE_HOOK_FILE)) {
18523
+ if (!existsSync6(ACCEPTANCE_HOOK_FILE)) {
18621
18524
  checks.push({
18622
18525
  level: "warn",
18623
18526
  label: "acceptance hook",
@@ -18642,9 +18545,9 @@ var doctorCommand = defineCommand({
18642
18545
  function inspectClaudeSettings() {
18643
18546
  for (const scope of CLAUDE_SCOPE_ORDER) {
18644
18547
  const path = claudeSettingsPath(scope);
18645
- if (!existsSync7(path)) continue;
18548
+ if (!existsSync6(path)) continue;
18646
18549
  try {
18647
- const raw = JSON.parse(readFileSync7(path, "utf8"));
18550
+ const raw = JSON.parse(readFileSync6(path, "utf8"));
18648
18551
  if (typeof raw.env?.ANTHROPIC_BASE_URL === "string") {
18649
18552
  return {
18650
18553
  level: "ok",
@@ -18682,9 +18585,9 @@ function emit(checks) {
18682
18585
  }
18683
18586
 
18684
18587
  // src/commands/init.ts
18685
- import { existsSync as existsSync8, writeFileSync as writeFileSync7 } from "fs";
18588
+ import { existsSync as existsSync7, writeFileSync as writeFileSync6 } from "fs";
18686
18589
  import { execFileSync as execFileSync2 } from "child_process";
18687
- import { join as join9 } from "path";
18590
+ import { join as join8 } from "path";
18688
18591
  var DEFAULT_TICKET_PATTERN2 = "[A-Z]+-\\d+";
18689
18592
  var initCommand = defineCommand({
18690
18593
  meta: {
@@ -18707,8 +18610,8 @@ var initCommand = defineCommand({
18707
18610
  },
18708
18611
  run({ args }) {
18709
18612
  const cwd = userCwd();
18710
- const target = join9(cwd, ".codevector.json");
18711
- if (existsSync8(target) && !args.force) {
18613
+ const target = join8(cwd, ".codevector.json");
18614
+ if (existsSync7(target) && !args.force) {
18712
18615
  throw new Error(`.codevector.json already exists in ${cwd}. Pass --force to overwrite.`);
18713
18616
  }
18714
18617
  const projectName = args.project ?? deriveProjectName(cwd);
@@ -18718,7 +18621,7 @@ var initCommand = defineCommand({
18718
18621
  );
18719
18622
  }
18720
18623
  const ticketPattern = args["ticket-pattern"] ?? DEFAULT_TICKET_PATTERN2;
18721
- writeFileSync7(target, `${JSON.stringify({ projectName, ticketPattern }, null, 2)}
18624
+ writeFileSync6(target, `${JSON.stringify({ projectName, ticketPattern }, null, 2)}
18722
18625
  `, {
18723
18626
  mode: 420
18724
18627
  });
@@ -18833,7 +18736,7 @@ async function resolveTarget(args) {
18833
18736
  }
18834
18737
  const picked = unwrap(
18835
18738
  await Ee({
18836
- message: "Which opencode.json should be refreshed?",
18739
+ message: "Which opencode.json should be refreshed? (arrow keys to move, enter to select)",
18837
18740
  options: [
18838
18741
  { value: "local", label: "local", hint: "./opencode.json (this repo)" },
18839
18742
  { value: "project", label: "project", hint: "./opencode.json (this repo, committed)" },
@@ -19059,7 +18962,7 @@ function buildQuery(args) {
19059
18962
  // package.json
19060
18963
  var package_default = {
19061
18964
  name: "@codevector/cli",
19062
- version: "0.3.3",
18965
+ version: "0.3.4",
19063
18966
  description: "CodeVector CLI \u2014 installs and configures first-party coding-tool integrations.",
19064
18967
  license: "UNLICENSED",
19065
18968
  bin: {