@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/src/logger.js ADDED
@@ -0,0 +1,50 @@
1
+ const LEVEL_RANK = {
2
+ debug: 10,
3
+ info: 20,
4
+ warn: 30,
5
+ error: 40,
6
+ silent: Number.MAX_SAFE_INTEGER,
7
+ };
8
+ const LEVEL_LABEL = {
9
+ debug: 'DEBUG',
10
+ error: 'ERROR',
11
+ info: 'INFO',
12
+ warn: 'WARN',
13
+ };
14
+ const VALID_LEVELS = [
15
+ 'debug',
16
+ 'info',
17
+ 'warn',
18
+ 'error',
19
+ 'silent',
20
+ ];
21
+ export function loggerFromEnv(env, variable, fallback = 'info') {
22
+ const raw = env[variable];
23
+ return new Logger(VALID_LEVELS.find(level => level === raw) ?? fallback);
24
+ }
25
+ // stdout carries the MCP transport; everything here goes to stderr.
26
+ export class Logger {
27
+ activeLevel;
28
+ stderr;
29
+ constructor(activeLevel, stderr = line => process.stderr.write(`${line}\n`)) {
30
+ this.activeLevel = activeLevel;
31
+ this.stderr = stderr;
32
+ }
33
+ debug(message) {
34
+ this.log('debug', message);
35
+ }
36
+ error(message) {
37
+ this.log('error', message);
38
+ }
39
+ info(message) {
40
+ this.log('info', message);
41
+ }
42
+ warn(message) {
43
+ this.log('warn', message);
44
+ }
45
+ log(level, message) {
46
+ if (LEVEL_RANK[level] >= LEVEL_RANK[this.activeLevel]) {
47
+ this.stderr(`${LEVEL_LABEL[level]} ${message}`);
48
+ }
49
+ }
50
+ }
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
- readonly activeLevel: LogLevel,
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.js ADDED
@@ -0,0 +1,29 @@
1
+ export function describeError(err) {
2
+ return err instanceof Error ? err.message : String(err);
3
+ }
4
+ /**
5
+ * The wrapper owns the error-drift class: a thrown or rejected error (zod
6
+ * parse included) logs and answers an `isError` result instead of escaping
7
+ * the tool call.
8
+ */
9
+ export function toolHandler(log, toErrorResult) {
10
+ return tool;
11
+ function tool(name, run, schema) {
12
+ function fail(err) {
13
+ log.error(`${name} failed: ${describeError(err)}`);
14
+ return toErrorResult(err);
15
+ }
16
+ return raw => {
17
+ try {
18
+ const args = schema === undefined ? raw : schema.parse(raw);
19
+ const result = run(args);
20
+ return result instanceof Promise
21
+ ? result.then(undefined, fail)
22
+ : result;
23
+ }
24
+ catch (err) {
25
+ return fail(err);
26
+ }
27
+ };
28
+ }
29
+ }
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
+ }
@@ -0,0 +1,23 @@
1
+ /** Close each closable in order (best-effort), then force-exit. */
2
+ export function shutdownOnSignals(closables, signals = ['SIGINT', 'SIGTERM']) {
3
+ function shutdown() {
4
+ void closables
5
+ .reduce((acc, closable) => acc.then(async () => {
6
+ try {
7
+ await closable.close();
8
+ }
9
+ catch {
10
+ // best-effort; exiting regardless
11
+ }
12
+ return;
13
+ }), Promise.resolve())
14
+ .finally(() => {
15
+ // Force-exit on signal; the n/no-process-exit rule targets libraries.
16
+ // eslint-disable-next-line n/no-process-exit
17
+ process.exit(0);
18
+ });
19
+ }
20
+ for (const signal of signals) {
21
+ process.on(signal, shutdown);
22
+ }
23
+ }
package/src/vite.js ADDED
@@ -0,0 +1,63 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { createServerModuleRunner, createServer as createViteServer, } from 'vite';
3
+ import { configDefaults } from 'vitest/config';
4
+ import { watchWithReload } from "./dev.js";
5
+ import { describeError } from "./mcp.js";
6
+ import { shutdownOnSignals } from "./shutdown.js";
7
+ export function serverVite({ testTimeout } = {}) {
8
+ return {
9
+ // Vite 8 spins the HMR websocket from `server.ws`, not `server.hmr`
10
+ // (`hmr: false` stops messaging, not the socket): without this a second
11
+ // dev boot dies fighting over the default ws port 24678.
12
+ server: { ws: false },
13
+ test: {
14
+ globals: true,
15
+ environment: 'node',
16
+ ...(testTimeout === undefined ? {} : { testTimeout }),
17
+ exclude: [...configDefaults.exclude, 'dist/**'],
18
+ },
19
+ };
20
+ }
21
+ /**
22
+ * The hot-reload harness every MCP-server dev entry runs. The transport and
23
+ * McpServer are single-use under the SDK (`StdioServerTransport.start()`
24
+ * throws on a second call), so `start` creates and connects them once and
25
+ * only the registrations swap on each file change. `start` must register
26
+ * before connecting — the SDK's `registerCapabilities` throws post-connect.
27
+ * `entry` is the module that exports the registration families, at
28
+ * `<package root>/src/<name>.ts`.
29
+ */
30
+ export async function runDevServer(options) {
31
+ const vite = await createViteServer({
32
+ appType: 'custom',
33
+ configFile: false,
34
+ // Vite logs `info` (e.g. "(ssr) page reload") via console.log to stdout,
35
+ // which corrupts the MCP stream; `warn` keeps errors on stderr.
36
+ logLevel: 'warn',
37
+ root: resolve(dirname(options.entry), '..'),
38
+ server: { middlewareMode: true, ws: false },
39
+ });
40
+ // hmr: false in a hot-reload harness looks wrong but is right: the
41
+ // watcher re-imports the whole entry rather than patching modules.
42
+ const runner = createServerModuleRunner(vite.environments.ssr, {
43
+ hmr: false,
44
+ });
45
+ const first = await runner.import(options.entry);
46
+ const session = await options.start(first);
47
+ async function runOneReload() {
48
+ try {
49
+ runner.evaluatedModules.clear();
50
+ // Import the next module before reload removes the live
51
+ // registrations: a failed import leaves what is serving intact.
52
+ const next = await runner.import(options.entry);
53
+ session.reload(next);
54
+ options.log.info('[reload] success');
55
+ }
56
+ catch (err) {
57
+ options.log.error(`[reload] failed: ${describeError(err)}`);
58
+ }
59
+ }
60
+ watchWithReload(vite, runOneReload);
61
+ options.log.info('[reload] ready');
62
+ shutdownOnSignals([...session.closers, vite]);
63
+ }
package/src/vite.ts CHANGED
@@ -1,71 +1,89 @@
1
- /**
2
- * The one two-mode build every mod home runs — a home's `vite.config.ts` is a
3
- * thin wrapper handing `modVite` its standalone mode name and extra entries.
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
- export interface ModViteOptions {
8
- /** Extra CLI-pass entries beside the CLI itself, e.g. tokens' `text` library chunk. */
9
- entries?: Record<string, string>;
10
- /** The `--mode` that builds the standalone artifact, and the `bin/<name>.mjs` it emits. */
11
- standalone: { mode: string; name: string };
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 modVite(
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
- build: {
47
- outDir: 'dist',
48
- emptyOutDir: true,
49
- sourcemap: true,
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
- exclude: [...configDefaults.exclude, 'dist/**', 'tests/**'],
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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"}