@henols/c64-re-tools 2.0.0-rc.8 → 2.0.0-rc.9
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/README.md +3 -1
- package/package.json +1 -1
- package/src/host/main.ts +27 -6
- package/src/trace.ts +55 -23
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ Start the Host Runtime and leave it running:
|
|
|
27
27
|
npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
Add `--log-console` to print each request and each c1541 or petcat run, or `--log-dir <directory>` to write them to a file. Both are off by default.
|
|
31
|
+
|
|
30
32
|
Install into your project and start the agent:
|
|
31
33
|
|
|
32
34
|
```
|
|
@@ -64,7 +66,7 @@ The Feature installs ACME, dxa, Java 21 and Ghidra, puts the toolkit into the pr
|
|
|
64
66
|
| `C64RT_GHIDRA` | skills, CLI | The Ghidra installation directory, when `analyzeHeadless` is not on `PATH`. |
|
|
65
67
|
| `C64RT_HOST` | MCP, skills, CLI | `host:port` of the Host Runtime when it is not on 127.0.0.1, `host.docker.internal` or `host.containers.internal` at 6464. |
|
|
66
68
|
| `C64RT_VIDEO` | MCP | `pal` (default) or `ntsc`, fixed for the life of the MCP process. |
|
|
67
|
-
| `C64RT_TRACE` |
|
|
69
|
+
| `C64RT_TRACE` | MCP, skills, CLI | A directory for a trace of what the programs do, for a person who debugs a run. See [docs/debug-install.md](docs/debug-install.md). Nothing of the trace reaches the agent. The host uses `--log-dir` instead. |
|
|
68
70
|
|
|
69
71
|
Ghidra runs with its own settings directory for each request. The toolkit
|
|
70
72
|
brings its own NMOS 6510 language (all 256 opcodes); your Ghidra installation
|
package/package.json
CHANGED
package/src/host/main.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { PACKAGE } from "../package.ts";
|
|
|
15
15
|
const HELP = `c64-re-tools-host
|
|
16
16
|
|
|
17
17
|
Usage:
|
|
18
|
-
npx -y --package=${PACKAGE.name}@${PACKAGE.tag} c64-re-tools-host [--port <port>]
|
|
18
|
+
npx -y --package=${PACKAGE.name}@${PACKAGE.tag} c64-re-tools-host [--port <port>] [--log-console] [--log-dir <directory>]
|
|
19
19
|
c64-re-tools-host --help
|
|
20
20
|
|
|
21
21
|
Runs the c64-re-tools Host Runtime in the foreground. It first starts VICE
|
|
@@ -27,8 +27,15 @@ connected c64-re-tools-mcp process, and runs the tools that come with VICE
|
|
|
27
27
|
emulator and tool it started.
|
|
28
28
|
|
|
29
29
|
Options:
|
|
30
|
-
--port <port>
|
|
31
|
-
--
|
|
30
|
+
--port <port> Listen on this port instead (0 picks a free port).
|
|
31
|
+
--log-console Also print each request with its result, and each run
|
|
32
|
+
of c1541 or petcat, to the console.
|
|
33
|
+
--log-dir <directory> Write the same events as JSON lines to a file in this
|
|
34
|
+
directory, with a copy of each VICE log.
|
|
35
|
+
--help Show this help.
|
|
36
|
+
|
|
37
|
+
Without --log-console and --log-dir the host logs only its start, its stop,
|
|
38
|
+
connections and failures. C64RT_TRACE does not apply to the host.`;
|
|
32
39
|
|
|
33
40
|
function log(line: string): void {
|
|
34
41
|
process.stderr.write(`c64-re-tools-host: ${line}\n`);
|
|
@@ -36,9 +43,12 @@ function log(line: string): void {
|
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
async function main(): Promise<number> {
|
|
39
|
-
let values: { help?: boolean; port?: string };
|
|
46
|
+
let values: { help?: boolean; port?: string; "log-console"?: boolean; "log-dir"?: string };
|
|
40
47
|
try {
|
|
41
|
-
({ values } = parseArgs({
|
|
48
|
+
({ values } = parseArgs({
|
|
49
|
+
options: { help: { type: "boolean" }, port: { type: "string" }, "log-console": { type: "boolean" }, "log-dir": { type: "string" } },
|
|
50
|
+
strict: true,
|
|
51
|
+
}));
|
|
42
52
|
} catch (error) {
|
|
43
53
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n\n${HELP}\n`);
|
|
44
54
|
return 2;
|
|
@@ -47,7 +57,18 @@ async function main(): Promise<number> {
|
|
|
47
57
|
process.stdout.write(`${HELP}\n`);
|
|
48
58
|
return 0;
|
|
49
59
|
}
|
|
50
|
-
|
|
60
|
+
const logDir = values["log-dir"];
|
|
61
|
+
if (logDir === "") {
|
|
62
|
+
process.stderr.write("--log-dir needs a directory.\n");
|
|
63
|
+
return 2;
|
|
64
|
+
}
|
|
65
|
+
startTrace("host", {
|
|
66
|
+
target: {
|
|
67
|
+
...(logDir === undefined ? {} : { dir: { path: logDir, given: `--log-dir ${logDir}` } }),
|
|
68
|
+
...(values["log-console"] ? { echo: (line: string) => process.stderr.write(`c64-re-tools-host: ${line}\n`) } : {}),
|
|
69
|
+
},
|
|
70
|
+
warn: log,
|
|
71
|
+
});
|
|
51
72
|
|
|
52
73
|
const port = values.port === undefined ? DEFAULT_HOST_PORT : parsePort(values.port);
|
|
53
74
|
if (port === undefined) {
|
package/src/trace.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// An opt-in trace for the person who debugs a run. With C64RT_TRACE=<directory>
|
|
2
|
-
// in the environment, each process appends its events as
|
|
3
|
-
// of its own in that directory
|
|
4
|
-
//
|
|
2
|
+
// in the environment (the host: --log-dir), each process appends its events as
|
|
3
|
+
// JSON lines to a file of its own in that directory. The host can also echo
|
|
4
|
+
// each event to the console (--log-console). Off, every call does nothing. The
|
|
5
|
+
// trace is for people: nothing of it reaches the agent, and no result depends on it.
|
|
5
6
|
|
|
6
7
|
import { appendFileSync, copyFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
7
8
|
import { join } from "node:path";
|
|
@@ -66,14 +67,16 @@ export class Trace {
|
|
|
66
67
|
readonly kind: TraceKind;
|
|
67
68
|
/** The directory of the trace, or undefined when the trace is off. */
|
|
68
69
|
readonly dir: string | undefined;
|
|
69
|
-
/** The file this trace appends to, or undefined when the trace
|
|
70
|
+
/** The file this trace appends to, or undefined when the trace writes no file. */
|
|
70
71
|
readonly file: string | undefined;
|
|
72
|
+
readonly #echo: ((line: string) => void) | undefined;
|
|
71
73
|
#broken = false;
|
|
72
74
|
|
|
73
|
-
private constructor(kind: TraceKind, dir: string | undefined, file: string | undefined) {
|
|
75
|
+
private constructor(kind: TraceKind, dir: string | undefined, file: string | undefined, echo?: (line: string) => void) {
|
|
74
76
|
this.kind = kind;
|
|
75
77
|
this.dir = dir;
|
|
76
78
|
this.file = file;
|
|
79
|
+
this.#echo = echo;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/** A trace that records nothing. */
|
|
@@ -81,31 +84,46 @@ export class Trace {
|
|
|
81
84
|
return new Trace(kind, undefined, undefined);
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
/** A trace that writes no file and gives each event to `echo` as one line. */
|
|
88
|
+
static echoing(kind: TraceKind, echo: (line: string) => void): Trace {
|
|
89
|
+
return new Trace(kind, undefined, undefined, echo);
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
/**
|
|
85
93
|
* A trace into `dir`, created when it is missing. The file is
|
|
86
94
|
* `<kind>-<CET time>-<pid>.jsonl`, and its first event is `trace.start`.
|
|
87
95
|
* Throws when the directory cannot be used.
|
|
88
96
|
*/
|
|
89
|
-
static open(kind: TraceKind, dir: string, now = new Date()): Trace {
|
|
97
|
+
static open(kind: TraceKind, dir: string, now = new Date(), echo?: (line: string) => void): Trace {
|
|
90
98
|
mkdirSync(dir, { recursive: true });
|
|
91
|
-
const trace = new Trace(kind, dir, join(dir, `${kind}-${fileStampCet(now)}-${process.pid}.jsonl`));
|
|
99
|
+
const trace = new Trace(kind, dir, join(dir, `${kind}-${fileStampCet(now)}-${process.pid}.jsonl`), echo);
|
|
92
100
|
trace.event("trace.start", { version: runningVersion(), pid: process.pid, argv: process.argv.slice(1), cwd: process.cwd(), node: process.version, platform: process.platform });
|
|
93
101
|
return trace;
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
get on(): boolean {
|
|
105
|
+
return this.#writes || this.#echo !== undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
get #writes(): boolean {
|
|
97
109
|
return this.file !== undefined && !this.#broken;
|
|
98
110
|
}
|
|
99
111
|
|
|
100
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* Records one event: appends it to the file, and gives it to the echo as
|
|
114
|
+
* "<event> <fields as JSON>". The echo skips `log` events, which the program
|
|
115
|
+
* prints itself. A write that fails stops the file; nothing throws.
|
|
116
|
+
*/
|
|
101
117
|
event(name: string, fields: TraceFields = {}): void {
|
|
102
118
|
if (!this.on) return;
|
|
103
|
-
const
|
|
119
|
+
const summary: Record<string, unknown> = {};
|
|
104
120
|
for (const [key, value] of Object.entries(fields)) {
|
|
105
|
-
if (!RESERVED.has(key) && value !== undefined)
|
|
121
|
+
if (!RESERVED.has(key) && value !== undefined) summary[key] = summarize(value);
|
|
106
122
|
}
|
|
123
|
+
if (this.#echo !== undefined && name !== "log") this.#echo(`${name} ${JSON.stringify(summary)}`);
|
|
124
|
+
if (!this.#writes) return;
|
|
107
125
|
try {
|
|
108
|
-
appendFileSync(this.file!, `${JSON.stringify(
|
|
126
|
+
appendFileSync(this.file!, `${JSON.stringify({ t: isoCet(), kind: this.kind, event: name, ...summary })}\n`);
|
|
109
127
|
} catch {
|
|
110
128
|
this.#broken = true;
|
|
111
129
|
}
|
|
@@ -121,7 +139,7 @@ export class Trace {
|
|
|
121
139
|
* trace, as `<kind>-<pid>-<name>`. A copy that fails is recorded, not thrown.
|
|
122
140
|
*/
|
|
123
141
|
keep(source: string, name: string): void {
|
|
124
|
-
if (!this
|
|
142
|
+
if (!this.#writes) return;
|
|
125
143
|
const target = join(this.dir!, `${this.kind}-${process.pid}-${name}`);
|
|
126
144
|
try {
|
|
127
145
|
copyFileSync(source, target);
|
|
@@ -135,23 +153,37 @@ export class Trace {
|
|
|
135
153
|
// Off until a program calls startTrace; the kind is a placeholder until then.
|
|
136
154
|
let current = Trace.off("script");
|
|
137
155
|
|
|
156
|
+
/** Where the trace of a process goes: a directory for its file, and an echo for each event. */
|
|
157
|
+
export interface TraceTarget {
|
|
158
|
+
/** The directory, and how the user gave it (`--log-dir <path>`, for example) for a warning. */
|
|
159
|
+
dir?: { path: string; given: string };
|
|
160
|
+
echo?: (line: string) => void;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The target that C64RT_TRACE in `env` gives: its directory, or nothing. */
|
|
164
|
+
export function traceTargetFromEnv(env: NodeJS.ProcessEnv = process.env): TraceTarget {
|
|
165
|
+
const path = env[TRACE_VARIABLE];
|
|
166
|
+
return path === undefined || path === "" ? {} : { dir: { path, given: `${TRACE_VARIABLE}=${path}` } };
|
|
167
|
+
}
|
|
168
|
+
|
|
138
169
|
/**
|
|
139
|
-
* Switches the trace of this process on
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* start of a program.
|
|
170
|
+
* Switches the trace of this process on for `target` (by default what
|
|
171
|
+
* C64RT_TRACE in `env` gives), and off when the target is empty. A directory
|
|
172
|
+
* that cannot be used writes no file and reports that through `warn`; an echo
|
|
173
|
+
* stays on. Called once, at the start of a program.
|
|
143
174
|
*/
|
|
144
|
-
export function startTrace(kind: TraceKind, options: { env?: NodeJS.ProcessEnv; warn?: (line: string) => void } = {}): Trace {
|
|
145
|
-
const dir =
|
|
146
|
-
|
|
147
|
-
|
|
175
|
+
export function startTrace(kind: TraceKind, options: { env?: NodeJS.ProcessEnv; target?: TraceTarget; warn?: (line: string) => void } = {}): Trace {
|
|
176
|
+
const { dir, echo } = options.target ?? traceTargetFromEnv(options.env);
|
|
177
|
+
const noFile = () => (echo === undefined ? Trace.off(kind) : Trace.echoing(kind, echo));
|
|
178
|
+
if (dir === undefined) {
|
|
179
|
+
current = noFile();
|
|
148
180
|
return current;
|
|
149
181
|
}
|
|
150
182
|
try {
|
|
151
|
-
current = Trace.open(kind, dir);
|
|
183
|
+
current = Trace.open(kind, dir.path, new Date(), echo);
|
|
152
184
|
} catch (error) {
|
|
153
|
-
current =
|
|
154
|
-
options.warn?.(`${
|
|
185
|
+
current = noFile();
|
|
186
|
+
options.warn?.(`${dir.given} cannot be used (${error instanceof Error ? error.message : String(error)}); no trace file is written.`);
|
|
155
187
|
}
|
|
156
188
|
return current;
|
|
157
189
|
}
|