auxilo-mcp 0.8.2 → 0.9.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,157 @@
1
+ /**
2
+ * scripts/sources/generic-jsonl.js — Generic Claude-style JSONL Source (UC-1)
3
+ *
4
+ * Best-effort normalizer for clients that speak a Claude-style JSONL dialect
5
+ * (one JSON object per line carrying `role`/`content` or
6
+ * `message.role`/`message.content`) but don't have a dedicated adapter yet
7
+ * (cursor, windsurf, codex, copilot, factory). capture-core forwards the
8
+ * client id via `--source <id>`; runner.js falls back to this adapter for
9
+ * unknown ids in single-file mode and tags uploads with that id.
10
+ *
11
+ * FORMAT PROBE (UC §5 risk rule): at least 80% of non-empty lines must parse
12
+ * as JSON objects, or readSession() refuses (returns null with a single
13
+ * stderr line). Never throws on bad format, never mis-parses garbage into a
14
+ * transcript.
15
+ *
16
+ * Poll-mode methods are inert: there is no canonical on-disk location for a
17
+ * "generic" client, so detect() → false and discoverSessions() → [].
18
+ *
19
+ * @module sources/generic-jsonl
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const fs = require('fs');
25
+ const { TranscriptSource } = require('./source.interface');
26
+
27
+ /** Minimum fraction of non-empty lines that must parse as JSON objects. */
28
+ const PROBE_MIN_JSON_RATIO = 0.8;
29
+
30
+ /** Map dialect role names to our normalized two-role vocabulary. */
31
+ function normalizeRole(role) {
32
+ const r = String(role || '').toLowerCase();
33
+ if (r === 'user' || r === 'human') return 'user';
34
+ if (r === 'assistant' || r === 'model' || r === 'gemini' || r === 'ai') return 'assistant';
35
+ return null; // system / tool / function / unknown → skip
36
+ }
37
+
38
+ /** Extract plain text from a string | block-array | {text} content value. */
39
+ function extractText(raw) {
40
+ if (raw == null) return '';
41
+ if (typeof raw === 'string') return raw;
42
+ if (Array.isArray(raw)) {
43
+ return raw
44
+ .filter(b => b && (b.type === 'text' || b.type === 'input_text' || typeof b.text === 'string'))
45
+ .map(b => b.text || '')
46
+ .filter(Boolean)
47
+ .join('\n');
48
+ }
49
+ if (typeof raw === 'object' && typeof raw.text === 'string') return raw.text;
50
+ return '';
51
+ }
52
+
53
+ /**
54
+ * Normalize an array of raw JSONL lines into `[user]:`/`[assistant]:` turns.
55
+ * Shared with the antigravity adapter. Skips tool/function internals and any
56
+ * line whose role doesn't normalize to user/assistant.
57
+ *
58
+ * @param {string[]} lines - non-empty JSONL lines
59
+ * @returns {string} normalized transcript (may be empty)
60
+ */
61
+ function normalizeJsonlLines(lines) {
62
+ const turns = [];
63
+ for (const line of lines) {
64
+ let entry;
65
+ try { entry = JSON.parse(line); } catch { continue; }
66
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
67
+
68
+ // Skip explicit tool/function internals regardless of role fields.
69
+ const t = String(entry.type || '').toLowerCase();
70
+ if (['tool_use', 'tool_result', 'function_call', 'function_response', 'tool'].includes(t)) continue;
71
+
72
+ const role = normalizeRole(entry.message?.role ?? entry.role ?? entry.sender ?? entry.type);
73
+ if (!role) continue;
74
+
75
+ const text = extractText(entry.message?.content ?? entry.content ?? entry.text);
76
+ if (text.trim().length > 0) turns.push(`[${role}]: ${text}`);
77
+ }
78
+ return turns.join('\n\n');
79
+ }
80
+
81
+ /**
82
+ * Format probe: true when the file looks like JSONL (≥80% of non-empty lines
83
+ * parse as JSON objects).
84
+ *
85
+ * @param {string[]} lines - non-empty lines
86
+ * @returns {boolean}
87
+ */
88
+ function probeJsonl(lines) {
89
+ if (lines.length === 0) return false;
90
+ let objects = 0;
91
+ for (const line of lines) {
92
+ try {
93
+ const v = JSON.parse(line);
94
+ if (v && typeof v === 'object' && !Array.isArray(v)) objects++;
95
+ } catch { /* not JSON */ }
96
+ }
97
+ return objects / lines.length >= PROBE_MIN_JSON_RATIO;
98
+ }
99
+
100
+ class GenericJsonlSource extends TranscriptSource {
101
+ static id = 'generic-jsonl';
102
+ static displayName = 'Generic Claude-style JSONL';
103
+ static version = '1.0.0';
104
+
105
+ /**
106
+ * @param {object} [config]
107
+ * @param {string} [config.id] - actual client id this instance represents
108
+ * (e.g. "cursor", "windsurf"); used as the upload source tag.
109
+ */
110
+ constructor(config = {}) {
111
+ super(config);
112
+ // The marketplace source tag is the CLIENT id, not "generic-jsonl".
113
+ this.type = config.id || GenericJsonlSource.id;
114
+ this.label = config.label || `${this.type} (generic JSONL)`;
115
+ }
116
+
117
+ /** No canonical data dir for a generic client — never auto-detected. */
118
+ async detect() { return false; }
119
+
120
+ /** Poll discovery unsupported — hook-fired single-file mode only. */
121
+ async discoverSessions({ since } = {}) { return []; }
122
+
123
+ /**
124
+ * Read + normalize a JSONL transcript. Returns null (single stderr log
125
+ * line) when the format probe refuses — never throws on bad format.
126
+ */
127
+ async readSession(sessionRef) {
128
+ const filePath = sessionRef.path;
129
+ if (!filePath || !fs.existsSync(filePath)) {
130
+ throw new Error(`Transcript file not found: ${filePath}`);
131
+ }
132
+ const content = fs.readFileSync(filePath, 'utf-8');
133
+ const lines = content.split('\n').filter(l => l.trim());
134
+
135
+ if (!probeJsonl(lines)) {
136
+ process.stderr.write(`[generic-jsonl] format probe refused ${filePath} (source=${this.type}) — skipping\n`);
137
+ return null;
138
+ }
139
+
140
+ const transcript = normalizeJsonlLines(lines);
141
+ return {
142
+ transcript,
143
+ metadata: {
144
+ sessionId: sessionRef.sessionId,
145
+ source: this.type,
146
+ adapter: GenericJsonlSource.id,
147
+ mtime: sessionRef.mtime,
148
+ bytes: sessionRef.bytes,
149
+ },
150
+ };
151
+ }
152
+
153
+ /** Hook wiring is the installer's job (UC-1). */
154
+ async registerSessionEndHook(cb) { return null; }
155
+ }
156
+
157
+ module.exports = { GenericJsonlSource, normalizeJsonlLines, probeJsonl, normalizeRole, extractText };