@v1nvn/agentic-core 0.44.0 → 0.45.1

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 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` | quiet commander parsing, usage/exit, and the runMain failure tail |
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.44.0",
4
- "description": "Shared runtime for the @v1nvn agent tools — last-reply transcript reader.",
3
+ "version": "0.45.1",
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": "./dist/index.js",
6
+ "main": "./src/index.js",
7
7
  "exports": {
8
- ".": {
9
- "types": "./src/index.ts",
10
- "default": "./dist/index.js"
11
- },
12
- "./vite": {
13
- "types": "./src/vite.ts",
14
- "default": "./dist/vite.js"
15
- }
8
+ ".": "./src/index.js",
9
+ "./draw": "./src/draw.js",
10
+ "./vite": "./src/vite.js"
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": ">=22"
45
+ "node": ">=23.6"
51
46
  }
52
47
  }
package/src/cli.js ADDED
@@ -0,0 +1,194 @@
1
+ import { Command, InvalidArgumentError, Option } from 'commander';
2
+ import { describeError } from "./mcp.js";
3
+ // A --help ask caught mid-parse: the parse doors return it instead of exiting,
4
+ // and runCli answers it (the help payload) on stdout with 0.
5
+ export class HelpRequested extends Error {
6
+ command;
7
+ constructor(command) {
8
+ super(`${command.name()} --help`);
9
+ this.command = command;
10
+ }
11
+ }
12
+ function addOption(cmd, option) {
13
+ const bare = option.env === undefined &&
14
+ option.choices === undefined &&
15
+ option.default === undefined &&
16
+ option.parse === undefined;
17
+ if (bare) {
18
+ cmd.option(option.flags, option.description);
19
+ return;
20
+ }
21
+ const built = new Option(option.flags, option.description);
22
+ if (option.env !== undefined) {
23
+ built.env(option.env);
24
+ }
25
+ if (option.choices !== undefined) {
26
+ built.choices(option.choices);
27
+ }
28
+ if (option.default !== undefined) {
29
+ built.default(option.default);
30
+ }
31
+ const { parse } = option;
32
+ if (parse !== undefined) {
33
+ // A plain Error from a parse fn skips commander's exitOverride, which is
34
+ // what records the subcommand for the usage print — wrap it so it rides.
35
+ built.argParser((value) => {
36
+ try {
37
+ return parse(value);
38
+ }
39
+ catch (err) {
40
+ if (err.code === 'commander.invalidArgument') {
41
+ throw err;
42
+ }
43
+ throw new InvalidArgumentError(describeError(err));
44
+ }
45
+ });
46
+ }
47
+ cmd.addOption(built);
48
+ }
49
+ function wire(cmd, spec, program, isRoot, sink) {
50
+ if (spec.description !== undefined) {
51
+ cmd.description(spec.description);
52
+ }
53
+ if (spec.argument !== undefined) {
54
+ cmd.argument(spec.argument.flags, spec.argument.description);
55
+ }
56
+ for (const option of spec.options ?? []) {
57
+ addOption(cmd, option);
58
+ }
59
+ if (isRoot && spec.version !== undefined) {
60
+ cmd.option('-V, --version', 'print the version and exit');
61
+ }
62
+ cmd.action((...all) => {
63
+ const options = all.at(-2);
64
+ const command = all.at(-1);
65
+ sink?.({
66
+ command: isRoot ? undefined : spec.name,
67
+ options,
68
+ args: command.args,
69
+ root: program.opts(),
70
+ });
71
+ });
72
+ for (const sub of spec.commands ?? []) {
73
+ const child = new Command(sub.name);
74
+ wire(child, sub, program, false, sink);
75
+ cmd.addCommand(child);
76
+ }
77
+ }
78
+ function materialize(spec, sink) {
79
+ const program = new Command(spec.name);
80
+ wire(program, spec, program, true, sink);
81
+ return program;
82
+ }
83
+ export function buildCli(spec) {
84
+ return materialize(spec);
85
+ }
86
+ function quiet(command, onUsageError) {
87
+ command.configureOutput({
88
+ writeOut: () => undefined,
89
+ writeErr: () => undefined,
90
+ });
91
+ command.exitOverride(err => {
92
+ const code = err.code;
93
+ if (code === 'commander.help' || code === 'commander.helpDisplayed') {
94
+ throw new HelpRequested(command);
95
+ }
96
+ onUsageError(command);
97
+ throw err;
98
+ });
99
+ command.commands.forEach(c => {
100
+ quiet(c, onUsageError);
101
+ });
102
+ }
103
+ export function parseCli(spec, argv, onHelp, onUsageError) {
104
+ let invocation;
105
+ const program = materialize(spec, found => {
106
+ invocation = found;
107
+ });
108
+ let errored;
109
+ quiet(program, command => {
110
+ errored = command;
111
+ });
112
+ try {
113
+ program.parse([...argv], { from: 'user' });
114
+ }
115
+ catch (err) {
116
+ if (!(err instanceof HelpRequested)) {
117
+ if (errored !== undefined) {
118
+ const cause = err instanceof Error ? err : new Error(String(err));
119
+ onUsageError?.(errored, cause);
120
+ }
121
+ return undefined;
122
+ }
123
+ return onHelp !== undefined
124
+ ? onHelp(err.command.name(), err.command.helpInformation())
125
+ : err;
126
+ }
127
+ return invocation;
128
+ }
129
+ export function helpTextFor(spec, topic) {
130
+ const program = materialize(spec);
131
+ const command = topic === spec.name
132
+ ? program
133
+ : program.commands.find(c => c.name() === topic);
134
+ if (command === undefined) {
135
+ throw new Error(`no '${topic}' command to describe`);
136
+ }
137
+ return command.helpInformation();
138
+ }
139
+ function usageError(spec, topic, message) {
140
+ if (message !== undefined) {
141
+ // Commander 15 builds its messages with the 'error: ' prefix already in.
142
+ process.stderr.write(`${message}\n`);
143
+ }
144
+ process.stderr.write(`${helpTextFor(spec, topic === undefined ? spec.name : topic)}\n`);
145
+ return spec.usageExit ?? 1;
146
+ }
147
+ async function call(run, options, args) {
148
+ try {
149
+ return (await run(options, args)) ?? 0;
150
+ }
151
+ catch (err) {
152
+ console.error(err.message);
153
+ return 1;
154
+ }
155
+ }
156
+ export async function runCli(spec, argv, onHelp) {
157
+ let asked;
158
+ let usageTopic;
159
+ let usageMessage;
160
+ const parsed = parseCli(spec, argv, (topic, text) => {
161
+ asked = { topic, text };
162
+ return 0;
163
+ }, (command, err) => {
164
+ usageTopic = command.name();
165
+ usageMessage = err.message;
166
+ });
167
+ if (parsed === undefined) {
168
+ return usageError(spec, usageTopic, usageMessage);
169
+ }
170
+ if (asked !== undefined) {
171
+ if (onHelp !== undefined) {
172
+ return onHelp(asked.topic, asked.text) ?? 0;
173
+ }
174
+ process.stdout.write(`${asked.text}\n`);
175
+ return 0;
176
+ }
177
+ const invocation = parsed;
178
+ if (spec.version !== undefined && invocation.root.version === true) {
179
+ process.stdout.write(`${spec.version}\n`);
180
+ return 0;
181
+ }
182
+ if (invocation.command === undefined) {
183
+ if ((spec.commands?.length ?? 0) > 0) {
184
+ return usageError(spec);
185
+ }
186
+ return spec.run === undefined
187
+ ? 0
188
+ : await call(spec.run, invocation.options, invocation.args);
189
+ }
190
+ const sub = spec.commands?.find(c => c.name === invocation.command);
191
+ return sub?.run === undefined
192
+ ? 0
193
+ : await call(sub.run, invocation.options, invocation.args);
194
+ }
package/src/cli.ts CHANGED
@@ -1,60 +1,274 @@
1
- import type { Command } from 'commander';
1
+ import { Command, InvalidArgumentError, Option } from 'commander';
2
2
 
3
- // A --help ask caught mid-parse: parseQuietly returns it instead of exiting,
4
- // so a spec can drive the parse and the bin decides to print and exit.
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
- constructor(readonly command: Command) {
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 function parseQuietly(
12
- program: Command,
13
- args: readonly string[],
14
- ): Command | HelpRequested | undefined {
15
- function quiet(command: Command): void {
16
- command.configureOutput({
17
- writeOut: () => undefined,
18
- writeErr: () => undefined,
19
- });
20
- command.exitOverride(err => {
21
- const code = (err as { code?: string }).code;
22
- if (code === 'commander.help' || code === 'commander.helpDisplayed') {
23
- throw new HelpRequested(command);
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
- quiet(program);
31
- program.parse([...args], { from: 'user' });
32
- return program;
175
+ program.parse([...argv], { from: 'user' });
33
176
  } catch (err) {
34
- return err instanceof HelpRequested ? err : undefined;
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 exitWithCode(code: number): never {
39
- // eslint-disable-next-line n/no-process-exit
40
- process.exit(code);
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
- export function printHelpAndExit(command: Command): never {
44
- console.log(command.helpInformation());
45
- exitWithCode(0);
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
- export function printUsageAndExit(program: Command): never {
49
- console.error(program.helpInformation());
50
- exitWithCode(1);
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 runMain(main: () => Promise<void> | void): Promise<void> {
54
- try {
55
- await main();
56
- } catch (e) {
57
- console.error((e as Error).message);
58
- exitWithCode(1);
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.js ADDED
@@ -0,0 +1,25 @@
1
+ const RELOAD_DEBOUNCE_MS = 150;
2
+ const IGNORED_SEGMENTS = ['/node_modules/', '/dist/', '/.git/', '/coverage/'];
3
+ function shouldReload(file) {
4
+ return IGNORED_SEGMENTS.every(seg => !file.includes(seg));
5
+ }
6
+ /**
7
+ * Debounced file-change reloads, serialized via a promise chain: concurrent
8
+ * saves never overlap, and a mid-reload save still produces a follow-up.
9
+ */
10
+ export function watchWithReload(vite, reload) {
11
+ let chain = Promise.resolve();
12
+ let timer;
13
+ vite.watcher.on('change', file => {
14
+ if (!shouldReload(file)) {
15
+ return;
16
+ }
17
+ if (timer) {
18
+ clearTimeout(timer);
19
+ }
20
+ timer = setTimeout(() => {
21
+ timer = undefined;
22
+ chain = chain.then(reload);
23
+ }, RELOAD_DEBOUNCE_MS);
24
+ });
25
+ }
package/src/dev.ts CHANGED
@@ -1,11 +1,6 @@
1
- // Structural stand-ins for vite's dev server and module runner — the shared
2
- // hot-reload wiring below must not pull vite into core's runtime surface; the
3
- // MCP packages pass their real instances through.
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: {