claude-multi 0.4.4 â 0.5.5
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/skills/three/SKILL.md +106 -0
- package/.agents/skills/threejs-animation/SKILL.md +552 -0
- package/.agents/skills/threejs-fundamentals/SKILL.md +488 -0
- package/.agents/skills/threejs-geometry/SKILL.md +548 -0
- package/.agents/skills/threejs-interaction/SKILL.md +660 -0
- package/.agents/skills/threejs-lighting/SKILL.md +481 -0
- package/.agents/skills/threejs-loaders/SKILL.md +623 -0
- package/.agents/skills/threejs-materials/SKILL.md +520 -0
- package/.agents/skills/threejs-postprocessing/SKILL.md +602 -0
- package/.agents/skills/threejs-shaders/SKILL.md +642 -0
- package/.agents/skills/threejs-textures/SKILL.md +628 -0
- package/.commandcode/taste/taste.md +9 -0
- package/CHANGELOG.md +121 -0
- package/README.md +62 -179
- package/astro.config.mjs +69 -0
- package/bun.lock +1349 -0
- package/dist/cli.js +52530 -4022
- package/package.json +27 -8
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon-animated.svg +140 -0
- package/public/favicon.json +1 -0
- package/public/favicon.svg +14 -0
- package/public/logo.svg +14 -0
- package/public/site.webmanifest +11 -0
- package/screenshots-review/01-main-menu.png +0 -0
- package/screenshots-review/02-list-instances.png +0 -0
- package/screenshots-review/07-mcp-details.png +0 -0
- package/screenshots-review/v2-01-main-menu.png +0 -0
- package/scripts/capture-screens.tsx +440 -0
- package/scripts/gen-lottie.cjs +160 -0
- package/scripts/shoot-changelog.mjs +18 -0
- package/skills-lock.json +71 -0
- package/ROADMAP.md +0 -104
- package/development.md +0 -602
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { renderToString, Box, Text } from "ink";
|
|
3
|
+
import { Header } from "../src/ink/components/Header.js";
|
|
4
|
+
import { Footer } from "../src/ink/components/Footer.js";
|
|
5
|
+
import { WarningBanner } from "../src/ink/components/WarningBanner.js";
|
|
6
|
+
import { InstanceCard } from "../src/ink/components/InstanceCard.js";
|
|
7
|
+
import { StatusBar } from "../src/ink/components/StatusBar.js";
|
|
8
|
+
import { StepIndicator } from "../src/ink/components/StepIndicator.js";
|
|
9
|
+
import { writeFileSync, mkdirSync } from "node:fs";
|
|
10
|
+
|
|
11
|
+
// Ensure static renders (skip animations)
|
|
12
|
+
process.env.CAPTURE_MODE = "1";
|
|
13
|
+
|
|
14
|
+
const SCREENSHOTS_DIR = "./screenshots";
|
|
15
|
+
mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
|
16
|
+
|
|
17
|
+
const testInstances = [
|
|
18
|
+
{ name: "glm", configDir: "/Users/hmziq/.claude-glm", binaryPath: "/Users/hmziq/.local/bin/claude-glm", createdAt: "2025-01-01T00:00:00.000Z", autoSync: true },
|
|
19
|
+
{ name: "mm", configDir: "/Users/hmziq/.claude-mm", binaryPath: "/Users/hmziq/.local/bin/claude-mm", createdAt: "2025-02-15T00:00:00.000Z", autoSync: true },
|
|
20
|
+
{ name: "g2", configDir: "/Users/hmziq/.claude-g2", binaryPath: "/Users/hmziq/.local/bin/claude-g2", createdAt: "2025-06-01T00:00:00.000Z", autoSync: false },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const COLS = 120;
|
|
24
|
+
|
|
25
|
+
const screens: Record<string, React.ReactElement> = {
|
|
26
|
+
"01-main-menu": (
|
|
27
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
28
|
+
<Header title="đ¤ Claude Multi" subtitle="Interactive Mode" />
|
|
29
|
+
<Box marginBottom={1}>
|
|
30
|
+
<Text dimColor>3 instance(s): glm, mm, g2</Text>
|
|
31
|
+
</Box>
|
|
32
|
+
<Text>What would you like to do?</Text>
|
|
33
|
+
<Box marginTop={1}>
|
|
34
|
+
<Text color="green">⯠</Text>
|
|
35
|
+
<Text>â Add new instance</Text>
|
|
36
|
+
</Box>
|
|
37
|
+
<Box><Text> </Text><Text>đ List all instances</Text></Box>
|
|
38
|
+
<Box><Text> </Text><Text>âšī¸ Instance details</Text></Box>
|
|
39
|
+
<Box><Text> </Text><Text>đ Manage plugins</Text></Box>
|
|
40
|
+
<Box><Text> </Text><Text>đ Toggle auto-sync</Text></Box>
|
|
41
|
+
<Box><Text> </Text><Text>đ Re-sync symlinks</Text></Box>
|
|
42
|
+
<Box><Text> </Text><Text>đī¸ Remove instance</Text></Box>
|
|
43
|
+
<Box><Text> </Text><Text>âī¸ MCP servers</Text></Box>
|
|
44
|
+
<Box><Text> </Text><Text>đĒ Exit</Text></Box>
|
|
45
|
+
<Footer />
|
|
46
|
+
</Box>
|
|
47
|
+
),
|
|
48
|
+
|
|
49
|
+
"01b-main-menu-warnings": (
|
|
50
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
51
|
+
<Header title="đ¤ Claude Multi" subtitle="Interactive Mode" />
|
|
52
|
+
<WarningBanner issueCount={2} errorCount={1} warningCount={1} />
|
|
53
|
+
<Box marginBottom={1}>
|
|
54
|
+
<Text dimColor>3 instance(s): glm, mm, g2</Text>
|
|
55
|
+
</Box>
|
|
56
|
+
<Text>What would you like to do?</Text>
|
|
57
|
+
<Box marginTop={1}>
|
|
58
|
+
<Text color="green">⯠</Text>
|
|
59
|
+
<Text>â Add new instance</Text>
|
|
60
|
+
</Box>
|
|
61
|
+
<Box><Text> </Text><Text>đ List all instances</Text></Box>
|
|
62
|
+
<Box><Text> </Text><Text>đĒ Exit</Text></Box>
|
|
63
|
+
<Footer showHealthHint={true} />
|
|
64
|
+
</Box>
|
|
65
|
+
),
|
|
66
|
+
|
|
67
|
+
"02-list-instances": (
|
|
68
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
69
|
+
<Header title="đ All Instances" />
|
|
70
|
+
<Text bold>Found 3 instance(s):</Text>
|
|
71
|
+
<Box flexDirection="column">
|
|
72
|
+
<InstanceCard instance={testInstances[0]} />
|
|
73
|
+
<InstanceCard instance={testInstances[1]} />
|
|
74
|
+
<InstanceCard instance={testInstances[2]} />
|
|
75
|
+
</Box>
|
|
76
|
+
<Box marginTop={1}>
|
|
77
|
+
<Text dimColor>ESC to go back</Text>
|
|
78
|
+
</Box>
|
|
79
|
+
</Box>
|
|
80
|
+
),
|
|
81
|
+
|
|
82
|
+
"03-instance-detail": (
|
|
83
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
84
|
+
<Header title="âšī¸ Instance Details" />
|
|
85
|
+
<Box flexDirection="column" gap={0}>
|
|
86
|
+
<Box gap={1}>
|
|
87
|
+
<Text color="cyan">â</Text>
|
|
88
|
+
<Text bold color="cyan">glm</Text>
|
|
89
|
+
</Box>
|
|
90
|
+
<Box marginLeft={2} flexDirection="column">
|
|
91
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Binary:</Text><Text>/Users/hmziq/.local/bin/claude-glm</Text></Box>
|
|
92
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Config:</Text><Text>/Users/hmziq/.claude-glm</Text></Box>
|
|
93
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Created:</Text><Text>1/1/2025, 12:00:00 AM</Text></Box>
|
|
94
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Auto-sync:</Text><Text color="green">â Enabled</Text></Box>
|
|
95
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Plugins:</Text><Text>12 installed, 8 enabled</Text></Box>
|
|
96
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>MCP Servers:</Text><Text>6 total (5 from plugins, 1 custom)</Text></Box>
|
|
97
|
+
</Box>
|
|
98
|
+
</Box>
|
|
99
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
100
|
+
</Box>
|
|
101
|
+
),
|
|
102
|
+
|
|
103
|
+
"04-add-instance-name": (
|
|
104
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
105
|
+
<Header title="â Add New Instance" />
|
|
106
|
+
<StepIndicator current={1} total={3} label="Instance Name" />
|
|
107
|
+
<Box flexDirection="column" gap={1}>
|
|
108
|
+
<Text>Instance name:</Text>
|
|
109
|
+
<Text dimColor>Letters, numbers, hyphens, underscores only</Text>
|
|
110
|
+
<Text>my-instance_</Text>
|
|
111
|
+
</Box>
|
|
112
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
113
|
+
</Box>
|
|
114
|
+
),
|
|
115
|
+
|
|
116
|
+
"04-add-instance-provider": (
|
|
117
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
118
|
+
<Header title="â Add New Instance" />
|
|
119
|
+
<StepIndicator current={2} total={3} label="Provider Template" />
|
|
120
|
+
<Box flexDirection="column" gap={1}>
|
|
121
|
+
<Text>Select a provider:</Text>
|
|
122
|
+
<Box>
|
|
123
|
+
<Text color="green">⯠</Text><Text>GLM (æēč°ąAI) â GLM-5.1 via z.ai</Text>
|
|
124
|
+
</Box>
|
|
125
|
+
<Box><Text> </Text><Text>MiniMax â MiniMax-M2.7 via minimax.io</Text></Box>
|
|
126
|
+
<Box><Text> </Text><Text>DeepSeek â DeepSeek-V4 via deepseek.com</Text></Box>
|
|
127
|
+
<Box><Text> </Text><Text dimColor>None / Custom</Text></Box>
|
|
128
|
+
</Box>
|
|
129
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
130
|
+
</Box>
|
|
131
|
+
),
|
|
132
|
+
|
|
133
|
+
"04c-add-copy-options": (
|
|
134
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
135
|
+
<Header title="â Add New Instance" />
|
|
136
|
+
<StepIndicator current={3} total={3} label="Copy Options" />
|
|
137
|
+
<Box flexDirection="column" gap={1}>
|
|
138
|
+
<Text>Found existing Claude config at ~/.claude</Text>
|
|
139
|
+
<Text>What to copy?</Text>
|
|
140
|
+
<Box>
|
|
141
|
+
<Text color="green">⯠</Text><Text>Select plugins to install</Text>
|
|
142
|
+
</Box>
|
|
143
|
+
<Box><Text> </Text><Text>Nothing â start fresh</Text></Box>
|
|
144
|
+
<Box><Text> </Text><Text>Only settings.json</Text></Box>
|
|
145
|
+
<Box><Text> </Text><Text>All files (settings, CLAUDE.md, plugins, etc.)</Text></Box>
|
|
146
|
+
</Box>
|
|
147
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
148
|
+
</Box>
|
|
149
|
+
),
|
|
150
|
+
|
|
151
|
+
"04d-add-select-plugins": (
|
|
152
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
153
|
+
<Header title="â Add New Instance" />
|
|
154
|
+
<StepIndicator current={3} total={3} label="Select Plugins" />
|
|
155
|
+
<Box flexDirection="column" gap={1}>
|
|
156
|
+
<Text>Select plugins to install:</Text>
|
|
157
|
+
<Text dimColor>50 available ¡ space to toggle ¡ enter to confirm</Text>
|
|
158
|
+
<Box flexDirection="column">
|
|
159
|
+
<Box gap={1}>
|
|
160
|
+
<Text color="green">â¯</Text>
|
|
161
|
+
<Text color="green">â</Text>
|
|
162
|
+
<Text>context7 (MCP) [ext]</Text>
|
|
163
|
+
</Box>
|
|
164
|
+
<Box gap={1}>
|
|
165
|
+
<Text> </Text>
|
|
166
|
+
<Text dimColor>â¯</Text>
|
|
167
|
+
<Text>playwright (MCP) [ext]</Text>
|
|
168
|
+
</Box>
|
|
169
|
+
<Box gap={1}>
|
|
170
|
+
<Text> </Text>
|
|
171
|
+
<Text color="green">â</Text>
|
|
172
|
+
<Text>github (MCP) [ext]</Text>
|
|
173
|
+
</Box>
|
|
174
|
+
<Box gap={1}>
|
|
175
|
+
<Text> </Text>
|
|
176
|
+
<Text dimColor>â¯</Text>
|
|
177
|
+
<Text>discord (MCP) [ext]</Text>
|
|
178
|
+
</Box>
|
|
179
|
+
<Box gap={1}>
|
|
180
|
+
<Text> </Text>
|
|
181
|
+
<Text dimColor>â¯</Text>
|
|
182
|
+
<Text>feature-dev</Text>
|
|
183
|
+
</Box>
|
|
184
|
+
</Box>
|
|
185
|
+
</Box>
|
|
186
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
187
|
+
</Box>
|
|
188
|
+
),
|
|
189
|
+
|
|
190
|
+
"05-remove-confirm": (
|
|
191
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
192
|
+
<Header title="đī¸ Remove Instance" />
|
|
193
|
+
<Box flexDirection="column" gap={1}>
|
|
194
|
+
<Box gap={1}>
|
|
195
|
+
<Text bold color="red">â About to remove</Text>
|
|
196
|
+
<Text bold color="red">'g2'</Text>
|
|
197
|
+
</Box>
|
|
198
|
+
<Box marginLeft={2} flexDirection="column">
|
|
199
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Binary:</Text><Text dimColor>/Users/hmziq/.local/bin/claude-g2</Text></Box>
|
|
200
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Config:</Text><Text dimColor>/Users/hmziq/.claude-g2</Text></Box>
|
|
201
|
+
</Box>
|
|
202
|
+
<Text dimColor>Config directory will NOT be deleted automatically.</Text>
|
|
203
|
+
<Text>Confirm removal?</Text>
|
|
204
|
+
<Box>
|
|
205
|
+
<Text>y/N </Text>
|
|
206
|
+
<Text color="green">â¯</Text>
|
|
207
|
+
</Box>
|
|
208
|
+
</Box>
|
|
209
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
210
|
+
</Box>
|
|
211
|
+
),
|
|
212
|
+
|
|
213
|
+
"06-autosync-toggle": (
|
|
214
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
215
|
+
<Header title="đ Toggle Auto-Sync" />
|
|
216
|
+
<Box flexDirection="column" gap={1}>
|
|
217
|
+
<Box>
|
|
218
|
+
<Text>Auto-sync for <Text bold color="cyan">g2</Text> is currently <Text bold color="red">OFF</Text></Text>
|
|
219
|
+
</Box>
|
|
220
|
+
<Text>What would you like to do?</Text>
|
|
221
|
+
<Box marginTop={1}>
|
|
222
|
+
<Text color="green">⯠</Text><Text>Turn on (use symlinks)</Text>
|
|
223
|
+
</Box>
|
|
224
|
+
<Box><Text> </Text><Text>Cancel</Text></Box>
|
|
225
|
+
</Box>
|
|
226
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
227
|
+
</Box>
|
|
228
|
+
),
|
|
229
|
+
|
|
230
|
+
"07-mcp-details": (
|
|
231
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
232
|
+
<Header title="âī¸ Manage MCP Servers" />
|
|
233
|
+
<StatusBar message="3 MCP server(s) found" type="success" />
|
|
234
|
+
<Box marginLeft={2} flexDirection="column">
|
|
235
|
+
<Box gap={1}>
|
|
236
|
+
<Text bold color="cyan">context7</Text>
|
|
237
|
+
<Text color="green">â</Text>
|
|
238
|
+
<Text dimColor>[context7]</Text>
|
|
239
|
+
</Box>
|
|
240
|
+
<Box marginLeft={2} flexDirection="column">
|
|
241
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Type:</Text><Text>stdio</Text></Box>
|
|
242
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Command:</Text><Text>npx -y @upstash/context7-mcp</Text></Box>
|
|
243
|
+
</Box>
|
|
244
|
+
</Box>
|
|
245
|
+
<Box marginLeft={2} flexDirection="column" marginTop={1}>
|
|
246
|
+
<Box gap={1}>
|
|
247
|
+
<Text bold color="cyan">playwright</Text>
|
|
248
|
+
<Text color="green">â</Text>
|
|
249
|
+
<Text dimColor>[playwright]</Text>
|
|
250
|
+
</Box>
|
|
251
|
+
<Box marginLeft={2} flexDirection="column">
|
|
252
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Type:</Text><Text>stdio</Text></Box>
|
|
253
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Command:</Text><Text>npx @playwright/mcp</Text></Box>
|
|
254
|
+
</Box>
|
|
255
|
+
</Box>
|
|
256
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
257
|
+
</Box>
|
|
258
|
+
),
|
|
259
|
+
|
|
260
|
+
"07b-manage-plugins": (
|
|
261
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
262
|
+
<Header title="đ Manage Plugins" />
|
|
263
|
+
<Box flexDirection="column" gap={1}>
|
|
264
|
+
<Text>What would you like to do?</Text>
|
|
265
|
+
<Box>
|
|
266
|
+
<Text color="green">⯠</Text><Text>đĨ Install plugins from default</Text>
|
|
267
|
+
</Box>
|
|
268
|
+
<Box><Text> </Text><Text>đī¸ Remove installed plugins</Text></Box>
|
|
269
|
+
<Box><Text> </Text><Text>â
Enable plugins</Text></Box>
|
|
270
|
+
<Box><Text> </Text><Text>â Disable plugins</Text></Box>
|
|
271
|
+
<Box><Text> </Text><Text>đ List installed plugins</Text></Box>
|
|
272
|
+
<Box><Text> </Text><Text>Cancel</Text></Box>
|
|
273
|
+
</Box>
|
|
274
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
275
|
+
</Box>
|
|
276
|
+
),
|
|
277
|
+
|
|
278
|
+
"07c-plugin-list": (
|
|
279
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
280
|
+
<Header title="đ Manage Plugins" />
|
|
281
|
+
<Box flexDirection="column" gap={1}>
|
|
282
|
+
<Text bold>12 plugin(s) installed in 'glm':</Text>
|
|
283
|
+
<Box gap={1} marginLeft={2}>
|
|
284
|
+
<Text color="green">â</Text>
|
|
285
|
+
<Text bold>context7</Text>
|
|
286
|
+
<Text color="cyan" dimColor>(MCP)</Text>
|
|
287
|
+
<Text dimColor>[ext]</Text>
|
|
288
|
+
</Box>
|
|
289
|
+
<Box gap={1} marginLeft={2}>
|
|
290
|
+
<Text color="green">â</Text>
|
|
291
|
+
<Text bold>playwright</Text>
|
|
292
|
+
<Text color="cyan" dimColor>(MCP)</Text>
|
|
293
|
+
<Text dimColor>[ext]</Text>
|
|
294
|
+
</Box>
|
|
295
|
+
<Box gap={1} marginLeft={2}>
|
|
296
|
+
<Text color="green">â</Text>
|
|
297
|
+
<Text bold>feature-dev</Text>
|
|
298
|
+
</Box>
|
|
299
|
+
<Box gap={1} marginLeft={2}>
|
|
300
|
+
<Text color="red">â</Text>
|
|
301
|
+
<Text>github</Text>
|
|
302
|
+
<Text color="cyan" dimColor>(MCP)</Text>
|
|
303
|
+
<Text dimColor>[ext]</Text>
|
|
304
|
+
</Box>
|
|
305
|
+
<Box gap={1} marginLeft={2}>
|
|
306
|
+
<Text color="red">â</Text>
|
|
307
|
+
<Text>code-review</Text>
|
|
308
|
+
</Box>
|
|
309
|
+
</Box>
|
|
310
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
311
|
+
</Box>
|
|
312
|
+
),
|
|
313
|
+
|
|
314
|
+
"08-add-success": (
|
|
315
|
+
<Box flexDirection="column" width="100" paddingX={2} paddingY={1}>
|
|
316
|
+
<Header title="â Add New Instance" />
|
|
317
|
+
<StepIndicator current={3} total={3} label="Complete" />
|
|
318
|
+
<Box flexDirection="column" gap={1}>
|
|
319
|
+
<StatusBar message="Instance 'deepseek' created successfully!" type="success" />
|
|
320
|
+
<Box marginLeft={2} flexDirection="column">
|
|
321
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Binary:</Text><Text>/Users/hmziq/.local/bin/claude-deepseek</Text></Box>
|
|
322
|
+
<Box gap={1}><Text dimColor>ââ</Text><Text dimColor bold>Config:</Text><Text>/Users/hmziq/.claude-deepseek</Text></Box>
|
|
323
|
+
</Box>
|
|
324
|
+
</Box>
|
|
325
|
+
<Box marginTop={1}><Text dimColor>ESC back â q quit</Text></Box>
|
|
326
|
+
</Box>
|
|
327
|
+
),
|
|
328
|
+
|
|
329
|
+
"09-goodbye": (
|
|
330
|
+
<Box paddingX={2} paddingY={1}>
|
|
331
|
+
<Text dimColor>đ Goodbye!</Text>
|
|
332
|
+
</Box>
|
|
333
|
+
),
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// 256-color palette (Catppuccin Mocha theme)
|
|
337
|
+
const COLOR_256: string[] = new Array(256);
|
|
338
|
+
const palette16 = [
|
|
339
|
+
"#555","#e06c75","#98c379","#e5c07b","#61afef","#c678dd","#56b6c2","#abb2bf",
|
|
340
|
+
"#636d83","#e06c75","#98c379","#e5c07b","#61afef","#c678dd","#56b6c2","#cdd6f4",
|
|
341
|
+
];
|
|
342
|
+
for (let i = 0; i < 16; i++) COLOR_256[i] = palette16[i];
|
|
343
|
+
for (let i = 16; i < 232; i++) {
|
|
344
|
+
const c = i - 16;
|
|
345
|
+
const b = c % 36;
|
|
346
|
+
const g = Math.floor(c / 36) % 6;
|
|
347
|
+
const r = Math.floor(c / 216);
|
|
348
|
+
const toHex = (v: number) => v > 0 ? 55 + 40 * v : 0;
|
|
349
|
+
COLOR_256[i] = `rgb(${toHex(r)},${toHex(g)},${toHex(b)})`;
|
|
350
|
+
}
|
|
351
|
+
for (let i = 232; i < 256; i++) {
|
|
352
|
+
const v = 8 + 10 * (i - 232);
|
|
353
|
+
COLOR_256[i] = `rgb(${v},${v},${v})`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function ansiToHtml(text: string): string {
|
|
357
|
+
let html = text;
|
|
358
|
+
html = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
359
|
+
|
|
360
|
+
const fgMap: Record<string, string> = {
|
|
361
|
+
"30": "#555", "31": "#e06c75", "32": "#98c379", "33": "#e5c07b",
|
|
362
|
+
"34": "#61afef", "35": "#c678dd", "36": "#56b6c2", "37": "#abb2bf",
|
|
363
|
+
"90": "#636d83", "91": "#e06c75", "92": "#98c379", "93": "#e5c07b",
|
|
364
|
+
"94": "#61afef", "95": "#c678dd", "96": "#56b6c2", "97": "#cdd6f4",
|
|
365
|
+
};
|
|
366
|
+
const bgMap: Record<string, string> = {
|
|
367
|
+
"40": "#555", "41": "#e06c75", "42": "#98c379", "43": "#e5c07b",
|
|
368
|
+
"44": "#61afef", "45": "#c678dd", "46": "#56b6c2", "47": "#abb2bf",
|
|
369
|
+
"100": "#636d83", "101": "#e06c75", "102": "#98c379", "103": "#e5c07b",
|
|
370
|
+
"104": "#61afef", "105": "#c678dd", "106": "#56b6c2", "107": "#cdd6f4",
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
html = html.replace(/\x1b\[([0-9;]*)m/g, (_match, codes: string) => {
|
|
374
|
+
const parts = codes.split(";").map(Number);
|
|
375
|
+
const styles: string[] = [];
|
|
376
|
+
let i = 0;
|
|
377
|
+
|
|
378
|
+
while (i < parts.length) {
|
|
379
|
+
const code = parts[i];
|
|
380
|
+
if (code === 0) return "</span>";
|
|
381
|
+
if (code === 1) styles.push("font-weight:bold");
|
|
382
|
+
else if (code === 2) styles.push("opacity:0.6");
|
|
383
|
+
else if (code === 3) styles.push("font-style:italic");
|
|
384
|
+
else if (code === 4) styles.push("text-decoration:underline");
|
|
385
|
+
else if (code === 9) styles.push("text-decoration:line-through");
|
|
386
|
+
else if (code === 22) styles.push("font-weight:normal");
|
|
387
|
+
else if (code === 39) styles.push("color:inherit");
|
|
388
|
+
else if (code === 49) styles.push("background-color:inherit");
|
|
389
|
+
else if (code === 38 && parts[i + 1] === 5 && parts[i + 2] != null) {
|
|
390
|
+
styles.push(`color:${COLOR_256[parts[i + 2]] ?? "#cdd6f4"}`);
|
|
391
|
+
i += 2;
|
|
392
|
+
} else if (code === 38 && parts[i + 1] === 2 && parts[i + 4] != null) {
|
|
393
|
+
styles.push(`color:rgb(${parts[i + 2]},${parts[i + 3]},${parts[i + 4]})`);
|
|
394
|
+
i += 4;
|
|
395
|
+
} else if (code === 48 && parts[i + 1] === 5 && parts[i + 2] != null) {
|
|
396
|
+
styles.push(`background-color:${COLOR_256[parts[i + 2]] ?? "#1e1e2e"}`);
|
|
397
|
+
i += 2;
|
|
398
|
+
} else if (code === 48 && parts[i + 1] === 2 && parts[i + 4] != null) {
|
|
399
|
+
styles.push(`background-color:rgb(${parts[i + 2]},${parts[i + 3]},${parts[i + 4]})`);
|
|
400
|
+
i += 4;
|
|
401
|
+
} else if (fgMap[String(code)]) {
|
|
402
|
+
styles.push(`color:${fgMap[String(code)]}`);
|
|
403
|
+
} else if (bgMap[String(code)]) {
|
|
404
|
+
styles.push(`background-color:${bgMap[String(code)]}`);
|
|
405
|
+
}
|
|
406
|
+
i++;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return styles.length > 0 ? `<span style="${styles.join(";")}">` : "</span>";
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
html = html.replace(/\x1b\[[^m]*m/g, "");
|
|
413
|
+
html = html.replace(/\x1b\].*?\x07/g, "");
|
|
414
|
+
|
|
415
|
+
return html;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
for (const [name, component] of Object.entries(screens)) {
|
|
419
|
+
const ansiOutput = renderToString(component, { columns: COLS });
|
|
420
|
+
const htmlContent = ansiToHtml(ansiOutput);
|
|
421
|
+
|
|
422
|
+
const fullHtml = `<!DOCTYPE html>
|
|
423
|
+
<html><head><meta charset="utf-8"><title>${name}</title>
|
|
424
|
+
<style>
|
|
425
|
+
body { margin: 0; padding: 20px; background: #1e1e2e; display: flex; justify-content: center; }
|
|
426
|
+
.terminal {
|
|
427
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, monospace;
|
|
428
|
+
font-size: 14px; line-height: 1.5; color: #cdd6f4;
|
|
429
|
+
background: #1e1e2e; border: 2px solid #313244;
|
|
430
|
+
border-radius: 8px; padding: 16px; max-width: ${COLS}ch;
|
|
431
|
+
white-space: pre; box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
432
|
+
}
|
|
433
|
+
</style></head>
|
|
434
|
+
<body><div class="terminal">${htmlContent}</div></body></html>`;
|
|
435
|
+
|
|
436
|
+
writeFileSync(`${SCREENSHOTS_DIR}/${name}.html`, fullHtml);
|
|
437
|
+
console.log(`Captured: ${name}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
console.log(`\nAll screens captured to ${SCREENSHOTS_DIR}/`);
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
const W = 64, H = 64, FPS = 30, FRAMES = 90;
|
|
5
|
+
|
|
6
|
+
const hex = (h, a = 1) => [
|
|
7
|
+
parseInt(h.slice(1, 3), 16) / 255,
|
|
8
|
+
parseInt(h.slice(3, 5), 16) / 255,
|
|
9
|
+
parseInt(h.slice(5, 7), 16) / 255,
|
|
10
|
+
a,
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const stat = v => ({ a: 0, k: v });
|
|
14
|
+
|
|
15
|
+
function ease(start, end, t0, t1) {
|
|
16
|
+
const s = Array.isArray(start) ? [...start] : [start];
|
|
17
|
+
const e = Array.isArray(end) ? [...end] : [end];
|
|
18
|
+
return {
|
|
19
|
+
a: 1,
|
|
20
|
+
k: [
|
|
21
|
+
{
|
|
22
|
+
i: { x: s.map(() => 0.4), y: s.map(() => 1) },
|
|
23
|
+
o: { x: s.map(() => 0.6), y: s.map(() => 0) },
|
|
24
|
+
t: t0,
|
|
25
|
+
s,
|
|
26
|
+
e,
|
|
27
|
+
},
|
|
28
|
+
{ t: t1, s: e },
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function multiEase(keyframes) {
|
|
34
|
+
const k = keyframes.map((kf, i) => {
|
|
35
|
+
const s = Array.isArray(kf.s) ? [...kf.s] : [kf.s];
|
|
36
|
+
if (i === keyframes.length - 1) return { t: kf.t, s };
|
|
37
|
+
const e = Array.isArray(kf.e) ? [...kf.e] : [kf.e];
|
|
38
|
+
return {
|
|
39
|
+
i: { x: s.map(() => 0.4), y: s.map(() => 1) },
|
|
40
|
+
o: { x: s.map(() => 0.6), y: s.map(() => 0) },
|
|
41
|
+
t: kf.t,
|
|
42
|
+
s,
|
|
43
|
+
e,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
return { a: 1, k };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const rc = (cx, cy, w, h, r) => ({ ty: 'rc', d: 1, s: stat([w, h]), p: stat([cx, cy]), r: stat(r) });
|
|
50
|
+
|
|
51
|
+
const fl = (color, opacity = 100) => ({ ty: 'fl', c: stat(hex(color)), o: stat(opacity), r: 1, bm: 0 });
|
|
52
|
+
|
|
53
|
+
const st = (color, w, opacity = 100) => ({ ty: 'st', c: stat(hex(color)), o: stat(opacity), w: stat(w), lc: 2, lj: 2, bm: 0 });
|
|
54
|
+
|
|
55
|
+
const gf = (sx, sy, ex, ey, stops) => ({
|
|
56
|
+
ty: 'gf', o: stat(100), t: 1, bm: 0,
|
|
57
|
+
g: { p: stops.length, k: stat(stops.flatMap(([o, r, g, b]) => [o, r, g, b])) },
|
|
58
|
+
s: stat([sx, sy]),
|
|
59
|
+
e: stat([ex, ey]),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const sh = (verts) => ({
|
|
63
|
+
ty: 'sh', d: 1,
|
|
64
|
+
ks: stat({
|
|
65
|
+
i: verts.map(() => [0, 0]),
|
|
66
|
+
o: verts.map(() => [0, 0]),
|
|
67
|
+
v: verts,
|
|
68
|
+
c: false,
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
function grp(items, pos, opacity) {
|
|
73
|
+
return {
|
|
74
|
+
ty: 'gr',
|
|
75
|
+
it: [
|
|
76
|
+
...items,
|
|
77
|
+
{
|
|
78
|
+
ty: 'tr',
|
|
79
|
+
p: pos || stat([0, 0]),
|
|
80
|
+
a: stat([0, 0]),
|
|
81
|
+
s: stat([100, 100]),
|
|
82
|
+
r: stat(0),
|
|
83
|
+
o: opacity || stat(100),
|
|
84
|
+
sk: stat(0),
|
|
85
|
+
sa: stat(0),
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const GS = [0.851, 0.976, 0.616];
|
|
92
|
+
const GE = [0.396, 0.639, 0.051];
|
|
93
|
+
|
|
94
|
+
const lottie = {
|
|
95
|
+
v: '5.7.4',
|
|
96
|
+
fr: FPS,
|
|
97
|
+
ip: 0,
|
|
98
|
+
op: FRAMES,
|
|
99
|
+
w: W,
|
|
100
|
+
h: H,
|
|
101
|
+
nm: 'claude-multi',
|
|
102
|
+
ddd: 0,
|
|
103
|
+
assets: [],
|
|
104
|
+
layers: [
|
|
105
|
+
{
|
|
106
|
+
ddd: 0, ind: 0, ty: 4, nm: 'Icon',
|
|
107
|
+
sr: 1,
|
|
108
|
+
ks: {
|
|
109
|
+
o: stat(100),
|
|
110
|
+
r: stat(0),
|
|
111
|
+
p: stat([32, 32, 0]),
|
|
112
|
+
a: stat([32, 32, 0]),
|
|
113
|
+
s: multiEase([
|
|
114
|
+
{ t: 0, s: [100, 100, 100], e: [100, 100, 100] },
|
|
115
|
+
{ t: 36, s: [100, 100, 100], e: [104, 104, 100] },
|
|
116
|
+
{ t: 54, s: [104, 104, 100], e: [100, 100, 100] },
|
|
117
|
+
{ t: 72, s: [100, 100, 100], e: [104, 104, 100] },
|
|
118
|
+
{ t: 90, s: [104, 104, 100] },
|
|
119
|
+
]),
|
|
120
|
+
},
|
|
121
|
+
ao: 0,
|
|
122
|
+
shapes: [
|
|
123
|
+
grp(
|
|
124
|
+
[rc(28, 36, 36, 28, 5), fl('#0c0c0f'), st('#6dd9a1', 1, 60)],
|
|
125
|
+
ease([-20, 8], [0, 0], 0, 12),
|
|
126
|
+
ease(0, 100, 0, 12),
|
|
127
|
+
),
|
|
128
|
+
grp(
|
|
129
|
+
[rc(32, 32, 36, 28, 5), fl('#0c0c0f'), st('#c79bff', 1, 70)],
|
|
130
|
+
ease([-20, 8], [0, 0], 5, 17),
|
|
131
|
+
ease(0, 100, 5, 17),
|
|
132
|
+
),
|
|
133
|
+
grp(
|
|
134
|
+
[rc(36, 28, 36, 28, 5), fl('#0c0c0f'), st('#7aa7ff', 1, 80)],
|
|
135
|
+
ease([-20, 8], [0, 0], 10, 22),
|
|
136
|
+
ease(0, 100, 10, 22),
|
|
137
|
+
),
|
|
138
|
+
grp(
|
|
139
|
+
[rc(34, 27, 28, 22, 5), gf(20, 16, 48, 38, [[0, ...GS], [1, ...GE]])],
|
|
140
|
+
ease([-5, 5], [0, 0], 18, 28),
|
|
141
|
+
ease(0, 100, 18, 28),
|
|
142
|
+
),
|
|
143
|
+
grp(
|
|
144
|
+
[sh([[28, 22], [34, 27], [28, 32]]), st('#0c0c0f', 2.4)],
|
|
145
|
+
stat([0, 0]),
|
|
146
|
+
ease(0, 100, 24, 32),
|
|
147
|
+
),
|
|
148
|
+
grp(
|
|
149
|
+
[sh([[36, 32], [42, 32]]), st('#0c0c0f', 2.4)],
|
|
150
|
+
stat([0, 0]),
|
|
151
|
+
ease(0, 100, 28, 34),
|
|
152
|
+
),
|
|
153
|
+
],
|
|
154
|
+
ip: 0, op: FRAMES, st: 0, bm: 0,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
fs.writeFileSync('public/favicon.json', JSON.stringify(lottie));
|
|
160
|
+
console.log('Created public/favicon.json');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
|
|
3
|
+
const url = process.argv[2] || 'http://localhost:4323/changelog/';
|
|
4
|
+
const out = process.argv[3] || 'screenshots-review/changelog/before.png';
|
|
5
|
+
|
|
6
|
+
const browser = await chromium.launch();
|
|
7
|
+
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2 });
|
|
8
|
+
const page = await ctx.newPage();
|
|
9
|
+
await page.goto(url, { waitUntil: 'networkidle' });
|
|
10
|
+
await page.waitForTimeout(500);
|
|
11
|
+
// trigger any IntersectionObserver-based reveals by scrolling through the page
|
|
12
|
+
await page.evaluate(() => {
|
|
13
|
+
document.querySelectorAll('.reveal').forEach((el) => el.classList.add('visible'));
|
|
14
|
+
});
|
|
15
|
+
await page.waitForTimeout(500);
|
|
16
|
+
await page.screenshot({ path: out, fullPage: true });
|
|
17
|
+
await browser.close();
|
|
18
|
+
console.log('shot', url, '->', out);
|