@empirical-gauzza/cli 1.0.19 → 1.0.20

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.
@@ -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') {
@@ -1,93 +1,110 @@
1
- 'use strict';
2
-
3
- const { log, warn } = require('../ui');
4
- const { commandExists, runResolvedCommand, summarizeProcessOutput } = require('../fsUtils');
5
- const { readStoredOAuthClient } = require('../oauthClient');
6
- const {
7
- EMPIRICAL_MCP_NAME, EMPIRICAL_MCP_LEGACY_NAMES, EMPIRICAL_MCP_URL
8
- } = require('../constants');
9
- const { INSTALL_PROMPT_OPTIONS_DEFAULTS } = require('./promptBlocks');
10
- const { installClaudeHooks } = require('./hooks');
11
- const { installNativePlugin } = require('./nativePlugin');
12
-
13
- function runClaude(args, options = {}) {
14
- return runResolvedCommand('claude', args, options);
15
- }
16
-
17
- function isClaudeServerConfigured(name) {
18
- const result = runClaude(['mcp', 'get', name], { stdio: 'ignore' });
19
- return result.status === 0;
20
- }
21
-
22
- function installClaude(headlessToken, promptOptions = INSTALL_PROMPT_OPTIONS_DEFAULTS, installOptions = {}) {
23
- log(' - Claude global guidance file unchanged; native plugin guidance is authoritative.');
24
-
25
- // installNativePlugin throws if the plugin install/update genuinely fails (see
26
- // assertPluginInstallSucceeded in nativePlugin.js) - hooks are only installed AFTER this
27
- // succeeds, never before, so a failed/skipped plugin install never leaves an orphaned hook
28
- // pointing at a plugin that isn't actually present.
29
- installNativePlugin('claude', installOptions.pluginNames);
30
- log(` - Claude native plugins installed: ${(installOptions.pluginNames || ['empirical-memory']).join(', ')}.`);
31
-
32
- try {
33
- installClaudeHooks(installOptions.pluginNames);
34
- } catch (error) {
35
- warn(` - Could not install Claude hooks for deterministic memory lookups: ${error.message}`);
36
- }
37
-
38
- if (!commandExists('claude')) {
39
- warn(' - Claude CLI not found on PATH. Skipped Claude MCP registration.');
40
- return;
41
- }
42
-
43
- const storedClient = readStoredOAuthClient();
44
-
45
- // Keep the server definition deterministic across canonical and legacy names.
46
- for (const serverName of [EMPIRICAL_MCP_NAME, ...EMPIRICAL_MCP_LEGACY_NAMES]) {
47
- runClaude(['mcp', 'remove', serverName], { stdio: 'ignore' });
48
- runClaude(['mcp', 'remove', serverName, '-s', 'user'], { stdio: 'ignore' });
49
- runClaude(['mcp', 'remove', serverName, '-s', 'local'], { stdio: 'ignore' });
50
- runClaude(['mcp', 'remove', serverName, '-s', 'project'], { stdio: 'ignore' });
51
- }
52
-
53
- let lastFailureSummary = '';
54
- for (let attempt = 1; attempt <= 2; attempt += 1) {
55
- if (attempt > 1) {
56
- log(' - Retrying Claude MCP registration...');
57
- }
58
-
59
- const addArgs = [
60
- 'mcp',
61
- 'add',
62
- '--scope',
63
- 'user',
64
- '--transport',
65
- 'http',
66
- EMPIRICAL_MCP_NAME,
67
- EMPIRICAL_MCP_URL
68
- ];
69
- if (storedClient?.clientId) {
70
- addArgs.push('--client-id', storedClient.clientId);
71
- }
72
-
73
- const addResult = runClaude(addArgs, { stdio: attempt === 1 ? 'inherit' : 'pipe' });
74
- if (addResult.status === 0) {
75
- return;
76
- }
77
- lastFailureSummary = summarizeProcessOutput(addResult);
78
-
79
- if (isClaudeServerConfigured(EMPIRICAL_MCP_NAME)) {
80
- warn(' - Claude returned a non-zero status, but MCP server is present. Continuing.');
81
- return;
82
- }
83
- }
84
-
85
- const detail = lastFailureSummary ? ` Details: ${lastFailureSummary}` : '';
86
- throw new Error(`claude mcp add failed after retry.${detail}`);
87
- }
88
-
89
- module.exports = {
90
- runClaude,
91
- isClaudeServerConfigured,
92
- installClaude
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
+ };
@@ -1,88 +1,101 @@
1
- 'use strict';
2
-
3
- const { log, warn } = require('../ui');
4
- const { commandExists, runResolvedCommand, summarizeProcessOutput } = require('../fsUtils');
5
- const { persistAccessToken } = require('../oauthClient');
6
- const {
7
- EMPIRICAL_MCP_NAME, EMPIRICAL_MCP_LEGACY_NAMES, EMPIRICAL_MCP_URL
8
- } = require('../constants');
9
- const { INSTALL_PROMPT_OPTIONS_DEFAULTS } = require('./promptBlocks');
10
- const { installCodexHooks } = require('./hooks');
11
- const { installNativePlugin } = require('./nativePlugin');
12
-
13
- function runCodex(args, options = {}) {
14
- return runResolvedCommand('codex', args, options);
15
- }
16
-
17
- function isCodexServerConfigured(name) {
18
- const result = runCodex(['mcp', 'get', name], { stdio: 'ignore' });
19
- return result.status === 0;
20
- }
21
-
22
- function installCodex(headlessToken, promptOptions = INSTALL_PROMPT_OPTIONS_DEFAULTS, installOptions = {}) {
23
- log(' - Codex global AGENTS instructions unchanged; native plugin guidance is authoritative.');
24
-
25
- // installNativePlugin throws on genuine failure (see assertPluginInstallSucceeded in
26
- // nativePlugin.js) - hooks install only AFTER this succeeds, never before, so a failed/skipped
27
- // plugin install never leaves an orphaned hook pointing at a plugin that isn't present.
28
- installNativePlugin('codex', installOptions.pluginNames);
29
- log(` - Codex native plugins installed: ${(installOptions.pluginNames || ['empirical-memory']).join(', ')}.`);
30
-
31
- try {
32
- installCodexHooks(installOptions.pluginNames);
33
- } catch (error) {
34
- warn(` - Could not install Codex hooks for deterministic memory lookups: ${error.message}`);
35
- }
36
-
37
- if (!commandExists('codex')) {
38
- warn(' - Codex CLI not found on PATH. Skipped Codex MCP registration.');
39
- return;
40
- }
41
-
42
- for (const serverName of [EMPIRICAL_MCP_NAME, ...EMPIRICAL_MCP_LEGACY_NAMES]) {
43
- if (isCodexServerConfigured(serverName)) {
44
- runCodex(['mcp', 'remove', serverName], { stdio: 'ignore' });
45
- }
46
- }
47
-
48
- const addArgs = ['mcp', 'add', EMPIRICAL_MCP_NAME, '--url', EMPIRICAL_MCP_URL];
49
- const permanentToken = headlessToken?.apiKey || '';
50
- if (permanentToken) {
51
- persistAccessToken(permanentToken);
52
- addArgs.push('--bearer-token-env-var', 'EMPIRICAL_ACCESS_TOKEN');
53
- log(' - Registering Codex MCP server with permanent environment-backed authentication...');
54
- } else {
55
- log(' - Registering Codex MCP server (OAuth flow may open in browser)...');
56
- }
57
-
58
- let lastFailureSummary = '';
59
- for (let attempt = 1; attempt <= 2; attempt += 1) {
60
- if (attempt > 1) {
61
- log(' - Retrying Codex MCP registration...');
62
- }
63
-
64
- const addResult = runCodex(
65
- addArgs,
66
- { stdio: attempt === 1 ? 'inherit' : 'pipe' }
67
- );
68
- if (addResult.status === 0) {
69
- return { needsPostLogin: false };
70
- }
71
- lastFailureSummary = summarizeProcessOutput(addResult);
72
-
73
- // Codex OAuth flow can return non-zero even when the server is persisted.
74
- if (isCodexServerConfigured(EMPIRICAL_MCP_NAME)) {
75
- warn(' - Codex returned a non-zero status, but MCP server is present. Continuing.');
76
- return { needsPostLogin: true };
77
- }
78
- }
79
-
80
- const detail = lastFailureSummary ? ` Details: ${lastFailureSummary}` : '';
81
- throw new Error(`codex mcp add failed after retry.${detail}`);
82
- }
83
-
84
- module.exports = {
85
- runCodex,
86
- isCodexServerConfigured,
87
- installCodex
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
- function installCopilot(headlessToken, promptOptions = INSTALL_PROMPT_OPTIONS_DEFAULTS, installOptions = {}) {
17
- const home = os.homedir();
18
- const copilotDir = path.join(home, '.copilot');
19
- const mcpConfigPath = path.join(copilotDir, 'mcp-config.json');
20
- const instructionsPath = path.join(copilotDir, 'copilot-instructions.md');
21
-
22
- ensureDir(copilotDir);
23
-
24
- const mcpConfig = readJsonIfExists(mcpConfigPath) || {};
25
- if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== 'object') {
26
- mcpConfig.mcpServers = {};
27
- }
28
-
29
- for (const legacyName of EMPIRICAL_MCP_LEGACY_NAMES) {
30
- if (legacyName && legacyName in mcpConfig.mcpServers) {
31
- delete mcpConfig.mcpServers[legacyName];
32
- }
33
- }
34
-
35
- const storedClient = readStoredOAuthClient();
36
- const oauthClientId = getEnv(
37
- 'EMPIRICAL_COPILOT_OAUTH_CLIENT_ID',
38
- storedClient?.clientId || COPILOT_OAUTH_CLIENT_ID
39
- );
40
- const oauthClientSecret = getEnv(
41
- 'EMPIRICAL_COPILOT_OAUTH_CLIENT_SECRET',
42
- storedClient?.clientSecret || ''
43
- );
44
-
45
- mcpConfig.mcpServers[EMPIRICAL_MCP_NAME] = {
46
- oauthPublicClient: !oauthClientSecret,
47
- type: 'http',
48
- tools: ['*'],
49
- url: EMPIRICAL_MCP_URL,
50
- oauthClientId
51
- };
52
- if (oauthClientSecret) {
53
- mcpConfig.mcpServers[EMPIRICAL_MCP_NAME].oauthClientSecret = oauthClientSecret;
54
- } else if ('oauthClientSecret' in mcpConfig.mcpServers[EMPIRICAL_MCP_NAME]) {
55
- delete mcpConfig.mcpServers[EMPIRICAL_MCP_NAME].oauthClientSecret;
56
- }
57
-
58
- if ('httpHeaders' in mcpConfig.mcpServers[EMPIRICAL_MCP_NAME]) {
59
- delete mcpConfig.mcpServers[EMPIRICAL_MCP_NAME].httpHeaders;
60
- }
61
-
62
- writeFile(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}\n`);
63
-
64
- log(' - Copilot global instructions unchanged; native plugin guidance is authoritative.');
65
-
66
- // installNativePlugin throws on genuine failure (see assertPluginInstallSucceeded in
67
- // nativePlugin.js) - hooks install only AFTER this succeeds, never before, so a failed/skipped
68
- // plugin install never leaves an orphaned hook pointing at a plugin that isn't present.
69
- installNativePlugin('copilot', installOptions.pluginNames);
70
- log(` - Copilot native plugins installed: ${(installOptions.pluginNames || ['empirical-memory']).join(', ')}.`);
71
-
72
- try {
73
- installCopilotHooks(installOptions.pluginNames);
74
- } catch (error) {
75
- warn(` - Could not install Copilot hooks for deterministic memory lookups: ${error.message}`);
76
- }
77
-
78
- log(' - Copilot MCP config updated.');
79
- if (storedClient?.clientId) {
80
- log(` - Copilot OAuth client loaded from ${getLocalOAuthClientPath()}.`);
81
- }
82
- log(' - Copilot OAuth is typically completed on first MCP use (no standalone `copilot mcp login` command).');
83
- }
84
-
85
- module.exports = {
86
- installCopilot
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
+ };