greprag 5.75.0 → 5.77.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.
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /** greprag persona — tenant-scoped speaking instructions. adr: adr/persona.md
3
+ *
4
+ * Thin HTTP wrapper over /v1/persona. The server owns built-in template content;
5
+ * custom text is sent verbatim and injected without rewriting.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.runPersona = runPersona;
42
+ const fs = __importStar(require("fs"));
43
+ function getConfig() {
44
+ return {
45
+ apiUrl: process.env.GREPRAG_API_URL || 'https://api.greprag.com',
46
+ apiKey: process.env.GREPRAG_API_KEY || '',
47
+ };
48
+ }
49
+ function getFlag(args, flag) {
50
+ const idx = args.indexOf(flag);
51
+ return idx === -1 || idx + 1 >= args.length ? undefined : args[idx + 1];
52
+ }
53
+ const HELP = `greprag persona — tenant-scoped instructions for how agents speak to you
54
+
55
+ USAGE
56
+ greprag persona list
57
+ List built-in Persona templates.
58
+
59
+ greprag persona use <template>
60
+ Activate a built-in template, e.g. vibe-shaper.
61
+
62
+ greprag persona set <text>
63
+ greprag persona set --file <path>
64
+ greprag persona set --stdin
65
+ Save and activate a custom Persona verbatim.
66
+
67
+ greprag persona show
68
+ Show the active Persona.
69
+
70
+ greprag persona clear
71
+ Remove the active Persona.`;
72
+ async function api(method, pathname, body) {
73
+ const cfg = getConfig();
74
+ if (!cfg.apiKey) {
75
+ console.error('GREPRAG_API_KEY not set — run `greprag init` first.');
76
+ process.exit(1);
77
+ }
78
+ const res = await fetch(`${cfg.apiUrl}${pathname}`, {
79
+ method,
80
+ headers: {
81
+ 'Authorization': `Bearer ${cfg.apiKey}`,
82
+ 'Content-Type': 'application/json',
83
+ },
84
+ body: body === undefined ? undefined : JSON.stringify(body),
85
+ });
86
+ let data = {};
87
+ try {
88
+ data = await res.json();
89
+ }
90
+ catch { /* non-JSON */ }
91
+ return { ok: res.ok, status: res.status, data };
92
+ }
93
+ function fail(data, status) {
94
+ console.error(`Error${status ? ` (${status})` : ''}: ${data.error || 'request failed'}`);
95
+ process.exit(1);
96
+ }
97
+ function readPersonaInput(args) {
98
+ const file = getFlag(args, '--file');
99
+ if (file) {
100
+ try {
101
+ return file === '-' ? fs.readFileSync(0, 'utf-8') : fs.readFileSync(file, 'utf-8');
102
+ }
103
+ catch (err) {
104
+ console.error(`persona set: could not read --file ${file}: ${err.message}`);
105
+ process.exit(1);
106
+ }
107
+ }
108
+ if (args.includes('--stdin')) {
109
+ try {
110
+ return fs.readFileSync(0, 'utf-8');
111
+ }
112
+ catch (err) {
113
+ console.error(`persona set: could not read stdin: ${err.message}`);
114
+ process.exit(1);
115
+ }
116
+ }
117
+ return args.filter((a) => a !== '--stdin').join(' ');
118
+ }
119
+ async function list() {
120
+ const { ok, status, data } = await api('GET', '/v1/persona/templates');
121
+ if (!ok)
122
+ fail(data, status);
123
+ const templates = data.templates || [];
124
+ if (!templates.length) {
125
+ console.log('No built-in Persona templates.');
126
+ return;
127
+ }
128
+ console.log('Built-in Persona templates:\n');
129
+ for (const t of templates) {
130
+ console.log(` ${t.id} — ${t.name}`);
131
+ console.log(` ${t.description}`);
132
+ }
133
+ }
134
+ async function useTemplate(id) {
135
+ if (!id) {
136
+ console.error('Usage: greprag persona use <template>');
137
+ process.exit(1);
138
+ }
139
+ const { ok, status, data } = await api('PUT', '/v1/persona/template', { templateId: id });
140
+ if (!ok)
141
+ fail(data, status);
142
+ const p = data.persona;
143
+ console.log(`Persona active: ${p.templateId} (built-in template)`);
144
+ }
145
+ async function set(args) {
146
+ const content = readPersonaInput(args);
147
+ const { ok, status, data } = await api('PUT', '/v1/persona/custom', { content });
148
+ if (!ok)
149
+ fail(data, status);
150
+ const p = data.persona;
151
+ console.log(`Persona active: custom (${p.content?.length ?? content.length} chars)`);
152
+ }
153
+ async function show() {
154
+ const { ok, status, data } = await api('GET', '/v1/persona');
155
+ if (!ok)
156
+ fail(data, status);
157
+ const p = data.persona;
158
+ if (!p) {
159
+ console.log('No Persona set.');
160
+ return;
161
+ }
162
+ const source = p.mode === 'template' ? `template ${p.templateId}` : 'custom';
163
+ console.log(`Persona (${source})`);
164
+ console.log('');
165
+ console.log(p.content || '');
166
+ }
167
+ async function clear() {
168
+ const { ok, status, data } = await api('DELETE', '/v1/persona');
169
+ if (!ok)
170
+ fail(data, status);
171
+ console.log(data.removed ? 'Persona cleared.' : 'No Persona was set.');
172
+ }
173
+ async function runPersona(args) {
174
+ const sub = args[0];
175
+ const rest = args.slice(1);
176
+ if (!sub || sub === '--help' || sub === '-h' || sub === 'help') {
177
+ console.log(HELP);
178
+ return;
179
+ }
180
+ switch (sub) {
181
+ case 'list': return list();
182
+ case 'use': return useTemplate(rest[0]);
183
+ case 'set': return set(rest);
184
+ case 'show': return show();
185
+ case 'clear': return clear();
186
+ default:
187
+ console.error(`Unknown persona command: ${sub}\n`);
188
+ console.log(HELP);
189
+ process.exit(1);
190
+ }
191
+ }
@@ -28,6 +28,7 @@ const skill_mirror_reminder_1 = require("./skill-mirror-reminder");
28
28
  const procedure_reminder_1 = require("./procedure-reminder");
29
29
  const loadout_reminder_1 = require("./loadout-reminder");
30
30
  const delivery_reminder_1 = require("./delivery-reminder");
31
+ const persona_reminder_1 = require("./persona-reminder");
31
32
  /** Registry order = display order. THE single agent-facing announce/reminder assembly:
32
33
  * the hook does I/O → fills ReminderEnv → collectAnnounces (SessionStart) / collectReminders
33
34
  * (per turn) render every module here in this order. A module may own either surface or both;
@@ -46,6 +47,7 @@ exports.REGISTRY = [
46
47
  skill_mirror_reminder_1.skillMirrorAnnounceModule, // native-equivalent activation index for load-only mirrored skills
47
48
  procedure_reminder_1.procedureAnnounceModule, // active project procedures, rendered generically from operator-owned installs
48
49
  delivery_reminder_1.deliveryControlModule, // repo-scoped announce-first Delivery System pilot (all harnesses)
50
+ persona_reminder_1.personaAnnounceModule, // tenant-scoped Persona, shared across projects/harnesses
49
51
  loadout_reminder_1.loadoutRegistrarModule, // equipped loadouts: one advisor announce each + per-turn keyword Match (docs/loadout.md)
50
52
  setup_reminder_1.setupWarningModule,
51
53
  version_reminder_1.versionUpgradeModule, // Deficiency-gated announce — silent unless a newer release exists
@@ -191,6 +191,7 @@ function buildStatus(cwd, platform = 'all') {
191
191
  pre_tool_use_chip_guard: hasCodexHook(codexHooks.hooks?.PreToolUse, 'codex-pretooluse')
192
192
  || hasCodexHook(codexHooks.hooks?.PreToolUse, 'codex-chip-hook'),
193
193
  stop_store: hasCodexHook(codexHooks.hooks?.Stop, 'codex-store'),
194
+ post_compact_reannounce: hasCodexHook(codexHooks.hooks?.PostCompact, 'recompact'),
194
195
  post_compact_session_id: hasCodexHook(codexHooks.hooks?.PostCompact, 'session-id'),
195
196
  };
196
197
  const opencodePluginInstalled = fs.existsSync(opencodePluginPath);
@@ -267,7 +268,7 @@ function buildStatus(cwd, platform = 'all') {
267
268
  skill_path: grokSkillPath,
268
269
  skill_installed: fs.existsSync(grokSkillPath),
269
270
  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.',
271
+ 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
272
  },
272
273
  },
273
274
  project: {
package/dist/hook.js CHANGED
@@ -1334,7 +1334,7 @@ function grokSidecarHead(short, full) {
1334
1334
  return (0, session_id_1.buildSessionIdContext)(short, (0, session_id_1.readIdentityAlias)())
1335
1335
  + `\n\nARM (Grok monitor persistent:true): \`${arm}\`\n`
1336
1336
  + `Send with --from-session ${full || short} (full UUID or 16-hex, never 8-hex).\n`
1337
- + 'Second session: spawn_subagent background=true; child arms its own quiet watch; parent keeps ONE watch.\n'
1337
+ + 'Chip: `greprag load grok-chip-spawn` then `greprag grok spawn`. Helper: spawn_subagent. Parent keeps ONE watch. Peers: `greprag inbox watchers` then `greprag send`.\n'
1338
1338
  + 'If a greprag inbox monitor is already listed, do not start another. Instant exit = already armed.\n\n';
1339
1339
  }
1340
1340
  function writeRecapOutput(text, mode, grokShort, grokFull) {
@@ -1613,11 +1613,29 @@ async function recap(input, mode = 'plain', opts = {}) {
1613
1613
  return empty;
1614
1614
  }
1615
1615
  })();
1616
+ // Tenant Persona primer. The server owns both template resolution and
1617
+ // the provenance envelope; harnesses transport it unchanged. adr: adr/persona.md
1618
+ const personaPromise = (async () => {
1619
+ try {
1620
+ const res = await fetch(`${cfg.apiUrl}/v1/persona`, {
1621
+ headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
1622
+ });
1623
+ if (!res.ok)
1624
+ return null;
1625
+ const data = await res.json();
1626
+ return typeof data.announce === 'string' && data.announce.length > 0
1627
+ ? data.announce
1628
+ : null;
1629
+ }
1630
+ catch {
1631
+ return null;
1632
+ }
1633
+ })();
1616
1634
  const updateAvailablePromise = checkForUpdate();
1617
1635
  const bodyPromise = (!opts.compact && anchor.sessionStartRecap)
1618
1636
  ? (0, opencode_plugin_helpers_1.buildRecapBody)(cfg.apiUrl, cfg.apiKey, anchor)
1619
1637
  : Promise.resolve('');
1620
- const [, corpusApiDocs, docPointers, enrichmentDown, skillGains, mirroredSkills, loadoutState, updateAvailable, body,] = await Promise.all([
1638
+ const [, corpusApiDocs, docPointers, enrichmentDown, skillGains, mirroredSkills, loadoutState, personaAnnounce, updateAvailable, body,] = await Promise.all([
1621
1639
  matchsetPromise,
1622
1640
  corpusApiDocsPromise,
1623
1641
  docPointersPromise,
@@ -1625,6 +1643,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1625
1643
  skillGainsPromise,
1626
1644
  mirroredSkillsPromise,
1627
1645
  loadoutStatePromise,
1646
+ personaPromise,
1628
1647
  updateAvailablePromise,
1629
1648
  bodyPromise,
1630
1649
  ]);
@@ -1704,6 +1723,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1704
1723
  mirroredSkills,
1705
1724
  procedureAnnounces,
1706
1725
  equippedLoadouts,
1726
+ personaAnnounce,
1707
1727
  updateAvailable,
1708
1728
  activeCommandDrift: activeCommandDrift(),
1709
1729
  loadoutUpdateRequired,
@@ -2023,7 +2043,9 @@ async function collisionCheck(input) {
2023
2043
  * poll live-tails with no double-delivery. Closes the dead-window gap where a
2024
2044
  * session-directed message that lands between watcher death and re-arm sits
2025
2045
  * unread until a manual `greprag inbox`. Inbound-only — the front desk (cold
2026
- * opens + email) stays with the human-scoped `mail` hook. All real logic lives
2046
+ * opens + email) stays with the human-scoped `mail` hook. Injects only when
2047
+ * unarmed (`isLocallyArmed`); a live watcher already delivered the body.
2048
+ * Grok also wires this on Stop as the unarmed floor. All real logic lives
2027
2049
  * in ./commands/inbox-drain (pure + tested). Best-effort: any miss → silent,
2028
2050
  * never blocks SessionStart. adr: adr/monitor-resilience.md */
2029
2051
  async function drain(input) {
@@ -2034,7 +2056,10 @@ async function drain(input) {
2034
2056
  const short = (0, session_id_1.truncateSessionId)(input.session_id);
2035
2057
  if (!short)
2036
2058
  return; // no session id → silent
2037
- const result = await (0, inbox_drain_1.runInboxDrain)({ session: short, apiUrl: cfg.apiUrl, apiKey: cfg.apiKey });
2059
+ const result = await (0, inbox_drain_1.runInboxDrain)({
2060
+ session: short, apiUrl: cfg.apiUrl, apiKey: cfg.apiKey,
2061
+ armed: (0, watcher_registry_1.isLocallyArmed)(short),
2062
+ });
2038
2063
  if (result.context) {
2039
2064
  (0, hook_runtime_1.writeAdditionalContext)(input.hook_event_name || 'SessionStart', result.context);
2040
2065
  if ((0, harness_1.inferCurrentHarness)() === 'grok')
@@ -2259,7 +2284,10 @@ async function main() {
2259
2284
  // opt out per-module with reannounceOnCompact:false) so the doctrine the compaction
2260
2285
  // wiped (incl. the watcher-arm announce) returns. additionalContext = the JSON form
2261
2286
  // PostCompact injects. adr: adr/monitor-resilience.md, docs/reminder-interrupt.md
2262
- await recap(input, 'additionalContext', { compact: true });
2287
+ await recap(input, 'additionalContext', {
2288
+ compact: true,
2289
+ platform: harness === 'grok' ? 'grok' : undefined,
2290
+ });
2263
2291
  }
2264
2292
  else if (subcommand === 'codex-recap') {
2265
2293
  await recap(input, 'additionalContext', { platform: 'codex' });
@@ -2417,10 +2445,11 @@ async function main() {
2417
2445
  // best-effort state stash. docs/ingress-trigger-bridge.md
2418
2446
  await store(input, harness === 'grok' ? 'grok' : 'claude-code');
2419
2447
  if (harness === 'grok') {
2420
- // Floor: inject unread mail. Do NOT nag UNARMED here — Grok Stop
2421
- // fires every turn, and a pidfile-alias miss made that a re-arm loop
2422
- // (watch already live → singleton-guard exits instantly → model arms
2423
- // again). Arm teaching is sidecar + rules. adr: adr/grok-platform.md
2448
+ // Floor: inject unread mail only when unarmed. Do NOT nag UNARMED here —
2449
+ // Grok Stop fires every turn, and a pidfile-alias miss made that a
2450
+ // re-arm loop (watch already live → singleton-guard exits instantly →
2451
+ // model arms again). Arm teaching is sidecar + rules.
2452
+ // adr: adr/grok-platform.md
2424
2453
  await drain(input);
2425
2454
  }
2426
2455
  stateUpdate(input);
package/dist/index.js CHANGED
@@ -43,6 +43,7 @@ var __importStar = (this && this.__importStar) || (function () {
43
43
  Object.defineProperty(exports, "__esModule", { value: true });
44
44
  const path = __importStar(require("path"));
45
45
  const fs = __importStar(require("fs"));
46
+ const os = __importStar(require("os"));
46
47
  const init_1 = require("./commands/init");
47
48
  const status_1 = require("./commands/status");
48
49
  const doctor_1 = require("./commands/doctor");
@@ -65,6 +66,7 @@ const doc_1 = require("./commands/doc");
65
66
  const parity_1 = require("./commands/parity");
66
67
  const agents_1 = require("./commands/agents");
67
68
  const loadout_1 = require("./commands/loadout");
69
+ const persona_1 = require("./commands/persona");
68
70
  const assistant_1 = require("./commands/assistant");
69
71
  const email_1 = require("./commands/email");
70
72
  const sms_1 = require("./commands/sms");
@@ -77,6 +79,7 @@ const crush_1 = require("./commands/crush");
77
79
  const crush_stats_1 = require("./commands/crush-stats");
78
80
  const archive_1 = require("./commands/archive");
79
81
  const inbox_watch_1 = require("./commands/inbox-watch");
82
+ const grok_spawn_1 = require("./commands/grok-spawn");
80
83
  const inbox_attachments_1 = require("./inbox-attachments");
81
84
  const discord_1 = require("./commands/discord");
82
85
  const project_anchor_1 = require("./project-anchor");
@@ -272,6 +275,23 @@ undici error, OS kill); and it SELF-TERMINATES when its Monitor consumer's pipe
272
275
  breaks (session reload/end) so it never orphans. Within-session robustness only;
273
276
  a watcher cannot survive session reload / --resume / /compact.
274
277
  adr: adr/monitor-resilience.md`;
278
+ function readProjectRegistry() {
279
+ try {
280
+ const file = path.join(os.homedir(), '.greprag', 'projects.json');
281
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
282
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
283
+ return {};
284
+ const out = {};
285
+ for (const [k, v] of Object.entries(parsed)) {
286
+ if (typeof v === 'string' && v)
287
+ out[k] = v;
288
+ }
289
+ return out;
290
+ }
291
+ catch {
292
+ return {};
293
+ }
294
+ }
275
295
  /** greprag inbox [--all] [--session <id>] | inbox keep <id> | inbox delete <id> | inbox watch */
276
296
  /** greprag desk — the machine's desk-line (reverse-RPC relay to the cloud).
277
297
  * desk run hold the line open and answer cloud questions (long-running)
@@ -405,8 +425,13 @@ async function inbox(args) {
405
425
  }
406
426
  const res = await apiGet(`${cfg.apiUrl}/v1/inbox/watchers`, cfg.apiKey);
407
427
  const watchers = (res.watchers || []);
428
+ const repos = readProjectRegistry();
429
+ const decorated = watchers.map(w => ({
430
+ ...w,
431
+ repo: w.repo || (w.project_name && repos[w.project_name]) || null,
432
+ }));
408
433
  if (json) {
409
- console.log(JSON.stringify({ watchers }, null, 2));
434
+ console.log(JSON.stringify({ watchers: decorated }, null, 2));
410
435
  return;
411
436
  }
412
437
  if (watchers.length === 0) {
@@ -415,13 +440,17 @@ async function inbox(args) {
415
440
  }
416
441
  console.log(`${watchers.length} live watcher(s):\n`);
417
442
  // Label = project · title — both resolved from the session's memory (the
418
- // auto-armed watcher carries neither on its WS tag). The 8-hex session id
443
+ // auto-armed watcher carries neither on its WS tag). The session short
419
444
  // rides in parens; orchestrator mode reads it back to address sends.
420
- for (const w of watchers) {
445
+ // Platform + local repo path (from ~/.greprag/projects.json) say which
446
+ // harness armed it and which checkout it's in.
447
+ for (const w of decorated) {
421
448
  const proj = w.project_name ? w.project_name : (w.wide ? '(tenant-wide)' : '(no project)');
422
449
  const label = w.title ? `${proj} · ${w.title}` : proj;
423
450
  const sess = w.session_id ? ` (${w.session_id})` : '';
424
- console.log(` ${label}${sess}`);
451
+ const plat = w.platform ? ` [${w.platform}]` : '';
452
+ const repo = w.repo ? ` ${w.repo}` : '';
453
+ console.log(` ${label}${sess}${plat}${repo}`);
425
454
  }
426
455
  return;
427
456
  }
@@ -666,7 +695,7 @@ function parseFileFlag(raw) {
666
695
  * is a session id; anything else is a project name. Project names that
667
696
  * look like UUIDs are rejected at registration time so this is unambiguous.
668
697
  * adr: adr/session-id-awareness.md, adr/address-grammar.md */
669
- const SESSION_ID_PATTERN = /^([0-9a-f]{8}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
698
+ const SESSION_ID_PATTERN = /^([0-9a-f]{8}|[0-9a-f]{16}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
670
699
  /** Parse a send address under the v0.12 grammar:
671
700
  * <handle>@greprag.com[/<target>]
672
701
  * where <target> is exactly one segment — a session UUID (or 8-hex short form)
@@ -719,7 +748,7 @@ function parseSendAddress(addr) {
719
748
  if (!target) {
720
749
  return { ok: false, error: `address "${addr}" has an empty target segment.` };
721
750
  }
722
- const kind = SESSION_ID_PATTERN.test(target) ? 'session' : 'project';
751
+ const kind = ((0, session_id_1.isSessionAddressTarget)(target) || SESSION_ID_PATTERN.test(target)) ? 'session' : 'project';
723
752
  return { ok: true, targetKind: kind, target };
724
753
  }
725
754
  /** Internal (self-desk) message types — KEEP IN SYNC with
@@ -1267,6 +1296,10 @@ Commands:
1267
1296
  Configure lifecycle hooks + anchor for Codex
1268
1297
  init --grok [--api-key <key>] [--tenant-id <handle>]
1269
1298
  Configure Grok Build hooks + skill + rules
1299
+ grok spawn [--cwd <path>] [--prompt "<text>"]
1300
+ Open a fresh Grok TUI in a new terminal
1301
+ (Windows schtasks bootloader — survives the
1302
+ tool Job Object; child arms its own watch)
1270
1303
  init --all [--root <path>] Standard init for cwd, then bulk-register every
1271
1304
  other git repo at depth 1 under <path> (default:
1272
1305
  parent of repo root). Each becomes inbox-addressable.
@@ -1306,11 +1339,12 @@ Inbox (email-style messaging across tenants):
1306
1339
  --session scopes to one specific session's view.
1307
1340
  --project filters by project name.
1308
1341
  --peek: NON-MUTATING — does not mark any message read.
1309
- inbox watchers [--json] List currently-attached live GrepRAG/Claude/OpenCode
1310
- watchers under this tenant. Each row: project ·
1311
- title (session_id) project + nano title resolved
1312
- from session memory. Codex task and repo/workspace
1313
- discovery uses codex_app.list_threads.
1342
+ inbox watchers [--json] List currently-attached live watchers
1343
+ (Claude / Codex / OpenCode / Grok). Each row:
1344
+ project · title (session_id) [platform] repo
1345
+ project + nano title from session memory, repo
1346
+ from ~/.greprag/projects.json. Codex task
1347
+ discovery also uses codex_app.list_threads.
1314
1348
  inbox watch Long-lived SSE stream — prints each message as it lands.
1315
1349
  Self-supervising by default: a parent process
1316
1350
  respawns the SSE loop (via CreateProcess) on any
@@ -1535,6 +1569,14 @@ Loadout (gift a skill bundle to another tenant — offer, never auto-install):
1535
1569
  loadout unequip <name> [--delete-skills]
1536
1570
  Stop a loadout's auto-inflate.
1537
1571
 
1572
+ Persona (tenant-scoped speaking instructions):
1573
+ persona list List built-in Persona templates.
1574
+ persona use <template> Activate a built-in Persona template.
1575
+ persona set <text>|--file <f>|--stdin
1576
+ Save and activate a custom Persona verbatim.
1577
+ persona show Show the active Persona.
1578
+ persona clear Remove the active Persona.
1579
+
1538
1580
  GrepRAG Instructions (canonical global/repo CLAUDE.md + AGENTS.md):
1539
1581
  instructions pull <file> [--scope global|repo] [--push]
1540
1582
  Pull disk instructions into canon.
@@ -1606,6 +1648,7 @@ const HELP_ALL_GROUPS = [
1606
1648
  ['delivery', delivery_1.runDelivery],
1607
1649
  ['assistant', assistant_1.runAssistant],
1608
1650
  ['instructions', agents_1.runInstructions],
1651
+ ['persona', persona_1.runPersona],
1609
1652
  ['doc', doc_1.runDoc],
1610
1653
  ['skill', skill_1.runSkill],
1611
1654
  ['opencode', opencode],
@@ -1893,8 +1936,10 @@ async function main() {
1893
1936
  case 'instructions': return (0, agents_1.runInstructions)(subArgs); // Canonical GrepRAG Instructions + rendered followers
1894
1937
  case 'agents': return (0, agents_1.runAgents)(subArgs); // Deprecated alias for GrepRAG Instructions
1895
1938
  case 'loadout': return (0, loadout_1.runLoadout)(subArgs); // Loadout: cross-tenant skill-bundle gifting (docs/loadout.md)
1939
+ case 'persona': return (0, persona_1.runPersona)(subArgs); // tenant-scoped speaking instructions
1896
1940
  case 'assistant': return (0, assistant_1.runAssistant)(subArgs); // designate this project as the tenant's Assistant (role flag)
1897
1941
  case 'opencode': return opencode(subArgs);
1942
+ case 'grok': return (0, grok_spawn_1.runGrok)(subArgs);
1898
1943
  default:
1899
1944
  console.error(`Unknown command: ${command}\n`);
1900
1945
  console.log(HELP);