greprag 5.21.0 → 5.22.0
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 +47 -12
- package/dist/codex-steering.d.ts +29 -0
- package/dist/codex-steering.js +168 -0
- package/dist/codex-steering.js.map +1 -0
- package/dist/commands/codex.d.ts +33 -0
- package/dist/commands/codex.js +418 -0
- package/dist/commands/codex.js.map +1 -0
- package/dist/commands/discover.d.ts +1 -1
- package/dist/commands/discover.js +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +7 -2
- package/dist/commands/init.js +479 -59
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/status.d.ts +22 -10
- package/dist/commands/status.js +159 -69
- package/dist/commands/status.js.map +1 -1
- package/dist/hook.js +120 -13
- package/dist/hook.js.map +1 -1
- package/dist/index.js +20 -10
- package/dist/index.js.map +1 -1
- package/dist/opencode-plugin.d.ts +2 -2
- package/dist/opencode-plugin.js +47 -14
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/project-anchor.d.ts +11 -9
- package/dist/project-anchor.js +58 -27
- package/dist/project-anchor.js.map +1 -1
- package/package.json +4 -2
- package/scripts/postinstall.js +51 -46
- package/skill/greprag/SKILL.md +22 -4
- package/skill/greprag/docs/doctor.md +1 -1
- package/skill/greprag/docs/setup.md +93 -6
- package/skill/lore-advisor/SKILL.md +38 -34
package/dist/commands/init.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/** greprag init — configure
|
|
2
|
+
/** greprag init — configure agent clients for GrepRAG memory.
|
|
3
3
|
*
|
|
4
4
|
* 1. Obtain API key (provided or provisioned)
|
|
5
5
|
* 2. Validate key against health endpoint
|
|
6
|
-
* 3. Update
|
|
6
|
+
* 3. Update the target platform's native hooks/plugin/config */
|
|
7
7
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
8
|
if (k2 === undefined) k2 = k;
|
|
9
9
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.runInit = runInit;
|
|
42
|
+
exports.applyCodexHooks = applyCodexHooks;
|
|
42
43
|
exports.applySettings = applySettings;
|
|
43
44
|
exports.patchClaudeMdContent = patchClaudeMdContent;
|
|
44
45
|
const fs = __importStar(require("fs"));
|
|
@@ -47,7 +48,112 @@ const os = __importStar(require("os"));
|
|
|
47
48
|
const readline = __importStar(require("readline"));
|
|
48
49
|
const child_process_1 = require("child_process");
|
|
49
50
|
const project_anchor_1 = require("../project-anchor");
|
|
51
|
+
const codex_1 = require("./codex");
|
|
50
52
|
const API_URL = 'https://api.greprag.com';
|
|
53
|
+
function hasEnvPrefix(prefix) {
|
|
54
|
+
return Object.keys(process.env).some(k => k.startsWith(prefix));
|
|
55
|
+
}
|
|
56
|
+
function findUp(start, fileName) {
|
|
57
|
+
let dir = path.resolve(start);
|
|
58
|
+
while (true) {
|
|
59
|
+
const candidate = path.join(dir, fileName);
|
|
60
|
+
if (fs.existsSync(candidate))
|
|
61
|
+
return candidate;
|
|
62
|
+
const parent = path.dirname(dir);
|
|
63
|
+
if (parent === dir)
|
|
64
|
+
return null;
|
|
65
|
+
dir = parent;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function detectAgentTargets(cwd = process.cwd()) {
|
|
69
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
70
|
+
const detections = [];
|
|
71
|
+
const claudeEvidence = [];
|
|
72
|
+
if (process.env.CLAUDE_CODE_SESSION_ID)
|
|
73
|
+
claudeEvidence.push('CLAUDE_CODE_SESSION_ID is set');
|
|
74
|
+
if (process.env.CLAUDE_PROJECT_DIR)
|
|
75
|
+
claudeEvidence.push('CLAUDE_PROJECT_DIR is set');
|
|
76
|
+
if (home && fs.existsSync(path.join(home, '.claude')))
|
|
77
|
+
claudeEvidence.push('~/.claude exists');
|
|
78
|
+
if (claudeEvidence.length > 0)
|
|
79
|
+
detections.push({ target: 'claude', evidence: claudeEvidence });
|
|
80
|
+
const codexEvidence = [];
|
|
81
|
+
if (hasEnvPrefix('CODEX_'))
|
|
82
|
+
codexEvidence.push('CODEX_* environment is set');
|
|
83
|
+
if (home && fs.existsSync(path.join(home, '.codex')))
|
|
84
|
+
codexEvidence.push('~/.codex exists');
|
|
85
|
+
const agentsPath = findUp(cwd, 'AGENTS.md');
|
|
86
|
+
if (agentsPath)
|
|
87
|
+
codexEvidence.push(`AGENTS.md found at ${agentsPath}`);
|
|
88
|
+
if (codexEvidence.length > 0)
|
|
89
|
+
detections.push({ target: 'codex', evidence: codexEvidence });
|
|
90
|
+
const opencodeEvidence = [];
|
|
91
|
+
if (hasEnvPrefix('OPENCODE'))
|
|
92
|
+
opencodeEvidence.push('OPENCODE* environment is set');
|
|
93
|
+
if (home && fs.existsSync(path.join(home, '.config', 'opencode')))
|
|
94
|
+
opencodeEvidence.push('~/.config/opencode exists');
|
|
95
|
+
if (opencodeEvidence.length > 0)
|
|
96
|
+
detections.push({ target: 'opencode', evidence: opencodeEvidence });
|
|
97
|
+
return detections;
|
|
98
|
+
}
|
|
99
|
+
function describeTarget(target) {
|
|
100
|
+
if (target === 'claude')
|
|
101
|
+
return 'Claude Code';
|
|
102
|
+
if (target === 'codex')
|
|
103
|
+
return 'Codex';
|
|
104
|
+
return 'OpenCode';
|
|
105
|
+
}
|
|
106
|
+
function explicitInitHint() {
|
|
107
|
+
return [
|
|
108
|
+
'Run one of:',
|
|
109
|
+
' greprag init --codex',
|
|
110
|
+
' greprag init --claude',
|
|
111
|
+
' greprag init --opencode',
|
|
112
|
+
].join('\n');
|
|
113
|
+
}
|
|
114
|
+
async function chooseInitTarget() {
|
|
115
|
+
const detections = detectAgentTargets();
|
|
116
|
+
if (!process.stdin.isTTY) {
|
|
117
|
+
if (detections.length === 1)
|
|
118
|
+
return detections[0].target;
|
|
119
|
+
console.error(' Error: greprag init could not safely choose an agent target in non-interactive mode.');
|
|
120
|
+
if (detections.length > 1) {
|
|
121
|
+
console.error(' Detected more than one possible agent:');
|
|
122
|
+
for (const d of detections) {
|
|
123
|
+
console.error(` - ${describeTarget(d.target)}: ${d.evidence.join('; ')}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
console.error(explicitInitHint());
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
if (detections.length === 1) {
|
|
130
|
+
const detected = detections[0];
|
|
131
|
+
console.log(`\n Detected ${describeTarget(detected.target)} (${detected.evidence.join('; ')}).`);
|
|
132
|
+
const answer = (await prompt(` Configure GrepRAG for ${describeTarget(detected.target)}? (Y/n): `)).trim().toLowerCase();
|
|
133
|
+
if (!answer || answer === 'y' || answer === 'yes')
|
|
134
|
+
return detected.target;
|
|
135
|
+
}
|
|
136
|
+
else if (detections.length > 1) {
|
|
137
|
+
console.log('\n Multiple agent environments look available:');
|
|
138
|
+
for (const d of detections) {
|
|
139
|
+
console.log(` - ${describeTarget(d.target)}: ${d.evidence.join('; ')}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
console.log('\n Which agent are you setting up GrepRAG for?');
|
|
143
|
+
console.log(' 1. Codex');
|
|
144
|
+
console.log(' 2. Claude Code');
|
|
145
|
+
console.log(' 3. OpenCode');
|
|
146
|
+
const answer = (await prompt(' Choose 1, 2, or 3: ')).trim().toLowerCase();
|
|
147
|
+
if (answer === '1' || answer === 'codex')
|
|
148
|
+
return 'codex';
|
|
149
|
+
if (answer === '2' || answer === 'claude' || answer === 'claude code')
|
|
150
|
+
return 'claude';
|
|
151
|
+
if (answer === '3' || answer === 'opencode' || answer === 'open code')
|
|
152
|
+
return 'opencode';
|
|
153
|
+
console.error(' Error: no agent target selected.');
|
|
154
|
+
console.error(explicitInitHint());
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
51
157
|
async function runInit(opts) {
|
|
52
158
|
// --all: run the FULL standard init for cwd first (API key, hooks, anchor,
|
|
53
159
|
// global anchor, skill, chip-spawn doc, CLAUDE.md patch), THEN bulk-register
|
|
@@ -56,24 +162,37 @@ async function runInit(opts) {
|
|
|
56
162
|
// Reuse the existing GREPRAG_API_KEY from settings if one is already there,
|
|
57
163
|
// so re-runs don't churn the tenant's key via the provision path.
|
|
58
164
|
if (opts.all) {
|
|
59
|
-
let apiKey = opts.apiKey;
|
|
60
|
-
if (!apiKey) {
|
|
61
|
-
apiKey = readSettings(getSettingsPath()).env?.GREPRAG_API_KEY;
|
|
62
|
-
}
|
|
165
|
+
let apiKey = opts.apiKey || readSharedApiKey();
|
|
63
166
|
await runInit({ ...opts, all: false, apiKey });
|
|
64
167
|
return runInitAll(opts.root);
|
|
65
168
|
}
|
|
66
|
-
// --global: create ~/.
|
|
169
|
+
// --global: create ~/.greprag/project.json only — no API key, no hooks.
|
|
67
170
|
if (opts.global) {
|
|
68
171
|
return runGlobalInit(opts.name);
|
|
69
172
|
}
|
|
173
|
+
const explicitTargets = [opts.claude, opts.codex, opts.opencode].filter(Boolean).length;
|
|
174
|
+
if (explicitTargets > 1) {
|
|
175
|
+
console.error(' Error: choose only one target: --claude, --codex, or --opencode.');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
70
178
|
// --opencode: configure for OpenCode instead of Claude Code.
|
|
71
179
|
if (opts.opencode) {
|
|
72
180
|
return runOpenCodeInit(opts);
|
|
73
181
|
}
|
|
182
|
+
// --codex: configure Codex lifecycle hooks.
|
|
183
|
+
if (opts.codex) {
|
|
184
|
+
return runCodexInit(opts);
|
|
185
|
+
}
|
|
186
|
+
if (!opts.claude) {
|
|
187
|
+
const target = await chooseInitTarget();
|
|
188
|
+
if (target === 'codex')
|
|
189
|
+
return runCodexInit(opts);
|
|
190
|
+
if (target === 'opencode')
|
|
191
|
+
return runOpenCodeInit(opts);
|
|
192
|
+
}
|
|
74
193
|
console.log('\n greprag init — Setting up agent memory for Claude Code\n');
|
|
75
194
|
// Step 1: Get API key
|
|
76
|
-
let apiKey = opts.apiKey;
|
|
195
|
+
let apiKey = opts.apiKey || readSharedApiKey();
|
|
77
196
|
if (!apiKey) {
|
|
78
197
|
let tenantId = opts.tenantId;
|
|
79
198
|
if (!tenantId) {
|
|
@@ -101,21 +220,7 @@ async function runInit(opts) {
|
|
|
101
220
|
process.exit(1);
|
|
102
221
|
}
|
|
103
222
|
console.log(' Key validated against api.greprag.com');
|
|
104
|
-
|
|
105
|
-
// a usable routing address offline. adr: adr/numeric-handles.md
|
|
106
|
-
try {
|
|
107
|
-
const { fetchIdentity, writeCache } = await Promise.resolve().then(() => __importStar(require('./identity')));
|
|
108
|
-
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
109
|
-
const cachePath = `${home}/.greprag/identity.json`;
|
|
110
|
-
const ident = await fetchIdentity('https://api.greprag.com', apiKey);
|
|
111
|
-
writeCache(cachePath, ident);
|
|
112
|
-
if (ident.handle)
|
|
113
|
-
console.log(` Identity: ${ident.handle}`);
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
// Non-fatal — `greprag identity show` can repopulate later.
|
|
117
|
-
console.log(` Identity cache deferred (${e.message})`);
|
|
118
|
-
}
|
|
223
|
+
await cacheIdentity(apiKey);
|
|
119
224
|
// Step 3: Update settings.json
|
|
120
225
|
console.log(' Configuring Claude Code settings...');
|
|
121
226
|
const settingsPath = getSettingsPath();
|
|
@@ -133,7 +238,7 @@ async function runInit(opts) {
|
|
|
133
238
|
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — derived from git history`);
|
|
134
239
|
}
|
|
135
240
|
else {
|
|
136
|
-
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .
|
|
241
|
+
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .greprag/project.json`);
|
|
137
242
|
// Only patch gitignore when the file is load-bearing for identity.
|
|
138
243
|
const gitignoreResult = ensureAnchorTrackable(process.cwd());
|
|
139
244
|
if (gitignoreResult)
|
|
@@ -155,25 +260,19 @@ async function runInit(opts) {
|
|
|
155
260
|
// with capabilities the operator may not want. Postinstall mirrors this
|
|
156
261
|
// policy (sync greprag/ always, refresh-only for opted-in advisors).
|
|
157
262
|
// adr: adr/skill-bundle-shape.md
|
|
158
|
-
const skillRoot =
|
|
159
|
-
const skillsTargetRoot = path.join(os.homedir(), '.claude', 'skills');
|
|
160
|
-
const CORE_SKILL = 'greprag';
|
|
263
|
+
const skillRoot = getSkillRoot();
|
|
161
264
|
const optionalSkills = [];
|
|
162
265
|
if (fs.existsSync(skillRoot)) {
|
|
266
|
+
const installed = installCoreSkill('claude');
|
|
267
|
+
if (installed)
|
|
268
|
+
changes.push(`Skill: installed → ${installed}`);
|
|
163
269
|
for (const entry of fs.readdirSync(skillRoot, { withFileTypes: true })) {
|
|
164
270
|
if (!entry.isDirectory())
|
|
165
271
|
continue;
|
|
166
272
|
if (entry.name === 'templates')
|
|
167
273
|
continue;
|
|
168
|
-
if (entry.name
|
|
169
|
-
const srcDir = path.join(skillRoot, entry.name);
|
|
170
|
-
const destDir = path.join(skillsTargetRoot, entry.name);
|
|
171
|
-
copySkillDir(srcDir, destDir);
|
|
172
|
-
changes.push(`Skill: installed → ${destDir}`);
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
274
|
+
if (entry.name !== 'greprag')
|
|
175
275
|
optionalSkills.push(entry.name);
|
|
176
|
-
}
|
|
177
276
|
}
|
|
178
277
|
}
|
|
179
278
|
// Step 6b: Install the chip-spawn doc to ~/.claude/docs/chip-spawn.md. The
|
|
@@ -219,14 +318,14 @@ async function runInit(opts) {
|
|
|
219
318
|
* global plugins directory.
|
|
220
319
|
*
|
|
221
320
|
* Does NOT create a separate `.opencode/project.json` — the opencode plugin's
|
|
222
|
-
* `readAnchor` checks `.
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
321
|
+
* `readAnchor` checks `.greprag/project.json` first, so a single shared
|
|
322
|
+
* anchor is used by all clients and memory accumulates under one project_id.
|
|
323
|
+
* A user who initialized opencode first and Claude Code second on the same
|
|
324
|
+
* repo gets the same project_id from both inits (idempotent). */
|
|
226
325
|
async function runOpenCodeInit(opts) {
|
|
227
326
|
console.log('\n greprag init — Setting up agent memory for OpenCode\n');
|
|
228
327
|
// Step 1: Get API key (same provision path as Claude Code init)
|
|
229
|
-
let apiKey = opts.apiKey;
|
|
328
|
+
let apiKey = opts.apiKey || readSharedApiKey();
|
|
230
329
|
if (!apiKey) {
|
|
231
330
|
let tenantId = opts.tenantId;
|
|
232
331
|
if (!tenantId) {
|
|
@@ -254,14 +353,15 @@ async function runOpenCodeInit(opts) {
|
|
|
254
353
|
process.exit(1);
|
|
255
354
|
}
|
|
256
355
|
console.log(' Key validated against api.greprag.com');
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
//
|
|
356
|
+
await cacheIdentity(apiKey);
|
|
357
|
+
writeGrepragEnv(apiKey);
|
|
358
|
+
// Step 3: Ensure env vars are also set in ~/.claude/settings.json for
|
|
359
|
+
// backwards compatibility with older OpenCode plugin installs.
|
|
260
360
|
const settingsPath = getSettingsPath();
|
|
261
361
|
const settings = readSettings(settingsPath);
|
|
262
362
|
if (!settings.env)
|
|
263
363
|
settings.env = {};
|
|
264
|
-
const envChanges = [];
|
|
364
|
+
const envChanges = ['Wrote ~/.greprag/.env'];
|
|
265
365
|
if (!settings.env.GREPRAG_API_KEY) {
|
|
266
366
|
settings.env.GREPRAG_API_KEY = apiKey;
|
|
267
367
|
envChanges.push('Set GREPRAG_API_KEY');
|
|
@@ -274,7 +374,7 @@ async function runOpenCodeInit(opts) {
|
|
|
274
374
|
writeSettings(settingsPath, settings);
|
|
275
375
|
}
|
|
276
376
|
// Step 4: Resolve the canonical project anchor via the same cascade Claude
|
|
277
|
-
// Code uses. Idempotent — if `.
|
|
377
|
+
// Code uses. Idempotent — if `.greprag/project.json` already exists, this
|
|
278
378
|
// returns the existing anchor unchanged. In a git repo with commits, the
|
|
279
379
|
// identity is derived from the root commit SHA and the file holds only
|
|
280
380
|
// settings (no project_id). The opencode plugin's readAnchor reads this
|
|
@@ -334,11 +434,114 @@ async function runOpenCodeInit(opts) {
|
|
|
334
434
|
console.log(` - Project anchor: ${anchor.anchorPath} (source: ${anchor.source})`);
|
|
335
435
|
if (pluginInstalled)
|
|
336
436
|
console.log(` - Plugin: ${pluginDest}`);
|
|
437
|
+
console.log(` - Shared env: ${getGrepragEnvPath()}`);
|
|
337
438
|
console.log(' OpenCode will load the plugin automatically on next session start.');
|
|
338
439
|
console.log(' The /greprag skill is also available for on-demand briefings.\n');
|
|
339
440
|
}
|
|
441
|
+
/** greprag init --codex
|
|
442
|
+
* Configures GrepRAG for Codex lifecycle hooks. Codex does not read
|
|
443
|
+
* ~/.claude/settings.json env, so this path writes ~/.greprag/.env and a
|
|
444
|
+
* user-level ~/.codex/hooks.json. The hooks still call the same greprag-hook
|
|
445
|
+
* binary; codex-* subcommands only adapt Codex's prompt/Stop payload shape. */
|
|
446
|
+
async function runCodexInit(opts) {
|
|
447
|
+
console.log('\n greprag init — Setting up agent memory for Codex\n');
|
|
448
|
+
let apiKey = opts.apiKey || readSharedApiKey();
|
|
449
|
+
if (!apiKey) {
|
|
450
|
+
let tenantId = opts.tenantId;
|
|
451
|
+
if (!tenantId) {
|
|
452
|
+
tenantId = await prompt(' Enter a tenant ID (alphanumeric + hyphens, 3-32 chars): ');
|
|
453
|
+
}
|
|
454
|
+
tenantId = tenantId.trim().toLowerCase();
|
|
455
|
+
if (!tenantId) {
|
|
456
|
+
console.error(' Error: tenant ID is required');
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
console.log(` Provisioning API key for tenant "${tenantId}"...`);
|
|
460
|
+
const result = await provision(tenantId);
|
|
461
|
+
if (!result.ok) {
|
|
462
|
+
console.error(` Error: ${result.error}`);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
apiKey = result.apiKey;
|
|
466
|
+
console.log(` API key created: ${apiKey.slice(0, 17)}...`);
|
|
467
|
+
}
|
|
468
|
+
console.log(' Validating API key...');
|
|
469
|
+
const valid = await validateKey(apiKey);
|
|
470
|
+
if (!valid) {
|
|
471
|
+
console.error(' Error: API key validation failed.');
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
console.log(' Key validated against api.greprag.com');
|
|
475
|
+
const changes = [];
|
|
476
|
+
writeGrepragEnv(apiKey);
|
|
477
|
+
changes.push('Wrote ~/.greprag/.env for Codex hooks');
|
|
478
|
+
await cacheIdentity(apiKey);
|
|
479
|
+
const anchor = (0, project_anchor_1.ensureAnchor)(process.cwd());
|
|
480
|
+
if (anchor.source === 'git') {
|
|
481
|
+
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — derived from git history`);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .greprag/project.json`);
|
|
485
|
+
const gitignoreResult = ensureAnchorTrackable(process.cwd());
|
|
486
|
+
if (gitignoreResult)
|
|
487
|
+
changes.push(gitignoreResult);
|
|
488
|
+
}
|
|
489
|
+
const globalAnchor = (0, project_anchor_1.ensureGlobalAnchor)();
|
|
490
|
+
changes.push(`Global anchor: ${globalAnchor.projectName} (${globalAnchor.projectId.slice(0, 8)})`);
|
|
491
|
+
writeProjectPath(anchor.projectName, process.cwd());
|
|
492
|
+
changes.push(`Registered repo path for ${anchor.projectName} → ~/.greprag/projects.json`);
|
|
493
|
+
const codexSkill = installCoreSkill('codex');
|
|
494
|
+
if (codexSkill)
|
|
495
|
+
changes.push(`Skill: installed → ${codexSkill}`);
|
|
496
|
+
try {
|
|
497
|
+
const res = await fetch(`${API_URL}/v1/inbox/projects/register`, {
|
|
498
|
+
method: 'POST',
|
|
499
|
+
headers: {
|
|
500
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
501
|
+
'Content-Type': 'application/json',
|
|
502
|
+
},
|
|
503
|
+
body: JSON.stringify({ project_id: anchor.projectId, project_name: anchor.projectName }),
|
|
504
|
+
});
|
|
505
|
+
const reg = await res.json();
|
|
506
|
+
changes.push(reg.ok ? 'Project registered for inbox addressing' : `Inbox registration: ${reg.error || 'failed'}`);
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
changes.push('Inbox registration skipped (network)');
|
|
510
|
+
}
|
|
511
|
+
const hooksPath = getCodexHooksPath();
|
|
512
|
+
const hooks = readCodexHooks(hooksPath);
|
|
513
|
+
const hookChanges = applyCodexHooks(hooks);
|
|
514
|
+
writeCodexHooks(hooksPath, hooks);
|
|
515
|
+
changes.push(...hookChanges);
|
|
516
|
+
const startup = (0, codex_1.codexStartupInfo)();
|
|
517
|
+
if (startup.installed) {
|
|
518
|
+
changes.push(`Codex live inbox startup watcher already installed (${startup.path})`);
|
|
519
|
+
}
|
|
520
|
+
else if (process.stdin.isTTY) {
|
|
521
|
+
const answer = (await prompt(' Install Codex live inbox watcher at login? (Y/n): ')).trim().toLowerCase();
|
|
522
|
+
if (!answer || answer === 'y' || answer === 'yes') {
|
|
523
|
+
(0, codex_1.installCodexStartup)();
|
|
524
|
+
changes.push(`Codex live inbox startup watcher installed (${startup.path})`);
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
changes.push('Codex live inbox startup watcher skipped (run `greprag codex startup install` later)');
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
changes.push('Codex live inbox startup watcher not installed (run `greprag codex startup install`)');
|
|
532
|
+
}
|
|
533
|
+
console.log('\n Setup complete!\n');
|
|
534
|
+
for (const change of changes) {
|
|
535
|
+
console.log(` - ${change}`);
|
|
536
|
+
}
|
|
537
|
+
console.log(`\n Codex hooks file: ${hooksPath}`);
|
|
538
|
+
console.log(` Project anchor: ${anchor.anchorPath}`);
|
|
539
|
+
console.log(' In Codex, run /hooks and trust the GrepRAG hook definitions.');
|
|
540
|
+
console.log(' For live inbox push, run: greprag codex startup install');
|
|
541
|
+
console.log(' Memory hooks will activate on your next Codex session.\n');
|
|
542
|
+
}
|
|
340
543
|
/** greprag init --global
|
|
341
|
-
* Creates ~/.
|
|
544
|
+
* Creates ~/.greprag/project.json with a stable UUID.
|
|
342
545
|
* Designed for Cowork / any agent session whose cwd has no repo-level anchor.
|
|
343
546
|
* Idempotent — safe to run again; returns the existing anchor if already present. */
|
|
344
547
|
async function runGlobalInit(name) {
|
|
@@ -356,7 +559,7 @@ async function runGlobalInit(name) {
|
|
|
356
559
|
}
|
|
357
560
|
/** greprag init --all
|
|
358
561
|
* Bulk-register every git repo at depth 1 under a scan root (default: parent
|
|
359
|
-
* of cwd). Per-repo work: ensureAnchor (writes .
|
|
562
|
+
* of cwd). Per-repo work: ensureAnchor (writes .greprag/project.json or relies
|
|
360
563
|
* on the git-derived cascade), writeProjectPath (~/.greprag/projects.json),
|
|
361
564
|
* inbox registration (so `send --to <name>@.../<project-name>` resolves), and
|
|
362
565
|
* gitignore patch when the anchor file is load-bearing for identity.
|
|
@@ -367,11 +570,9 @@ async function runGlobalInit(name) {
|
|
|
367
570
|
async function runInitAll(rootPath) {
|
|
368
571
|
console.log('\n greprag init --all — Register every git repo for inbox addressing\n');
|
|
369
572
|
// 1. Existing API key required.
|
|
370
|
-
const
|
|
371
|
-
const settings = readSettings(settingsPath);
|
|
372
|
-
const apiKey = settings.env?.GREPRAG_API_KEY;
|
|
573
|
+
const apiKey = readSharedApiKey();
|
|
373
574
|
if (!apiKey) {
|
|
374
|
-
console.error(' Error: no GREPRAG_API_KEY in ~/.claude/settings.json.');
|
|
575
|
+
console.error(' Error: no GREPRAG_API_KEY in ~/.greprag/.env or ~/.claude/settings.json.');
|
|
375
576
|
console.error(' Run `greprag init` once to set up the key + hooks, then re-run init --all.');
|
|
376
577
|
process.exit(1);
|
|
377
578
|
}
|
|
@@ -587,6 +788,10 @@ function getSettingsPath() {
|
|
|
587
788
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
588
789
|
return path.join(home, '.claude', 'settings.json');
|
|
589
790
|
}
|
|
791
|
+
function getGrepragEnvPath() {
|
|
792
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
793
|
+
return path.join(home, '.greprag', '.env');
|
|
794
|
+
}
|
|
590
795
|
function readSettings(settingsPath) {
|
|
591
796
|
try {
|
|
592
797
|
const raw = fs.readFileSync(settingsPath, 'utf-8');
|
|
@@ -603,6 +808,221 @@ function writeSettings(settingsPath, settings) {
|
|
|
603
808
|
}
|
|
604
809
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
605
810
|
}
|
|
811
|
+
function writeGrepragEnv(apiKey) {
|
|
812
|
+
const file = getGrepragEnvPath();
|
|
813
|
+
const dir = path.dirname(file);
|
|
814
|
+
let existing = {};
|
|
815
|
+
try {
|
|
816
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
817
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
818
|
+
const trimmed = line.trim();
|
|
819
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
820
|
+
continue;
|
|
821
|
+
const idx = trimmed.indexOf('=');
|
|
822
|
+
if (idx < 1)
|
|
823
|
+
continue;
|
|
824
|
+
existing[trimmed.slice(0, idx)] = trimmed.slice(idx + 1);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
catch { /* missing — write fresh */ }
|
|
828
|
+
existing.GREPRAG_API_KEY = apiKey;
|
|
829
|
+
existing.MEMORY_HOOK_ENABLED = 'true';
|
|
830
|
+
if (!fs.existsSync(dir))
|
|
831
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
832
|
+
const body = Object.entries(existing)
|
|
833
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
834
|
+
.join('\n') + '\n';
|
|
835
|
+
fs.writeFileSync(file, body);
|
|
836
|
+
}
|
|
837
|
+
function readEnvFile(file) {
|
|
838
|
+
const out = {};
|
|
839
|
+
try {
|
|
840
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
841
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
842
|
+
const trimmed = line.trim();
|
|
843
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
844
|
+
continue;
|
|
845
|
+
const idx = trimmed.indexOf('=');
|
|
846
|
+
if (idx < 1)
|
|
847
|
+
continue;
|
|
848
|
+
let value = trimmed.slice(idx + 1).trim();
|
|
849
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
850
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
851
|
+
value = value.slice(1, -1);
|
|
852
|
+
}
|
|
853
|
+
out[trimmed.slice(0, idx).trim()] = value;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
catch { /* missing env file */ }
|
|
857
|
+
return out;
|
|
858
|
+
}
|
|
859
|
+
function readSharedApiKey() {
|
|
860
|
+
return process.env.GREPRAG_API_KEY
|
|
861
|
+
|| readEnvFile(getGrepragEnvPath()).GREPRAG_API_KEY
|
|
862
|
+
|| readSettings(getSettingsPath()).env?.GREPRAG_API_KEY;
|
|
863
|
+
}
|
|
864
|
+
async function cacheIdentity(apiKey) {
|
|
865
|
+
try {
|
|
866
|
+
const { fetchIdentity, writeCache } = await Promise.resolve().then(() => __importStar(require('./identity')));
|
|
867
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
868
|
+
const cachePath = path.join(home, '.greprag', 'identity.json');
|
|
869
|
+
const ident = await fetchIdentity('https://api.greprag.com', apiKey);
|
|
870
|
+
writeCache(cachePath, ident);
|
|
871
|
+
if (ident.handle)
|
|
872
|
+
console.log(` Identity: ${ident.handle}`);
|
|
873
|
+
}
|
|
874
|
+
catch (e) {
|
|
875
|
+
console.log(` Identity cache deferred (${e.message})`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function getSkillRoot() {
|
|
879
|
+
return path.join(__dirname, '../../skill');
|
|
880
|
+
}
|
|
881
|
+
function skillTargetRoot(target) {
|
|
882
|
+
const dir = target === 'codex' ? '.codex' : '.claude';
|
|
883
|
+
return path.join(os.homedir(), dir, 'skills');
|
|
884
|
+
}
|
|
885
|
+
function installCoreSkill(target) {
|
|
886
|
+
const srcDir = path.join(getSkillRoot(), 'greprag');
|
|
887
|
+
if (!fs.existsSync(srcDir))
|
|
888
|
+
return null;
|
|
889
|
+
const destDir = path.join(skillTargetRoot(target), 'greprag');
|
|
890
|
+
copySkillDir(srcDir, destDir);
|
|
891
|
+
return destDir;
|
|
892
|
+
}
|
|
893
|
+
function getCodexHooksPath() {
|
|
894
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
895
|
+
return path.join(home, '.codex', 'hooks.json');
|
|
896
|
+
}
|
|
897
|
+
function readCodexHooks(hooksPath) {
|
|
898
|
+
try {
|
|
899
|
+
const raw = fs.readFileSync(hooksPath, 'utf-8');
|
|
900
|
+
return JSON.parse(raw);
|
|
901
|
+
}
|
|
902
|
+
catch {
|
|
903
|
+
return {};
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function writeCodexHooks(hooksPath, hooks) {
|
|
907
|
+
const dir = path.dirname(hooksPath);
|
|
908
|
+
if (!fs.existsSync(dir))
|
|
909
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
910
|
+
fs.writeFileSync(hooksPath, JSON.stringify(hooks, null, 2) + '\n');
|
|
911
|
+
}
|
|
912
|
+
function applyCodexHooks(config) {
|
|
913
|
+
const changes = [];
|
|
914
|
+
if (!config.hooks)
|
|
915
|
+
config.hooks = {};
|
|
916
|
+
const recapHook = {
|
|
917
|
+
matcher: 'startup|resume|clear|compact',
|
|
918
|
+
hooks: [{
|
|
919
|
+
type: 'command',
|
|
920
|
+
command: 'greprag-hook recap',
|
|
921
|
+
timeout: 3,
|
|
922
|
+
statusMessage: 'Loading GrepRAG memory',
|
|
923
|
+
}],
|
|
924
|
+
};
|
|
925
|
+
if (!hasGrepragHookWithMatcher(config.hooks.SessionStart, 'recap', recapHook.matcher)) {
|
|
926
|
+
if (!config.hooks.SessionStart)
|
|
927
|
+
config.hooks.SessionStart = [];
|
|
928
|
+
config.hooks.SessionStart.push(recapHook);
|
|
929
|
+
changes.push('Added Codex SessionStart hook (memory recap)');
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
changes.push('Codex SessionStart recap hook already configured (skipped)');
|
|
933
|
+
}
|
|
934
|
+
const sessionHook = {
|
|
935
|
+
matcher: 'startup|resume|clear|compact',
|
|
936
|
+
hooks: [{
|
|
937
|
+
type: 'command',
|
|
938
|
+
command: 'greprag-hook session-id',
|
|
939
|
+
timeout: 3,
|
|
940
|
+
statusMessage: 'Loading GrepRAG session id',
|
|
941
|
+
}],
|
|
942
|
+
};
|
|
943
|
+
if (!hasGrepragHookWithMatcher(config.hooks.SessionStart, 'session-id', sessionHook.matcher)) {
|
|
944
|
+
if (!config.hooks.SessionStart)
|
|
945
|
+
config.hooks.SessionStart = [];
|
|
946
|
+
config.hooks.SessionStart.push(sessionHook);
|
|
947
|
+
changes.push('Added Codex SessionStart hook (session-id awareness)');
|
|
948
|
+
}
|
|
949
|
+
else {
|
|
950
|
+
changes.push('Codex SessionStart session-id hook already configured (skipped)');
|
|
951
|
+
}
|
|
952
|
+
const notifyHook = {
|
|
953
|
+
matcher: '',
|
|
954
|
+
hooks: [{
|
|
955
|
+
type: 'command',
|
|
956
|
+
command: 'greprag-hook codex-notify',
|
|
957
|
+
timeout: 3,
|
|
958
|
+
statusMessage: 'Checking GrepRAG inbox',
|
|
959
|
+
}],
|
|
960
|
+
};
|
|
961
|
+
if (!hasGrepragHook(config.hooks.UserPromptSubmit, 'codex-notify')) {
|
|
962
|
+
if (!config.hooks.UserPromptSubmit)
|
|
963
|
+
config.hooks.UserPromptSubmit = [];
|
|
964
|
+
config.hooks.UserPromptSubmit.push(notifyHook);
|
|
965
|
+
changes.push('Added Codex UserPromptSubmit hook (prompt cache + inbox steering)');
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
changes.push('Codex UserPromptSubmit hook already configured (skipped)');
|
|
969
|
+
}
|
|
970
|
+
const inboxHook = {
|
|
971
|
+
matcher: '',
|
|
972
|
+
hooks: [{
|
|
973
|
+
type: 'command',
|
|
974
|
+
command: 'greprag-hook codex-inbox',
|
|
975
|
+
timeout: 3,
|
|
976
|
+
statusMessage: 'Checking GrepRAG inbox',
|
|
977
|
+
}],
|
|
978
|
+
};
|
|
979
|
+
if (!hasGrepragHook(config.hooks.PostToolUse, 'codex-inbox')) {
|
|
980
|
+
if (!config.hooks.PostToolUse)
|
|
981
|
+
config.hooks.PostToolUse = [];
|
|
982
|
+
config.hooks.PostToolUse.push(inboxHook);
|
|
983
|
+
changes.push('Added Codex PostToolUse hook (inbox steering)');
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
changes.push('Codex PostToolUse inbox hook already configured (skipped)');
|
|
987
|
+
}
|
|
988
|
+
const storeHook = {
|
|
989
|
+
matcher: '',
|
|
990
|
+
hooks: [{
|
|
991
|
+
type: 'command',
|
|
992
|
+
command: 'greprag-hook codex-store',
|
|
993
|
+
timeout: 10,
|
|
994
|
+
statusMessage: 'Storing GrepRAG turn',
|
|
995
|
+
}],
|
|
996
|
+
};
|
|
997
|
+
if (!hasGrepragHook(config.hooks.Stop, 'codex-store')) {
|
|
998
|
+
if (!config.hooks.Stop)
|
|
999
|
+
config.hooks.Stop = [];
|
|
1000
|
+
config.hooks.Stop.push(storeHook);
|
|
1001
|
+
changes.push('Added Codex Stop hook (memory store)');
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
changes.push('Codex Stop hook already configured (skipped)');
|
|
1005
|
+
}
|
|
1006
|
+
const postCompactHook = {
|
|
1007
|
+
matcher: 'manual|auto',
|
|
1008
|
+
hooks: [{
|
|
1009
|
+
type: 'command',
|
|
1010
|
+
command: 'greprag-hook session-id',
|
|
1011
|
+
timeout: 3,
|
|
1012
|
+
statusMessage: 'Restoring GrepRAG session id',
|
|
1013
|
+
}],
|
|
1014
|
+
};
|
|
1015
|
+
if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'session-id', postCompactHook.matcher)) {
|
|
1016
|
+
if (!config.hooks.PostCompact)
|
|
1017
|
+
config.hooks.PostCompact = [];
|
|
1018
|
+
config.hooks.PostCompact.push(postCompactHook);
|
|
1019
|
+
changes.push('Added Codex PostCompact hook (session-id awareness)');
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
changes.push('Codex PostCompact session-id hook already configured (skipped)');
|
|
1023
|
+
}
|
|
1024
|
+
return changes;
|
|
1025
|
+
}
|
|
606
1026
|
function applySettings(settings, apiKey) {
|
|
607
1027
|
const changes = [];
|
|
608
1028
|
// Env vars
|
|
@@ -812,14 +1232,14 @@ function patchGlobalClaudeMd() {
|
|
|
812
1232
|
return true;
|
|
813
1233
|
}
|
|
814
1234
|
// -- Anchor trackability ----------------------------------------------------
|
|
815
|
-
/** When a repo gitignores `.
|
|
1235
|
+
/** When a repo gitignores `.greprag/`, the anchor file we just wrote won't
|
|
816
1236
|
* survive a fresh clone or worktree switch — the deterministic-hash fallback
|
|
817
1237
|
* will silently take over, splitting the project_id and breaking inbox
|
|
818
1238
|
* addressing. Detect that case at init time and patch the gitignore so the
|
|
819
1239
|
* anchor is durably tracked.
|
|
820
1240
|
*
|
|
821
|
-
* Common patterns we handle: `.
|
|
822
|
-
* Each is rewritten to its `*`-contents form so a `!.
|
|
1241
|
+
* Common patterns we handle: `.greprag/`, `.greprag`, `/.greprag/`, `/.greprag`.
|
|
1242
|
+
* Each is rewritten to its `*`-contents form so a `!.greprag/project.json`
|
|
823
1243
|
* re-include works (git won't descend into an excluded directory, so the
|
|
824
1244
|
* trailing-slash directory pattern must become a contents-only pattern).
|
|
825
1245
|
*
|
|
@@ -838,7 +1258,7 @@ function ensureAnchorTrackable(projectRoot) {
|
|
|
838
1258
|
// 1 when not ignored. Either non-zero exit means "no work to do."
|
|
839
1259
|
let checkOutput;
|
|
840
1260
|
try {
|
|
841
|
-
checkOutput = (0, child_process_1.execSync)('git check-ignore -v .
|
|
1261
|
+
checkOutput = (0, child_process_1.execSync)('git check-ignore -v .greprag/project.json', {
|
|
842
1262
|
cwd: projectRoot,
|
|
843
1263
|
encoding: 'utf-8',
|
|
844
1264
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -862,7 +1282,7 @@ function ensureAnchorTrackable(projectRoot) {
|
|
|
862
1282
|
return null;
|
|
863
1283
|
const trimmed = pattern.trim();
|
|
864
1284
|
const dirPatterns = new Set([
|
|
865
|
-
'.
|
|
1285
|
+
'.greprag/', '.greprag', '/.greprag/', '/.greprag',
|
|
866
1286
|
]);
|
|
867
1287
|
if (dirPatterns.has(trimmed)) {
|
|
868
1288
|
// Preserve leading slash if present in the original pattern.
|
|
@@ -871,13 +1291,13 @@ function ensureAnchorTrackable(projectRoot) {
|
|
|
871
1291
|
// Replace the single directory-ignore line with the contents-ignore form
|
|
872
1292
|
// plus the re-include for project.json. Git evaluates patterns in order
|
|
873
1293
|
// and re-includes override later, so the order matters: `*` first, then `!`.
|
|
874
|
-
lines.splice(lineNum - 1, 1, `${prefix}.
|
|
1294
|
+
lines.splice(lineNum - 1, 1, `${prefix}.greprag/*`, `!${prefix}.greprag/project.json`);
|
|
875
1295
|
fs.writeFileSync(gitignorePath, lines.join('\n'));
|
|
876
|
-
return `Patched ${gitignoreRel}: \`${trimmed}\`
|
|
1296
|
+
return `Patched ${gitignoreRel}: \`${trimmed}\` -> \`${prefix}.greprag/*\` + \`!${prefix}.greprag/project.json\` (anchor now trackable)`;
|
|
877
1297
|
}
|
|
878
1298
|
// Unrecognized pattern — don't risk breaking the user's other intent. Tell
|
|
879
1299
|
// them what to do manually.
|
|
880
|
-
return `WARNING: .
|
|
1300
|
+
return `WARNING: .greprag/project.json is ignored by \`${pattern}\` in ${gitignoreRel}. The anchor won't survive a fresh clone. Add \`!.greprag/project.json\` after that line manually, or run \`git add -f .greprag/project.json\` to force-track it once.`;
|
|
881
1301
|
}
|
|
882
1302
|
// -- Input ------------------------------------------------------------------
|
|
883
1303
|
/** Recursively copy a skill directory from the package bundle into
|