crosspad-mcp-server 8.1.2 → 9.1.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.
Files changed (69) hide show
  1. package/.claude-plugin/marketplace.json +13 -0
  2. package/.claude-plugin/plugin.json +14 -0
  3. package/.mcp.json +9 -0
  4. package/README.md +95 -0
  5. package/dist/config.d.ts +3 -0
  6. package/dist/config.js +8 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +381 -57
  10. package/dist/index.js.map +1 -1
  11. package/dist/tools/idf-flash.js +2 -2
  12. package/dist/tools/idf-flash.js.map +1 -1
  13. package/dist/tools/idf-monitor.d.ts +3 -1
  14. package/dist/tools/idf-monitor.js +19 -3
  15. package/dist/tools/idf-monitor.js.map +1 -1
  16. package/dist/tools/midi.js +20 -16
  17. package/dist/tools/midi.js.map +1 -1
  18. package/dist/tools/symbols.d.ts +3 -1
  19. package/dist/tools/symbols.js +31 -1
  20. package/dist/tools/symbols.js.map +1 -1
  21. package/dist/tools/trace-buffer.d.ts +40 -0
  22. package/dist/tools/trace-buffer.js +74 -0
  23. package/dist/tools/trace-buffer.js.map +1 -0
  24. package/dist/tools/trace-device.d.ts +10 -0
  25. package/dist/tools/trace-device.js +26 -0
  26. package/dist/tools/trace-device.js.map +1 -0
  27. package/dist/tools/trace-doctor.d.ts +43 -0
  28. package/dist/tools/trace-doctor.js +150 -0
  29. package/dist/tools/trace-doctor.js.map +1 -0
  30. package/dist/tools/trace-export.d.ts +4 -0
  31. package/dist/tools/trace-export.js +14 -0
  32. package/dist/tools/trace-export.js.map +1 -0
  33. package/dist/tools/trace-session.d.ts +118 -0
  34. package/dist/tools/trace-session.js +346 -0
  35. package/dist/tools/trace-session.js.map +1 -0
  36. package/dist/tools/trace-symbols.d.ts +24 -0
  37. package/dist/tools/trace-symbols.js +44 -0
  38. package/dist/tools/trace-symbols.js.map +1 -0
  39. package/dist/tools/trace-webui.d.ts +53 -0
  40. package/dist/tools/trace-webui.js +222 -0
  41. package/dist/tools/trace-webui.js.map +1 -0
  42. package/dist/utils/device.d.ts +5 -0
  43. package/dist/utils/device.js +43 -15
  44. package/dist/utils/device.js.map +1 -1
  45. package/dist/utils/exec.js +26 -0
  46. package/dist/utils/exec.js.map +1 -1
  47. package/dist/utils/userConfig.d.ts +13 -0
  48. package/dist/utils/userConfig.js +43 -0
  49. package/dist/utils/userConfig.js.map +1 -0
  50. package/package.json +12 -4
  51. package/skills/crosspad/SKILL.md +58 -0
  52. package/skills/crosspad/reference/faq.md +40 -0
  53. package/skills/crosspad/reference/install.md +84 -0
  54. package/skills/crosspad/reference/repos.md +29 -0
  55. package/skills/crosspad/reference/role-contributor.md +64 -0
  56. package/skills/crosspad/reference/role-fw-dev.md +44 -0
  57. package/skills/crosspad/reference/role-user.md +49 -0
  58. package/skills/crosspad/reference/tools.md +68 -0
  59. package/skills/crosspad/scripts/doctor.sh +65 -0
  60. package/skills/crosspad/scripts/setup.sh +53 -0
  61. package/skills/swd-tracer/SKILL.md +135 -0
  62. package/skills/swd-tracer/reference/signals.md +42 -0
  63. package/skills/swd-tracer/scripts/detect-env.sh +61 -0
  64. package/skills/swd-tracer/scripts/install-udev-rules.sh +25 -0
  65. package/skills/swd-tracer/scripts/setup-venv.sh +26 -0
  66. package/tracer/PROTOCOL.md +260 -0
  67. package/tracer/README.md +327 -0
  68. package/tracer/swd_tracer.py +1083 -0
  69. package/tracer/ui/index.html +834 -0
@@ -0,0 +1,74 @@
1
+ export class TraceBuffer {
2
+ signals;
3
+ capacity;
4
+ buf = [];
5
+ constructor(signals, capacity) {
6
+ this.signals = signals;
7
+ this.capacity = capacity;
8
+ }
9
+ push(s) {
10
+ this.buf.push(s);
11
+ if (this.buf.length > this.capacity)
12
+ this.buf.shift();
13
+ }
14
+ count() { return this.buf.length; }
15
+ signalNames() { return [...this.signals]; }
16
+ /** Add a signal to the watched set (no-op if already present). Does not touch
17
+ * stored samples — history of a previously-removed signal survives in the ring. */
18
+ addSignal(name) {
19
+ if (!this.signals.includes(name))
20
+ this.signals.push(name);
21
+ }
22
+ /** Drop a signal from the watched set (no-op if absent). Stored samples are
23
+ * left intact so already-captured history remains queryable until it ages out. */
24
+ removeSignal(name) {
25
+ const i = this.signals.indexOf(name);
26
+ if (i >= 0)
27
+ this.signals.splice(i, 1);
28
+ }
29
+ stats(sig) {
30
+ const pts = this.buf.filter((s) => sig in s.values);
31
+ if (pts.length === 0)
32
+ return null;
33
+ let min = Infinity, max = -Infinity, sum = 0;
34
+ for (const p of pts) {
35
+ const v = p.values[sig];
36
+ if (v < min)
37
+ min = v;
38
+ if (v > max)
39
+ max = v;
40
+ sum += v;
41
+ }
42
+ const first = pts[0], last = pts[pts.length - 1];
43
+ const dt = last.t - first.t;
44
+ const slope = dt !== 0 ? (last.values[sig] - first.values[sig]) / dt : 0;
45
+ return { min, max, avg: sum / pts.length, last: last.values[sig], slope, first_t: first.t, last_t: last.t, n: pts.length };
46
+ }
47
+ downsample(sig, maxPoints, window) {
48
+ let pts = this.buf
49
+ .filter((s) => sig in s.values)
50
+ .map((s) => ({ t: s.t, v: s.values[sig] }));
51
+ if (window) {
52
+ const lo = window.fromT ?? -Infinity, hi = window.toT ?? Infinity;
53
+ pts = pts.filter((p) => p.t >= lo && p.t <= hi);
54
+ }
55
+ if (pts.length <= maxPoints)
56
+ return pts;
57
+ const last = pts[pts.length - 1];
58
+ if (maxPoints <= 1)
59
+ return [last];
60
+ const stride = Math.ceil(pts.length / maxPoints);
61
+ const out = [];
62
+ for (let i = 0; i < pts.length - 1; i += stride) {
63
+ out.push(pts[i]);
64
+ if (out.length >= maxPoints - 1)
65
+ break;
66
+ }
67
+ if (out[out.length - 1].t !== last.t)
68
+ out.push(last);
69
+ return out;
70
+ }
71
+ /** Read-only view of stored samples (oldest→newest). */
72
+ samples() { return this.buf; }
73
+ }
74
+ //# sourceMappingURL=trace-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-buffer.js","sourceRoot":"","sources":["../../src/tools/trace-buffer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export interface DeviceStateResult {
2
+ success: boolean;
3
+ regs?: Record<string, number | null>;
4
+ decoded?: Record<string, unknown>;
5
+ accessible?: boolean;
6
+ error?: string;
7
+ }
8
+ /** Pure: extract the device_state JSON from mixed daemon output (scan lines reverse). */
9
+ export declare function parseDeviceState(out: string): DeviceStateResult;
10
+ export declare function getDeviceState(signal?: AbortSignal): Promise<DeviceStateResult>;
@@ -0,0 +1,26 @@
1
+ import { runArgvStream } from "../utils/exec.js";
2
+ import { daemonPath, resolvedPython } from "./trace-symbols.js";
3
+ /** Pure: extract the device_state JSON from mixed daemon output (scan lines reverse). */
4
+ export function parseDeviceState(out) {
5
+ const lines = out.split("\n").map((l) => l.trim()).filter(Boolean);
6
+ for (let i = lines.length - 1; i >= 0; i--) {
7
+ const l = lines[i];
8
+ if (l.startsWith("{") && l.includes('"device_state"')) {
9
+ try {
10
+ const o = JSON.parse(l);
11
+ if (o.type === "device_state") {
12
+ return { success: true, regs: o.regs, decoded: o.decoded, accessible: o.accessible, error: o.error };
13
+ }
14
+ }
15
+ catch { /* keep scanning */ }
16
+ }
17
+ }
18
+ return { success: false, error: out.split("\n").filter(Boolean).slice(-3).join(" | ") || "no output" };
19
+ }
20
+ export async function getDeviceState(signal) {
21
+ let out = "";
22
+ await runArgvStream(resolvedPython(), [daemonPath(), "device-state"], process.cwd(), (s, line) => { if (s === "stdout")
23
+ out += line + "\n"; }, 30_000, signal);
24
+ return parseDeviceState(out);
25
+ }
26
+ //# sourceMappingURL=trace-device.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-device.js","sourceRoot":"","sources":["../../src/tools/trace-device.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAUhE,yFAAyF;AACzF,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACnE,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACxB,IAAI,CAAC,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;oBAC9B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;gBACvG,CAAC;YACH,CAAC;YAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC;AACzG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAoB;IACvD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,MAAM,aAAa,CAAC,cAAc,EAAE,EAAE,CAAC,UAAU,EAAE,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,EACjF,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,QAAQ;QAAE,GAAG,IAAI,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5E,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,43 @@
1
+ export type Severity = "blocking" | "error" | "warning" | "info";
2
+ export interface DoctorIssue {
3
+ id: string;
4
+ severity: Severity;
5
+ detail: string;
6
+ suggested_fix: string;
7
+ }
8
+ export interface DoctorProbe {
9
+ pyocdInstalled: () => boolean;
10
+ elfPath: () => string;
11
+ elfExists: () => boolean;
12
+ stlinkProbe: () => {
13
+ found: boolean;
14
+ serial?: string;
15
+ chipid?: string;
16
+ };
17
+ udevRulesPresent: () => boolean;
18
+ /** Which user-config keys are explicitly set (not on fallback). */
19
+ configKeysSet: () => string[];
20
+ /** §11.7 real probe-presence detection. Runs `pyocd list` (preferred, via the
21
+ * daemon python) or `st-info --probe` with a short timeout. `present` =
22
+ * at least one probe enumerated; `toolAvailable` distinguishes "no probe on
23
+ * USB" from "neither pyocd nor st-info installed" (the latter must NOT be a
24
+ * false "no probe"). */
25
+ probeList?: () => Promise<{
26
+ present: boolean;
27
+ toolAvailable: boolean;
28
+ }>;
29
+ }
30
+ export interface DoctorResult {
31
+ ok: boolean;
32
+ issues: DoctorIssue[];
33
+ probe?: {
34
+ serial?: string;
35
+ chipid?: string;
36
+ };
37
+ }
38
+ export declare function runDoctor(p: DoctorProbe): Promise<DoctorResult>;
39
+ /**
40
+ * Production wiring: reads the actual filesystem, runs st-info, and imports
41
+ * pyocd to validate the environment. Safe to call even when tools are absent.
42
+ */
43
+ export declare function realProbe(): DoctorProbe;
@@ -0,0 +1,150 @@
1
+ // src/tools/trace-doctor.ts
2
+ import fs from "fs";
3
+ import { runCommand } from "../utils/exec.js";
4
+ import { resolveConfigValue, loadUserConfig } from "../utils/userConfig.js";
5
+ import { STM_ELF_DEFAULT } from "../config.js";
6
+ export async function runDoctor(p) {
7
+ const issues = [];
8
+ if (!p.pyocdInstalled()) {
9
+ issues.push({
10
+ id: "pyocd_missing",
11
+ severity: "blocking",
12
+ detail: "pyocd Python package not importable — required to talk to the ST-Link.",
13
+ suggested_fix: "Run: pip install pyocd pyelftools (use the interpreter set in config key 'pyocd_python').",
14
+ });
15
+ }
16
+ if (!p.elfExists()) {
17
+ issues.push({
18
+ id: "elf_missing",
19
+ severity: "blocking",
20
+ detail: `Firmware ELF not found at ${p.elfPath()} — symbol resolution needs it.`,
21
+ suggested_fix: "Build a Debug firmware (cmake --build build/Debug) or set config key 'stm_elf_path' to the real ELF.",
22
+ });
23
+ }
24
+ const probe = p.stlinkProbe();
25
+ // st-info's negative is authoritative ONLY when the §11.7 probeList check
26
+ // (`pyocd list` — the actual trace mechanism) isn't wired. stlinkProbe()
27
+ // returns found:false when st-info is merely *not installed*, which must not
28
+ // block tracing if pyocd can still enumerate the probe. When probeList is
29
+ // present it owns probe presence (below), so skip this redundant block.
30
+ if (!probe.found && !p.probeList) {
31
+ issues.push({
32
+ id: "probe_missing",
33
+ severity: "blocking",
34
+ detail: "No ST-Link probe detected (st-info --probe found nothing).",
35
+ suggested_fix: "Connect the ST-Link, check the SWD cable and target power.",
36
+ });
37
+ }
38
+ if (!p.udevRulesPresent()) {
39
+ issues.push({
40
+ id: "udev_missing",
41
+ severity: "warning",
42
+ detail: "No ST-Link udev rules found in /etc/udev/rules.d — pyOCD/libusb may be denied access without root.",
43
+ suggested_fix: "Install ST-Link udev rules: write /etc/udev/rules.d/49-stlinkv2.rules (SUBSYSTEMS=='usb', ATTRS{idVendor}=='0483', ATTRS{idProduct}=='3748', MODE='0666'), then: sudo udevadm control --reload-rules && sudo udevadm trigger. (st-info succeeding only proves the current user already has access.)",
44
+ });
45
+ }
46
+ // §11.7 real probe-presence check (distinct from the st-info `probe_missing`
47
+ // above and from the udev warning). Blocks `start` so a vanished/replug-needed
48
+ // ST-Link surfaces a clear, actionable error instead of a daemon connect hang.
49
+ // "tool missing" (neither pyocd nor st-info installed) is a non-fatal note, not
50
+ // a false "no probe".
51
+ if (p.probeList) {
52
+ const r = await p.probeList();
53
+ if (!r.toolAvailable) {
54
+ issues.push({
55
+ id: "probe_list_tool_missing",
56
+ severity: "info",
57
+ detail: "Could not run a probe-presence check — neither `pyocd list` nor `st-info` is available.",
58
+ suggested_fix: "Install pyocd (pip install pyocd) or stlink-tools (st-info) to enable USB probe detection.",
59
+ });
60
+ }
61
+ else if (!r.present) {
62
+ issues.push({
63
+ id: "no_probe_detected",
64
+ severity: "error",
65
+ detail: "No ST-Link detected on USB (replug the probe).",
66
+ suggested_fix: "Reconnect the ST-Link USB cable; verify with `pyocd list` / `lsusb`.",
67
+ });
68
+ }
69
+ }
70
+ if (p.configKeysSet().length === 0) {
71
+ issues.push({
72
+ id: "config_defaults",
73
+ severity: "info",
74
+ detail: "No user-config keys set — all paths are on built-in/env defaults.",
75
+ suggested_fix: "After resolving the above, persist resolved paths with crosspad_trace action=config_set.",
76
+ });
77
+ }
78
+ const ok = !issues.some((i) => i.severity === "blocking" || i.severity === "error");
79
+ return { ok, issues, probe: probe.found ? { serial: probe.serial, chipid: probe.chipid } : undefined };
80
+ }
81
+ function resolvedElfPath() {
82
+ return resolveConfigValue("stm_elf_path", "CROSSPAD_STM_ELF", process.env.CROSSPAD_STM_ELF, STM_ELF_DEFAULT);
83
+ }
84
+ function resolvedPython() {
85
+ return resolveConfigValue("pyocd_python", "CROSSPAD_TRACE_PYTHON", process.env.CROSSPAD_TRACE_PYTHON, "python3");
86
+ }
87
+ /**
88
+ * Production wiring: reads the actual filesystem, runs st-info, and imports
89
+ * pyocd to validate the environment. Safe to call even when tools are absent.
90
+ */
91
+ export function realProbe() {
92
+ return {
93
+ pyocdInstalled: () => {
94
+ const r = runCommand(`${resolvedPython()} -c "import pyocd, elftools"`, process.cwd(), 10_000);
95
+ return r.success;
96
+ },
97
+ elfPath: resolvedElfPath,
98
+ elfExists: () => {
99
+ try {
100
+ return fs.existsSync(resolvedElfPath());
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ },
106
+ stlinkProbe: () => {
107
+ const r = runCommand("st-info --probe", process.cwd(), 10_000);
108
+ if (!r.success || !/Found \d+ stlink/i.test(r.stdout))
109
+ return { found: false };
110
+ const serial = r.stdout.match(/serial:\s*([0-9A-Fa-f]+)/)?.[1];
111
+ const chipid = r.stdout.match(/chipid:\s*(0x[0-9A-Fa-f]+)/)?.[1];
112
+ return { found: true, serial, chipid };
113
+ },
114
+ udevRulesPresent: () => {
115
+ try {
116
+ return fs.readdirSync("/etc/udev/rules.d").some((f) => /stlink|49-stlinkv/i.test(f));
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ },
122
+ configKeysSet: () => Object.keys(loadUserConfig()),
123
+ // §11.7: enumerate USB probes. Prefer `pyocd list` (same python the daemon
124
+ // uses, so it sees exactly what the trace will); fall back to `st-info
125
+ // --probe`. Short timeouts — a wedged/absent probe must fail fast, not hang.
126
+ probeList: async () => {
127
+ // `<python> -m pyocd list` prints one row per probe; "No available debug
128
+ // probes" (or an empty table) when none are connected.
129
+ const py = runCommand(`${resolvedPython()} -m pyocd list`, process.cwd(), 8_000);
130
+ if (py.success) {
131
+ const present = /\b0\d{6,}|STLink|ST-LINK|\bstlink\b/i.test(py.stdout) && !/no available debug probes/i.test(py.stdout);
132
+ // pyocd prints a header row even with 0 probes; treat an explicit
133
+ // "No available debug probes" OR a body with no probe-id rows as empty.
134
+ const hasRow = py.stdout.split("\n").some((l) => /^\s*\d+\s/.test(l));
135
+ return { present: present || hasRow, toolAvailable: true };
136
+ }
137
+ // pyocd absent/failed → try st-info.
138
+ const st = runCommand("st-info --probe", process.cwd(), 8_000);
139
+ if (st.success || st.stdout.length > 0) {
140
+ const present = /Found [1-9]\d* stlink/i.test(st.stdout);
141
+ // st-info ran (even "Found 0 stlink programmers" is a successful run).
142
+ const toolAvailable = /Found \d+ stlink/i.test(st.stdout) || st.success;
143
+ return { present, toolAvailable };
144
+ }
145
+ // Neither tool produced usable output → tool missing, not "no probe".
146
+ return { present: false, toolAvailable: false };
147
+ },
148
+ };
149
+ }
150
+ //# sourceMappingURL=trace-doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-doctor.js","sourceRoot":"","sources":["../../src/tools/trace-doctor.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAmC/C,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,CAAc;IAC5C,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,IAAI,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,eAAe;YACnB,QAAQ,EAAE,UAAU;YACpB,MAAM,EAAE,wEAAwE;YAChF,aAAa,EAAE,4FAA4F;SAC5G,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC;QACnB,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,aAAa;YACjB,QAAQ,EAAE,UAAU;YACpB,MAAM,EAAE,6BAA6B,CAAC,CAAC,OAAO,EAAE,gCAAgC;YAChF,aAAa,EAAE,sGAAsG;SACtH,CAAC,CAAC;IACL,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9B,0EAA0E;IAC1E,yEAAyE;IACzE,6EAA6E;IAC7E,0EAA0E;IAC1E,wEAAwE;IACxE,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,eAAe;YACnB,QAAQ,EAAE,UAAU;YACpB,MAAM,EAAE,4DAA4D;YACpE,aAAa,EAAE,4DAA4D;SAC5E,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,CAAC,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,cAAc;YAClB,QAAQ,EAAE,SAAS;YACnB,MAAM,EAAE,oGAAoG;YAC5G,aAAa,EACX,qSAAqS;SACxS,CAAC,CAAC;IACL,CAAC;IAED,6EAA6E;IAC7E,+EAA+E;IAC/E,+EAA+E;IAC/E,gFAAgF;IAChF,sBAAsB;IACtB,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;QAChB,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,yBAAyB;gBAC7B,QAAQ,EAAE,MAAM;gBAChB,MAAM,EAAE,yFAAyF;gBACjG,aAAa,EAAE,4FAA4F;aAC5G,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,mBAAmB;gBACvB,QAAQ,EAAE,OAAO;gBACjB,MAAM,EAAE,gDAAgD;gBACxD,aAAa,EAAE,sEAAsE;aACtF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,iBAAiB;YACrB,QAAQ,EAAE,MAAM;YAChB,MAAM,EAAE,mEAAmE;YAC3E,aAAa,EAAE,0FAA0F;SAC1G,CAAC,CAAC;IACL,CAAC;IAED,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IACpF,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;AACzG,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,kBAAkB,CAAC,cAAc,EAAE,kBAAkB,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;AAC/G,CAAC;AACD,SAAS,cAAc;IACrB,OAAO,kBAAkB,CAAC,cAAc,EAAE,uBAAuB,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,SAAS,CAAC,CAAC;AACnH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,cAAc,EAAE,GAAG,EAAE;YACnB,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,cAAc,EAAE,8BAA8B,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;YAC/F,OAAO,CAAC,CAAC,OAAO,CAAC;QACnB,CAAC;QACD,OAAO,EAAE,eAAe;QACxB,SAAS,EAAE,GAAG,EAAE;YACd,IAAI,CAAC;gBAAC,OAAO,EAAE,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,KAAK,CAAC;YAAC,CAAC;QAC1E,CAAC;QACD,WAAW,EAAE,GAAG,EAAE;YAChB,MAAM,CAAC,GAAG,UAAU,CAAC,iBAAiB,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;YAC/D,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YAC/E,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC/D,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACjE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QACzC,CAAC;QACD,gBAAgB,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC;gBACH,OAAO,EAAE,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACvF,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,KAAK,CAAC;YAAC,CAAC;QAC3B,CAAC;QACD,aAAa,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;QAClD,2EAA2E;QAC3E,uEAAuE;QACvE,6EAA6E;QAC7E,SAAS,EAAE,KAAK,IAAI,EAAE;YACpB,yEAAyE;YACzE,uDAAuD;YACvD,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,cAAc,EAAE,gBAAgB,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;YACjF,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;gBACf,MAAM,OAAO,GAAG,sCAAsC,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;gBACxH,kEAAkE;gBAClE,wEAAwE;gBACxE,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;gBACtE,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;YAC7D,CAAC;YACD,qCAAqC;YACrC,MAAM,EAAE,GAAG,UAAU,CAAC,iBAAiB,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;YAC/D,IAAI,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvC,MAAM,OAAO,GAAG,wBAAwB,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;gBACzD,uEAAuE;gBACvE,MAAM,aAAa,GAAG,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC;gBACxE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;YACpC,CAAC;YACD,sEAAsE;YACtE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;QAClD,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { TraceBuffer } from "./trace-buffer.js";
2
+ /** Render the buffer as CSV. Uses the documented samples() accessor on TraceBuffer. */
3
+ export declare function bufferToCsv(buf: TraceBuffer, signals: string[]): string;
4
+ export declare function writeCsv(path: string, buf: TraceBuffer, signals: string[]): void;
@@ -0,0 +1,14 @@
1
+ import fs from "fs";
2
+ /** Render the buffer as CSV. Uses the documented samples() accessor on TraceBuffer. */
3
+ export function bufferToCsv(buf, signals) {
4
+ const rows = [signals.length ? `t,${signals.join(",")}` : "t"];
5
+ for (const s of buf.samples()) {
6
+ const cells = signals.map((sig) => (sig in s.values ? String(s.values[sig]) : ""));
7
+ rows.push([String(s.t), ...cells].join(","));
8
+ }
9
+ return rows.join("\n");
10
+ }
11
+ export function writeCsv(path, buf, signals) {
12
+ fs.writeFileSync(path, bufferToCsv(buf, signals));
13
+ }
14
+ //# sourceMappingURL=trace-export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-export.js","sourceRoot":"","sources":["../../src/tools/trace-export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,uFAAuF;AACvF,MAAM,UAAU,WAAW,CAAC,GAAgB,EAAE,OAAiB;IAC7D,MAAM,IAAI,GAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzE,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACnF,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,GAAgB,EAAE,OAAiB;IACxE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,118 @@
1
+ import { TraceBuffer } from "./trace-buffer.js";
2
+ export interface SignalSpec {
3
+ name: string;
4
+ address: number;
5
+ size: number;
6
+ encoding: string;
7
+ }
8
+ export type Frame = {
9
+ type: "sample";
10
+ t: number;
11
+ values: Record<string, number>;
12
+ } | {
13
+ type: "status";
14
+ device_state: string;
15
+ t?: number;
16
+ samples?: number;
17
+ } | {
18
+ type: "error";
19
+ error: string;
20
+ } | {
21
+ type: "signals";
22
+ signals: SignalSpec[];
23
+ unresolved: string[];
24
+ };
25
+ /** Pure: parse one NDJSON line from the daemon into a Frame, or null. */
26
+ export declare function parseFrame(line: string): Frame | null;
27
+ export interface SessionOpts {
28
+ signals: string[];
29
+ rateHz: number;
30
+ elf?: string;
31
+ outFile?: string;
32
+ capacity?: number;
33
+ swo?: string[];
34
+ }
35
+ export declare class TraceSession {
36
+ private opts;
37
+ private proc;
38
+ buffer: TraceBuffer;
39
+ deviceState: string;
40
+ startedAt: number;
41
+ filePath: string | null;
42
+ private stdoutBuf;
43
+ private onFrameExtra;
44
+ private pendingReconcile;
45
+ private termTimer;
46
+ private killTimer;
47
+ private static readonly STDERR_RING;
48
+ private stderrLines;
49
+ private stderrBuf;
50
+ private sawErrorFrame;
51
+ private firstFrameSeen;
52
+ private firstFrameWaiters;
53
+ constructor(opts: SessionOpts);
54
+ onFrame(cb: (f: Frame) => void): void;
55
+ /** Detach the frame sink so late `sample`/`status` frames during a shutdown
56
+ * race aren't rebroadcast after `trace_end`, and the consumer (Dashboard)
57
+ * doesn't hold this session longer than necessary. */
58
+ offFrame(): void;
59
+ private stoppedCbs;
60
+ /** Register a callback for when the daemon process has fully exited. If the
61
+ * process is already gone, fires synchronously. */
62
+ onStopped(cb: () => void): void;
63
+ private fireStopped;
64
+ /** The ELF this session traces against (explicit opt or the configured default).
65
+ * Used by the web UI's /symbols endpoint so autocomplete matches the trace. */
66
+ elfPath(): string;
67
+ start(): void;
68
+ /** §11.6: fold daemon stderr into a bounded ring (last STDERR_RING lines). */
69
+ private ingestStderr;
70
+ /** §11.6: the last `n` captured daemon stderr lines, newest last. */
71
+ stderrTail(n?: number): string;
72
+ /** Daemon exited. §11.6: if it died non-zero WITHOUT emitting an error frame,
73
+ * synthesize an `error: <stderr tail>` device_state so status/start never
74
+ * reports a stale "running" for a process that's actually dead. */
75
+ private onExit;
76
+ private ingest;
77
+ /** Reconcile the buffer's watched-signal set to match the daemon's authoritative
78
+ * list (from a "signals" frame): add any new names, drop any no longer present. */
79
+ private reconcileSignals;
80
+ /** Add signals to the live poll set (NDJSON `add` to daemon stdin).
81
+ * Resolves with the POST-reconcile signal-name set once the daemon's next
82
+ * "signals" frame is ingested (PROTOCOL §4); ~2 s timeout → resolves with the
83
+ * current `buffer.signalNames()` so a missing frame never hangs the caller. */
84
+ addSignals(specs: string[]): Promise<string[]>;
85
+ /** Remove signals from the live poll set (NDJSON `remove` to daemon stdin).
86
+ * Same post-reconcile / timeout contract as addSignals. */
87
+ removeSignals(specs: string[]): Promise<string[]>;
88
+ /** Queue a resolver fired by the next "signals" frame; falls back to the
89
+ * current signal set after ~2 s so callers can't hang on a silent daemon. */
90
+ private awaitReconcile;
91
+ /** §11.6: resolve on the FIRST ingested frame (signals|sample|error), or
92
+ * `null` on timeout / if the daemon exits first. Lets MCP `start` report a
93
+ * truthful device_state instead of an optimistic "running". Idempotent:
94
+ * if a frame was already seen before the call, resolves immediately. */
95
+ waitForFirstFrame(timeoutMs?: number): Promise<Frame | null>;
96
+ private lastFirstFrame;
97
+ /** Fire all pending first-frame waiters once. `f` is the triggering frame
98
+ * (or null when the daemon exited / spawn-errored before any frame). */
99
+ private resolveFirstFrame;
100
+ /** Cancel any pending SIGTERM/SIGKILL escalation timers (clean exit). */
101
+ private clearKillTimers;
102
+ /** §11.5 guaranteed teardown — escalate {cmd:stop} → SIGTERM → SIGKILL.
103
+ * Idempotent and safe to call when `proc` is already null (the daemon may
104
+ * have self-exited on a connect failure / crash / target reset). A wedged
105
+ * daemon blocked in uninterruptible libusb ignores SIGTERM, so the SIGKILL
106
+ * fallback is the ONLY thing that guarantees no `pkill -9` is ever needed.
107
+ * The escalation timers are cleared on real exit (see onExit) so a clean
108
+ * shutdown never fires a stray kill at a recycled PID.
109
+ *
110
+ * §12.1: stop() ONLY ends the daemon now. The web UI is owned by the
111
+ * persistent Dashboard singleton (see trace-webui.ts), which OUTLIVES the
112
+ * session — the MCP `stop` handler calls dashboard.unbind() so the server
113
+ * keeps listening and the dashboard tab survives across traces. */
114
+ stop(): void;
115
+ isRunning(): boolean;
116
+ }
117
+ export declare function getActiveSession(): TraceSession | null;
118
+ export declare function setActiveSession(s: TraceSession | null): void;