@jhizzard/termdeck-stack 0.5.1 → 0.6.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.
@@ -15,9 +15,12 @@
15
15
  * without needing them in the parent shell.
16
16
  *
17
17
  * Behavior:
18
- * 1. Reads {transcript_path, cwd, session_id, sessionType?} from stdin (Claude
19
- * Code SessionEnd payload, or a future server-driven invocation for
20
- * non-Claude agents).
18
+ * 1. Reads {transcript_path, cwd, session_id, sessionType?, source_agent?}
19
+ * from stdin (Claude Code SessionEnd payload, or Sprint 50 T1 — a
20
+ * server-driven invocation for non-Claude agents). source_agent
21
+ * defaults to 'claude' when absent (Claude Code's existing hook
22
+ * payload doesn't carry it; the TermDeck server's per-adapter
23
+ * onPanelClose interceptor sets it explicitly for codex/gemini/grok).
21
24
  * 2. Loads ~/.termdeck/secrets.env into process.env if any required key is
22
25
  * absent OR is a literal `${VAR}` placeholder (Sprint 47.5 hotfix
23
26
  * discipline — Claude Code does not expand `${VAR}` in MCP env, and we
@@ -268,6 +271,57 @@ function parseGeminiJson(raw) {
268
271
  return messages;
269
272
  }
270
273
 
274
+ // Sprint 50 T1 — Grok parser. Mirrors packages/server/src/agent-adapters/grok.js
275
+ // parseTranscript: accepts either a JSON array or JSONL of `{role, content}`
276
+ // objects, where content is a string OR an array of `{type, text, ...}` parts
277
+ // (AI SDK provider shape). Tool-call / tool-result / reasoning parts are
278
+ // skipped — only the `type:'text'` parts contribute to the summary.
279
+ //
280
+ // The JSON envelope is produced server-side by the Grok adapter's
281
+ // `resolveTranscriptPath` (which extracts from ~/.grok/grok.db SQLite via
282
+ // better-sqlite3 and writes a tempfile). The hook itself never opens grok.db
283
+ // — that would require better-sqlite3 to be reachable from ~/.claude/hooks/,
284
+ // which isn't part of the install contract. The transcript_path the server
285
+ // hands the hook is the tempfile, and the sessionType in the payload is
286
+ // 'grok' so this parser is the one selected.
287
+ function parseGrokJson(raw) {
288
+ if (typeof raw !== 'string' || raw.length === 0) return [];
289
+ let messages = null;
290
+ try {
291
+ const parsed = JSON.parse(raw);
292
+ if (Array.isArray(parsed)) messages = parsed;
293
+ } catch (_) { /* fall through to JSONL */ }
294
+ if (!messages) {
295
+ messages = [];
296
+ for (const line of raw.split('\n')) {
297
+ const trimmed = line.trim();
298
+ if (!trimmed) continue;
299
+ try {
300
+ const obj = JSON.parse(trimmed);
301
+ if (obj && typeof obj === 'object') messages.push(obj);
302
+ } catch (_) { continue; }
303
+ }
304
+ }
305
+ const out = [];
306
+ for (const msg of messages) {
307
+ if (!msg || typeof msg !== 'object') continue;
308
+ const role = msg.role;
309
+ if (role !== 'user' && role !== 'assistant') continue;
310
+ const content = msg.content;
311
+ let text = '';
312
+ if (typeof content === 'string') {
313
+ text = content;
314
+ } else if (Array.isArray(content)) {
315
+ text = content
316
+ .filter((c) => c && c.type === 'text' && typeof c.text === 'string')
317
+ .map((c) => c.text)
318
+ .join(' ');
319
+ }
320
+ if (text) out.push({ role, content: text.slice(0, 400) });
321
+ }
322
+ return out;
323
+ }
324
+
271
325
  function parseAutoDetect(raw) {
272
326
  // Fallback when sessionType is absent. Tries Gemini's single-JSON shape
273
327
  // first (cheap to detect — starts with `{` and has a top-level `messages`
@@ -327,10 +381,10 @@ const TRANSCRIPT_PARSERS = {
327
381
  'claude-code': parseClaudeJsonl,
328
382
  'codex': parseCodexJsonl,
329
383
  'gemini': parseGeminiJson,
330
- // Sprint 45 T3 — grok parser entry goes here once the adapter lands.
331
- // Source-of-truth lives in packages/server/src/agent-adapters/grok.js;
332
- // mirror that adapter's parseTranscript function body into this dispatch
333
- // table at sprint close so the bundled hook can ingest grok transcripts.
384
+ // Sprint 50 T1 — grok parser. Server-side `resolveTranscriptPath` extracts
385
+ // ~/.grok/grok.db rows via better-sqlite3 and writes a JSON envelope to a
386
+ // tempfile; the hook reads that tempfile with parseGrokJson here.
387
+ 'grok': parseGrokJson,
334
388
  };
335
389
  const DEFAULT_SESSION_TYPE = 'auto';
336
390
 
@@ -390,7 +444,25 @@ async function embedText(text, openaiKey) {
390
444
  }
391
445
  }
392
446
 
393
- async function postMemoryItem({ supabaseUrl, supabaseKey, content, embedding, project, sessionId }) {
447
+ // Sprint 50 T2: every row written by this hook carries an LLM-provenance
448
+ // tag (memory_items.source_agent). Defaults to 'claude' for backwards
449
+ // compat with Claude Code's existing SessionEnd payload, which doesn't
450
+ // supply the field; TermDeck server's per-adapter onPanelClose
451
+ // interceptor (Sprint 50 T1) sets it explicitly to 'codex'/'gemini'/'grok'
452
+ // for non-Claude panels. The set is open-ended on the server side; this
453
+ // constant gates only the spelling-mistake/empty-string case.
454
+ const ALLOWED_SOURCE_AGENTS = new Set([
455
+ 'claude', 'codex', 'gemini', 'grok', 'orchestrator',
456
+ ]);
457
+
458
+ function normalizeSourceAgent(raw) {
459
+ if (typeof raw !== 'string') return 'claude';
460
+ const v = raw.trim().toLowerCase();
461
+ if (!v) return 'claude';
462
+ return ALLOWED_SOURCE_AGENTS.has(v) ? v : 'claude';
463
+ }
464
+
465
+ async function postMemoryItem({ supabaseUrl, supabaseKey, content, embedding, project, sessionId, sourceAgent }) {
394
466
  try {
395
467
  const res = await fetch(`${supabaseUrl}/rest/v1/memory_items`, {
396
468
  method: 'POST',
@@ -407,6 +479,7 @@ async function postMemoryItem({ supabaseUrl, supabaseKey, content, embedding, pr
407
479
  category: 'workflow',
408
480
  project,
409
481
  source_session_id: sessionId || null,
482
+ source_agent: normalizeSourceAgent(sourceAgent),
410
483
  }),
411
484
  });
412
485
  if (!res.ok) {
@@ -442,6 +515,17 @@ async function processStdinPayload(input) {
442
515
  process.env.TERMDECK_SESSION_TYPE ||
443
516
  DEFAULT_SESSION_TYPE;
444
517
 
518
+ // Sprint 50 T2: provenance tag the row with the LLM that produced it.
519
+ // Default 'claude' — Claude Code's native SessionEnd payload doesn't
520
+ // carry source_agent, so any unset path is implicitly Claude. The
521
+ // TermDeck server's per-adapter onPanelClose interceptor (Sprint 50 T1)
522
+ // sets it explicitly for non-Claude panels.
523
+ const sourceAgent =
524
+ data.source_agent ||
525
+ data.sourceAgent ||
526
+ process.env.TERMDECK_SOURCE_AGENT ||
527
+ 'claude';
528
+
445
529
  if (!transcriptPath) { log('no-transcript-path: skipping'); return; }
446
530
 
447
531
  let stat;
@@ -472,9 +556,10 @@ async function processStdinPayload(input) {
472
556
  embedding,
473
557
  project,
474
558
  sessionId,
559
+ sourceAgent,
475
560
  });
476
561
 
477
- if (ok) log(`ingested: project="${project}" session=${sessionId} bytes=${summary.length} sessionType=${sessionType}`);
562
+ if (ok) log(`ingested: project="${project}" session=${sessionId} bytes=${summary.length} sessionType=${sessionType} sourceAgent=${normalizeSourceAgent(sourceAgent)}`);
478
563
  }
479
564
 
480
565
  // Module-export contract for testability. When run as a script (require.main === module),
@@ -502,7 +587,11 @@ if (require.main === module) {
502
587
  parseClaudeJsonl,
503
588
  parseCodexJsonl,
504
589
  parseGeminiJson,
590
+ parseGrokJson,
505
591
  parseAutoDetect,
506
592
  selectTranscriptParser,
593
+ // Sprint 50 T2 — source_agent provenance plumbing.
594
+ normalizeSourceAgent,
595
+ ALLOWED_SOURCE_AGENTS,
507
596
  };
508
597
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhizzard/termdeck-stack",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "One-command installer for the TermDeck developer memory stack: TermDeck + Mnestra + Rumen + Supabase MCP",
5
5
  "bin": {
6
6
  "termdeck-stack": "./src/index.js"