tinker-agent 1.0.65 → 1.2.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/CHANGELOG.md +36 -0
- package/LICENSE +201 -0
- package/NOTICE +6 -0
- package/README.md +90 -9
- package/bin/tinker.js +39 -0
- package/node_modules/ansi-regex/index.d.ts +33 -0
- package/node_modules/ansi-regex/index.js +14 -0
- package/node_modules/ansi-regex/license +9 -0
- package/node_modules/ansi-regex/package.json +61 -0
- package/node_modules/ansi-regex/readme.md +66 -0
- package/node_modules/ansi-styles/index.d.ts +236 -0
- package/node_modules/ansi-styles/index.js +223 -0
- package/node_modules/ansi-styles/license +9 -0
- package/node_modules/ansi-styles/package.json +54 -0
- package/node_modules/ansi-styles/readme.md +173 -0
- package/node_modules/chalk/license +9 -0
- package/node_modules/chalk/package.json +83 -0
- package/node_modules/chalk/readme.md +297 -0
- package/node_modules/chalk/source/index.d.ts +325 -0
- package/node_modules/chalk/source/index.js +225 -0
- package/node_modules/chalk/source/utilities.js +33 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/node_modules/character-entities/index.d.ts +6 -0
- package/node_modules/character-entities/index.js +2132 -0
- package/node_modules/character-entities/license +22 -0
- package/node_modules/character-entities/package.json +78 -0
- package/node_modules/character-entities/readme.md +152 -0
- package/node_modules/decode-named-character-reference/index.d.ts +13 -0
- package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.js +32 -0
- package/node_modules/decode-named-character-reference/index.js +19 -0
- package/node_modules/decode-named-character-reference/license +22 -0
- package/node_modules/decode-named-character-reference/package.json +90 -0
- package/node_modules/decode-named-character-reference/readme.md +136 -0
- package/node_modules/get-east-asian-width/index.d.ts +60 -0
- package/node_modules/get-east-asian-width/index.js +30 -0
- package/node_modules/get-east-asian-width/license +9 -0
- package/node_modules/get-east-asian-width/lookup-data.js +21 -0
- package/node_modules/get-east-asian-width/lookup.js +138 -0
- package/node_modules/get-east-asian-width/package.json +71 -0
- package/node_modules/get-east-asian-width/readme.md +65 -0
- package/node_modules/get-east-asian-width/utilities.js +24 -0
- package/node_modules/has-flag/index.d.ts +39 -0
- package/node_modules/has-flag/index.js +8 -0
- package/node_modules/has-flag/license +9 -0
- package/node_modules/has-flag/package.json +49 -0
- package/node_modules/has-flag/readme.md +74 -0
- package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
- package/node_modules/is-fullwidth-code-point/index.js +12 -0
- package/node_modules/is-fullwidth-code-point/license +9 -0
- package/node_modules/is-fullwidth-code-point/package.json +53 -0
- package/node_modules/is-fullwidth-code-point/readme.md +31 -0
- package/node_modules/markdansi/LICENSE +21 -0
- package/node_modules/markdansi/README.md +167 -0
- package/node_modules/markdansi/dist/ast.d.ts +99 -0
- package/node_modules/markdansi/dist/ast.js +1 -0
- package/node_modules/markdansi/dist/cli.d.ts +17 -0
- package/node_modules/markdansi/dist/cli.js +208 -0
- package/node_modules/markdansi/dist/hyperlink.d.ts +9 -0
- package/node_modules/markdansi/dist/hyperlink.js +26 -0
- package/node_modules/markdansi/dist/index.d.ts +10 -0
- package/node_modules/markdansi/dist/index.js +16 -0
- package/node_modules/markdansi/dist/parser.d.ts +2 -0
- package/node_modules/markdansi/dist/parser.js +205 -0
- package/node_modules/markdansi/dist/render.d.ts +9 -0
- package/node_modules/markdansi/dist/render.js +680 -0
- package/node_modules/markdansi/dist/stream.d.ts +39 -0
- package/node_modules/markdansi/dist/stream.js +192 -0
- package/node_modules/markdansi/dist/theme.d.ts +18 -0
- package/node_modules/markdansi/dist/theme.js +105 -0
- package/node_modules/markdansi/dist/types.d.ts +58 -0
- package/node_modules/markdansi/dist/types.js +1 -0
- package/node_modules/markdansi/dist/wrap.d.ts +10 -0
- package/node_modules/markdansi/dist/wrap.js +73 -0
- package/node_modules/markdansi/docs/spec.md +110 -0
- package/node_modules/markdansi/package.json +78 -0
- package/node_modules/markdansi/tsconfig.json +18 -0
- package/node_modules/marked/LICENSE +44 -0
- package/node_modules/marked/README.md +115 -0
- package/node_modules/marked/bin/main.js +284 -0
- package/node_modules/marked/bin/marked.js +16 -0
- package/node_modules/marked/lib/marked.d.ts +760 -0
- package/node_modules/marked/lib/marked.esm.js +77 -0
- package/node_modules/marked/lib/marked.esm.js.map +7 -0
- package/node_modules/marked/lib/marked.umd.js +79 -0
- package/node_modules/marked/lib/marked.umd.js.map +7 -0
- package/node_modules/marked/man/marked.1 +113 -0
- package/node_modules/marked/man/marked.1.md +93 -0
- package/node_modules/marked/package.json +103 -0
- package/node_modules/slice-ansi/index.d.ts +19 -0
- package/node_modules/slice-ansi/index.js +317 -0
- package/node_modules/slice-ansi/license +10 -0
- package/node_modules/slice-ansi/package.json +59 -0
- package/node_modules/slice-ansi/readme.md +55 -0
- package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
- package/node_modules/string-width/index.d.ts +39 -0
- package/node_modules/string-width/index.js +203 -0
- package/node_modules/string-width/license +9 -0
- package/node_modules/string-width/package.json +65 -0
- package/node_modules/string-width/readme.md +66 -0
- package/node_modules/strip-ansi/index.d.ts +15 -0
- package/node_modules/strip-ansi/index.js +19 -0
- package/node_modules/strip-ansi/license +9 -0
- package/node_modules/strip-ansi/package.json +59 -0
- package/node_modules/strip-ansi/readme.md +37 -0
- package/node_modules/supports-color/browser.d.ts +1 -0
- package/node_modules/supports-color/browser.js +35 -0
- package/node_modules/supports-color/index.d.ts +55 -0
- package/node_modules/supports-color/index.js +202 -0
- package/node_modules/supports-color/license +9 -0
- package/node_modules/supports-color/package.json +64 -0
- package/node_modules/supports-color/readme.md +75 -0
- package/node_modules/supports-hyperlinks/browser.js +10 -0
- package/node_modules/supports-hyperlinks/index.d.ts +25 -0
- package/node_modules/supports-hyperlinks/index.js +163 -0
- package/node_modules/supports-hyperlinks/license +10 -0
- package/node_modules/supports-hyperlinks/package.json +47 -0
- package/node_modules/supports-hyperlinks/readme.md +45 -0
- package/package.json +41 -10
- package/patches/markdansi@0.3.2.patch +22 -2
- package/src/agent/context-builder.ts +24 -10
- package/src/agent/context-meter.ts +89 -5
- package/src/agent/loop.ts +107 -10
- package/src/agent/runtime-session.ts +1437 -82
- package/src/agent/session-ledger.ts +169 -29
- package/src/agent/types.ts +12 -1
- package/src/agent/user-prompt-projection.ts +62 -0
- package/src/cli/config.ts +57 -14
- package/src/cli/model-profiles.ts +124 -0
- package/src/cli/run-runner.ts +4 -1
- package/src/cli/tui-runner.tsx +68 -30
- package/src/context/compiled-context-hash.ts +1 -1
- package/src/context/compiled-context-validator.ts +92 -15
- package/src/context/context-automation-policy.ts +121 -0
- package/src/context/context-manager.ts +390 -11
- package/src/context/context-policy.ts +8 -0
- package/src/context/context-protocol-validator.ts +11 -1
- package/src/context/context-revision-compiler.ts +190 -27
- package/src/context/context-revision.ts +105 -19
- package/src/context/context-surface.ts +329 -0
- package/src/context/prefix-retirement-planner.ts +570 -0
- package/src/context/protocol-frame.ts +30 -3
- package/src/context/recall-retirement-contract.ts +27 -0
- package/src/context/swap-planner.ts +34 -10
- package/src/events/observation-text-log.ts +3 -3
- package/src/events/stdout-event-printer.ts +39 -3
- package/src/events/types.ts +172 -35
- package/src/ids/runtime-id.ts +6 -0
- package/src/ids/uuid-v7.ts +7 -0
- package/src/image/image-asset-store.ts +319 -0
- package/src/image/image-input-policy.ts +14 -0
- package/src/image/image-probe.ts +233 -0
- package/src/image/image-types.ts +262 -0
- package/src/instructions/project-instructions.ts +4 -1
- package/src/mcp/mcp-manager.ts +83 -19
- package/src/model/fake-model-client.ts +9 -1
- package/src/model/input-token-estimator.ts +25 -0
- package/src/model/model-client.ts +60 -0
- package/src/model/model-request-preflight.ts +1 -0
- package/src/model/moonshot-input-token-estimator.ts +107 -0
- package/src/model/openai-chat-mapping.ts +77 -1
- package/src/model/openai-chat-model-client.ts +389 -59
- package/src/model/openai-chat-stream.ts +272 -0
- package/src/model/prompt-prefix-hash.ts +2 -4
- package/src/model/token-estimator.ts +4 -0
- package/src/observation/observation-builder.ts +29 -0
- package/src/session/resume-projection.ts +94 -34
- package/src/session/session-catalog.ts +4 -4
- package/src/session/session-errors.ts +1 -1
- package/src/session/session-history-reader.ts +151 -4
- package/src/session/session-last-response-reader.ts +189 -0
- package/src/session/session-schema.ts +349 -35
- package/src/session/session-store.ts +3266 -255
- package/src/session/sqlite-session-ledger.ts +9 -4
- package/src/skills/skill-catalog.ts +182 -0
- package/src/skills/skill-context.ts +364 -0
- package/src/skills/skill-loader.ts +779 -0
- package/src/skills/skill-tool.ts +297 -0
- package/src/tools/edit.ts +51 -64
- package/src/tools/ensure-parent-directory.ts +6 -0
- package/src/tools/read.ts +2 -2
- package/src/tools/recall.ts +43 -40
- package/src/tools/registry.ts +24 -3
- package/src/tools/ripgrep.ts +3 -2
- package/src/tools/types.ts +43 -4
- package/src/tools/write.ts +20 -31
- package/src/tui/app.tsx +333 -96
- package/src/tui/clipboard.ts +5 -0
- package/src/tui/components/mcp-panel.tsx +28 -0
- package/src/tui/components/prompt-input.tsx +790 -257
- package/src/tui/components/skills-panel.tsx +31 -0
- package/src/tui/components/timeline.tsx +30 -1
- package/src/tui/event-store.ts +85 -2
- package/src/tui/file-mention.ts +234 -0
- package/src/tui/line-editor.ts +1 -0
- package/src/tui/project-slash-commands.ts +229 -0
- package/src/tui/prompt-draft.ts +510 -0
- package/src/tui/prompt-history.ts +294 -22
- package/src/tui/slash-commands.ts +46 -2
- package/src/tui/tui-session-controller.ts +110 -48
- package/src/tui/workspace-file-search.ts +94 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# 🎨 Markdansi: Wraps, colors, links—no baggage.
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="./markdansi.png" alt="Markdansi README header" width="1100">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
   
|
|
8
|
+
|
|
9
|
+
Tiny, dependency-light Markdown → ANSI renderer and CLI for modern Node (>=22). Focuses on readable terminal output with sensible wrapping, GFM support (tables, task lists, strikethrough), optional OSC‑8 hyperlinks, and zero built‑in syntax highlighting (pluggable hook). Written in TypeScript, ships ESM.
|
|
10
|
+
|
|
11
|
+
Published on npm as `markdansi`.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Grab it from npm; no native deps, so install is instant on Node 22+.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add markdansi
|
|
19
|
+
# or
|
|
20
|
+
pnpm add markdansi
|
|
21
|
+
# or
|
|
22
|
+
npm install markdansi
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## CLI
|
|
26
|
+
|
|
27
|
+
Quick one-shot renderer: pipe Markdown in, ANSI comes out. Flags let you pick width, wrap, colors, links, and table/list styling.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
markdansi [FILE] [--in FILE] [--out FILE] [--width N] [--no-wrap] [--no-color] [--no-links] [--theme default|dim|bright]
|
|
31
|
+
[--list-indent N] [--quote-prefix STR]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- Input: positional `FILE`, `--in FILE`, or stdin when neither is given (use `--in -` for stdin explicitly).
|
|
35
|
+
- Output: stdout unless `--out` provided.
|
|
36
|
+
- Wrapping: on by default; `--no-wrap` disables hard wrapping.
|
|
37
|
+
- Links: OSC‑8 when supported; `--no-links` disables.
|
|
38
|
+
- Lists/quotes: `--list-indent` sets spaces per nesting level (default 2); `--quote-prefix` sets blockquote prefix (default `│ `).
|
|
39
|
+
|
|
40
|
+
## Library
|
|
41
|
+
|
|
42
|
+
Use the renderer directly in Node/TS for customizable theming, optional syntax highlighting hooks, and OSC‑8 link control.
|
|
43
|
+
|
|
44
|
+
### ESM / CommonJS
|
|
45
|
+
|
|
46
|
+
Markdansi ships ESM (`"type":"module"`). If you’re in CommonJS (or a tool like `tsx` running your script as CJS), prefer dynamic import:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const { render } = await import("markdansi");
|
|
50
|
+
console.log(render("# hello"));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Streaming (recommended: hybrid blocks)
|
|
54
|
+
|
|
55
|
+
If you’re streaming Markdown (LLM output), keep scrollback safe by emitting **completed fragments only**
|
|
56
|
+
and writing them once (append-only; no in-place redraw).
|
|
57
|
+
|
|
58
|
+
Hybrid mode streams regular lines as they complete, but buffers multi-line constructs that need context:
|
|
59
|
+
|
|
60
|
+
- Fenced code blocks (``` / ~~~) — flushed only after the closing fence
|
|
61
|
+
- Tables — flushed only after the header separator row + until the table ends
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { createMarkdownStreamer, render } from "markdansi";
|
|
65
|
+
|
|
66
|
+
const streamer = createMarkdownStreamer({
|
|
67
|
+
render: (md) => render(md, { width: process.stdout.columns ?? 80 }),
|
|
68
|
+
spacing: "single", // collapse consecutive blank lines
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
process.stdin.setEncoding("utf8");
|
|
72
|
+
process.stdin.on("data", (delta) => {
|
|
73
|
+
const chunk = streamer.push(delta);
|
|
74
|
+
if (chunk) process.stdout.write(chunk);
|
|
75
|
+
});
|
|
76
|
+
process.stdin.on("end", () => {
|
|
77
|
+
const tail = streamer.finish();
|
|
78
|
+
if (tail) process.stdout.write(tail);
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
````js
|
|
83
|
+
import { render, createRenderer, strip, themes } from "markdansi";
|
|
84
|
+
|
|
85
|
+
const ansi = render("# Hello **world**", { width: 60 });
|
|
86
|
+
|
|
87
|
+
const renderNoWrap = createRenderer({ wrap: false });
|
|
88
|
+
const out = renderNoWrap("A very long line...");
|
|
89
|
+
|
|
90
|
+
// Plain text (no ANSI/OSC)
|
|
91
|
+
const plain = strip("link to [x](https://example.com)");
|
|
92
|
+
|
|
93
|
+
// Custom theme and highlighter hook
|
|
94
|
+
const custom = createRenderer({
|
|
95
|
+
theme: {
|
|
96
|
+
...themes.default,
|
|
97
|
+
code: { color: "cyan", dim: true }, // fallback used for inline/block
|
|
98
|
+
inlineCode: { color: "red" },
|
|
99
|
+
blockCode: { color: "green" },
|
|
100
|
+
},
|
|
101
|
+
highlighter: (code, lang) => code.toUpperCase(),
|
|
102
|
+
});
|
|
103
|
+
console.log(custom("`inline`\n\n```\nblock code\n```"));
|
|
104
|
+
|
|
105
|
+
// Example: real syntax highlighting with Shiki (TS + Swift)
|
|
106
|
+
import { bundledLanguages, bundledThemes, createHighlighter } from "shiki";
|
|
107
|
+
|
|
108
|
+
const shiki = await createHighlighter({
|
|
109
|
+
themes: [bundledThemes["github-dark"]],
|
|
110
|
+
langs: [bundledLanguages.typescript, bundledLanguages.swift],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const highlighted = createRenderer({
|
|
114
|
+
highlighter: (code, lang) => {
|
|
115
|
+
if (!lang) return code;
|
|
116
|
+
const normalized = lang.toLowerCase();
|
|
117
|
+
if (!["ts", "typescript", "swift"].includes(normalized)) return code;
|
|
118
|
+
const { tokens } = shiki.codeToTokens(code, {
|
|
119
|
+
lang: normalized === "swift" ? "swift" : "ts",
|
|
120
|
+
theme: "github-dark",
|
|
121
|
+
});
|
|
122
|
+
return tokens
|
|
123
|
+
.map((line) =>
|
|
124
|
+
line
|
|
125
|
+
.map((token) =>
|
|
126
|
+
token.color
|
|
127
|
+
? `\u001b[38;2;${parseInt(token.color.slice(1, 3), 16)};${parseInt(
|
|
128
|
+
token.color.slice(3, 5),
|
|
129
|
+
16,
|
|
130
|
+
)};${parseInt(token.color.slice(5, 7), 16)}m${token.content}\u001b[39m`
|
|
131
|
+
: token.content,
|
|
132
|
+
)
|
|
133
|
+
.join(""),
|
|
134
|
+
)
|
|
135
|
+
.join("\n");
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
console.log(highlighted("```ts\nconst x: number = 1\n```\n```swift\nlet x = 1\n```"));
|
|
139
|
+
````
|
|
140
|
+
|
|
141
|
+
### Options
|
|
142
|
+
|
|
143
|
+
- `wrap` (default `true`): if `false`, no hard wrapping anywhere.
|
|
144
|
+
- `width`: used only when `wrap===true`; default TTY columns or 80.
|
|
145
|
+
- `color` (default TTY): `false` removes all ANSI/OSC.
|
|
146
|
+
- `hyperlinks` (default auto): enable/disable OSC‑8 links.
|
|
147
|
+
- `theme`: `default | dim | bright | solarized | monochrome | contrast` or custom theme object.
|
|
148
|
+
- `listIndent`: spaces per nesting level (default 2).
|
|
149
|
+
- `quotePrefix`: blockquote line prefix (default `│ `).
|
|
150
|
+
- `tableBorder`: `unicode` (default) | `ascii` | `none`.
|
|
151
|
+
- `tablePadding`: spaces inside cells (default 1); `tableDense` drops extra separators.
|
|
152
|
+
- `tableTruncate`: cap cells to column width (default `true`, ellipsis `…`).
|
|
153
|
+
- `codeBox`: draw a box around fenced code (default true); `codeGutter` shows line numbers; `codeWrap` wraps code lines by default.
|
|
154
|
+
- `highlighter(code, lang)`: optional hook to recolor code blocks; must not add/remove newlines.
|
|
155
|
+
|
|
156
|
+
## Notes
|
|
157
|
+
|
|
158
|
+
- Code blocks wrap to the render width by default; disable with `codeWrap=false`. If `lang` is present, a faint `[lang]` label is shown and boxes use unicode borders.
|
|
159
|
+
- Link/reference definitions that spill their titles onto indented lines are merged back into one line so copied notes don’t turn into boxed code.
|
|
160
|
+
- Tables use unicode borders by default, include padding, respect GFM alignment, and truncate long cells with `…` so layouts stay tidy. Turn off truncation with `tableTruncate=false`.
|
|
161
|
+
- Tight vs loose lists follow GFM; task items render `[ ]` / `[x]`.
|
|
162
|
+
|
|
163
|
+
See [`docs/spec.md`](docs/spec.md) for full behavior details.
|
|
164
|
+
|
|
165
|
+
Looking for the Swift port? Check out [Swiftdansi](https://github.com/steipete/Swiftdansi).
|
|
166
|
+
|
|
167
|
+
MIT license.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export type Position = unknown;
|
|
2
|
+
type Node = {
|
|
3
|
+
type: string;
|
|
4
|
+
position?: Position;
|
|
5
|
+
};
|
|
6
|
+
export type Text = Node & {
|
|
7
|
+
type: "text";
|
|
8
|
+
value: string;
|
|
9
|
+
};
|
|
10
|
+
export type Emphasis = Node & {
|
|
11
|
+
type: "emphasis";
|
|
12
|
+
children: InlineNode[];
|
|
13
|
+
};
|
|
14
|
+
export type Strong = Node & {
|
|
15
|
+
type: "strong";
|
|
16
|
+
children: InlineNode[];
|
|
17
|
+
};
|
|
18
|
+
export type Delete = Node & {
|
|
19
|
+
type: "delete";
|
|
20
|
+
children: InlineNode[];
|
|
21
|
+
};
|
|
22
|
+
export type InlineCode = Node & {
|
|
23
|
+
type: "inlineCode";
|
|
24
|
+
value: string;
|
|
25
|
+
};
|
|
26
|
+
export type Link = Node & {
|
|
27
|
+
type: "link";
|
|
28
|
+
url: string;
|
|
29
|
+
title?: string | null | undefined;
|
|
30
|
+
children: InlineNode[];
|
|
31
|
+
};
|
|
32
|
+
export type Break = Node & {
|
|
33
|
+
type: "break";
|
|
34
|
+
};
|
|
35
|
+
export type Html = Node & {
|
|
36
|
+
type: "html";
|
|
37
|
+
value: string;
|
|
38
|
+
};
|
|
39
|
+
export type InlineNode = Text | Emphasis | Strong | Delete | InlineCode | Link | Break | Html;
|
|
40
|
+
export type Paragraph = Node & {
|
|
41
|
+
type: "paragraph";
|
|
42
|
+
children: InlineNode[];
|
|
43
|
+
};
|
|
44
|
+
export type Heading = Node & {
|
|
45
|
+
type: "heading";
|
|
46
|
+
depth: number;
|
|
47
|
+
children: InlineNode[];
|
|
48
|
+
};
|
|
49
|
+
export type ThematicBreak = Node & {
|
|
50
|
+
type: "thematicBreak";
|
|
51
|
+
};
|
|
52
|
+
export type Blockquote = Node & {
|
|
53
|
+
type: "blockquote";
|
|
54
|
+
children: BlockNode[];
|
|
55
|
+
};
|
|
56
|
+
export type List = Node & {
|
|
57
|
+
type: "list";
|
|
58
|
+
ordered?: boolean | undefined;
|
|
59
|
+
start?: number | null | undefined;
|
|
60
|
+
spread?: boolean | undefined;
|
|
61
|
+
children: ListItem[];
|
|
62
|
+
};
|
|
63
|
+
export type ListItem = Node & {
|
|
64
|
+
type: "listItem";
|
|
65
|
+
checked?: boolean | null | undefined;
|
|
66
|
+
spread?: boolean | undefined;
|
|
67
|
+
children: BlockNode[];
|
|
68
|
+
};
|
|
69
|
+
export type Code = Node & {
|
|
70
|
+
type: "code";
|
|
71
|
+
value: string;
|
|
72
|
+
lang?: string | null | undefined;
|
|
73
|
+
meta?: string | null | undefined;
|
|
74
|
+
};
|
|
75
|
+
export type TableCell = Node & {
|
|
76
|
+
type: "tableCell";
|
|
77
|
+
children: InlineNode[];
|
|
78
|
+
};
|
|
79
|
+
export type TableRow = Node & {
|
|
80
|
+
type: "tableRow";
|
|
81
|
+
children: TableCell[];
|
|
82
|
+
};
|
|
83
|
+
export type Table = Node & {
|
|
84
|
+
type: "table";
|
|
85
|
+
align?: Array<"left" | "right" | "center" | null> | undefined;
|
|
86
|
+
children: TableRow[];
|
|
87
|
+
};
|
|
88
|
+
export type Definition = Node & {
|
|
89
|
+
type: "definition";
|
|
90
|
+
identifier: string;
|
|
91
|
+
url?: string | undefined;
|
|
92
|
+
title?: string | null | undefined;
|
|
93
|
+
};
|
|
94
|
+
export type BlockNode = Paragraph | Heading | ThematicBreak | Blockquote | List | ListItem | Code | Table | Definition;
|
|
95
|
+
export type Root = Node & {
|
|
96
|
+
type: "root";
|
|
97
|
+
children: BlockNode[];
|
|
98
|
+
};
|
|
99
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import type { RenderOptions } from "./types.js";
|
|
3
|
+
type CliArgs = Partial<RenderOptions> & {
|
|
4
|
+
in?: string;
|
|
5
|
+
out?: string;
|
|
6
|
+
help?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Ignore EPIPE when downstream (e.g., `head`) closes early.
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleStdoutEpipe(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Parse CLI arguments into RenderOptions-ish object (plus in/out paths).
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseArgs(argv: string[]): CliArgs;
|
|
16
|
+
export declare function isDirectCliInvocation(metaUrl: string, argv1?: string): boolean;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { render } from "./index.js";
|
|
6
|
+
function readOptionValue(argv, index, option, allowLeadingDashes = false) {
|
|
7
|
+
const value = argv[index + 1];
|
|
8
|
+
if (value === undefined || (!allowLeadingDashes && value.startsWith("--"))) {
|
|
9
|
+
throw new Error(`${option} requires a value`);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function parseIntegerOption(option, value, minimum) {
|
|
14
|
+
const parsed = Number(value);
|
|
15
|
+
if (!/^\d+$/u.test(value) || !Number.isSafeInteger(parsed) || parsed < minimum) {
|
|
16
|
+
const range = minimum === 0 ? "a non-negative integer" : "a positive integer";
|
|
17
|
+
throw new Error(`${option} must be ${range}`);
|
|
18
|
+
}
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
function parseTheme(value) {
|
|
22
|
+
if (value === "default" || value === "dim" || value === "bright")
|
|
23
|
+
return value;
|
|
24
|
+
throw new Error("--theme must be default, dim, or bright");
|
|
25
|
+
}
|
|
26
|
+
function parseTableBorder(value) {
|
|
27
|
+
if (value === "unicode" || value === "ascii" || value === "none")
|
|
28
|
+
return value;
|
|
29
|
+
throw new Error("--table-border must be unicode, ascii, or none");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ignore EPIPE when downstream (e.g., `head`) closes early.
|
|
33
|
+
*/
|
|
34
|
+
export function handleStdoutEpipe() {
|
|
35
|
+
process.stdout.on("error", (err) => {
|
|
36
|
+
if (err && err.code === "EPIPE") {
|
|
37
|
+
process.exit(0);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// For other stdout errors, fail fast but don't throw unhandled.
|
|
41
|
+
console.error(err instanceof Error ? err.message : err);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parse CLI arguments into RenderOptions-ish object (plus in/out paths).
|
|
47
|
+
*/
|
|
48
|
+
export function parseArgs(argv) {
|
|
49
|
+
const args = {};
|
|
50
|
+
for (let i = 2; i < argv.length; i += 1) {
|
|
51
|
+
const a = argv[i];
|
|
52
|
+
if (!a)
|
|
53
|
+
continue;
|
|
54
|
+
if (a === "--no-wrap")
|
|
55
|
+
args.wrap = false;
|
|
56
|
+
else if (a === "--no-color")
|
|
57
|
+
args.color = false;
|
|
58
|
+
else if (a === "--no-links")
|
|
59
|
+
args.hyperlinks = false;
|
|
60
|
+
else if (a === "--code-wrap")
|
|
61
|
+
args.codeWrap = true;
|
|
62
|
+
else if (a === "--code-wrap=false")
|
|
63
|
+
args.codeWrap = false;
|
|
64
|
+
else if (a === "--code-wrap=true")
|
|
65
|
+
args.codeWrap = true;
|
|
66
|
+
else if (a === "--code-box")
|
|
67
|
+
args.codeBox = true;
|
|
68
|
+
else if (a === "--code-box=false")
|
|
69
|
+
args.codeBox = false;
|
|
70
|
+
else if (a === "--code-box=true")
|
|
71
|
+
args.codeBox = true;
|
|
72
|
+
else if (a === "--code-gutter")
|
|
73
|
+
args.codeGutter = true;
|
|
74
|
+
else if (a === "--code-gutter=true")
|
|
75
|
+
args.codeGutter = true;
|
|
76
|
+
else if (a === "--code-gutter=false")
|
|
77
|
+
args.codeGutter = false;
|
|
78
|
+
else if (a.startsWith("--table-border=")) {
|
|
79
|
+
args.tableBorder = parseTableBorder(a.slice("--table-border=".length));
|
|
80
|
+
}
|
|
81
|
+
else if (a === "--table-border") {
|
|
82
|
+
args.tableBorder = parseTableBorder(readOptionValue(argv, i, a));
|
|
83
|
+
i += 1;
|
|
84
|
+
}
|
|
85
|
+
else if (a === "--table-dense")
|
|
86
|
+
args.tableDense = true;
|
|
87
|
+
else if (a === "--table-truncate")
|
|
88
|
+
args.tableTruncate = true;
|
|
89
|
+
else if (a === "--table-truncate=false")
|
|
90
|
+
args.tableTruncate = false;
|
|
91
|
+
else if (a === "--table-truncate=true")
|
|
92
|
+
args.tableTruncate = true;
|
|
93
|
+
else if (a === "--table-padding") {
|
|
94
|
+
const next = readOptionValue(argv, i, a);
|
|
95
|
+
args.tablePadding = parseIntegerOption(a, next, 0);
|
|
96
|
+
i += 1;
|
|
97
|
+
}
|
|
98
|
+
else if (a === "--table-ellipsis") {
|
|
99
|
+
args.tableEllipsis = readOptionValue(argv, i, a, true);
|
|
100
|
+
i += 1;
|
|
101
|
+
}
|
|
102
|
+
else if (a === "--in") {
|
|
103
|
+
args.in = readOptionValue(argv, i, a, true);
|
|
104
|
+
i += 1;
|
|
105
|
+
}
|
|
106
|
+
else if (a === "--out") {
|
|
107
|
+
args.out = readOptionValue(argv, i, a, true);
|
|
108
|
+
i += 1;
|
|
109
|
+
}
|
|
110
|
+
else if (a === "--width") {
|
|
111
|
+
const next = readOptionValue(argv, i, a);
|
|
112
|
+
args.width = parseIntegerOption(a, next, 1);
|
|
113
|
+
i += 1;
|
|
114
|
+
}
|
|
115
|
+
else if (a.startsWith("--theme=")) {
|
|
116
|
+
args.theme = parseTheme(a.slice("--theme=".length));
|
|
117
|
+
}
|
|
118
|
+
else if (a === "--theme") {
|
|
119
|
+
args.theme = parseTheme(readOptionValue(argv, i, a));
|
|
120
|
+
i += 1;
|
|
121
|
+
}
|
|
122
|
+
else if (a === "--list-indent") {
|
|
123
|
+
const next = readOptionValue(argv, i, a);
|
|
124
|
+
args.listIndent = parseIntegerOption(a, next, 0);
|
|
125
|
+
i += 1;
|
|
126
|
+
}
|
|
127
|
+
else if (a === "--quote-prefix") {
|
|
128
|
+
args.quotePrefix = readOptionValue(argv, i, a, true);
|
|
129
|
+
i += 1;
|
|
130
|
+
}
|
|
131
|
+
else if (a === "--help" || a === "-h")
|
|
132
|
+
args.help = true;
|
|
133
|
+
else if (a.startsWith("-"))
|
|
134
|
+
throw new Error(`unknown option: ${a}`);
|
|
135
|
+
else if (!args.in)
|
|
136
|
+
args.in = a;
|
|
137
|
+
else
|
|
138
|
+
throw new Error(`unexpected positional argument: ${a}`);
|
|
139
|
+
}
|
|
140
|
+
return args;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* CLI entrypoint.
|
|
144
|
+
*/
|
|
145
|
+
function main() {
|
|
146
|
+
handleStdoutEpipe();
|
|
147
|
+
const { in: inputPath, out: outputPath, help, ...renderOptions } = parseArgs(process.argv);
|
|
148
|
+
if (help) {
|
|
149
|
+
process.stdout.write(`markdansi [FILE] [options]
|
|
150
|
+
|
|
151
|
+
markdansi file.md Render file
|
|
152
|
+
markdansi --in file.md Same (explicit)
|
|
153
|
+
cat file.md | markdansi Read from stdin
|
|
154
|
+
|
|
155
|
+
Options:
|
|
156
|
+
--in FILE Input file (default: stdin)
|
|
157
|
+
--out FILE Output file (default: stdout)
|
|
158
|
+
--width N Wrap width (default: TTY cols or 80)
|
|
159
|
+
--no-wrap Disable hard wrapping
|
|
160
|
+
--no-color Disable ANSI/OSC output
|
|
161
|
+
--no-links Disable OSC-8 hyperlinks
|
|
162
|
+
--theme NAME Theme (default|dim|bright)
|
|
163
|
+
--list-indent N Spaces per list nesting level (default: 2)
|
|
164
|
+
--quote-prefix STR Prefix for blockquotes (default: "│ ")
|
|
165
|
+
--table-border STR unicode|ascii|none
|
|
166
|
+
--table-padding N Spaces around table cell content
|
|
167
|
+
--table-dense Fewer separator rows
|
|
168
|
+
--table-truncate Default true; pass --table-truncate=false to disable
|
|
169
|
+
--table-ellipsis STR Ellipsis text for truncation
|
|
170
|
+
--code-wrap[=true|false] Wrap code lines (default true)
|
|
171
|
+
--code-box[=true|false] Box code blocks (default true)
|
|
172
|
+
--code-gutter[=true|false] Show code line numbers (default false)
|
|
173
|
+
`);
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
const input = inputPath && inputPath !== "-"
|
|
177
|
+
? fs.readFileSync(path.resolve(inputPath), "utf8")
|
|
178
|
+
: fs.readFileSync(0, "utf8");
|
|
179
|
+
const output = render(input, renderOptions);
|
|
180
|
+
if (outputPath) {
|
|
181
|
+
fs.writeFileSync(path.resolve(outputPath), output, "utf8");
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
process.stdout.write(output);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export function isDirectCliInvocation(metaUrl, argv1) {
|
|
188
|
+
if (!argv1)
|
|
189
|
+
return false;
|
|
190
|
+
try {
|
|
191
|
+
const entry = fs.realpathSync(argv1);
|
|
192
|
+
const self = fs.realpathSync(fileURLToPath(metaUrl));
|
|
193
|
+
return entry === self;
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Only run the CLI when executed directly, not when imported for tests.
|
|
200
|
+
if (isDirectCliInvocation(import.meta.url, process.argv[1])) {
|
|
201
|
+
try {
|
|
202
|
+
main();
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.error(`markdansi: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
+
process.exitCode = 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WriteStream } from "node:tty";
|
|
2
|
+
/**
|
|
3
|
+
* Detect OSC-8 hyperlink support for a given stream (defaults to stdout).
|
|
4
|
+
*/
|
|
5
|
+
export declare function hyperlinkSupported(stream?: WriteStream): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Build an OSC-8 hyperlink sequence.
|
|
8
|
+
*/
|
|
9
|
+
export declare function osc8(url: string, text: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import supportsHyperlinks from "supports-hyperlinks";
|
|
2
|
+
/**
|
|
3
|
+
* Detect OSC-8 hyperlink support for a given stream (defaults to stdout).
|
|
4
|
+
*/
|
|
5
|
+
export function hyperlinkSupported(stream = process.stdout) {
|
|
6
|
+
const helper = supportsHyperlinks;
|
|
7
|
+
try {
|
|
8
|
+
if (typeof supportsHyperlinks === "function") {
|
|
9
|
+
return Boolean(supportsHyperlinks(stream));
|
|
10
|
+
}
|
|
11
|
+
if (helper.stdout)
|
|
12
|
+
return Boolean(helper.stdout(stream));
|
|
13
|
+
if (helper.default && typeof helper.default === "function")
|
|
14
|
+
return Boolean(helper.default(stream));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build an OSC-8 hyperlink sequence.
|
|
23
|
+
*/
|
|
24
|
+
export function osc8(url, text) {
|
|
25
|
+
return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createRenderer, render as renderMarkdown } from "./render.js";
|
|
2
|
+
import { createMarkdownStreamer } from "./stream.js";
|
|
3
|
+
import { themes } from "./theme.js";
|
|
4
|
+
import type { RenderOptions, Theme, ThemeName } from "./types.js";
|
|
5
|
+
export { createMarkdownStreamer, createRenderer, renderMarkdown as render, themes };
|
|
6
|
+
export type { RenderOptions, Theme, ThemeName };
|
|
7
|
+
/**
|
|
8
|
+
* Render Markdown to plain text (no ANSI, no hyperlinks) while preserving layout/wrapping.
|
|
9
|
+
*/
|
|
10
|
+
export declare function strip(markdown: string, options?: RenderOptions): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRenderer, render as renderMarkdown } from "./render.js";
|
|
2
|
+
import { createMarkdownStreamer } from "./stream.js";
|
|
3
|
+
import { themes } from "./theme.js";
|
|
4
|
+
export { createMarkdownStreamer, createRenderer, renderMarkdown as render, themes };
|
|
5
|
+
/**
|
|
6
|
+
* Render Markdown to plain text (no ANSI, no hyperlinks) while preserving layout/wrapping.
|
|
7
|
+
*/
|
|
8
|
+
export function strip(markdown, options = {}) {
|
|
9
|
+
return renderMarkdown(markdown, {
|
|
10
|
+
...options,
|
|
11
|
+
color: false,
|
|
12
|
+
hyperlinks: false,
|
|
13
|
+
// ensure flags like codeWrap/tableTruncate not lost
|
|
14
|
+
wrap: options.wrap ?? true,
|
|
15
|
+
});
|
|
16
|
+
}
|