@codevector/cli 0.3.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codevector/cli",
3
- "version": "0.3.4",
3
+ "version": "0.5.0",
4
4
  "description": "CodeVector CLI — installs and configures first-party coding-tool integrations.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "bin",
11
11
  "dist",
12
+ "scripts",
12
13
  "src/hooks"
13
14
  ],
14
15
  "type": "module",
@@ -16,7 +17,7 @@
16
17
  "access": "public"
17
18
  },
18
19
  "dependencies": {
19
- "@clack/prompts": "1.3.0",
20
+ "@clack/prompts": "1.4.0",
20
21
  "citty": "^0.2.2",
21
22
  "hono": "4.12.16",
22
23
  "smol-toml": "^1.6.1"
@@ -28,8 +29,8 @@
28
29
  "typescript": "6.0.3",
29
30
  "vitest": "4.1.5",
30
31
  "zod": "4.4.2",
31
- "@codevector/api": "1.7.3",
32
- "@codevector/common": "1.7.3"
32
+ "@codevector/api": "1.13.0",
33
+ "@codevector/common": "1.13.0"
33
34
  },
34
35
  "engines": {
35
36
  "node": ">=20"
@@ -39,6 +40,7 @@
39
40
  "build": "tsup",
40
41
  "type-check": "tsc --noEmit",
41
42
  "test": "vitest run",
42
- "test:watch": "vitest"
43
+ "test:watch": "vitest",
44
+ "postinstall": "node scripts/postinstall.mjs"
43
45
  }
44
46
  }
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Post-install tasks. Two independent things, each wrapped so neither can
4
+ * fail the install:
5
+ *
6
+ * 1. Migrate legacy single-profile credentials.json to the multi-profile
7
+ * format introduced in v0.4.0.
8
+ * 2. Detect which package manager invoked the install (npm / pnpm / yarn),
9
+ * parsed out of `process.env.npm_config_user_agent`, and persist it to
10
+ * `~/.config/codevector/install.json` so `codevector update` can use
11
+ * the same manager without prompting.
12
+ *
13
+ * Runs automatically after `npm install` / `pnpm install` / `yarn add`.
14
+ * Zero external dependencies — only Node built-ins. Any error is silently
15
+ * ignored so the install never fails.
16
+ */
17
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
18
+ import { dirname, join } from "node:path";
19
+ import { homedir } from "node:os";
20
+
21
+ const CONFIG_DIR = process.env.CODEVECTOR_CONFIG_DIR ?? join(homedir(), ".config", "codevector");
22
+ const CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
23
+ const INSTALL_FILE = join(CONFIG_DIR, "install.json");
24
+
25
+ try {
26
+ migrateCredentialsIfNeeded();
27
+ } catch {
28
+ // Never fail the install.
29
+ }
30
+
31
+ try {
32
+ capturePackageManagerIfPossible();
33
+ } catch {
34
+ // Never fail the install.
35
+ }
36
+
37
+ function migrateCredentialsIfNeeded() {
38
+ let raw;
39
+ try {
40
+ raw = readFileSync(CREDENTIALS_FILE, "utf8");
41
+ } catch (err) {
42
+ if (err.code === "ENOENT") return; // Nothing to migrate.
43
+ throw err;
44
+ }
45
+
46
+ let parsed;
47
+ try {
48
+ parsed = JSON.parse(raw);
49
+ } catch {
50
+ return; // Corrupted file — leave it for the CLI to surface.
51
+ }
52
+
53
+ // Already migrated (or created by a newer CLI).
54
+ if (parsed && typeof parsed === "object" && "profiles" in parsed) return;
55
+
56
+ // Legacy format guard.
57
+ if (
58
+ !parsed ||
59
+ typeof parsed !== "object" ||
60
+ typeof parsed.apiKey !== "string" ||
61
+ typeof parsed.gatewayUrl !== "string"
62
+ ) {
63
+ return;
64
+ }
65
+
66
+ const migrated = {
67
+ activeProfile: "default",
68
+ profiles: { default: parsed },
69
+ };
70
+
71
+ mkdirSync(dirname(CREDENTIALS_FILE), { recursive: true, mode: 0o700 });
72
+
73
+ // Always enforce 0600 — secrets file.
74
+ const mode = 0o600;
75
+ const tmp = `${CREDENTIALS_FILE}.${process.pid}.tmp`;
76
+ writeFileSync(tmp, JSON.stringify(migrated, null, 2), { mode });
77
+ try {
78
+ chmodSync(tmp, mode);
79
+ } catch {
80
+ // Windows or unsupported FS.
81
+ }
82
+ renameSync(tmp, CREDENTIALS_FILE);
83
+ }
84
+
85
+ function capturePackageManagerIfPossible() {
86
+ const pm = detectPackageManager();
87
+ if (!pm) return;
88
+
89
+ // Preserve an existing user-pinned choice — only auto-detection writes
90
+ // are overwritten by later installs.
91
+ if (existsSync(INSTALL_FILE)) {
92
+ try {
93
+ const existing = JSON.parse(readFileSync(INSTALL_FILE, "utf8"));
94
+ if (existing && existing.source === "user") return;
95
+ // If the user is reinstalling with the same PM, no-op so we don't
96
+ // churn mtimes on every `npm i -g` rerun.
97
+ if (existing && existing.packageManager === pm && existing.source === "auto") return;
98
+ } catch {
99
+ // Fall through and overwrite a corrupt install.json.
100
+ }
101
+ }
102
+
103
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
104
+ const payload = {
105
+ packageManager: pm,
106
+ source: "auto",
107
+ detectedAt: new Date().toISOString(),
108
+ };
109
+ const tmp = `${INSTALL_FILE}.${process.pid}.tmp`;
110
+ writeFileSync(tmp, JSON.stringify(payload, null, 2));
111
+ renameSync(tmp, INSTALL_FILE);
112
+ }
113
+
114
+ /**
115
+ * Parse npm_config_user_agent. All three managers set it during script
116
+ * execution. Example values:
117
+ *
118
+ * npm/10.2.4 node/v20.11.0 darwin arm64 workspaces/false
119
+ * pnpm/8.15.0 npm/? node/v20.11.0 darwin arm64
120
+ * yarn/1.22.19 npm/? node/v20.11.0 darwin arm64
121
+ *
122
+ * We look at the LEADING token: that's the manager actually running this
123
+ * script. pnpm/yarn both append a ghost "npm/?" segment, which is why we
124
+ * never use a contains-check.
125
+ */
126
+ function detectPackageManager() {
127
+ const ua = process.env.npm_config_user_agent;
128
+ if (typeof ua !== "string" || ua.length === 0) return null;
129
+ const head = ua.split(" ")[0] ?? "";
130
+ const slash = head.indexOf("/");
131
+ const name = slash > 0 ? head.slice(0, slash) : head;
132
+ switch (name) {
133
+ case "npm":
134
+ case "pnpm":
135
+ case "yarn":
136
+ return name;
137
+ default:
138
+ return null;
139
+ }
140
+ }