@jhizzard/termdeck 0.7.0 → 0.7.2
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/package.json +1 -1
- package/packages/cli/src/doctor.js +4 -4
- package/packages/cli/src/update-check.js +5 -5
- package/packages/server/src/mnestra-bridge/index.js +8 -0
- package/packages/server/src/rag.js +39 -12
- package/packages/server/src/session.js +14 -2
- package/packages/server/src/theme-resolver.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Browser-based terminal multiplexer with metadata overlays, panel flashback memory recall, and AI-aware session management",
|
|
5
5
|
"bin": {
|
|
6
6
|
"termdeck": "./packages/cli/src/index.js"
|
|
@@ -58,7 +58,7 @@ async function _detectInstalled(pkg) {
|
|
|
58
58
|
child = spawn('npm', ['ls', '-g', pkg, '--depth=0', '--json'], {
|
|
59
59
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
60
60
|
});
|
|
61
|
-
} catch {
|
|
61
|
+
} catch (_err) {
|
|
62
62
|
return resolve(null);
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -80,7 +80,7 @@ async function _detectInstalled(pkg) {
|
|
|
80
80
|
const dep = parsed && parsed.dependencies && parsed.dependencies[pkg];
|
|
81
81
|
if (dep && typeof dep.version === 'string') return resolve(dep.version);
|
|
82
82
|
return resolve(null);
|
|
83
|
-
} catch {
|
|
83
|
+
} catch (_err) {
|
|
84
84
|
return resolve(null);
|
|
85
85
|
}
|
|
86
86
|
});
|
|
@@ -118,13 +118,13 @@ async function _fetchLatest(pkg) {
|
|
|
118
118
|
const parsed = JSON.parse(body);
|
|
119
119
|
if (parsed && typeof parsed.latest === 'string') return done(parsed.latest);
|
|
120
120
|
return done(null);
|
|
121
|
-
} catch {
|
|
121
|
+
} catch (_err) {
|
|
122
122
|
return done(null);
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
125
|
res.on('error', () => done(null));
|
|
126
126
|
});
|
|
127
|
-
} catch {
|
|
127
|
+
} catch (_err) {
|
|
128
128
|
return done(null);
|
|
129
129
|
}
|
|
130
130
|
req.on('timeout', () => {
|
|
@@ -38,7 +38,7 @@ function defaultPackageVersion() {
|
|
|
38
38
|
try {
|
|
39
39
|
const pkg = require(path.join(__dirname, '..', '..', '..', 'package.json'));
|
|
40
40
|
return pkg && pkg.version ? String(pkg.version) : null;
|
|
41
|
-
} catch {
|
|
41
|
+
} catch (_err) {
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -68,7 +68,7 @@ function readCache(cachePath) {
|
|
|
68
68
|
if (!parsed || typeof parsed !== 'object') return null;
|
|
69
69
|
if (parsed.version !== CACHE_VERSION) return null;
|
|
70
70
|
return parsed;
|
|
71
|
-
} catch {
|
|
71
|
+
} catch (_err) {
|
|
72
72
|
return null;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -77,7 +77,7 @@ function writeCache(cachePath, data) {
|
|
|
77
77
|
try {
|
|
78
78
|
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
79
79
|
fs.writeFileSync(cachePath, JSON.stringify(data, null, 2), 'utf8');
|
|
80
|
-
} catch {
|
|
80
|
+
} catch (_err) {
|
|
81
81
|
// Read-only home, ENOSPC, race with another process — all benign here.
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -91,7 +91,7 @@ async function fetchLatest(registryUrl) {
|
|
|
91
91
|
const json = await res.json();
|
|
92
92
|
const latest = json && json.latest;
|
|
93
93
|
return isValidSemver(latest) ? latest : null;
|
|
94
|
-
} catch {
|
|
94
|
+
} catch (_err) {
|
|
95
95
|
return null;
|
|
96
96
|
} finally {
|
|
97
97
|
clearTimeout(timeout);
|
|
@@ -144,7 +144,7 @@ async function checkAndPrintHint(_config, opts) {
|
|
|
144
144
|
' Or run `termdeck doctor` for the whole stack. ' +
|
|
145
145
|
'Suppress with TERMDECK_NO_UPDATE_CHECK=1.'
|
|
146
146
|
);
|
|
147
|
-
} catch {
|
|
147
|
+
} catch (_err) {
|
|
148
148
|
// Never throw from a fire-and-forget hook.
|
|
149
149
|
}
|
|
150
150
|
}
|
|
@@ -231,13 +231,21 @@ function createBridge(config) {
|
|
|
231
231
|
// back to resolving the session's cwd against config.projects so queries
|
|
232
232
|
// don't leak into unrelated repos via basename collisions.
|
|
233
233
|
let effectiveProject = project;
|
|
234
|
+
let projectSource = project ? 'explicit' : 'none';
|
|
234
235
|
if (!effectiveProject) {
|
|
235
236
|
const ctxCwd = cwd || (sessionContext && sessionContext.cwd);
|
|
236
237
|
if (ctxCwd) {
|
|
237
238
|
effectiveProject = resolveProjectName(ctxCwd, config);
|
|
239
|
+
projectSource = effectiveProject ? 'cwd' : 'none';
|
|
238
240
|
}
|
|
239
241
|
}
|
|
240
242
|
|
|
243
|
+
// Sprint 34 observability: every Flashback query announces its project tag
|
|
244
|
+
// and how it was resolved. If the writer chain is ever mis-emitting a tag
|
|
245
|
+
// (as happened pre-v0.7.2 with the `chopin-nashville` regression from the
|
|
246
|
+
// out-of-repo session-end hook), the mismatch surfaces here at query time.
|
|
247
|
+
console.log(`[mnestra-bridge] query project=${effectiveProject ?? 'ALL'} source=${searchAll ? 'searchAll' : projectSource} mode=${mode}`);
|
|
248
|
+
|
|
241
249
|
switch (mode) {
|
|
242
250
|
case 'webhook':
|
|
243
251
|
return queryWebhook({ question, project: effectiveProject, searchAll });
|
|
@@ -97,53 +97,80 @@ class RAGIntegration {
|
|
|
97
97
|
|
|
98
98
|
// Canonical project tag for a session. Prefers the explicit config.yaml name
|
|
99
99
|
// (set at session creation), falls back to cwd → config.projects resolution.
|
|
100
|
+
// Returns { tag, source } so callers can audit which resolution path fired —
|
|
101
|
+
// explicit (session.meta.project), cwd (cwd matched a config.projects entry),
|
|
102
|
+
// fallback (cwd basename), or null (no cwd, no config). Sprint 34: the
|
|
103
|
+
// chopin-nashville mis-tag came from an out-of-repo writer, but source
|
|
104
|
+
// attribution here makes any future TermDeck-side regression visible in logs.
|
|
105
|
+
_resolveProjectAttribution(session) {
|
|
106
|
+
if (session.meta.project) return { tag: session.meta.project, source: 'explicit' };
|
|
107
|
+
const tag = resolveProjectName(session.meta.cwd, this.config);
|
|
108
|
+
if (!tag) return { tag: null, source: 'none' };
|
|
109
|
+
const cwdResolved = session.meta.cwd && path.resolve(String(session.meta.cwd).replace(/^~/, os.homedir()));
|
|
110
|
+
const matchedConfig = !!cwdResolved && Object.values((this.config && this.config.projects) || {}).some((def) => {
|
|
111
|
+
if (!def || typeof def.path !== 'string') return false;
|
|
112
|
+
const p = path.resolve(def.path.replace(/^~/, os.homedir()));
|
|
113
|
+
return cwdResolved === p || cwdResolved.startsWith(p + path.sep);
|
|
114
|
+
});
|
|
115
|
+
return { tag, source: matchedConfig ? 'cwd' : 'fallback' };
|
|
116
|
+
}
|
|
117
|
+
|
|
100
118
|
_projectFor(session) {
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
return this._resolveProjectAttribution(session).tag;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Single attribution + observability point for session events. Logs once per
|
|
123
|
+
// record() so future drift in the project-resolution chain (e.g. a writer
|
|
124
|
+
// that bypasses _projectFor and stamps a raw path segment) is visible in
|
|
125
|
+
// stdout. Cheap: ~one log line per RAG event, off the hot path.
|
|
126
|
+
_recordForSession(session, eventType, payload) {
|
|
127
|
+
const { tag, source } = this._resolveProjectAttribution(session);
|
|
128
|
+
console.log(`[rag] write project=${tag ?? 'null'} source=${source} session=${session.id} event=${eventType}`);
|
|
129
|
+
this.record(session.id, eventType, payload, tag);
|
|
103
130
|
}
|
|
104
131
|
|
|
105
132
|
// Event types to record
|
|
106
133
|
onSessionCreated(session) {
|
|
107
|
-
this.
|
|
134
|
+
this._recordForSession(session, 'session_created', {
|
|
108
135
|
type: session.meta.type,
|
|
109
136
|
command: session.meta.command,
|
|
110
137
|
cwd: session.meta.cwd,
|
|
111
138
|
reason: session.meta.reason
|
|
112
|
-
}
|
|
139
|
+
});
|
|
113
140
|
}
|
|
114
141
|
|
|
115
142
|
onCommandExecuted(session, command, outputSnippet) {
|
|
116
|
-
this.
|
|
143
|
+
this._recordForSession(session, 'command_executed', {
|
|
117
144
|
command,
|
|
118
145
|
output_snippet: outputSnippet?.slice(0, 500), // Truncate for storage
|
|
119
146
|
type: session.meta.type
|
|
120
|
-
}
|
|
147
|
+
});
|
|
121
148
|
}
|
|
122
149
|
|
|
123
150
|
onStatusChanged(session, oldStatus, newStatus) {
|
|
124
|
-
this.
|
|
151
|
+
this._recordForSession(session, 'status_changed', {
|
|
125
152
|
from: oldStatus,
|
|
126
153
|
to: newStatus,
|
|
127
154
|
detail: session.meta.statusDetail,
|
|
128
155
|
type: session.meta.type
|
|
129
|
-
}
|
|
156
|
+
});
|
|
130
157
|
}
|
|
131
158
|
|
|
132
159
|
onSessionEnded(session) {
|
|
133
|
-
this.
|
|
160
|
+
this._recordForSession(session, 'session_ended', {
|
|
134
161
|
type: session.meta.type,
|
|
135
162
|
duration_ms: Date.now() - new Date(session.meta.createdAt).getTime(),
|
|
136
163
|
command_count: session.meta.lastCommands.length,
|
|
137
164
|
exit_code: session.meta.exitCode
|
|
138
|
-
}
|
|
165
|
+
});
|
|
139
166
|
}
|
|
140
167
|
|
|
141
168
|
onFileEdited(session, filepath, editType) {
|
|
142
|
-
this.
|
|
169
|
+
this._recordForSession(session, 'file_edited', {
|
|
143
170
|
filepath,
|
|
144
171
|
edit_type: editType,
|
|
145
172
|
type: session.meta.type
|
|
146
|
-
}
|
|
173
|
+
});
|
|
147
174
|
}
|
|
148
175
|
|
|
149
176
|
// Circuit breaker check — returns true if pushes to this table are disabled.
|
|
@@ -69,7 +69,15 @@ const PATTERNS = {
|
|
|
69
69
|
// Stricter line-anchored variant for Claude Code, whose tool output (grep
|
|
70
70
|
// results, test logs, file contents) routinely mentions "Error" mid-line
|
|
71
71
|
// without representing an actual failure of the agent itself.
|
|
72
|
-
errorLineStart: /^\s*(error|Error|ERROR|exception|Exception|Traceback|fatal|FATAL|segmentation fault|panic|EACCES|ECONNREFUSED|ENOENT|command not found|undefined reference|cannot find module|failed with exit code|No such file or directory|Permission denied)\b/m
|
|
72
|
+
errorLineStart: /^\s*(error|Error|ERROR|exception|Exception|Traceback|fatal|FATAL|segmentation fault|panic|EACCES|ECONNREFUSED|ENOENT|command not found|undefined reference|cannot find module|failed with exit code|No such file or directory|Permission denied)\b/m,
|
|
73
|
+
// Sprint 33: PATTERNS.error misses the most common Unix shell errors —
|
|
74
|
+
// `cat: /foo: No such file or directory`, `bash: foo: command not found`,
|
|
75
|
+
// `rm: cannot remove ...: Permission denied`. These have a colon-prefix
|
|
76
|
+
// shape (`<cmd>: ...: <phrase>`) that distinguishes them from prose
|
|
77
|
+
// mentioning the same words. Each branch requires either the colon-prefix
|
|
78
|
+
// structure or a stand-alone anchored keyword. Validated against an
|
|
79
|
+
// adversarial prose suite (see tests/analyzer-error-fixtures.test.js).
|
|
80
|
+
shellError: /(?:^|\n)(?:[^\n]*:\s+(?:.*?:\s+)?(?:No such file or directory|Permission denied|Is a directory|Not a directory|command not found)\b|[^\n]*?\(\d+\)\s+Could not resolve host\b|\s*ModuleNotFoundError:\s+\S|\s*Segmentation fault\b|\s*fatal:\s+\S)/m
|
|
73
81
|
};
|
|
74
82
|
|
|
75
83
|
class Session {
|
|
@@ -345,7 +353,11 @@ class Session {
|
|
|
345
353
|
const pattern = this.meta.type === 'claude-code'
|
|
346
354
|
? PATTERNS.errorLineStart
|
|
347
355
|
: PATTERNS.error;
|
|
348
|
-
|
|
356
|
+
// Sprint 33 fix: the structured patterns above miss `cat: /foo: No such
|
|
357
|
+
// file or directory` and friends — the most common Unix shell error
|
|
358
|
+
// shapes Josh hits day-to-day. Fall through to PATTERNS.shellError so
|
|
359
|
+
// the analyzer flips status='errored' and Flashback can fire.
|
|
360
|
+
if (!pattern.test(clean) && !PATTERNS.shellError.test(clean)) return;
|
|
349
361
|
|
|
350
362
|
const oldStatus = this.meta.status;
|
|
351
363
|
this.meta.status = 'errored';
|
|
@@ -40,7 +40,7 @@ function getCurrentConfig() {
|
|
|
40
40
|
const { loadConfig } = require('./config');
|
|
41
41
|
_configCache = { mtimeMs: stat.mtimeMs, value: loadConfig(), frozen: false };
|
|
42
42
|
return _configCache.value;
|
|
43
|
-
} catch {
|
|
43
|
+
} catch (_err) {
|
|
44
44
|
return _configCache.value || {};
|
|
45
45
|
}
|
|
46
46
|
}
|