@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.
- package/.tsbuildinfo +1 -0
- package/commands/configure.d.ts +24 -0
- package/commands/configure.d.ts.map +1 -0
- package/commands/configure.js +170 -0
- package/commands/configure.js.map +1 -0
- package/commands/doctor.d.ts +136 -0
- package/commands/doctor.d.ts.map +1 -0
- package/commands/doctor.js +606 -0
- package/commands/doctor.js.map +1 -0
- package/commands/install.d.ts +3 -0
- package/commands/install.d.ts.map +1 -0
- package/commands/install.js +88 -0
- package/commands/install.js.map +1 -0
- package/commands/list.d.ts +3 -0
- package/commands/list.d.ts.map +1 -0
- package/commands/list.js +141 -0
- package/commands/list.js.map +1 -0
- package/commands/login.d.ts +15 -0
- package/commands/login.d.ts.map +1 -0
- package/commands/login.js +102 -0
- package/commands/login.js.map +1 -0
- package/commands/publish.d.ts +3 -0
- package/commands/publish.d.ts.map +1 -0
- package/commands/publish.js +102 -0
- package/commands/publish.js.map +1 -0
- package/commands/run.d.ts +76 -0
- package/commands/run.d.ts.map +1 -0
- package/commands/run.js +417 -0
- package/commands/run.js.map +1 -0
- package/commands/service.d.ts +29 -0
- package/commands/service.d.ts.map +1 -0
- package/commands/service.js +142 -0
- package/commands/service.js.map +1 -0
- package/commands/update.d.ts +3 -0
- package/commands/update.d.ts.map +1 -0
- package/commands/update.js +141 -0
- package/commands/update.js.map +1 -0
- package/index.d.ts +3 -0
- package/index.d.ts.map +1 -0
- package/index.js +8508 -0
- package/index.js.map +1 -0
- package/package.json +30 -0
- package/utils/agent-invoker.d.ts +393 -0
- package/utils/agent-invoker.d.ts.map +1 -0
- package/utils/agent-invoker.js +1632 -0
- package/utils/agent-invoker.js.map +1 -0
- package/utils/api-client.d.ts +54 -0
- package/utils/api-client.d.ts.map +1 -0
- package/utils/api-client.js +214 -0
- package/utils/api-client.js.map +1 -0
- package/utils/auth-client.d.ts +61 -0
- package/utils/auth-client.d.ts.map +1 -0
- package/utils/auth-client.js +135 -0
- package/utils/auth-client.js.map +1 -0
- package/utils/claude-runtime.d.ts +116 -0
- package/utils/claude-runtime.d.ts.map +1 -0
- package/utils/claude-runtime.js +291 -0
- package/utils/claude-runtime.js.map +1 -0
- package/utils/config-store.d.ts +128 -0
- package/utils/config-store.d.ts.map +1 -0
- package/utils/config-store.js +137 -0
- package/utils/config-store.js.map +1 -0
- package/utils/file-manager.d.ts +32 -0
- package/utils/file-manager.d.ts.map +1 -0
- package/utils/file-manager.js +208 -0
- package/utils/file-manager.js.map +1 -0
- package/utils/keychain.d.ts +46 -0
- package/utils/keychain.d.ts.map +1 -0
- package/utils/keychain.js +167 -0
- package/utils/keychain.js.map +1 -0
- package/utils/loopback.d.ts +45 -0
- package/utils/loopback.d.ts.map +1 -0
- package/utils/loopback.js +95 -0
- package/utils/loopback.js.map +1 -0
- package/utils/mailbox-client.d.ts +98 -0
- package/utils/mailbox-client.d.ts.map +1 -0
- package/utils/mailbox-client.js +178 -0
- package/utils/mailbox-client.js.map +1 -0
- package/utils/mcp-config-writer.d.ts +133 -0
- package/utils/mcp-config-writer.d.ts.map +1 -0
- package/utils/mcp-config-writer.js +184 -0
- package/utils/mcp-config-writer.js.map +1 -0
- package/utils/pack-bundler.d.ts +30 -0
- package/utils/pack-bundler.d.ts.map +1 -0
- package/utils/pack-bundler.js +141 -0
- package/utils/pack-bundler.js.map +1 -0
- package/utils/prompt.d.ts +31 -0
- package/utils/prompt.d.ts.map +1 -0
- package/utils/prompt.js +86 -0
- package/utils/prompt.js.map +1 -0
- package/utils/table-formatter.d.ts +42 -0
- package/utils/table-formatter.d.ts.map +1 -0
- package/utils/table-formatter.js +125 -0
- package/utils/table-formatter.js.map +1 -0
- package/utils/version-resolver.d.ts +31 -0
- package/utils/version-resolver.d.ts.map +1 -0
- package/utils/version-resolver.js +75 -0
- 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":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC7F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AA0D7B,MAAM,cAAc,GAAG,oBAAoB,CAAC;AAE5C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAErD,+EAA+E;AAC/E,MAAM,UAAU,YAAY;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC7C,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AACrC,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,CAAC,YAAY,EAAE,EAAE,aAAa,CAAC,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;QAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAChE,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,OAAO,MAAuB,CAAC;QACjC,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAqB;IAC/C,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;IAC7B,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAC1D,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,kFAAkF;IAClF,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,gFAAgF;IAClF,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAA6B;IACxD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAkB,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IACvD,WAAW,CAAC,MAAM,CAAC,CAAC;IACpB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAqB,EACrB,MAAyB,OAAO,CAAC,GAAG;IAEpC,IAAI,MAAM,CAAC,aAAa,IAAI,GAAG,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QAC7D,GAAG,CAAC,cAAc,GAAG,MAAM,CAAC;IAC9B,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAIjC;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,eAAe,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjE,OAAO,SAAS,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -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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"}
|