mixdog 0.9.52 → 0.9.53
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 +1 -1
- package/scripts/bench-run.mjs +2 -2
- package/scripts/compact-pressure-test.mjs +104 -0
- package/scripts/desktop-session-bridge-test.mjs +704 -0
- package/scripts/freevar-smoke.mjs +7 -4
- package/scripts/lifecycle-api-test.mjs +65 -4
- package/scripts/max-output-recovery-test.mjs +31 -0
- package/scripts/memory-core-input-test.mjs +10 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +63 -3
- package/scripts/openai-ws-early-settle-test.mjs +40 -0
- package/scripts/parent-abort-link-test.mjs +24 -0
- package/scripts/process-lifecycle-test.mjs +80 -22
- package/scripts/provider-contract-test.mjs +257 -0
- package/scripts/provider-toolcall-test.mjs +172 -10
- package/scripts/session-orphan-sweep-test.mjs +27 -1
- package/src/lib/keychain-cjs.cjs +36 -23
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +1 -13
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +7 -8
- package/src/runtime/agent/orchestrator/agent-trace.mjs +33 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -300
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -4
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +18 -266
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +15 -130
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +5 -115
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +224 -0
- package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +119 -0
- package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +109 -0
- package/src/runtime/agent/orchestrator/providers/lib/openai-tool-args.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +14 -71
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +47 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -7
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +72 -77
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -20
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +10 -10
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +37 -28
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +5 -7
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +170 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +20 -223
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +11 -17
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -5
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +3 -58
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +24 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +57 -16
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +305 -127
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +46 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-grep-chunks.mjs +173 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +117 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-insights.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-spawn-helpers.mjs +107 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +1 -40
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +19 -277
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +22 -298
- package/src/runtime/agent/orchestrator/tools/lib/shell-spawn-retry.mjs +67 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -71
- package/src/runtime/channels/backends/discord-gateway.mjs +1 -1
- package/src/runtime/channels/backends/discord.mjs +6 -6
- package/src/runtime/channels/lib/config.mjs +15 -2
- package/src/runtime/channels/lib/memory-client.mjs +20 -3
- package/src/runtime/channels/lib/owned-runtime.mjs +19 -12
- package/src/runtime/channels/lib/status-snapshot.mjs +9 -0
- package/src/runtime/channels/lib/tool-dispatch.mjs +9 -5
- package/src/runtime/channels/lib/worker-main.mjs +16 -5
- package/src/runtime/memory/index.mjs +24 -198
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -53
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +115 -0
- package/src/runtime/memory/lib/memory-port-advertiser.mjs +105 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +0 -4
- package/src/runtime/memory/lib/query-handlers.mjs +2 -122
- package/src/runtime/memory/lib/query-maintenance-handlers.mjs +126 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +57 -0
- package/src/runtime/search/lib/http-fetch.mjs +1 -1
- package/src/runtime/shared/config.mjs +58 -13
- package/src/runtime/shared/llm/cost.mjs +14 -4
- package/src/runtime/shared/memory-snapshot.mjs +236 -0
- package/src/runtime/shared/process-lifecycle.mjs +92 -19
- package/src/runtime/shared/process-shutdown.mjs +6 -0
- package/src/runtime/shared/resource-admission.mjs +7 -2
- package/src/session-runtime/channel-config-api.mjs +7 -7
- package/src/session-runtime/config-lifecycle.mjs +20 -17
- package/src/session-runtime/cwd-plugins.mjs +9 -7
- package/src/session-runtime/env.mjs +1 -2
- package/src/session-runtime/hitch-profile.mjs +45 -0
- package/src/session-runtime/lifecycle-api.mjs +36 -9
- package/src/session-runtime/mcp-glue.mjs +6 -11
- package/src/session-runtime/provider-init-key.mjs +17 -0
- package/src/session-runtime/runtime-core.mjs +44 -103
- package/src/session-runtime/runtime-paths.mjs +20 -0
- package/src/session-runtime/runtime-tool-routing.mjs +55 -0
- package/src/session-runtime/tool-catalog-data.mjs +51 -0
- package/src/session-runtime/tool-catalog.mjs +15 -89
- package/src/standalone/agent-tool/spawn-preset.mjs +73 -0
- package/src/standalone/agent-tool/worker-rows.mjs +93 -0
- package/src/standalone/agent-tool.mjs +12 -162
- package/src/standalone/channel-admin.mjs +29 -0
- package/src/tui/App.jsx +11 -5
- package/src/tui/dist/index.mjs +202 -65
- package/src/tui/engine/session-api-ext.mjs +37 -4
- package/src/tui/index.jsx +1 -0
- package/src/tui/lib/voice-setup.mjs +5 -5
- package/scripts/_devtools-stub.mjs +0 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -288
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { spawnSync } = require('child_process');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const { resolvePluginData } = require('./plugin-paths.cjs');
|
|
7
|
-
|
|
8
|
-
const SERVICE = 'mixdog';
|
|
9
|
-
// Shared bound for every synchronous keychain backend (DPAPI/PowerShell on
|
|
10
|
-
// Windows, security(1) on macOS). A single env override keeps them consistent.
|
|
11
|
-
const KEYCHAIN_TIMEOUT_MS = Number(process.env.MIXDOG_KEYCHAIN_TIMEOUT_MS || 15000);
|
|
12
|
-
const POWERSHELL_TIMEOUT_MS = KEYCHAIN_TIMEOUT_MS;
|
|
13
|
-
|
|
14
|
-
// CommonJS module: cannot import the ESM src/shared/wsl.mjs, so inline an
|
|
15
|
-
// equivalent WSL check (process.platform reports 'linux' inside WSL).
|
|
16
|
-
function isWSL() {
|
|
17
|
-
if (process.platform !== 'linux') return false;
|
|
18
|
-
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
|
|
19
|
-
try { return /microsoft|wsl/i.test(fs.readFileSync('/proc/version', 'utf8')); }
|
|
20
|
-
catch { return false; }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Helpers
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
|
|
27
|
-
function platform() {
|
|
28
|
-
return process.platform;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function run(cmd, args, opts) {
|
|
32
|
-
// windowsHide + stdio:['ignore','pipe','pipe'] keeps powershell.exe
|
|
33
|
-
// consoleless during DPAPI ops. Without these flags every keychain
|
|
34
|
-
// read/write flashes a conhost window: setup-html's loaders call
|
|
35
|
-
// /agent/config and /memory/auth, and each credential probe can trigger
|
|
36
|
-
// one powershell.exe spawn — users saw 8-15 console flashes during
|
|
37
|
-
// config-UI page load.
|
|
38
|
-
// Default opts go BEFORE the spread so callers can still override.
|
|
39
|
-
const result = spawnSync(cmd, args, {
|
|
40
|
-
encoding: 'utf8',
|
|
41
|
-
shell: false,
|
|
42
|
-
windowsHide: true,
|
|
43
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
44
|
-
...opts,
|
|
45
|
-
});
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// PS 5.1(powershell.exe) first: pwsh(PS 7) does not auto-load ProtectedData assembly,
|
|
50
|
-
// so DPAPI calls silently fail there. Windows always ships powershell.exe.
|
|
51
|
-
function powershell(script) {
|
|
52
|
-
// Bound DPAPI PowerShell calls with a timeout: a hung powershell.exe
|
|
53
|
-
// (AV scan stall, profile loader, transient cert chain lookup) would
|
|
54
|
-
// otherwise block hook/server callers synchronously. The default is long
|
|
55
|
-
// enough for cold PowerShell startup on Windows without silently dropping
|
|
56
|
-
// setup UI credential saves.
|
|
57
|
-
// Resolve powershell.exe to its deterministic System32 location rather than
|
|
58
|
-
// a PATH lookup, so a shadow `powershell.exe` earlier in PATH cannot hijack
|
|
59
|
-
// secret encryption/decryption. (pwsh/PS7 is intentionally not used: it does
|
|
60
|
-
// not auto-load the ProtectedData assembly, so DPAPI silently fails there.)
|
|
61
|
-
const _sysRoot = process.env.SystemRoot || process.env.windir;
|
|
62
|
-
if (!_sysRoot) {
|
|
63
|
-
throw new Error('[keychain] cannot resolve SystemRoot to locate powershell.exe for DPAPI');
|
|
64
|
-
}
|
|
65
|
-
const _psHosts = [path.join(_sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')];
|
|
66
|
-
let timedOut = null;
|
|
67
|
-
for (const exe of _psHosts) {
|
|
68
|
-
const r = run(exe, ['-NonInteractive', '-NoProfile', '-Command', script], { timeout: POWERSHELL_TIMEOUT_MS });
|
|
69
|
-
if (r.error && r.error.code === 'ENOENT') continue;
|
|
70
|
-
if (r.error && r.error.code === 'ETIMEDOUT') {
|
|
71
|
-
timedOut = { exe, timeoutMs: POWERSHELL_TIMEOUT_MS };
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
return r;
|
|
75
|
-
}
|
|
76
|
-
if (timedOut) {
|
|
77
|
-
throw new Error(`[keychain] PowerShell DPAPI command timed out after ${timedOut.timeoutMs}ms (last host: ${timedOut.exe})`);
|
|
78
|
-
}
|
|
79
|
-
const err = new Error('[keychain] PowerShell not found (tried powershell, pwsh)');
|
|
80
|
-
throw err;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function secretsDir() {
|
|
84
|
-
return path.join(resolvePluginData(), 'secrets');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function dpApiFile(account) {
|
|
88
|
-
return path.join(secretsDir(), account + '.dpapi');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
// darwin — security(1)
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
// Bound security(1) calls so a stuck Keychain prompt (locked keychain, GUI
|
|
96
|
-
// unlock dialog with no display) cannot block hook/server callers forever —
|
|
97
|
-
// matching the Windows DPAPI and Linux keytar timeouts.
|
|
98
|
-
function darwinRun(args) {
|
|
99
|
-
const r = run('security', args, { timeout: KEYCHAIN_TIMEOUT_MS });
|
|
100
|
-
if (r.error && r.error.code === 'ETIMEDOUT') {
|
|
101
|
-
throw new Error(`[keychain] security command timed out after ${KEYCHAIN_TIMEOUT_MS}ms`);
|
|
102
|
-
}
|
|
103
|
-
return r;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function darwinGet(account) {
|
|
107
|
-
const r = darwinRun(['find-generic-password', '-a', account, '-s', SERVICE, '-w']);
|
|
108
|
-
if (r.status !== 0) return null;
|
|
109
|
-
return r.stdout.trimEnd();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function darwinSet(account, value) {
|
|
113
|
-
const r = darwinRun(['add-generic-password', '-a', account, '-s', SERVICE, '-w', value, '-U']);
|
|
114
|
-
if (r.status !== 0) throw new Error(`[keychain] security set failed: ${r.stderr || r.stdout}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function darwinDelete(account) {
|
|
118
|
-
const r = darwinRun(['delete-generic-password', '-a', account, '-s', SERVICE]);
|
|
119
|
-
if (r.status !== 0) throw new Error(`[keychain] security delete failed: ${r.stderr || r.stdout}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
// linux/WSL — keytar (libsecret binding, optionalDependency)
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
// keytar must be installed: npm install keytar (requires libsecret-dev on
|
|
126
|
-
// Debian/Ubuntu, or libsecret on other distros). If not installed, every
|
|
127
|
-
// call throws immediately — silent credential loss is not permitted.
|
|
128
|
-
|
|
129
|
-
let _keytarMod = null;
|
|
130
|
-
function loadKeytar() {
|
|
131
|
-
if (_keytarMod !== null) return _keytarMod;
|
|
132
|
-
try { _keytarMod = require('keytar'); }
|
|
133
|
-
catch {
|
|
134
|
-
throw new Error(
|
|
135
|
-
'[keychain] keytar is not installed — run: npm install keytar\n' +
|
|
136
|
-
' Requires libsecret-dev (Debian/Ubuntu) or libsecret (other distros).\n' +
|
|
137
|
-
' Cannot access credentials on this platform without it.' +
|
|
138
|
-
(isWSL()
|
|
139
|
-
? '\n Detected WSL: there is usually no Secret Service here; ' +
|
|
140
|
-
'set the relevant MIXDOG_*/PROVIDER_API_KEY env var instead.'
|
|
141
|
-
: '')
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
return _keytarMod;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// keytar is Promise-based; bridge to sync via spawnSync of a child Node process.
|
|
148
|
-
// Avoids Atomics.wait on main thread (which hangs when SAB is not forwarded to worker).
|
|
149
|
-
function keytarSync(method, ...args) {
|
|
150
|
-
loadKeytar(); // throws if not installed — before spawning child
|
|
151
|
-
// Pass service/account/value via env to avoid shell injection entirely.
|
|
152
|
-
const env = {
|
|
153
|
-
...process.env,
|
|
154
|
-
_KEYTAR_METHOD: method,
|
|
155
|
-
_KEYTAR_ARGS: JSON.stringify(args),
|
|
156
|
-
};
|
|
157
|
-
const script = [
|
|
158
|
-
'const kt = require("keytar");',
|
|
159
|
-
'const method = process.env._KEYTAR_METHOD;',
|
|
160
|
-
'const args = JSON.parse(process.env._KEYTAR_ARGS);',
|
|
161
|
-
'kt[method](...args)',
|
|
162
|
-
' .then(v => { process.stdout.write(JSON.stringify({ ok: true, value: v })); })',
|
|
163
|
-
' .catch(e => { process.stdout.write(JSON.stringify({ ok: false, error: e.message })); });',
|
|
164
|
-
].join(' ');
|
|
165
|
-
const r = spawnSync(process.execPath, ['-e', script], {
|
|
166
|
-
env,
|
|
167
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
168
|
-
timeout: 5000,
|
|
169
|
-
encoding: 'utf8',
|
|
170
|
-
windowsHide: true,
|
|
171
|
-
});
|
|
172
|
-
if (r.error) throw new Error(`[keychain] keytar.${method} spawnSync failed: ${r.error.message}`);
|
|
173
|
-
if (r.status !== 0) {
|
|
174
|
-
const detail = (r.stderr || '').trim() || `exit ${r.status}`;
|
|
175
|
-
throw new Error(`[keychain] keytar.${method} child exited with error: ${detail}`);
|
|
176
|
-
}
|
|
177
|
-
let parsed;
|
|
178
|
-
try { parsed = JSON.parse(r.stdout); } catch {
|
|
179
|
-
throw new Error(`[keychain] keytar.${method} child returned unparseable output: ${String(r.stdout).slice(0, 200)}`);
|
|
180
|
-
}
|
|
181
|
-
if (!parsed.ok) throw new Error(`[keychain] keytar.${method} failed: ${parsed.error}`);
|
|
182
|
-
return parsed.value ?? null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function linuxGet(account) {
|
|
186
|
-
return keytarSync('getPassword', SERVICE, account);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function linuxSet(account, value) {
|
|
190
|
-
keytarSync('setPassword', SERVICE, account, value);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function linuxDelete(account) {
|
|
194
|
-
keytarSync('deletePassword', SERVICE, account);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ---------------------------------------------------------------------------
|
|
198
|
-
// win32 — DPAPI via PowerShell
|
|
199
|
-
// ---------------------------------------------------------------------------
|
|
200
|
-
|
|
201
|
-
// Add-Type loads System.Security on PS 5.1 hosts where the assembly is not
|
|
202
|
-
// auto-loaded — ProtectedData/DataProtectionScope resolve to TypeNotFound
|
|
203
|
-
// otherwise, and the host returns exit 0 with empty stdout, which then falls
|
|
204
|
-
// through win32Set's empty-output guard. PS 7+ already has the type loaded;
|
|
205
|
-
// SilentlyContinue covers the benign "already loaded" case on repeat calls.
|
|
206
|
-
const PS_PROTECT = [
|
|
207
|
-
'Add-Type -AssemblyName System.Security -ErrorAction SilentlyContinue;',
|
|
208
|
-
'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;',
|
|
209
|
-
'$value = $args[0];',
|
|
210
|
-
'$bytes = [System.Text.Encoding]::UTF8.GetBytes($value);',
|
|
211
|
-
'$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser;',
|
|
212
|
-
'$enc = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, $scope);',
|
|
213
|
-
'[Convert]::ToBase64String($enc)',
|
|
214
|
-
].join(' ');
|
|
215
|
-
|
|
216
|
-
const PS_UNPROTECT = [
|
|
217
|
-
'Add-Type -AssemblyName System.Security -ErrorAction SilentlyContinue;',
|
|
218
|
-
'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8;',
|
|
219
|
-
'$b64 = $args[0];',
|
|
220
|
-
'$enc = [Convert]::FromBase64String($b64);',
|
|
221
|
-
'$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser;',
|
|
222
|
-
'$dec = [System.Security.Cryptography.ProtectedData]::Unprotect($enc, $null, $scope);',
|
|
223
|
-
'[System.Text.Encoding]::UTF8.GetString($dec)',
|
|
224
|
-
].join(' ');
|
|
225
|
-
|
|
226
|
-
function win32Get(account) {
|
|
227
|
-
const file = dpApiFile(account);
|
|
228
|
-
if (!fs.existsSync(file)) return null;
|
|
229
|
-
const b64 = fs.readFileSync(file, 'utf8').trim();
|
|
230
|
-
if (!b64) throw new Error(`[keychain] DPAPI ciphertext file is empty: ${file}`);
|
|
231
|
-
const r = powershell(`& { ${PS_UNPROTECT} } '${b64}'`);
|
|
232
|
-
if (r.status !== 0) throw new Error(`[keychain] DPAPI decrypt failed (exit ${r.status}): ${r.stderr || r.stdout}`);
|
|
233
|
-
const out = (r.stdout || '').trim();
|
|
234
|
-
if (!out) throw new Error(`[keychain] DPAPI decrypt returned empty output (stderr: ${(r.stderr || '').trim()})`);
|
|
235
|
-
return out;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function win32Set(account, value) {
|
|
239
|
-
const dir = secretsDir();
|
|
240
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
241
|
-
// Pass value as a PS argument via -Command inline to avoid shell injection;
|
|
242
|
-
// value is embedded as a PS single-quoted string literal with ' escaped.
|
|
243
|
-
const escaped = value.replace(/'/g, "''");
|
|
244
|
-
const r = powershell(`& { ${PS_PROTECT} } '${escaped}'`);
|
|
245
|
-
if (r.status !== 0) throw new Error(`[keychain] DPAPI encrypt failed (exit ${r.status}): ${r.stderr || r.stdout}`);
|
|
246
|
-
const out = (r.stdout || '').trim();
|
|
247
|
-
if (!out) throw new Error(`[keychain] DPAPI encrypt returned empty output (stderr: ${(r.stderr || '').trim()})`);
|
|
248
|
-
fs.writeFileSync(dpApiFile(account), out, 'utf8');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function win32Delete(account) {
|
|
252
|
-
const file = dpApiFile(account);
|
|
253
|
-
if (!fs.existsSync(file)) throw new Error(`[keychain] secret not found: ${account}`);
|
|
254
|
-
fs.unlinkSync(file);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ---------------------------------------------------------------------------
|
|
258
|
-
// Public API
|
|
259
|
-
// ---------------------------------------------------------------------------
|
|
260
|
-
|
|
261
|
-
function getSecret(account) {
|
|
262
|
-
switch (platform()) {
|
|
263
|
-
case 'darwin': return darwinGet(account);
|
|
264
|
-
case 'linux': return linuxGet(account);
|
|
265
|
-
case 'win32': return win32Get(account);
|
|
266
|
-
default: throw new Error(`[keychain] unsupported platform: ${process.platform}`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function setSecret(account, value) {
|
|
271
|
-
switch (platform()) {
|
|
272
|
-
case 'darwin': return darwinSet(account, value);
|
|
273
|
-
case 'linux': return linuxSet(account, value);
|
|
274
|
-
case 'win32': return win32Set(account, value);
|
|
275
|
-
default: throw new Error(`[keychain] unsupported platform: ${process.platform}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function deleteSecret(account) {
|
|
280
|
-
switch (platform()) {
|
|
281
|
-
case 'darwin': return darwinDelete(account);
|
|
282
|
-
case 'linux': return linuxDelete(account);
|
|
283
|
-
case 'win32': return win32Delete(account);
|
|
284
|
-
default: throw new Error(`[keychain] unsupported platform: ${process.platform}`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
module.exports = { getSecret, setSecret, deleteSecret, SERVICE };
|