@v1nvn/agentic-core 0.44.0 → 0.45.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.
- package/README.md +1 -1
- package/package.json +10 -15
- package/src/cli.ts +252 -38
- package/src/dev.ts +3 -8
- package/src/draw.ts +332 -0
- package/src/index.ts +8 -6
- package/src/logger.ts +21 -1
- package/src/mcp.ts +69 -0
- package/src/vite.ts +78 -60
- package/dist/index.js +0 -214
- package/dist/index.js.map +0 -1
- package/dist/vite.js +0 -54
- package/dist/vite.js.map +0 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ User-facing docs: [root README](../../README.md).
|
|
|
13
13
|
| ------------------- | ----------------------------------------------------------------------- |
|
|
14
14
|
| `src/last-reply.ts` | finds the previous assistant reply across local Claude Code transcripts |
|
|
15
15
|
| `src/input.ts` | stdin and markdown-file input with the CLI error contracts |
|
|
16
|
-
| `src/cli.ts` |
|
|
16
|
+
| `src/cli.ts` | the runCli/parseCli doors over declarative command specs |
|
|
17
17
|
| `src/logger.ts` | level-filtered stderr logger for the MCP servers |
|
|
18
18
|
| `src/shutdown.ts` | close-then-exit signal handling for the server entrypoints |
|
|
19
19
|
| `src/dev.ts` | the hot-reload watcher wiring the servers' `yarn dev` harnesses share |
|
package/package.json
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v1nvn/agentic-core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared
|
|
3
|
+
"version": "0.45.0",
|
|
4
|
+
"description": "Shared source home for the @v1nvn agent tools — runtime helpers and the drawing kit every process-side surface imports.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
"./vite": {
|
|
13
|
-
"types": "./src/vite.ts",
|
|
14
|
-
"default": "./dist/vite.js"
|
|
15
|
-
}
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./draw": "./src/draw.ts",
|
|
10
|
+
"./vite": "./src/vite.ts"
|
|
16
11
|
},
|
|
17
12
|
"scripts": {
|
|
18
|
-
"build": "vite build",
|
|
19
13
|
"test": "vitest run",
|
|
20
14
|
"test:watch": "vitest"
|
|
21
15
|
},
|
|
@@ -35,11 +29,12 @@
|
|
|
35
29
|
"directory": "packages/core"
|
|
36
30
|
},
|
|
37
31
|
"files": [
|
|
38
|
-
"dist",
|
|
39
32
|
"src"
|
|
40
33
|
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"commander": "^15.0.0"
|
|
36
|
+
},
|
|
41
37
|
"devDependencies": {
|
|
42
|
-
"commander": "^15.0.0",
|
|
43
38
|
"vite": "^8.1.4",
|
|
44
39
|
"vitest": "^4.1.10"
|
|
45
40
|
},
|
|
@@ -47,6 +42,6 @@
|
|
|
47
42
|
"access": "public"
|
|
48
43
|
},
|
|
49
44
|
"engines": {
|
|
50
|
-
"node": ">=
|
|
45
|
+
"node": ">=23.6"
|
|
51
46
|
}
|
|
52
47
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,60 +1,274 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Command, InvalidArgumentError, Option } from 'commander';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { describeError } from './mcp.ts';
|
|
4
|
+
|
|
5
|
+
export interface CliOption {
|
|
6
|
+
readonly choices?: readonly string[];
|
|
7
|
+
readonly default?: string;
|
|
8
|
+
readonly description: string;
|
|
9
|
+
readonly env?: string;
|
|
10
|
+
readonly flags: string;
|
|
11
|
+
readonly parse?: (value: string) => unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type CliResult = number | undefined;
|
|
15
|
+
|
|
16
|
+
export type CliRun = (
|
|
17
|
+
options: Record<string, unknown>,
|
|
18
|
+
args: readonly string[],
|
|
19
|
+
) => CliResult | Promise<CliResult>;
|
|
20
|
+
|
|
21
|
+
export interface CliSpec {
|
|
22
|
+
readonly argument?: { readonly description: string; readonly flags: string };
|
|
23
|
+
readonly commands?: readonly CliSpec[];
|
|
24
|
+
readonly description?: string;
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly options?: readonly CliOption[];
|
|
27
|
+
readonly run?: CliRun;
|
|
28
|
+
readonly usageExit?: number;
|
|
29
|
+
readonly version?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CliInvocation {
|
|
33
|
+
readonly args: readonly string[];
|
|
34
|
+
readonly command?: string;
|
|
35
|
+
readonly options: Record<string, unknown>;
|
|
36
|
+
readonly root: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// A --help ask caught mid-parse: the parse doors return it instead of exiting,
|
|
40
|
+
// and runCli answers it (the help payload) on stdout with 0.
|
|
5
41
|
export class HelpRequested extends Error {
|
|
6
|
-
|
|
42
|
+
readonly command: Command;
|
|
43
|
+
|
|
44
|
+
constructor(command: Command) {
|
|
7
45
|
super(`${command.name()} --help`);
|
|
46
|
+
this.command = command;
|
|
8
47
|
}
|
|
9
48
|
}
|
|
10
49
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
50
|
+
export type CliHelpHook<T> = (topic: string, text: string) => T;
|
|
51
|
+
|
|
52
|
+
function addOption(cmd: Command, option: CliOption): void {
|
|
53
|
+
const bare =
|
|
54
|
+
option.env === undefined &&
|
|
55
|
+
option.choices === undefined &&
|
|
56
|
+
option.default === undefined &&
|
|
57
|
+
option.parse === undefined;
|
|
58
|
+
if (bare) {
|
|
59
|
+
cmd.option(option.flags, option.description);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const built = new Option(option.flags, option.description);
|
|
63
|
+
if (option.env !== undefined) {
|
|
64
|
+
built.env(option.env);
|
|
65
|
+
}
|
|
66
|
+
if (option.choices !== undefined) {
|
|
67
|
+
built.choices(option.choices);
|
|
68
|
+
}
|
|
69
|
+
if (option.default !== undefined) {
|
|
70
|
+
built.default(option.default);
|
|
71
|
+
}
|
|
72
|
+
const { parse } = option;
|
|
73
|
+
if (parse !== undefined) {
|
|
74
|
+
// A plain Error from a parse fn skips commander's exitOverride, which is
|
|
75
|
+
// what records the subcommand for the usage print — wrap it so it rides.
|
|
76
|
+
built.argParser((value: string) => {
|
|
77
|
+
try {
|
|
78
|
+
return parse(value);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if ((err as { code?: string }).code === 'commander.invalidArgument') {
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
throw new InvalidArgumentError(describeError(err));
|
|
24
84
|
}
|
|
25
|
-
throw err;
|
|
26
85
|
});
|
|
27
|
-
command.commands.forEach(quiet);
|
|
28
86
|
}
|
|
87
|
+
cmd.addOption(built);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function wire(
|
|
91
|
+
cmd: Command,
|
|
92
|
+
spec: CliSpec,
|
|
93
|
+
program: Command,
|
|
94
|
+
isRoot: boolean,
|
|
95
|
+
sink?: (invocation: CliInvocation) => void,
|
|
96
|
+
): void {
|
|
97
|
+
if (spec.description !== undefined) {
|
|
98
|
+
cmd.description(spec.description);
|
|
99
|
+
}
|
|
100
|
+
if (spec.argument !== undefined) {
|
|
101
|
+
cmd.argument(spec.argument.flags, spec.argument.description);
|
|
102
|
+
}
|
|
103
|
+
for (const option of spec.options ?? []) {
|
|
104
|
+
addOption(cmd, option);
|
|
105
|
+
}
|
|
106
|
+
if (isRoot && spec.version !== undefined) {
|
|
107
|
+
cmd.option('-V, --version', 'print the version and exit');
|
|
108
|
+
}
|
|
109
|
+
cmd.action((...all: unknown[]) => {
|
|
110
|
+
const options = all.at(-2) as Record<string, unknown>;
|
|
111
|
+
const command = all.at(-1) as Command;
|
|
112
|
+
sink?.({
|
|
113
|
+
command: isRoot ? undefined : spec.name,
|
|
114
|
+
options,
|
|
115
|
+
args: command.args,
|
|
116
|
+
root: program.opts(),
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
for (const sub of spec.commands ?? []) {
|
|
120
|
+
const child = new Command(sub.name);
|
|
121
|
+
wire(child, sub, program, false, sink);
|
|
122
|
+
cmd.addCommand(child);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function materialize(
|
|
127
|
+
spec: CliSpec,
|
|
128
|
+
sink?: (invocation: CliInvocation) => void,
|
|
129
|
+
): Command {
|
|
130
|
+
const program = new Command(spec.name);
|
|
131
|
+
wire(program, spec, program, true, sink);
|
|
132
|
+
return program;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function buildCli(spec: CliSpec): Command {
|
|
136
|
+
return materialize(spec);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function quiet(
|
|
140
|
+
command: Command,
|
|
141
|
+
onUsageError: (command: Command) => void,
|
|
142
|
+
): void {
|
|
143
|
+
command.configureOutput({
|
|
144
|
+
writeOut: () => undefined,
|
|
145
|
+
writeErr: () => undefined,
|
|
146
|
+
});
|
|
147
|
+
command.exitOverride(err => {
|
|
148
|
+
const code = (err as { code?: string }).code;
|
|
149
|
+
if (code === 'commander.help' || code === 'commander.helpDisplayed') {
|
|
150
|
+
throw new HelpRequested(command);
|
|
151
|
+
}
|
|
152
|
+
onUsageError(command);
|
|
153
|
+
throw err;
|
|
154
|
+
});
|
|
155
|
+
command.commands.forEach(c => {
|
|
156
|
+
quiet(c, onUsageError);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function parseCli<T = HelpRequested>(
|
|
161
|
+
spec: CliSpec,
|
|
162
|
+
argv: readonly string[],
|
|
163
|
+
onHelp?: CliHelpHook<T>,
|
|
164
|
+
onUsageError?: (command: Command, err: Error) => void,
|
|
165
|
+
): CliInvocation | HelpRequested | T | undefined {
|
|
166
|
+
let invocation: CliInvocation | undefined;
|
|
167
|
+
const program = materialize(spec, found => {
|
|
168
|
+
invocation = found;
|
|
169
|
+
});
|
|
170
|
+
let errored: Command | undefined;
|
|
171
|
+
quiet(program, command => {
|
|
172
|
+
errored = command;
|
|
173
|
+
});
|
|
29
174
|
try {
|
|
30
|
-
|
|
31
|
-
program.parse([...args], { from: 'user' });
|
|
32
|
-
return program;
|
|
175
|
+
program.parse([...argv], { from: 'user' });
|
|
33
176
|
} catch (err) {
|
|
34
|
-
|
|
177
|
+
if (!(err instanceof HelpRequested)) {
|
|
178
|
+
if (errored !== undefined) {
|
|
179
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
180
|
+
onUsageError?.(errored, cause);
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
return onHelp !== undefined
|
|
185
|
+
? onHelp(err.command.name(), err.command.helpInformation())
|
|
186
|
+
: err;
|
|
35
187
|
}
|
|
188
|
+
return invocation;
|
|
36
189
|
}
|
|
37
190
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
191
|
+
export function helpTextFor(spec: CliSpec, topic: string): string {
|
|
192
|
+
const program = materialize(spec);
|
|
193
|
+
const command =
|
|
194
|
+
topic === spec.name
|
|
195
|
+
? program
|
|
196
|
+
: program.commands.find(c => c.name() === topic);
|
|
197
|
+
if (command === undefined) {
|
|
198
|
+
throw new Error(`no '${topic}' command to describe`);
|
|
199
|
+
}
|
|
200
|
+
return command.helpInformation();
|
|
41
201
|
}
|
|
42
202
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
203
|
+
function usageError(spec: CliSpec, topic?: string, message?: string): number {
|
|
204
|
+
if (message !== undefined) {
|
|
205
|
+
// Commander 15 builds its messages with the 'error: ' prefix already in.
|
|
206
|
+
process.stderr.write(`${message}\n`);
|
|
207
|
+
}
|
|
208
|
+
process.stderr.write(
|
|
209
|
+
`${helpTextFor(spec, topic === undefined ? spec.name : topic)}\n`,
|
|
210
|
+
);
|
|
211
|
+
return spec.usageExit ?? 1;
|
|
46
212
|
}
|
|
47
213
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
214
|
+
async function call(
|
|
215
|
+
run: CliRun,
|
|
216
|
+
options: Record<string, unknown>,
|
|
217
|
+
args: readonly string[],
|
|
218
|
+
): Promise<number> {
|
|
219
|
+
try {
|
|
220
|
+
return (await run(options, args)) ?? 0;
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error((err as Error).message);
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
51
225
|
}
|
|
52
226
|
|
|
53
|
-
export async function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
227
|
+
export async function runCli(
|
|
228
|
+
spec: CliSpec,
|
|
229
|
+
argv: readonly string[],
|
|
230
|
+
onHelp?: CliHelpHook<CliResult>,
|
|
231
|
+
): Promise<number> {
|
|
232
|
+
let asked: undefined | { readonly text: string; readonly topic: string };
|
|
233
|
+
let usageTopic: string | undefined;
|
|
234
|
+
let usageMessage: string | undefined;
|
|
235
|
+
const parsed = parseCli(
|
|
236
|
+
spec,
|
|
237
|
+
argv,
|
|
238
|
+
(topic, text) => {
|
|
239
|
+
asked = { topic, text };
|
|
240
|
+
return 0;
|
|
241
|
+
},
|
|
242
|
+
(command, err) => {
|
|
243
|
+
usageTopic = command.name();
|
|
244
|
+
usageMessage = err.message;
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
if (parsed === undefined) {
|
|
248
|
+
return usageError(spec, usageTopic, usageMessage);
|
|
249
|
+
}
|
|
250
|
+
if (asked !== undefined) {
|
|
251
|
+
if (onHelp !== undefined) {
|
|
252
|
+
return onHelp(asked.topic, asked.text) ?? 0;
|
|
253
|
+
}
|
|
254
|
+
process.stdout.write(`${asked.text}\n`);
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
const invocation = parsed as CliInvocation;
|
|
258
|
+
if (spec.version !== undefined && invocation.root.version === true) {
|
|
259
|
+
process.stdout.write(`${spec.version}\n`);
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
if (invocation.command === undefined) {
|
|
263
|
+
if ((spec.commands?.length ?? 0) > 0) {
|
|
264
|
+
return usageError(spec);
|
|
265
|
+
}
|
|
266
|
+
return spec.run === undefined
|
|
267
|
+
? 0
|
|
268
|
+
: await call(spec.run, invocation.options, invocation.args);
|
|
59
269
|
}
|
|
270
|
+
const sub = spec.commands?.find(c => c.name === invocation.command);
|
|
271
|
+
return sub?.run === undefined
|
|
272
|
+
? 0
|
|
273
|
+
: await call(sub.run, invocation.options, invocation.args);
|
|
60
274
|
}
|
package/src/dev.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
export interface ReloadableRunner {
|
|
5
|
-
readonly evaluatedModules: { clear(): void };
|
|
6
|
-
import<T>(entry: string): Promise<T>;
|
|
7
|
-
}
|
|
8
|
-
|
|
1
|
+
// A structural stand-in for vite's dev server — the shared hot-reload wiring
|
|
2
|
+
// below must not pull vite into core's runtime surface; vite.ts passes the
|
|
3
|
+
// real instance through.
|
|
9
4
|
export interface WatchableDevServer {
|
|
10
5
|
close(): Promise<unknown>;
|
|
11
6
|
readonly watcher: {
|
package/src/draw.ts
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The drawing kit — the one home of the repo's terminal drawing model: ink
|
|
3
|
+
* kinds are a closed union, color values are data, surfaces render
|
|
4
|
+
* documents, and `toSgr` is the one skin, resetting each inked segment
|
|
5
|
+
* unless it carries its ink into the next. Loaded only by processes — the
|
|
6
|
+
* engine's island loader imports its own files by relative path alone.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type InkKind =
|
|
10
|
+
| 'blue'
|
|
11
|
+
| 'bold'
|
|
12
|
+
| 'brightBlack'
|
|
13
|
+
| 'cyan'
|
|
14
|
+
| 'dim'
|
|
15
|
+
| 'green'
|
|
16
|
+
| 'magenta'
|
|
17
|
+
| 'mark'
|
|
18
|
+
| 'plain'
|
|
19
|
+
| 'red'
|
|
20
|
+
| 'white'
|
|
21
|
+
| 'yellow';
|
|
22
|
+
|
|
23
|
+
export type Ink =
|
|
24
|
+
| InkKind
|
|
25
|
+
| { readonly bg: Ink }
|
|
26
|
+
| { readonly indexed: number }
|
|
27
|
+
| { readonly rgb: readonly [number, number, number] };
|
|
28
|
+
|
|
29
|
+
export function rgb(r: number, g: number, b: number): Ink {
|
|
30
|
+
return { rgb: [r, g, b] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function indexed(n: number): Ink {
|
|
34
|
+
return { indexed: n };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function bg(ink: Ink): Ink {
|
|
38
|
+
return { bg: ink };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Segment {
|
|
42
|
+
readonly carry?: boolean;
|
|
43
|
+
readonly ink?: Ink;
|
|
44
|
+
readonly text: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare const lineBrand: unique symbol;
|
|
48
|
+
declare const docBrand: unique symbol;
|
|
49
|
+
|
|
50
|
+
export type Line = readonly Segment[] & { readonly [lineBrand]: true };
|
|
51
|
+
export type Doc = readonly Line[] & { readonly [docBrand]: true };
|
|
52
|
+
|
|
53
|
+
export function line(...segments: Segment[]): Line {
|
|
54
|
+
return segments as unknown as Line;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function doc(...rows: Line[]): Doc {
|
|
58
|
+
return rows as unknown as Doc;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function plain(text: string): Segment {
|
|
62
|
+
return { text };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function dim(text: string): Segment {
|
|
66
|
+
return { text, ink: 'dim' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function bold(text: string): Segment {
|
|
70
|
+
return { text, ink: 'bold' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function green(text: string): Segment {
|
|
74
|
+
return { text, ink: 'green' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function magenta(text: string): Segment {
|
|
78
|
+
return { text, ink: 'magenta' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function red(text: string): Segment {
|
|
82
|
+
return { text, ink: 'red' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function yellow(text: string): Segment {
|
|
86
|
+
return { text, ink: 'yellow' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function assertNever(kind: never): never {
|
|
90
|
+
throw new Error(`unhandled Ink kind: ${String(kind)}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function paramsOf(ink: Ink): string {
|
|
94
|
+
if (typeof ink !== 'string') {
|
|
95
|
+
if ('bg' in ink) {
|
|
96
|
+
return paramsOf(ink.bg)
|
|
97
|
+
.replace(/^3(\d)/, '4$1')
|
|
98
|
+
.replace(/^9(\d)/, '10$1');
|
|
99
|
+
}
|
|
100
|
+
return 'indexed' in ink
|
|
101
|
+
? `38;5;${ink.indexed}`
|
|
102
|
+
: `38;2;${ink.rgb[0]};${ink.rgb[1]};${ink.rgb[2]}`;
|
|
103
|
+
}
|
|
104
|
+
switch (ink) {
|
|
105
|
+
case 'blue':
|
|
106
|
+
return '34';
|
|
107
|
+
case 'bold':
|
|
108
|
+
return '1';
|
|
109
|
+
case 'brightBlack':
|
|
110
|
+
return '90';
|
|
111
|
+
case 'cyan':
|
|
112
|
+
return '36';
|
|
113
|
+
case 'dim':
|
|
114
|
+
return '2';
|
|
115
|
+
case 'green':
|
|
116
|
+
return '32';
|
|
117
|
+
case 'magenta':
|
|
118
|
+
return '35';
|
|
119
|
+
case 'mark':
|
|
120
|
+
return '1;97';
|
|
121
|
+
case 'plain':
|
|
122
|
+
return '39';
|
|
123
|
+
case 'red':
|
|
124
|
+
return '31';
|
|
125
|
+
case 'white':
|
|
126
|
+
return '37';
|
|
127
|
+
case 'yellow':
|
|
128
|
+
return '33';
|
|
129
|
+
default:
|
|
130
|
+
return assertNever(ink);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function rowsOf(rows: Doc | Line): readonly (readonly Segment[])[] {
|
|
135
|
+
return (rows as readonly unknown[]).every(r => Array.isArray(r))
|
|
136
|
+
? (rows as Doc)
|
|
137
|
+
: [rows as Line];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function toSgr(rows: Doc | Line): string {
|
|
141
|
+
return rowsOf(rows)
|
|
142
|
+
.map(row => {
|
|
143
|
+
let out = '';
|
|
144
|
+
let inkOpen = false;
|
|
145
|
+
for (const seg of row) {
|
|
146
|
+
if (seg.ink === undefined) {
|
|
147
|
+
out += (inkOpen ? '\x1b[0m' : '') + seg.text;
|
|
148
|
+
inkOpen = false;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
out += `\x1b[${paramsOf(seg.ink)}m${seg.text}`;
|
|
152
|
+
if (seg.carry === true) {
|
|
153
|
+
inkOpen = true;
|
|
154
|
+
} else {
|
|
155
|
+
out += '\x1b[0m';
|
|
156
|
+
inkOpen = false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return inkOpen ? `${out}\x1b[0m` : out;
|
|
160
|
+
})
|
|
161
|
+
.join('\n');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function stripSgr(text: string): string {
|
|
165
|
+
// eslint-disable-next-line no-control-regex
|
|
166
|
+
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function textOf(rows: Doc | Line): string {
|
|
170
|
+
return rowsOf(rows)
|
|
171
|
+
.map(row => row.map(seg => seg.text).join(''))
|
|
172
|
+
.join('\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface ToolLineSource {
|
|
176
|
+
readonly deny?: string;
|
|
177
|
+
readonly isError?: boolean;
|
|
178
|
+
readonly result?: unknown;
|
|
179
|
+
readonly text?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface Stdio {
|
|
183
|
+
readonly stderr?: unknown;
|
|
184
|
+
readonly stdout?: unknown;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function stdioOf(result: unknown): Stdio {
|
|
188
|
+
return typeof result === 'object' && result !== null ? result : {};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function stdoutOf(result: unknown): string {
|
|
192
|
+
const { stdout } = stdioOf(result);
|
|
193
|
+
return typeof stdout === 'string' ? stdout : '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The one line an exec shows: the refusal's reason, stdout, else stderr or `fallback`. */
|
|
197
|
+
export function lineOf(call: ToolLineSource, fallback: string): string {
|
|
198
|
+
if (call.deny !== undefined) {
|
|
199
|
+
return call.deny;
|
|
200
|
+
}
|
|
201
|
+
if (call.isError === true) {
|
|
202
|
+
return call.text ?? fallback;
|
|
203
|
+
}
|
|
204
|
+
const out = stdoutOf(call.result).trim();
|
|
205
|
+
if (out !== '') {
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
const { stderr } = stdioOf(call.result);
|
|
209
|
+
const err = typeof stderr === 'string' ? stderr.trim() : '';
|
|
210
|
+
return err !== '' ? err : fallback;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export const RULE_WIDTH = 68;
|
|
214
|
+
|
|
215
|
+
export const BLOCK = '█';
|
|
216
|
+
export const TRACK = '·';
|
|
217
|
+
export const EIGHTHS: readonly (string | undefined)[] = [
|
|
218
|
+
'',
|
|
219
|
+
'▏',
|
|
220
|
+
'▎',
|
|
221
|
+
'▍',
|
|
222
|
+
'▌',
|
|
223
|
+
'▋',
|
|
224
|
+
'▊',
|
|
225
|
+
'▉',
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
export function rule(width: number = RULE_WIDTH): string {
|
|
229
|
+
return '─'.repeat(Math.max(0, width));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function pad(
|
|
233
|
+
text: string,
|
|
234
|
+
width: number,
|
|
235
|
+
side: 'left' | 'right',
|
|
236
|
+
): string {
|
|
237
|
+
if (text.length >= width) {
|
|
238
|
+
return text;
|
|
239
|
+
}
|
|
240
|
+
const gap = ' '.repeat(width - text.length);
|
|
241
|
+
return side === 'left' ? gap + text : text + gap;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function meter(
|
|
245
|
+
pct: null | number | undefined,
|
|
246
|
+
width: number,
|
|
247
|
+
filled: Ink = 'green',
|
|
248
|
+
): Line {
|
|
249
|
+
const safePct = typeof pct === 'number' && Number.isFinite(pct) ? pct : 0;
|
|
250
|
+
const n = Math.max(0, Math.min(width, Math.round((safePct / 100) * width)));
|
|
251
|
+
return line(
|
|
252
|
+
{ text: BLOCK.repeat(n), ink: filled },
|
|
253
|
+
{ text: TRACK.repeat(width - n), ink: 'dim' },
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Fixed-width bar field: full blocks, an eighth-fraction edge, spaces to width. */
|
|
258
|
+
export function bar(value: number, max: number, width: number): string {
|
|
259
|
+
if (!(value > 0) || !(max > 0)) {
|
|
260
|
+
return ' '.repeat(width);
|
|
261
|
+
}
|
|
262
|
+
const scaled = (value / max) * width;
|
|
263
|
+
let full = Math.floor(scaled);
|
|
264
|
+
let fi = Math.round((scaled - full) * 8);
|
|
265
|
+
if (fi === 8) {
|
|
266
|
+
full += 1;
|
|
267
|
+
fi = 0;
|
|
268
|
+
}
|
|
269
|
+
if (full === 0 && fi === 0) {
|
|
270
|
+
fi = 1;
|
|
271
|
+
}
|
|
272
|
+
let out = BLOCK.repeat(Math.min(full, width));
|
|
273
|
+
if (full < width && fi > 0) {
|
|
274
|
+
out += EIGHTHS[fi] ?? '';
|
|
275
|
+
}
|
|
276
|
+
return pad(out, width, 'right');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function pad2(n: number): string {
|
|
280
|
+
return String(n).padStart(2, '0');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export const MONTHS = [
|
|
284
|
+
'Jan',
|
|
285
|
+
'Feb',
|
|
286
|
+
'Mar',
|
|
287
|
+
'Apr',
|
|
288
|
+
'May',
|
|
289
|
+
'Jun',
|
|
290
|
+
'Jul',
|
|
291
|
+
'Aug',
|
|
292
|
+
'Sep',
|
|
293
|
+
'Oct',
|
|
294
|
+
'Nov',
|
|
295
|
+
'Dec',
|
|
296
|
+
] as const;
|
|
297
|
+
|
|
298
|
+
/** Compact count: 1.2K / 3.4M / 5.6B; '—' for absent or NaN. */
|
|
299
|
+
export function fmtTokens(n: null | number | undefined): string {
|
|
300
|
+
if (n == null || Number.isNaN(n)) {
|
|
301
|
+
return '—';
|
|
302
|
+
}
|
|
303
|
+
if (n >= 1e9) {
|
|
304
|
+
return (n / 1e9).toFixed(1) + 'B';
|
|
305
|
+
}
|
|
306
|
+
if (n >= 1e6) {
|
|
307
|
+
return (n / 1e6).toFixed(1) + 'M';
|
|
308
|
+
}
|
|
309
|
+
if (n >= 1e3) {
|
|
310
|
+
return (n / 1e3).toFixed(1) + 'K';
|
|
311
|
+
}
|
|
312
|
+
return String(n);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Grouped integer, en-US separators; 0 for absent. */
|
|
316
|
+
export function fmtNum(n: null | number | undefined): string {
|
|
317
|
+
return (n || 0).toLocaleString('en-US');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export type Clock = 'minute' | 'none' | 'second';
|
|
321
|
+
|
|
322
|
+
/** Local date-time stamp: `YYYY-MM-DD`, plus `HH:mm` and optionally seconds. */
|
|
323
|
+
export function localStamp(now: Date, clock: Clock): string {
|
|
324
|
+
const day = `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())}`;
|
|
325
|
+
if (clock === 'none') {
|
|
326
|
+
return day;
|
|
327
|
+
}
|
|
328
|
+
const hhmm = `${pad2(now.getHours())}:${pad2(now.getMinutes())}`;
|
|
329
|
+
return clock === 'second'
|
|
330
|
+
? `${day} ${hhmm}:${pad2(now.getSeconds())}`
|
|
331
|
+
: `${day} ${hhmm}`;
|
|
332
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export * from './cli.
|
|
2
|
-
export * from './dev.
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './
|
|
1
|
+
export * from './cli.ts';
|
|
2
|
+
export * from './dev.ts';
|
|
3
|
+
export * from './draw.ts';
|
|
4
|
+
export * from './input.ts';
|
|
5
|
+
export * from './last-reply.ts';
|
|
6
|
+
export * from './logger.ts';
|
|
7
|
+
export * from './mcp.ts';
|
|
8
|
+
export * from './shutdown.ts';
|
package/src/logger.ts
CHANGED
|
@@ -15,16 +15,36 @@ const LEVEL_LABEL: Record<Exclude<LogLevel, 'silent'>, string> = {
|
|
|
15
15
|
warn: 'WARN',
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
const VALID_LEVELS: readonly LogLevel[] = [
|
|
19
|
+
'debug',
|
|
20
|
+
'info',
|
|
21
|
+
'warn',
|
|
22
|
+
'error',
|
|
23
|
+
'silent',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export function loggerFromEnv(
|
|
27
|
+
env: NodeJS.ProcessEnv,
|
|
28
|
+
variable: string,
|
|
29
|
+
fallback: LogLevel = 'info',
|
|
30
|
+
): Logger {
|
|
31
|
+
const raw = env[variable];
|
|
32
|
+
return new Logger(VALID_LEVELS.find(level => level === raw) ?? fallback);
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
type Writer = (message: string) => void;
|
|
19
36
|
|
|
20
37
|
// stdout carries the MCP transport; everything here goes to stderr.
|
|
21
38
|
export class Logger {
|
|
39
|
+
readonly activeLevel: LogLevel;
|
|
40
|
+
|
|
22
41
|
private readonly stderr: Writer;
|
|
23
42
|
|
|
24
43
|
constructor(
|
|
25
|
-
|
|
44
|
+
activeLevel: LogLevel,
|
|
26
45
|
stderr: Writer = line => process.stderr.write(`${line}\n`),
|
|
27
46
|
) {
|
|
47
|
+
this.activeLevel = activeLevel;
|
|
28
48
|
this.stderr = stderr;
|
|
29
49
|
}
|
|
30
50
|
|
package/src/mcp.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Logger } from './logger.ts';
|
|
2
|
+
|
|
3
|
+
export interface TextContent {
|
|
4
|
+
readonly text: string;
|
|
5
|
+
readonly type: 'text';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// The index signature mirrors the SDK's CallToolResult (zod-inferred, so it
|
|
9
|
+
// carries one); without it an interface would not assign to that type.
|
|
10
|
+
export interface ErrorResult {
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
readonly content: TextContent[];
|
|
13
|
+
readonly isError: true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function describeError(err: unknown): string {
|
|
17
|
+
return err instanceof Error ? err.message : String(err);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ToolRun<Args, Result> = (args: Args) => Promise<Result> | Result;
|
|
21
|
+
|
|
22
|
+
/** A zod schema, named structurally so core stays zod-free. */
|
|
23
|
+
export interface ToolInputSchema<Args> {
|
|
24
|
+
parse(raw: unknown): Args;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The wrapper owns the error-drift class: a thrown or rejected error (zod
|
|
29
|
+
* parse included) logs and answers an `isError` result instead of escaping
|
|
30
|
+
* the tool call.
|
|
31
|
+
*/
|
|
32
|
+
export function toolHandler<Result>(
|
|
33
|
+
log: Logger,
|
|
34
|
+
toErrorResult: (err: unknown) => Result,
|
|
35
|
+
) {
|
|
36
|
+
return tool;
|
|
37
|
+
|
|
38
|
+
function tool<Args>(
|
|
39
|
+
name: string,
|
|
40
|
+
run: (args: Args) => Promise<Result>,
|
|
41
|
+
schema?: ToolInputSchema<Args>,
|
|
42
|
+
): (raw?: unknown) => Promise<Result>;
|
|
43
|
+
function tool<Args>(
|
|
44
|
+
name: string,
|
|
45
|
+
run: (args: Args) => Result,
|
|
46
|
+
schema?: ToolInputSchema<Args>,
|
|
47
|
+
): (raw?: unknown) => Result;
|
|
48
|
+
function tool<Args>(
|
|
49
|
+
name: string,
|
|
50
|
+
run: ToolRun<Args, Result>,
|
|
51
|
+
schema?: ToolInputSchema<Args>,
|
|
52
|
+
): ToolRun<unknown, Result> {
|
|
53
|
+
function fail(err: unknown): Result {
|
|
54
|
+
log.error(`${name} failed: ${describeError(err)}`);
|
|
55
|
+
return toErrorResult(err);
|
|
56
|
+
}
|
|
57
|
+
return raw => {
|
|
58
|
+
try {
|
|
59
|
+
const args = schema === undefined ? (raw as Args) : schema.parse(raw);
|
|
60
|
+
const result = run(args);
|
|
61
|
+
return result instanceof Promise
|
|
62
|
+
? result.then(undefined, fail)
|
|
63
|
+
: result;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
return fail(err);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/vite.ts
CHANGED
|
@@ -1,71 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
createServerModuleRunner,
|
|
4
|
+
createServer as createViteServer,
|
|
5
|
+
} from 'vite';
|
|
5
6
|
import { configDefaults } from 'vitest/config';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import type { Logger } from './logger.ts';
|
|
9
|
+
|
|
10
|
+
import { watchWithReload } from './dev.ts';
|
|
11
|
+
import { describeError } from './mcp.ts';
|
|
12
|
+
import { shutdownOnSignals } from './shutdown.ts';
|
|
13
|
+
|
|
14
|
+
export interface ServerViteOptions {
|
|
15
|
+
testTimeout?: number;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
export function
|
|
15
|
-
mode: string,
|
|
16
|
-
{ standalone, entries = {} }: ModViteOptions,
|
|
17
|
-
) {
|
|
18
|
-
// The standalone pass emits the artifact the plugin folder carries — the
|
|
19
|
-
// mod execs it through the session's Bash tool, where no node_modules
|
|
20
|
-
// exists, so it inlines every dependency. Only ever run under `node`, where
|
|
21
|
-
// the banner is inert.
|
|
22
|
-
if (mode === standalone.mode) {
|
|
23
|
-
return {
|
|
24
|
-
ssr: { noExternal: true },
|
|
25
|
-
build: {
|
|
26
|
-
outDir: 'dist',
|
|
27
|
-
emptyOutDir: false,
|
|
28
|
-
sourcemap: true,
|
|
29
|
-
minify: false,
|
|
30
|
-
ssr: true,
|
|
31
|
-
rollupOptions: {
|
|
32
|
-
input: { [standalone.name]: 'src/index.ts' },
|
|
33
|
-
output: {
|
|
34
|
-
banner: '#!/usr/bin/env node',
|
|
35
|
-
entryFileNames: `${standalone.name}.mjs`,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
// The CLI pass builds the default way: external deps, installed beside it
|
|
42
|
-
// by npx. SSR/Node build: Vite externalizes node built-ins AND package.json
|
|
43
|
-
// `dependencies` (`@v1nvn/agentic-core` stays a real dep), so only source
|
|
44
|
-
// is bundled; ESM out matches "type":"module".
|
|
18
|
+
export function serverVite({ testTimeout }: ServerViteOptions = {}) {
|
|
45
19
|
return {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
minify: false,
|
|
51
|
-
ssr: true,
|
|
52
|
-
rollupOptions: {
|
|
53
|
-
input: { index: 'src/index.ts', ...entries },
|
|
54
|
-
// `bin` (dist/index.js) is exec'd by the kernel; without a shebang
|
|
55
|
-
// the OS runs it under /bin/sh and `npx <bin>` dies parsing `import`.
|
|
56
|
-
// Library chunks carry none.
|
|
57
|
-
output: {
|
|
58
|
-
banner: (chunk: { facadeModuleId?: string; isEntry: boolean }) =>
|
|
59
|
-
chunk.isEntry && chunk.facadeModuleId?.endsWith('src/index.ts')
|
|
60
|
-
? '#!/usr/bin/env node'
|
|
61
|
-
: '',
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
20
|
+
// Vite 8 spins the HMR websocket from `server.ws`, not `server.hmr`
|
|
21
|
+
// (`hmr: false` stops messaging, not the socket): without this a second
|
|
22
|
+
// dev boot dies fighting over the default ws port 24678.
|
|
23
|
+
server: { ws: false },
|
|
65
24
|
test: {
|
|
66
25
|
globals: true,
|
|
67
26
|
environment: 'node',
|
|
68
|
-
|
|
27
|
+
...(testTimeout === undefined ? {} : { testTimeout }),
|
|
28
|
+
exclude: [...configDefaults.exclude, 'dist/**'],
|
|
69
29
|
},
|
|
70
30
|
};
|
|
71
31
|
}
|
|
32
|
+
|
|
33
|
+
export interface DevSession<T> {
|
|
34
|
+
closers: readonly { close(): Promise<unknown> }[];
|
|
35
|
+
reload(module: T): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The hot-reload harness every MCP-server dev entry runs. The transport and
|
|
40
|
+
* McpServer are single-use under the SDK (`StdioServerTransport.start()`
|
|
41
|
+
* throws on a second call), so `start` creates and connects them once and
|
|
42
|
+
* only the registrations swap on each file change. `start` must register
|
|
43
|
+
* before connecting — the SDK's `registerCapabilities` throws post-connect.
|
|
44
|
+
* `entry` is the module that exports the registration families, at
|
|
45
|
+
* `<package root>/src/<name>.ts`.
|
|
46
|
+
*/
|
|
47
|
+
export async function runDevServer<T>(options: {
|
|
48
|
+
entry: string;
|
|
49
|
+
log: Logger;
|
|
50
|
+
start: (module: T) => DevSession<T> | Promise<DevSession<T>>;
|
|
51
|
+
}): Promise<void> {
|
|
52
|
+
const vite = await createViteServer({
|
|
53
|
+
appType: 'custom',
|
|
54
|
+
configFile: false,
|
|
55
|
+
// Vite logs `info` (e.g. "(ssr) page reload") via console.log to stdout,
|
|
56
|
+
// which corrupts the MCP stream; `warn` keeps errors on stderr.
|
|
57
|
+
logLevel: 'warn',
|
|
58
|
+
root: resolve(dirname(options.entry), '..'),
|
|
59
|
+
server: { middlewareMode: true, ws: false },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// hmr: false in a hot-reload harness looks wrong but is right: the
|
|
63
|
+
// watcher re-imports the whole entry rather than patching modules.
|
|
64
|
+
const runner = createServerModuleRunner(vite.environments.ssr, {
|
|
65
|
+
hmr: false,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const first = await runner.import<T>(options.entry);
|
|
69
|
+
const session = await options.start(first);
|
|
70
|
+
|
|
71
|
+
async function runOneReload(): Promise<void> {
|
|
72
|
+
try {
|
|
73
|
+
runner.evaluatedModules.clear();
|
|
74
|
+
// Import the next module before reload removes the live
|
|
75
|
+
// registrations: a failed import leaves what is serving intact.
|
|
76
|
+
const next = await runner.import<T>(options.entry);
|
|
77
|
+
session.reload(next);
|
|
78
|
+
options.log.info('[reload] success');
|
|
79
|
+
} catch (err) {
|
|
80
|
+
options.log.error(`[reload] failed: ${describeError(err)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
watchWithReload(vite, runOneReload);
|
|
85
|
+
|
|
86
|
+
options.log.info('[reload] ready');
|
|
87
|
+
|
|
88
|
+
shutdownOnSignals([...session.closers, vite]);
|
|
89
|
+
}
|
package/dist/index.js
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
//#region src/cli.ts
|
|
5
|
-
var HelpRequested = class extends Error {
|
|
6
|
-
command;
|
|
7
|
-
constructor(command) {
|
|
8
|
-
super(`${command.name()} --help`);
|
|
9
|
-
this.command = command;
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
function parseQuietly(program, args) {
|
|
13
|
-
function quiet(command) {
|
|
14
|
-
command.configureOutput({
|
|
15
|
-
writeOut: () => void 0,
|
|
16
|
-
writeErr: () => void 0
|
|
17
|
-
});
|
|
18
|
-
command.exitOverride((err) => {
|
|
19
|
-
const code = err.code;
|
|
20
|
-
if (code === "commander.help" || code === "commander.helpDisplayed") throw new HelpRequested(command);
|
|
21
|
-
throw err;
|
|
22
|
-
});
|
|
23
|
-
command.commands.forEach(quiet);
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
quiet(program);
|
|
27
|
-
program.parse([...args], { from: "user" });
|
|
28
|
-
return program;
|
|
29
|
-
} catch (err) {
|
|
30
|
-
return err instanceof HelpRequested ? err : void 0;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function exitWithCode(code) {
|
|
34
|
-
process.exit(code);
|
|
35
|
-
}
|
|
36
|
-
function printHelpAndExit(command) {
|
|
37
|
-
console.log(command.helpInformation());
|
|
38
|
-
exitWithCode(0);
|
|
39
|
-
}
|
|
40
|
-
function printUsageAndExit(program) {
|
|
41
|
-
console.error(program.helpInformation());
|
|
42
|
-
exitWithCode(1);
|
|
43
|
-
}
|
|
44
|
-
async function runMain(main) {
|
|
45
|
-
try {
|
|
46
|
-
await main();
|
|
47
|
-
} catch (e) {
|
|
48
|
-
console.error(e.message);
|
|
49
|
-
exitWithCode(1);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
//#endregion
|
|
53
|
-
//#region src/dev.ts
|
|
54
|
-
var RELOAD_DEBOUNCE_MS = 150;
|
|
55
|
-
var IGNORED_SEGMENTS = [
|
|
56
|
-
"/node_modules/",
|
|
57
|
-
"/dist/",
|
|
58
|
-
"/.git/",
|
|
59
|
-
"/coverage/"
|
|
60
|
-
];
|
|
61
|
-
function shouldReload(file) {
|
|
62
|
-
return IGNORED_SEGMENTS.every((seg) => !file.includes(seg));
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Debounced file-change reloads, serialized via a promise chain: concurrent
|
|
66
|
-
* saves never overlap, and a mid-reload save still produces a follow-up.
|
|
67
|
-
*/
|
|
68
|
-
function watchWithReload(vite, reload) {
|
|
69
|
-
let chain = Promise.resolve();
|
|
70
|
-
let timer;
|
|
71
|
-
vite.watcher.on("change", (file) => {
|
|
72
|
-
if (!shouldReload(file)) return;
|
|
73
|
-
if (timer) clearTimeout(timer);
|
|
74
|
-
timer = setTimeout(() => {
|
|
75
|
-
timer = void 0;
|
|
76
|
-
chain = chain.then(reload);
|
|
77
|
-
}, RELOAD_DEBOUNCE_MS);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
//#endregion
|
|
81
|
-
//#region src/input.ts
|
|
82
|
-
function readAll(stream) {
|
|
83
|
-
return new Promise((resolve, reject) => {
|
|
84
|
-
let data = "";
|
|
85
|
-
stream.setEncoding("utf8");
|
|
86
|
-
stream.on("data", (chunk) => {
|
|
87
|
-
data += chunk;
|
|
88
|
-
});
|
|
89
|
-
stream.on("end", () => {
|
|
90
|
-
resolve(data);
|
|
91
|
-
});
|
|
92
|
-
stream.on("error", reject);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
function readStdin(stream = process.stdin) {
|
|
96
|
-
return readAll(stream);
|
|
97
|
-
}
|
|
98
|
-
/** A markdown file's contents, with the CLI's no-such-file error. */
|
|
99
|
-
function readMarkdownFile(path) {
|
|
100
|
-
if (!statSync(path, { throwIfNoEntry: false })?.isFile()) throw new Error(`no such file: ${path}`);
|
|
101
|
-
return readFileSync(path, "utf8");
|
|
102
|
-
}
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/last-reply.ts
|
|
105
|
-
function isFile(path) {
|
|
106
|
-
try {
|
|
107
|
-
return statSync(path).isFile();
|
|
108
|
-
} catch {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function textBlocks(entry) {
|
|
113
|
-
const content = entry.message?.content;
|
|
114
|
-
if (entry.type !== "assistant" || !Array.isArray(content)) return;
|
|
115
|
-
const blocks = content.filter((b) => typeof b === "object" && b !== null && b.type === "text");
|
|
116
|
-
return blocks.length > 0 ? blocks : void 0;
|
|
117
|
-
}
|
|
118
|
-
function claudeProjectsDir() {
|
|
119
|
-
const claudeDir = process.env.CLAUDE_DIR ?? join(homedir(), ".claude");
|
|
120
|
-
return join(claudeDir, "projects");
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* The last assistant text reply from a Claude Code session, reproducing Claude
|
|
124
|
-
* Code's `/copy` byte-for-byte: the last assistant entry that contains a
|
|
125
|
-
* `text` block, only its `text` block(s) (tool_use / thinking dropped), blocks
|
|
126
|
-
* joined with a blank line, and no trailing newline.
|
|
127
|
-
*
|
|
128
|
-
* @param arg a transcript file, a session UUID under the project dir, or
|
|
129
|
-
* nothing to use the newest session for the current project.
|
|
130
|
-
*/
|
|
131
|
-
function lastReply(arg) {
|
|
132
|
-
const proj = (process.env.CLAUDE_PROJECT_DIR ?? process.cwd()).replaceAll("/", "-");
|
|
133
|
-
const projDir = join(claudeProjectsDir(), proj);
|
|
134
|
-
let file;
|
|
135
|
-
if (arg !== void 0) file = isFile(arg) ? arg : join(projDir, `${arg}.jsonl`);
|
|
136
|
-
else if (existsSync(projDir)) {
|
|
137
|
-
const newest = readdirSync(projDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({
|
|
138
|
-
f,
|
|
139
|
-
mtimeMs: statSync(join(projDir, f)).mtimeMs
|
|
140
|
-
})).sort((a, b) => b.mtimeMs - a.mtimeMs).at(0);
|
|
141
|
-
if (newest) file = join(projDir, newest.f);
|
|
142
|
-
}
|
|
143
|
-
if (file === void 0 || !isFile(file)) throw new Error(`no session transcript found in ${projDir}`);
|
|
144
|
-
let last;
|
|
145
|
-
for (const line of readFileSync(file, "utf8").split("\n")) {
|
|
146
|
-
if (!line.includes("\"assistant\"")) continue;
|
|
147
|
-
let entry;
|
|
148
|
-
try {
|
|
149
|
-
entry = JSON.parse(line);
|
|
150
|
-
} catch {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
const blocks = textBlocks(entry);
|
|
154
|
-
if (blocks) last = blocks;
|
|
155
|
-
}
|
|
156
|
-
return (last ?? []).map((b) => b.text ?? "").join("\n\n");
|
|
157
|
-
}
|
|
158
|
-
//#endregion
|
|
159
|
-
//#region src/logger.ts
|
|
160
|
-
var LEVEL_RANK = {
|
|
161
|
-
debug: 10,
|
|
162
|
-
info: 20,
|
|
163
|
-
warn: 30,
|
|
164
|
-
error: 40,
|
|
165
|
-
silent: Number.MAX_SAFE_INTEGER
|
|
166
|
-
};
|
|
167
|
-
var LEVEL_LABEL = {
|
|
168
|
-
debug: "DEBUG",
|
|
169
|
-
error: "ERROR",
|
|
170
|
-
info: "INFO",
|
|
171
|
-
warn: "WARN"
|
|
172
|
-
};
|
|
173
|
-
var Logger = class {
|
|
174
|
-
activeLevel;
|
|
175
|
-
stderr;
|
|
176
|
-
constructor(activeLevel, stderr = (line) => process.stderr.write(`${line}\n`)) {
|
|
177
|
-
this.activeLevel = activeLevel;
|
|
178
|
-
this.stderr = stderr;
|
|
179
|
-
}
|
|
180
|
-
debug(message) {
|
|
181
|
-
this.log("debug", message);
|
|
182
|
-
}
|
|
183
|
-
error(message) {
|
|
184
|
-
this.log("error", message);
|
|
185
|
-
}
|
|
186
|
-
info(message) {
|
|
187
|
-
this.log("info", message);
|
|
188
|
-
}
|
|
189
|
-
warn(message) {
|
|
190
|
-
this.log("warn", message);
|
|
191
|
-
}
|
|
192
|
-
log(level, message) {
|
|
193
|
-
if (LEVEL_RANK[level] >= LEVEL_RANK[this.activeLevel]) this.stderr(`${LEVEL_LABEL[level]} ${message}`);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
//#endregion
|
|
197
|
-
//#region src/shutdown.ts
|
|
198
|
-
/** Close each closable in order (best-effort), then force-exit. */
|
|
199
|
-
function shutdownOnSignals(closables, signals = ["SIGINT", "SIGTERM"]) {
|
|
200
|
-
function shutdown() {
|
|
201
|
-
closables.reduce((acc, closable) => acc.then(async () => {
|
|
202
|
-
try {
|
|
203
|
-
await closable.close();
|
|
204
|
-
} catch {}
|
|
205
|
-
}), Promise.resolve()).finally(() => {
|
|
206
|
-
process.exit(0);
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
for (const signal of signals) process.on(signal, shutdown);
|
|
210
|
-
}
|
|
211
|
-
//#endregion
|
|
212
|
-
export { HelpRequested, Logger, claudeProjectsDir, lastReply, parseQuietly, printHelpAndExit, printUsageAndExit, readMarkdownFile, readStdin, runMain, shutdownOnSignals, watchWithReload };
|
|
213
|
-
|
|
214
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/cli.ts","../src/dev.ts","../src/input.ts","../src/last-reply.ts","../src/logger.ts","../src/shutdown.ts"],"sourcesContent":["import type { Command } from 'commander';\n\n// A --help ask caught mid-parse: parseQuietly returns it instead of exiting,\n// so a spec can drive the parse and the bin decides to print and exit.\nexport class HelpRequested extends Error {\n constructor(readonly command: Command) {\n super(`${command.name()} --help`);\n }\n}\n\nexport function parseQuietly(\n program: Command,\n args: readonly string[],\n): Command | HelpRequested | undefined {\n function quiet(command: Command): void {\n command.configureOutput({\n writeOut: () => undefined,\n writeErr: () => undefined,\n });\n command.exitOverride(err => {\n const code = (err as { code?: string }).code;\n if (code === 'commander.help' || code === 'commander.helpDisplayed') {\n throw new HelpRequested(command);\n }\n throw err;\n });\n command.commands.forEach(quiet);\n }\n try {\n quiet(program);\n program.parse([...args], { from: 'user' });\n return program;\n } catch (err) {\n return err instanceof HelpRequested ? err : undefined;\n }\n}\n\nfunction exitWithCode(code: number): never {\n // eslint-disable-next-line n/no-process-exit\n process.exit(code);\n}\n\nexport function printHelpAndExit(command: Command): never {\n console.log(command.helpInformation());\n exitWithCode(0);\n}\n\nexport function printUsageAndExit(program: Command): never {\n console.error(program.helpInformation());\n exitWithCode(1);\n}\n\nexport async function runMain(main: () => Promise<void> | void): Promise<void> {\n try {\n await main();\n } catch (e) {\n console.error((e as Error).message);\n exitWithCode(1);\n }\n}\n","// Structural stand-ins for vite's dev server and module runner — the shared\n// hot-reload wiring below must not pull vite into core's runtime surface; the\n// MCP packages pass their real instances through.\nexport interface ReloadableRunner {\n readonly evaluatedModules: { clear(): void };\n import<T>(entry: string): Promise<T>;\n}\n\nexport interface WatchableDevServer {\n close(): Promise<unknown>;\n readonly watcher: {\n on(event: 'change', cb: (file: string) => void): unknown;\n };\n}\n\nconst RELOAD_DEBOUNCE_MS = 150;\n\nconst IGNORED_SEGMENTS = ['/node_modules/', '/dist/', '/.git/', '/coverage/'];\n\nfunction shouldReload(file: string): boolean {\n return IGNORED_SEGMENTS.every(seg => !file.includes(seg));\n}\n\n/**\n * Debounced file-change reloads, serialized via a promise chain: concurrent\n * saves never overlap, and a mid-reload save still produces a follow-up.\n */\nexport function watchWithReload(\n vite: WatchableDevServer,\n reload: () => Promise<void>,\n): void {\n let chain: Promise<void> = Promise.resolve();\n let timer: NodeJS.Timeout | undefined;\n vite.watcher.on('change', file => {\n if (!shouldReload(file)) {\n return;\n }\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n timer = undefined;\n chain = chain.then(reload);\n }, RELOAD_DEBOUNCE_MS);\n });\n}\n","import { readFileSync, statSync } from 'node:fs';\n\nfunction readAll(stream: NodeJS.ReadableStream): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = '';\n stream.setEncoding('utf8');\n stream.on('data', (chunk: string) => {\n data += chunk;\n });\n stream.on('end', () => {\n resolve(data);\n });\n stream.on('error', reject);\n });\n}\n\nexport function readStdin(\n stream: NodeJS.ReadableStream = process.stdin,\n): Promise<string> {\n return readAll(stream);\n}\n\n/** A markdown file's contents, with the CLI's no-such-file error. */\nexport function readMarkdownFile(path: string): string {\n const stats = statSync(path, { throwIfNoEntry: false });\n if (!stats?.isFile()) {\n throw new Error(`no such file: ${path}`);\n }\n return readFileSync(path, 'utf8');\n}\n","import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nfunction isFile(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\ninterface ContentBlock {\n text?: string;\n type: string;\n}\n\ninterface TranscriptEntry {\n message?: { content?: unknown };\n type?: string;\n}\n\nfunction textBlocks(entry: TranscriptEntry): ContentBlock[] | undefined {\n const content = entry.message?.content;\n if (entry.type !== 'assistant' || !Array.isArray(content)) {\n return undefined;\n }\n const blocks = content.filter(\n (b): b is ContentBlock =>\n typeof b === 'object' &&\n b !== null &&\n (b as ContentBlock).type === 'text',\n );\n return blocks.length > 0 ? blocks : undefined;\n}\n\nexport function claudeProjectsDir(): string {\n const claudeDir = process.env.CLAUDE_DIR ?? join(homedir(), '.claude');\n return join(claudeDir, 'projects');\n}\n\n/**\n * The last assistant text reply from a Claude Code session, reproducing Claude\n * Code's `/copy` byte-for-byte: the last assistant entry that contains a\n * `text` block, only its `text` block(s) (tool_use / thinking dropped), blocks\n * joined with a blank line, and no trailing newline.\n *\n * @param arg a transcript file, a session UUID under the project dir, or\n * nothing to use the newest session for the current project.\n */\nexport function lastReply(arg?: string): string {\n // Claude Code keys transcripts under the project root with \"/\" → \"-\".\n // Prefer $CLAUDE_PROJECT_DIR (exported to hook processes) over $PWD.\n const proj = (process.env.CLAUDE_PROJECT_DIR ?? process.cwd()).replaceAll(\n '/',\n '-',\n );\n const projDir = join(claudeProjectsDir(), proj);\n\n let file: string | undefined;\n if (arg !== undefined) {\n file = isFile(arg) ? arg : join(projDir, `${arg}.jsonl`);\n } else if (existsSync(projDir)) {\n const newest = readdirSync(projDir)\n .filter(f => f.endsWith('.jsonl'))\n .map(f => ({ f, mtimeMs: statSync(join(projDir, f)).mtimeMs }))\n .sort((a, b) => b.mtimeMs - a.mtimeMs)\n .at(0);\n if (newest) {\n file = join(projDir, newest.f);\n }\n }\n\n if (file === undefined || !isFile(file)) {\n throw new Error(`no session transcript found in ${projDir}`);\n }\n\n let last: ContentBlock[] | undefined;\n for (const line of readFileSync(file, 'utf8').split('\\n')) {\n if (!line.includes('\"assistant\"')) {\n continue;\n }\n let entry: TranscriptEntry;\n try {\n entry = JSON.parse(line) as TranscriptEntry;\n } catch {\n continue;\n }\n const blocks = textBlocks(entry);\n if (blocks) {\n last = blocks;\n }\n }\n return (last ?? []).map(b => b.text ?? '').join('\\n\\n');\n}\n","export type LogLevel = 'debug' | 'error' | 'info' | 'silent' | 'warn';\n\nconst LEVEL_RANK: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n silent: Number.MAX_SAFE_INTEGER,\n};\n\nconst LEVEL_LABEL: Record<Exclude<LogLevel, 'silent'>, string> = {\n debug: 'DEBUG',\n error: 'ERROR',\n info: 'INFO',\n warn: 'WARN',\n};\n\ntype Writer = (message: string) => void;\n\n// stdout carries the MCP transport; everything here goes to stderr.\nexport class Logger {\n private readonly stderr: Writer;\n\n constructor(\n readonly activeLevel: LogLevel,\n stderr: Writer = line => process.stderr.write(`${line}\\n`),\n ) {\n this.stderr = stderr;\n }\n\n debug(message: string): void {\n this.log('debug', message);\n }\n\n error(message: string): void {\n this.log('error', message);\n }\n\n info(message: string): void {\n this.log('info', message);\n }\n\n warn(message: string): void {\n this.log('warn', message);\n }\n\n private log(level: Exclude<LogLevel, 'silent'>, message: string): void {\n if (LEVEL_RANK[level] >= LEVEL_RANK[this.activeLevel]) {\n this.stderr(`${LEVEL_LABEL[level]} ${message}`);\n }\n }\n}\n","/** Close each closable in order (best-effort), then force-exit. */\nexport function shutdownOnSignals(\n closables: readonly { close(): Promise<unknown> }[],\n signals: readonly NodeJS.Signals[] = ['SIGINT', 'SIGTERM'],\n): void {\n function shutdown(): void {\n void closables\n .reduce(\n (acc: Promise<void>, closable) =>\n acc.then(async () => {\n try {\n await closable.close();\n } catch {\n // best-effort; exiting regardless\n }\n return;\n }),\n Promise.resolve(),\n )\n .finally(() => {\n // Force-exit on signal; the n/no-process-exit rule targets libraries.\n // eslint-disable-next-line n/no-process-exit\n process.exit(0);\n });\n }\n for (const signal of signals) {\n process.on(signal, shutdown);\n }\n}\n"],"mappings":"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"}
|
package/dist/vite.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { configDefaults } from "vitest/config";
|
|
2
|
-
//#region src/vite.ts
|
|
3
|
-
/**
|
|
4
|
-
* The one two-mode build every mod home runs — a home's `vite.config.ts` is a
|
|
5
|
-
* thin wrapper handing `modVite` its standalone mode name and extra entries.
|
|
6
|
-
*/
|
|
7
|
-
function modVite(mode, { standalone, entries = {} }) {
|
|
8
|
-
if (mode === standalone.mode) return {
|
|
9
|
-
ssr: { noExternal: true },
|
|
10
|
-
build: {
|
|
11
|
-
outDir: "dist",
|
|
12
|
-
emptyOutDir: false,
|
|
13
|
-
sourcemap: true,
|
|
14
|
-
minify: false,
|
|
15
|
-
ssr: true,
|
|
16
|
-
rollupOptions: {
|
|
17
|
-
input: { [standalone.name]: "src/index.ts" },
|
|
18
|
-
output: {
|
|
19
|
-
banner: "#!/usr/bin/env node",
|
|
20
|
-
entryFileNames: `${standalone.name}.mjs`
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
return {
|
|
26
|
-
build: {
|
|
27
|
-
outDir: "dist",
|
|
28
|
-
emptyOutDir: true,
|
|
29
|
-
sourcemap: true,
|
|
30
|
-
minify: false,
|
|
31
|
-
ssr: true,
|
|
32
|
-
rollupOptions: {
|
|
33
|
-
input: {
|
|
34
|
-
index: "src/index.ts",
|
|
35
|
-
...entries
|
|
36
|
-
},
|
|
37
|
-
output: { banner: (chunk) => chunk.isEntry && chunk.facadeModuleId?.endsWith("src/index.ts") ? "#!/usr/bin/env node" : "" }
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
test: {
|
|
41
|
-
globals: true,
|
|
42
|
-
environment: "node",
|
|
43
|
-
exclude: [
|
|
44
|
-
...configDefaults.exclude,
|
|
45
|
-
"dist/**",
|
|
46
|
-
"tests/**"
|
|
47
|
-
]
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
//#endregion
|
|
52
|
-
export { modVite };
|
|
53
|
-
|
|
54
|
-
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["/**\n * The one two-mode build every mod home runs — a home's `vite.config.ts` is a\n * thin wrapper handing `modVite` its standalone mode name and extra entries.\n */\nimport { configDefaults } from 'vitest/config';\n\nexport interface ModViteOptions {\n /** Extra CLI-pass entries beside the CLI itself, e.g. tokens' `text` library chunk. */\n entries?: Record<string, string>;\n /** The `--mode` that builds the standalone artifact, and the `bin/<name>.mjs` it emits. */\n standalone: { mode: string; name: string };\n}\n\nexport function modVite(\n mode: string,\n { standalone, entries = {} }: ModViteOptions,\n) {\n // The standalone pass emits the artifact the plugin folder carries — the\n // mod execs it through the session's Bash tool, where no node_modules\n // exists, so it inlines every dependency. Only ever run under `node`, where\n // the banner is inert.\n if (mode === standalone.mode) {\n return {\n ssr: { noExternal: true },\n build: {\n outDir: 'dist',\n emptyOutDir: false,\n sourcemap: true,\n minify: false,\n ssr: true,\n rollupOptions: {\n input: { [standalone.name]: 'src/index.ts' },\n output: {\n banner: '#!/usr/bin/env node',\n entryFileNames: `${standalone.name}.mjs`,\n },\n },\n },\n };\n }\n // The CLI pass builds the default way: external deps, installed beside it\n // by npx. SSR/Node build: Vite externalizes node built-ins AND package.json\n // `dependencies` (`@v1nvn/agentic-core` stays a real dep), so only source\n // is bundled; ESM out matches \"type\":\"module\".\n return {\n build: {\n outDir: 'dist',\n emptyOutDir: true,\n sourcemap: true,\n minify: false,\n ssr: true,\n rollupOptions: {\n input: { index: 'src/index.ts', ...entries },\n // `bin` (dist/index.js) is exec'd by the kernel; without a shebang\n // the OS runs it under /bin/sh and `npx <bin>` dies parsing `import`.\n // Library chunks carry none.\n output: {\n banner: (chunk: { facadeModuleId?: string; isEntry: boolean }) =>\n chunk.isEntry && chunk.facadeModuleId?.endsWith('src/index.ts')\n ? '#!/usr/bin/env node'\n : '',\n },\n },\n },\n test: {\n globals: true,\n environment: 'node',\n exclude: [...configDefaults.exclude, 'dist/**', 'tests/**'],\n },\n };\n}\n"],"mappings":";;;;;;AAaA,SAAgB,QACd,MACA,EAAE,YAAY,UAAU,CAAC,KACzB;CAKA,IAAI,SAAS,WAAW,MACtB,OAAO;EACL,KAAK,EAAE,YAAY,KAAK;EACxB,OAAO;GACL,QAAQ;GACR,aAAa;GACb,WAAW;GACX,QAAQ;GACR,KAAK;GACL,eAAe;IACb,OAAO,GAAG,WAAW,OAAO,eAAe;IAC3C,QAAQ;KACN,QAAQ;KACR,gBAAgB,GAAG,WAAW,KAAK;IACrC;GACF;EACF;CACF;CAMF,OAAO;EACL,OAAO;GACL,QAAQ;GACR,aAAa;GACb,WAAW;GACX,QAAQ;GACR,KAAK;GACL,eAAe;IACb,OAAO;KAAE,OAAO;KAAgB,GAAG;IAAQ;IAI3C,QAAQ,EACN,SAAS,UACP,MAAM,WAAW,MAAM,gBAAgB,SAAS,cAAc,IAC1D,wBACA,GACR;GACF;EACF;EACA,MAAM;GACJ,SAAS;GACT,aAAa;GACb,SAAS;IAAC,GAAG,eAAe;IAAS;IAAW;GAAU;EAC5D;CACF;AACF"}
|