@v1nvn/rm 0.14.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/dist/index.js +197 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
//#region ../core/dist/index.js
|
|
7
|
+
/**
|
|
8
|
+
* Read the Claude Code hook event JSON from stdin; empty or unparseable input
|
|
9
|
+
* yields an empty event, never a thrown error — a hook must always answer.
|
|
10
|
+
*/
|
|
11
|
+
function readHookEvent(stream = process.stdin) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
let data = "";
|
|
14
|
+
stream.setEncoding("utf8");
|
|
15
|
+
stream.on("data", (chunk) => {
|
|
16
|
+
data += chunk;
|
|
17
|
+
});
|
|
18
|
+
stream.on("end", () => {
|
|
19
|
+
try {
|
|
20
|
+
resolve(JSON.parse(data));
|
|
21
|
+
} catch {
|
|
22
|
+
resolve({});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
stream.on("error", () => {
|
|
26
|
+
resolve({});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Which transcript lastReply should read, per the hook event: transcript_path
|
|
32
|
+
* when it names a real file, else session_id, else nothing (lastReply then
|
|
33
|
+
* falls back to the newest session for the current project).
|
|
34
|
+
*/
|
|
35
|
+
function replyTarget(event) {
|
|
36
|
+
if (event.transcript_path !== void 0 && isFile$1(event.transcript_path)) return event.transcript_path;
|
|
37
|
+
return event.session_id || void 0;
|
|
38
|
+
}
|
|
39
|
+
function isFile$1(path) {
|
|
40
|
+
try {
|
|
41
|
+
return statSync(path).isFile();
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Print the UserPromptExpansion decision for a zero-token command: `block`
|
|
48
|
+
* keeps the command from reaching the model, with the output as the `reason`.
|
|
49
|
+
*/
|
|
50
|
+
function emitHookBlock(reason) {
|
|
51
|
+
process.stdout.write(`${JSON.stringify({
|
|
52
|
+
decision: "block",
|
|
53
|
+
reason
|
|
54
|
+
})}\n`);
|
|
55
|
+
}
|
|
56
|
+
function isFile(path) {
|
|
57
|
+
try {
|
|
58
|
+
return statSync(path).isFile();
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function textBlocks(entry) {
|
|
64
|
+
const content = entry.message?.content;
|
|
65
|
+
if (entry.type !== "assistant" || !Array.isArray(content)) return;
|
|
66
|
+
const blocks = content.filter((b) => typeof b === "object" && b !== null && b.type === "text");
|
|
67
|
+
return blocks.length > 0 ? blocks : void 0;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The last assistant text reply from a Claude Code session, reproducing Claude
|
|
71
|
+
* Code's `/copy` byte-for-byte: the last assistant entry that contains a
|
|
72
|
+
* `text` block, only its `text` block(s) (tool_use / thinking dropped), blocks
|
|
73
|
+
* joined with a blank line, and no trailing newline.
|
|
74
|
+
*
|
|
75
|
+
* @param arg a transcript file, a session UUID under the project dir, or
|
|
76
|
+
* nothing to use the newest session for the current project.
|
|
77
|
+
*/
|
|
78
|
+
function lastReply(arg) {
|
|
79
|
+
const claudeDir = process.env.CLAUDE_DIR ?? join(homedir(), ".claude");
|
|
80
|
+
const proj = (process.env.CLAUDE_PROJECT_DIR ?? process.cwd()).replaceAll("/", "-");
|
|
81
|
+
const projDir = join(claudeDir, "projects", proj);
|
|
82
|
+
let file;
|
|
83
|
+
if (arg !== void 0) file = isFile(arg) ? arg : join(projDir, `${arg}.jsonl`);
|
|
84
|
+
else if (existsSync(projDir)) {
|
|
85
|
+
const newest = readdirSync(projDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({
|
|
86
|
+
f,
|
|
87
|
+
mtimeMs: statSync(join(projDir, f)).mtimeMs
|
|
88
|
+
})).sort((a, b) => b.mtimeMs - a.mtimeMs).at(0);
|
|
89
|
+
if (newest) file = join(projDir, newest.f);
|
|
90
|
+
}
|
|
91
|
+
if (file === void 0 || !isFile(file)) throw new Error(`no session transcript found in ${projDir}`);
|
|
92
|
+
let last;
|
|
93
|
+
for (const line of readFileSync(file, "utf8").split("\n")) {
|
|
94
|
+
if (!line.includes("\"assistant\"")) continue;
|
|
95
|
+
let entry;
|
|
96
|
+
try {
|
|
97
|
+
entry = JSON.parse(line);
|
|
98
|
+
} catch {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const blocks = textBlocks(entry);
|
|
102
|
+
if (blocks) last = blocks;
|
|
103
|
+
}
|
|
104
|
+
return (last ?? []).map((b) => b.text ?? "").join("\n\n");
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/send.ts
|
|
108
|
+
/**
|
|
109
|
+
* Convert Markdown to EPUB (pandoc) and push it to the reMarkable over SSH.
|
|
110
|
+
*
|
|
111
|
+
* rm-send path/to/reply.md # from a file
|
|
112
|
+
* rm-send # from the last Claude reply (core.lastReply)
|
|
113
|
+
*
|
|
114
|
+
* Env knobs: REMARKABLE_HOST (default: remarkable), REMARKABLE_DIR (default: /home/root/books)
|
|
115
|
+
*/
|
|
116
|
+
function timestamp(now) {
|
|
117
|
+
function p(n) {
|
|
118
|
+
return String(n).padStart(2, "0");
|
|
119
|
+
}
|
|
120
|
+
return `${now.getFullYear()}-${p(now.getMonth() + 1)}-${p(now.getDate())} ${p(now.getHours())}:${p(now.getMinutes())}`;
|
|
121
|
+
}
|
|
122
|
+
/** Title = first Markdown heading, else a timestamp. */
|
|
123
|
+
function titleOf(markdown, now = /* @__PURE__ */ new Date()) {
|
|
124
|
+
return markdown.split("\n").find((line) => line.startsWith("#"))?.replace(/^#+\s*/, "") || `Claude reply ${timestamp(now)}`;
|
|
125
|
+
}
|
|
126
|
+
/** Slugify a title into a safe filename: runs of non-alphanumerics → '_', capped at 60 chars. */
|
|
127
|
+
function slugify(title) {
|
|
128
|
+
return title.replaceAll(/[^a-zA-Z0-9]+/g, "_").slice(0, 60);
|
|
129
|
+
}
|
|
130
|
+
function run(cmd, args) {
|
|
131
|
+
const res = spawnSync(cmd, args, { stdio: [
|
|
132
|
+
"ignore",
|
|
133
|
+
"ignore",
|
|
134
|
+
"pipe"
|
|
135
|
+
] });
|
|
136
|
+
if (res.error) throw new Error(`${cmd}: ${res.error.message}`);
|
|
137
|
+
if (res.status !== 0) throw new Error(`${cmd} failed: ${res.stderr.toString().trim()}`);
|
|
138
|
+
}
|
|
139
|
+
/** Returns the "Sent: …" status line; throws with the failing command's output. */
|
|
140
|
+
function sendToRemarkable(markdown) {
|
|
141
|
+
const host = process.env.REMARKABLE_HOST ?? "remarkable";
|
|
142
|
+
const dir = process.env.REMARKABLE_DIR ?? "/home/root/books";
|
|
143
|
+
const title = titleOf(markdown);
|
|
144
|
+
const safe = slugify(title);
|
|
145
|
+
const tmp = mkdtempSync(join(tmpdir(), "rm-send-"));
|
|
146
|
+
try {
|
|
147
|
+
const mdPath = join(tmp, "reply.md");
|
|
148
|
+
const epubPath = join(tmp, `${safe}.epub`);
|
|
149
|
+
writeFileSync(mdPath, markdown);
|
|
150
|
+
run("pandoc", [
|
|
151
|
+
mdPath,
|
|
152
|
+
"-o",
|
|
153
|
+
epubPath,
|
|
154
|
+
"--metadata",
|
|
155
|
+
`title=${title}`
|
|
156
|
+
]);
|
|
157
|
+
run("ssh", [host, `mkdir -p '${dir}'`]);
|
|
158
|
+
run("scp", [
|
|
159
|
+
"-q",
|
|
160
|
+
epubPath,
|
|
161
|
+
`${host}:${dir}/${safe}.epub`
|
|
162
|
+
]);
|
|
163
|
+
return `Sent: ${safe}.epub → ${host}:${dir}`;
|
|
164
|
+
} finally {
|
|
165
|
+
rmSync(tmp, {
|
|
166
|
+
recursive: true,
|
|
167
|
+
force: true
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/index.ts
|
|
173
|
+
var file = process.argv[2];
|
|
174
|
+
function readMarkdown() {
|
|
175
|
+
if (file === void 0) return lastReply();
|
|
176
|
+
if (!statSync(file, { throwIfNoEntry: false })?.isFile()) throw new Error(`no such file: ${file}`);
|
|
177
|
+
return readFileSync(file, "utf8");
|
|
178
|
+
}
|
|
179
|
+
if (process.argv.includes("--hook")) {
|
|
180
|
+
const event = await readHookEvent();
|
|
181
|
+
let reason;
|
|
182
|
+
try {
|
|
183
|
+
reason = sendToRemarkable(lastReply(replyTarget(event)));
|
|
184
|
+
} catch (e) {
|
|
185
|
+
reason = `send failed: ${e.message}`;
|
|
186
|
+
}
|
|
187
|
+
emitHookBlock(reason);
|
|
188
|
+
} else try {
|
|
189
|
+
console.log(sendToRemarkable(readMarkdown()));
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.error(e.message);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
195
|
+
export {};
|
|
196
|
+
|
|
197
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../core/dist/index.js","../src/send.ts","../src/index.ts"],"sourcesContent":["import { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n//#region src/hook.ts\n/**\n* Read the Claude Code hook event JSON from stdin; empty or unparseable input\n* yields an empty event, never a thrown error — a hook must always answer.\n*/\nfunction readHookEvent(stream = process.stdin) {\n\treturn new Promise((resolve) => {\n\t\tlet data = \"\";\n\t\tstream.setEncoding(\"utf8\");\n\t\tstream.on(\"data\", (chunk) => {\n\t\t\tdata += chunk;\n\t\t});\n\t\tstream.on(\"end\", () => {\n\t\t\ttry {\n\t\t\t\tresolve(JSON.parse(data));\n\t\t\t} catch {\n\t\t\t\tresolve({});\n\t\t\t}\n\t\t});\n\t\tstream.on(\"error\", () => {\n\t\t\tresolve({});\n\t\t});\n\t});\n}\n/**\n* Which transcript lastReply should read, per the hook event: transcript_path\n* when it names a real file, else session_id, else nothing (lastReply then\n* falls back to the newest session for the current project).\n*/\nfunction replyTarget(event) {\n\tif (event.transcript_path !== void 0 && isFile$1(event.transcript_path)) return event.transcript_path;\n\treturn event.session_id || void 0;\n}\nfunction isFile$1(path) {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n/**\n* Print the UserPromptExpansion decision for a zero-token command: `block`\n* keeps the command from reaching the model, with the output as the `reason`.\n*/\nfunction emitHookBlock(reason) {\n\tprocess.stdout.write(`${JSON.stringify({\n\t\tdecision: \"block\",\n\t\treason\n\t})}\\n`);\n}\n//#endregion\n//#region src/last-reply.ts\nfunction isFile(path) {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\nfunction textBlocks(entry) {\n\tconst content = entry.message?.content;\n\tif (entry.type !== \"assistant\" || !Array.isArray(content)) return;\n\tconst blocks = content.filter((b) => typeof b === \"object\" && b !== null && b.type === \"text\");\n\treturn blocks.length > 0 ? blocks : void 0;\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*/\nfunction lastReply(arg) {\n\tconst claudeDir = process.env.CLAUDE_DIR ?? join(homedir(), \".claude\");\n\tconst proj = (process.env.CLAUDE_PROJECT_DIR ?? process.cwd()).replaceAll(\"/\", \"-\");\n\tconst projDir = join(claudeDir, \"projects\", proj);\n\tlet file;\n\tif (arg !== void 0) file = isFile(arg) ? arg : join(projDir, `${arg}.jsonl`);\n\telse if (existsSync(projDir)) {\n\t\tconst newest = readdirSync(projDir).filter((f) => f.endsWith(\".jsonl\")).map((f) => ({\n\t\t\tf,\n\t\t\tmtimeMs: statSync(join(projDir, f)).mtimeMs\n\t\t})).sort((a, b) => b.mtimeMs - a.mtimeMs).at(0);\n\t\tif (newest) file = join(projDir, newest.f);\n\t}\n\tif (file === void 0 || !isFile(file)) throw new Error(`no session transcript found in ${projDir}`);\n\tlet last;\n\tfor (const line of readFileSync(file, \"utf8\").split(\"\\n\")) {\n\t\tif (!line.includes(\"\\\"assistant\\\"\")) continue;\n\t\tlet entry;\n\t\ttry {\n\t\t\tentry = JSON.parse(line);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tconst blocks = textBlocks(entry);\n\t\tif (blocks) last = blocks;\n\t}\n\treturn (last ?? []).map((b) => b.text ?? \"\").join(\"\\n\\n\");\n}\n//#endregion\n//#region src/text-format.ts\n/**\n* Plain-text rendering primitives shared by the zai and tokens report\n* renderers. Output targets a monospace terminal / hook-block `reason`, so\n* everything here is fixed-width: padding, block-glyph bars, and compact\n* number formatting.\n*/\nvar MONTHS = [\n\t\"Jan\",\n\t\"Feb\",\n\t\"Mar\",\n\t\"Apr\",\n\t\"May\",\n\t\"Jun\",\n\t\"Jul\",\n\t\"Aug\",\n\t\"Sep\",\n\t\"Oct\",\n\t\"Nov\",\n\t\"Dec\"\n];\nvar EIGHTHS = [\n\t\"\",\n\t\"▏\",\n\t\"▎\",\n\t\"▍\",\n\t\"▌\",\n\t\"▋\",\n\t\"▊\",\n\t\"▉\"\n];\nfunction fmtTokens(n) {\n\tif (n == null || Number.isNaN(n)) return \"—\";\n\tif (n >= 1e9) return (n / 1e9).toFixed(1) + \"B\";\n\tif (n >= 1e6) return (n / 1e6).toFixed(1) + \"M\";\n\tif (n >= 1e3) return (n / 1e3).toFixed(1) + \"K\";\n\treturn String(n);\n}\nfunction fmtNum(n) {\n\treturn (n || 0).toLocaleString(\"en-US\");\n}\nfunction padR(s, n) {\n\treturn s.length >= n ? s : s + \" \".repeat(n - s.length);\n}\nfunction padL(s, n) {\n\treturn s.length >= n ? s : \" \".repeat(n - s.length) + s;\n}\n/** Fixed-width bar field (width cols): █ blocks + an eighth-fraction + trailing spaces. */\nfunction barField(v, max, width) {\n\tif (!v || v <= 0 || max <= 0) return \" \".repeat(width);\n\tconst scaled = v / max * width;\n\tlet full = Math.floor(scaled);\n\tlet fi = Math.round((scaled - full) * 8);\n\tif (fi === 8) {\n\t\tfull += 1;\n\t\tfi = 0;\n\t}\n\tif (full === 0 && fi === 0) fi = 1;\n\tlet s = \"█\".repeat(Math.min(full, width));\n\tif (full < width && fi > 0) s += EIGHTHS[fi];\n\tif (s.length < width) s += \" \".repeat(width - s.length);\n\treturn s.slice(0, width);\n}\n/** Filled/empty meter: █ for used, ░ for remaining. */\nfunction meter(pct, width) {\n\tlet filled = Math.round((pct || 0) / 100 * width);\n\tfilled = Math.max(0, Math.min(width, filled));\n\treturn \"█\".repeat(filled) + \"░\".repeat(width - filled);\n}\n//#endregion\nexport { MONTHS, barField, emitHookBlock, fmtNum, fmtTokens, lastReply, meter, padL, padR, readHookEvent, replyTarget };\n\n//# sourceMappingURL=index.js.map","/**\n * Convert Markdown to EPUB (pandoc) and push it to the reMarkable over SSH.\n *\n * rm-send path/to/reply.md # from a file\n * rm-send # from the last Claude reply (core.lastReply)\n *\n * Env knobs: REMARKABLE_HOST (default: remarkable), REMARKABLE_DIR (default: /home/root/books)\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { mkdtempSync, rmSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\nfunction timestamp(now: Date): string {\n function p(n: number): string {\n return String(n).padStart(2, '0');\n }\n return `${now.getFullYear()}-${p(now.getMonth() + 1)}-${p(now.getDate())} ${p(now.getHours())}:${p(now.getMinutes())}`;\n}\n\n/** Title = first Markdown heading, else a timestamp. */\nexport function titleOf(markdown: string, now = new Date()): string {\n const heading = markdown.split('\\n').find(line => line.startsWith('#'));\n return heading?.replace(/^#+\\s*/, '') || `Claude reply ${timestamp(now)}`;\n}\n\n/** Slugify a title into a safe filename: runs of non-alphanumerics → '_', capped at 60 chars. */\nexport function slugify(title: string): string {\n return title.replaceAll(/[^a-zA-Z0-9]+/g, '_').slice(0, 60);\n}\n\nfunction run(cmd: string, args: string[]): void {\n const res = spawnSync(cmd, args, { stdio: ['ignore', 'ignore', 'pipe'] });\n if (res.error) {\n throw new Error(`${cmd}: ${res.error.message}`);\n }\n if (res.status !== 0) {\n throw new Error(`${cmd} failed: ${res.stderr.toString().trim()}`);\n }\n}\n\n/** Returns the \"Sent: …\" status line; throws with the failing command's output. */\nexport function sendToRemarkable(markdown: string): string {\n const host = process.env.REMARKABLE_HOST ?? 'remarkable';\n const dir = process.env.REMARKABLE_DIR ?? '/home/root/books';\n const title = titleOf(markdown);\n const safe = slugify(title);\n\n const tmp = mkdtempSync(join(tmpdir(), 'rm-send-'));\n try {\n const mdPath = join(tmp, 'reply.md');\n const epubPath = join(tmp, `${safe}.epub`);\n writeFileSync(mdPath, markdown);\n run('pandoc', [mdPath, '-o', epubPath, '--metadata', `title=${title}`]);\n run('ssh', [host, `mkdir -p '${dir}'`]);\n run('scp', ['-q', epubPath, `${host}:${dir}/${safe}.epub`]);\n return `Sent: ${safe}.epub → ${host}:${dir}`;\n } finally {\n rmSync(tmp, { recursive: true, force: true });\n }\n}\n","import {\n emitHookBlock,\n lastReply,\n readHookEvent,\n replyTarget,\n} from '@v1nvn/agentic-core';\nimport { readFileSync, statSync } from 'node:fs';\n\nimport { sendToRemarkable } from './send.js';\n\nconst file = process.argv[2] as string | undefined;\n\nfunction readMarkdown(): string {\n if (file === undefined) {\n return lastReply();\n }\n const stats = statSync(file, { throwIfNoEntry: false });\n if (!stats?.isFile()) {\n throw new Error(`no such file: ${file}`);\n }\n return readFileSync(file, 'utf8');\n}\n\nif (process.argv.includes('--hook')) {\n const event = await readHookEvent();\n let reason: string;\n try {\n reason = sendToRemarkable(lastReply(replyTarget(event)));\n } catch (e) {\n reason = `send failed: ${(e as Error).message}`;\n }\n emitHookBlock(reason);\n} else {\n try {\n console.log(sendToRemarkable(readMarkdown()));\n } catch (e) {\n console.error((e as Error).message);\n // CLIs report failure through the exit code; the rule targets libraries.\n // eslint-disable-next-line n/no-process-exit\n process.exit(1);\n }\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@v1nvn/rm",
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "Beam a Markdown reply to a reMarkable as EPUB — the rm-send CLI behind the rm plugin.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rm-send": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "vite build",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"remarkable",
|
|
16
|
+
"epub",
|
|
17
|
+
"claude-code"
|
|
18
|
+
],
|
|
19
|
+
"author": "v1nvn",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/v1nvn/agentic.git",
|
|
24
|
+
"directory": "packages/rm"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@v1nvn/agentic-core": "^0.14.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"vite": "^8.1.4",
|
|
40
|
+
"vitest": "^4.1.10"
|
|
41
|
+
}
|
|
42
|
+
}
|