greprag 5.21.0 → 5.22.1

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,143 @@ 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 --tenant-id <your-handle>',
110
+ ' greprag init --claude --tenant-id <your-handle>',
111
+ ' greprag init --opencode --tenant-id <your-handle>',
112
+ ].join('\n');
113
+ }
114
+ function normalizeHandle(raw) {
115
+ return raw.trim().toLowerCase().replace(/@greprag\.com$/i, '');
116
+ }
117
+ function validateHandle(handle) {
118
+ return /^[a-z0-9-]{3,32}$/.test(handle);
119
+ }
120
+ async function promptForHandle() {
121
+ const handle = normalizeHandle(await prompt(' Choose your public GrepRAG handle, e.g. tanya -> tanya@greprag.com: '));
122
+ if (!validateHandle(handle)) {
123
+ console.error(' Error: public handle must be 3-32 chars: lowercase letters, numbers, and hyphens.');
124
+ process.exit(1);
125
+ }
126
+ const answer = (await prompt(` This will create/use public handle ${handle}@greprag.com. Continue? (Y/n): `)).trim().toLowerCase();
127
+ if (answer && answer !== 'y' && answer !== 'yes') {
128
+ console.error(' Setup cancelled before provisioning a public handle.');
129
+ process.exit(1);
130
+ }
131
+ return handle;
132
+ }
133
+ async function resolveProvisionHandle(opts) {
134
+ if (opts.tenantId) {
135
+ const handle = normalizeHandle(opts.tenantId);
136
+ if (!validateHandle(handle)) {
137
+ console.error(' Error: --tenant-id must be 3-32 chars: lowercase letters, numbers, and hyphens.');
138
+ process.exit(1);
139
+ }
140
+ console.log(` Public handle: ${handle}@greprag.com`);
141
+ return handle;
142
+ }
143
+ return promptForHandle();
144
+ }
145
+ async function chooseInitTarget() {
146
+ const detections = detectAgentTargets();
147
+ if (!process.stdin.isTTY) {
148
+ if (detections.length === 1)
149
+ return detections[0].target;
150
+ console.error(' Error: greprag init could not safely choose an agent target in non-interactive mode.');
151
+ if (detections.length > 1) {
152
+ console.error(' Detected more than one possible agent:');
153
+ for (const d of detections) {
154
+ console.error(` - ${describeTarget(d.target)}: ${d.evidence.join('; ')}`);
155
+ }
156
+ }
157
+ console.error(explicitInitHint());
158
+ process.exit(1);
159
+ }
160
+ if (detections.length === 1) {
161
+ const detected = detections[0];
162
+ console.log(`\n Detected ${describeTarget(detected.target)} (${detected.evidence.join('; ')}).`);
163
+ const answer = (await prompt(` Configure GrepRAG for ${describeTarget(detected.target)}? (Y/n): `)).trim().toLowerCase();
164
+ if (!answer || answer === 'y' || answer === 'yes')
165
+ return detected.target;
166
+ }
167
+ else if (detections.length > 1) {
168
+ console.log('\n Multiple agent environments look available:');
169
+ for (const d of detections) {
170
+ console.log(` - ${describeTarget(d.target)}: ${d.evidence.join('; ')}`);
171
+ }
172
+ }
173
+ console.log('\n Which agent are you setting up GrepRAG for?');
174
+ console.log(' 1. Codex');
175
+ console.log(' 2. Claude Code');
176
+ console.log(' 3. OpenCode');
177
+ const answer = (await prompt(' Choose 1, 2, or 3: ')).trim().toLowerCase();
178
+ if (answer === '1' || answer === 'codex')
179
+ return 'codex';
180
+ if (answer === '2' || answer === 'claude' || answer === 'claude code')
181
+ return 'claude';
182
+ if (answer === '3' || answer === 'opencode' || answer === 'open code')
183
+ return 'opencode';
184
+ console.error(' Error: no agent target selected.');
185
+ console.error(explicitInitHint());
186
+ process.exit(1);
187
+ }
51
188
  async function runInit(opts) {
52
189
  // --all: run the FULL standard init for cwd first (API key, hooks, anchor,
53
190
  // global anchor, skill, chip-spawn doc, CLAUDE.md patch), THEN bulk-register
@@ -56,35 +193,40 @@ async function runInit(opts) {
56
193
  // Reuse the existing GREPRAG_API_KEY from settings if one is already there,
57
194
  // so re-runs don't churn the tenant's key via the provision path.
58
195
  if (opts.all) {
59
- let apiKey = opts.apiKey;
60
- if (!apiKey) {
61
- apiKey = readSettings(getSettingsPath()).env?.GREPRAG_API_KEY;
62
- }
196
+ let apiKey = opts.apiKey || readSharedApiKey();
63
197
  await runInit({ ...opts, all: false, apiKey });
64
198
  return runInitAll(opts.root);
65
199
  }
66
- // --global: create ~/.claude/project.json only — no API key, no hooks.
200
+ // --global: create ~/.greprag/project.json only — no API key, no hooks.
67
201
  if (opts.global) {
68
202
  return runGlobalInit(opts.name);
69
203
  }
204
+ const explicitTargets = [opts.claude, opts.codex, opts.opencode].filter(Boolean).length;
205
+ if (explicitTargets > 1) {
206
+ console.error(' Error: choose only one target: --claude, --codex, or --opencode.');
207
+ process.exit(1);
208
+ }
70
209
  // --opencode: configure for OpenCode instead of Claude Code.
71
210
  if (opts.opencode) {
72
211
  return runOpenCodeInit(opts);
73
212
  }
213
+ // --codex: configure Codex lifecycle hooks.
214
+ if (opts.codex) {
215
+ return runCodexInit(opts);
216
+ }
217
+ if (!opts.claude) {
218
+ const target = await chooseInitTarget();
219
+ if (target === 'codex')
220
+ return runCodexInit(opts);
221
+ if (target === 'opencode')
222
+ return runOpenCodeInit(opts);
223
+ }
74
224
  console.log('\n greprag init — Setting up agent memory for Claude Code\n');
75
225
  // Step 1: Get API key
76
- let apiKey = opts.apiKey;
226
+ let apiKey = opts.apiKey || readSharedApiKey();
77
227
  if (!apiKey) {
78
- let tenantId = opts.tenantId;
79
- if (!tenantId) {
80
- tenantId = await prompt(' Enter a tenant ID (alphanumeric + hyphens, 3-32 chars): ');
81
- }
82
- tenantId = tenantId.trim().toLowerCase();
83
- if (!tenantId) {
84
- console.error(' Error: tenant ID is required');
85
- process.exit(1);
86
- }
87
- console.log(` Provisioning API key for tenant "${tenantId}"...`);
228
+ const tenantId = await resolveProvisionHandle(opts);
229
+ console.log(` Provisioning API key for public handle ${tenantId}@greprag.com...`);
88
230
  const result = await provision(tenantId);
89
231
  if (!result.ok) {
90
232
  console.error(` Error: ${result.error}`);
@@ -101,21 +243,7 @@ async function runInit(opts) {
101
243
  process.exit(1);
102
244
  }
103
245
  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
- }
246
+ await cacheIdentity(apiKey);
119
247
  // Step 3: Update settings.json
120
248
  console.log(' Configuring Claude Code settings...');
121
249
  const settingsPath = getSettingsPath();
@@ -133,7 +261,7 @@ async function runInit(opts) {
133
261
  changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — derived from git history`);
134
262
  }
135
263
  else {
136
- changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .claude/project.json`);
264
+ changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .greprag/project.json`);
137
265
  // Only patch gitignore when the file is load-bearing for identity.
138
266
  const gitignoreResult = ensureAnchorTrackable(process.cwd());
139
267
  if (gitignoreResult)
@@ -155,25 +283,19 @@ async function runInit(opts) {
155
283
  // with capabilities the operator may not want. Postinstall mirrors this
156
284
  // policy (sync greprag/ always, refresh-only for opted-in advisors).
157
285
  // 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';
286
+ const skillRoot = getSkillRoot();
161
287
  const optionalSkills = [];
162
288
  if (fs.existsSync(skillRoot)) {
289
+ const installed = installCoreSkill('claude');
290
+ if (installed)
291
+ changes.push(`Skill: installed → ${installed}`);
163
292
  for (const entry of fs.readdirSync(skillRoot, { withFileTypes: true })) {
164
293
  if (!entry.isDirectory())
165
294
  continue;
166
295
  if (entry.name === 'templates')
167
296
  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 {
297
+ if (entry.name !== 'greprag')
175
298
  optionalSkills.push(entry.name);
176
- }
177
299
  }
178
300
  }
179
301
  // Step 6b: Install the chip-spawn doc to ~/.claude/docs/chip-spawn.md. The
@@ -219,25 +341,17 @@ async function runInit(opts) {
219
341
  * global plugins directory.
220
342
  *
221
343
  * 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). */
344
+ * `readAnchor` checks `.greprag/project.json` first, so a single shared
345
+ * anchor is used by all clients and memory accumulates under one project_id.
346
+ * A user who initialized opencode first and Claude Code second on the same
347
+ * repo gets the same project_id from both inits (idempotent). */
226
348
  async function runOpenCodeInit(opts) {
227
349
  console.log('\n greprag init — Setting up agent memory for OpenCode\n');
228
350
  // Step 1: Get API key (same provision path as Claude Code init)
229
- let apiKey = opts.apiKey;
351
+ let apiKey = opts.apiKey || readSharedApiKey();
230
352
  if (!apiKey) {
231
- let tenantId = opts.tenantId;
232
- if (!tenantId) {
233
- tenantId = await prompt(' Enter a tenant ID (alphanumeric + hyphens, 3-32 chars): ');
234
- }
235
- tenantId = tenantId.trim().toLowerCase();
236
- if (!tenantId) {
237
- console.error(' Error: tenant ID is required');
238
- process.exit(1);
239
- }
240
- console.log(` Provisioning API key for tenant "${tenantId}"...`);
353
+ const tenantId = await resolveProvisionHandle(opts);
354
+ console.log(` Provisioning API key for public handle ${tenantId}@greprag.com...`);
241
355
  const result = await provision(tenantId);
242
356
  if (!result.ok) {
243
357
  console.error(` Error: ${result.error}`);
@@ -254,14 +368,15 @@ async function runOpenCodeInit(opts) {
254
368
  process.exit(1);
255
369
  }
256
370
  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)
371
+ await cacheIdentity(apiKey);
372
+ writeGrepragEnv(apiKey);
373
+ // Step 3: Ensure env vars are also set in ~/.claude/settings.json for
374
+ // backwards compatibility with older OpenCode plugin installs.
260
375
  const settingsPath = getSettingsPath();
261
376
  const settings = readSettings(settingsPath);
262
377
  if (!settings.env)
263
378
  settings.env = {};
264
- const envChanges = [];
379
+ const envChanges = ['Wrote ~/.greprag/.env'];
265
380
  if (!settings.env.GREPRAG_API_KEY) {
266
381
  settings.env.GREPRAG_API_KEY = apiKey;
267
382
  envChanges.push('Set GREPRAG_API_KEY');
@@ -274,7 +389,7 @@ async function runOpenCodeInit(opts) {
274
389
  writeSettings(settingsPath, settings);
275
390
  }
276
391
  // Step 4: Resolve the canonical project anchor via the same cascade Claude
277
- // Code uses. Idempotent — if `.claude/project.json` already exists, this
392
+ // Code uses. Idempotent — if `.greprag/project.json` already exists, this
278
393
  // returns the existing anchor unchanged. In a git repo with commits, the
279
394
  // identity is derived from the root commit SHA and the file holds only
280
395
  // settings (no project_id). The opencode plugin's readAnchor reads this
@@ -334,11 +449,110 @@ async function runOpenCodeInit(opts) {
334
449
  console.log(` - Project anchor: ${anchor.anchorPath} (source: ${anchor.source})`);
335
450
  if (pluginInstalled)
336
451
  console.log(` - Plugin: ${pluginDest}`);
452
+ console.log(` - Shared env: ${getGrepragEnvPath()}`);
337
453
  console.log(' OpenCode will load the plugin automatically on next session start.');
338
454
  console.log(' The /greprag skill is also available for on-demand briefings.\n');
339
455
  }
456
+ /** greprag init --codex
457
+ * Configures GrepRAG for Codex lifecycle hooks. Codex does not read
458
+ * ~/.claude/settings.json env, so this path writes ~/.greprag/.env and a
459
+ * user-level ~/.codex/hooks.json. The hooks still call the same greprag-hook
460
+ * binary; codex-* subcommands only adapt Codex's prompt/Stop payload shape. */
461
+ async function runCodexInit(opts) {
462
+ console.log('\n greprag init — Setting up agent memory for Codex\n');
463
+ let apiKey = opts.apiKey || readSharedApiKey();
464
+ if (!apiKey) {
465
+ const tenantId = await resolveProvisionHandle(opts);
466
+ console.log(` Provisioning API key for public handle ${tenantId}@greprag.com...`);
467
+ const result = await provision(tenantId);
468
+ if (!result.ok) {
469
+ console.error(` Error: ${result.error}`);
470
+ process.exit(1);
471
+ }
472
+ apiKey = result.apiKey;
473
+ console.log(` API key created: ${apiKey.slice(0, 17)}...`);
474
+ }
475
+ console.log(' Validating API key...');
476
+ const valid = await validateKey(apiKey);
477
+ if (!valid) {
478
+ console.error(' Error: API key validation failed.');
479
+ process.exit(1);
480
+ }
481
+ console.log(' Key validated against api.greprag.com');
482
+ const changes = [];
483
+ writeGrepragEnv(apiKey);
484
+ changes.push('Wrote ~/.greprag/.env for Codex hooks');
485
+ await cacheIdentity(apiKey);
486
+ const anchor = (0, project_anchor_1.ensureAnchor)(process.cwd());
487
+ if (anchor.source === 'git') {
488
+ changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — derived from git history`);
489
+ }
490
+ else {
491
+ changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .greprag/project.json`);
492
+ const gitignoreResult = ensureAnchorTrackable(process.cwd());
493
+ if (gitignoreResult)
494
+ changes.push(gitignoreResult);
495
+ }
496
+ const globalAnchor = (0, project_anchor_1.ensureGlobalAnchor)();
497
+ changes.push(`Global anchor: ${globalAnchor.projectName} (${globalAnchor.projectId.slice(0, 8)})`);
498
+ writeProjectPath(anchor.projectName, process.cwd());
499
+ changes.push(`Registered repo path for ${anchor.projectName} → ~/.greprag/projects.json`);
500
+ const codexSkill = installCoreSkill('codex');
501
+ if (codexSkill)
502
+ changes.push(`Skill: installed → ${codexSkill}`);
503
+ try {
504
+ const res = await fetch(`${API_URL}/v1/inbox/projects/register`, {
505
+ method: 'POST',
506
+ headers: {
507
+ 'Authorization': `Bearer ${apiKey}`,
508
+ 'Content-Type': 'application/json',
509
+ },
510
+ body: JSON.stringify({ project_id: anchor.projectId, project_name: anchor.projectName }),
511
+ });
512
+ const reg = await res.json();
513
+ changes.push(reg.ok ? 'Project registered for inbox addressing' : `Inbox registration: ${reg.error || 'failed'}`);
514
+ }
515
+ catch {
516
+ changes.push('Inbox registration skipped (network)');
517
+ }
518
+ const hooksPath = getCodexHooksPath();
519
+ const hooks = readCodexHooks(hooksPath);
520
+ const hookChanges = applyCodexHooks(hooks);
521
+ writeCodexHooks(hooksPath, hooks);
522
+ changes.push(...hookChanges);
523
+ const startup = (0, codex_1.codexStartupInfo)();
524
+ if (startup.installed) {
525
+ changes.push(`Codex live inbox startup watcher already installed (${startup.path})`);
526
+ }
527
+ else if (opts.installWatcher) {
528
+ (0, codex_1.installCodexStartup)();
529
+ changes.push(`Codex live inbox startup watcher installed (${startup.path})`);
530
+ }
531
+ else if (process.stdin.isTTY) {
532
+ const answer = (await prompt(' Install Codex live inbox watcher at login? (Y/n): ')).trim().toLowerCase();
533
+ if (!answer || answer === 'y' || answer === 'yes') {
534
+ (0, codex_1.installCodexStartup)();
535
+ changes.push(`Codex live inbox startup watcher installed (${startup.path})`);
536
+ }
537
+ else {
538
+ changes.push('Codex live inbox startup watcher skipped (run `greprag codex startup install` later)');
539
+ }
540
+ }
541
+ else {
542
+ changes.push('Codex live inbox startup watcher not installed (run `greprag codex startup install`)');
543
+ }
544
+ console.log('\n Setup complete!\n');
545
+ for (const change of changes) {
546
+ console.log(` - ${change}`);
547
+ }
548
+ console.log(`\n Codex hooks file: ${hooksPath}`);
549
+ console.log(` Project anchor: ${anchor.anchorPath}`);
550
+ console.log(' In Codex Desktop, open Settings -> Settings -> Hooks and trust the 6 GrepRAG hook definitions.');
551
+ console.log(' For live inbox push, run: greprag codex startup install');
552
+ console.log(' Memory hooks will activate on your next Codex session.\n');
553
+ }
340
554
  /** greprag init --global
341
- * Creates ~/.claude/project.json with a stable UUID.
555
+ * Creates ~/.greprag/project.json with a stable UUID.
342
556
  * Designed for Cowork / any agent session whose cwd has no repo-level anchor.
343
557
  * Idempotent — safe to run again; returns the existing anchor if already present. */
344
558
  async function runGlobalInit(name) {
@@ -356,7 +570,7 @@ async function runGlobalInit(name) {
356
570
  }
357
571
  /** greprag init --all
358
572
  * 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
573
+ * of cwd). Per-repo work: ensureAnchor (writes .greprag/project.json or relies
360
574
  * on the git-derived cascade), writeProjectPath (~/.greprag/projects.json),
361
575
  * inbox registration (so `send --to <name>@.../<project-name>` resolves), and
362
576
  * gitignore patch when the anchor file is load-bearing for identity.
@@ -367,11 +581,9 @@ async function runGlobalInit(name) {
367
581
  async function runInitAll(rootPath) {
368
582
  console.log('\n greprag init --all — Register every git repo for inbox addressing\n');
369
583
  // 1. Existing API key required.
370
- const settingsPath = getSettingsPath();
371
- const settings = readSettings(settingsPath);
372
- const apiKey = settings.env?.GREPRAG_API_KEY;
584
+ const apiKey = readSharedApiKey();
373
585
  if (!apiKey) {
374
- console.error(' Error: no GREPRAG_API_KEY in ~/.claude/settings.json.');
586
+ console.error(' Error: no GREPRAG_API_KEY in ~/.greprag/.env or ~/.claude/settings.json.');
375
587
  console.error(' Run `greprag init` once to set up the key + hooks, then re-run init --all.');
376
588
  process.exit(1);
377
589
  }
@@ -587,6 +799,10 @@ function getSettingsPath() {
587
799
  const home = process.env.HOME || process.env.USERPROFILE || '';
588
800
  return path.join(home, '.claude', 'settings.json');
589
801
  }
802
+ function getGrepragEnvPath() {
803
+ const home = process.env.HOME || process.env.USERPROFILE || '';
804
+ return path.join(home, '.greprag', '.env');
805
+ }
590
806
  function readSettings(settingsPath) {
591
807
  try {
592
808
  const raw = fs.readFileSync(settingsPath, 'utf-8');
@@ -603,6 +819,221 @@ function writeSettings(settingsPath, settings) {
603
819
  }
604
820
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
605
821
  }
822
+ function writeGrepragEnv(apiKey) {
823
+ const file = getGrepragEnvPath();
824
+ const dir = path.dirname(file);
825
+ let existing = {};
826
+ try {
827
+ const raw = fs.readFileSync(file, 'utf-8');
828
+ for (const line of raw.split(/\r?\n/)) {
829
+ const trimmed = line.trim();
830
+ if (!trimmed || trimmed.startsWith('#'))
831
+ continue;
832
+ const idx = trimmed.indexOf('=');
833
+ if (idx < 1)
834
+ continue;
835
+ existing[trimmed.slice(0, idx)] = trimmed.slice(idx + 1);
836
+ }
837
+ }
838
+ catch { /* missing — write fresh */ }
839
+ existing.GREPRAG_API_KEY = apiKey;
840
+ existing.MEMORY_HOOK_ENABLED = 'true';
841
+ if (!fs.existsSync(dir))
842
+ fs.mkdirSync(dir, { recursive: true });
843
+ const body = Object.entries(existing)
844
+ .map(([k, v]) => `${k}=${v}`)
845
+ .join('\n') + '\n';
846
+ fs.writeFileSync(file, body);
847
+ }
848
+ function readEnvFile(file) {
849
+ const out = {};
850
+ try {
851
+ const raw = fs.readFileSync(file, 'utf-8');
852
+ for (const line of raw.split(/\r?\n/)) {
853
+ const trimmed = line.trim();
854
+ if (!trimmed || trimmed.startsWith('#'))
855
+ continue;
856
+ const idx = trimmed.indexOf('=');
857
+ if (idx < 1)
858
+ continue;
859
+ let value = trimmed.slice(idx + 1).trim();
860
+ if ((value.startsWith('"') && value.endsWith('"')) ||
861
+ (value.startsWith("'") && value.endsWith("'"))) {
862
+ value = value.slice(1, -1);
863
+ }
864
+ out[trimmed.slice(0, idx).trim()] = value;
865
+ }
866
+ }
867
+ catch { /* missing env file */ }
868
+ return out;
869
+ }
870
+ function readSharedApiKey() {
871
+ return process.env.GREPRAG_API_KEY
872
+ || readEnvFile(getGrepragEnvPath()).GREPRAG_API_KEY
873
+ || readSettings(getSettingsPath()).env?.GREPRAG_API_KEY;
874
+ }
875
+ async function cacheIdentity(apiKey) {
876
+ try {
877
+ const { fetchIdentity, writeCache } = await Promise.resolve().then(() => __importStar(require('./identity')));
878
+ const home = process.env.HOME || process.env.USERPROFILE || '';
879
+ const cachePath = path.join(home, '.greprag', 'identity.json');
880
+ const ident = await fetchIdentity('https://api.greprag.com', apiKey);
881
+ writeCache(cachePath, ident);
882
+ if (ident.handle)
883
+ console.log(` Identity: ${ident.handle}`);
884
+ }
885
+ catch (e) {
886
+ console.log(` Identity cache deferred (${e.message})`);
887
+ }
888
+ }
889
+ function getSkillRoot() {
890
+ return path.join(__dirname, '../../skill');
891
+ }
892
+ function skillTargetRoot(target) {
893
+ const dir = target === 'codex' ? '.codex' : '.claude';
894
+ return path.join(os.homedir(), dir, 'skills');
895
+ }
896
+ function installCoreSkill(target) {
897
+ const srcDir = path.join(getSkillRoot(), 'greprag');
898
+ if (!fs.existsSync(srcDir))
899
+ return null;
900
+ const destDir = path.join(skillTargetRoot(target), 'greprag');
901
+ copySkillDir(srcDir, destDir);
902
+ return destDir;
903
+ }
904
+ function getCodexHooksPath() {
905
+ const home = process.env.HOME || process.env.USERPROFILE || '';
906
+ return path.join(home, '.codex', 'hooks.json');
907
+ }
908
+ function readCodexHooks(hooksPath) {
909
+ try {
910
+ const raw = fs.readFileSync(hooksPath, 'utf-8');
911
+ return JSON.parse(raw);
912
+ }
913
+ catch {
914
+ return {};
915
+ }
916
+ }
917
+ function writeCodexHooks(hooksPath, hooks) {
918
+ const dir = path.dirname(hooksPath);
919
+ if (!fs.existsSync(dir))
920
+ fs.mkdirSync(dir, { recursive: true });
921
+ fs.writeFileSync(hooksPath, JSON.stringify(hooks, null, 2) + '\n');
922
+ }
923
+ function applyCodexHooks(config) {
924
+ const changes = [];
925
+ if (!config.hooks)
926
+ config.hooks = {};
927
+ const recapHook = {
928
+ matcher: 'startup|resume|clear|compact',
929
+ hooks: [{
930
+ type: 'command',
931
+ command: 'greprag-hook recap',
932
+ timeout: 3,
933
+ statusMessage: 'Loading GrepRAG memory',
934
+ }],
935
+ };
936
+ if (!hasGrepragHookWithMatcher(config.hooks.SessionStart, 'recap', recapHook.matcher)) {
937
+ if (!config.hooks.SessionStart)
938
+ config.hooks.SessionStart = [];
939
+ config.hooks.SessionStart.push(recapHook);
940
+ changes.push('Added Codex SessionStart hook (memory recap)');
941
+ }
942
+ else {
943
+ changes.push('Codex SessionStart recap hook already configured (skipped)');
944
+ }
945
+ const sessionHook = {
946
+ matcher: 'startup|resume|clear|compact',
947
+ hooks: [{
948
+ type: 'command',
949
+ command: 'greprag-hook session-id',
950
+ timeout: 3,
951
+ statusMessage: 'Loading GrepRAG session id',
952
+ }],
953
+ };
954
+ if (!hasGrepragHookWithMatcher(config.hooks.SessionStart, 'session-id', sessionHook.matcher)) {
955
+ if (!config.hooks.SessionStart)
956
+ config.hooks.SessionStart = [];
957
+ config.hooks.SessionStart.push(sessionHook);
958
+ changes.push('Added Codex SessionStart hook (session-id awareness)');
959
+ }
960
+ else {
961
+ changes.push('Codex SessionStart session-id hook already configured (skipped)');
962
+ }
963
+ const notifyHook = {
964
+ matcher: '',
965
+ hooks: [{
966
+ type: 'command',
967
+ command: 'greprag-hook codex-notify',
968
+ timeout: 3,
969
+ statusMessage: 'Checking GrepRAG inbox',
970
+ }],
971
+ };
972
+ if (!hasGrepragHook(config.hooks.UserPromptSubmit, 'codex-notify')) {
973
+ if (!config.hooks.UserPromptSubmit)
974
+ config.hooks.UserPromptSubmit = [];
975
+ config.hooks.UserPromptSubmit.push(notifyHook);
976
+ changes.push('Added Codex UserPromptSubmit hook (prompt cache + inbox steering)');
977
+ }
978
+ else {
979
+ changes.push('Codex UserPromptSubmit hook already configured (skipped)');
980
+ }
981
+ const inboxHook = {
982
+ matcher: '',
983
+ hooks: [{
984
+ type: 'command',
985
+ command: 'greprag-hook codex-inbox',
986
+ timeout: 3,
987
+ statusMessage: 'Checking GrepRAG inbox',
988
+ }],
989
+ };
990
+ if (!hasGrepragHook(config.hooks.PostToolUse, 'codex-inbox')) {
991
+ if (!config.hooks.PostToolUse)
992
+ config.hooks.PostToolUse = [];
993
+ config.hooks.PostToolUse.push(inboxHook);
994
+ changes.push('Added Codex PostToolUse hook (inbox steering)');
995
+ }
996
+ else {
997
+ changes.push('Codex PostToolUse inbox hook already configured (skipped)');
998
+ }
999
+ const storeHook = {
1000
+ matcher: '',
1001
+ hooks: [{
1002
+ type: 'command',
1003
+ command: 'greprag-hook codex-store',
1004
+ timeout: 10,
1005
+ statusMessage: 'Storing GrepRAG turn',
1006
+ }],
1007
+ };
1008
+ if (!hasGrepragHook(config.hooks.Stop, 'codex-store')) {
1009
+ if (!config.hooks.Stop)
1010
+ config.hooks.Stop = [];
1011
+ config.hooks.Stop.push(storeHook);
1012
+ changes.push('Added Codex Stop hook (memory store)');
1013
+ }
1014
+ else {
1015
+ changes.push('Codex Stop hook already configured (skipped)');
1016
+ }
1017
+ const postCompactHook = {
1018
+ matcher: 'manual|auto',
1019
+ hooks: [{
1020
+ type: 'command',
1021
+ command: 'greprag-hook session-id',
1022
+ timeout: 3,
1023
+ statusMessage: 'Restoring GrepRAG session id',
1024
+ }],
1025
+ };
1026
+ if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'session-id', postCompactHook.matcher)) {
1027
+ if (!config.hooks.PostCompact)
1028
+ config.hooks.PostCompact = [];
1029
+ config.hooks.PostCompact.push(postCompactHook);
1030
+ changes.push('Added Codex PostCompact hook (session-id awareness)');
1031
+ }
1032
+ else {
1033
+ changes.push('Codex PostCompact session-id hook already configured (skipped)');
1034
+ }
1035
+ return changes;
1036
+ }
606
1037
  function applySettings(settings, apiKey) {
607
1038
  const changes = [];
608
1039
  // Env vars
@@ -812,14 +1243,14 @@ function patchGlobalClaudeMd() {
812
1243
  return true;
813
1244
  }
814
1245
  // -- Anchor trackability ----------------------------------------------------
815
- /** When a repo gitignores `.claude/`, the anchor file we just wrote won't
1246
+ /** When a repo gitignores `.greprag/`, the anchor file we just wrote won't
816
1247
  * survive a fresh clone or worktree switch — the deterministic-hash fallback
817
1248
  * will silently take over, splitting the project_id and breaking inbox
818
1249
  * addressing. Detect that case at init time and patch the gitignore so the
819
1250
  * anchor is durably tracked.
820
1251
  *
821
- * Common patterns we handle: `.claude/`, `.claude`, `/.claude/`, `/.claude`.
822
- * Each is rewritten to its `*`-contents form so a `!.claude/project.json`
1252
+ * Common patterns we handle: `.greprag/`, `.greprag`, `/.greprag/`, `/.greprag`.
1253
+ * Each is rewritten to its `*`-contents form so a `!.greprag/project.json`
823
1254
  * re-include works (git won't descend into an excluded directory, so the
824
1255
  * trailing-slash directory pattern must become a contents-only pattern).
825
1256
  *
@@ -838,7 +1269,7 @@ function ensureAnchorTrackable(projectRoot) {
838
1269
  // 1 when not ignored. Either non-zero exit means "no work to do."
839
1270
  let checkOutput;
840
1271
  try {
841
- checkOutput = (0, child_process_1.execSync)('git check-ignore -v .claude/project.json', {
1272
+ checkOutput = (0, child_process_1.execSync)('git check-ignore -v .greprag/project.json', {
842
1273
  cwd: projectRoot,
843
1274
  encoding: 'utf-8',
844
1275
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -862,7 +1293,7 @@ function ensureAnchorTrackable(projectRoot) {
862
1293
  return null;
863
1294
  const trimmed = pattern.trim();
864
1295
  const dirPatterns = new Set([
865
- '.claude/', '.claude', '/.claude/', '/.claude',
1296
+ '.greprag/', '.greprag', '/.greprag/', '/.greprag',
866
1297
  ]);
867
1298
  if (dirPatterns.has(trimmed)) {
868
1299
  // Preserve leading slash if present in the original pattern.
@@ -871,13 +1302,13 @@ function ensureAnchorTrackable(projectRoot) {
871
1302
  // Replace the single directory-ignore line with the contents-ignore form
872
1303
  // plus the re-include for project.json. Git evaluates patterns in order
873
1304
  // and re-includes override later, so the order matters: `*` first, then `!`.
874
- lines.splice(lineNum - 1, 1, `${prefix}.claude/*`, `!${prefix}.claude/project.json`);
1305
+ lines.splice(lineNum - 1, 1, `${prefix}.greprag/*`, `!${prefix}.greprag/project.json`);
875
1306
  fs.writeFileSync(gitignorePath, lines.join('\n'));
876
- return `Patched ${gitignoreRel}: \`${trimmed}\` \`${prefix}.claude/*\` + \`!${prefix}.claude/project.json\` (anchor now trackable)`;
1307
+ return `Patched ${gitignoreRel}: \`${trimmed}\` -> \`${prefix}.greprag/*\` + \`!${prefix}.greprag/project.json\` (anchor now trackable)`;
877
1308
  }
878
1309
  // Unrecognized pattern — don't risk breaking the user's other intent. Tell
879
1310
  // 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.`;
1311
+ 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
1312
  }
882
1313
  // -- Input ------------------------------------------------------------------
883
1314
  /** Recursively copy a skill directory from the package bundle into