@mycompbox/compbox-cli 1.13.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.
Files changed (98) hide show
  1. package/.tsbuildinfo +1 -0
  2. package/commands/configure.d.ts +24 -0
  3. package/commands/configure.d.ts.map +1 -0
  4. package/commands/configure.js +170 -0
  5. package/commands/configure.js.map +1 -0
  6. package/commands/doctor.d.ts +136 -0
  7. package/commands/doctor.d.ts.map +1 -0
  8. package/commands/doctor.js +606 -0
  9. package/commands/doctor.js.map +1 -0
  10. package/commands/install.d.ts +3 -0
  11. package/commands/install.d.ts.map +1 -0
  12. package/commands/install.js +88 -0
  13. package/commands/install.js.map +1 -0
  14. package/commands/list.d.ts +3 -0
  15. package/commands/list.d.ts.map +1 -0
  16. package/commands/list.js +141 -0
  17. package/commands/list.js.map +1 -0
  18. package/commands/login.d.ts +15 -0
  19. package/commands/login.d.ts.map +1 -0
  20. package/commands/login.js +102 -0
  21. package/commands/login.js.map +1 -0
  22. package/commands/publish.d.ts +3 -0
  23. package/commands/publish.d.ts.map +1 -0
  24. package/commands/publish.js +102 -0
  25. package/commands/publish.js.map +1 -0
  26. package/commands/run.d.ts +76 -0
  27. package/commands/run.d.ts.map +1 -0
  28. package/commands/run.js +417 -0
  29. package/commands/run.js.map +1 -0
  30. package/commands/service.d.ts +29 -0
  31. package/commands/service.d.ts.map +1 -0
  32. package/commands/service.js +142 -0
  33. package/commands/service.js.map +1 -0
  34. package/commands/update.d.ts +3 -0
  35. package/commands/update.d.ts.map +1 -0
  36. package/commands/update.js +141 -0
  37. package/commands/update.js.map +1 -0
  38. package/index.d.ts +3 -0
  39. package/index.d.ts.map +1 -0
  40. package/index.js +8508 -0
  41. package/index.js.map +1 -0
  42. package/package.json +30 -0
  43. package/utils/agent-invoker.d.ts +393 -0
  44. package/utils/agent-invoker.d.ts.map +1 -0
  45. package/utils/agent-invoker.js +1632 -0
  46. package/utils/agent-invoker.js.map +1 -0
  47. package/utils/api-client.d.ts +54 -0
  48. package/utils/api-client.d.ts.map +1 -0
  49. package/utils/api-client.js +214 -0
  50. package/utils/api-client.js.map +1 -0
  51. package/utils/auth-client.d.ts +61 -0
  52. package/utils/auth-client.d.ts.map +1 -0
  53. package/utils/auth-client.js +135 -0
  54. package/utils/auth-client.js.map +1 -0
  55. package/utils/claude-runtime.d.ts +116 -0
  56. package/utils/claude-runtime.d.ts.map +1 -0
  57. package/utils/claude-runtime.js +291 -0
  58. package/utils/claude-runtime.js.map +1 -0
  59. package/utils/config-store.d.ts +128 -0
  60. package/utils/config-store.d.ts.map +1 -0
  61. package/utils/config-store.js +137 -0
  62. package/utils/config-store.js.map +1 -0
  63. package/utils/file-manager.d.ts +32 -0
  64. package/utils/file-manager.d.ts.map +1 -0
  65. package/utils/file-manager.js +208 -0
  66. package/utils/file-manager.js.map +1 -0
  67. package/utils/keychain.d.ts +46 -0
  68. package/utils/keychain.d.ts.map +1 -0
  69. package/utils/keychain.js +167 -0
  70. package/utils/keychain.js.map +1 -0
  71. package/utils/loopback.d.ts +45 -0
  72. package/utils/loopback.d.ts.map +1 -0
  73. package/utils/loopback.js +95 -0
  74. package/utils/loopback.js.map +1 -0
  75. package/utils/mailbox-client.d.ts +98 -0
  76. package/utils/mailbox-client.d.ts.map +1 -0
  77. package/utils/mailbox-client.js +178 -0
  78. package/utils/mailbox-client.js.map +1 -0
  79. package/utils/mcp-config-writer.d.ts +133 -0
  80. package/utils/mcp-config-writer.d.ts.map +1 -0
  81. package/utils/mcp-config-writer.js +184 -0
  82. package/utils/mcp-config-writer.js.map +1 -0
  83. package/utils/pack-bundler.d.ts +30 -0
  84. package/utils/pack-bundler.d.ts.map +1 -0
  85. package/utils/pack-bundler.js +141 -0
  86. package/utils/pack-bundler.js.map +1 -0
  87. package/utils/prompt.d.ts +31 -0
  88. package/utils/prompt.d.ts.map +1 -0
  89. package/utils/prompt.js +86 -0
  90. package/utils/prompt.js.map +1 -0
  91. package/utils/table-formatter.d.ts +42 -0
  92. package/utils/table-formatter.d.ts.map +1 -0
  93. package/utils/table-formatter.js +125 -0
  94. package/utils/table-formatter.js.map +1 -0
  95. package/utils/version-resolver.d.ts +31 -0
  96. package/utils/version-resolver.d.ts.map +1 -0
  97. package/utils/version-resolver.js +75 -0
  98. package/utils/version-resolver.js.map +1 -0
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Local configuration store for the CompBox employee agent.
3
+ *
4
+ * Persists NON-SECRET pairing configuration to `~/.compbox/config.json`:
5
+ * - the central API base URL
6
+ * - the per-employee CompBox API token (an `st_...` pairing credential)
7
+ * - resolved identity (userId / email / name) for display and doctor checks
8
+ * - the selected AI mode
9
+ *
10
+ * The AI provider secret (e.g. an Anthropic API key) is NEVER stored here — it
11
+ * lives in the OS keychain (see `keychain.ts`) and is never transmitted to
12
+ * central. The API token is a pairing credential and is written to this file
13
+ * with `0600` permissions (owner-only), mirroring how tools like npm persist
14
+ * registry tokens in `.npmrc`.
15
+ *
16
+ * The config directory can be overridden with the `COMPBOX_CONFIG_DIR`
17
+ * environment variable (used by tests to avoid touching the real home dir).
18
+ */
19
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, statSync } from 'fs';
20
+ import { join } from 'path';
21
+ import { homedir } from 'os';
22
+ const CONFIG_DIR_ENV = 'COMPBOX_CONFIG_DIR';
23
+ /** Environment fallback for the project repo directory (ENG-164 / H2). */
24
+ export const PROJECT_DIR_ENV = 'COMPBOX_PROJECT_DIR';
25
+ /** Absolute path to the CompBox config directory (`~/.compbox` by default). */
26
+ export function getConfigDir() {
27
+ const override = process.env[CONFIG_DIR_ENV];
28
+ if (override && override.trim().length > 0) {
29
+ return override;
30
+ }
31
+ return join(homedir(), '.compbox');
32
+ }
33
+ /** Absolute path to the config file. */
34
+ export function getConfigPath() {
35
+ return join(getConfigDir(), 'config.json');
36
+ }
37
+ /**
38
+ * Read the local config. Returns an empty object if the file is missing or
39
+ * unreadable/corrupt — callers treat a missing config as "not configured yet".
40
+ */
41
+ export function readConfig() {
42
+ try {
43
+ const path = getConfigPath();
44
+ if (!existsSync(path)) {
45
+ return {};
46
+ }
47
+ const parsed = JSON.parse(readFileSync(path, 'utf-8'));
48
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
49
+ return parsed;
50
+ }
51
+ return {};
52
+ }
53
+ catch {
54
+ return {};
55
+ }
56
+ }
57
+ /**
58
+ * Write the full config object, creating the directory if needed and enforcing
59
+ * owner-only (`0600`) permissions since it contains the API token.
60
+ */
61
+ export function writeConfig(config) {
62
+ const dir = getConfigDir();
63
+ mkdirSync(dir, { recursive: true });
64
+ const path = getConfigPath();
65
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, {
66
+ encoding: 'utf-8',
67
+ mode: 0o600,
68
+ });
69
+ // writeFileSync's `mode` only applies on creation; enforce on existing files too.
70
+ try {
71
+ chmodSync(path, 0o600);
72
+ }
73
+ catch {
74
+ // Best-effort: chmod is a no-op / unsupported on some platforms (e.g. Windows).
75
+ }
76
+ }
77
+ /**
78
+ * Shallow-merge a partial patch into the existing config and persist it.
79
+ * Returns the merged config.
80
+ */
81
+ export function updateConfig(patch) {
82
+ const current = readConfig();
83
+ const merged = { ...current, ...patch };
84
+ writeConfig(merged);
85
+ return merged;
86
+ }
87
+ /**
88
+ * Promote the persisted pxpipe enable flag into the environment (ENG-E26) so
89
+ * the daemon routes agent calls through pxpipe even when running as a
90
+ * background service (which does not source the user's shell profile). A pre-set
91
+ * PXPIPE_ENABLED in the environment always wins and is left untouched.
92
+ */
93
+ export function applyPxpipeEnv(config, env = process.env) {
94
+ if (config.pxpipeEnabled && env.PXPIPE_ENABLED === undefined) {
95
+ env.PXPIPE_ENABLED = 'true';
96
+ }
97
+ }
98
+ /**
99
+ * Resolve the project repository directory for a `start_execution` job (ENG-164
100
+ * / H2), in a fixed, unit-tested precedence order:
101
+ *
102
+ * 1. An explicit `--project-dir <path>` flag (highest — an operator override
103
+ * for this run; run.ts also persists it so later runs inherit it).
104
+ * 2. The `COMPBOX_PROJECT_DIR` environment variable.
105
+ * 3. The persisted `config.projectDir` (set by a prior `--project-dir`).
106
+ *
107
+ * Returns the first non-empty candidate (trimmed), or `undefined` when none is
108
+ * set. This is a pure resolution helper — it does NOT check that the path is a
109
+ * git repository; validation is the caller's job so the failure can carry an
110
+ * actionable, path-specific message (see the invoker's git-repo check).
111
+ */
112
+ export function resolveProjectDir(opts) {
113
+ const env = opts.env ?? process.env;
114
+ const candidates = [opts.flag, env[PROJECT_DIR_ENV], opts.config?.projectDir];
115
+ for (const candidate of candidates) {
116
+ if (typeof candidate === 'string' && candidate.trim().length > 0) {
117
+ return candidate.trim();
118
+ }
119
+ }
120
+ return undefined;
121
+ }
122
+ /**
123
+ * True when `dir` looks like a git repository — a `.git` entry (directory for a
124
+ * normal checkout, or a file for a worktree/submodule) exists inside it. Used to
125
+ * validate {@link resolveProjectDir}'s result before spawning the planner so a
126
+ * misconfigured path fails fast with an actionable message rather than letting
127
+ * `claude` start in a non-repo (where `.mcp.json`/`CLAUDE.md` would be absent).
128
+ */
129
+ export function isGitRepoDir(dir) {
130
+ try {
131
+ return existsSync(join(dir, '.git')) && statSync(dir).isDirectory();
132
+ }
133
+ catch {
134
+ return false;
135
+ }
136
+ }
137
+ //# sourceMappingURL=config-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-store.js","sourceRoot":"","sources":["../../src/utils/config-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ export interface LocalManifest {
2
+ installedPacks: Record<string, {
3
+ version: string;
4
+ installedAt: string;
5
+ files: string[];
6
+ }>;
7
+ }
8
+ export declare class FileManager {
9
+ private manifestPath;
10
+ private githubDir;
11
+ private compboxDir;
12
+ constructor(projectRoot?: string);
13
+ ensureDirectories(): Promise<void>;
14
+ readManifest(): Promise<LocalManifest>;
15
+ writeManifest(manifest: LocalManifest): Promise<void>;
16
+ addPackToManifest(packName: string, version: string, files: string[]): Promise<void>;
17
+ removePackFromManifest(packName: string): Promise<void>;
18
+ copyFilesToGithub(filesBuffer: Buffer, _packName: string): Promise<string[]>;
19
+ /**
20
+ * Recursively get all files in a directory
21
+ */
22
+ private getAllFiles;
23
+ deletePackFiles(files: string[]): Promise<void>;
24
+ /**
25
+ * Recursively remove empty directories up to .github/
26
+ */
27
+ private removeEmptyDirs;
28
+ mergeMcpConfig(newConfig: Record<string, unknown>): Promise<void>;
29
+ githubDirExists(): boolean;
30
+ compboxDirExists(): boolean;
31
+ }
32
+ //# sourceMappingURL=file-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-manager.d.ts","sourceRoot":"","sources":["../../src/utils/file-manager.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC,CAAC;CACN;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;gBAEf,WAAW,CAAC,EAAE,MAAM;IAO1B,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAKlC,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC;IAUtC,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpF,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAwElF;;OAEG;YACW,WAAW;IAuBnB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAmCrD;;OAEG;YACW,eAAe;IAsBvB,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBvE,eAAe,IAAI,OAAO;IAI1B,gBAAgB,IAAI,OAAO;CAG5B"}
@@ -0,0 +1,208 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { existsSync } from 'fs';
4
+ export class FileManager {
5
+ manifestPath;
6
+ githubDir;
7
+ compboxDir;
8
+ constructor(projectRoot) {
9
+ const root = projectRoot ?? process.cwd();
10
+ this.githubDir = path.join(root, '.github');
11
+ this.compboxDir = path.join(root, '.compbox');
12
+ this.manifestPath = path.join(this.compboxDir, 'manifest.json');
13
+ }
14
+ async ensureDirectories() {
15
+ await fs.mkdir(this.githubDir, { recursive: true });
16
+ await fs.mkdir(this.compboxDir, { recursive: true });
17
+ }
18
+ async readManifest() {
19
+ try {
20
+ const content = await fs.readFile(this.manifestPath, 'utf-8');
21
+ return JSON.parse(content);
22
+ }
23
+ catch {
24
+ // Return empty manifest if file doesn't exist
25
+ return { installedPacks: {} };
26
+ }
27
+ }
28
+ async writeManifest(manifest) {
29
+ await this.ensureDirectories();
30
+ await fs.writeFile(this.manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
31
+ }
32
+ async addPackToManifest(packName, version, files) {
33
+ const manifest = await this.readManifest();
34
+ manifest.installedPacks[packName] = {
35
+ version,
36
+ installedAt: new Date().toISOString(),
37
+ files,
38
+ };
39
+ await this.writeManifest(manifest);
40
+ }
41
+ async removePackFromManifest(packName) {
42
+ const manifest = await this.readManifest();
43
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
44
+ delete manifest.installedPacks[packName];
45
+ await this.writeManifest(manifest);
46
+ }
47
+ async copyFilesToGithub(filesBuffer, _packName) {
48
+ await this.ensureDirectories();
49
+ // Extract tar.gz archive
50
+ const tar = await import('tar');
51
+ const fs = await import('fs/promises');
52
+ const path = await import('path');
53
+ const os = await import('os');
54
+ // Create a temporary directory for extraction
55
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'compbox-extract-'));
56
+ try {
57
+ // Write buffer to temp file
58
+ const tarPath = path.join(tmpDir, 'pack.tar.gz');
59
+ await fs.writeFile(tarPath, filesBuffer);
60
+ // Extract to temp directory
61
+ await tar.extract({
62
+ file: tarPath,
63
+ cwd: tmpDir,
64
+ });
65
+ // Get list of extracted files
66
+ const extractedFiles = await this.getAllFiles(tmpDir);
67
+ // Copy extracted files into the .github/ directory
68
+ // Registry pack paths may or may not include a .github/ prefix:
69
+ // - Legacy/test packs: paths like ".github/agents/foo.md"
70
+ // - Current registry packs: paths like "agents/foo.md"
71
+ // We normalize by stripping the .github/ prefix if present,
72
+ // then writing into this.githubDir so files always land in .github/.
73
+ const installedFiles = [];
74
+ for (const file of extractedFiles) {
75
+ // Skip the tarball itself
76
+ if (file === 'pack.tar.gz')
77
+ continue;
78
+ // Strip .github/ prefix if present to avoid double-nesting
79
+ const normalizedFile = file.startsWith('.github/') || file.startsWith('.github\\')
80
+ ? file.slice('.github/'.length)
81
+ : file;
82
+ const sourcePath = path.join(tmpDir, file);
83
+ const targetPath = path.join(this.githubDir, normalizedFile);
84
+ const targetFileDir = path.dirname(targetPath);
85
+ // Ensure target directory exists
86
+ await fs.mkdir(targetFileDir, { recursive: true });
87
+ // Copy file
88
+ await fs.copyFile(sourcePath, targetPath);
89
+ // Track relative path from .github/
90
+ installedFiles.push(normalizedFile);
91
+ }
92
+ // Clean up temp directory
93
+ await fs.rm(tmpDir, { recursive: true, force: true });
94
+ return installedFiles;
95
+ }
96
+ catch (error) {
97
+ // Clean up temp directory on error
98
+ try {
99
+ await fs.rm(tmpDir, { recursive: true, force: true });
100
+ }
101
+ catch {
102
+ // Ignore cleanup errors
103
+ }
104
+ throw error;
105
+ }
106
+ }
107
+ /**
108
+ * Recursively get all files in a directory
109
+ */
110
+ async getAllFiles(dir, baseDir) {
111
+ const fs = await import('fs/promises');
112
+ const path = await import('path');
113
+ const base = baseDir ?? dir;
114
+ const entries = await fs.readdir(dir, { withFileTypes: true });
115
+ const files = [];
116
+ for (const entry of entries) {
117
+ const fullPath = path.join(dir, entry.name);
118
+ if (entry.isDirectory()) {
119
+ const nestedFiles = await this.getAllFiles(fullPath, base);
120
+ files.push(...nestedFiles);
121
+ }
122
+ else if (entry.isFile()) {
123
+ const relativePath = path.relative(base, fullPath);
124
+ files.push(relativePath);
125
+ }
126
+ }
127
+ return files;
128
+ }
129
+ async deletePackFiles(files) {
130
+ const githubRoot = path.resolve(this.githubDir);
131
+ const deletedDirs = new Set();
132
+ for (const file of files) {
133
+ try {
134
+ // Normalize separators and legacy prefixes (../ or .github/) from older manifests
135
+ let normalizedFile = file.replace(/\\/g, '/');
136
+ if (normalizedFile.startsWith('../')) {
137
+ normalizedFile = normalizedFile.slice(3);
138
+ }
139
+ if (normalizedFile.startsWith('.github/')) {
140
+ normalizedFile = normalizedFile.slice('.github/'.length);
141
+ }
142
+ // Validate resolved path stays within .github/ to prevent path traversal
143
+ const resolved = path.resolve(this.githubDir, normalizedFile);
144
+ const relative = path.relative(githubRoot, resolved);
145
+ if (!relative || relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) {
146
+ continue;
147
+ }
148
+ await fs.unlink(resolved);
149
+ deletedDirs.add(path.dirname(resolved));
150
+ }
151
+ catch {
152
+ // Ignore errors if file doesn't exist
153
+ }
154
+ }
155
+ // Clean up empty directories left behind after file deletion
156
+ for (const dir of deletedDirs) {
157
+ await this.removeEmptyDirs(dir);
158
+ }
159
+ }
160
+ /**
161
+ * Recursively remove empty directories up to .github/
162
+ */
163
+ async removeEmptyDirs(dir) {
164
+ const githubRoot = path.resolve(this.githubDir);
165
+ const targetDir = path.resolve(dir);
166
+ const relative = path.relative(githubRoot, targetDir);
167
+ // Don't delete .github/ itself or anything above it
168
+ if (!relative || relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) {
169
+ return;
170
+ }
171
+ try {
172
+ const entries = await fs.readdir(targetDir);
173
+ if (entries.length === 0) {
174
+ await fs.rmdir(targetDir);
175
+ // Recurse up to parent
176
+ await this.removeEmptyDirs(path.dirname(targetDir));
177
+ }
178
+ }
179
+ catch {
180
+ // Directory may not exist or not be empty
181
+ }
182
+ }
183
+ async mergeMcpConfig(newConfig) {
184
+ const mcpConfigPath = path.join(this.githubDir, 'copilot-instructions', 'mcp.json');
185
+ let existingConfig = {};
186
+ if (existsSync(mcpConfigPath)) {
187
+ const content = await fs.readFile(mcpConfigPath, 'utf-8');
188
+ existingConfig = JSON.parse(content);
189
+ }
190
+ // Merge configs - new tools are added, existing tools are preserved
191
+ const mergedConfig = {
192
+ ...existingConfig,
193
+ mcpServers: {
194
+ ...(existingConfig.mcpServers ?? {}),
195
+ ...(newConfig.mcpServers ?? {}),
196
+ },
197
+ };
198
+ await fs.mkdir(path.dirname(mcpConfigPath), { recursive: true });
199
+ await fs.writeFile(mcpConfigPath, JSON.stringify(mergedConfig, null, 2), 'utf-8');
200
+ }
201
+ githubDirExists() {
202
+ return existsSync(this.githubDir);
203
+ }
204
+ compboxDirExists() {
205
+ return existsSync(this.compboxDir);
206
+ }
207
+ }
208
+ //# sourceMappingURL=file-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-manager.js","sourceRoot":"","sources":["../../src/utils/file-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * OS keychain wrapper for storing the employee's AI provider secret.
3
+ *
4
+ * Primary backend is `keytar`, which bridges to the native OS credential store:
5
+ * - macOS → Keychain
6
+ * - Windows → Credential Manager
7
+ * - Linux → Secret Service (libsecret / GNOME Keyring / KWallet)
8
+ *
9
+ * `keytar` is an OPTIONAL native dependency. On headless Linux (CI, servers, a
10
+ * bare SSH session with no Secret Service running) or any machine where the
11
+ * native module isn't available, we fall back to an owner-only (`0600`) file at
12
+ * `~/.compbox/secrets.json`. The fallback is clearly signalled to callers via
13
+ * the returned {@link KeychainBackend} so the CLI can warn the user.
14
+ *
15
+ * The secret handled here (e.g. an Anthropic API key) is NEVER transmitted to
16
+ * central — it stays on the employee's machine.
17
+ */
18
+ /** Which backend actually serviced a keychain operation. */
19
+ export type KeychainBackend = 'keychain' | 'file';
20
+ /** Keytar service name namespacing all CompBox AI secrets. */
21
+ export declare const KEYCHAIN_SERVICE = "compbox-ai";
22
+ /** Account key under which the AI provider API key is stored. */
23
+ export declare const AI_KEY_ACCOUNT = "anthropic-api-key";
24
+ /**
25
+ * Account key under which this machine's mailbox token (`mk_...`) is stored.
26
+ * Separate from the user-level API token (`config-store.ts`'s `apiToken`,
27
+ * an `st_...` token) — the machine token authenticates the daemon to the
28
+ * daemon-only mailbox endpoints (next-job, heartbeat, complete-job) and must
29
+ * never be used for general API access.
30
+ */
31
+ export declare const MACHINE_TOKEN_ACCOUNT = "machine-token";
32
+ /**
33
+ * Store a secret. Prefers the OS keychain; falls back to the owner-only file
34
+ * store. Returns the backend that was used.
35
+ */
36
+ export declare function storeSecret(account: string, secret: string): Promise<KeychainBackend>;
37
+ /** Retrieve a secret, reporting which backend held it. */
38
+ export declare function retrieveSecret(account: string): Promise<{
39
+ value: string | null;
40
+ backend: KeychainBackend;
41
+ }>;
42
+ /** Delete a secret from whichever backend holds it. */
43
+ export declare function deleteSecret(account: string): Promise<KeychainBackend>;
44
+ /** Test-only: reset the memoized keytar load so mocks can be re-evaluated. */
45
+ export declare function __resetKeytarCacheForTests(): void;
46
+ //# sourceMappingURL=keychain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain.d.ts","sourceRoot":"","sources":["../../src/utils/keychain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,MAAM,CAAC;AAElD,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAE7C,iEAAiE;AACjE,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAoErD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC,CAiB1B;AAED,0DAA0D;AAC1D,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,CAAC,CAkB7D;AAED,uDAAuD;AACvD,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA2B5E;AAED,8EAA8E;AAC9E,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * OS keychain wrapper for storing the employee's AI provider secret.
3
+ *
4
+ * Primary backend is `keytar`, which bridges to the native OS credential store:
5
+ * - macOS → Keychain
6
+ * - Windows → Credential Manager
7
+ * - Linux → Secret Service (libsecret / GNOME Keyring / KWallet)
8
+ *
9
+ * `keytar` is an OPTIONAL native dependency. On headless Linux (CI, servers, a
10
+ * bare SSH session with no Secret Service running) or any machine where the
11
+ * native module isn't available, we fall back to an owner-only (`0600`) file at
12
+ * `~/.compbox/secrets.json`. The fallback is clearly signalled to callers via
13
+ * the returned {@link KeychainBackend} so the CLI can warn the user.
14
+ *
15
+ * The secret handled here (e.g. an Anthropic API key) is NEVER transmitted to
16
+ * central — it stays on the employee's machine.
17
+ */
18
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, rmSync } from 'fs';
19
+ import { join } from 'path';
20
+ import { getConfigDir } from './config-store.js';
21
+ /** Keytar service name namespacing all CompBox AI secrets. */
22
+ export const KEYCHAIN_SERVICE = 'compbox-ai';
23
+ /** Account key under which the AI provider API key is stored. */
24
+ export const AI_KEY_ACCOUNT = 'anthropic-api-key';
25
+ /**
26
+ * Account key under which this machine's mailbox token (`mk_...`) is stored.
27
+ * Separate from the user-level API token (`config-store.ts`'s `apiToken`,
28
+ * an `st_...` token) — the machine token authenticates the daemon to the
29
+ * daemon-only mailbox endpoints (next-job, heartbeat, complete-job) and must
30
+ * never be used for general API access.
31
+ */
32
+ export const MACHINE_TOKEN_ACCOUNT = 'machine-token';
33
+ let keytarPromise;
34
+ /**
35
+ * Lazily attempt to load keytar. A non-literal specifier keeps TypeScript from
36
+ * requiring the module to be present at compile time (it's an optional native
37
+ * dep). Any load failure resolves to `null`, triggering the file fallback.
38
+ */
39
+ async function loadKeytar() {
40
+ if (keytarPromise === undefined) {
41
+ // `as string` keeps `import(specifier)` a runtime-only dynamic import typed
42
+ // Promise<any>, so it needs no keytar type declarations at build time.
43
+ const specifier = 'keytar';
44
+ keytarPromise = import(specifier)
45
+ .then((mod) => {
46
+ const candidate = mod.default ?? mod;
47
+ return candidate;
48
+ })
49
+ .catch(() => null);
50
+ }
51
+ return keytarPromise;
52
+ }
53
+ function secretsPath() {
54
+ return join(getConfigDir(), 'secrets.json');
55
+ }
56
+ function readSecretsFile() {
57
+ try {
58
+ const path = secretsPath();
59
+ if (!existsSync(path)) {
60
+ return {};
61
+ }
62
+ const parsed = JSON.parse(readFileSync(path, 'utf-8'));
63
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
64
+ return parsed;
65
+ }
66
+ return {};
67
+ }
68
+ catch {
69
+ return {};
70
+ }
71
+ }
72
+ function writeSecretsFile(secrets) {
73
+ const dir = getConfigDir();
74
+ mkdirSync(dir, { recursive: true });
75
+ const path = secretsPath();
76
+ writeFileSync(path, `${JSON.stringify(secrets, null, 2)}\n`, {
77
+ encoding: 'utf-8',
78
+ mode: 0o600,
79
+ });
80
+ try {
81
+ chmodSync(path, 0o600);
82
+ }
83
+ catch {
84
+ // Best-effort on platforms without POSIX permissions.
85
+ }
86
+ }
87
+ /**
88
+ * Store a secret. Prefers the OS keychain; falls back to the owner-only file
89
+ * store. Returns the backend that was used.
90
+ */
91
+ export async function storeSecret(account, secret) {
92
+ const keytar = await loadKeytar();
93
+ if (keytar) {
94
+ try {
95
+ await keytar.setPassword(KEYCHAIN_SERVICE, account, secret);
96
+ return 'keychain';
97
+ }
98
+ catch {
99
+ // Keychain present but unusable (e.g. no Secret Service on headless Linux):
100
+ // fall through to file storage.
101
+ }
102
+ }
103
+ // File fallback. base64 is obfuscation only (not encryption); the real
104
+ // protection is the 0600 file mode restricting access to the owner.
105
+ const secrets = readSecretsFile();
106
+ secrets[account] = Buffer.from(secret, 'utf-8').toString('base64');
107
+ writeSecretsFile(secrets);
108
+ return 'file';
109
+ }
110
+ /** Retrieve a secret, reporting which backend held it. */
111
+ export async function retrieveSecret(account) {
112
+ const keytar = await loadKeytar();
113
+ if (keytar) {
114
+ try {
115
+ const value = await keytar.getPassword(KEYCHAIN_SERVICE, account);
116
+ if (value !== null) {
117
+ return { value, backend: 'keychain' };
118
+ }
119
+ }
120
+ catch {
121
+ // Fall through to file store.
122
+ }
123
+ }
124
+ const secrets = readSecretsFile();
125
+ const encoded = secrets[account];
126
+ if (encoded === undefined) {
127
+ return { value: null, backend: 'file' };
128
+ }
129
+ return { value: Buffer.from(encoded, 'base64').toString('utf-8'), backend: 'file' };
130
+ }
131
+ /** Delete a secret from whichever backend holds it. */
132
+ export async function deleteSecret(account) {
133
+ const keytar = await loadKeytar();
134
+ if (keytar) {
135
+ try {
136
+ const removed = await keytar.deletePassword(KEYCHAIN_SERVICE, account);
137
+ if (removed) {
138
+ return 'keychain';
139
+ }
140
+ }
141
+ catch {
142
+ // Fall through to file store.
143
+ }
144
+ }
145
+ const secrets = readSecretsFile();
146
+ if (account in secrets) {
147
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
148
+ delete secrets[account];
149
+ if (Object.keys(secrets).length === 0) {
150
+ try {
151
+ rmSync(secretsPath(), { force: true });
152
+ }
153
+ catch {
154
+ // ignore
155
+ }
156
+ }
157
+ else {
158
+ writeSecretsFile(secrets);
159
+ }
160
+ }
161
+ return 'file';
162
+ }
163
+ /** Test-only: reset the memoized keytar load so mocks can be re-evaluated. */
164
+ export function __resetKeytarCacheForTests() {
165
+ keytarPromise = undefined;
166
+ }
167
+ //# sourceMappingURL=keychain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../src/utils/keychain.ts"],"names":[],"mappings":"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"}