@empirical-gauzza/cli 1.0.19 → 1.0.21
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/bin/empirical.js +12 -4
- package/lib/auth.js +126 -43
- package/lib/constants.js +72 -8
- package/lib/deviceConfig.js +37 -18
- package/lib/doctor.js +484 -116
- package/lib/help.js +8 -2
- package/lib/installWizard.js +8 -2
- package/lib/installers/claude.js +110 -93
- package/lib/installers/codex.js +101 -88
- package/lib/installers/copilot.js +97 -87
- package/lib/installers/coreMemoryBlock.js +91 -0
- package/lib/installers/nativePlugin.js +147 -87
- package/lib/installers/skill-variants/SKILL.both.md +154 -0
- package/lib/installers/skill-variants/SKILL.cli.md +150 -0
- package/lib/installers/skill-variants/SKILL.mcp.md +134 -0
- package/lib/installers/skillVariants.js +59 -0
- package/lib/kitCommands.js +219 -122
- package/lib/memoryCommands.js +362 -16
- package/lib/pluginCommands.js +84 -81
- package/lib/pluginUpdateCheck.js +48 -14
- package/lib/skillPaths.js +25 -0
- package/lib/skillSync.js +48 -56
- package/package.json +15 -2
package/lib/installWizard.js
CHANGED
|
@@ -24,6 +24,7 @@ const { installClaudeHooks, installCodexHooks, installCopilotHooks } = require('
|
|
|
24
24
|
const { runCodex } = require('./installers/codex');
|
|
25
25
|
const { installNativePlugin, OPTIONAL_PLUGIN_NAMES } = require('./installers/nativePlugin');
|
|
26
26
|
const { policyFromInstallerOptions, savePolicy: persistPolicy } = require('./policyCommands');
|
|
27
|
+
const { writeIntegrationMode } = require('./deviceConfig');
|
|
27
28
|
|
|
28
29
|
function pluginNamesForInstallerOptions(options = {}) {
|
|
29
30
|
const names = ['empirical-memory'];
|
|
@@ -74,6 +75,10 @@ async function runInstallFlow() {
|
|
|
74
75
|
log('');
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
// C-001: persist the chosen mode so later, unattended Skill refreshes (a plugin auto-update or
|
|
79
|
+
// a `kit sync`/`kit use` run) still pick the mode the user actually chose here, not a default.
|
|
80
|
+
writeIntegrationMode(integrationMode);
|
|
81
|
+
|
|
77
82
|
let promptOptions;
|
|
78
83
|
let savePolicyEnabled;
|
|
79
84
|
|
|
@@ -231,7 +236,7 @@ async function runInstallFlow() {
|
|
|
231
236
|
if (integrationMode === 2) {
|
|
232
237
|
// CLI-direct: skip MCP registration; the native plugin owns guidance.
|
|
233
238
|
const pluginNames = pluginNamesForInstallerOptions(promptOptions);
|
|
234
|
-
installNativePlugin(target.id, pluginNames);
|
|
239
|
+
installNativePlugin(target.id, pluginNames, integrationMode);
|
|
235
240
|
log(` - ${target.label} native plugins installed: ${pluginNames.join(', ')}.`);
|
|
236
241
|
if (target.id === 'claude') {
|
|
237
242
|
try {
|
|
@@ -260,7 +265,8 @@ async function runInstallFlow() {
|
|
|
260
265
|
const pluginNames = pluginNamesForInstallerOptions(promptOptions);
|
|
261
266
|
const installResult = target.install(headlessToken, promptOptions, {
|
|
262
267
|
writeGlobalInstructions: false,
|
|
263
|
-
pluginNames
|
|
268
|
+
pluginNames,
|
|
269
|
+
integrationMode
|
|
264
270
|
});
|
|
265
271
|
log(uiSuccess(`Done: ${target.label}`));
|
|
266
272
|
if (target.id === 'codex') {
|
package/lib/installers/claude.js
CHANGED
|
@@ -1,93 +1,110 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
warn(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { log, warn } = require('../ui');
|
|
6
|
+
const { commandExists, runResolvedCommand, summarizeProcessOutput } = require('../fsUtils');
|
|
7
|
+
const { readStoredOAuthClient } = require('../oauthClient');
|
|
8
|
+
const {
|
|
9
|
+
EMPIRICAL_MCP_NAME, EMPIRICAL_MCP_LEGACY_NAMES, EMPIRICAL_MCP_URL
|
|
10
|
+
} = require('../constants');
|
|
11
|
+
const { INSTALL_PROMPT_OPTIONS_DEFAULTS } = require('./promptBlocks');
|
|
12
|
+
const { installClaudeHooks } = require('./hooks');
|
|
13
|
+
const { installNativePlugin, CORE_PLUGIN_NAME } = require('./nativePlugin');
|
|
14
|
+
const { buildCoreMemoryBlock, upsertCoreMemoryBlock } = require('./coreMemoryBlock');
|
|
15
|
+
|
|
16
|
+
function runClaude(args, options = {}) {
|
|
17
|
+
return runResolvedCommand('claude', args, options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isClaudeServerConfigured(name) {
|
|
21
|
+
const result = runClaude(['mcp', 'get', name], { stdio: 'ignore' });
|
|
22
|
+
return result.status === 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function installClaude(headlessToken, promptOptions = INSTALL_PROMPT_OPTIONS_DEFAULTS, installOptions = {}) {
|
|
26
|
+
// 2026-08-24: previously left CLAUDE.md untouched, trusting hooks + the installable skill
|
|
27
|
+
// alone. That trust proved false in a real session (see project memory "agent behavior" scar
|
|
28
|
+
// tissue) -- an agent's own built-in memory feature, framed in first-person identity language
|
|
29
|
+
// in its system prompt, out-competed the skill despite reminder hooks firing every turn. This
|
|
30
|
+
// writes the same kind of identity-framed statement into the one file Claude reads every
|
|
31
|
+
// session unconditionally, inside a clearly delimited block so any of the user's own content
|
|
32
|
+
// in CLAUDE.md is left untouched.
|
|
33
|
+
try {
|
|
34
|
+
const claudeMdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
|
35
|
+
const block = buildCoreMemoryBlock({ pluginNames: installOptions.pluginNames || [CORE_PLUGIN_NAME] });
|
|
36
|
+
upsertCoreMemoryBlock(claudeMdPath, block);
|
|
37
|
+
log(` - Wrote Empirical core memory guidance to ${claudeMdPath}.`);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
warn(` - Could not write Empirical core memory guidance to CLAUDE.md: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// installNativePlugin throws if the plugin install/update genuinely fails (see
|
|
43
|
+
// assertPluginInstallSucceeded in nativePlugin.js) - hooks are only installed AFTER this
|
|
44
|
+
// succeeds, never before, so a failed/skipped plugin install never leaves an orphaned hook
|
|
45
|
+
// pointing at a plugin that isn't actually present.
|
|
46
|
+
installNativePlugin('claude', installOptions.pluginNames, installOptions.integrationMode);
|
|
47
|
+
log(` - Claude native plugins installed: ${(installOptions.pluginNames || ['empirical-memory']).join(', ')}.`);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
installClaudeHooks(installOptions.pluginNames);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
warn(` - Could not install Claude hooks for deterministic memory lookups: ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!commandExists('claude')) {
|
|
56
|
+
warn(' - Claude CLI not found on PATH. Skipped Claude MCP registration.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const storedClient = readStoredOAuthClient();
|
|
61
|
+
|
|
62
|
+
// Keep the server definition deterministic across canonical and legacy names.
|
|
63
|
+
for (const serverName of [EMPIRICAL_MCP_NAME, ...EMPIRICAL_MCP_LEGACY_NAMES]) {
|
|
64
|
+
runClaude(['mcp', 'remove', serverName], { stdio: 'ignore' });
|
|
65
|
+
runClaude(['mcp', 'remove', serverName, '-s', 'user'], { stdio: 'ignore' });
|
|
66
|
+
runClaude(['mcp', 'remove', serverName, '-s', 'local'], { stdio: 'ignore' });
|
|
67
|
+
runClaude(['mcp', 'remove', serverName, '-s', 'project'], { stdio: 'ignore' });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let lastFailureSummary = '';
|
|
71
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
72
|
+
if (attempt > 1) {
|
|
73
|
+
log(' - Retrying Claude MCP registration...');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const addArgs = [
|
|
77
|
+
'mcp',
|
|
78
|
+
'add',
|
|
79
|
+
'--scope',
|
|
80
|
+
'user',
|
|
81
|
+
'--transport',
|
|
82
|
+
'http',
|
|
83
|
+
EMPIRICAL_MCP_NAME,
|
|
84
|
+
EMPIRICAL_MCP_URL
|
|
85
|
+
];
|
|
86
|
+
if (storedClient?.clientId) {
|
|
87
|
+
addArgs.push('--client-id', storedClient.clientId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const addResult = runClaude(addArgs, { stdio: attempt === 1 ? 'inherit' : 'pipe' });
|
|
91
|
+
if (addResult.status === 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
lastFailureSummary = summarizeProcessOutput(addResult);
|
|
95
|
+
|
|
96
|
+
if (isClaudeServerConfigured(EMPIRICAL_MCP_NAME)) {
|
|
97
|
+
warn(' - Claude returned a non-zero status, but MCP server is present. Continuing.');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const detail = lastFailureSummary ? ` Details: ${lastFailureSummary}` : '';
|
|
103
|
+
throw new Error(`claude mcp add failed after retry.${detail}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
runClaude,
|
|
108
|
+
isClaudeServerConfigured,
|
|
109
|
+
installClaude
|
|
110
|
+
};
|
package/lib/installers/codex.js
CHANGED
|
@@ -1,88 +1,101 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { log, warn } = require('../ui');
|
|
6
|
+
const { commandExists, runResolvedCommand, summarizeProcessOutput } = require('../fsUtils');
|
|
7
|
+
const { persistAccessToken } = require('../oauthClient');
|
|
8
|
+
const {
|
|
9
|
+
EMPIRICAL_MCP_NAME, EMPIRICAL_MCP_LEGACY_NAMES, EMPIRICAL_MCP_URL
|
|
10
|
+
} = require('../constants');
|
|
11
|
+
const { INSTALL_PROMPT_OPTIONS_DEFAULTS } = require('./promptBlocks');
|
|
12
|
+
const { installCodexHooks } = require('./hooks');
|
|
13
|
+
const { installNativePlugin, CORE_PLUGIN_NAME } = require('./nativePlugin');
|
|
14
|
+
const { buildCoreMemoryBlock, upsertCoreMemoryBlock } = require('./coreMemoryBlock');
|
|
15
|
+
|
|
16
|
+
function runCodex(args, options = {}) {
|
|
17
|
+
return runResolvedCommand('codex', args, options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isCodexServerConfigured(name) {
|
|
21
|
+
const result = runCodex(['mcp', 'get', name], { stdio: 'ignore' });
|
|
22
|
+
return result.status === 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function installCodex(headlessToken, promptOptions = INSTALL_PROMPT_OPTIONS_DEFAULTS, installOptions = {}) {
|
|
26
|
+
// 2026-08-24: see claude.js's identical block for why this now writes AGENTS.md instead of
|
|
27
|
+
// leaving it untouched -- hooks + an installable skill alone were not enough against a
|
|
28
|
+
// competing identity-framed built-in memory feature in a real session.
|
|
29
|
+
try {
|
|
30
|
+
const agentsMdPath = path.join(os.homedir(), '.codex', 'AGENTS.md');
|
|
31
|
+
const block = buildCoreMemoryBlock({ pluginNames: installOptions.pluginNames || [CORE_PLUGIN_NAME] });
|
|
32
|
+
upsertCoreMemoryBlock(agentsMdPath, block);
|
|
33
|
+
log(` - Wrote Empirical core memory guidance to ${agentsMdPath}.`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
warn(` - Could not write Empirical core memory guidance to AGENTS.md: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// installNativePlugin throws on genuine failure (see assertPluginInstallSucceeded in
|
|
39
|
+
// nativePlugin.js) - hooks install only AFTER this succeeds, never before, so a failed/skipped
|
|
40
|
+
// plugin install never leaves an orphaned hook pointing at a plugin that isn't present.
|
|
41
|
+
installNativePlugin('codex', installOptions.pluginNames, installOptions.integrationMode);
|
|
42
|
+
log(` - Codex native plugins installed: ${(installOptions.pluginNames || ['empirical-memory']).join(', ')}.`);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
installCodexHooks(installOptions.pluginNames);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
warn(` - Could not install Codex hooks for deterministic memory lookups: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!commandExists('codex')) {
|
|
51
|
+
warn(' - Codex CLI not found on PATH. Skipped Codex MCP registration.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const serverName of [EMPIRICAL_MCP_NAME, ...EMPIRICAL_MCP_LEGACY_NAMES]) {
|
|
56
|
+
if (isCodexServerConfigured(serverName)) {
|
|
57
|
+
runCodex(['mcp', 'remove', serverName], { stdio: 'ignore' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const addArgs = ['mcp', 'add', EMPIRICAL_MCP_NAME, '--url', EMPIRICAL_MCP_URL];
|
|
62
|
+
const permanentToken = headlessToken?.apiKey || '';
|
|
63
|
+
if (permanentToken) {
|
|
64
|
+
persistAccessToken(permanentToken);
|
|
65
|
+
addArgs.push('--bearer-token-env-var', 'EMPIRICAL_ACCESS_TOKEN');
|
|
66
|
+
log(' - Registering Codex MCP server with permanent environment-backed authentication...');
|
|
67
|
+
} else {
|
|
68
|
+
log(' - Registering Codex MCP server (OAuth flow may open in browser)...');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let lastFailureSummary = '';
|
|
72
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
73
|
+
if (attempt > 1) {
|
|
74
|
+
log(' - Retrying Codex MCP registration...');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const addResult = runCodex(
|
|
78
|
+
addArgs,
|
|
79
|
+
{ stdio: attempt === 1 ? 'inherit' : 'pipe' }
|
|
80
|
+
);
|
|
81
|
+
if (addResult.status === 0) {
|
|
82
|
+
return { needsPostLogin: false };
|
|
83
|
+
}
|
|
84
|
+
lastFailureSummary = summarizeProcessOutput(addResult);
|
|
85
|
+
|
|
86
|
+
// Codex OAuth flow can return non-zero even when the server is persisted.
|
|
87
|
+
if (isCodexServerConfigured(EMPIRICAL_MCP_NAME)) {
|
|
88
|
+
warn(' - Codex returned a non-zero status, but MCP server is present. Continuing.');
|
|
89
|
+
return { needsPostLogin: true };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const detail = lastFailureSummary ? ` Details: ${lastFailureSummary}` : '';
|
|
94
|
+
throw new Error(`codex mcp add failed after retry.${detail}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
runCodex,
|
|
99
|
+
isCodexServerConfigured,
|
|
100
|
+
installCodex
|
|
101
|
+
};
|
|
@@ -1,87 +1,97 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { log, warn } = require('../ui');
|
|
6
|
-
const { ensureDir, readJsonIfExists, readUtf8IfExists, writeFile } = require('../fsUtils');
|
|
7
|
-
const { getEnv } = require('../env');
|
|
8
|
-
const { readStoredOAuthClient, getLocalOAuthClientPath } = require('../oauthClient');
|
|
9
|
-
const {
|
|
10
|
-
EMPIRICAL_MCP_NAME, EMPIRICAL_MCP_LEGACY_NAMES, EMPIRICAL_MCP_URL, COPILOT_OAUTH_CLIENT_ID
|
|
11
|
-
} = require('../constants');
|
|
12
|
-
const { INSTALL_PROMPT_OPTIONS_DEFAULTS } = require('./promptBlocks');
|
|
13
|
-
const { installCopilotHooks } = require('./hooks');
|
|
14
|
-
const { installNativePlugin } = require('./nativePlugin');
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { log, warn } = require('../ui');
|
|
6
|
+
const { ensureDir, readJsonIfExists, readUtf8IfExists, writeFile } = require('../fsUtils');
|
|
7
|
+
const { getEnv } = require('../env');
|
|
8
|
+
const { readStoredOAuthClient, getLocalOAuthClientPath } = require('../oauthClient');
|
|
9
|
+
const {
|
|
10
|
+
EMPIRICAL_MCP_NAME, EMPIRICAL_MCP_LEGACY_NAMES, EMPIRICAL_MCP_URL, COPILOT_OAUTH_CLIENT_ID
|
|
11
|
+
} = require('../constants');
|
|
12
|
+
const { INSTALL_PROMPT_OPTIONS_DEFAULTS } = require('./promptBlocks');
|
|
13
|
+
const { installCopilotHooks } = require('./hooks');
|
|
14
|
+
const { installNativePlugin, CORE_PLUGIN_NAME } = require('./nativePlugin');
|
|
15
|
+
const { buildCoreMemoryBlock, upsertCoreMemoryBlock } = require('./coreMemoryBlock');
|
|
16
|
+
|
|
17
|
+
function installCopilot(headlessToken, promptOptions = INSTALL_PROMPT_OPTIONS_DEFAULTS, installOptions = {}) {
|
|
18
|
+
const home = os.homedir();
|
|
19
|
+
const copilotDir = path.join(home, '.copilot');
|
|
20
|
+
const mcpConfigPath = path.join(copilotDir, 'mcp-config.json');
|
|
21
|
+
const instructionsPath = path.join(copilotDir, 'copilot-instructions.md');
|
|
22
|
+
|
|
23
|
+
ensureDir(copilotDir);
|
|
24
|
+
|
|
25
|
+
const mcpConfig = readJsonIfExists(mcpConfigPath) || {};
|
|
26
|
+
if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== 'object') {
|
|
27
|
+
mcpConfig.mcpServers = {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const legacyName of EMPIRICAL_MCP_LEGACY_NAMES) {
|
|
31
|
+
if (legacyName && legacyName in mcpConfig.mcpServers) {
|
|
32
|
+
delete mcpConfig.mcpServers[legacyName];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const storedClient = readStoredOAuthClient();
|
|
37
|
+
const oauthClientId = getEnv(
|
|
38
|
+
'EMPIRICAL_COPILOT_OAUTH_CLIENT_ID',
|
|
39
|
+
storedClient?.clientId || COPILOT_OAUTH_CLIENT_ID
|
|
40
|
+
);
|
|
41
|
+
const oauthClientSecret = getEnv(
|
|
42
|
+
'EMPIRICAL_COPILOT_OAUTH_CLIENT_SECRET',
|
|
43
|
+
storedClient?.clientSecret || ''
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
mcpConfig.mcpServers[EMPIRICAL_MCP_NAME] = {
|
|
47
|
+
oauthPublicClient: !oauthClientSecret,
|
|
48
|
+
type: 'http',
|
|
49
|
+
tools: ['*'],
|
|
50
|
+
url: EMPIRICAL_MCP_URL,
|
|
51
|
+
oauthClientId
|
|
52
|
+
};
|
|
53
|
+
if (oauthClientSecret) {
|
|
54
|
+
mcpConfig.mcpServers[EMPIRICAL_MCP_NAME].oauthClientSecret = oauthClientSecret;
|
|
55
|
+
} else if ('oauthClientSecret' in mcpConfig.mcpServers[EMPIRICAL_MCP_NAME]) {
|
|
56
|
+
delete mcpConfig.mcpServers[EMPIRICAL_MCP_NAME].oauthClientSecret;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if ('httpHeaders' in mcpConfig.mcpServers[EMPIRICAL_MCP_NAME]) {
|
|
60
|
+
delete mcpConfig.mcpServers[EMPIRICAL_MCP_NAME].httpHeaders;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
writeFile(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}\n`);
|
|
64
|
+
|
|
65
|
+
// 2026-08-24: see claude.js's identical block for why this now writes copilot-instructions.md
|
|
66
|
+
// instead of leaving it untouched -- hooks + an installable skill alone were not enough
|
|
67
|
+
// against a competing identity-framed built-in memory feature in a real session.
|
|
68
|
+
try {
|
|
69
|
+
const block = buildCoreMemoryBlock({ pluginNames: installOptions.pluginNames || [CORE_PLUGIN_NAME] });
|
|
70
|
+
upsertCoreMemoryBlock(instructionsPath, block);
|
|
71
|
+
log(` - Wrote Empirical core memory guidance to ${instructionsPath}.`);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
warn(` - Could not write Empirical core memory guidance to copilot-instructions.md: ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// installNativePlugin throws on genuine failure (see assertPluginInstallSucceeded in
|
|
77
|
+
// nativePlugin.js) - hooks install only AFTER this succeeds, never before, so a failed/skipped
|
|
78
|
+
// plugin install never leaves an orphaned hook pointing at a plugin that isn't present.
|
|
79
|
+
installNativePlugin('copilot', installOptions.pluginNames, installOptions.integrationMode);
|
|
80
|
+
log(` - Copilot native plugins installed: ${(installOptions.pluginNames || ['empirical-memory']).join(', ')}.`);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
installCopilotHooks(installOptions.pluginNames);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
warn(` - Could not install Copilot hooks for deterministic memory lookups: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
log(' - Copilot MCP config updated.');
|
|
89
|
+
if (storedClient?.clientId) {
|
|
90
|
+
log(` - Copilot OAuth client loaded from ${getLocalOAuthClientPath()}.`);
|
|
91
|
+
}
|
|
92
|
+
log(' - Copilot OAuth is typically completed on first MCP use (no standalone `copilot mcp login` command).');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
installCopilot
|
|
97
|
+
};
|