claude-math 0.1.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/.claude-plugin/marketplace.json +13 -0
- package/.claude-plugin/plugin.json +9 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/bin/claude-math.js +318 -0
- package/package.json +52 -0
- package/skills/math-unicode/SKILL.md +211 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vladimirrott",
|
|
3
|
+
"owner": { "name": "Vladimir Rotariu" },
|
|
4
|
+
"metadata": { "description": "Vladimir's Claude Code plugins" },
|
|
5
|
+
"plugins": [
|
|
6
|
+
{
|
|
7
|
+
"name": "claude-math",
|
|
8
|
+
"source": "./",
|
|
9
|
+
"description": "Renders math as Unicode glyphs in the terminal instead of unrenderable LaTeX",
|
|
10
|
+
"version": "0.1.0"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-math",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Render math legibly in Claude Code. Emits Unicode glyphs instead of unrenderable LaTeX in the terminal (works in every terminal). Graphical sixel/kitty rendering is on the roadmap.",
|
|
5
|
+
"author": { "name": "Vladimir Rotariu", "email": "vladimirrott@users.noreply.github.com" },
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/vladimirrott/claude-math",
|
|
8
|
+
"keywords": ["math", "latex", "katex", "unicode", "rendering", "skill"]
|
|
9
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `claude-math` documented here. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [0.1.0] — 2026-05-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `math-unicode` skill — auto-triggers on math; instructs Claude to emit math as Unicode glyphs (Greek, operators, relations, logic, sub/super) instead of raw LaTeX dollar-sign syntax.
|
|
9
|
+
- LaTeX → Unicode cheatsheet, style rules, common-forms reference, anti-patterns.
|
|
10
|
+
- `claude-math` CLI (`bin/claude-math.js`) — `install` / `uninstall` / `status` / `prepack` commands. Symlinks plugin into `~/.claude/plugins/local/` and patches `installed_plugins.json` + `settings.json`.
|
|
11
|
+
- **Install safety**: atomic JSON writes (`.tmp` + rename); `.claude-math.bak` backup of `settings.json` and `installed_plugins.json` on first touch; refuses to overwrite a foreign symlink or non-plugin directory without `--force`.
|
|
12
|
+
- **Install modes**: auto-copy when invoked from an npx cache path (ephemeral location); junction on Windows; symlink elsewhere. `--copy` flag forces copy.
|
|
13
|
+
- **Tests**: `node --test` suite covering install/uninstall round-trip, idempotency, atomicity, backups, copy mode, foreign-symlink refusal, and prepack version sync.
|
|
14
|
+
- **CI**: `.github/workflows/ci.yml` (matrix: ubuntu/macos × node 18/20/22) and `.github/workflows/publish.yml` (tag-triggered `npm publish --provenance`).
|
|
15
|
+
- `prepack` script auto-syncs `.claude-plugin/plugin.json` version from `package.json` so the two never drift.
|
|
16
|
+
- Roadmap notes for opt-in graphical rendering (sixel / kitty graphics via a `claude-math-render` MCP server).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vladimir Rotariu
|
|
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,140 @@
|
|
|
1
|
+
# claude-math
|
|
2
|
+
|
|
3
|
+
Make math in Claude Code legible.
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="assets/demo.svg" alt="Without claude-math, LaTeX prints as raw dollar-sign noise; with it, the same answer renders as clean Unicode math." width="760">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
Claude Code's terminal does not render LaTeX. Without help, a formula like
|
|
10
|
+
`$f(x) = \sum_{i=1}^n x_i$` appears as raw dollar signs and backslashes,
|
|
11
|
+
exactly the noise you wanted formatting to remove. This plugin ships a single
|
|
12
|
+
skill (`math-unicode`) that instructs Claude to emit math as Unicode glyphs
|
|
13
|
+
inline, which every terminal already renders.
|
|
14
|
+
|
|
15
|
+
## Before / after
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Before: The qualifying cohort is $Q = \{ (s,r) \in T : n_{s,r} \geq 18 \wedge p^0_{s,r} < 0.9 \}$,
|
|
19
|
+
with $|Q| / |T| \approx 17.3\%$.
|
|
20
|
+
|
|
21
|
+
After: The qualifying cohort is Q = { (s,r) ∈ T : n_{s,r} ≥ 18 ∧ p⁰_{s,r} < 0.9 },
|
|
22
|
+
with |Q| / |T| ≈ 17.3 %.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
The quickest install is through the plugin marketplace (see below). The npm and manual paths also work.
|
|
28
|
+
|
|
29
|
+
### via npm
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g claude-math
|
|
33
|
+
claude-math install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Symlinks the package into `~/.claude/plugins/local/claude-math`, registers it
|
|
37
|
+
in `installed_plugins.json`, and enables it in `settings.json` (atomically;
|
|
38
|
+
both files get a `.claude-math.bak` backup on first touch). Restart Claude
|
|
39
|
+
Code and the skill loads.
|
|
40
|
+
|
|
41
|
+
`npx claude-math install` also works: the CLI auto-detects an npx-cache
|
|
42
|
+
install path and **copies** rather than symlinks (since the cache directory
|
|
43
|
+
is ephemeral). Prefer the global install if you want updates via
|
|
44
|
+
`npm update -g claude-math` to propagate automatically.
|
|
45
|
+
|
|
46
|
+
Other commands:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
claude-math status # report target, validity, settings state, next-install mode
|
|
50
|
+
claude-math uninstall # remove symlink/dir, deregister, disable
|
|
51
|
+
claude-math --help # full flag list, env overrides
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Flags: `--force` overrides safety checks (foreign symlinks, non-plugin
|
|
55
|
+
directories at the target). `--copy` forces a real copy instead of symlink.
|
|
56
|
+
|
|
57
|
+
### Manual install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/vladimirrott/claude-math \
|
|
61
|
+
~/.claude/plugins/local/claude-math
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then add `"claude-math@local": true` under `enabledPlugins` in
|
|
65
|
+
`~/.claude/settings.json` and a matching entry in
|
|
66
|
+
`~/.claude/plugins/installed_plugins.json`. The CLI does both; running it
|
|
67
|
+
once is the easiest path.
|
|
68
|
+
|
|
69
|
+
### Via Claude Code's plugin marketplace (recommended)
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
/plugin marketplace add vladimirrott/claude-math
|
|
73
|
+
/plugin install claude-math@vladimirrott
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Codex CLI
|
|
77
|
+
|
|
78
|
+
The `math-unicode` skill works in [OpenAI Codex CLI](https://developers.openai.com/codex/) too: Codex reads skills from `$CODEX_HOME/skills/<name>/SKILL.md` using the same `name` + `description` frontmatter format, so the exact same skill drops straight in.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install -g claude-math
|
|
82
|
+
claude-math install --codex
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This copies the skill into `$CODEX_HOME/skills/math-unicode/` (default `~/.codex/skills/`). Codex auto-detects new skills (restart Codex if it does not appear); invoke it with `/skills` or by mentioning `$math-unicode`. Use `claude-math status --codex` to check and `claude-math uninstall --codex` to remove.
|
|
86
|
+
|
|
87
|
+
### Hacking on this repo
|
|
88
|
+
|
|
89
|
+
Working from a clone (before or after publish):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/vladimirrott/claude-math && cd claude-math
|
|
93
|
+
node bin/claude-math.js install # uses the cloned directory directly
|
|
94
|
+
node --test test/ # run the test suite
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`CLAUDE_CONFIG_DIR=/tmp/somewhere claude-math install` lets you test against
|
|
98
|
+
a sandbox without touching your real `~/.claude` state, useful for
|
|
99
|
+
contributing.
|
|
100
|
+
|
|
101
|
+
The `math-unicode` skill auto-triggers any time Claude writes or explains math.
|
|
102
|
+
No configuration required.
|
|
103
|
+
|
|
104
|
+
## Graphical rendering (sixel / kitty): not inside the chat
|
|
105
|
+
|
|
106
|
+
Rendering math as an actual image (sixel or kitty graphics) is **not possible
|
|
107
|
+
inside the Claude Code chat**. The TUI repaints its own screen buffer on every
|
|
108
|
+
update and overwrites any graphics escape sequences a plugin emits, and its
|
|
109
|
+
line accounting does not know an image's height. So in-chat output stays
|
|
110
|
+
Unicode, which is the point of this skill.
|
|
111
|
+
|
|
112
|
+
Roadmap (not built yet): a standalone `claude-math render "<latex>"` command
|
|
113
|
+
that converts LaTeX to an image and prints it via the terminal's graphics
|
|
114
|
+
protocol in your own graphics-capable terminal (kitty, wezterm, ghostty,
|
|
115
|
+
foot), outside the Claude Code TUI. It would be a convenience for viewing an
|
|
116
|
+
equation on demand, not in-chat rendering.
|
|
117
|
+
|
|
118
|
+
## Why Unicode by default?
|
|
119
|
+
|
|
120
|
+
| Path | Works in plain terminal | SSH / tmux | CI logs | Copy-paste | Install cost |
|
|
121
|
+
|---|---|---|---|---|---|
|
|
122
|
+
| Unicode (this skill) | ✓ | ✓ | ✓ | ✓ | 1 file |
|
|
123
|
+
| Sixel / kitty graphics | ✗ (not in chat) | partial | ✗ | ✗ images | separate render CLI (roadmap) |
|
|
124
|
+
| Pipe through external viewer | ✗ TUI breaks | n/a | ✓ if `--print` | ✓ | shell wrapper |
|
|
125
|
+
|
|
126
|
+
Unicode is the only path that survives every distribution channel a Claude
|
|
127
|
+
Code session ends up in. The graphical path is roadmap-only and, even then,
|
|
128
|
+
would run as a separate terminal command outside the chat.
|
|
129
|
+
|
|
130
|
+
## Related issues & prior art
|
|
131
|
+
|
|
132
|
+
- [anthropics/claude-code#44479](https://github.com/anthropics/claude-code/issues/44479): native LaTeX in terminal output (open)
|
|
133
|
+
- [openai/codex#15865](https://github.com/openai/codex/issues/15865): LaTeX in Codex CLI output (claude-math covers this via `claude-math install --codex`)
|
|
134
|
+
- [warpdotdev/warp#9677](https://github.com/warpdotdev/warp/issues/9677): same gap on Warp
|
|
135
|
+
- [`markless`](https://github.com/jvanderberg/markless): terminal Markdown viewer with Typst math and kitty/sixel images
|
|
136
|
+
- [`mdviewer`](https://github.com/aquele-dinho/mdviewer), [`glowm`](https://github.com/atani/glowm), [`mdterm`](https://www.toolhunter.cc/tools/mdterm): adjacent viewers
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// claude-math — install/uninstall the plugin into Claude Code's local plugin dir.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
copyFileSync,
|
|
6
|
+
cpSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
lstatSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
readlinkSync,
|
|
12
|
+
renameSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
symlinkSync,
|
|
15
|
+
unlinkSync,
|
|
16
|
+
writeFileSync,
|
|
17
|
+
} from "node:fs";
|
|
18
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
const PLUGIN_ROOT = resolve(__dirname, "..");
|
|
25
|
+
const PLUGIN_NAME = "claude-math";
|
|
26
|
+
const PLUGIN_ID = `${PLUGIN_NAME}@local`;
|
|
27
|
+
|
|
28
|
+
const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
|
|
29
|
+
const PLUGINS_DIR = join(CLAUDE_DIR, "plugins");
|
|
30
|
+
const LOCAL_DIR = join(PLUGINS_DIR, "local");
|
|
31
|
+
const TARGET = join(LOCAL_DIR, PLUGIN_NAME);
|
|
32
|
+
const SETTINGS = join(CLAUDE_DIR, "settings.json");
|
|
33
|
+
const INSTALLED = join(PLUGINS_DIR, "installed_plugins.json");
|
|
34
|
+
|
|
35
|
+
// Codex target: Codex reads skills from $CODEX_HOME/skills/<name>/SKILL.md.
|
|
36
|
+
// The SKILL.md format (name + description frontmatter) is identical to Claude Code's,
|
|
37
|
+
// so the same skill directory drops straight in.
|
|
38
|
+
const SKILL_NAME = "math-unicode";
|
|
39
|
+
const SKILL_SRC = join(PLUGIN_ROOT, "skills", SKILL_NAME);
|
|
40
|
+
const CODEX_HOME = process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
41
|
+
const CODEX_SKILLS_DIR = join(CODEX_HOME, "skills");
|
|
42
|
+
const CODEX_TARGET = join(CODEX_SKILLS_DIR, SKILL_NAME);
|
|
43
|
+
|
|
44
|
+
const pkg = JSON.parse(readFileSync(join(PLUGIN_ROOT, "package.json"), "utf8"));
|
|
45
|
+
|
|
46
|
+
const args = process.argv.slice(2);
|
|
47
|
+
const flags = new Set(args.filter((a) => a.startsWith("-")));
|
|
48
|
+
const cmd = args.find((a) => !a.startsWith("-"));
|
|
49
|
+
const FORCE = flags.has("--force") || flags.has("-f");
|
|
50
|
+
const FORCE_COPY = flags.has("--copy");
|
|
51
|
+
const CODEX = flags.has("--codex");
|
|
52
|
+
|
|
53
|
+
function log(msg) { console.log(`[claude-math] ${msg}`); }
|
|
54
|
+
function die(msg) { console.error(`[claude-math] ${msg}`); process.exit(1); }
|
|
55
|
+
|
|
56
|
+
function lstatSafe(p) { try { return lstatSync(p); } catch { return null; } }
|
|
57
|
+
|
|
58
|
+
function readJson(path, fallback) {
|
|
59
|
+
if (!existsSync(path)) return fallback;
|
|
60
|
+
try { return JSON.parse(readFileSync(path, "utf8")); }
|
|
61
|
+
catch (e) { die(`could not parse ${path}: ${e.message}`); }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeJsonAtomic(path, obj) {
|
|
65
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
66
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
67
|
+
writeFileSync(tmp, JSON.stringify(obj, null, 2) + "\n");
|
|
68
|
+
renameSync(tmp, path);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function backupOnce(path) {
|
|
72
|
+
const bak = `${path}.claude-math.bak`;
|
|
73
|
+
if (existsSync(path) && !existsSync(bak)) {
|
|
74
|
+
copyFileSync(path, bak);
|
|
75
|
+
log(`backed up ${path} → ${bak}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isEphemeralPath(p) {
|
|
80
|
+
return /[\\/](?:_npx|_npm-cache)[\\/]/.test(p);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function looksLikeOurPlugin(path) {
|
|
84
|
+
return existsSync(join(path, ".claude-plugin", "plugin.json"))
|
|
85
|
+
&& existsSync(join(path, "skills", "math-unicode", "SKILL.md"));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function chooseMode() {
|
|
89
|
+
if (FORCE_COPY) return "copy";
|
|
90
|
+
if (isEphemeralPath(PLUGIN_ROOT)) return "copy";
|
|
91
|
+
if (process.platform === "win32") return "junction";
|
|
92
|
+
return "symlink";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function clearTarget() {
|
|
96
|
+
const stat = lstatSafe(TARGET);
|
|
97
|
+
if (!stat) return;
|
|
98
|
+
|
|
99
|
+
if (stat.isSymbolicLink()) {
|
|
100
|
+
let current = "";
|
|
101
|
+
try { current = readlinkSync(TARGET); } catch {}
|
|
102
|
+
if (current === PLUGIN_ROOT || FORCE) {
|
|
103
|
+
unlinkSync(TARGET);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
die(`${TARGET} is a symlink to ${current}, not ${PLUGIN_ROOT}. Re-run with --force to overwrite.`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (stat.isDirectory()) {
|
|
110
|
+
if (!looksLikeOurPlugin(TARGET) && !FORCE) {
|
|
111
|
+
die(`${TARGET} is a directory but does not look like a claude-math install. Inspect manually, then re-run with --force.`);
|
|
112
|
+
}
|
|
113
|
+
rmSync(TARGET, { recursive: true, force: true });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
die(`${TARGET} exists and is neither symlink nor directory. Refusing to touch.`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function placePlugin(mode) {
|
|
121
|
+
mkdirSync(LOCAL_DIR, { recursive: true });
|
|
122
|
+
if (mode === "copy") {
|
|
123
|
+
cpSync(PLUGIN_ROOT, TARGET, {
|
|
124
|
+
recursive: true,
|
|
125
|
+
filter: (src) => {
|
|
126
|
+
const rel = relative(PLUGIN_ROOT, src);
|
|
127
|
+
if (!rel) return true;
|
|
128
|
+
const top = rel.split(/[\\/]/, 1)[0];
|
|
129
|
+
return !["node_modules", ".git", "test", "tests"].includes(top);
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
log(`copied ${PLUGIN_ROOT} → ${TARGET}`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const linkType = mode === "junction" ? "junction" : "dir";
|
|
136
|
+
symlinkSync(PLUGIN_ROOT, TARGET, linkType);
|
|
137
|
+
log(`linked ${PLUGIN_ROOT} → ${TARGET} (${mode})`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function registerInstalled() {
|
|
141
|
+
backupOnce(INSTALLED);
|
|
142
|
+
const data = readJson(INSTALLED, { version: 2, plugins: {} });
|
|
143
|
+
if (!data.plugins) data.plugins = {};
|
|
144
|
+
const now = new Date().toISOString();
|
|
145
|
+
const prev = data.plugins[PLUGIN_ID]?.[0];
|
|
146
|
+
data.plugins[PLUGIN_ID] = [{
|
|
147
|
+
scope: "user",
|
|
148
|
+
installPath: TARGET,
|
|
149
|
+
version: pkg.version,
|
|
150
|
+
installedAt: prev?.installedAt ?? now,
|
|
151
|
+
lastUpdated: now,
|
|
152
|
+
}];
|
|
153
|
+
writeJsonAtomic(INSTALLED, data);
|
|
154
|
+
log(`registered ${PLUGIN_ID}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function enableInSettings() {
|
|
158
|
+
backupOnce(SETTINGS);
|
|
159
|
+
const data = readJson(SETTINGS, {});
|
|
160
|
+
if (!data.enabledPlugins) data.enabledPlugins = {};
|
|
161
|
+
data.enabledPlugins[PLUGIN_ID] = true;
|
|
162
|
+
writeJsonAtomic(SETTINGS, data);
|
|
163
|
+
log(`enabled ${PLUGIN_ID}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function removeTarget() {
|
|
167
|
+
const stat = lstatSafe(TARGET);
|
|
168
|
+
if (!stat) return;
|
|
169
|
+
if (stat.isSymbolicLink()) {
|
|
170
|
+
unlinkSync(TARGET);
|
|
171
|
+
log(`removed symlink ${TARGET}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (stat.isDirectory() && (looksLikeOurPlugin(TARGET) || FORCE)) {
|
|
175
|
+
rmSync(TARGET, { recursive: true, force: true });
|
|
176
|
+
log(`removed directory ${TARGET}`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
log(`${TARGET} exists but is not our plugin — leaving alone (use --force to remove anyway)`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function unregister() {
|
|
183
|
+
const installed = readJson(INSTALLED, null);
|
|
184
|
+
if (installed?.plugins?.[PLUGIN_ID]) {
|
|
185
|
+
delete installed.plugins[PLUGIN_ID];
|
|
186
|
+
writeJsonAtomic(INSTALLED, installed);
|
|
187
|
+
log(`unregistered ${PLUGIN_ID}`);
|
|
188
|
+
}
|
|
189
|
+
const settings = readJson(SETTINGS, null);
|
|
190
|
+
if (settings?.enabledPlugins?.[PLUGIN_ID] !== undefined) {
|
|
191
|
+
delete settings.enabledPlugins[PLUGIN_ID];
|
|
192
|
+
writeJsonAtomic(SETTINGS, settings);
|
|
193
|
+
log(`disabled ${PLUGIN_ID}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function installCodex() {
|
|
198
|
+
mkdirSync(CODEX_SKILLS_DIR, { recursive: true });
|
|
199
|
+
const stat = lstatSafe(CODEX_TARGET);
|
|
200
|
+
if (stat) {
|
|
201
|
+
const ours = existsSync(join(CODEX_TARGET, "SKILL.md"));
|
|
202
|
+
if (!ours && !FORCE) {
|
|
203
|
+
die(`${CODEX_TARGET} exists and is not a math-unicode skill. Inspect it, then re-run with --force.`);
|
|
204
|
+
}
|
|
205
|
+
rmSync(CODEX_TARGET, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
cpSync(SKILL_SRC, CODEX_TARGET, { recursive: true });
|
|
208
|
+
log(`installed skill → ${CODEX_TARGET}`);
|
|
209
|
+
log("Codex auto-detects new skills; restart Codex if it doesn't appear. Invoke with /skills or $math-unicode.");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function uninstallCodex() {
|
|
213
|
+
const stat = lstatSafe(CODEX_TARGET);
|
|
214
|
+
if (!stat) { log(`nothing to remove at ${CODEX_TARGET}`); return; }
|
|
215
|
+
if (existsSync(join(CODEX_TARGET, "SKILL.md")) || FORCE) {
|
|
216
|
+
rmSync(CODEX_TARGET, { recursive: true, force: true });
|
|
217
|
+
log(`removed skill ${CODEX_TARGET}`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
log(`${CODEX_TARGET} exists but is not our skill — leaving alone (use --force to remove anyway)`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function statusCodex() {
|
|
224
|
+
const present = existsSync(join(CODEX_TARGET, "SKILL.md"));
|
|
225
|
+
console.log(`codex skill: ${present ? "✓" : "✗"} ${CODEX_TARGET}`);
|
|
226
|
+
console.log(`codex home: ${CODEX_HOME}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function install() {
|
|
230
|
+
if (CODEX) return installCodex();
|
|
231
|
+
const mode = chooseMode();
|
|
232
|
+
clearTarget();
|
|
233
|
+
placePlugin(mode);
|
|
234
|
+
registerInstalled();
|
|
235
|
+
enableInSettings();
|
|
236
|
+
log("done. Restart Claude Code to load the skill.");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function uninstall() {
|
|
240
|
+
if (CODEX) return uninstallCodex();
|
|
241
|
+
removeTarget();
|
|
242
|
+
unregister();
|
|
243
|
+
log("done. Restart Claude Code to drop the skill.");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function status() {
|
|
247
|
+
if (CODEX) return statusCodex();
|
|
248
|
+
const stat = lstatSafe(TARGET);
|
|
249
|
+
const kind = !stat ? "missing"
|
|
250
|
+
: stat.isSymbolicLink() ? "symlink"
|
|
251
|
+
: stat.isDirectory() ? "directory"
|
|
252
|
+
: "other";
|
|
253
|
+
const installed = !!readJson(INSTALLED, { plugins: {} }).plugins?.[PLUGIN_ID];
|
|
254
|
+
const enabled = readJson(SETTINGS, {}).enabledPlugins?.[PLUGIN_ID] === true;
|
|
255
|
+
const valid = stat ? looksLikeOurPlugin(TARGET) : false;
|
|
256
|
+
console.log(`target: ${kind === "missing" ? "✗" : "✓"} ${TARGET} (${kind})`);
|
|
257
|
+
console.log(`valid: ${valid ? "✓" : "✗"} plugin files present`);
|
|
258
|
+
console.log(`installed: ${installed ? "✓" : "✗"} ${INSTALLED}`);
|
|
259
|
+
console.log(`enabled: ${enabled ? "✓" : "✗"} ${SETTINGS}`);
|
|
260
|
+
console.log(`mode: ${chooseMode()} (would-be on next install)`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function prepack() {
|
|
264
|
+
const pluginJsonPath = join(PLUGIN_ROOT, ".claude-plugin", "plugin.json");
|
|
265
|
+
const pj = JSON.parse(readFileSync(pluginJsonPath, "utf8"));
|
|
266
|
+
if (pj.version !== pkg.version) {
|
|
267
|
+
pj.version = pkg.version;
|
|
268
|
+
writeFileSync(pluginJsonPath, JSON.stringify(pj, null, 2) + "\n");
|
|
269
|
+
log(`synced plugin.json version → ${pkg.version}`);
|
|
270
|
+
} else {
|
|
271
|
+
log(`plugin.json already at ${pkg.version}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function help() {
|
|
276
|
+
console.log(`claude-math v${pkg.version}
|
|
277
|
+
|
|
278
|
+
Usage:
|
|
279
|
+
claude-math install [--force] [--copy] [--codex]
|
|
280
|
+
Symlink (or copy) the plugin into ~/.claude/plugins/local/ and enable it.
|
|
281
|
+
Auto-copies if invoked via npx; auto-junctions on Windows.
|
|
282
|
+
--force Overwrite an existing install at the target path.
|
|
283
|
+
--copy Force copy mode even on supported platforms.
|
|
284
|
+
--codex Install the math-unicode skill for Codex CLI instead, into
|
|
285
|
+
$CODEX_HOME/skills/math-unicode/ (default ~/.codex/skills/).
|
|
286
|
+
|
|
287
|
+
claude-math uninstall [--force] [--codex]
|
|
288
|
+
Remove the plugin and disable it. --force lets it remove a directory
|
|
289
|
+
that does not look like a claude-math install. --codex removes the
|
|
290
|
+
Codex skill instead.
|
|
291
|
+
|
|
292
|
+
claude-math status [--codex]
|
|
293
|
+
Show install state, target kind, and which mode would be used.
|
|
294
|
+
--codex shows the Codex skill install state instead.
|
|
295
|
+
|
|
296
|
+
claude-math prepack
|
|
297
|
+
Sync .claude-plugin/plugin.json version from package.json. Runs as an
|
|
298
|
+
npm \`prepack\` hook; safe to run manually.
|
|
299
|
+
|
|
300
|
+
claude-math --version
|
|
301
|
+
claude-math --help
|
|
302
|
+
|
|
303
|
+
Env:
|
|
304
|
+
CLAUDE_CONFIG_DIR Override the Claude config directory (default ~/.claude).
|
|
305
|
+
`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
switch (cmd) {
|
|
309
|
+
case "install": install(); break;
|
|
310
|
+
case "uninstall":
|
|
311
|
+
case "remove": uninstall(); break;
|
|
312
|
+
case "status": status(); break;
|
|
313
|
+
case "prepack": prepack(); break;
|
|
314
|
+
case undefined:
|
|
315
|
+
if (flags.has("--version") || flags.has("-v")) { console.log(pkg.version); break; }
|
|
316
|
+
help(); break;
|
|
317
|
+
default: die(`unknown command: ${cmd}. Run 'claude-math --help'.`);
|
|
318
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-math",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Render math legibly in Claude Code. Emits Unicode glyphs instead of unrenderable LaTeX in the terminal. (Graphical sixel/kitty rendering is on the roadmap.)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"claude-code-plugin",
|
|
9
|
+
"skill",
|
|
10
|
+
"math",
|
|
11
|
+
"latex",
|
|
12
|
+
"katex",
|
|
13
|
+
"unicode",
|
|
14
|
+
"terminal"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/vladimirrott/claude-math",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/vladimirrott/claude-math/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/vladimirrott/claude-math.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": {
|
|
26
|
+
"name": "Vladimir Rotariu",
|
|
27
|
+
"email": "vladimirrott@users.noreply.github.com"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"bin": {
|
|
31
|
+
"claude-math": "bin/claude-math.js"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
".claude-plugin/",
|
|
35
|
+
"skills/",
|
|
36
|
+
"bin/",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE",
|
|
39
|
+
"CHANGELOG.md"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"test": "node --test test/*.test.js",
|
|
46
|
+
"prepack": "node bin/claude-math.js prepack",
|
|
47
|
+
"prepublishOnly": "npm test && npm pack --dry-run"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: math-unicode
|
|
3
|
+
description: Use whenever you need to express mathematical notation — equations, filters, set-builder, statistics, calculus, logic, ratios, drops, counts. Emit Unicode math glyphs INLINE; never wrap in `$…$`, `\(...\)`, or `$$...$$`. Claude Code terminal does not render LaTeX, so raw delimiters appear as noise. This skill activates automatically when math is involved.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# math-unicode
|
|
7
|
+
|
|
8
|
+
When emitting mathematical notation in Claude Code, **always use Unicode glyphs inline** — never wrap math in `$…$`, `\(...\)`, or `$$...$$`. Claude Code's terminal does not render LaTeX; raw delimiters appear as plain dollar signs and reduce readability.
|
|
9
|
+
|
|
10
|
+
## When this skill applies
|
|
11
|
+
|
|
12
|
+
Triggers (use Unicode math):
|
|
13
|
+
- Equations, formulas, derivations
|
|
14
|
+
- Filter conditions, set-builder notation
|
|
15
|
+
- Statistics: probabilities, expectations, distributions
|
|
16
|
+
- Calculus, linear algebra, logic
|
|
17
|
+
- Counts, ratios, fractions, drops where precision matters
|
|
18
|
+
|
|
19
|
+
Skip (do not transform):
|
|
20
|
+
- The user explicitly asks for LaTeX or a `.tex` file
|
|
21
|
+
- Math inside fenced code blocks (preserve source syntax)
|
|
22
|
+
- Strings being passed to a system that consumes LaTeX (KaTeX MCP, etc.)
|
|
23
|
+
|
|
24
|
+
## Glyph cheatsheet
|
|
25
|
+
|
|
26
|
+
### Greek
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
lowercase α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω
|
|
30
|
+
uppercase Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω
|
|
31
|
+
variants ϵ ϑ ϕ ϖ ϱ ς
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Operators
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
arithmetic + − × ÷ ± ∓ · ∗ ⋅ ∘ ⊕ ⊖ ⊗ ⊘ ⊙
|
|
38
|
+
big ∑ ∏ ∐ ∫ ∮ ∬ ∭ ⨁ ⨂ ⨅ ⨆
|
|
39
|
+
roots √ ∛ ∜
|
|
40
|
+
calculus ∂ ∇ Δ ∆ ⅆ ⅇ
|
|
41
|
+
constants ∞ ∅ ℵ ℶ
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Relations
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
equality = ≠ ≈ ≅ ≡ ≜ ≝ ≐ ∝ ∼ ≃ ≢
|
|
48
|
+
order < > ≤ ≥ ≪ ≫ ⋘ ⋙ ⊴ ⊵
|
|
49
|
+
set ∈ ∉ ∋ ∌ ⊂ ⊃ ⊆ ⊇ ⊊ ⊋ ⊏ ⊐ ⊑ ⊒
|
|
50
|
+
set ops ∪ ∩ ⊎ ⊔ ⊓ ∖
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Logic & arrows
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
logic ∧ ∨ ¬ ⊕ ⊻ ⊼ ⊽ ⊢ ⊨ ⊥ ⊤
|
|
57
|
+
quantifiers ∀ ∃ ∄ ∴ ∵
|
|
58
|
+
arrows → ← ↔ ⇒ ⇐ ⇔ ↦ ↪ ↩ ↑ ↓ ⇑ ⇓ ⟶ ⟵ ⟷ ⟹ ⟸ ⟺ ⊸
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Number sets & brackets
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
sets ℕ ℤ ℚ ℝ ℂ ℙ ℍ 𝔽
|
|
65
|
+
brackets ⟨ ⟩ ⌈ ⌉ ⌊ ⌋ ‖ ‖ 〈 〉
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Sub/superscript glyph blocks
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
superscript ⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ⁱ ⁿ ᵃ ᵇ ᶜ ᵈ ᵉ ᶠ ᵍ ʰ ʲ ᵏ ˡ ᵐ ᵒ ᵖ ʳ ˢ ᵗ ᵘ ᵛ ʷ ˣ ʸ ᶻ
|
|
72
|
+
subscript ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₌ ₍ ₎ ₐ ₑ ₕ ᵢ ⱼ ₖ ₗ ₘ ₙ ₒ ₚ ᵣ ₛ ₜ ᵤ ᵥ ₓ
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Common LaTeX → Unicode
|
|
76
|
+
|
|
77
|
+
| LaTeX | Unicode | LaTeX | Unicode | LaTeX | Unicode |
|
|
78
|
+
|---|---|---|---|---|---|
|
|
79
|
+
| `\alpha` | α | `\sum` | ∑ | `\in` | ∈ |
|
|
80
|
+
| `\beta` | β | `\prod` | ∏ | `\notin` | ∉ |
|
|
81
|
+
| `\gamma` | γ | `\int` | ∫ | `\subset` | ⊂ |
|
|
82
|
+
| `\delta` | δ | `\partial` | ∂ | `\subseteq` | ⊆ |
|
|
83
|
+
| `\epsilon` | ε | `\nabla` | ∇ | `\cup` | ∪ |
|
|
84
|
+
| `\theta` | θ | `\infty` | ∞ | `\cap` | ∩ |
|
|
85
|
+
| `\lambda` | λ | `\emptyset` | ∅ | `\setminus` | ∖ |
|
|
86
|
+
| `\mu` | μ | `\leq` | ≤ | `\wedge` | ∧ |
|
|
87
|
+
| `\pi` | π | `\geq` | ≥ | `\vee` | ∨ |
|
|
88
|
+
| `\sigma` | σ | `\neq` | ≠ | `\neg` | ¬ |
|
|
89
|
+
| `\phi` | φ | `\approx` | ≈ | `\Rightarrow` | ⇒ |
|
|
90
|
+
| `\omega` | ω | `\equiv` | ≡ | `\Leftrightarrow` | ⇔ |
|
|
91
|
+
| `\sqrt` | √ | `\propto` | ∝ | `\forall` | ∀ |
|
|
92
|
+
| `\pm` | ± | `\cdot` | · | `\exists` | ∃ |
|
|
93
|
+
| `\times` | × | `\to` | → | `\mathbb{R}` | ℝ |
|
|
94
|
+
|
|
95
|
+
## Style rules
|
|
96
|
+
|
|
97
|
+
### Rule 1 — Inline math: Unicode, no delimiters
|
|
98
|
+
|
|
99
|
+
Bad: `The filter $f(T; m) = \{(s,r) : n_{s,r} \geq m\}$ produces the cohort.`
|
|
100
|
+
Good: `The filter f(T; m) = { (s,r) : n_{s,r} ≥ m } produces the cohort.`
|
|
101
|
+
|
|
102
|
+
### Rule 2 — Block math: own line(s), still no delimiters
|
|
103
|
+
|
|
104
|
+
Bad:
|
|
105
|
+
|
|
106
|
+
$$|Q| / |T| = 5238 / 31075 \approx 16.9\%$$
|
|
107
|
+
|
|
108
|
+
Good:
|
|
109
|
+
|
|
110
|
+
|Q| / |T| = 5 238 / 31 075 ≈ 16.9 %
|
|
111
|
+
|
|
112
|
+
### Rule 3 — Subscripts
|
|
113
|
+
|
|
114
|
+
- Single Unicode-renderable index: prefer the glyph (x₁, x₂, xᵢ, xⱼ)
|
|
115
|
+
- Multi-character or non-mappable subscript: keep `_{...}` syntax for clarity
|
|
116
|
+
- `n_{s,r}` ← (s,r) has no Unicode subscript form
|
|
117
|
+
- `x_max`, `σ_obs` ← multi-letter
|
|
118
|
+
- Never mix: don't write `x_₁` or `x_{1}` when `x₁` works.
|
|
119
|
+
|
|
120
|
+
### Rule 4 — Superscripts (powers)
|
|
121
|
+
|
|
122
|
+
- Integer powers, simple: x², x³, xⁿ, e^x → eˣ
|
|
123
|
+
- Multi-character / non-mappable: `x^{T}`, `x^{(i)}`, `A^{-1}` (or A⁻¹ if the exponent is simple)
|
|
124
|
+
|
|
125
|
+
### Rule 5 — Big operators with indices
|
|
126
|
+
|
|
127
|
+
Use Unicode operator + ASCII bounds:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
∑_{i=1}^{n} aᵢ
|
|
131
|
+
∏_{k ∈ K} pₖ
|
|
132
|
+
∫_a^b f(x) dx
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Rule 6 — Fractions
|
|
136
|
+
|
|
137
|
+
- Inline: `a/b`, `(a + b) / (c + d)`
|
|
138
|
+
- Block (only when it aids clarity):
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
a + b
|
|
142
|
+
─────────────
|
|
143
|
+
c² + d²
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Rule 7 — Matrices / vectors
|
|
147
|
+
|
|
148
|
+
ASCII art with corner glyphs:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
A = ⎡ a b ⎤ v = ( v₁ , v₂ , v₃ )ᵀ
|
|
152
|
+
⎣ c d ⎦
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Rule 8 — Sets and conditions
|
|
156
|
+
|
|
157
|
+
Prefer set-builder with `|` or `:`:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
Q = { (s,r) ∈ T : n_{s,r} ≥ 18 ∧ p⁰_{s,r} < 0.9 }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Rule 9 — Numbers
|
|
164
|
+
|
|
165
|
+
- Thousands: thin space (` `) — `5 238`, `34 601` — not commas (locale ambiguous).
|
|
166
|
+
- Decimal: dot — `16.9 %`.
|
|
167
|
+
- Percent: space before `%` — `16.9 %` (typographic convention; readable).
|
|
168
|
+
- Approximations: ≈, ∼. Order of magnitude: ~. Confidence: `x = 5.2 ± 0.3`.
|
|
169
|
+
|
|
170
|
+
### Rule 10 — When Unicode hurts, fall back explicitly
|
|
171
|
+
|
|
172
|
+
If a glyph chain becomes denser than the LaTeX it replaces, switch to readable ASCII pseudo-LaTeX and annotate it. Example:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
H(p) = − Σ_{x ∈ X} p(x) · log p(x) (Σ = sum over the support X)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The reader's comprehension is the only metric. Choose whichever form is clearest, then stay consistent within a passage.
|
|
179
|
+
|
|
180
|
+
### Rule 11 — Plain letters for variables; never style with math-alphanumeric codepoints
|
|
181
|
+
|
|
182
|
+
Write variable names and identifiers with ordinary letters (x, A, Var, RSS). Do **not** reach into the Unicode *Mathematical Alphanumeric Symbols* block (𝐀 bold, 𝐴 italic, 𝓐 script, 𝔸 styled double-struck) to *style* ordinary letters. Those codepoints garble on copy/paste, terminal search, and screen readers — the same failure Claude Code hit in issue #61558.
|
|
183
|
+
|
|
184
|
+
Exception: the standard, semantically meaningful blackboard-bold sets and operators are correct notation, not styling — keep using ℕ ℤ ℚ ℝ ℂ ℙ 𝔽 (number sets) and 𝔼 (expectation). Use those; don't hand-style anything else.
|
|
185
|
+
|
|
186
|
+
## Quick reference — common forms
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
Mean / std μ ± σ x̄ ± s
|
|
190
|
+
Probability P(A | B) ℙ(A ∩ B) = ℙ(A) · ℙ(B | A)
|
|
191
|
+
Expectation 𝔼[X] = ∫ x · f(x) dx
|
|
192
|
+
Variance Var(X) = 𝔼[X²] − 𝔼[X]²
|
|
193
|
+
Gradient ∇f = ( ∂f/∂x₁ , ... , ∂f/∂xₙ )
|
|
194
|
+
Norm ‖x‖₂ = √(Σᵢ xᵢ²)
|
|
195
|
+
Big-O T(n) = O(n log n)
|
|
196
|
+
Limit lim_{n → ∞} aₙ = L
|
|
197
|
+
Sum bounds ∑_{i=1}^{n} i = n(n+1)/2
|
|
198
|
+
Quantile q_α = inf{ x : F(x) ≥ α }
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Anti-patterns — never emit these in Claude Code terminal
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
✗ $f(x) = \sum_{i=1}^{n} x_i$
|
|
205
|
+
✗ \( a^2 + b^2 = c^2 \)
|
|
206
|
+
✗ $$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$
|
|
207
|
+
✗ \[ |Q|/|T| \approx 16.9\% \]
|
|
208
|
+
✗ Let 𝑉𝑎𝑟 = … or matrix 𝐀 = … (math-alphanumeric styling; garbles on copy/search — write Var, A)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
If asked to produce raw LaTeX (e.g. for a `.tex` file or a KaTeX-rendering tool downstream), do so — and call it out explicitly: *"Raw LaTeX as requested; this will not render in the terminal."*
|