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.
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
- /** greprag init — configure Claude Code for GrepRAG memory.
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 ~/.claude/settings.json with env vars + hooks */
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 ~/.claude/project.json only — no API key, no hooks.
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
- // Cache identity (numeric handle + aliases) so `greprag status` can return
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 .claude/project.json`);
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 = path.join(__dirname, '../../skill');
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 === CORE_SKILL) {
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 `.claude/project.json` first, so a single anchor in
223
- * `.claude/` is shared between both clients and memory accumulates under one
224
- * project_id. A user who initialized opencode first and Claude Code second
225
- * on the same repo gets the same project_id from both inits (idempotent). */
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
- // Step 3: Ensure env vars are set in ~/.claude/settings.json
258
- // (shared env between Claude Code and OpenCode — the plugin reads this
259
- // file via loadClaudeEnv on boot to get GREPRAG_API_KEY + MEMORY_HOOK_ENABLED)
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 `.claude/project.json` already exists, this
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 ~/.claude/project.json with a stable UUID.
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 .claude/project.json or relies
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 settingsPath = getSettingsPath();
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 `.claude/`, the anchor file we just wrote won't
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: `.claude/`, `.claude`, `/.claude/`, `/.claude`.
822
- * Each is rewritten to its `*`-contents form so a `!.claude/project.json`
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 .claude/project.json', {
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
- '.claude/', '.claude', '/.claude/', '/.claude',
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}.claude/*`, `!${prefix}.claude/project.json`);
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}\` \`${prefix}.claude/*\` + \`!${prefix}.claude/project.json\` (anchor now trackable)`;
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: .claude/project.json is ignored by \`${pattern}\` in ${gitignoreRel}. The anchor won't survive a fresh clone. Add \`!.claude/project.json\` after that line manually, or run \`git add -f .claude/project.json\` to force-track it once.`;
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