@empirical-gauzza/cli 1.0.34 → 1.0.35
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/lib/auth.js +24 -9
- package/lib/fsUtils.js +44 -2
- package/lib/installers/nativePlugin.js +2 -2
- package/package.json +1 -1
package/lib/auth.js
CHANGED
|
@@ -6,7 +6,7 @@ const os = require('os');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { spawnSync } = require('child_process');
|
|
8
8
|
const { log, warn, uiQuestion, uiDim, uiLink, ask } = require('./ui');
|
|
9
|
-
const { readJsonIfExists, writeFile, ensureDir, quotePowerShellArg, runResolvedCommand } = require('./fsUtils');
|
|
9
|
+
const { readJsonIfExists, writeFile, ensureDir, quotePowerShellArg, runResolvedCommand, runPowerShell } = require('./fsUtils');
|
|
10
10
|
const { httpGet, httpJson, httpForm } = require('./http');
|
|
11
11
|
const { getEnv, parseFlag, parseOption } = require('./env');
|
|
12
12
|
const {
|
|
@@ -81,17 +81,26 @@ async function oauthPreflight(baseMcpUrl) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/** Best-effort browser launch. Returns true when the launcher ran, false when it could not. */
|
|
84
85
|
function openBrowser(url) {
|
|
86
|
+
let result;
|
|
85
87
|
if (process.platform === 'win32') {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
// runPowerShell falls back from pwsh to powershell.exe; the old hardcoded `pwsh` silently
|
|
89
|
+
// did nothing on a stock Windows install (2026-09-23, real user report).
|
|
90
|
+
result = runPowerShell(`Start-Process ${quotePowerShellArg(url)}`, { stdio: 'ignore' });
|
|
88
91
|
} else if (process.platform === 'darwin') {
|
|
89
|
-
spawnSync('open', [url], { stdio: 'ignore' });
|
|
92
|
+
result = spawnSync('open', [url], { stdio: 'ignore' });
|
|
90
93
|
} else {
|
|
91
|
-
spawnSync('xdg-open', [url], { stdio: 'ignore' });
|
|
94
|
+
result = spawnSync('xdg-open', [url], { stdio: 'ignore' });
|
|
92
95
|
}
|
|
96
|
+
return Boolean(result) && !result.error && result.status === 0;
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
// How long the loopback listener waits for the browser to come back. Was 5 minutes; a user who
|
|
100
|
+
// has to find the printed link by hand, log in, and pick a workspace can take longer than that,
|
|
101
|
+
// and once the listener closes the browser lands on a dead 127.0.0.1 port with no explanation.
|
|
102
|
+
const OAUTH_CALLBACK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
103
|
+
|
|
95
104
|
async function runMcpOAuthFlow(baseUrl) {
|
|
96
105
|
return new Promise((resolve, reject) => {
|
|
97
106
|
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
@@ -103,9 +112,9 @@ async function runMcpOAuthFlow(baseUrl) {
|
|
|
103
112
|
if (server) server.close();
|
|
104
113
|
if (!resolved) {
|
|
105
114
|
resolved = true;
|
|
106
|
-
reject(new Error(
|
|
115
|
+
reject(new Error(`OAuth callback timeout (${OAUTH_CALLBACK_TIMEOUT_MS / 60000} minutes). The login link has expired; run the command again to get a fresh one.`));
|
|
107
116
|
}
|
|
108
|
-
},
|
|
117
|
+
}, OAUTH_CALLBACK_TIMEOUT_MS);
|
|
109
118
|
|
|
110
119
|
server = http.createServer((req, res) => {
|
|
111
120
|
const url = new URL(`http://localhost${req.url}`);
|
|
@@ -162,8 +171,14 @@ async function runMcpOAuthFlow(baseUrl) {
|
|
|
162
171
|
authUrl.searchParams.set('scope', 'memory:read memory:write memory:delete offline_access');
|
|
163
172
|
|
|
164
173
|
log(uiQuestion('Opening browser for Empirical MCP authentication...'));
|
|
165
|
-
openBrowser(authUrl.toString());
|
|
166
|
-
|
|
174
|
+
const opened = openBrowser(authUrl.toString());
|
|
175
|
+
if (opened) {
|
|
176
|
+
log(uiDim(`If browser doesn't open, visit: ${uiLink(authUrl.toString())}`));
|
|
177
|
+
} else {
|
|
178
|
+
warn('Could not open a browser automatically. Open this link yourself:');
|
|
179
|
+
log(uiLink(authUrl.toString()));
|
|
180
|
+
}
|
|
181
|
+
log(uiDim(`Waiting up to ${OAUTH_CALLBACK_TIMEOUT_MS / 60000} minutes for the browser to finish...`));
|
|
167
182
|
});
|
|
168
183
|
|
|
169
184
|
server.on('error', (error) => {
|
package/lib/fsUtils.js
CHANGED
|
@@ -4,11 +4,51 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
6
|
|
|
7
|
+
// 2026-09-23: every Windows shell-out used to hardcode `pwsh` (PowerShell 7). Windows ships only
|
|
8
|
+
// Windows PowerShell 5.1 (`powershell.exe`); PowerShell 7 is a separate install most people do
|
|
9
|
+
// not have. A real user on plain Windows hit `spawnSync pwsh ENOENT` on every plugin install AND
|
|
10
|
+
// the browser never opened for OAuth (openBrowser also went through pwsh), so they found the
|
|
11
|
+
// login link late, after the loopback listener had closed. Resolve once per process: prefer
|
|
12
|
+
// pwsh when present, fall back to powershell.exe, and surface a clear error if neither works.
|
|
13
|
+
let resolvedPowerShell;
|
|
14
|
+
function resolvePowerShell() {
|
|
15
|
+
if (resolvedPowerShell !== undefined) return resolvedPowerShell;
|
|
16
|
+
const override = String(process.env.EMPIRICAL_POWERSHELL || '').trim();
|
|
17
|
+
const candidates = override ? [override] : ['pwsh', 'powershell'];
|
|
18
|
+
for (const candidate of candidates) {
|
|
19
|
+
const probe = spawnSync(candidate, ['-NoProfile', '-Command', 'exit 0'], { stdio: 'ignore', timeout: 15000 });
|
|
20
|
+
if (!probe.error && probe.status === 0) {
|
|
21
|
+
resolvedPowerShell = candidate;
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
resolvedPowerShell = null;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Runs a PowerShell script on Windows through whichever PowerShell exists. Returns a spawnSync-
|
|
31
|
+
* shaped result; when no PowerShell can be found the result carries an `error` (never throws),
|
|
32
|
+
* the same shape spawnSync itself returns for ENOENT, so callers' existing error paths apply.
|
|
33
|
+
*/
|
|
34
|
+
function runPowerShell(script, options = {}) {
|
|
35
|
+
const shell = resolvePowerShell();
|
|
36
|
+
if (!shell) {
|
|
37
|
+
return {
|
|
38
|
+
status: null,
|
|
39
|
+
stdout: '',
|
|
40
|
+
stderr: '',
|
|
41
|
+
error: new Error('No PowerShell found (tried pwsh and powershell). Install PowerShell 7 or set EMPIRICAL_POWERSHELL to your PowerShell executable.')
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return spawnSync(shell, ['-NoProfile', '-Command', script], options);
|
|
45
|
+
}
|
|
46
|
+
|
|
7
47
|
function commandExists(cmd) {
|
|
8
48
|
if (process.platform === 'win32') {
|
|
9
49
|
const escaped = cmd.replace(/'/g, "''");
|
|
10
50
|
const script = `if (Get-Command '${escaped}' -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }`;
|
|
11
|
-
const result =
|
|
51
|
+
const result = runPowerShell(script, { stdio: 'ignore' });
|
|
12
52
|
return result.status === 0;
|
|
13
53
|
}
|
|
14
54
|
const result = spawnSync('which', [cmd], { stdio: 'ignore' });
|
|
@@ -57,7 +97,7 @@ function runResolvedCommand(command, args, options = {}) {
|
|
|
57
97
|
}
|
|
58
98
|
|
|
59
99
|
const script = `& ${quotePowerShellArg(command)} ${args.map(quotePowerShellArg).join(' ')}`;
|
|
60
|
-
return
|
|
100
|
+
return runPowerShell(script, {
|
|
61
101
|
encoding: 'utf8',
|
|
62
102
|
...(input !== undefined ? { input } : { stdio })
|
|
63
103
|
});
|
|
@@ -79,6 +119,8 @@ function summarizeProcessOutput(result) {
|
|
|
79
119
|
}
|
|
80
120
|
|
|
81
121
|
module.exports = {
|
|
122
|
+
resolvePowerShell,
|
|
123
|
+
runPowerShell,
|
|
82
124
|
commandExists,
|
|
83
125
|
ensureDir,
|
|
84
126
|
readUtf8IfExists,
|
|
@@ -77,9 +77,9 @@ function detectLockHolders(agentId) {
|
|
|
77
77
|
const names = LOCK_HOLDER_PROCESS_NAMES[agentId] || [];
|
|
78
78
|
if (!names.length) return [];
|
|
79
79
|
try {
|
|
80
|
-
const {
|
|
80
|
+
const { runPowerShell } = require('../fsUtils');
|
|
81
81
|
const script = `Get-Process -Name ${names.join(',')} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty ProcessName -Unique`;
|
|
82
|
-
const probe =
|
|
82
|
+
const probe = runPowerShell(script, { encoding: 'utf8', timeout: 5000 });
|
|
83
83
|
return String(probe.stdout || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
84
84
|
} catch (_error) {
|
|
85
85
|
return [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empirical-gauzza/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.35",
|
|
4
4
|
"description": "Persistent cross-tool memory for AI agents. Give Claude Code, Codex, Copilot, Cursor, and ChatGPT one shared memory: save project context once, recall it everywhere via CLI or MCP.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"memory",
|