greprag 5.74.21 → 5.76.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/dist/commands/arm-reminder.js +4 -2
- package/dist/commands/codex.js +2 -7
- package/dist/commands/grok-spawn.js +208 -0
- package/dist/commands/inbox-drain.js +36 -6
- package/dist/commands/inbox-primer-reminder.js +16 -11
- package/dist/commands/inbox-watch-supervisor.js +17 -7
- package/dist/commands/inbox-watch.js +67 -31
- package/dist/commands/init.js +173 -4
- package/dist/commands/load-primer-reminder.js +8 -1
- package/dist/commands/load.js +4 -0
- package/dist/commands/os-primer-reminder.js +2 -1
- package/dist/commands/pipe-wrap.js +6 -5
- package/dist/commands/search-guard.js +2 -1
- package/dist/commands/status.js +29 -1
- package/dist/commands/watcher-registry.js +67 -22
- package/dist/grok-session.js +125 -0
- package/dist/harness.js +6 -0
- package/dist/hook-once.js +67 -0
- package/dist/hook-runtime.js +43 -0
- package/dist/hook.js +86 -19
- package/dist/index.js +62 -22
- package/dist/opencode-plugin.bundle.js +21 -8
- package/dist/session-id.js +59 -8
- package/package.json +3 -2
- package/skill/greprag/SKILL.md +41 -2
- package/skill/templates/chip-spawn.md +2 -0
- package/skill/templates/grok-chip-spawn.md +101 -0
|
@@ -50,14 +50,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
50
50
|
})();
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
52
|
exports.CONSUMER_GONE_EXIT_CODE = void 0;
|
|
53
|
+
exports.resolveWatchPlatform = resolveWatchPlatform;
|
|
53
54
|
exports.getConfig = getConfig;
|
|
54
55
|
exports.fmtDuration = fmtDuration;
|
|
55
56
|
exports.parseEventBlock = parseEventBlock;
|
|
56
57
|
exports.withTruncatedSessions = withTruncatedSessions;
|
|
58
|
+
exports.isWatchQuiet = isWatchQuiet;
|
|
57
59
|
exports.runInboxWatch = runInboxWatch;
|
|
58
60
|
const fs = __importStar(require("fs"));
|
|
59
61
|
const path = __importStar(require("path"));
|
|
60
62
|
const session_id_1 = require("../session-id");
|
|
63
|
+
const harness_1 = require("../harness");
|
|
61
64
|
const mechanic_friction_1 = require("../mechanic-friction");
|
|
62
65
|
const inbox_watch_supervisor_1 = require("./inbox-watch-supervisor");
|
|
63
66
|
const API_URL_DEFAULT = 'https://api.greprag.com';
|
|
@@ -83,6 +86,14 @@ exports.CONSUMER_GONE_EXIT_CODE = 65;
|
|
|
83
86
|
// than waiting for the next real message that may never come. Env-overridable
|
|
84
87
|
// for tests (the idle path otherwise takes the full 30s to observe).
|
|
85
88
|
const CONSUMER_PROBE_MS = Number(process.env.GREPRAG_WATCH_PROBE_MS) || 30_000;
|
|
89
|
+
const WATCH_PLATFORMS = new Set(['claude-code', 'codex', 'opencode', 'grok']);
|
|
90
|
+
/** Platform tag for this watch attach. Explicit opt wins; else infer. */
|
|
91
|
+
function resolveWatchPlatform(explicit) {
|
|
92
|
+
if (explicit && WATCH_PLATFORMS.has(explicit))
|
|
93
|
+
return explicit;
|
|
94
|
+
const h = (0, harness_1.inferCurrentHarness)();
|
|
95
|
+
return (h && WATCH_PLATFORMS.has(h)) ? h : undefined;
|
|
96
|
+
}
|
|
86
97
|
// -- Config (mirrors the loader in index.ts/discover.ts) -------------------
|
|
87
98
|
function loadEnvFile(filePath) {
|
|
88
99
|
try {
|
|
@@ -166,9 +177,9 @@ function parseEventBlock(block) {
|
|
|
166
177
|
return null;
|
|
167
178
|
return { event, id, data: dataLines.join('\n') };
|
|
168
179
|
}
|
|
169
|
-
/** Truncate every session_id field on a StreamMessage to its
|
|
170
|
-
* Used for both the JSON --json line and
|
|
171
|
-
* Exported for tests. adr: adr/session-id-awareness.md */
|
|
180
|
+
/** Truncate every session_id field on a StreamMessage to its short form
|
|
181
|
+
* (8-hex, or 16-hex for UUIDv7). Used for both the JSON --json line and
|
|
182
|
+
* the pretty-printed display. Exported for tests. adr: adr/session-id-awareness.md */
|
|
172
183
|
function withTruncatedSessions(msg) {
|
|
173
184
|
const fromShort = (0, session_id_1.truncateSessionId)(msg.from.session_id ?? null);
|
|
174
185
|
const toShort = (0, session_id_1.truncateSessionId)(msg.to_session_id ?? null);
|
|
@@ -347,6 +358,9 @@ async function readStream(url, apiKey, signal, initialId, json, idleTimeoutMs, s
|
|
|
347
358
|
catch {
|
|
348
359
|
continue;
|
|
349
360
|
}
|
|
361
|
+
if (session && msg.to_session_id && !(0, session_id_1.sameSessionId)(session, msg.to_session_id)) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
350
364
|
const displayMsg = withTruncatedSessions(msg);
|
|
351
365
|
emitDmReplyDirective(displayMsg, session);
|
|
352
366
|
if (json)
|
|
@@ -362,14 +376,14 @@ async function readStream(url, apiKey, signal, initialId, json, idleTimeoutMs, s
|
|
|
362
376
|
const data = JSON.parse(ev.data || '{}');
|
|
363
377
|
if (typeof data.count === 'number' && data.count > 0) {
|
|
364
378
|
const plural = data.count === 1 ? '' : 's';
|
|
365
|
-
|
|
379
|
+
watchErr(`${LOG_PREFIX} replayed ${data.count} missed row${plural}, resuming live tail`);
|
|
366
380
|
}
|
|
367
381
|
}
|
|
368
382
|
catch { /* malformed control payload — ignore */ }
|
|
369
383
|
}
|
|
370
384
|
else if (ev.event === 'error') {
|
|
371
385
|
// Surface server-side error then let the loop reconnect.
|
|
372
|
-
|
|
386
|
+
watchErr(`${LOG_PREFIX} server error: ${ev.data}`);
|
|
373
387
|
return lastId;
|
|
374
388
|
}
|
|
375
389
|
// "open" event is informational — ignored at print time.
|
|
@@ -388,7 +402,7 @@ async function readStream(url, apiKey, signal, initialId, json, idleTimeoutMs, s
|
|
|
388
402
|
signal.removeEventListener('abort', onOuterAbort);
|
|
389
403
|
}
|
|
390
404
|
}
|
|
391
|
-
function buildUrl(apiUrl, project, session, since, receptionist, assistant, mechanic) {
|
|
405
|
+
function buildUrl(apiUrl, project, session, since, receptionist, assistant, mechanic, platform) {
|
|
392
406
|
const u = new URL(apiUrl.replace(/\/+$/, '') + '/v1/inbox/stream');
|
|
393
407
|
if (project)
|
|
394
408
|
u.searchParams.set('project', project);
|
|
@@ -402,6 +416,8 @@ function buildUrl(apiUrl, project, session, since, receptionist, assistant, mech
|
|
|
402
416
|
u.searchParams.set('role', 'mechanic');
|
|
403
417
|
else if (assistant)
|
|
404
418
|
u.searchParams.set('role', 'assistant');
|
|
419
|
+
if (platform)
|
|
420
|
+
u.searchParams.set('platform', platform);
|
|
405
421
|
return u.toString();
|
|
406
422
|
}
|
|
407
423
|
/** Public entry for `greprag inbox watch`. Dispatches by mode:
|
|
@@ -423,13 +439,30 @@ function buildUrl(apiUrl, project, session, since, receptionist, assistant, mech
|
|
|
423
439
|
* carries its stdout into chat is destroyed on reload, by design). See
|
|
424
440
|
* adr/monitor-resilience.md (2026-06-02 + 2026-06-03 entries).
|
|
425
441
|
* adr: adr/monitor-resilience.md */
|
|
442
|
+
function isWatchQuiet(opts) {
|
|
443
|
+
if (opts?.quiet)
|
|
444
|
+
return true;
|
|
445
|
+
if (process.env.GREPRAG_WATCH_QUIET === '1')
|
|
446
|
+
return true;
|
|
447
|
+
if (process.env.GROK_SESSION_ID)
|
|
448
|
+
return true;
|
|
449
|
+
return process.argv.includes('--quiet');
|
|
450
|
+
}
|
|
451
|
+
function watchErr(msg, opts) {
|
|
452
|
+
if (isWatchQuiet(opts))
|
|
453
|
+
return;
|
|
454
|
+
console.error(msg);
|
|
455
|
+
}
|
|
426
456
|
async function runInboxWatch(opts) {
|
|
457
|
+
const quiet = isWatchQuiet(opts);
|
|
458
|
+
if (quiet)
|
|
459
|
+
process.env.GREPRAG_WATCH_QUIET = '1';
|
|
427
460
|
const directMode = !!opts.signal || !!opts.noSupervise;
|
|
428
461
|
if (directMode)
|
|
429
|
-
return runWatchLoop(opts);
|
|
462
|
+
return runWatchLoop({ ...opts, quiet });
|
|
430
463
|
if (process.env[inbox_watch_supervisor_1.SUPERVISE_ENV] === '1') {
|
|
431
464
|
(0, inbox_watch_supervisor_1.installLastResortHandlers)();
|
|
432
|
-
return runWatchLoop(opts);
|
|
465
|
+
return runWatchLoop({ ...opts, quiet });
|
|
433
466
|
}
|
|
434
467
|
return (0, inbox_watch_supervisor_1.runSupervisor)();
|
|
435
468
|
}
|
|
@@ -446,7 +479,7 @@ async function runInboxWatch(opts) {
|
|
|
446
479
|
* the deleted `while true` wrapper's job, done right: the watcher notices its
|
|
447
480
|
* own consumer is gone instead of an external shell keeping it alive forever.
|
|
448
481
|
* adr: adr/monitor-resilience.md */
|
|
449
|
-
function bindToConsumer(onGone) {
|
|
482
|
+
function bindToConsumer(onGone, quiet = false) {
|
|
450
483
|
let gone = false;
|
|
451
484
|
const trip = () => {
|
|
452
485
|
if (gone)
|
|
@@ -457,26 +490,29 @@ function bindToConsumer(onGone) {
|
|
|
457
490
|
const onErr = () => trip();
|
|
458
491
|
process.stdout.on('error', onErr);
|
|
459
492
|
process.stderr.on('error', onErr);
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
probe.unref
|
|
493
|
+
// Claude Monitor keys notifications on stdout JSON; a NUL on stderr is
|
|
494
|
+
// invisible there. Grok monitor merges stderr into wake events, so the
|
|
495
|
+
// probe would ping the model every 30s and trip Grok's volume killer.
|
|
496
|
+
// adr: adr/grok-platform.md
|
|
497
|
+
let probe = null;
|
|
498
|
+
if (!quiet) {
|
|
499
|
+
probe = setInterval(() => {
|
|
500
|
+
try {
|
|
501
|
+
process.stderr.write(String.fromCharCode(0), (err) => { if (err)
|
|
502
|
+
trip(); });
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
trip();
|
|
506
|
+
}
|
|
507
|
+
}, CONSUMER_PROBE_MS);
|
|
508
|
+
if (typeof probe.unref === 'function')
|
|
509
|
+
probe.unref();
|
|
510
|
+
}
|
|
476
511
|
return {
|
|
477
512
|
gone: () => gone,
|
|
478
513
|
dispose: () => {
|
|
479
|
-
|
|
514
|
+
if (probe)
|
|
515
|
+
clearInterval(probe);
|
|
480
516
|
process.stdout.removeListener('error', onErr);
|
|
481
517
|
process.stderr.removeListener('error', onErr);
|
|
482
518
|
},
|
|
@@ -514,14 +550,14 @@ async function runWatchLoop(opts) {
|
|
|
514
550
|
// the consuming session is gone — abort and exit terminal so we don't orphan.
|
|
515
551
|
// Skip in SDK/test mode (opts.signal present): those callers own the process
|
|
516
552
|
// and their pipes aren't a Monitor consumer.
|
|
517
|
-
const consumer = opts.signal ? null : bindToConsumer(() => controller.abort());
|
|
553
|
+
const consumer = opts.signal ? null : bindToConsumer(() => controller.abort(), isWatchQuiet(opts));
|
|
518
554
|
while (!controller.signal.aborted) {
|
|
519
555
|
if (!isFirstAttempt) {
|
|
520
556
|
const lastSeen = cursor ? cursor.slice(0, 8) : 'none';
|
|
521
|
-
|
|
557
|
+
watchErr(`${LOG_PREFIX} reconnecting (last_seen_id=${lastSeen})`, opts);
|
|
522
558
|
}
|
|
523
559
|
isFirstAttempt = false;
|
|
524
|
-
const url = buildUrl(cfg.apiUrl, opts.project, opts.session, cursor, !!opts.receptionist, !!opts.assistant, !!opts.mechanic);
|
|
560
|
+
const url = buildUrl(cfg.apiUrl, opts.project, opts.session, cursor, !!opts.receptionist, !!opts.assistant, !!opts.mechanic, resolveWatchPlatform(opts.platform));
|
|
525
561
|
try {
|
|
526
562
|
const lastId = await readStream(url, cfg.apiKey, controller.signal, cursor, !!opts.json, idleTimeoutMs, opts.session);
|
|
527
563
|
if (lastId)
|
|
@@ -543,11 +579,11 @@ async function runWatchLoop(opts) {
|
|
|
543
579
|
// (no supervisor) still sees a non-zero exit.
|
|
544
580
|
const m = /^HTTP (4\d\d)/.exec(msg);
|
|
545
581
|
if (m) {
|
|
546
|
-
|
|
582
|
+
watchErr(`${LOG_PREFIX} ${msg}`, opts);
|
|
547
583
|
process.exit(inbox_watch_supervisor_1.FATAL_EXIT_CODE);
|
|
548
584
|
}
|
|
549
585
|
const lastSeen = cursor ? cursor.slice(0, 8) : 'none';
|
|
550
|
-
|
|
586
|
+
watchErr(`${LOG_PREFIX} disconnect: ${msg} — retrying in ${fmtDuration(backoff)} (last_seen_id=${lastSeen})`, opts);
|
|
551
587
|
}
|
|
552
588
|
if (controller.signal.aborted)
|
|
553
589
|
break;
|
package/dist/commands/init.js
CHANGED
|
@@ -40,6 +40,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.GREPRAG_SECTIONS = void 0;
|
|
42
42
|
exports.runInit = runInit;
|
|
43
|
+
exports.applyGrokHooks = applyGrokHooks;
|
|
43
44
|
exports.applyCodexHooks = applyCodexHooks;
|
|
44
45
|
exports.applySettings = applySettings;
|
|
45
46
|
exports.patchClaudeMdContent = patchClaudeMdContent;
|
|
@@ -140,6 +141,13 @@ function detectAgentTargets(cwd = process.cwd()) {
|
|
|
140
141
|
opencodeEvidence.push('~/.config/opencode exists');
|
|
141
142
|
if (opencodeEvidence.length > 0)
|
|
142
143
|
detections.push({ target: 'opencode', evidence: opencodeEvidence });
|
|
144
|
+
const grokEvidence = [];
|
|
145
|
+
if (process.env.GROK_SESSION_ID || process.env.GROK_HOOK_EVENT)
|
|
146
|
+
grokEvidence.push('GROK_* environment is set');
|
|
147
|
+
if (home && fs.existsSync(path.join(home, '.grok')))
|
|
148
|
+
grokEvidence.push('~/.grok exists');
|
|
149
|
+
if (grokEvidence.length > 0)
|
|
150
|
+
detections.push({ target: 'grok', evidence: grokEvidence });
|
|
143
151
|
return detections;
|
|
144
152
|
}
|
|
145
153
|
function describeTarget(target) {
|
|
@@ -147,6 +155,8 @@ function describeTarget(target) {
|
|
|
147
155
|
return 'Claude Code';
|
|
148
156
|
if (target === 'codex')
|
|
149
157
|
return 'Codex';
|
|
158
|
+
if (target === 'grok')
|
|
159
|
+
return 'Grok Build';
|
|
150
160
|
return 'OpenCode';
|
|
151
161
|
}
|
|
152
162
|
function explicitInitHint() {
|
|
@@ -155,6 +165,7 @@ function explicitInitHint() {
|
|
|
155
165
|
' greprag init --codex --tenant-id <your-handle>',
|
|
156
166
|
' greprag init --claude --tenant-id <your-handle>',
|
|
157
167
|
' greprag init --opencode --tenant-id <your-handle>',
|
|
168
|
+
' greprag init --grok --tenant-id <your-handle>',
|
|
158
169
|
].join('\n');
|
|
159
170
|
}
|
|
160
171
|
function normalizeHandle(raw) {
|
|
@@ -220,13 +231,16 @@ async function chooseInitTarget() {
|
|
|
220
231
|
console.log(' 1. Codex');
|
|
221
232
|
console.log(' 2. Claude Code');
|
|
222
233
|
console.log(' 3. OpenCode');
|
|
223
|
-
|
|
234
|
+
console.log(' 4. Grok Build');
|
|
235
|
+
const answer = (await prompt(' Choose 1, 2, 3, or 4: ')).trim().toLowerCase();
|
|
224
236
|
if (answer === '1' || answer === 'codex')
|
|
225
237
|
return 'codex';
|
|
226
238
|
if (answer === '2' || answer === 'claude' || answer === 'claude code')
|
|
227
239
|
return 'claude';
|
|
228
240
|
if (answer === '3' || answer === 'opencode' || answer === 'open code')
|
|
229
241
|
return 'opencode';
|
|
242
|
+
if (answer === '4' || answer === 'grok' || answer === 'grok build')
|
|
243
|
+
return 'grok';
|
|
230
244
|
console.error(' Error: no agent target selected.');
|
|
231
245
|
console.error(explicitInitHint());
|
|
232
246
|
process.exit(1);
|
|
@@ -247,9 +261,9 @@ async function runInit(opts) {
|
|
|
247
261
|
if (opts.global) {
|
|
248
262
|
return runGlobalInit(opts.name);
|
|
249
263
|
}
|
|
250
|
-
const explicitTargets = [opts.claude, opts.codex, opts.opencode].filter(Boolean).length;
|
|
264
|
+
const explicitTargets = [opts.claude, opts.codex, opts.opencode, opts.grok].filter(Boolean).length;
|
|
251
265
|
if (explicitTargets > 1) {
|
|
252
|
-
console.error(' Error: choose only one target: --claude, --codex, or --
|
|
266
|
+
console.error(' Error: choose only one target: --claude, --codex, --opencode, or --grok.');
|
|
253
267
|
process.exit(1);
|
|
254
268
|
}
|
|
255
269
|
// --opencode: configure for OpenCode instead of Claude Code.
|
|
@@ -260,12 +274,17 @@ async function runInit(opts) {
|
|
|
260
274
|
if (opts.codex) {
|
|
261
275
|
return runCodexInit(opts);
|
|
262
276
|
}
|
|
277
|
+
if (opts.grok) {
|
|
278
|
+
return runGrokInit(opts);
|
|
279
|
+
}
|
|
263
280
|
if (!opts.claude) {
|
|
264
281
|
const target = await chooseInitTarget();
|
|
265
282
|
if (target === 'codex')
|
|
266
283
|
return runCodexInit(opts);
|
|
267
284
|
if (target === 'opencode')
|
|
268
285
|
return runOpenCodeInit(opts);
|
|
286
|
+
if (target === 'grok')
|
|
287
|
+
return runGrokInit(opts);
|
|
269
288
|
}
|
|
270
289
|
console.log('\n greprag init — Setting up agent memory for Claude Code\n');
|
|
271
290
|
// Step 1: Get API key
|
|
@@ -640,6 +659,156 @@ async function runCodexInit(opts) {
|
|
|
640
659
|
console.log(' Inbox messages for Codex surface on SessionStart and UserPromptSubmit hook boundaries.');
|
|
641
660
|
console.log(' Hooks will activate after the Codex host restart; starting only a new task is not sufficient after hook changes.\n');
|
|
642
661
|
}
|
|
662
|
+
const GROK_RULES = `# GrepRAG on Grok Build
|
|
663
|
+
|
|
664
|
+
Before other work this session:
|
|
665
|
+
1. If \`~/.greprag/grok-context/<16-hex>.md\` exists, Read it. Else run \`greprag memory recap\`.
|
|
666
|
+
2. Session id is \`$GROK_SESSION_ID\` (UUIDv7). Short form is **16 hex**, not 8. 8-hex collides for ~65s. Send with \`--from-session $GROK_SESSION_ID\` (full UUID).
|
|
667
|
+
3. Idle wake: Grok \`monitor\` persistent:true, command:
|
|
668
|
+
\`greprag inbox watch --session $GROK_SESSION_ID --json --quiet\`
|
|
669
|
+
Not Claude Monitor. No bash while-true. \`--quiet\` required (stderr is a wake).
|
|
670
|
+
If a greprag inbox monitor is already listed in this session, do not start another. Instant exit = already armed, not a crash.
|
|
671
|
+
4. Chip: \`greprag load grok-chip-spawn\` then \`greprag grok spawn\` (real TUI + inbox). Helper: \`spawn_subagent\` background=true. Child arms its own quiet watch with ITS \`$GROK_SESSION_ID\`. Parent sends:
|
|
672
|
+
\`greprag send "…" --to travis@greprag.com/<child-full-uuid> --from-session $GROK_SESSION_ID\`
|
|
673
|
+
Parent keeps ONE watch, for itself. Child watch events may appear on the parent TUI — resume the child to act. Do not arm a second parent watch.
|
|
674
|
+
5. Tool shell: stdin redirected + \`-NonInteractive\`. \`Read-Host\` throws. \`Start-Process\` windows die with the tool Job Object. Secret paste: \`~/.claude/scripts/prompt-secret.ps1\`. Sticky console: \`schtasks /Create … /IT\` then \`schtasks /Run\`. npm Hello: \`npm login --auth-type=web\` in that window; browser Use security key; page token ≠ Hello PIN.
|
|
675
|
+
`;
|
|
676
|
+
function getGrokHooksPath() {
|
|
677
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
678
|
+
return path.join(home, '.grok', 'hooks', 'greprag.json');
|
|
679
|
+
}
|
|
680
|
+
function getGrokRulesPath() {
|
|
681
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
682
|
+
return path.join(home, '.grok', 'rules', 'greprag.md');
|
|
683
|
+
}
|
|
684
|
+
function grokCommand(sub, timeout) {
|
|
685
|
+
const script = path.resolve(__dirname, '..', 'hook.js');
|
|
686
|
+
const command = fs.existsSync(script)
|
|
687
|
+
? `node ${shellQuote(script)} ${sub}`
|
|
688
|
+
: (process.platform === 'win32' ? `greprag-hook.cmd ${sub}` : `greprag-hook ${sub}`);
|
|
689
|
+
return { type: 'command', command, timeout };
|
|
690
|
+
}
|
|
691
|
+
function applyGrokHooks(config) {
|
|
692
|
+
const changes = [];
|
|
693
|
+
if (!config.hooks)
|
|
694
|
+
config.hooks = {};
|
|
695
|
+
// Empty matcher = every SessionStart source (startup, resume, /new, dashboard
|
|
696
|
+
// dispatch, fork). `startup|resume` missed /new and dashboard agents, so this
|
|
697
|
+
// session never got a recap sidecar. adr: adr/grok-platform.md
|
|
698
|
+
const sessionStartMatcher = '';
|
|
699
|
+
if (!config.hooks.SessionStart)
|
|
700
|
+
config.hooks.SessionStart = [];
|
|
701
|
+
const retargeted = ['recap', 'session-id', 'drain'].reduce((n, sub) => (n + retargetGrepragHookMatcher(config.hooks.SessionStart, sub, ['startup|resume'], sessionStartMatcher)), 0);
|
|
702
|
+
if (retargeted)
|
|
703
|
+
changes.push(`Retargeted Grok SessionStart matcher (${retargeted})`);
|
|
704
|
+
const add = (event, matcher, sub, timeout, label) => {
|
|
705
|
+
if (hasGrepragHook(config.hooks[event], sub)) {
|
|
706
|
+
changes.push(`Grok ${event} ${label} already configured (skipped)`);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (!config.hooks[event])
|
|
710
|
+
config.hooks[event] = [];
|
|
711
|
+
config.hooks[event].push({ matcher, hooks: [grokCommand(sub, timeout)] });
|
|
712
|
+
changes.push(`Added Grok ${event} hook (${label})`);
|
|
713
|
+
};
|
|
714
|
+
add('SessionStart', sessionStartMatcher, 'recap', 15, 'memory recap');
|
|
715
|
+
add('SessionStart', sessionStartMatcher, 'session-id', 5, 'session-id');
|
|
716
|
+
add('SessionStart', sessionStartMatcher, 'drain', 8, 'inbox drain');
|
|
717
|
+
add('UserPromptSubmit', '', 'notify', 8, 'prompt cache');
|
|
718
|
+
add('PreToolUse', 'Bash|run_terminal_command', 'crush-wrap', 5, 'crush-wrap');
|
|
719
|
+
add('PreToolUse', '*', 'guard', 5, 'guard');
|
|
720
|
+
add('Stop', '', 'store', 15, 'turn capture');
|
|
721
|
+
add('Stop', '', 'drain', 8, 'inbox drain');
|
|
722
|
+
add('PostCompact', '', 'session-id', 5, 'session-id');
|
|
723
|
+
return changes;
|
|
724
|
+
}
|
|
725
|
+
function writeGrokRules() {
|
|
726
|
+
const file = getGrokRulesPath();
|
|
727
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
728
|
+
if (fs.existsSync(file) && fs.readFileSync(file, 'utf-8') === GROK_RULES) {
|
|
729
|
+
return 'Grok rules already current';
|
|
730
|
+
}
|
|
731
|
+
fs.writeFileSync(file, GROK_RULES);
|
|
732
|
+
return `Wrote ${file}`;
|
|
733
|
+
}
|
|
734
|
+
/** greprag init --grok
|
|
735
|
+
* Dedicated Grok Build adapter. Does not ride ~/.claude/settings.json.
|
|
736
|
+
* adr: adr/grok-platform.md */
|
|
737
|
+
async function runGrokInit(opts) {
|
|
738
|
+
console.log('\n greprag init — Setting up agent memory for Grok Build\n');
|
|
739
|
+
let apiKey = opts.apiKey || readSharedApiKey();
|
|
740
|
+
if (!apiKey) {
|
|
741
|
+
const tenantId = await resolveProvisionHandle(opts);
|
|
742
|
+
console.log(` Provisioning API key for public handle ${tenantId}@greprag.com...`);
|
|
743
|
+
const result = await provision(tenantId);
|
|
744
|
+
if (!result.ok) {
|
|
745
|
+
console.error(` Error: ${result.error}`);
|
|
746
|
+
process.exit(1);
|
|
747
|
+
}
|
|
748
|
+
apiKey = result.apiKey;
|
|
749
|
+
console.log(` API key created: ${apiKey.slice(0, 17)}...`);
|
|
750
|
+
}
|
|
751
|
+
console.log(' Validating API key...');
|
|
752
|
+
const valid = await validateKey(apiKey);
|
|
753
|
+
if (!valid) {
|
|
754
|
+
console.error(' Error: API key validation failed.');
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|
|
757
|
+
console.log(' Key validated against api.greprag.com');
|
|
758
|
+
const changes = [];
|
|
759
|
+
writeGrepragEnv(apiKey);
|
|
760
|
+
changes.push('Wrote ~/.greprag/.env for Grok hooks');
|
|
761
|
+
await cacheIdentity(apiKey);
|
|
762
|
+
const anchor = (0, project_anchor_1.ensureAnchor)(process.cwd());
|
|
763
|
+
if (anchor.source === 'git') {
|
|
764
|
+
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — derived from git history`);
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .greprag/project.json`);
|
|
768
|
+
const gitignoreResult = ensureAnchorTrackable(process.cwd());
|
|
769
|
+
if (gitignoreResult)
|
|
770
|
+
changes.push(gitignoreResult);
|
|
771
|
+
}
|
|
772
|
+
const globalAnchor = (0, project_anchor_1.ensureGlobalAnchor)();
|
|
773
|
+
changes.push(`Global anchor: ${globalAnchor.projectName} (${globalAnchor.projectId.slice(0, 8)})`);
|
|
774
|
+
writeProjectPath(anchor.projectName, process.cwd());
|
|
775
|
+
changes.push(`Registered repo path for ${anchor.projectName} → ~/.greprag/projects.json`);
|
|
776
|
+
const grokSkill = installCoreSkill('grok');
|
|
777
|
+
if (grokSkill)
|
|
778
|
+
changes.push(`Skill: installed → ${grokSkill}`);
|
|
779
|
+
changes.push(writeGrokRules());
|
|
780
|
+
try {
|
|
781
|
+
const res = await fetch(`${API_URL}/v1/inbox/projects/register`, {
|
|
782
|
+
method: 'POST',
|
|
783
|
+
headers: {
|
|
784
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
785
|
+
'Content-Type': 'application/json',
|
|
786
|
+
},
|
|
787
|
+
body: JSON.stringify({ project_id: anchor.projectId, project_name: anchor.projectName }),
|
|
788
|
+
});
|
|
789
|
+
const reg = await res.json();
|
|
790
|
+
changes.push(reg.ok ? 'Project registered for inbox addressing' : `Inbox registration: ${reg.error || 'failed'}`);
|
|
791
|
+
}
|
|
792
|
+
catch {
|
|
793
|
+
changes.push('Inbox registration skipped (network)');
|
|
794
|
+
}
|
|
795
|
+
const hooksPath = getGrokHooksPath();
|
|
796
|
+
const hooks = readCodexHooks(hooksPath);
|
|
797
|
+
const hookChanges = applyGrokHooks(hooks);
|
|
798
|
+
writeCodexHooks(hooksPath, hooks);
|
|
799
|
+
changes.push(...hookChanges);
|
|
800
|
+
console.log('\n Setup complete!\n');
|
|
801
|
+
for (const change of changes) {
|
|
802
|
+
console.log(` - ${change}`);
|
|
803
|
+
}
|
|
804
|
+
console.log(`\n Grok hooks file: ${hooksPath}`);
|
|
805
|
+
console.log(` Grok rules: ${getGrokRulesPath()}`);
|
|
806
|
+
console.log(` Project anchor: ${anchor.anchorPath}`);
|
|
807
|
+
console.log(' Reload hooks in Grok (/hooks then r) or start a fresh session.');
|
|
808
|
+
console.log(' Recap lands in ~/.greprag/grok-context/<16-hex>.md — Grok ignores SessionStart stdout.');
|
|
809
|
+
console.log(' Idle inbox: Grok monitor + `greprag inbox watch --session $GROK_SESSION_ID --json --quiet`.');
|
|
810
|
+
console.log(' Second session: `greprag grok spawn` (TUI) or spawn_subagent; child arms its own watch; parent keeps one.\n');
|
|
811
|
+
}
|
|
643
812
|
/** greprag init --global
|
|
644
813
|
* Creates ~/.greprag/project.json with a stable UUID.
|
|
645
814
|
* Designed for Cowork / any agent session whose cwd has no repo-level anchor.
|
|
@@ -979,7 +1148,7 @@ function getSkillRoot() {
|
|
|
979
1148
|
return path.join(__dirname, '../../skill');
|
|
980
1149
|
}
|
|
981
1150
|
function skillTargetRoot(target) {
|
|
982
|
-
const dir = target === 'codex' ? '.codex' : '.claude';
|
|
1151
|
+
const dir = target === 'codex' ? '.codex' : target === 'grok' ? '.grok' : '.claude';
|
|
983
1152
|
return path.join(os.homedir(), dir, 'skills');
|
|
984
1153
|
}
|
|
985
1154
|
function installCoreSkill(target) {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* load-primer` / `chip-spawn-pointer`). Announce-only: detect → silent,
|
|
20
20
|
* reminder → null. adr: docs/load-system.md, docs/reminder-interrupt.md */
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.codexChipSpawnPointerModule = exports.chipSpawnPointerModule = exports.loadPrimerModule = exports.CODEX_CHIP_SPAWN_POINTER = exports.OPENCODE_CHIP_SPAWN_POINTER = exports.CHIP_SPAWN_POINTER = exports.LOAD_PRIMER = void 0;
|
|
22
|
+
exports.codexChipSpawnPointerModule = exports.chipSpawnPointerModule = exports.loadPrimerModule = exports.CODEX_CHIP_SPAWN_POINTER = exports.GROK_CHIP_SPAWN_POINTER = exports.OPENCODE_CHIP_SPAWN_POINTER = exports.CHIP_SPAWN_POINTER = exports.LOAD_PRIMER = void 0;
|
|
23
23
|
/** PRIMER — teaches the loader itself. Resident (fires every SessionStart +
|
|
24
24
|
* re-teaches after compaction). Terse: it is in context every session. */
|
|
25
25
|
exports.LOAD_PRIMER = [
|
|
@@ -43,6 +43,11 @@ exports.OPENCODE_CHIP_SPAWN_POINTER = [
|
|
|
43
43
|
'[greprag chips — agent coordination is built in (messaging plumbing in the inbox primer).]',
|
|
44
44
|
'• Delegating a component of a plan to an isolated chip session, or `greprag fix spawn` just printed a FIX-chip mission? → run `greprag load chip-bootloader` FIRST for the method: OpenCode chips spawn natively via `greprag opencode chip goal create` + `greprag opencode chip spawn` (HTTP child session; manual paste only when the API is down). ≥2 chips at one objective → `greprag load chip-leader-opencode` BEFORE the first spawn.',
|
|
45
45
|
].join('\n');
|
|
46
|
+
/** POINTER — Grok Build TUI chip. Real window + inbox mesh, not spawn_subagent. */
|
|
47
|
+
exports.GROK_CHIP_SPAWN_POINTER = [
|
|
48
|
+
'[greprag chips — agent coordination is built in (messaging plumbing in the inbox primer).]',
|
|
49
|
+
'• About to spawn a chip? → run `greprag load grok-chip-spawn` FIRST. Vehicle is `greprag grok spawn` (cmd /k TUI bootloader). Child arms its own quiet inbox watch; parent keeps one and talks via `greprag send`. `spawn_subagent` is a helper, not a chip. ≥2 chips at one objective → `greprag load chip-leader` BEFORE the first spawn.',
|
|
50
|
+
].join('\n');
|
|
46
51
|
/** POINTER — Codex Desktop's native chip path. This is intentionally distinct
|
|
47
52
|
* from Claude Code spawn_task: different lifecycle, runtime, and handoff. */
|
|
48
53
|
exports.CODEX_CHIP_SPAWN_POINTER = [
|
|
@@ -70,6 +75,8 @@ exports.chipSpawnPointerModule = {
|
|
|
70
75
|
return null;
|
|
71
76
|
if (env.platform === 'opencode')
|
|
72
77
|
return exports.OPENCODE_CHIP_SPAWN_POINTER;
|
|
78
|
+
if (env.platform === 'grok')
|
|
79
|
+
return exports.GROK_CHIP_SPAWN_POINTER;
|
|
73
80
|
return exports.CHIP_SPAWN_POINTER;
|
|
74
81
|
},
|
|
75
82
|
reminder: () => null,
|
package/dist/commands/load.js
CHANGED
|
@@ -73,6 +73,10 @@ const LIBRARY = {
|
|
|
73
73
|
files: ['skill/templates/codex-chip-spawn.md'],
|
|
74
74
|
purpose: 'Spawn an independent writable native Codex Desktop task with an isolated worktree and native completion/cleanup.',
|
|
75
75
|
},
|
|
76
|
+
'grok-chip-spawn': {
|
|
77
|
+
files: ['skill/templates/grok-chip-spawn.md'],
|
|
78
|
+
purpose: 'Spawn a Grok TUI chip (cmd /k bootloader + isolated worktree). Child arms its own inbox watch; parent talks via greprag send. Not spawn_subagent.',
|
|
79
|
+
},
|
|
76
80
|
'skill-change': {
|
|
77
81
|
files: ['skill/templates/skill-change.md'],
|
|
78
82
|
purpose: 'Internal bundled schema for safely updating a skill after a run: when to edit, Convention A/B shapes, and when to propose instead.',
|
|
@@ -20,7 +20,8 @@ function buildOsPrimer(env) {
|
|
|
20
20
|
// vehicle is the chip-bootloader entry: native `greprag opencode chip` spawn).
|
|
21
21
|
const spawnEntry = env?.platform === 'codex' ? 'codex-chip-spawn'
|
|
22
22
|
: env?.platform === 'opencode' ? 'chip-bootloader'
|
|
23
|
-
: 'chip-spawn'
|
|
23
|
+
: env?.platform === 'grok' ? 'grok-chip-spawn'
|
|
24
|
+
: 'chip-spawn';
|
|
24
25
|
return [
|
|
25
26
|
'[grepragOS — the operating laws. Full doctrine: `greprag load os`.]',
|
|
26
27
|
'• Doctrine vs state: methods ship in the CLI (`greprag load`); live state lives in the repo. A skill that depends on repo state carries a "STATE — read these first" block naming exact paths.',
|
|
@@ -68,6 +68,7 @@ exports.readPipeWrapConfig = readPipeWrapConfig;
|
|
|
68
68
|
exports.runPipeWrap = runPipeWrap;
|
|
69
69
|
const fs = __importStar(require("fs"));
|
|
70
70
|
const project_anchor_1 = require("../project-anchor");
|
|
71
|
+
const hook_runtime_1 = require("../hook-runtime");
|
|
71
72
|
// ---------- Structural safety gates (pure) ----------------------------------
|
|
72
73
|
/** True when single/double quotes balance with bash-style escaping
|
|
73
74
|
* (backslash escapes inside double quotes and bare text; not inside
|
|
@@ -282,11 +283,11 @@ function readPipeWrapConfig(cwd) {
|
|
|
282
283
|
enabled = false;
|
|
283
284
|
return { enabled };
|
|
284
285
|
}
|
|
285
|
-
/** PreToolUse[Bash] — returns the updatedInput envelope
|
|
286
|
-
* qualifies for wrapping, null for silence. NO permissionDecision:
|
|
287
|
-
* rewritten command still goes through the normal permission flow. */
|
|
286
|
+
/** PreToolUse[Bash / run_terminal_command] — returns the updatedInput envelope
|
|
287
|
+
* when the command qualifies for wrapping, null for silence. NO permissionDecision:
|
|
288
|
+
* the rewritten command still goes through the normal permission flow. */
|
|
288
289
|
function runPipeWrap(input) {
|
|
289
|
-
if (input.tool_name
|
|
290
|
+
if (!(0, hook_runtime_1.isShellTool)(input.tool_name))
|
|
290
291
|
return null;
|
|
291
292
|
const command = input.tool_input?.command;
|
|
292
293
|
if (typeof command !== 'string' || !command.trim())
|
|
@@ -317,7 +318,7 @@ function runPipeWrap(input) {
|
|
|
317
318
|
return {
|
|
318
319
|
hookSpecificOutput: {
|
|
319
320
|
hookEventName: 'PreToolUse',
|
|
320
|
-
updatedInput: { command: rewriteCommand(target, decision) },
|
|
321
|
+
updatedInput: { ...(input.tool_input || {}), command: rewriteCommand(target, decision) },
|
|
321
322
|
},
|
|
322
323
|
};
|
|
323
324
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.hazardousRgTargets = hazardousRgTargets;
|
|
4
4
|
exports.buildSearchGuardReason = buildSearchGuardReason;
|
|
5
5
|
exports.runSearchGuard = runSearchGuard;
|
|
6
|
+
const hook_runtime_1 = require("../hook-runtime");
|
|
6
7
|
const HAZARDOUS_DIRS = new Set([
|
|
7
8
|
'.next',
|
|
8
9
|
'.turbo',
|
|
@@ -151,7 +152,7 @@ function buildSearchGuardReason(targets) {
|
|
|
151
152
|
].join(' ');
|
|
152
153
|
}
|
|
153
154
|
function runSearchGuard(input) {
|
|
154
|
-
if (input.tool_name
|
|
155
|
+
if (!(0, hook_runtime_1.isShellTool)(input.tool_name))
|
|
155
156
|
return null;
|
|
156
157
|
const command = input.tool_input?.command;
|
|
157
158
|
if (typeof command !== 'string' || !command.trim())
|
package/dist/commands/status.js
CHANGED
|
@@ -146,6 +146,9 @@ function readVersion() {
|
|
|
146
146
|
return 'unknown';
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
+
function grokHooksPath() {
|
|
150
|
+
return path.join(home(), '.grok', 'hooks', 'greprag.json');
|
|
151
|
+
}
|
|
149
152
|
function platformFromArgs(args) {
|
|
150
153
|
if (args.includes('--claude'))
|
|
151
154
|
return 'claude';
|
|
@@ -153,6 +156,8 @@ function platformFromArgs(args) {
|
|
|
153
156
|
return 'codex';
|
|
154
157
|
if (args.includes('--opencode'))
|
|
155
158
|
return 'opencode';
|
|
159
|
+
if (args.includes('--grok'))
|
|
160
|
+
return 'grok';
|
|
156
161
|
if (args.includes('--all-platforms'))
|
|
157
162
|
return 'all';
|
|
158
163
|
return 'all';
|
|
@@ -167,6 +172,8 @@ function buildStatus(cwd, platform = 'all') {
|
|
|
167
172
|
const identityCache = (0, identity_1.readCache)(identityCachePath);
|
|
168
173
|
const codexSkillPath = path.join(home(), '.codex', 'skills', 'greprag');
|
|
169
174
|
const claudeSkillPath = path.join(home(), '.claude', 'skills', 'greprag');
|
|
175
|
+
const grokSkillPath = path.join(home(), '.grok', 'skills', 'greprag');
|
|
176
|
+
const grokHooks = readJson(grokHooksPath(), {});
|
|
170
177
|
const opencodePluginPath = path.join(home(), '.config', 'opencode', 'plugins', 'greprag-memory.js');
|
|
171
178
|
const claudeHooks = {
|
|
172
179
|
session_start_recap: hasHook(settings.hooks?.SessionStart, 'recap'),
|
|
@@ -187,6 +194,15 @@ function buildStatus(cwd, platform = 'all') {
|
|
|
187
194
|
post_compact_session_id: hasCodexHook(codexHooks.hooks?.PostCompact, 'session-id'),
|
|
188
195
|
};
|
|
189
196
|
const opencodePluginInstalled = fs.existsSync(opencodePluginPath);
|
|
197
|
+
const grokHookStatus = {
|
|
198
|
+
session_start_recap: hasCodexHook(grokHooks.hooks?.SessionStart, 'recap'),
|
|
199
|
+
session_id: hasCodexHook(grokHooks.hooks?.SessionStart, 'session-id'),
|
|
200
|
+
session_start_drain: hasCodexHook(grokHooks.hooks?.SessionStart, 'drain'),
|
|
201
|
+
user_prompt_submit: hasCodexHook(grokHooks.hooks?.UserPromptSubmit, 'notify'),
|
|
202
|
+
crush_wrap: hasCodexHook(grokHooks.hooks?.PreToolUse, 'crush-wrap'),
|
|
203
|
+
stop_store: hasCodexHook(grokHooks.hooks?.Stop, 'store'),
|
|
204
|
+
stop_drain: hasCodexHook(grokHooks.hooks?.Stop, 'drain'),
|
|
205
|
+
};
|
|
190
206
|
return {
|
|
191
207
|
installed: true,
|
|
192
208
|
version: readVersion(),
|
|
@@ -212,6 +228,8 @@ function buildStatus(cwd, platform = 'all') {
|
|
|
212
228
|
codex_pre_tool_use_safety: codexHookStatus.pre_tool_use_safety,
|
|
213
229
|
codex_pre_tool_use_chip_guard: codexHookStatus.pre_tool_use_chip_guard,
|
|
214
230
|
codex_stop_store: codexHookStatus.stop_store,
|
|
231
|
+
grok_session_start_recap: grokHookStatus.session_start_recap,
|
|
232
|
+
grok_stop_store: grokHookStatus.stop_store,
|
|
215
233
|
},
|
|
216
234
|
platforms: {
|
|
217
235
|
claude: {
|
|
@@ -241,6 +259,16 @@ function buildStatus(cwd, platform = 'all') {
|
|
|
241
259
|
plugin_installed: opencodePluginInstalled,
|
|
242
260
|
note: 'OpenCode loads the GrepRAG plugin from its global plugin directory.',
|
|
243
261
|
},
|
|
262
|
+
grok: {
|
|
263
|
+
configured: !!api.key && grokHookStatus.session_start_recap && grokHookStatus.stop_store
|
|
264
|
+
&& fs.existsSync(grokSkillPath),
|
|
265
|
+
config_path: grokHooksPath(),
|
|
266
|
+
config_present: fs.existsSync(grokHooksPath()),
|
|
267
|
+
skill_path: grokSkillPath,
|
|
268
|
+
skill_installed: fs.existsSync(grokSkillPath),
|
|
269
|
+
hooks: grokHookStatus,
|
|
270
|
+
note: 'Grok recap is a sidecar + ~/.grok/rules (SessionStart stdout is ignored). Idle inbox: Grok monitor + quiet watch. Stop drain injects unread mail only when unarmed.',
|
|
271
|
+
},
|
|
244
272
|
},
|
|
245
273
|
project: {
|
|
246
274
|
cwd: path.resolve(cwd),
|
|
@@ -293,7 +321,7 @@ function renderPlatform(name, p) {
|
|
|
293
321
|
}
|
|
294
322
|
function renderHuman(s, docMirror) {
|
|
295
323
|
const yes = (b) => b ? 'yes' : 'no';
|
|
296
|
-
const platformNames = s.platform === 'all' ? ['claude', 'codex', 'opencode'] : [s.platform];
|
|
324
|
+
const platformNames = s.platform === 'all' ? ['claude', 'codex', 'opencode', 'grok'] : [s.platform];
|
|
297
325
|
return [
|
|
298
326
|
`greprag ${s.version}`,
|
|
299
327
|
`Shared env: ${s.shared_env_path}`,
|