@laitszkin/apollo-toolkit 3.13.2 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +7 -7
- package/CHANGELOG.md +27 -0
- package/CLAUDE.md +8 -8
- package/analyse-app-logs/SKILL.md +3 -3
- package/bin/apollo-toolkit.ts +7 -0
- package/codex/codex-memory-manager/SKILL.md +2 -2
- package/codex/learn-skill-from-conversations/SKILL.md +3 -3
- package/dist/bin/apollo-toolkit.d.ts +2 -0
- package/dist/bin/apollo-toolkit.js +7 -0
- package/dist/lib/cli.d.ts +41 -0
- package/dist/lib/cli.js +655 -0
- package/dist/lib/installer.d.ts +59 -0
- package/dist/lib/installer.js +404 -0
- package/dist/lib/tool-runner.d.ts +19 -0
- package/dist/lib/tool-runner.js +536 -0
- package/dist/lib/tools/architecture.d.ts +2 -0
- package/dist/lib/tools/architecture.js +34 -0
- package/dist/lib/tools/create-specs.d.ts +2 -0
- package/dist/lib/tools/create-specs.js +175 -0
- package/dist/lib/tools/docs-to-voice.d.ts +2 -0
- package/dist/lib/tools/docs-to-voice.js +705 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.d.ts +2 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.js +312 -0
- package/dist/lib/tools/extract-conversations.d.ts +2 -0
- package/dist/lib/tools/extract-conversations.js +105 -0
- package/dist/lib/tools/extract-pdf-text.d.ts +2 -0
- package/dist/lib/tools/extract-pdf-text.js +92 -0
- package/dist/lib/tools/filter-logs.d.ts +2 -0
- package/dist/lib/tools/filter-logs.js +94 -0
- package/dist/lib/tools/find-github-issues.d.ts +2 -0
- package/dist/lib/tools/find-github-issues.js +176 -0
- package/dist/lib/tools/generate-storyboard-images.d.ts +2 -0
- package/dist/lib/tools/generate-storyboard-images.js +419 -0
- package/dist/lib/tools/log-cli-utils.d.ts +35 -0
- package/dist/lib/tools/log-cli-utils.js +233 -0
- package/dist/lib/tools/open-github-issue.d.ts +2 -0
- package/dist/lib/tools/open-github-issue.js +750 -0
- package/dist/lib/tools/read-github-issue.d.ts +2 -0
- package/dist/lib/tools/read-github-issue.js +134 -0
- package/dist/lib/tools/render-error-book.d.ts +2 -0
- package/dist/lib/tools/render-error-book.js +265 -0
- package/dist/lib/tools/render-katex.d.ts +2 -0
- package/dist/lib/tools/render-katex.js +294 -0
- package/dist/lib/tools/review-threads.d.ts +2 -0
- package/dist/lib/tools/review-threads.js +491 -0
- package/dist/lib/tools/search-logs.d.ts +2 -0
- package/dist/lib/tools/search-logs.js +164 -0
- package/dist/lib/tools/sync-memory-index.d.ts +2 -0
- package/dist/lib/tools/sync-memory-index.js +113 -0
- package/dist/lib/tools/validate-openai-agent-config.d.ts +2 -0
- package/dist/lib/tools/validate-openai-agent-config.js +184 -0
- package/dist/lib/tools/validate-skill-frontmatter.d.ts +2 -0
- package/dist/lib/tools/validate-skill-frontmatter.js +118 -0
- package/dist/lib/types.d.ts +82 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/updater.d.ts +34 -0
- package/dist/lib/updater.js +112 -0
- package/dist/lib/utils/format.d.ts +2 -0
- package/dist/lib/utils/format.js +6 -0
- package/dist/lib/utils/terminal.d.ts +12 -0
- package/dist/lib/utils/terminal.js +26 -0
- package/docs-to-voice/SKILL.md +0 -1
- package/generate-spec/SKILL.md +1 -1
- package/katex/SKILL.md +1 -2
- package/lib/cli.ts +780 -0
- package/lib/installer.ts +466 -0
- package/lib/tool-runner.ts +561 -0
- package/lib/tools/architecture.ts +34 -0
- package/lib/tools/create-specs.ts +204 -0
- package/lib/tools/docs-to-voice.ts +799 -0
- package/lib/tools/enforce-video-aspect-ratio.ts +368 -0
- package/lib/tools/extract-conversations.ts +114 -0
- package/lib/tools/extract-pdf-text.ts +99 -0
- package/lib/tools/filter-logs.ts +118 -0
- package/lib/tools/find-github-issues.ts +211 -0
- package/lib/tools/generate-storyboard-images.ts +455 -0
- package/lib/tools/log-cli-utils.ts +262 -0
- package/lib/tools/open-github-issue.ts +930 -0
- package/lib/tools/read-github-issue.ts +179 -0
- package/lib/tools/render-error-book.ts +300 -0
- package/lib/tools/render-katex.ts +325 -0
- package/lib/tools/review-threads.ts +590 -0
- package/lib/tools/search-logs.ts +200 -0
- package/lib/tools/sync-memory-index.ts +114 -0
- package/lib/tools/validate-openai-agent-config.ts +209 -0
- package/lib/tools/validate-skill-frontmatter.ts +124 -0
- package/lib/types.ts +90 -0
- package/lib/updater.ts +165 -0
- package/lib/utils/format.ts +7 -0
- package/lib/utils/terminal.ts +22 -0
- package/open-github-issue/SKILL.md +2 -2
- package/optimise-skill/SKILL.md +1 -1
- package/package.json +13 -4
- package/resources/project-architecture/assets/architecture.css +764 -0
- package/resources/project-architecture/assets/viewer.client.js +144 -0
- package/resources/project-architecture/index.html +42 -0
- package/review-spec-related-changes/SKILL.md +1 -1
- package/solve-issues-found-during-review/SKILL.md +2 -1
- package/tsconfig.json +28 -0
- package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/filter_logs_by_time.py +0 -64
- package/analyse-app-logs/scripts/log_cli_utils.py +0 -112
- package/analyse-app-logs/scripts/search_logs.py +0 -137
- package/analyse-app-logs/tests/test_filter_logs_by_time.py +0 -95
- package/analyse-app-logs/tests/test_search_logs.py +0 -100
- package/codex/codex-memory-manager/scripts/extract_recent_conversations.py +0 -369
- package/codex/codex-memory-manager/scripts/sync_memory_index.py +0 -130
- package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +0 -177
- package/codex/codex-memory-manager/tests/test_memory_template.py +0 -37
- package/codex/codex-memory-manager/tests/test_sync_memory_index.py +0 -84
- package/codex/learn-skill-from-conversations/scripts/extract_recent_conversations.py +0 -369
- package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +0 -177
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/docs-to-voice/scripts/docs_to_voice.py +0 -1385
- package/docs-to-voice/scripts/docs_to_voice.sh +0 -11
- package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +0 -210
- package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +0 -115
- package/docs-to-voice/tests/test_docs_to_voice_settings.py +0 -43
- package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +0 -51
- package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +0 -57
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/generate-spec/scripts/create-specs +0 -215
- package/generate-spec/tests/test_create_specs.py +0 -200
- package/init-project-html/scripts/architecture-bootstrap-render.js +0 -16
- package/init-project-html/scripts/architecture.js +0 -296
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/katex/scripts/render_katex.py +0 -247
- package/katex/scripts/render_katex.sh +0 -11
- package/katex/tests/test_render_katex.py +0 -174
- package/learning-error-book/scripts/render_error_book_json_to_pdf.py +0 -590
- package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +0 -134
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/open-github-issue/scripts/open_github_issue.py +0 -705
- package/open-github-issue/tests/test_open_github_issue.py +0 -381
- package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +0 -763
- package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +0 -177
- package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/find_issues.py +0 -148
- package/read-github-issue/scripts/read_issue.py +0 -108
- package/read-github-issue/tests/test_find_issues.py +0 -127
- package/read-github-issue/tests/test_read_issue.py +0 -109
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/resolve-review-comments/scripts/review_threads.py +0 -425
- package/resolve-review-comments/tests/test_review_threads.py +0 -74
- package/scripts/validate_openai_agent_config.py +0 -209
- package/scripts/validate_skill_frontmatter.py +0 -131
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
- package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +0 -350
- package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +0 -194
- package/weekly-financial-event-report/scripts/extract_pdf_text_pdfkit.swift +0 -99
- package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +0 -64
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// architecture.js — thin shim over lib/atlas/cli.js.
|
|
3
|
-
//
|
|
4
|
-
// Backward-compatible legacy entrypoint:
|
|
5
|
-
// architecture.js # same as `open`
|
|
6
|
-
// architecture.js open [--project <root>] [--no-open]
|
|
7
|
-
// architecture.js diff [--project <root>] [--out <dir>] [--no-open]
|
|
8
|
-
//
|
|
9
|
-
// All new declarative verbs (feature add, submodule add, function add,
|
|
10
|
-
// variable add, dataflow add|remove|reorder, error add, edge add, meta
|
|
11
|
-
// set, actor add, render, validate, undo) are routed through
|
|
12
|
-
// lib/atlas/cli.js, which owns layout, no-overlap, DOM, CSS, and pan/zoom.
|
|
13
|
-
|
|
14
|
-
'use strict';
|
|
15
|
-
|
|
16
|
-
const fs = require('node:fs');
|
|
17
|
-
const path = require('node:path');
|
|
18
|
-
const { spawn, spawnSync } = require('node:child_process');
|
|
19
|
-
|
|
20
|
-
const newCli = require('../lib/atlas/cli');
|
|
21
|
-
|
|
22
|
-
const ATLAS_REL = path.join('resources', 'project-architecture', 'index.html');
|
|
23
|
-
const RESOURCES_REL = path.join('resources', 'project-architecture');
|
|
24
|
-
const PLANS_REL = path.join('docs', 'plans');
|
|
25
|
-
const DIFF_DIRNAME = 'architecture_diff';
|
|
26
|
-
const REMOVED_FILE = '_removed.txt';
|
|
27
|
-
const ATLAS_DIRNAME = 'atlas';
|
|
28
|
-
const DEFAULT_OUT_REL = path.join('.apollo-toolkit', 'architecture-diff');
|
|
29
|
-
|
|
30
|
-
const LEGACY_VERBS = new Set(['open', 'diff']);
|
|
31
|
-
|
|
32
|
-
const USAGE = newCli.buildArchitectureHelpPage();
|
|
33
|
-
|
|
34
|
-
function parseArgs(argv) {
|
|
35
|
-
const args = [...argv];
|
|
36
|
-
const result = {
|
|
37
|
-
subcommand: 'open',
|
|
38
|
-
projectRoot: null,
|
|
39
|
-
out: null,
|
|
40
|
-
open: true,
|
|
41
|
-
help: false,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
if (args.length > 0 && !args[0].startsWith('-')) {
|
|
45
|
-
const candidate = args[0];
|
|
46
|
-
if (candidate === 'open' || candidate === 'diff') {
|
|
47
|
-
result.subcommand = candidate;
|
|
48
|
-
args.shift();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
while (args.length > 0) {
|
|
53
|
-
const arg = args.shift();
|
|
54
|
-
if (arg === '--help' || arg === '-h') {
|
|
55
|
-
result.help = true;
|
|
56
|
-
} else if (arg === '--project') {
|
|
57
|
-
const value = args.shift();
|
|
58
|
-
if (!value) throw new Error('Missing value for --project');
|
|
59
|
-
result.projectRoot = path.resolve(value);
|
|
60
|
-
} else if (arg === '--out') {
|
|
61
|
-
const value = args.shift();
|
|
62
|
-
if (!value) throw new Error('Missing value for --out');
|
|
63
|
-
result.out = path.resolve(value);
|
|
64
|
-
} else if (arg === '--no-open') {
|
|
65
|
-
result.open = false;
|
|
66
|
-
} else {
|
|
67
|
-
throw new Error(`Unexpected argument: ${arg}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return result;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function findProjectRoot(startDir) {
|
|
75
|
-
let dir = path.resolve(startDir);
|
|
76
|
-
while (true) {
|
|
77
|
-
if (fs.existsSync(path.join(dir, ATLAS_REL))) return dir;
|
|
78
|
-
if (fs.existsSync(path.join(dir, RESOURCES_REL, ATLAS_DIRNAME, 'atlas.index.yaml'))) return dir;
|
|
79
|
-
const parent = path.dirname(dir);
|
|
80
|
-
if (parent === dir) return null;
|
|
81
|
-
dir = parent;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function openInBrowser(filePath) {
|
|
86
|
-
const platform = process.platform;
|
|
87
|
-
let command;
|
|
88
|
-
let args;
|
|
89
|
-
if (platform === 'darwin') { command = 'open'; args = [filePath]; }
|
|
90
|
-
else if (platform === 'win32') { command = 'cmd'; args = ['/c', 'start', '""', filePath]; }
|
|
91
|
-
else { command = 'xdg-open'; args = [filePath]; }
|
|
92
|
-
try {
|
|
93
|
-
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
94
|
-
child.on('error', () => {});
|
|
95
|
-
child.unref();
|
|
96
|
-
} catch (_e) { /* best effort */ }
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function walkArchitectureDiffDirs(plansRoot) {
|
|
100
|
-
const result = [];
|
|
101
|
-
if (!fs.existsSync(plansRoot)) return result;
|
|
102
|
-
function recurse(dir) {
|
|
103
|
-
let entries;
|
|
104
|
-
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_e) { return; }
|
|
105
|
-
for (const entry of entries) {
|
|
106
|
-
if (!entry.isDirectory()) continue;
|
|
107
|
-
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
108
|
-
const full = path.join(dir, entry.name);
|
|
109
|
-
if (entry.name === DIFF_DIRNAME) { result.push(full); continue; }
|
|
110
|
-
recurse(full);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
recurse(plansRoot);
|
|
114
|
-
return result;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function walkAfterStateHtml(diffDir) {
|
|
118
|
-
const out = [];
|
|
119
|
-
function recurse(dir, relParts) {
|
|
120
|
-
let entries;
|
|
121
|
-
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_e) { return; }
|
|
122
|
-
for (const entry of entries) {
|
|
123
|
-
if (entry.name === 'assets') continue;
|
|
124
|
-
if (entry.name === ATLAS_DIRNAME) continue;
|
|
125
|
-
if (entry.name === REMOVED_FILE) continue;
|
|
126
|
-
if (entry.name.startsWith('.')) continue;
|
|
127
|
-
const full = path.join(dir, entry.name);
|
|
128
|
-
const nextRel = [...relParts, entry.name];
|
|
129
|
-
if (entry.isDirectory()) recurse(full, nextRel);
|
|
130
|
-
else if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
|
|
131
|
-
out.push({ abs: full, rel: nextRel.join('/') });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
recurse(diffDir, []);
|
|
136
|
-
return out;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function readRemovedManifest(diffDir) {
|
|
140
|
-
const manifestPath = path.join(diffDir, REMOVED_FILE);
|
|
141
|
-
if (!fs.existsSync(manifestPath)) return [];
|
|
142
|
-
return fs.readFileSync(manifestPath, 'utf8')
|
|
143
|
-
.split(/\r?\n/)
|
|
144
|
-
.map((line) => line.trim())
|
|
145
|
-
.filter((line) => line && !line.startsWith('#'));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function collectChanges(projectRoot) {
|
|
149
|
-
const resourcesRoot = path.join(projectRoot, RESOURCES_REL);
|
|
150
|
-
const plansRoot = path.join(projectRoot, PLANS_REL);
|
|
151
|
-
const diffDirs = walkArchitectureDiffDirs(plansRoot);
|
|
152
|
-
const changes = [];
|
|
153
|
-
|
|
154
|
-
for (const diffDir of diffDirs) {
|
|
155
|
-
const specDir = path.dirname(diffDir);
|
|
156
|
-
const specLabel = path.relative(projectRoot, specDir);
|
|
157
|
-
for (const after of walkAfterStateHtml(diffDir)) {
|
|
158
|
-
const beforeAbs = path.join(resourcesRoot, after.rel);
|
|
159
|
-
const beforeExists = fs.existsSync(beforeAbs);
|
|
160
|
-
changes.push({
|
|
161
|
-
kind: beforeExists ? 'modified' : 'added',
|
|
162
|
-
rel: after.rel,
|
|
163
|
-
spec: specLabel,
|
|
164
|
-
beforePath: beforeExists ? path.relative(projectRoot, beforeAbs) : null,
|
|
165
|
-
afterPath: path.relative(projectRoot, after.abs),
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
for (const removedRel of readRemovedManifest(diffDir)) {
|
|
169
|
-
const beforeAbs = path.join(resourcesRoot, removedRel);
|
|
170
|
-
if (!fs.existsSync(beforeAbs)) continue;
|
|
171
|
-
changes.push({
|
|
172
|
-
kind: 'removed',
|
|
173
|
-
rel: removedRel,
|
|
174
|
-
spec: specLabel,
|
|
175
|
-
beforePath: path.relative(projectRoot, beforeAbs),
|
|
176
|
-
afterPath: null,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
changes.sort((a, b) => {
|
|
182
|
-
if (a.spec !== b.spec) return a.spec.localeCompare(b.spec);
|
|
183
|
-
if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
|
|
184
|
-
return a.rel.localeCompare(b.rel);
|
|
185
|
-
});
|
|
186
|
-
return changes;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function toViewerRel(outDir, projectRoot, projectRelPath) {
|
|
190
|
-
if (!projectRelPath) return null;
|
|
191
|
-
const absolute = path.resolve(projectRoot, projectRelPath);
|
|
192
|
-
const rel = path.relative(outDir, absolute);
|
|
193
|
-
return rel.split(path.sep).join('/');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function renderViewer({ changes, projectRoot, outDir }) {
|
|
197
|
-
return newCli.renderDiffViewer({ changes, projectRoot, outDir });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function runOpen(opts, io) {
|
|
201
|
-
let projectRoot = opts.projectRoot
|
|
202
|
-
? path.resolve(opts.projectRoot)
|
|
203
|
-
: findProjectRoot(process.cwd());
|
|
204
|
-
if (!projectRoot) {
|
|
205
|
-
projectRoot = process.cwd();
|
|
206
|
-
}
|
|
207
|
-
fs.mkdirSync(path.join(projectRoot, RESOURCES_REL), { recursive: true });
|
|
208
|
-
const atlas = path.join(projectRoot, ATLAS_REL);
|
|
209
|
-
if (!fs.existsSync(atlas)) {
|
|
210
|
-
const bootstrap = path.join(__dirname, 'architecture-bootstrap-render.js');
|
|
211
|
-
const result = spawnSync(process.execPath, [bootstrap, 'render', '--project', projectRoot, '--no-open'], {
|
|
212
|
-
stdio: 'ignore',
|
|
213
|
-
});
|
|
214
|
-
if (result.status !== 0) {
|
|
215
|
-
io.stderr.write(`Atlas not found and render failed: ${atlas}\n`);
|
|
216
|
-
return 1;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (!fs.existsSync(atlas)) {
|
|
220
|
-
io.stderr.write(`Atlas not found: ${atlas}\n`);
|
|
221
|
-
return 1;
|
|
222
|
-
}
|
|
223
|
-
io.stdout.write(`${atlas}\n`);
|
|
224
|
-
if (opts.open) openInBrowser(atlas);
|
|
225
|
-
return 0;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function runDiff(opts, io) {
|
|
229
|
-
let projectRoot = opts.projectRoot
|
|
230
|
-
? path.resolve(opts.projectRoot)
|
|
231
|
-
: findProjectRoot(process.cwd());
|
|
232
|
-
if (!projectRoot) {
|
|
233
|
-
projectRoot = process.cwd();
|
|
234
|
-
}
|
|
235
|
-
const bootstrap = path.join(__dirname, 'architecture-bootstrap-render.js');
|
|
236
|
-
const args = [bootstrap, 'diff', '--project', projectRoot];
|
|
237
|
-
if (opts.out) args.push('--out', opts.out);
|
|
238
|
-
if (!opts.open) args.push('--no-open');
|
|
239
|
-
const result = spawnSync(process.execPath, args, { encoding: 'utf8' });
|
|
240
|
-
if (result.stdout) io.stdout.write(result.stdout);
|
|
241
|
-
if (result.stderr) io.stderr.write(result.stderr);
|
|
242
|
-
return result.status || 0;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// main(argv, io) is sync and supports the legacy verbs `open` and
|
|
246
|
-
// `diff` only. Tests rely on the sync return-code contract. All other
|
|
247
|
-
// verbs go through dispatchAsync().
|
|
248
|
-
function main(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
|
|
249
|
-
let opts;
|
|
250
|
-
try {
|
|
251
|
-
opts = parseArgs(argv);
|
|
252
|
-
} catch (error) {
|
|
253
|
-
io.stderr.write(`${error.message}\n\n${USAGE}\n`);
|
|
254
|
-
return 1;
|
|
255
|
-
}
|
|
256
|
-
if (opts.help) {
|
|
257
|
-
const explicitLegacyVerb = argv.length > 0 && !argv[0].startsWith('-') && LEGACY_VERBS.has(argv[0]);
|
|
258
|
-
const helpText = explicitLegacyVerb ? newCli.buildArchitectureHelpPage(argv[0]) : USAGE;
|
|
259
|
-
io.stdout.write(`${helpText}\n`);
|
|
260
|
-
return 0;
|
|
261
|
-
}
|
|
262
|
-
if (opts.subcommand === 'open') return runOpen(opts, io);
|
|
263
|
-
if (opts.subcommand === 'diff') return runDiff(opts, io);
|
|
264
|
-
io.stderr.write(`Unknown subcommand: ${opts.subcommand}\n\n${USAGE}\n`);
|
|
265
|
-
return 1;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async function dispatchAsync(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
|
|
269
|
-
return newCli.dispatch(argv, io);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (require.main === module) {
|
|
273
|
-
const argv = process.argv.slice(2);
|
|
274
|
-
const verb = argv[0];
|
|
275
|
-
if (!verb || verb.startsWith('-') || LEGACY_VERBS.has(verb)) {
|
|
276
|
-
process.exit(main(argv));
|
|
277
|
-
} else {
|
|
278
|
-
dispatchAsync(argv).then((code) => process.exit(code)).catch((err) => {
|
|
279
|
-
process.stderr.write(`${err && err.stack ? err.stack : err}\n`);
|
|
280
|
-
process.exit(1);
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
module.exports = {
|
|
286
|
-
parseArgs,
|
|
287
|
-
findProjectRoot,
|
|
288
|
-
collectChanges,
|
|
289
|
-
renderViewer,
|
|
290
|
-
toViewerRel,
|
|
291
|
-
walkArchitectureDiffDirs,
|
|
292
|
-
walkAfterStateHtml,
|
|
293
|
-
readRemovedManifest,
|
|
294
|
-
main,
|
|
295
|
-
dispatchAsync,
|
|
296
|
-
};
|
|
Binary file
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Render TeX formulas with the official KaTeX CLI and wrap the output for reuse."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import argparse
|
|
7
|
-
import json
|
|
8
|
-
import os
|
|
9
|
-
import pathlib
|
|
10
|
-
import subprocess
|
|
11
|
-
import sys
|
|
12
|
-
import tempfile
|
|
13
|
-
from typing import Iterable
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
DEFAULT_CSS_HREF = "https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.css"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class KatexRenderError(Exception):
|
|
20
|
-
"""User-facing error for rendering failures."""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
24
|
-
parser = argparse.ArgumentParser(
|
|
25
|
-
prog="render_katex.py",
|
|
26
|
-
description="Render TeX with KaTeX and emit insertion-ready output.",
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
input_group = parser.add_mutually_exclusive_group(required=True)
|
|
30
|
-
input_group.add_argument("--tex", help="Raw TeX expression without delimiters.")
|
|
31
|
-
input_group.add_argument("--input-file", help="Path to a UTF-8 text file containing raw TeX.")
|
|
32
|
-
|
|
33
|
-
parser.add_argument(
|
|
34
|
-
"--output-format",
|
|
35
|
-
choices=("html-fragment", "html-page", "markdown-inline", "markdown-block", "json"),
|
|
36
|
-
default="html-fragment",
|
|
37
|
-
help="How to wrap the rendered KaTeX output.",
|
|
38
|
-
)
|
|
39
|
-
parser.add_argument(
|
|
40
|
-
"--katex-format",
|
|
41
|
-
choices=("html", "mathml", "htmlAndMathml"),
|
|
42
|
-
default="htmlAndMathml",
|
|
43
|
-
help="KaTeX internal output format.",
|
|
44
|
-
)
|
|
45
|
-
parser.add_argument("--display-mode", action="store_true", help="Render in display mode.")
|
|
46
|
-
parser.add_argument("--output-file", help="Write the wrapped output to a file.")
|
|
47
|
-
parser.add_argument("--css-href", default=DEFAULT_CSS_HREF, help="Stylesheet href for html-page/json output.")
|
|
48
|
-
parser.add_argument("--title", default="KaTeX Render", help="Document title for html-page output.")
|
|
49
|
-
parser.add_argument("--lang", default="en", help="HTML lang attribute for html-page output.")
|
|
50
|
-
|
|
51
|
-
parser.add_argument("--macro", action="append", default=[], help="Macro definition in NAME:VALUE form.")
|
|
52
|
-
parser.add_argument("--macro-file", help="Path to a JSON file mapping macro names to expansion strings.")
|
|
53
|
-
parser.add_argument("--error-color", help="Hex color or CSS color name for parse errors.")
|
|
54
|
-
parser.add_argument("--strict", help="KaTeX strict mode setting.")
|
|
55
|
-
parser.add_argument("--trust", help="KaTeX trust mode setting.")
|
|
56
|
-
parser.add_argument("--max-size", type=float, help="Maximum user-specified size in em.")
|
|
57
|
-
parser.add_argument("--max-expand", type=int, help="Maximum macro expansion count.")
|
|
58
|
-
parser.add_argument("--min-rule-thickness", type=float, help="Minimum rule thickness in em.")
|
|
59
|
-
parser.add_argument("--leqno", action="store_true", help="Render display equations with left equation numbers.")
|
|
60
|
-
parser.add_argument("--fleqn", action="store_true", help="Render display equations flush left.")
|
|
61
|
-
parser.add_argument(
|
|
62
|
-
"--color-is-text-color",
|
|
63
|
-
action="store_true",
|
|
64
|
-
help="Interpret \\\\color like legacy text color behavior.",
|
|
65
|
-
)
|
|
66
|
-
parser.add_argument(
|
|
67
|
-
"--no-throw-on-error",
|
|
68
|
-
action="store_true",
|
|
69
|
-
help="Render invalid input with colored source text instead of failing.",
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
return parser.parse_args(argv)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def normalize_path(raw_path: str) -> pathlib.Path:
|
|
76
|
-
path = pathlib.Path(raw_path).expanduser()
|
|
77
|
-
if not path.is_absolute():
|
|
78
|
-
path = pathlib.Path.cwd() / path
|
|
79
|
-
return path.resolve()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def load_tex(args: argparse.Namespace) -> str:
|
|
83
|
-
if args.input_file:
|
|
84
|
-
path = normalize_path(args.input_file)
|
|
85
|
-
if not path.is_file():
|
|
86
|
-
raise KatexRenderError(f"Input file not found: {path}")
|
|
87
|
-
return path.read_text(encoding="utf-8").strip()
|
|
88
|
-
return (args.tex or "").strip()
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def load_macro_pairs(values: Iterable[str]) -> list[tuple[str, str]]:
|
|
92
|
-
pairs: list[tuple[str, str]] = []
|
|
93
|
-
for raw_value in values:
|
|
94
|
-
if ":" not in raw_value:
|
|
95
|
-
raise KatexRenderError(f"Invalid --macro value '{raw_value}'. Use NAME:VALUE.")
|
|
96
|
-
name, expansion = raw_value.split(":", 1)
|
|
97
|
-
name = name.strip()
|
|
98
|
-
expansion = expansion.strip()
|
|
99
|
-
if not name or not expansion:
|
|
100
|
-
raise KatexRenderError(f"Invalid --macro value '{raw_value}'. Use NAME:VALUE.")
|
|
101
|
-
pairs.append((name, expansion))
|
|
102
|
-
return pairs
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def run_katex_cli(tex: str, args: argparse.Namespace) -> str:
|
|
106
|
-
command = [
|
|
107
|
-
"npx",
|
|
108
|
-
"--yes",
|
|
109
|
-
"--package",
|
|
110
|
-
"katex",
|
|
111
|
-
"katex",
|
|
112
|
-
"--format",
|
|
113
|
-
args.katex_format,
|
|
114
|
-
]
|
|
115
|
-
|
|
116
|
-
if args.display_mode:
|
|
117
|
-
command.append("--display-mode")
|
|
118
|
-
if args.leqno:
|
|
119
|
-
command.append("--leqno")
|
|
120
|
-
if args.fleqn:
|
|
121
|
-
command.append("--fleqn")
|
|
122
|
-
if args.color_is_text_color:
|
|
123
|
-
command.append("--color-is-text-color")
|
|
124
|
-
if args.no_throw_on_error:
|
|
125
|
-
command.append("--no-throw-on-error")
|
|
126
|
-
|
|
127
|
-
if args.error_color:
|
|
128
|
-
command.extend(["--error-color", args.error_color])
|
|
129
|
-
if args.strict:
|
|
130
|
-
command.extend(["--strict", args.strict])
|
|
131
|
-
if args.trust:
|
|
132
|
-
command.extend(["--trust", args.trust])
|
|
133
|
-
if args.max_size is not None:
|
|
134
|
-
command.extend(["--max-size", str(args.max_size)])
|
|
135
|
-
if args.max_expand is not None:
|
|
136
|
-
command.extend(["--max-expand", str(args.max_expand)])
|
|
137
|
-
if args.min_rule_thickness is not None:
|
|
138
|
-
command.extend(["--min-rule-thickness", str(args.min_rule_thickness)])
|
|
139
|
-
|
|
140
|
-
for name, expansion in load_macro_pairs(args.macro):
|
|
141
|
-
command.extend(["--macro", f"{name}:{expansion}"])
|
|
142
|
-
|
|
143
|
-
if args.macro_file:
|
|
144
|
-
macro_file = normalize_path(args.macro_file)
|
|
145
|
-
if not macro_file.is_file():
|
|
146
|
-
raise KatexRenderError(f"Macro file not found: {macro_file}")
|
|
147
|
-
command.extend(["--macro-file", str(macro_file)])
|
|
148
|
-
|
|
149
|
-
with tempfile.NamedTemporaryFile("w", suffix=".tex", encoding="utf-8", delete=False) as handle:
|
|
150
|
-
handle.write(tex)
|
|
151
|
-
handle.write("\n")
|
|
152
|
-
temp_path = pathlib.Path(handle.name)
|
|
153
|
-
|
|
154
|
-
try:
|
|
155
|
-
command.extend(["--input", str(temp_path)])
|
|
156
|
-
result = subprocess.run(
|
|
157
|
-
command,
|
|
158
|
-
check=False,
|
|
159
|
-
capture_output=True,
|
|
160
|
-
text=True,
|
|
161
|
-
encoding="utf-8",
|
|
162
|
-
)
|
|
163
|
-
finally:
|
|
164
|
-
temp_path.unlink(missing_ok=True)
|
|
165
|
-
|
|
166
|
-
if result.returncode != 0:
|
|
167
|
-
stderr = result.stderr.strip() or "KaTeX CLI failed."
|
|
168
|
-
raise KatexRenderError(stderr)
|
|
169
|
-
|
|
170
|
-
return result.stdout.strip()
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def build_html_page(rendered_html: str, args: argparse.Namespace) -> str:
|
|
174
|
-
css_link = ""
|
|
175
|
-
if args.css_href.strip():
|
|
176
|
-
css_link = f' <link rel="stylesheet" href="{args.css_href.strip()}">\n'
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
"<!DOCTYPE html>\n"
|
|
180
|
-
f'<html lang="{args.lang}">\n'
|
|
181
|
-
"<head>\n"
|
|
182
|
-
' <meta charset="utf-8">\n'
|
|
183
|
-
f" <title>{args.title}</title>\n"
|
|
184
|
-
f"{css_link}"
|
|
185
|
-
"</head>\n"
|
|
186
|
-
"<body>\n"
|
|
187
|
-
f"{rendered_html}\n"
|
|
188
|
-
"</body>\n"
|
|
189
|
-
"</html>\n"
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def wrap_output(rendered_html: str, tex: str, args: argparse.Namespace) -> str:
|
|
194
|
-
if args.output_format == "html-fragment":
|
|
195
|
-
return f"{rendered_html}\n"
|
|
196
|
-
if args.output_format == "html-page":
|
|
197
|
-
return build_html_page(rendered_html, args)
|
|
198
|
-
if args.output_format == "markdown-inline":
|
|
199
|
-
return f"{rendered_html}\n"
|
|
200
|
-
if args.output_format == "markdown-block":
|
|
201
|
-
return f"\n{rendered_html}\n"
|
|
202
|
-
if args.output_format == "json":
|
|
203
|
-
payload = {
|
|
204
|
-
"tex": tex,
|
|
205
|
-
"displayMode": args.display_mode,
|
|
206
|
-
"katexFormat": args.katex_format,
|
|
207
|
-
"cssHref": args.css_href,
|
|
208
|
-
"content": rendered_html,
|
|
209
|
-
}
|
|
210
|
-
return json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
|
|
211
|
-
raise KatexRenderError(f"Unsupported output format: {args.output_format}")
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def write_output(content: str, output_file: str | None) -> None:
|
|
215
|
-
if not output_file:
|
|
216
|
-
sys.stdout.write(content)
|
|
217
|
-
return
|
|
218
|
-
|
|
219
|
-
path = normalize_path(output_file)
|
|
220
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
221
|
-
path.write_text(content, encoding="utf-8")
|
|
222
|
-
sys.stdout.write(str(path) + "\n")
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def main(argv: list[str]) -> int:
|
|
226
|
-
try:
|
|
227
|
-
args = parse_args(argv)
|
|
228
|
-
tex = load_tex(args)
|
|
229
|
-
if not tex:
|
|
230
|
-
raise KatexRenderError("Input TeX is empty.")
|
|
231
|
-
rendered_html = run_katex_cli(tex, args)
|
|
232
|
-
wrapped = wrap_output(rendered_html, tex, args)
|
|
233
|
-
write_output(wrapped, args.output_file)
|
|
234
|
-
return 0
|
|
235
|
-
except KatexRenderError as exc:
|
|
236
|
-
print(f"[ERROR] {exc}", file=sys.stderr)
|
|
237
|
-
return 1
|
|
238
|
-
except FileNotFoundError as exc:
|
|
239
|
-
missing = exc.filename or "required executable"
|
|
240
|
-
if os.path.basename(missing) in {"npx", "node"}:
|
|
241
|
-
print("[ERROR] node and npx are required to render KaTeX.", file=sys.stderr)
|
|
242
|
-
return 1
|
|
243
|
-
raise
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if __name__ == "__main__":
|
|
247
|
-
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
5
|
-
|
|
6
|
-
if ! command -v python3 >/dev/null 2>&1; then
|
|
7
|
-
echo "[ERROR] python3 is required." >&2
|
|
8
|
-
exit 1
|
|
9
|
-
fi
|
|
10
|
-
|
|
11
|
-
exec python3 "$script_dir/render_katex.py" "$@"
|