@v1nvn/md 0.35.1 → 0.36.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 +37 -17
- package/dist/index.js +23 -68
- package/dist/index.js.map +1 -1
- package/dist/send.mjs +3138 -0
- package/dist/send.mjs.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# @v1nvn/md
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The home of the md plugin — and its plugin root: the `md-send` CLI in `src/`,
|
|
4
|
+
the mod's hooks module at `hooks/register.ts` (`.ts`/`.tsx` the engine loads
|
|
5
|
+
directly, no build), engine tests at `tests/`. It sends a Markdown reply — the
|
|
6
|
+
last one, `-` on stdin, or any Markdown file — to a self-hosted
|
|
7
|
+
Markdown-Viewer as a `#share=` URL, opens the page, and copies the link.
|
|
6
8
|
|
|
7
9
|
User-facing docs: [root README](../../README.md).
|
|
8
10
|
|
|
9
11
|
## Quickstart
|
|
10
12
|
|
|
11
|
-
In Claude Code, the plugin is the way in —
|
|
12
|
-
`/md
|
|
13
|
-
|
|
13
|
+
In Claude Code, the plugin is the way in — a mod serves the two surfaces the
|
|
14
|
+
model never reads: `/md-edit` (editable) and `/md-view` (read-only) exec the
|
|
15
|
+
shipped CLI and show its status line as a dim transcript row (nothing execs
|
|
16
|
+
until a command runs).
|
|
14
17
|
|
|
15
18
|
```sh
|
|
16
19
|
claude plugin marketplace add v1nvn/agentic
|
|
@@ -20,19 +23,21 @@ claude plugin install md@agentic
|
|
|
20
23
|
In a terminal, last reply or a named file:
|
|
21
24
|
|
|
22
25
|
```sh
|
|
23
|
-
npx -y @v1nvn/md@0.
|
|
24
|
-
npx -y @v1nvn/md@0.
|
|
25
|
-
npx -y @v1nvn/md@0.
|
|
26
|
+
npx -y @v1nvn/md@0.36.1 # editable — both panes
|
|
27
|
+
npx -y @v1nvn/md@0.36.1 --view # read-only — preview pane only
|
|
28
|
+
npx -y @v1nvn/md@0.36.1 reply.md
|
|
26
29
|
```
|
|
27
30
|
|
|
28
31
|
## Usage
|
|
29
32
|
|
|
30
33
|
| Invocation | Does |
|
|
31
34
|
|---|---|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
| `npx -y @v1nvn/md@0.
|
|
35
|
-
| `npx -y @v1nvn/md@0.
|
|
35
|
+
| `/md-edit` | the previous assistant reply → viewer, edit pane enabled, one dim status row |
|
|
36
|
+
| `/md-view` | the same share read-only — preview pane only |
|
|
37
|
+
| `npx -y @v1nvn/md@0.36.1` | the same share, printed bare |
|
|
38
|
+
| `npx -y @v1nvn/md@0.36.1 --view` | read-only share |
|
|
39
|
+
| `npx -y @v1nvn/md@0.36.1 reply.md` | that file instead of the last reply |
|
|
40
|
+
| `npx -y @v1nvn/md@0.36.1 -` | Markdown from stdin |
|
|
36
41
|
|
|
37
42
|
`MD_VIEWER_URL` sets the viewer (default `https://md.v1n.space`); any set
|
|
38
43
|
`MD_NO_OPEN` value (not just `1`) skips opening the browser.
|
|
@@ -40,19 +45,34 @@ npx -y @v1nvn/md@0.35.1 reply.md
|
|
|
40
45
|
## Develop
|
|
41
46
|
|
|
42
47
|
```sh
|
|
43
|
-
yarn workspace @v1nvn/md
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
yarn workspace @v1nvn/md test # the CLI side (vitest)
|
|
49
|
+
claude plugin validate packages/md # the mod, as the engine reads it
|
|
50
|
+
claude plugin test packages/md # the mod, through the engine
|
|
51
|
+
yarn lint && yarn typecheck # from the repo root (typecheck
|
|
52
|
+
# covers tsconfig.mods.json too)
|
|
46
53
|
```
|
|
47
54
|
|
|
55
|
+
The mod API is early access and moves between Claude Code releases — a build
|
|
56
|
+
that refuses the module loads nothing, so validate after every engine update
|
|
57
|
+
and re-vendor `types/claude-code.d.ts` (repo root) from the engine-laid
|
|
58
|
+
`.claude-plugin/types/` when it changes. Read `references/mods.md` first.
|
|
59
|
+
|
|
48
60
|
## Modules
|
|
49
61
|
|
|
50
62
|
| File | Role |
|
|
51
63
|
|---|---|
|
|
52
64
|
| `src/index.ts` | bin entry (`md-send`) — dispatch, exit codes |
|
|
53
|
-
| `src/share.ts` | the
|
|
65
|
+
| `src/share.ts` | the `#share=` URL, browser open, clipboard, status line |
|
|
66
|
+
| `hooks/register.ts` | the mod: registers `/md-edit` and `/md-view`, execs the CLI, shows its line |
|
|
67
|
+
| `bin/send.mjs` | the standalone build the mod execs — committed, synced by `yarn build` |
|
|
54
68
|
|
|
55
69
|
## Contracts
|
|
56
70
|
|
|
57
71
|
- One status line of output (e.g. `Opened in Markdown-Viewer (link copied).`).
|
|
58
72
|
- The document reaches only the configured viewer — no third-party service.
|
|
73
|
+
- `/md-edit` and `/md-view` are each one `node <plugin root>/bin/send.mjs`
|
|
74
|
+
exec through the session's Bash tool (`/md-view` adds `--view`) — the same
|
|
75
|
+
share `npx md-send` runs. The one allow rule both need is
|
|
76
|
+
`Bash(node <plugin root>/bin/send.mjs:*)`; the first run asks for it once,
|
|
77
|
+
then it is remembered. Its status line is a dim transcript row the model
|
|
78
|
+
never reads.
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,21 @@ import { Command } from "commander";
|
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { deflateSync } from "node:zlib";
|
|
8
8
|
//#region ../core/dist/index.js
|
|
9
|
+
function parseQuietly(program, args, recover) {
|
|
10
|
+
try {
|
|
11
|
+
program.allowExcessArguments(false).exitOverride().configureOutput({
|
|
12
|
+
writeOut: () => void 0,
|
|
13
|
+
writeErr: () => void 0
|
|
14
|
+
}).parse([...args], { from: "user" });
|
|
15
|
+
return program;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
return recover?.(err);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function printUsageAndExit(program) {
|
|
21
|
+
console.error(program.helpInformation());
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
9
24
|
function readAll(stream) {
|
|
10
25
|
return new Promise((resolve, reject) => {
|
|
11
26
|
let data = "";
|
|
@@ -27,28 +42,6 @@ function readMarkdownFile(path) {
|
|
|
27
42
|
if (!statSync(path, { throwIfNoEntry: false })?.isFile()) throw new Error(`no such file: ${path}`);
|
|
28
43
|
return readFileSync(path, "utf8");
|
|
29
44
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Read the Claude Code hook event JSON from stdin; empty or unparseable input
|
|
32
|
-
* yields an empty event, never a thrown error — a hook must always answer.
|
|
33
|
-
*/
|
|
34
|
-
function readHookEvent(stream = process.stdin) {
|
|
35
|
-
return readAll(stream).then((data) => {
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(data);
|
|
38
|
-
} catch {
|
|
39
|
-
return {};
|
|
40
|
-
}
|
|
41
|
-
}, () => ({}));
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Which transcript lastReply should read, per the hook event: transcript_path
|
|
45
|
-
* when it names a real file, else session_id, else nothing (lastReply then
|
|
46
|
-
* falls back to the newest session for the current project).
|
|
47
|
-
*/
|
|
48
|
-
function replyTarget(event) {
|
|
49
|
-
if (event.transcript_path !== void 0 && isFile(event.transcript_path)) return event.transcript_path;
|
|
50
|
-
return event.session_id || void 0;
|
|
51
|
-
}
|
|
52
45
|
function isFile(path) {
|
|
53
46
|
try {
|
|
54
47
|
return statSync(path).isFile();
|
|
@@ -56,49 +49,6 @@ function isFile(path) {
|
|
|
56
49
|
return false;
|
|
57
50
|
}
|
|
58
51
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Print the UserPromptExpansion decision for a zero-token command: `block`
|
|
61
|
-
* keeps the command from reaching the model, with the output as the `reason`.
|
|
62
|
-
*/
|
|
63
|
-
function emitHookBlock(reason) {
|
|
64
|
-
process.stdout.write(`${JSON.stringify({
|
|
65
|
-
decision: "block",
|
|
66
|
-
reason
|
|
67
|
-
})}\n`);
|
|
68
|
-
}
|
|
69
|
-
function parseQuietly(program, args, recover) {
|
|
70
|
-
try {
|
|
71
|
-
program.allowExcessArguments(false).exitOverride().configureOutput({
|
|
72
|
-
writeOut: () => void 0,
|
|
73
|
-
writeErr: () => void 0
|
|
74
|
-
}).parse([...args], { from: "user" });
|
|
75
|
-
return program;
|
|
76
|
-
} catch (err) {
|
|
77
|
-
return recover?.(err);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function printUsageAndExit(program) {
|
|
81
|
-
console.error(program.helpInformation());
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
async function hookOrPrint(hook, failure, run, stdin = process.stdin) {
|
|
85
|
-
if (hook) {
|
|
86
|
-
let reason;
|
|
87
|
-
try {
|
|
88
|
-
reason = await run(await readHookEvent(stdin));
|
|
89
|
-
} catch (e) {
|
|
90
|
-
reason = `${failure}: ${e.message}`;
|
|
91
|
-
}
|
|
92
|
-
emitHookBlock(reason);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
console.log(await run(void 0));
|
|
97
|
-
} catch (e) {
|
|
98
|
-
console.error(e.message);
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
52
|
function textBlocks(entry) {
|
|
103
53
|
const content = entry.message?.content;
|
|
104
54
|
if (entry.type !== "assistant" || !Array.isArray(content)) return;
|
|
@@ -210,16 +160,21 @@ function mdSend(markdown, view = false) {
|
|
|
210
160
|
}
|
|
211
161
|
//#endregion
|
|
212
162
|
//#region src/index.ts
|
|
213
|
-
var program = new Command().name("md-send").description("Send a Markdown reply to the Markdown-Viewer as a #share= URL").argument("[file]", "Markdown file, - for stdin; the last reply when omitted").option("--view", "open the viewer read-only, without the edit pane")
|
|
163
|
+
var program = new Command().name("md-send").description("Send a Markdown reply to the Markdown-Viewer as a #share= URL").argument("[file]", "Markdown file, - for stdin; the last reply when omitted").option("--view", "open the viewer read-only, without the edit pane");
|
|
214
164
|
var parsed = parseQuietly(program, process.argv.slice(2)) ?? printUsageAndExit(program);
|
|
215
165
|
var arg = parsed.args.at(0);
|
|
216
|
-
var {
|
|
166
|
+
var { view } = parsed.opts();
|
|
217
167
|
function readMarkdown() {
|
|
218
168
|
if (arg === "-") return readStdin();
|
|
219
169
|
if (arg === void 0) return lastReply();
|
|
220
170
|
return readMarkdownFile(arg);
|
|
221
171
|
}
|
|
222
|
-
|
|
172
|
+
try {
|
|
173
|
+
console.log(mdSend(await readMarkdown(), view ?? false));
|
|
174
|
+
} catch (e) {
|
|
175
|
+
console.error(e.message);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
223
178
|
//#endregion
|
|
224
179
|
export {};
|
|
225
180
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../core/dist/index.js","../src/share.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/input.ts\nfunction readAll(stream) {\n\treturn new Promise((resolve, reject) => {\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\tresolve(data);\n\t\t});\n\t\tstream.on(\"error\", reject);\n\t});\n}\nfunction readStdin(stream = process.stdin) {\n\treturn readAll(stream);\n}\n/** A markdown file's contents, with the CLI's no-such-file error. */\nfunction readMarkdownFile(path) {\n\tif (!statSync(path, { throwIfNoEntry: false })?.isFile()) throw new Error(`no such file: ${path}`);\n\treturn readFileSync(path, \"utf8\");\n}\n//#endregion\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 readAll(stream).then((data) => {\n\t\ttry {\n\t\t\treturn JSON.parse(data);\n\t\t} catch {\n\t\t\treturn {};\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(event.transcript_path)) return event.transcript_path;\n\treturn event.session_id || void 0;\n}\nfunction isFile(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/cli.ts\nfunction parseQuietly(program, args, recover) {\n\ttry {\n\t\tprogram.allowExcessArguments(false).exitOverride().configureOutput({\n\t\t\twriteOut: () => void 0,\n\t\t\twriteErr: () => void 0\n\t\t}).parse([...args], { from: \"user\" });\n\t\treturn program;\n\t} catch (err) {\n\t\treturn recover?.(err);\n\t}\n}\nfunction printUsageAndExit(program) {\n\tconsole.error(program.helpInformation());\n\tprocess.exit(1);\n}\nasync function hookOrPrint(hook, failure, run, stdin = process.stdin) {\n\tif (hook) {\n\t\tlet reason;\n\t\ttry {\n\t\t\treason = await run(await readHookEvent(stdin));\n\t\t} catch (e) {\n\t\t\treason = `${failure}: ${e.message}`;\n\t\t}\n\t\temitHookBlock(reason);\n\t\treturn;\n\t}\n\ttry {\n\t\tconsole.log(await run(void 0));\n\t} catch (e) {\n\t\tconsole.error(e.message);\n\t\tprocess.exit(1);\n\t}\n}\n//#endregion\n//#region src/dev.ts\nvar RELOAD_DEBOUNCE_MS = 150;\nvar IGNORED_SEGMENTS = [\n\t\"/node_modules/\",\n\t\"/dist/\",\n\t\"/.git/\",\n\t\"/coverage/\"\n];\nfunction shouldReload(file) {\n\treturn IGNORED_SEGMENTS.every((seg) => !file.includes(seg));\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*/\nfunction watchWithReload(vite, reload) {\n\tlet chain = Promise.resolve();\n\tlet timer;\n\tvite.watcher.on(\"change\", (file) => {\n\t\tif (!shouldReload(file)) return;\n\t\tif (timer) clearTimeout(timer);\n\t\ttimer = setTimeout(() => {\n\t\t\ttimer = void 0;\n\t\t\tchain = chain.then(reload);\n\t\t}, RELOAD_DEBOUNCE_MS);\n\t});\n}\n//#endregion\n//#region src/last-reply.ts\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}\nfunction claudeProjectsDir() {\n\tconst claudeDir = process.env.CLAUDE_DIR ?? join(homedir(), \".claude\");\n\treturn join(claudeDir, \"projects\");\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 proj = (process.env.CLAUDE_PROJECT_DIR ?? process.cwd()).replaceAll(\"/\", \"-\");\n\tconst projDir = join(claudeProjectsDir(), 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/logger.ts\nvar LEVEL_RANK = {\n\tdebug: 10,\n\tinfo: 20,\n\twarn: 30,\n\terror: 40,\n\tsilent: Number.MAX_SAFE_INTEGER\n};\nfunction levelEnabled(active, wanted) {\n\treturn LEVEL_RANK[wanted] >= LEVEL_RANK[active];\n}\nvar LEVEL_LABEL = {\n\tdebug: \"DEBUG\",\n\terror: \"ERROR\",\n\tinfo: \"INFO\",\n\twarn: \"WARN\"\n};\nvar Logger = class {\n\tactiveLevel;\n\tstderr;\n\tconstructor(activeLevel, stderr = (line) => process.stderr.write(`${line}\\n`)) {\n\t\tthis.activeLevel = activeLevel;\n\t\tthis.stderr = stderr;\n\t}\n\tdebug(message) {\n\t\tthis.log(\"debug\", message);\n\t}\n\terror(message) {\n\t\tthis.log(\"error\", message);\n\t}\n\tinfo(message) {\n\t\tthis.log(\"info\", message);\n\t}\n\twarn(message) {\n\t\tthis.log(\"warn\", message);\n\t}\n\tlog(level, message) {\n\t\tif (levelEnabled(this.activeLevel, level)) this.stderr(`${LEVEL_LABEL[level]} ${message}`);\n\t}\n};\n//#endregion\n//#region src/shutdown.ts\n/** Close each closable in order (best-effort), then force-exit. */\nfunction shutdownOnSignals(closables, signals = [\"SIGINT\", \"SIGTERM\"]) {\n\tfunction shutdown() {\n\t\tclosables.reduce((acc, closable) => acc.then(async () => {\n\t\t\ttry {\n\t\t\t\tawait closable.close();\n\t\t\t} catch {}\n\t\t}), Promise.resolve()).finally(() => {\n\t\t\tprocess.exit(0);\n\t\t});\n\t}\n\tfor (const signal of signals) process.on(signal, shutdown);\n}\n//#endregion\nexport { Logger, claudeProjectsDir, emitHookBlock, hookOrPrint, isFile, lastReply, levelEnabled, parseQuietly, printUsageAndExit, readAll, readHookEvent, readMarkdownFile, readStdin, replyTarget, shutdownOnSignals, watchWithReload };\n\n//# sourceMappingURL=index.js.map","/**\n * Send Markdown to the self-hosted Markdown-Viewer as a #share= URL — opens it\n * in the browser and copies the URL.\n *\n * md-send path/to/reply.md # from a file\n * md-send - # from stdin\n * md-send # from the last Claude reply (core.lastReply)\n * md-send --view # open read-only (no edit pane)\n *\n * Env knobs: MD_VIEWER_URL (default: https://md.v1n.space), MD_NO_OPEN=1 (skip browser).\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { deflateSync } from 'node:zlib';\n\n/**\n * Encode Markdown into the Markdown-Viewer share token.\n *\n * Pipeline: UTF-8 bytes → zlib deflate (RFC 1950, pako.deflate-compatible) →\n * base64url. Matches Markdown-Viewer's encodeMarkdownForShare() in script.js\n * byte-for-byte — the decoder (pako.inflate) accepts any valid zlib stream, so\n * compression level is irrelevant.\n */\nexport function encodeShare(markdown: string): string {\n return deflateSync(Buffer.from(markdown, 'utf8'))\n .toString('base64')\n .replaceAll('+', '-')\n .replaceAll('/', '_')\n .replace(/=+$/, '');\n}\n\nexport function shareUrl(markdown: string, view = false): string {\n const base = (process.env.MD_VIEWER_URL ?? 'https://md.v1n.space').replace(\n /\\/+$/,\n '',\n );\n const mode = view ? '' : '&edit=1';\n // Without `edit=1` the viewer opens the snapshot read-only — preview pane\n // only, with edit and split disabled (Markdown-Viewer script.js: loadFromShareHash).\n return `${base}/#share=${encodeShare(markdown)}${mode}`;\n}\n\nfunction copyToClipboard(text: string): boolean {\n try {\n return (\n spawnSync('pbcopy', { input: text, stdio: ['pipe', 'ignore', 'ignore'] })\n .status === 0\n );\n } catch {\n return false;\n }\n}\n\nfunction openUrl(url: string): boolean {\n try {\n return spawnSync('open', [url], { stdio: 'ignore' }).status === 0;\n } catch {\n return false;\n }\n}\n\nexport function statusLine(opened: boolean, copied: boolean): string {\n if (opened && copied) {\n return 'Opened in Markdown-Viewer (link copied).';\n }\n if (opened) {\n return 'Opened in Markdown-Viewer.';\n }\n if (copied) {\n return 'Markdown-Viewer link copied to clipboard.';\n }\n return 'Markdown-Viewer link ready.';\n}\n\n/** Shares the markdown and returns the status line; the URL itself is never printed. */\nexport function mdSend(markdown: string, view = false): string {\n const url = shareUrl(markdown, view);\n const copied = copyToClipboard(url);\n const opened = process.env.MD_NO_OPEN === undefined && openUrl(url);\n return statusLine(opened, copied);\n}\n","import {\n hookOrPrint,\n lastReply,\n parseQuietly,\n printUsageAndExit,\n readMarkdownFile,\n readStdin,\n replyTarget,\n} from '@v1nvn/agentic-core';\nimport { Command } from 'commander';\n\nimport { mdSend } from './share.js';\n\nconst program = new Command()\n .name('md-send')\n .description('Send a Markdown reply to the Markdown-Viewer as a #share= URL')\n .argument('[file]', 'Markdown file, - for stdin; the last reply when omitted')\n .option('--view', 'open the viewer read-only, without the edit pane')\n .option('--hook', 'emit a UserPromptExpansion block instead of printing');\n\nconst parsed =\n parseQuietly(program, process.argv.slice(2)) ?? printUsageAndExit(program);\nconst arg = parsed.args.at(0);\nconst { hook, view } = parsed.opts<{\n hook: boolean | undefined;\n view: boolean | undefined;\n}>();\n\nfunction readMarkdown(): Promise<string> | string {\n if (arg === '-') {\n return readStdin();\n }\n if (arg === undefined) {\n return lastReply();\n }\n return readMarkdownFile(arg);\n}\n\nawait hookOrPrint(hook ?? false, 'send failed', async event =>\n mdSend(\n event === undefined ? await readMarkdown() : lastReply(replyTarget(event)),\n view ?? false,\n ),\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../core/dist/index.js","../src/share.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/cli.ts\nfunction parseQuietly(program, args, recover) {\n\ttry {\n\t\tprogram.allowExcessArguments(false).exitOverride().configureOutput({\n\t\t\twriteOut: () => void 0,\n\t\t\twriteErr: () => void 0\n\t\t}).parse([...args], { from: \"user\" });\n\t\treturn program;\n\t} catch (err) {\n\t\treturn recover?.(err);\n\t}\n}\nfunction printUsageAndExit(program) {\n\tconsole.error(program.helpInformation());\n\tprocess.exit(1);\n}\n//#endregion\n//#region src/dev.ts\nvar RELOAD_DEBOUNCE_MS = 150;\nvar IGNORED_SEGMENTS = [\n\t\"/node_modules/\",\n\t\"/dist/\",\n\t\"/.git/\",\n\t\"/coverage/\"\n];\nfunction shouldReload(file) {\n\treturn IGNORED_SEGMENTS.every((seg) => !file.includes(seg));\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*/\nfunction watchWithReload(vite, reload) {\n\tlet chain = Promise.resolve();\n\tlet timer;\n\tvite.watcher.on(\"change\", (file) => {\n\t\tif (!shouldReload(file)) return;\n\t\tif (timer) clearTimeout(timer);\n\t\ttimer = setTimeout(() => {\n\t\t\ttimer = void 0;\n\t\t\tchain = chain.then(reload);\n\t\t}, RELOAD_DEBOUNCE_MS);\n\t});\n}\n//#endregion\n//#region src/input.ts\nfunction readAll(stream) {\n\treturn new Promise((resolve, reject) => {\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\tresolve(data);\n\t\t});\n\t\tstream.on(\"error\", reject);\n\t});\n}\nfunction readStdin(stream = process.stdin) {\n\treturn readAll(stream);\n}\n/** A markdown file's contents, with the CLI's no-such-file error. */\nfunction readMarkdownFile(path) {\n\tif (!statSync(path, { throwIfNoEntry: false })?.isFile()) throw new Error(`no such file: ${path}`);\n\treturn readFileSync(path, \"utf8\");\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}\nfunction claudeProjectsDir() {\n\tconst claudeDir = process.env.CLAUDE_DIR ?? join(homedir(), \".claude\");\n\treturn join(claudeDir, \"projects\");\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 proj = (process.env.CLAUDE_PROJECT_DIR ?? process.cwd()).replaceAll(\"/\", \"-\");\n\tconst projDir = join(claudeProjectsDir(), 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/logger.ts\nvar LEVEL_RANK = {\n\tdebug: 10,\n\tinfo: 20,\n\twarn: 30,\n\terror: 40,\n\tsilent: Number.MAX_SAFE_INTEGER\n};\nfunction levelEnabled(active, wanted) {\n\treturn LEVEL_RANK[wanted] >= LEVEL_RANK[active];\n}\nvar LEVEL_LABEL = {\n\tdebug: \"DEBUG\",\n\terror: \"ERROR\",\n\tinfo: \"INFO\",\n\twarn: \"WARN\"\n};\nvar Logger = class {\n\tactiveLevel;\n\tstderr;\n\tconstructor(activeLevel, stderr = (line) => process.stderr.write(`${line}\\n`)) {\n\t\tthis.activeLevel = activeLevel;\n\t\tthis.stderr = stderr;\n\t}\n\tdebug(message) {\n\t\tthis.log(\"debug\", message);\n\t}\n\terror(message) {\n\t\tthis.log(\"error\", message);\n\t}\n\tinfo(message) {\n\t\tthis.log(\"info\", message);\n\t}\n\twarn(message) {\n\t\tthis.log(\"warn\", message);\n\t}\n\tlog(level, message) {\n\t\tif (levelEnabled(this.activeLevel, level)) this.stderr(`${LEVEL_LABEL[level]} ${message}`);\n\t}\n};\n//#endregion\n//#region src/shutdown.ts\n/** Close each closable in order (best-effort), then force-exit. */\nfunction shutdownOnSignals(closables, signals = [\"SIGINT\", \"SIGTERM\"]) {\n\tfunction shutdown() {\n\t\tclosables.reduce((acc, closable) => acc.then(async () => {\n\t\t\ttry {\n\t\t\t\tawait closable.close();\n\t\t\t} catch {}\n\t\t}), Promise.resolve()).finally(() => {\n\t\t\tprocess.exit(0);\n\t\t});\n\t}\n\tfor (const signal of signals) process.on(signal, shutdown);\n}\n//#endregion\nexport { Logger, claudeProjectsDir, lastReply, levelEnabled, parseQuietly, printUsageAndExit, readMarkdownFile, readStdin, shutdownOnSignals, watchWithReload };\n\n//# sourceMappingURL=index.js.map","/**\n * Send Markdown to the self-hosted Markdown-Viewer as a #share= URL — opens it\n * in the browser and copies the URL.\n *\n * md-send path/to/reply.md # from a file\n * md-send - # from stdin\n * md-send # from the last Claude reply (core.lastReply)\n * md-send --view # open read-only (no edit pane)\n *\n * Env knobs: MD_VIEWER_URL (default: https://md.v1n.space), MD_NO_OPEN=1 (skip browser).\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { deflateSync } from 'node:zlib';\n\n/**\n * Encode Markdown into the Markdown-Viewer share token.\n *\n * Pipeline: UTF-8 bytes → zlib deflate (RFC 1950, pako.deflate-compatible) →\n * base64url. Matches Markdown-Viewer's encodeMarkdownForShare() in script.js\n * byte-for-byte — the decoder (pako.inflate) accepts any valid zlib stream, so\n * compression level is irrelevant.\n */\nexport function encodeShare(markdown: string): string {\n return deflateSync(Buffer.from(markdown, 'utf8'))\n .toString('base64')\n .replaceAll('+', '-')\n .replaceAll('/', '_')\n .replace(/=+$/, '');\n}\n\nexport function shareUrl(markdown: string, view = false): string {\n const base = (process.env.MD_VIEWER_URL ?? 'https://md.v1n.space').replace(\n /\\/+$/,\n '',\n );\n const mode = view ? '' : '&edit=1';\n // Without `edit=1` the viewer opens the snapshot read-only — preview pane\n // only, with edit and split disabled (Markdown-Viewer script.js: loadFromShareHash).\n return `${base}/#share=${encodeShare(markdown)}${mode}`;\n}\n\nfunction copyToClipboard(text: string): boolean {\n try {\n return (\n spawnSync('pbcopy', { input: text, stdio: ['pipe', 'ignore', 'ignore'] })\n .status === 0\n );\n } catch {\n return false;\n }\n}\n\nfunction openUrl(url: string): boolean {\n try {\n return spawnSync('open', [url], { stdio: 'ignore' }).status === 0;\n } catch {\n return false;\n }\n}\n\nexport function statusLine(opened: boolean, copied: boolean): string {\n if (opened && copied) {\n return 'Opened in Markdown-Viewer (link copied).';\n }\n if (opened) {\n return 'Opened in Markdown-Viewer.';\n }\n if (copied) {\n return 'Markdown-Viewer link copied to clipboard.';\n }\n return 'Markdown-Viewer link ready.';\n}\n\n/** Shares the markdown and returns the status line; the URL itself is never printed. */\nexport function mdSend(markdown: string, view = false): string {\n const url = shareUrl(markdown, view);\n const copied = copyToClipboard(url);\n const opened = process.env.MD_NO_OPEN === undefined && openUrl(url);\n return statusLine(opened, copied);\n}\n","import {\n lastReply,\n parseQuietly,\n printUsageAndExit,\n readMarkdownFile,\n readStdin,\n} from '@v1nvn/agentic-core';\nimport { Command } from 'commander';\n\nimport { mdSend } from './share.js';\n\nconst program = new Command()\n .name('md-send')\n .description('Send a Markdown reply to the Markdown-Viewer as a #share= URL')\n .argument('[file]', 'Markdown file, - for stdin; the last reply when omitted')\n .option('--view', 'open the viewer read-only, without the edit pane');\n\nconst parsed =\n parseQuietly(program, process.argv.slice(2)) ?? printUsageAndExit(program);\nconst arg = parsed.args.at(0);\nconst { view } = parsed.opts<{ view: boolean | undefined }>();\n\nfunction readMarkdown(): Promise<string> | string {\n if (arg === '-') {\n return readStdin();\n }\n if (arg === undefined) {\n return lastReply();\n }\n return readMarkdownFile(arg);\n}\n\ntry {\n console.log(mdSend(await readMarkdown(), view ?? false));\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"],"mappings":"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"}
|