@ghl-ai/aw 0.1.50 → 0.1.52
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/c4/templates/scripts/aw-c4-bootstrap.sh +5 -4
- package/cli.mjs +22 -9
- package/commands/c4.mjs +17 -9
- package/commands/doctor.mjs +2 -2
- package/commands/init.mjs +23 -5
- package/commands/integrations.mjs +254 -0
- package/commands/mcp.mjs +90 -0
- package/commands/nuke.mjs +1 -1
- package/commands/pull.mjs +3 -2
- package/commands/push.mjs +715 -29
- package/commands/startup.mjs +22 -3
- package/constants.mjs +23 -0
- package/ecc.mjs +1 -1
- package/git.mjs +6 -4
- package/integrate.mjs +94 -21
- package/integrations/context-mode.mjs +237 -57
- package/integrations.mjs +971 -0
- package/mcp.mjs +132 -16
- package/package.json +4 -3
- package/render-rules.mjs +25 -1
- package/startup.mjs +52 -8
package/mcp.mjs
CHANGED
|
@@ -1,16 +1,72 @@
|
|
|
1
1
|
// mcp.mjs — MCP config generation for Claude Code, Cursor, and Codex (global ~/ configs)
|
|
2
2
|
// Uses native Streamable HTTP — no bridge process needed.
|
|
3
3
|
|
|
4
|
-
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { existsSync, writeFileSync, readFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
6
|
import { createInterface } from 'node:readline';
|
|
7
|
-
import { join } from 'node:path';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
9
10
|
import * as p from '@clack/prompts';
|
|
10
11
|
import * as fmt from './fmt.mjs';
|
|
11
12
|
|
|
12
13
|
const HOME = homedir();
|
|
13
14
|
const DEFAULT_MCP_URL = 'https://services.leadconnectorhq.com/agentic-workspace/mcp';
|
|
15
|
+
const MCP_PREFS_FILENAME = 'mcp-preferences.json';
|
|
16
|
+
const ENABLED_MODE = 'enabled';
|
|
17
|
+
const DISABLED_MODE = 'disabled';
|
|
18
|
+
const DISABLE_MCP_ENV = 'AW_DISABLE_MCP';
|
|
19
|
+
const MCP_SERVER_NAME = 'ghl-ai';
|
|
20
|
+
|
|
21
|
+
function mcpPrefsPath(homeDir = HOME) {
|
|
22
|
+
return join(homeDir, '.aw', MCP_PREFS_FILENAME);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readJson(filePath, fallback = {}) {
|
|
26
|
+
if (!existsSync(filePath)) return fallback;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
29
|
+
} catch {
|
|
30
|
+
// Malformed or unreadable JSON should behave like an absent optional config.
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeJson(filePath, value) {
|
|
36
|
+
const dir = dirname(filePath);
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
const tmpPath = join(dir, `.${randomBytes(8).toString('hex')}.tmp`);
|
|
39
|
+
writeFileSync(tmpPath, `${JSON.stringify(value, null, 2)}\n`);
|
|
40
|
+
renameSync(tmpPath, filePath);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function envDisablesMcp(env = process.env) {
|
|
44
|
+
const value = String(env[DISABLE_MCP_ENV] || '').trim().toLowerCase();
|
|
45
|
+
return ['1', 'true', 'yes', 'on'].includes(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function loadMcpPreferences(homeDir = HOME) {
|
|
49
|
+
const prefs = readJson(mcpPrefsPath(homeDir), {});
|
|
50
|
+
return {
|
|
51
|
+
mode: prefs.mode === DISABLED_MODE ? DISABLED_MODE : ENABLED_MODE,
|
|
52
|
+
updatedAt: typeof prefs.updatedAt === 'string' ? prefs.updatedAt : null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function saveMcpPreferences(mode, homeDir = HOME) {
|
|
57
|
+
const normalizedMode = mode === DISABLED_MODE ? DISABLED_MODE : ENABLED_MODE;
|
|
58
|
+
const next = {
|
|
59
|
+
mode: normalizedMode,
|
|
60
|
+
updatedAt: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
writeJson(mcpPrefsPath(homeDir), next);
|
|
63
|
+
return next;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isMcpEnabled(homeDir = HOME, env = process.env) {
|
|
67
|
+
if (envDisablesMcp(env)) return false;
|
|
68
|
+
return loadMcpPreferences(homeDir).mode !== DISABLED_MODE;
|
|
69
|
+
}
|
|
14
70
|
|
|
15
71
|
/**
|
|
16
72
|
* Auto-detect MCP server paths.
|
|
@@ -250,11 +306,28 @@ async function resolveClickUpToken(silent = false, cwd = process.cwd()) {
|
|
|
250
306
|
}
|
|
251
307
|
|
|
252
308
|
/**
|
|
253
|
-
* Setup MCP configs globally for Claude Code and
|
|
309
|
+
* Setup MCP configs globally for Claude Code, Cursor, and Codex.
|
|
254
310
|
* Merges ghl-ai server into existing configs without overwriting other servers.
|
|
255
311
|
* Returns list of file paths that were created or updated.
|
|
256
312
|
*/
|
|
257
313
|
export async function setupMcp(cwd, namespace, { silent = false } = {}) {
|
|
314
|
+
const prefs = loadMcpPreferences(HOME);
|
|
315
|
+
if (prefs.mode === DISABLED_MODE) {
|
|
316
|
+
const removed = removeMcpConfig();
|
|
317
|
+
if (!silent) {
|
|
318
|
+
const reason = `${fmt.chalk.dim(mcpPrefsPath(HOME).replace(HOME, '~'))} is disabled`;
|
|
319
|
+
fmt.logInfo(`MCP disabled (${reason}); removed ${removed} AW-managed MCP config file${removed === 1 ? '' : 's'}`);
|
|
320
|
+
}
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (envDisablesMcp()) {
|
|
325
|
+
if (!silent) {
|
|
326
|
+
fmt.logInfo(`MCP disabled (${DISABLE_MCP_ENV}=1); skipping MCP config updates`);
|
|
327
|
+
}
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
|
|
258
331
|
const paths = detectPaths();
|
|
259
332
|
const updatedFiles = [];
|
|
260
333
|
|
|
@@ -286,13 +359,13 @@ export async function setupMcp(cwd, namespace, { silent = false } = {}) {
|
|
|
286
359
|
|
|
287
360
|
// ── Claude Code: ~/.claude.json (global) ──
|
|
288
361
|
const claudeJsonPath = join(HOME, '.claude.json');
|
|
289
|
-
if (mergeJsonMcpServer(claudeJsonPath,
|
|
362
|
+
if (mergeJsonMcpServer(claudeJsonPath, MCP_SERVER_NAME, ghlAiServerLocal)) {
|
|
290
363
|
updatedFiles.push(claudeJsonPath);
|
|
291
364
|
}
|
|
292
365
|
|
|
293
366
|
// ── Cursor: ~/.cursor/mcp.json (global) ──
|
|
294
367
|
const cursorMcpPath = join(HOME, '.cursor', 'mcp.json');
|
|
295
|
-
if (mergeJsonMcpServer(cursorMcpPath,
|
|
368
|
+
if (mergeJsonMcpServer(cursorMcpPath, MCP_SERVER_NAME, ghlAiServerLocal)) {
|
|
296
369
|
updatedFiles.push(cursorMcpPath);
|
|
297
370
|
}
|
|
298
371
|
|
|
@@ -302,11 +375,11 @@ export async function setupMcp(cwd, namespace, { silent = false } = {}) {
|
|
|
302
375
|
// survives — without this, each re-init overwrites ~/.codex/config.toml
|
|
303
376
|
// from the ECC source which doesn't have the ghl-ai block.
|
|
304
377
|
const codexTomlPath = join(HOME, '.codex', 'config.toml');
|
|
305
|
-
if (mergeTomlMcpServer(codexTomlPath,
|
|
378
|
+
if (mergeTomlMcpServer(codexTomlPath, MCP_SERVER_NAME, ghlAiServerLocal)) {
|
|
306
379
|
updatedFiles.push(codexTomlPath);
|
|
307
380
|
}
|
|
308
381
|
const eccCodexTomlPath = join(HOME, '.aw-ecc', '.codex', 'config.toml');
|
|
309
|
-
mergeTomlMcpServer(eccCodexTomlPath,
|
|
382
|
+
mergeTomlMcpServer(eccCodexTomlPath, MCP_SERVER_NAME, ghlAiServerLocal);
|
|
310
383
|
|
|
311
384
|
// Deduplicate
|
|
312
385
|
const unique = [...new Set(updatedFiles)];
|
|
@@ -326,12 +399,6 @@ export async function setupMcp(cwd, namespace, { silent = false } = {}) {
|
|
|
326
399
|
* Called by `aw nuke`. Returns number of files modified.
|
|
327
400
|
*/
|
|
328
401
|
export function removeMcpConfig() {
|
|
329
|
-
const targets = [
|
|
330
|
-
join(HOME, '.claude.json'),
|
|
331
|
-
join(HOME, '.cursor', 'mcp.json'),
|
|
332
|
-
];
|
|
333
|
-
|
|
334
|
-
// Also clean legacy locations that older versions wrote to
|
|
335
402
|
const jsonTargets = [
|
|
336
403
|
join(HOME, '.claude.json'),
|
|
337
404
|
join(HOME, '.cursor', 'mcp.json'),
|
|
@@ -345,8 +412,8 @@ export function removeMcpConfig() {
|
|
|
345
412
|
if (!existsSync(filePath)) continue;
|
|
346
413
|
try {
|
|
347
414
|
const config = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
348
|
-
if (!config.mcpServers?.[
|
|
349
|
-
delete config.mcpServers[
|
|
415
|
+
if (!config.mcpServers?.[MCP_SERVER_NAME]) continue;
|
|
416
|
+
delete config.mcpServers[MCP_SERVER_NAME];
|
|
350
417
|
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
|
|
351
418
|
writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
|
|
352
419
|
removed++;
|
|
@@ -354,13 +421,62 @@ export function removeMcpConfig() {
|
|
|
354
421
|
}
|
|
355
422
|
|
|
356
423
|
// Codex: ~/.codex/config.toml (TOML format)
|
|
357
|
-
if (removeTomlMcpServer(join(HOME, '.codex', 'config.toml'),
|
|
424
|
+
if (removeTomlMcpServer(join(HOME, '.codex', 'config.toml'), MCP_SERVER_NAME)) {
|
|
425
|
+
removed++;
|
|
426
|
+
}
|
|
427
|
+
if (removeTomlMcpServer(join(HOME, '.aw-ecc', '.codex', 'config.toml'), MCP_SERVER_NAME)) {
|
|
358
428
|
removed++;
|
|
359
429
|
}
|
|
360
430
|
|
|
361
431
|
return removed;
|
|
362
432
|
}
|
|
363
433
|
|
|
434
|
+
function jsonMcpHealth(filePath) {
|
|
435
|
+
const server = readJson(filePath, {})?.mcpServers?.[MCP_SERVER_NAME];
|
|
436
|
+
const authorization = typeof server?.headers?.Authorization === 'string'
|
|
437
|
+
&& server.headers.Authorization.trim().length > 0;
|
|
438
|
+
return {
|
|
439
|
+
path: filePath,
|
|
440
|
+
present: !!server,
|
|
441
|
+
url: typeof server?.url === 'string' && /^https?:\/\//.test(server.url),
|
|
442
|
+
authorization,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function tomlMcpHealth(filePath) {
|
|
447
|
+
if (!existsSync(filePath)) {
|
|
448
|
+
return { path: filePath, present: false, url: false, authorization: false };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const content = readFileSync(filePath, 'utf8');
|
|
453
|
+
return {
|
|
454
|
+
path: filePath,
|
|
455
|
+
present: new RegExp(`\\[mcp_servers\\.${MCP_SERVER_NAME}\\]`).test(content),
|
|
456
|
+
url: new RegExp(`\\[mcp_servers\\.${MCP_SERVER_NAME}\\][\\s\\S]*?url\\s*=\\s*"https?:\\/\\/[^"]+"`).test(content),
|
|
457
|
+
authorization: new RegExp(`\\[mcp_servers\\.${MCP_SERVER_NAME}\\.headers\\][\\s\\S]*?Authorization\\s*=\\s*".+"`).test(content),
|
|
458
|
+
};
|
|
459
|
+
} catch {
|
|
460
|
+
// Unreadable TOML should behave like an absent optional MCP config.
|
|
461
|
+
return { path: filePath, present: false, url: false, authorization: false };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function getMcpStatus(homeDir = HOME, env = process.env) {
|
|
466
|
+
const prefs = loadMcpPreferences(homeDir);
|
|
467
|
+
return {
|
|
468
|
+
...prefs,
|
|
469
|
+
effectiveMode: isMcpEnabled(homeDir, env) ? ENABLED_MODE : DISABLED_MODE,
|
|
470
|
+
envDisableMcp: envDisablesMcp(env),
|
|
471
|
+
envDisableMcpName: DISABLE_MCP_ENV,
|
|
472
|
+
preferencesPath: mcpPrefsPath(homeDir),
|
|
473
|
+
claude: jsonMcpHealth(join(homeDir, '.claude.json')),
|
|
474
|
+
cursor: jsonMcpHealth(join(homeDir, '.cursor', 'mcp.json')),
|
|
475
|
+
codex: tomlMcpHealth(join(homeDir, '.codex', 'config.toml')),
|
|
476
|
+
eccCodex: tomlMcpHealth(join(homeDir, '.aw-ecc', '.codex', 'config.toml')),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
364
480
|
// ── TOML helpers for Codex ~/.codex/config.toml ───────────────────────────
|
|
365
481
|
|
|
366
482
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghl-ai/aw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.52",
|
|
4
4
|
"description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"hooks/",
|
|
36
36
|
"startup.mjs",
|
|
37
37
|
"ecc.mjs",
|
|
38
|
+
"integrations.mjs",
|
|
38
39
|
"render-rules.mjs",
|
|
39
40
|
"telemetry.mjs"
|
|
40
41
|
],
|
|
@@ -52,9 +53,9 @@
|
|
|
52
53
|
"license": "MIT",
|
|
53
54
|
"scripts": {
|
|
54
55
|
"test": "yarn test:vitest && yarn test:node",
|
|
55
|
-
"test:vitest": "vitest run --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs tests/c4",
|
|
56
|
+
"test:vitest": "vitest run --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs tests/c4 tests/integrations-graphify.test.mjs",
|
|
56
57
|
"test:node": "node tests/run-node-tests.mjs",
|
|
57
|
-
"test:watch": "vitest --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs tests/c4",
|
|
58
|
+
"test:watch": "vitest --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs tests/c4 tests/integrations-graphify.test.mjs",
|
|
58
59
|
"preuninstall": "node bin.js nuke 2>/dev/null || true"
|
|
59
60
|
},
|
|
60
61
|
"publishConfig": {
|
package/render-rules.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, join, relative } from 'node:path';
|
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import * as fmt from './fmt.mjs';
|
|
7
7
|
import { RULES_RUNTIME_DIR } from './constants.mjs';
|
|
8
|
+
import { isDefaultRoutingEnabled } from './startup.mjs';
|
|
8
9
|
|
|
9
10
|
// The marker is placed AFTER the YAML frontmatter so Cursor/Markdown parsers
|
|
10
11
|
// see the frontmatter at byte 0. Putting an HTML comment before --- breaks
|
|
@@ -212,6 +213,24 @@ function pruneStaleGeneratedRules(outputDir, expectedFilenames) {
|
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
215
|
|
|
216
|
+
function defaultRoutingEnabled(options = {}) {
|
|
217
|
+
if (typeof options.defaultRoutingEnabled === 'boolean') {
|
|
218
|
+
return options.defaultRoutingEnabled;
|
|
219
|
+
}
|
|
220
|
+
return isDefaultRoutingEnabled(options.homeDir || homedir());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function cleanupRenderedRules(cwd, options = {}) {
|
|
224
|
+
pruneStaleGeneratedRules(join(cwd, '.cursor', 'rules'), new Set());
|
|
225
|
+
pruneStaleGeneratedRules(join(cwd, '.claude', 'rules', 'platform'), new Set());
|
|
226
|
+
|
|
227
|
+
const HOME = options.homeDir || homedir();
|
|
228
|
+
if (cwd !== HOME) {
|
|
229
|
+
pruneStaleGeneratedRules(join(HOME, '.cursor', 'rules'), new Set());
|
|
230
|
+
pruneStaleGeneratedRules(join(HOME, '.claude', 'rules', 'platform'), new Set());
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
215
234
|
function stackOverlaysEnabled(options = {}) {
|
|
216
235
|
if (typeof options.enableStackOverlays === 'boolean') {
|
|
217
236
|
return options.enableStackOverlays;
|
|
@@ -701,8 +720,13 @@ export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
|
701
720
|
* 2. Returns sections for CLAUDE.md and AGENTS.md injection
|
|
702
721
|
*/
|
|
703
722
|
export function renderRules(cwd, options = {}) {
|
|
723
|
+
if (!defaultRoutingEnabled(options)) {
|
|
724
|
+
cleanupRenderedRules(cwd, options);
|
|
725
|
+
return { cursorCount: 0, claudeCount: 0, claudeSection: '', agentsSection: '' };
|
|
726
|
+
}
|
|
727
|
+
|
|
704
728
|
const rulesDir = resolveRulesSourceDir(cwd, options);
|
|
705
|
-
if (!rulesDir) return { cursorCount: 0, claudeSection: '', agentsSection: '' };
|
|
729
|
+
if (!rulesDir) return { cursorCount: 0, claudeCount: 0, claudeSection: '', agentsSection: '' };
|
|
706
730
|
|
|
707
731
|
// Resolve applicable scopes for AGENTS.md / CLAUDE.md MUST-rule list.
|
|
708
732
|
// Order: explicit option → .aw/config.json awRuleScopes → auto-detect.
|
package/startup.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
const STARTUP_PREFS_FILENAME = 'startup-preferences.json';
|
|
14
14
|
const ENABLED_MODE = 'enabled';
|
|
15
15
|
const DISABLED_MODE = 'disabled';
|
|
16
|
+
const DISABLE_DEFAULT_ROUTING_ENV = 'AW_DISABLE_DEFAULT_ROUTING';
|
|
16
17
|
|
|
17
18
|
const CLAUDE_DISABLE_DESCRIPTION = 'AW-managed override: disable automatic AW session routing';
|
|
18
19
|
const CLAUDE_TELEMETRY_DESCRIPTION = 'AW usage telemetry';
|
|
@@ -542,9 +543,35 @@ function disableCodexStartup(homeDir = homedir()) {
|
|
|
542
543
|
return updatedFiles;
|
|
543
544
|
}
|
|
544
545
|
|
|
545
|
-
function
|
|
546
|
+
function cursorSessionStartShellScriptUsesAwRouting(homeDir = homedir()) {
|
|
547
|
+
const scriptPath = join(homeDir, '.cursor', 'hooks', 'session-start.sh');
|
|
548
|
+
if (!existsSync(scriptPath)) return false;
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
const content = readFileSync(scriptPath, 'utf8');
|
|
552
|
+
return content.includes('using-aw-skills/hooks/session-start.sh')
|
|
553
|
+
|| content.includes('skills/using-aw-skills/hooks/session-start.sh');
|
|
554
|
+
} catch {
|
|
555
|
+
// Unreadable Cursor shell hook is safest to treat as non-AW managed.
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function isManagedCursorSessionStartEntry(entry, homeDir = homedir()) {
|
|
546
561
|
const command = String(entry?.command || '');
|
|
547
|
-
|
|
562
|
+
if (command === CURSOR_SESSION_START_COMMAND || command.endsWith('.cursor/hooks/session-start.js')) {
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (command.includes('using-aw-skills/hooks/session-start.sh')) {
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (command.includes('.cursor/hooks/session-start.sh')) {
|
|
571
|
+
return cursorSessionStartShellScriptUsesAwRouting(homeDir);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return false;
|
|
548
575
|
}
|
|
549
576
|
|
|
550
577
|
function hasCursorSessionStartScript(homeDir = homedir()) {
|
|
@@ -560,7 +587,7 @@ function disableCursorStartup(homeDir = homedir()) {
|
|
|
560
587
|
return [];
|
|
561
588
|
}
|
|
562
589
|
|
|
563
|
-
const filtered = config.hooks.sessionStart.filter(entry => !isManagedCursorSessionStartEntry(entry));
|
|
590
|
+
const filtered = config.hooks.sessionStart.filter(entry => !isManagedCursorSessionStartEntry(entry, homeDir));
|
|
564
591
|
if (filtered.length === config.hooks.sessionStart.length) {
|
|
565
592
|
return [];
|
|
566
593
|
}
|
|
@@ -622,6 +649,16 @@ export function loadStartupPreferences(homeDir = homedir()) {
|
|
|
622
649
|
};
|
|
623
650
|
}
|
|
624
651
|
|
|
652
|
+
function envDisablesDefaultRouting(env = process.env) {
|
|
653
|
+
const value = String(env[DISABLE_DEFAULT_ROUTING_ENV] || '').trim().toLowerCase();
|
|
654
|
+
return ['1', 'true', 'yes', 'on'].includes(value);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export function isDefaultRoutingEnabled(homeDir = homedir(), env = process.env) {
|
|
658
|
+
if (envDisablesDefaultRouting(env)) return false;
|
|
659
|
+
return loadStartupPreferences(homeDir).mode !== DISABLED_MODE;
|
|
660
|
+
}
|
|
661
|
+
|
|
625
662
|
export function saveStartupPreferences(mode, homeDir = homedir()) {
|
|
626
663
|
const normalizedMode = mode === DISABLED_MODE ? DISABLED_MODE : ENABLED_MODE;
|
|
627
664
|
const next = {
|
|
@@ -658,11 +695,11 @@ export function applyGlobalStartupMode(mode, homeDir = homedir()) {
|
|
|
658
695
|
return [...new Set(updatedFiles)];
|
|
659
696
|
}
|
|
660
697
|
|
|
661
|
-
export function applyStoredStartupPreferences(homeDir = homedir()) {
|
|
662
|
-
return applyGlobalStartupMode(
|
|
698
|
+
export function applyStoredStartupPreferences(homeDir = homedir(), env = process.env) {
|
|
699
|
+
return applyGlobalStartupMode(isDefaultRoutingEnabled(homeDir, env) ? ENABLED_MODE : DISABLED_MODE, homeDir);
|
|
663
700
|
}
|
|
664
701
|
|
|
665
|
-
export function getStartupStatus(homeDir = homedir()) {
|
|
702
|
+
export function getStartupStatus(homeDir = homedir(), env = process.env) {
|
|
666
703
|
const prefs = loadStartupPreferences(homeDir);
|
|
667
704
|
const claudeSettingsPath = join(homeDir, '.claude', 'settings.json');
|
|
668
705
|
const codexHooksPath = join(homeDir, '.codex', 'hooks.json');
|
|
@@ -670,9 +707,15 @@ export function getStartupStatus(homeDir = homedir()) {
|
|
|
670
707
|
const claudeSettings = readJson(claudeSettingsPath, {});
|
|
671
708
|
const codexHooks = readJson(codexHooksPath, {});
|
|
672
709
|
const cursorHooks = readJson(cursorHooksPath, {});
|
|
710
|
+
const cursorSessionStartEntries = Array.isArray(cursorHooks?.hooks?.sessionStart)
|
|
711
|
+
? cursorHooks.hooks.sessionStart
|
|
712
|
+
: [];
|
|
673
713
|
|
|
674
714
|
return {
|
|
675
715
|
...prefs,
|
|
716
|
+
effectiveMode: isDefaultRoutingEnabled(homeDir, env) ? ENABLED_MODE : DISABLED_MODE,
|
|
717
|
+
envDisableDefaultRouting: envDisablesDefaultRouting(env),
|
|
718
|
+
envDisableDefaultRoutingName: DISABLE_DEFAULT_ROUTING_ENV,
|
|
676
719
|
preferencesPath: startupPrefsPath(homeDir),
|
|
677
720
|
claudePluginEnabled: claudeSettings?.enabledPlugins?.['aw@aw-marketplace'] === true,
|
|
678
721
|
claudePluginInstalled: hasClaudePluginCache(homeDir),
|
|
@@ -684,8 +727,9 @@ export function getStartupStatus(homeDir = homedir()) {
|
|
|
684
727
|
codexSessionStartPresent: Array.isArray(codexHooks?.hooks?.SessionStart) &&
|
|
685
728
|
codexHooks.hooks.SessionStart.some(isManagedCodexSessionStartEntry),
|
|
686
729
|
codexSessionStartScriptInstalled: hasCodexSessionStartScript(homeDir),
|
|
687
|
-
cursorSessionStartPresent:
|
|
688
|
-
|
|
730
|
+
cursorSessionStartPresent: cursorSessionStartEntries.some(entry =>
|
|
731
|
+
isManagedCursorSessionStartEntry(entry, homeDir)),
|
|
732
|
+
cursorAnySessionStartPresent: cursorSessionStartEntries.length > 0,
|
|
689
733
|
cursorSessionStartScriptInstalled: hasCursorSessionStartScript(homeDir),
|
|
690
734
|
};
|
|
691
735
|
}
|