@zlnap/astrolabe 0.16.0 → 0.17.2
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/README.md +10 -2
- package/bin/astrolabe.js +24 -10
- package/lib/setup/args.js +87 -0
- package/lib/setup/clients.js +959 -0
- package/lib/setup/codebuddy.js +93 -0
- package/lib/setup/cursor.js +112 -0
- package/lib/setup/dsh.js +137 -0
- package/lib/setup/grok.js +81 -0
- package/lib/setup/index.js +16 -0
- package/lib/setup/jsonc.js +334 -0
- package/lib/setup/kimicode.js +94 -0
- package/lib/setup/opencode.js +159 -0
- package/lib/setup/prompt.js +69 -0
- package/lib/setup/run.js +314 -0
- package/lib/setup/vendor/astrolabe-dsh-guard/README.md +95 -0
- package/lib/setup/vendor/astrolabe-dsh-guard/cordis.patch.yml +11 -0
- package/lib/setup/vendor/astrolabe-dsh-guard/lib/index.js +145 -0
- package/lib/setup/vendor/astrolabe-dsh-guard/package.json +38 -0
- package/lib/setup/zcode.js +185 -0
- package/lib/setup.test.js +817 -0
- package/package.json +7 -7
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const REMIND_CMD = 'astrolabe hooks remind --client=codebuddy';
|
|
7
|
+
const CLEANUP_CMD = 'astrolabe hooks cleanup';
|
|
8
|
+
|
|
9
|
+
function resolveCodebuddyMcpPath(home, existsFn) {
|
|
10
|
+
const candidates = [
|
|
11
|
+
path.join(home, '.codebuddy', '.mcp.json'),
|
|
12
|
+
path.join(home, '.codebuddy', 'mcp.json'),
|
|
13
|
+
path.join(home, '.codebuddy.json'),
|
|
14
|
+
];
|
|
15
|
+
for (const cand of candidates) {
|
|
16
|
+
if (existsFn(cand)) return cand;
|
|
17
|
+
}
|
|
18
|
+
return candidates[0];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function detectCodebuddy(home, env, { whichFn, existsFn }) {
|
|
22
|
+
const evidence = [];
|
|
23
|
+
if (whichFn('codebuddy', env)) evidence.push('codebuddy on PATH');
|
|
24
|
+
if (existsFn(path.join(home, '.codebuddy'))) evidence.push('~/.codebuddy/');
|
|
25
|
+
if (existsFn(path.join(home, '.codebuddy.json'))) evidence.push('~/.codebuddy.json');
|
|
26
|
+
return { detected: evidence.length > 0, evidence };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function commandHookEntry(command) {
|
|
30
|
+
return {
|
|
31
|
+
matcher: '',
|
|
32
|
+
hooks: [{ type: 'command', command }],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function entryHasAstrolabeCommand(entry, needle) {
|
|
37
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
38
|
+
const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
|
|
39
|
+
return hooks.some(
|
|
40
|
+
(h) => h && typeof h.command === 'string' && h.command.includes(needle),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Merge CC-compatible PreToolUse + SessionEnd into ~/.codebuddy/settings.json
|
|
46
|
+
* without clobbering other hook entries.
|
|
47
|
+
*/
|
|
48
|
+
function mergeCodebuddyHooksSettings(filePath, { create = true, existsFn = fs.existsSync } = {}) {
|
|
49
|
+
let created = false;
|
|
50
|
+
let doc = {};
|
|
51
|
+
if (existsFn(filePath)) {
|
|
52
|
+
const raw = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
|
|
53
|
+
doc = JSON.parse(raw);
|
|
54
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) {
|
|
55
|
+
throw new Error(`${filePath} root must be a JSON object`);
|
|
56
|
+
}
|
|
57
|
+
} else if (!create) {
|
|
58
|
+
return { skipped: true, reason: `missing ${filePath} (will not create)` };
|
|
59
|
+
} else {
|
|
60
|
+
created = true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!doc.hooks || typeof doc.hooks !== 'object' || Array.isArray(doc.hooks)) {
|
|
64
|
+
doc.hooks = {};
|
|
65
|
+
}
|
|
66
|
+
const hooks = doc.hooks;
|
|
67
|
+
let replaced = false;
|
|
68
|
+
|
|
69
|
+
for (const [event, command, needle] of [
|
|
70
|
+
['PreToolUse', REMIND_CMD, 'astrolabe hooks remind --client=codebuddy'],
|
|
71
|
+
['SessionEnd', CLEANUP_CMD, 'astrolabe hooks cleanup'],
|
|
72
|
+
]) {
|
|
73
|
+
if (!Array.isArray(hooks[event])) hooks[event] = [];
|
|
74
|
+
const idx = hooks[event].findIndex((e) => entryHasAstrolabeCommand(e, needle));
|
|
75
|
+
const next = commandHookEntry(command);
|
|
76
|
+
if (idx >= 0) {
|
|
77
|
+
hooks[event][idx] = next;
|
|
78
|
+
replaced = true;
|
|
79
|
+
} else {
|
|
80
|
+
hooks[event].push(next);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { filePath, doc, created, replaced };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
REMIND_CMD,
|
|
89
|
+
CLEANUP_CMD,
|
|
90
|
+
resolveCodebuddyMcpPath,
|
|
91
|
+
detectCodebuddy,
|
|
92
|
+
mergeCodebuddyHooksSettings,
|
|
93
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const REMIND_CMD = 'astrolabe hooks remind --client=cursor';
|
|
7
|
+
const CLEANUP_CMD = 'astrolabe hooks cleanup';
|
|
8
|
+
/** Matcher must include mcp__astrolabe__.* so successful Astrolabe calls reset counters. */
|
|
9
|
+
const REMIND_MATCHER = 'Shell|Read|Grep|mcp__astrolabe__.*';
|
|
10
|
+
|
|
11
|
+
function resolveCursorHooksPath(home) {
|
|
12
|
+
return path.join(home, '.cursor', 'hooks.json');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function remindEntry() {
|
|
16
|
+
return {
|
|
17
|
+
command: REMIND_CMD,
|
|
18
|
+
matcher: REMIND_MATCHER,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function cleanupEntry() {
|
|
23
|
+
return {
|
|
24
|
+
command: CLEANUP_CMD,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function entryCommand(entry) {
|
|
29
|
+
if (!entry || typeof entry !== 'object') return '';
|
|
30
|
+
if (typeof entry.command === 'string') return entry.command;
|
|
31
|
+
const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
|
|
32
|
+
for (const h of hooks) {
|
|
33
|
+
if (h && typeof h.command === 'string') return h.command;
|
|
34
|
+
}
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isAstrolabeRemind(entry) {
|
|
39
|
+
return entryCommand(entry).includes('astrolabe hooks remind');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isAstrolabeCleanup(entry) {
|
|
43
|
+
return entryCommand(entry).includes('astrolabe hooks cleanup');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Merge ~/.cursor/hooks.json (Cursor CLI native format: camelCase events,
|
|
48
|
+
* flat command entries). Enforces --client=cursor and the documented matcher
|
|
49
|
+
* that includes mcp__astrolabe__.* so counter resets work.
|
|
50
|
+
*/
|
|
51
|
+
function mergeCursorHooksFile(filePath, { create = true, existsFn = fs.existsSync } = {}) {
|
|
52
|
+
let created = false;
|
|
53
|
+
let doc = {};
|
|
54
|
+
if (existsFn(filePath)) {
|
|
55
|
+
const raw = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
|
|
56
|
+
doc = JSON.parse(raw);
|
|
57
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) {
|
|
58
|
+
throw new Error(`${filePath} root must be a JSON object`);
|
|
59
|
+
}
|
|
60
|
+
} else if (!create) {
|
|
61
|
+
return { skipped: true, reason: `missing ${filePath} (will not create)` };
|
|
62
|
+
} else {
|
|
63
|
+
created = true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (doc.version == null) doc.version = 1;
|
|
67
|
+
if (!doc.hooks || typeof doc.hooks !== 'object' || Array.isArray(doc.hooks)) {
|
|
68
|
+
doc.hooks = {};
|
|
69
|
+
}
|
|
70
|
+
const hooks = doc.hooks;
|
|
71
|
+
let replaced = false;
|
|
72
|
+
|
|
73
|
+
if (!Array.isArray(hooks.preToolUse)) hooks.preToolUse = [];
|
|
74
|
+
const remindIdx = hooks.preToolUse.findIndex(isAstrolabeRemind);
|
|
75
|
+
const nextRemind = remindEntry();
|
|
76
|
+
if (remindIdx >= 0) {
|
|
77
|
+
hooks.preToolUse[remindIdx] = nextRemind;
|
|
78
|
+
replaced = true;
|
|
79
|
+
} else {
|
|
80
|
+
hooks.preToolUse.push(nextRemind);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!Array.isArray(hooks.sessionEnd)) hooks.sessionEnd = [];
|
|
84
|
+
const cleanupIdx = hooks.sessionEnd.findIndex(isAstrolabeCleanup);
|
|
85
|
+
const nextCleanup = cleanupEntry();
|
|
86
|
+
if (cleanupIdx >= 0) {
|
|
87
|
+
hooks.sessionEnd[cleanupIdx] = nextCleanup;
|
|
88
|
+
replaced = true;
|
|
89
|
+
} else {
|
|
90
|
+
hooks.sessionEnd.push(nextCleanup);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
filePath,
|
|
95
|
+
doc,
|
|
96
|
+
created,
|
|
97
|
+
replaced,
|
|
98
|
+
entryPreview: JSON.stringify(
|
|
99
|
+
{ version: doc.version, hooks: { preToolUse: [nextRemind], sessionEnd: [nextCleanup] } },
|
|
100
|
+
null,
|
|
101
|
+
2,
|
|
102
|
+
),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
REMIND_CMD,
|
|
108
|
+
CLEANUP_CMD,
|
|
109
|
+
REMIND_MATCHER,
|
|
110
|
+
resolveCursorHooksPath,
|
|
111
|
+
mergeCursorHooksFile,
|
|
112
|
+
};
|
package/lib/setup/dsh.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const MANAGED_BEGIN = '# >>> astrolabe-managed-dsh-guard >>>';
|
|
7
|
+
const MANAGED_END = '# <<< astrolabe-managed-dsh-guard <<<';
|
|
8
|
+
|
|
9
|
+
const MANAGED_BLOCK = `${MANAGED_BEGIN}
|
|
10
|
+
- insert:
|
|
11
|
+
- id: astrolabe-guard
|
|
12
|
+
name: 'astrolabe-dsh-guard'
|
|
13
|
+
config:
|
|
14
|
+
client: claude-code
|
|
15
|
+
${MANAGED_END}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
function resolveDshPluginSource(existsFn, startDir = __dirname) {
|
|
19
|
+
const vendor = path.join(startDir, 'vendor', 'astrolabe-dsh-guard');
|
|
20
|
+
if (existsFn(path.join(vendor, 'package.json'))) return vendor;
|
|
21
|
+
|
|
22
|
+
let dir = startDir;
|
|
23
|
+
for (let i = 0; i < 10; i += 1) {
|
|
24
|
+
const cand = path.join(dir, 'dsh-plugin');
|
|
25
|
+
if (existsFn(path.join(cand, 'package.json'))) return cand;
|
|
26
|
+
const parent = path.dirname(dir);
|
|
27
|
+
if (parent === dir) break;
|
|
28
|
+
dir = parent;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Prefer hoisted ~/.dsh/profiles/node_modules (seen on macOS installs), else
|
|
35
|
+
* each profiles/<name>/node_modules, else create the hoisted path.
|
|
36
|
+
*/
|
|
37
|
+
function resolveDshPluginInstallDirs(home, existsFn) {
|
|
38
|
+
const profiles = path.join(home, '.dsh', 'profiles');
|
|
39
|
+
const dirs = [];
|
|
40
|
+
const hoisted = path.join(profiles, 'node_modules');
|
|
41
|
+
if (existsFn(hoisted) || existsFn(profiles)) {
|
|
42
|
+
dirs.push(path.join(hoisted, 'astrolabe-dsh-guard'));
|
|
43
|
+
}
|
|
44
|
+
if (existsFn(profiles)) {
|
|
45
|
+
let entries = [];
|
|
46
|
+
try {
|
|
47
|
+
entries = fs.readdirSync(profiles, { withFileTypes: true });
|
|
48
|
+
} catch {
|
|
49
|
+
entries = [];
|
|
50
|
+
}
|
|
51
|
+
for (const ent of entries) {
|
|
52
|
+
if (!ent.isDirectory()) continue;
|
|
53
|
+
if (ent.name === 'node_modules') continue;
|
|
54
|
+
const nm = path.join(profiles, ent.name, 'node_modules');
|
|
55
|
+
// Only install into a profile that already has node_modules or a package.json
|
|
56
|
+
// (active profile), so we do not invent empty profile trees.
|
|
57
|
+
if (existsFn(nm) || existsFn(path.join(profiles, ent.name, 'package.json'))) {
|
|
58
|
+
const target = path.join(nm, 'astrolabe-dsh-guard');
|
|
59
|
+
if (!dirs.includes(target)) dirs.push(target);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!dirs.length) {
|
|
64
|
+
dirs.push(path.join(home, '.dsh', 'profiles', 'node_modules', 'astrolabe-dsh-guard'));
|
|
65
|
+
}
|
|
66
|
+
// Dedupe while preferring hoisted first
|
|
67
|
+
return [...new Set(dirs)];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function copyDirRecursive(src, dest) {
|
|
71
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
72
|
+
for (const ent of fs.readdirSync(src, { withFileTypes: true })) {
|
|
73
|
+
const from = path.join(src, ent.name);
|
|
74
|
+
const to = path.join(dest, ent.name);
|
|
75
|
+
if (ent.isDirectory()) copyDirRecursive(from, to);
|
|
76
|
+
else fs.copyFileSync(from, to);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Merge the managed astrolabe-guard insert into ~/.dsh/cordis.patch.yml without
|
|
82
|
+
* touching the orca-managed-dsh-hooks block (or any other content).
|
|
83
|
+
*/
|
|
84
|
+
function mergeDshCordisPatch(filePath, { create = true, existsFn = fs.existsSync } = {}) {
|
|
85
|
+
let created = false;
|
|
86
|
+
let text = '';
|
|
87
|
+
if (existsFn(filePath)) {
|
|
88
|
+
text = fs.readFileSync(filePath, 'utf8');
|
|
89
|
+
} else if (!create) {
|
|
90
|
+
return { skipped: true, reason: `missing ${filePath} (will not create)` };
|
|
91
|
+
} else {
|
|
92
|
+
created = true;
|
|
93
|
+
text = '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const begin = text.indexOf(MANAGED_BEGIN);
|
|
97
|
+
const end = text.indexOf(MANAGED_END);
|
|
98
|
+
let next;
|
|
99
|
+
let replaced = false;
|
|
100
|
+
if (begin >= 0 && end > begin) {
|
|
101
|
+
replaced = true;
|
|
102
|
+
const afterEnd = end + MANAGED_END.length;
|
|
103
|
+
let tail = text.slice(afterEnd);
|
|
104
|
+
if (tail.startsWith('\n')) tail = tail.slice(1);
|
|
105
|
+
next = `${text.slice(0, begin)}${MANAGED_BLOCK}${tail}`;
|
|
106
|
+
} else if (begin >= 0 || end >= 0) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`${filePath} has a broken astrolabe-managed-dsh-guard marker; fix or remove it, then re-run setup`,
|
|
109
|
+
);
|
|
110
|
+
} else {
|
|
111
|
+
const trimmed = text.replace(/\s*$/, '');
|
|
112
|
+
next = trimmed ? `${trimmed}\n\n${MANAGED_BLOCK}` : `${MANAGED_BLOCK}`;
|
|
113
|
+
}
|
|
114
|
+
if (!next.endsWith('\n')) next += '\n';
|
|
115
|
+
return { filePath, text: next, created, replaced };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function detectDsh(home, env, { whichFn, existsFn }) {
|
|
119
|
+
const evidence = [];
|
|
120
|
+
if (whichFn('dsh', env)) evidence.push('dsh on PATH');
|
|
121
|
+
if (existsFn(path.join(home, '.dsh'))) evidence.push('~/.dsh/');
|
|
122
|
+
if (existsFn(path.join(home, '.dsh', 'cordis.patch.yml'))) {
|
|
123
|
+
evidence.push('~/.dsh/cordis.patch.yml');
|
|
124
|
+
}
|
|
125
|
+
return { detected: evidence.length > 0, evidence };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
MANAGED_BEGIN,
|
|
130
|
+
MANAGED_END,
|
|
131
|
+
MANAGED_BLOCK,
|
|
132
|
+
resolveDshPluginSource,
|
|
133
|
+
resolveDshPluginInstallDirs,
|
|
134
|
+
copyDirRecursive,
|
|
135
|
+
mergeDshCordisPatch,
|
|
136
|
+
detectDsh,
|
|
137
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const GROK_HOOKS_DOC = {
|
|
7
|
+
hooks: {
|
|
8
|
+
PreToolUse: [
|
|
9
|
+
{
|
|
10
|
+
matcher: 'Bash|Read|Grep|run_terminal_command|read_file|grep|mcp__astrolabe__.*',
|
|
11
|
+
hooks: [
|
|
12
|
+
{
|
|
13
|
+
type: 'command',
|
|
14
|
+
command: 'astrolabe hooks remind --client=grok',
|
|
15
|
+
timeout: 10,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
SessionEnd: [
|
|
21
|
+
{
|
|
22
|
+
matcher: '',
|
|
23
|
+
hooks: [
|
|
24
|
+
{
|
|
25
|
+
type: 'command',
|
|
26
|
+
command: 'astrolabe hooks cleanup',
|
|
27
|
+
timeout: 10,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function detectGrok(home, env, { whichFn, existsFn }) {
|
|
36
|
+
const evidence = [];
|
|
37
|
+
if (whichFn('grok', env)) evidence.push('grok on PATH');
|
|
38
|
+
if (existsFn(path.join(home, '.grok'))) evidence.push('~/.grok/');
|
|
39
|
+
if (existsFn(path.join(home, '.grok', 'config.toml'))) evidence.push('~/.grok/config.toml');
|
|
40
|
+
return { detected: evidence.length > 0, evidence };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveGrokConfigToml(home) {
|
|
44
|
+
return path.join(home, '.grok', 'config.toml');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveGrokHooksPath(home) {
|
|
48
|
+
return path.join(home, '.grok', 'hooks', 'astrolabe.json');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Write/replace the dedicated Astrolabe hooks file (merge root.hooks keys only
|
|
53
|
+
* when an existing file already has unrelated top-level fields).
|
|
54
|
+
*/
|
|
55
|
+
function mergeGrokHooksFile(filePath, { create = true, existsFn = fs.existsSync } = {}) {
|
|
56
|
+
let created = false;
|
|
57
|
+
let doc = {};
|
|
58
|
+
if (existsFn(filePath)) {
|
|
59
|
+
const raw = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
|
|
60
|
+
doc = JSON.parse(raw);
|
|
61
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) {
|
|
62
|
+
throw new Error(`${filePath} root must be a JSON object`);
|
|
63
|
+
}
|
|
64
|
+
} else if (!create) {
|
|
65
|
+
return { skipped: true, reason: `missing ${filePath} (will not create)` };
|
|
66
|
+
} else {
|
|
67
|
+
created = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const replaced = Boolean(doc.hooks);
|
|
71
|
+
doc.hooks = GROK_HOOKS_DOC.hooks;
|
|
72
|
+
return { filePath, doc, created, replaced, entryPreview: JSON.stringify(GROK_HOOKS_DOC, null, 2) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
GROK_HOOKS_DOC,
|
|
77
|
+
detectGrok,
|
|
78
|
+
resolveGrokConfigToml,
|
|
79
|
+
resolveGrokHooksPath,
|
|
80
|
+
mergeGrokHooksFile,
|
|
81
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { run } = require('./run');
|
|
4
|
+
const { CLIENTS, resolveLaunch, mergeJsonMcpFile, mergeCodexToml, mergeCursorPermissions } = require('./clients');
|
|
5
|
+
const { parseArgs, USAGE } = require('./args');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
run,
|
|
9
|
+
CLIENTS,
|
|
10
|
+
resolveLaunch,
|
|
11
|
+
mergeJsonMcpFile,
|
|
12
|
+
mergeCodexToml,
|
|
13
|
+
mergeCursorPermissions,
|
|
14
|
+
parseArgs,
|
|
15
|
+
USAGE,
|
|
16
|
+
};
|