@albinocrabs/feynman 0.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/.codex-plugin/plugin.json +43 -0
- package/LICENSE +21 -0
- package/README.md +292 -0
- package/bin/feynman-lint.js +123 -0
- package/bin/feynman.js +559 -0
- package/hooks/feynman-activate.js +103 -0
- package/hooks/feynman-lint.js +96 -0
- package/hooks/hooks.json +17 -0
- package/hooks.json +16 -0
- package/install.sh +18 -0
- package/lib/lint/index.js +100 -0
- package/lib/lint/parser.js +229 -0
- package/lib/lint/rules.js +550 -0
- package/package.json +55 -0
- package/rules/feynman-activate.md +168 -0
- package/skills/feynman/SKILL.md +63 -0
- package/uninstall.sh +18 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "feynman",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Auto-inject ASCII diagram rules into Codex and Claude Code prompts.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "apolenkov",
|
|
7
|
+
"url": "https://github.com/apolenkov/feynman"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/apolenkov/feynman",
|
|
10
|
+
"repository": "https://github.com/apolenkov/feynman",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"codex",
|
|
14
|
+
"claude-code",
|
|
15
|
+
"ascii",
|
|
16
|
+
"diagrams",
|
|
17
|
+
"hooks",
|
|
18
|
+
"productivity"
|
|
19
|
+
],
|
|
20
|
+
"hooks": "./hooks.json",
|
|
21
|
+
"skills": "./skills/",
|
|
22
|
+
"interface": {
|
|
23
|
+
"displayName": "feynman",
|
|
24
|
+
"shortDescription": "Draw ASCII diagrams when structure appears.",
|
|
25
|
+
"longDescription": "feynman injects concise diagram rules on every prompt so flows, hierarchies, comparisons, priorities, and status summaries render as readable ASCII diagrams.",
|
|
26
|
+
"developerName": "apolenkov",
|
|
27
|
+
"category": "Productivity",
|
|
28
|
+
"capabilities": [
|
|
29
|
+
"Interactive",
|
|
30
|
+
"Write"
|
|
31
|
+
],
|
|
32
|
+
"websiteURL": "https://github.com/apolenkov/feynman",
|
|
33
|
+
"privacyPolicyURL": "https://github.com/apolenkov/feynman/blob/main/README.md",
|
|
34
|
+
"termsOfServiceURL": "https://github.com/apolenkov/feynman/blob/main/LICENSE",
|
|
35
|
+
"defaultPrompt": [
|
|
36
|
+
"Use feynman diagrams for structured answers.",
|
|
37
|
+
"Explain this architecture visually.",
|
|
38
|
+
"Compare these options with ASCII columns."
|
|
39
|
+
],
|
|
40
|
+
"brandColor": "#10B981",
|
|
41
|
+
"screenshots": []
|
|
42
|
+
}
|
|
43
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 apolenkov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://em-content.zobj.net/source/apple/391/pencil_270f-fe0f.png" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">feynman</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>why explain in words when diagram do trick</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@albinocrabs/feynman"><img src="https://img.shields.io/npm/v/@albinocrabs/feynman?style=flat&color=blue" alt="npm version"></a>
|
|
13
|
+
<a href="https://github.com/apolenkov/feynman/actions/workflows/ci.yml"><img src="https://github.com/apolenkov/feynman/workflows/CI/badge.svg" alt="CI"></a>
|
|
14
|
+
<a href="https://github.com/apolenkov/feynman/blob/main/.github/coverage-badge.json"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/apolenkov/feynman/main/.github/coverage-badge.json" alt="Coverage"></a>
|
|
15
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/apolenkov/feynman?style=flat" alt="License"></a>
|
|
16
|
+
<a href="https://github.com/apolenkov/feynman/stargazers"><img src="https://img.shields.io/github/stars/apolenkov/feynman?style=flat&color=yellow" alt="Stars"></a>
|
|
17
|
+
<a href="https://github.com/apolenkov/feynman/commits/main"><img src="https://img.shields.io/github/last-commit/apolenkov/feynman?style=flat" alt="Last Commit"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="#why-feynman">Why</a> •
|
|
22
|
+
<a href="#before--after">Before/After</a> •
|
|
23
|
+
<a href="#install">Install</a> •
|
|
24
|
+
<a href="#intensity-levels">Levels</a> •
|
|
25
|
+
<a href="#lint">Lint</a> •
|
|
26
|
+
<a href="#examples">Examples</a> •
|
|
27
|
+
<a href="CONTRIBUTING.md">Contributing</a>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
A [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and Codex
|
|
33
|
+
plugin that automatically injects ASCII diagram rules into every prompt via the
|
|
34
|
+
`UserPromptSubmit` hook.
|
|
35
|
+
|
|
36
|
+
<!-- TODO: GIF after v0.3.0 visual capture -->
|
|
37
|
+
|
|
38
|
+
## Why feynman
|
|
39
|
+
|
|
40
|
+
Structured information explained in prose forces you to rebuild the structure
|
|
41
|
+
in your head before you can reason about it. feynman intercepts every Claude
|
|
42
|
+
Code or Codex prompt and injects rules that turn flows into arrows,
|
|
43
|
+
hierarchies into trees, comparisons into columns, and status into frames. The
|
|
44
|
+
structure is visible before you have to think about it.
|
|
45
|
+
|
|
46
|
+
## Before / After
|
|
47
|
+
|
|
48
|
+
<table>
|
|
49
|
+
<tr>
|
|
50
|
+
<td width="50%">
|
|
51
|
+
|
|
52
|
+
### Without feynman
|
|
53
|
+
|
|
54
|
+
> "The deployment pipeline has three stages: first the code is built, then tests run, then it deploys to prod."
|
|
55
|
+
|
|
56
|
+
</td>
|
|
57
|
+
<td width="50%">
|
|
58
|
+
|
|
59
|
+
### With feynman
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
[Build] --> [Test] --> [Deploy]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<tr>
|
|
68
|
+
<td>
|
|
69
|
+
|
|
70
|
+
### Without feynman
|
|
71
|
+
|
|
72
|
+
> "Option A is fast but stateless. Option B is slower but persists data. Option C gives you both at higher cost."
|
|
73
|
+
|
|
74
|
+
</td>
|
|
75
|
+
<td>
|
|
76
|
+
|
|
77
|
+
### With feynman
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Option A | Option B | Option C
|
|
81
|
+
---------------|---------------|----------
|
|
82
|
+
fast startup | slow startup | medium
|
|
83
|
+
stateless | persistent | persistent
|
|
84
|
+
free | free | $$$
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
</td>
|
|
88
|
+
</tr>
|
|
89
|
+
<tr>
|
|
90
|
+
<td>
|
|
91
|
+
|
|
92
|
+
### Without feynman
|
|
93
|
+
|
|
94
|
+
> "Fix the auth bug first since it's a security issue, then the memory leak, then the slow query."
|
|
95
|
+
|
|
96
|
+
</td>
|
|
97
|
+
<td>
|
|
98
|
+
|
|
99
|
+
### With feynman
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
▲ high
|
|
103
|
+
auth bug (security)
|
|
104
|
+
memory leak
|
|
105
|
+
▼ low
|
|
106
|
+
slow query
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
</td>
|
|
110
|
+
</tr>
|
|
111
|
+
<tr>
|
|
112
|
+
<td>
|
|
113
|
+
|
|
114
|
+
### Without feynman
|
|
115
|
+
|
|
116
|
+
> "The auth service talks to Redis for rate limiting and Postgres for user data."
|
|
117
|
+
|
|
118
|
+
</td>
|
|
119
|
+
<td>
|
|
120
|
+
|
|
121
|
+
### With feynman
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
[Auth Service]
|
|
125
|
+
├── [Redis] rate limiter
|
|
126
|
+
└── [Postgres] user data
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
</td>
|
|
130
|
+
</tr>
|
|
131
|
+
</table>
|
|
132
|
+
|
|
133
|
+
## Install
|
|
134
|
+
|
|
135
|
+
**Claude Code via npx:**
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npx @albinocrabs/feynman install --target claude
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Codex via npx:**
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx @albinocrabs/feynman install --target codex
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Both clients:**
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npx @albinocrabs/feynman install --target both
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The install command is idempotent: running it again updates the existing
|
|
154
|
+
feynman hook instead of adding duplicates.
|
|
155
|
+
|
|
156
|
+
**Claude Code via bash one-liner:**
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
git clone https://github.com/apolenkov/feynman && bash feynman/install.sh
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Restart Claude Code or Codex. Done.
|
|
163
|
+
|
|
164
|
+
**Verify:** `npx @albinocrabs/feynman doctor --target claude` or `npx @albinocrabs/feynman doctor --target codex`
|
|
165
|
+
|
|
166
|
+
**Uninstall:** `npx @albinocrabs/feynman uninstall --target claude|codex|both`
|
|
167
|
+
|
|
168
|
+
**Plugin manifests:** this repo also ships `.claude-plugin/plugin.json`,
|
|
169
|
+
`hooks/hooks.json`, `.codex-plugin/plugin.json`, and `hooks.json` so plugin
|
|
170
|
+
marketplaces can discover feynman. The npx installer remains the production
|
|
171
|
+
fallback because both clients still support direct user hook registration.
|
|
172
|
+
|
|
173
|
+
<details>
|
|
174
|
+
<summary>Manual install</summary>
|
|
175
|
+
|
|
176
|
+
Add to `~/.claude/settings.json` — use the absolute path, not `~/`
|
|
177
|
+
([bug #8810](https://github.com/anthropics/claude-code/issues/8810)):
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"hooks": {
|
|
182
|
+
"UserPromptSubmit": [
|
|
183
|
+
{
|
|
184
|
+
"hooks": [
|
|
185
|
+
{
|
|
186
|
+
"type": "command",
|
|
187
|
+
"command": "node \"/absolute/path/to/feynman/hooks/feynman-activate.js\"",
|
|
188
|
+
"timeout": 5,
|
|
189
|
+
"statusMessage": "Injecting diagram rules..."
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
For Codex, add the same shape to `~/.codex/hooks.json` and set
|
|
199
|
+
`FEYNMAN_HOME` so state lives under `~/.codex`:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"hooks": {
|
|
204
|
+
"UserPromptSubmit": [
|
|
205
|
+
{
|
|
206
|
+
"hooks": [
|
|
207
|
+
{
|
|
208
|
+
"type": "command",
|
|
209
|
+
"command": "FEYNMAN_HOME=\"$HOME/.codex\" node \"/absolute/path/to/feynman/hooks/feynman-activate.js\"",
|
|
210
|
+
"timeout": 5,
|
|
211
|
+
"statusMessage": "Injecting diagram rules..."
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
</details>
|
|
220
|
+
|
|
221
|
+
## Intensity Levels
|
|
222
|
+
|
|
223
|
+
| Level | What draws | Use when |
|
|
224
|
+
|-------|-----------|----------|
|
|
225
|
+
| **lite** | Flows + trees only | Minimal, subtle |
|
|
226
|
+
| **full** | All 5 diagram types (default) | Normal use |
|
|
227
|
+
| **ultra** | Force diagram even for short answers | Maximum visual structure |
|
|
228
|
+
|
|
229
|
+
Toggle via `/feynman`:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
/feynman lite — flows and trees only
|
|
233
|
+
/feynman full — all diagram types
|
|
234
|
+
/feynman ultra — force diagrams always
|
|
235
|
+
/feynman off — disable
|
|
236
|
+
/feynman on — re-enable
|
|
237
|
+
/feynman status — show current state
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Lint
|
|
241
|
+
|
|
242
|
+
feynman includes a linter for ASCII diagrams. It catches structural errors
|
|
243
|
+
before they reach readers: unclosed boxes, wrong tree characters, mixed arrow
|
|
244
|
+
styles, inconsistent column counts, and more.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npx @albinocrabs/feynman lint response.md
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
See [docs/lint-rules.md](docs/lint-rules.md) for the full L01-L08 reference.
|
|
251
|
+
|
|
252
|
+
## Examples
|
|
253
|
+
|
|
254
|
+
Domain-specific examples showing feynman in practice:
|
|
255
|
+
|
|
256
|
+
- [Architecture review](examples/architecture-review.md) — auth service topology
|
|
257
|
+
- [API flow](examples/api-flow.md) — POST /api/login request lifecycle
|
|
258
|
+
- [Database schema](examples/db-schema.md) — e-commerce entity model
|
|
259
|
+
- [Algorithm walkthrough](examples/algorithm-explain.md) — token-bucket rate limiter
|
|
260
|
+
- [Deploy pipeline](examples/deploy-pipeline.md) — monorepo CI/CD
|
|
261
|
+
- [Code review](examples/code-review.md) — priority + comparison diagrams
|
|
262
|
+
|
|
263
|
+
## How it works
|
|
264
|
+
|
|
265
|
+
The `UserPromptSubmit` hook fires on every Claude prompt. The hook reads
|
|
266
|
+
`~/.claude/.feynman/state.json`, extracts the rules for the active intensity
|
|
267
|
+
level, and injects them as `additionalContext` — invisible to you, visible to
|
|
268
|
+
Claude on every message.
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
[your prompt]
|
|
272
|
+
+
|
|
273
|
+
[feynman rules] ← injected by hook, ~2KB
|
|
274
|
+
│
|
|
275
|
+
▼
|
|
276
|
+
[Claude]
|
|
277
|
+
│
|
|
278
|
+
▼
|
|
279
|
+
[structured response with ASCII diagrams]
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
State is stored at `~/.claude/.feynman/state.json`. First run bootstraps
|
|
283
|
+
automatically. See [docs/architecture.md](docs/architecture.md) for internals.
|
|
284
|
+
|
|
285
|
+
## Contributing
|
|
286
|
+
|
|
287
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, PR checklist, and
|
|
288
|
+
rules-authoring guidelines.
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bin/feynman-lint.js — feynman diagram linter CLI
|
|
3
|
+
// Usage: feynman-lint <file.md>
|
|
4
|
+
// feynman-lint - (stdin)
|
|
5
|
+
// feynman-lint --json <file>
|
|
6
|
+
// feynman-lint --strict <file>
|
|
7
|
+
// feynman-lint --help
|
|
8
|
+
// Exit codes: 0 = pass, 1 = lint failure, 2 = usage error
|
|
9
|
+
// Zero deps. CJS only.
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { lint, format } = require('../lib/lint');
|
|
15
|
+
|
|
16
|
+
const USAGE = `Usage: feynman-lint <file.md>
|
|
17
|
+
feynman-lint - (read from stdin)
|
|
18
|
+
feynman-lint --json <file>
|
|
19
|
+
feynman-lint --strict <file>
|
|
20
|
+
feynman-lint --help
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
--json Output issues as JSON object
|
|
24
|
+
--strict Treat warnings as errors (exit 1 on any issue)
|
|
25
|
+
--help Show this help message
|
|
26
|
+
|
|
27
|
+
Exit codes:
|
|
28
|
+
0 No errors (or no issues in default mode)
|
|
29
|
+
1 Lint failure (errors found, or warnings in --strict mode)
|
|
30
|
+
2 Usage error (bad arguments, file not found)
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
// Parse arguments
|
|
34
|
+
const argv = process.argv.slice(2);
|
|
35
|
+
let useJson = false;
|
|
36
|
+
let useStrict = false;
|
|
37
|
+
let filePath = null;
|
|
38
|
+
let useStdin = false;
|
|
39
|
+
|
|
40
|
+
for (const arg of argv) {
|
|
41
|
+
if (arg === '--json') { useJson = true; continue; }
|
|
42
|
+
if (arg === '--strict') { useStrict = true; continue; }
|
|
43
|
+
if (arg === '--help') { process.stdout.write(USAGE); process.exit(0); }
|
|
44
|
+
if (arg === '-') { useStdin = true; continue; }
|
|
45
|
+
if (arg.startsWith('-') && arg !== '-') {
|
|
46
|
+
process.stderr.write(`feynman-lint: unknown flag '${arg}'\n${USAGE}`);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
if (filePath) {
|
|
50
|
+
process.stderr.write(`feynman-lint: too many file arguments\n${USAGE}`);
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
filePath = arg;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!filePath && !useStdin) {
|
|
57
|
+
process.stderr.write(USAGE);
|
|
58
|
+
process.exit(2);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function run(markdown, displayName) {
|
|
62
|
+
const result = lint(markdown);
|
|
63
|
+
const { issues } = result;
|
|
64
|
+
|
|
65
|
+
if (useJson) {
|
|
66
|
+
const out = {
|
|
67
|
+
file: displayName,
|
|
68
|
+
passed: useStrict ? issues.length === 0 : result.passed,
|
|
69
|
+
issues,
|
|
70
|
+
};
|
|
71
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
72
|
+
const failed = useStrict ? issues.length > 0 : !result.passed;
|
|
73
|
+
process.exit(failed ? 1 : 0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// gcc mode
|
|
77
|
+
const isTTY = process.stdout.isTTY;
|
|
78
|
+
const output = format(issues, 'gcc', displayName, isTTY);
|
|
79
|
+
|
|
80
|
+
if (output) {
|
|
81
|
+
process.stdout.write(output + '\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Determine pass/fail
|
|
85
|
+
const failed = useStrict
|
|
86
|
+
? issues.length > 0
|
|
87
|
+
: !result.passed;
|
|
88
|
+
|
|
89
|
+
if (failed) {
|
|
90
|
+
const errCount = issues.filter(i => i.severity === 'error').length;
|
|
91
|
+
const warnCount = issues.filter(i => i.severity === 'warn').length;
|
|
92
|
+
const parts = [];
|
|
93
|
+
if (errCount) parts.push(`${errCount} error${errCount !== 1 ? 's' : ''}`);
|
|
94
|
+
if (warnCount) parts.push(`${warnCount} warning${warnCount !== 1 ? 's' : ''}`);
|
|
95
|
+
process.stderr.write(`${displayName}: ${parts.join(', ')}\n`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
} else {
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Read input
|
|
103
|
+
if (useStdin) {
|
|
104
|
+
let buf = '';
|
|
105
|
+
process.stdin.setEncoding('utf8');
|
|
106
|
+
process.stdin.on('data', chunk => { buf += chunk; });
|
|
107
|
+
process.stdin.on('end', () => run(buf, '<stdin>'));
|
|
108
|
+
} else {
|
|
109
|
+
// File mode
|
|
110
|
+
const absPath = path.resolve(filePath);
|
|
111
|
+
if (!fs.existsSync(absPath)) {
|
|
112
|
+
process.stderr.write(`feynman-lint: file not found: ${filePath}\n`);
|
|
113
|
+
process.exit(2);
|
|
114
|
+
}
|
|
115
|
+
let markdown;
|
|
116
|
+
try {
|
|
117
|
+
markdown = fs.readFileSync(absPath, 'utf8');
|
|
118
|
+
} catch (e) {
|
|
119
|
+
process.stderr.write(`feynman-lint: cannot read file: ${filePath}: ${e.message}\n`);
|
|
120
|
+
process.exit(2);
|
|
121
|
+
}
|
|
122
|
+
run(markdown, filePath);
|
|
123
|
+
}
|