@litfamily/litclaude 1.0.7 → 1.0.9
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 +15 -2
- package/README.md +186 -139
- package/README_ko-KR.md +182 -119
- package/docs/agents.md +30 -8
- package/docs/assets/cover-motion-still.webp +0 -0
- package/docs/assets/cover-motion.webp +0 -0
- package/docs/assets/litclaude-continuity-1600.webp +0 -0
- package/docs/assets/litclaude-ignition-1600.webp +0 -0
- package/docs/assets/litfamily-machines.png +0 -0
- package/docs/assets/readme/JetBrainsMono-OFL.txt +93 -0
- package/docs/assets/readme/Lucide-LICENSE.txt +43 -0
- package/docs/assets/readme/ascii-readme.svg +5 -0
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/assets/readme/ignition-film.mp4 +0 -0
- package/docs/assets/readme/ignition-poster.png +0 -0
- package/docs/assets/readme/ignition-readme.gif +0 -0
- package/docs/assets/readme/lucide-book-open.svg +14 -0
- package/docs/assets/readme/lucide-play.svg +13 -0
- package/docs/assets/readme/lucide-shield-check.svg +14 -0
- package/docs/hooks.md +26 -10
- package/package.json +14 -1
- package/plugins/litclaude/.claude-plugin/plugin.json +1 -1
- package/plugins/litclaude/bin/litclaude-hook.js +55 -10
- package/plugins/litclaude/bin/litclaude-hud.js +5 -1
- package/plugins/litclaude/bin/litclaude-mcp.js +1 -1
- package/plugins/litclaude/commands/lit-handoff.md +3 -3
- package/plugins/litclaude/commands/lit-scientific-visualization.md +2 -2
- package/plugins/litclaude/lib/canonical-skill-catalog.mjs +1 -0
- package/plugins/litclaude/lib/canonical-skill-resources.mjs +26 -3
- package/plugins/litclaude/lib/hud-accent-palette.mjs +2 -2
- package/plugins/litclaude/lib/hud-ignition.mjs +74 -0
- package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +21 -12
- package/plugins/litclaude/skills/frontend-ui-ux/references/complete-contract.md +8 -9
- package/plugins/litclaude/skills/frontend-ui-ux/references/default-editorial-pixel.json +21 -0
- package/plugins/litclaude/skills/frontend-ui-ux/references/production-interview.md +17 -0
- package/plugins/litclaude/skills/lit-handoff/SKILL.md +2 -2
- package/plugins/litclaude/skills/lit-scientific-visualization/SKILL.md +3 -3
- package/plugins/litclaude/skills/litwork/SKILL.md +2 -2
- package/plugins/litclaude/skills/readme-studio/SKILL.md +86 -0
- package/plugins/litclaude/skills/readme-studio/references/decoration-and-navigation.md +55 -0
- package/plugins/litclaude/skills/readme-studio/references/facts-and-assembly.md +19 -0
- package/plugins/litclaude/skills/readme-studio/references/production-interview.md +11 -0
- package/plugins/litclaude/skills/readme-studio/references/render-and-deliver.md +44 -0
- package/plugins/litclaude/skills/readme-studio/references/type-and-background.md +23 -0
- package/plugins/litclaude/skills/readme-studio/scripts/validate-readme-facts.mjs +28 -0
- package/plugins/litclaude/skills/readme-studio/templates/cover-source.json +21 -0
- package/plugins/litclaude/skills/readme-studio/templates/hyperframes-cover/index.html +30 -0
- package/plugins/litclaude/skills/readme-studio/templates/hyperframes-cover/index.motion.json +1 -0
- package/plugins/litclaude/skills/readme-studio/templates/hyperframes-cover/package-lock.json +1969 -0
- package/plugins/litclaude/skills/readme-studio/templates/hyperframes-cover/package.json +8 -0
- package/plugins/litclaude/skills/readme-studio/templates/readme-cover-section.md +75 -0
- package/plugins/litclaude/skills/readme-studio/templates/readme-facts.json +4 -0
- package/plugins/litclaude/skills/readme-studio/templates/remotion-cover/package-lock.json +3783 -0
- package/plugins/litclaude/skills/readme-studio/templates/remotion-cover/package.json +7 -0
- package/plugins/litclaude/skills/readme-studio/templates/remotion-cover/src/index.tsx +36 -0
- package/plugins/litclaude/skills/readme-studio/templates/typography/package-lock.json +133 -0
- package/plugins/litclaude/skills/readme-studio/templates/typography/package.json +6 -0
- package/plugins/litclaude/skills/readme-studio/templates/typography/safe-files.mjs +57 -0
- package/plugins/litclaude/skills/readme-studio/templates/typography/shape-text-to-svg.mjs +47 -0
- package/scripts/qa-real-surface-lib.mjs +3 -2
- package/scripts/strip-ansi.mjs +1 -0
- package/tools/check-model-routing.mjs +6 -6
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "litclaude-readme-motion-workspace",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {"compositions": "remotion compositions src/index.tsx", "render": "remotion render", "still": "remotion still"},
|
|
6
|
+
"dependencies": {"remotion": "4.0.526", "@remotion/cli": "4.0.526", "react": "19.3.0", "react-dom": "19.3.0"}
|
|
7
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {AbsoluteFill, Composition, Easing, Img, interpolate, registerRoot, staticFile, useCurrentFrame, useVideoConfig} from 'remotion';
|
|
3
|
+
|
|
4
|
+
type CoverProps = {dark: boolean; mobile: boolean; blur: number; grain: number; expressive: boolean};
|
|
5
|
+
|
|
6
|
+
const Cover: React.FC<CoverProps> = ({dark, mobile, blur, grain, expressive}) => {
|
|
7
|
+
const frame = useCurrentFrame();
|
|
8
|
+
const {durationInFrames} = useVideoConfig();
|
|
9
|
+
const phase = frame / (durationInFrames - 1);
|
|
10
|
+
const entry = interpolate(frame, [0, 24, 265, 299], [0, 1, 1, 0], {easing: Easing.inOut(Easing.cubic), extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
|
|
11
|
+
const ink = dark ? 'light' : 'dark';
|
|
12
|
+
const paper = dark ? '#18272b' : '#f5f3eb';
|
|
13
|
+
const color = dark ? '#f5f3eb' : '#18272b';
|
|
14
|
+
const pad = mobile ? 54 : 80;
|
|
15
|
+
return <AbsoluteFill style={{background: paper, overflow: 'hidden'}}>
|
|
16
|
+
<Img className="depth-far" src={staticFile('background.png')} style={{position: 'absolute', width: '100%', height: '100%', objectFit: 'cover', filter: `blur(${Math.max(10, blur + 8)}px)`, transform: `scale(${1.08 + Math.sin(phase * Math.PI * 2) * 0.008})`}}/>
|
|
17
|
+
<Img className="depth-mid" src={staticFile('background.png')} style={{position: 'absolute', width: '100%', height: '100%', objectFit: 'cover', clipPath: 'inset(8% 8% 18% 26% round 40px)', filter: `blur(${Math.max(3, blur + 1)}px)`, opacity: 0.56, transform: `scale(${1.04 + Math.sin(phase * Math.PI * 2) * 0.008})`}}/>
|
|
18
|
+
<AbsoluteFill style={{background: `radial-gradient(ellipse at 85% 12%, transparent, ${paper}b0 85%)`}}/>
|
|
19
|
+
<div style={{position: 'absolute', width: mobile ? 600 : 900, height: 700, borderRadius: '50%', right: -250, top: -220, background: dark ? '#b7d8c733' : '#fff9d766', filter: 'blur(80px)', transform: `translateY(${Math.sin(phase*Math.PI*2)*(expressive ? 30 : 12)}px)`}}/>
|
|
20
|
+
<svg width="100%" height="100%" style={{position: 'absolute', opacity: grain}}><filter id="grain"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" seed="19" stitchTiles="stitch"/></filter><rect width="100%" height="100%" filter="url(#grain)"/></svg>
|
|
21
|
+
<div className="rim-light" data-light-zone="open-background" aria-hidden="true" style={{position: 'absolute', zIndex: 4, left: mobile ? pad + 56 : 1060, right: mobile ? pad + 56 : 80, top: mobile ? 20 : 48, height: mobile ? 56 : 200, borderRadius: '50%', background: 'radial-gradient(ellipse at 54% 42%, rgba(255,242,190,.6) 0%, rgba(255,242,190,.28) 38%, rgba(255,242,190,0) 76%)', filter: `blur(${mobile ? 10 : 14}px)`, opacity: 0.9, transform: mobile ? 'none' : 'rotate(-12deg)', pointerEvents: 'none'}}/>
|
|
22
|
+
<div className="foreground-plane" style={{position: 'absolute', zIndex: 5, left: pad, right: mobile ? pad : 580, top: mobile ? 100 : 145, padding: mobile ? 30 : 42, background: paper, border: `1px solid ${color}55`, transform: `translateY(${(1-entry)*16}px)`}}>
|
|
23
|
+
<Img src={staticFile(`label-${ink}.svg`)} style={{display: 'block', width: '58%', maxHeight: 40, objectFit: 'contain', objectPosition: 'left'}}/>
|
|
24
|
+
<Img src={staticFile(`title-${ink}.svg`)} style={{display: 'block', width: '100%', marginTop: 36}}/>
|
|
25
|
+
<div style={{width: 64, height: 5, background: '#c64d2e', margin: '28px 0'}}/>
|
|
26
|
+
<Img src={staticFile(`subtitle-${ink}.svg`)} style={{display: 'block', width: '90%'}}/>
|
|
27
|
+
</div>
|
|
28
|
+
<svg className="foreground-plane" viewBox="0 0 20 20" shapeRendering="crispEdges" style={{position: 'absolute', zIndex: 6, width: mobile ? 300 : 400, right: mobile ? 80 : 100, bottom: mobile ? 65 : 125, transform: `translateY(${-8*Math.sin(phase*Math.PI*2)}px)`}} aria-label="Original pixel notebook motif">
|
|
29
|
+
<path fill={paper} d="M3 1h12v2h2v15H3z"/><path fill={color} d="M3 1h12v2H5v13h10V3h2v15H3zM7 5h6v1H7zM7 8h6v1H7zM7 11h4v1H7z"/><path fill="#c64d2e" d="M1 4h4v2H1zM1 9h4v2H1zM1 14h4v2H1zM13 13h5v2h-5z"/>
|
|
30
|
+
</svg>
|
|
31
|
+
<div style={{position: 'absolute', left: pad, right: pad, bottom: 40, borderTop: `1px solid ${color}88`}}/>
|
|
32
|
+
</AbsoluteFill>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const Root = () => <>{[false,true].flatMap(mobile => [false,true].map(dark => <Composition key={`${mobile}-${dark}`} id={`Cover${mobile ? 'Mobile' : 'Wide'}${dark ? 'Dark' : 'Light'}`} component={Cover} width={mobile ? 800 : 1600} height={mobile ? 1000 : 800} fps={60} durationInFrames={300} defaultProps={{dark,mobile,blur:2,grain:0.06,expressive:false}}/>))}</>;
|
|
36
|
+
registerRoot(Root);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "litclaude-readme-type-workspace",
|
|
3
|
+
"lockfileVersion": 3,
|
|
4
|
+
"requires": true,
|
|
5
|
+
"packages": {
|
|
6
|
+
"": {
|
|
7
|
+
"name": "litclaude-readme-type-workspace",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"fontkit": "2.0.4"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"node_modules/@swc/helpers": {
|
|
13
|
+
"version": "0.5.23",
|
|
14
|
+
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
|
|
15
|
+
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"tslib": "^2.8.0"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"node_modules/base64-js": {
|
|
22
|
+
"version": "1.5.1",
|
|
23
|
+
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
|
24
|
+
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
|
25
|
+
"funding": [
|
|
26
|
+
{
|
|
27
|
+
"type": "github",
|
|
28
|
+
"url": "https://github.com/sponsors/feross"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "patreon",
|
|
32
|
+
"url": "https://www.patreon.com/feross"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "consulting",
|
|
36
|
+
"url": "https://feross.org/support"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT"
|
|
40
|
+
},
|
|
41
|
+
"node_modules/brotli": {
|
|
42
|
+
"version": "1.3.3",
|
|
43
|
+
"resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
|
|
44
|
+
"integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"base64-js": "^1.1.2"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"node_modules/clone": {
|
|
51
|
+
"version": "2.1.2",
|
|
52
|
+
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
|
53
|
+
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=0.8"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"node_modules/dfa": {
|
|
60
|
+
"version": "1.2.0",
|
|
61
|
+
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
|
|
62
|
+
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
|
|
63
|
+
"license": "MIT"
|
|
64
|
+
},
|
|
65
|
+
"node_modules/fast-deep-equal": {
|
|
66
|
+
"version": "3.1.3",
|
|
67
|
+
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
68
|
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
|
69
|
+
"license": "MIT"
|
|
70
|
+
},
|
|
71
|
+
"node_modules/fontkit": {
|
|
72
|
+
"version": "2.0.4",
|
|
73
|
+
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
|
|
74
|
+
"integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
|
|
75
|
+
"license": "MIT",
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"@swc/helpers": "^0.5.12",
|
|
78
|
+
"brotli": "^1.3.2",
|
|
79
|
+
"clone": "^2.1.2",
|
|
80
|
+
"dfa": "^1.2.0",
|
|
81
|
+
"fast-deep-equal": "^3.1.3",
|
|
82
|
+
"restructure": "^3.0.0",
|
|
83
|
+
"tiny-inflate": "^1.0.3",
|
|
84
|
+
"unicode-properties": "^1.4.0",
|
|
85
|
+
"unicode-trie": "^2.0.0"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"node_modules/pako": {
|
|
89
|
+
"version": "0.2.9",
|
|
90
|
+
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
|
91
|
+
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
|
|
92
|
+
"license": "MIT"
|
|
93
|
+
},
|
|
94
|
+
"node_modules/restructure": {
|
|
95
|
+
"version": "3.0.2",
|
|
96
|
+
"resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
|
|
97
|
+
"integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
|
|
98
|
+
"license": "MIT"
|
|
99
|
+
},
|
|
100
|
+
"node_modules/tiny-inflate": {
|
|
101
|
+
"version": "1.0.3",
|
|
102
|
+
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
|
103
|
+
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
|
104
|
+
"license": "MIT"
|
|
105
|
+
},
|
|
106
|
+
"node_modules/tslib": {
|
|
107
|
+
"version": "2.8.1",
|
|
108
|
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
|
109
|
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
|
110
|
+
"license": "0BSD"
|
|
111
|
+
},
|
|
112
|
+
"node_modules/unicode-properties": {
|
|
113
|
+
"version": "1.4.1",
|
|
114
|
+
"resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
|
|
115
|
+
"integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
|
|
116
|
+
"license": "MIT",
|
|
117
|
+
"dependencies": {
|
|
118
|
+
"base64-js": "^1.3.0",
|
|
119
|
+
"unicode-trie": "^2.0.0"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"node_modules/unicode-trie": {
|
|
123
|
+
"version": "2.0.0",
|
|
124
|
+
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
|
125
|
+
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
|
126
|
+
"license": "MIT",
|
|
127
|
+
"dependencies": {
|
|
128
|
+
"pako": "^0.2.5",
|
|
129
|
+
"tiny-inflate": "^1.0.0"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function checkedPath(input) {
|
|
5
|
+
const absolute = resolve(input);
|
|
6
|
+
let current = parse(absolute).root;
|
|
7
|
+
for (const segment of absolute.slice(current.length).split(sep).filter(Boolean)) {
|
|
8
|
+
current = join(current, segment);
|
|
9
|
+
if (lstatSync(current).isSymbolicLink()) throw new Error("SYMLINK_REFUSED");
|
|
10
|
+
}
|
|
11
|
+
return absolute;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function inside(root, input) {
|
|
15
|
+
if (typeof input !== "string" || !input || isAbsolute(input) || /[\\\x00-\x1f]/u.test(input) || input.split("/").some(part => part === ".." || part === "." || !part)) throw new Error("UNSAFE_RELATIVE_PATH");
|
|
16
|
+
const base = checkedPath(root);
|
|
17
|
+
if (!lstatSync(base).isDirectory()) throw new Error("ROOT_NOT_DIRECTORY");
|
|
18
|
+
const target = resolve(base, input);
|
|
19
|
+
const rel = relative(base, target);
|
|
20
|
+
if (!rel || rel.startsWith(`..${sep}`) || isAbsolute(rel)) throw new Error("PATH_ESCAPE");
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function boundedRead(input, limit) {
|
|
25
|
+
const file = checkedPath(input);
|
|
26
|
+
const before = lstatSync(file);
|
|
27
|
+
if (!before.isFile() || before.size < 1 || before.size > limit) throw new Error("BOUNDED_REGULAR_FILE_REQUIRED");
|
|
28
|
+
const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
29
|
+
try {
|
|
30
|
+
const start = fstatSync(fd);
|
|
31
|
+
if (!start.isFile() || start.ino !== before.ino || start.dev !== before.dev) throw new Error("FILE_IDENTITY_CHANGED");
|
|
32
|
+
const data = readFileSync(fd);
|
|
33
|
+
const after = fstatSync(fd);
|
|
34
|
+
if (data.length > limit || after.size !== start.size || after.mtimeMs !== start.mtimeMs || after.ctimeMs !== start.ctimeMs) throw new Error("FILE_CHANGED");
|
|
35
|
+
return data;
|
|
36
|
+
} finally { closeSync(fd); }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function exclusiveWrite(root, output, data) {
|
|
40
|
+
const target = inside(root, output);
|
|
41
|
+
const parent = checkedPath(dirname(target));
|
|
42
|
+
if (!lstatSync(parent).isDirectory() || realpathSync(parent) !== parent) throw new Error("UNSAFE_PARENT");
|
|
43
|
+
// The caller owns the output tree; exclusive creation also refuses existing symlink leaves.
|
|
44
|
+
const fd = openSync(target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o644);
|
|
45
|
+
try { writeFileSync(fd, data); } finally { closeSync(fd); }
|
|
46
|
+
return target;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function argumentsMap(argv, allowed) {
|
|
50
|
+
if (argv.length % 2) throw new Error("EXPECTED_FLAG_VALUE_PAIRS");
|
|
51
|
+
const result = new Map();
|
|
52
|
+
for (let i = 0; i < argv.length; i += 2) {
|
|
53
|
+
if (!allowed.includes(argv[i]) || result.has(argv[i])) throw new Error("UNKNOWN_OR_DUPLICATE_FLAG");
|
|
54
|
+
result.set(argv[i], argv[i + 1]);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { create } from "fontkit";
|
|
4
|
+
import { argumentsMap, boundedRead, exclusiveWrite } from "./safe-files.mjs";
|
|
5
|
+
|
|
6
|
+
const xml = value => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
7
|
+
try {
|
|
8
|
+
const args = argumentsMap(process.argv.slice(2), ["--font", "--family", "--text", "--output-root", "--output", "--font-size", "--fill", "--tracking"]);
|
|
9
|
+
for (const name of ["--font", "--family", "--text", "--output-root", "--output"]) if (!args.get(name)) throw new Error(`REQUIRED ${name}`);
|
|
10
|
+
const text = args.get("--text");
|
|
11
|
+
if (text.length > 4096 || /[\x00-\x1f\x7f]/u.test(text)) throw new Error("INVALID_TEXT_RUN");
|
|
12
|
+
const size = Number(args.get("--font-size") ?? 84);
|
|
13
|
+
const tracking = Number(args.get("--tracking") ?? 0);
|
|
14
|
+
const fill = args.get("--fill") ?? "#18272b";
|
|
15
|
+
if (!Number.isFinite(size) || size < 1 || size > 1000 || !Number.isFinite(tracking) || Math.abs(tracking) > 100 || !/^#[0-9a-f]{6}$/iu.test(fill)) throw new Error("INVALID_STYLE");
|
|
16
|
+
const bytes = boundedRead(args.get("--font"), 64 * 1024 * 1024);
|
|
17
|
+
const font = create(bytes);
|
|
18
|
+
if (typeof font.familyName !== "string" || !font.familyName.toLowerCase().includes(args.get("--family").toLowerCase())) throw new Error("FONT_IDENTITY_MISMATCH");
|
|
19
|
+
const run = font.layout(text);
|
|
20
|
+
if (!run.glyphs.length || run.glyphs.some(glyph => glyph.id === 0)) throw new Error("FONT_GLYPH_MISSING");
|
|
21
|
+
const scale = size / font.unitsPerEm;
|
|
22
|
+
if (!Number.isFinite(scale) || scale <= 0) throw new Error("INVALID_FONT_METRICS");
|
|
23
|
+
let x = 0, y = 0;
|
|
24
|
+
let minX = 0, maxX = 0, minY = -font.ascent * scale, maxY = -font.descent * scale;
|
|
25
|
+
const paths = [];
|
|
26
|
+
for (let i = 0; i < run.glyphs.length; i++) {
|
|
27
|
+
const glyph = run.glyphs[i], pos = run.positions[i];
|
|
28
|
+
if (![pos.xAdvance, pos.yAdvance, pos.xOffset, pos.yOffset].every(Number.isFinite)) throw new Error("INVALID_GLYPH_POSITION");
|
|
29
|
+
const gx = x + pos.xOffset * scale, gy = y - pos.yOffset * scale;
|
|
30
|
+
const outline = glyph.path.toSVG();
|
|
31
|
+
if (outline) {
|
|
32
|
+
const box = glyph.bbox;
|
|
33
|
+
if (![box.minX, box.minY, box.maxX, box.maxY].every(Number.isFinite)) throw new Error("INVALID_OUTLINE_BOUNDS");
|
|
34
|
+
minX = Math.min(minX, gx + box.minX * scale); maxX = Math.max(maxX, gx + box.maxX * scale);
|
|
35
|
+
minY = Math.min(minY, gy - box.maxY * scale); maxY = Math.max(maxY, gy - box.minY * scale);
|
|
36
|
+
paths.push(`<path d="${xml(outline)}" transform="translate(${gx} ${gy}) scale(${scale} ${-scale})"/>`);
|
|
37
|
+
}
|
|
38
|
+
x += pos.xAdvance * scale + (i < run.glyphs.length - 1 ? tracking : 0);
|
|
39
|
+
y -= pos.yAdvance * scale;
|
|
40
|
+
minX = Math.min(minX, x); maxX = Math.max(maxX, x);
|
|
41
|
+
}
|
|
42
|
+
const pad = Math.max(2, size * 0.04), width = maxX - minX + 2 * pad, height = maxY - minY + 2 * pad;
|
|
43
|
+
if (!paths.length || ![minX, minY, width, height].every(Number.isFinite) || width <= 0 || height <= 0) throw new Error("EMPTY_OR_INVALID_GEOMETRY");
|
|
44
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="${minX-pad} ${minY-pad} ${width} ${height}" fill="${fill}"><title>${xml(text)}</title>${paths.join("")}</svg>\n`;
|
|
45
|
+
const output = exclusiveWrite(args.get("--output-root"), args.get("--output"), svg);
|
|
46
|
+
console.log(JSON.stringify({ output, text, family: font.familyName, style: font.subfamilyName, version: font.version, font_sha256: createHash("sha256").update(bytes).digest("hex"), glyphs: run.glyphs.length, paths: paths.length, width, height, fill, license: "caller-must-verify-and-retain-notice" }));
|
|
47
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
@@ -25,6 +25,7 @@ import { tmpdir } from "node:os";
|
|
|
25
25
|
import { join, resolve } from "node:path";
|
|
26
26
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
27
27
|
import { deflateSync } from "node:zlib";
|
|
28
|
+
import { stripAnsi } from "./strip-ansi.mjs";
|
|
28
29
|
|
|
29
30
|
export const repoRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
30
31
|
|
|
@@ -606,8 +607,8 @@ function phaseCorrect(phase, run) {
|
|
|
606
607
|
if (phase.id === "session_start") return context.startsWith("LitClaude rules loaded for ");
|
|
607
608
|
if (phase.id === "s1_activation") {
|
|
608
609
|
return /LITWORK MODE ENABLED/u.test(context)
|
|
609
|
-
&&
|
|
610
|
-
&&
|
|
610
|
+
&& /🔥 LIT IGNITED · lit-loop 🔥/u.test(stripAnsi(run.parsed.systemMessage ?? ""))
|
|
611
|
+
&& /🔥 \*\*LIT IGNITED · lit-loop\*\* 🔥/u.test(context);
|
|
611
612
|
}
|
|
612
613
|
return run.parsed.systemMessage === undefined
|
|
613
614
|
&& context === "LitClaude prompt hook checked: no workflow activation.";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { stripVTControlCharacters as stripAnsi } from "node:util";
|
|
@@ -7,14 +7,14 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
8
|
const AGENTS_ROOT = join(ROOT, 'plugins', 'litclaude', 'agents');
|
|
9
9
|
const ROUTE_FIELDS = new Set(['route', 'model', 'effort', 'reasoning_effort']);
|
|
10
|
-
const MODEL_IDS = new Set(['gpt-5.6-sol', 'gpt-5.6-luna']);
|
|
10
|
+
const MODEL_IDS = new Set(['gpt-5.6-sol', 'gpt-5.6-luna', 'gpt-6-astra', 'gpt-6-sol', 'gpt-6-luna']);
|
|
11
11
|
const EFFORTS = new Set(['max', 'xhigh']);
|
|
12
12
|
|
|
13
13
|
export const APPROVED_MODEL_ROUTES = Object.freeze({
|
|
14
|
-
lead: Object.freeze({ model: 'gpt-
|
|
15
|
-
'ordinary-worker': Object.freeze({ model: 'gpt-
|
|
16
|
-
momus: Object.freeze({ model: 'gpt-
|
|
17
|
-
'litwork-reviewer': Object.freeze({ model: 'gpt-
|
|
14
|
+
lead: Object.freeze({ model: 'gpt-6-astra', effort: 'xhigh' }),
|
|
15
|
+
'ordinary-worker': Object.freeze({ model: 'gpt-6-luna', effort: 'max' }),
|
|
16
|
+
momus: Object.freeze({ model: 'gpt-6-astra', effort: 'xhigh' }),
|
|
17
|
+
'litwork-reviewer': Object.freeze({ model: 'gpt-6-astra', effort: 'xhigh' }),
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
export const EXPECTED_AGENT_PERMISSIONS = Object.freeze({
|
|
@@ -76,7 +76,7 @@ export function evaluateRequestedRoute(routeData) {
|
|
|
76
76
|
return blocked('UNKNOWN_EFFORT', 'effort is not in the approved G20 table');
|
|
77
77
|
}
|
|
78
78
|
if (routeData.model === 'gpt-5.6-luna' && effort === 'xhigh') {
|
|
79
|
-
return blocked('FORBIDDEN_MODEL_EFFORT', 'gpt-5.6-luna plus xhigh
|
|
79
|
+
return blocked('FORBIDDEN_MODEL_EFFORT', 'legacy policy blocks gpt-5.6-luna plus xhigh');
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
const expected = APPROVED_MODEL_ROUTES[routeData.route];
|