silvery 0.3.0 → 0.4.1

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.
Files changed (120) hide show
  1. package/README.md +41 -145
  2. package/dist/chalk.js +3 -0
  3. package/dist/chalk.js.map +11 -0
  4. package/dist/index.js +340 -0
  5. package/dist/index.js.map +282 -0
  6. package/dist/ink.js +129 -0
  7. package/dist/ink.js.map +140 -0
  8. package/dist/runtime.js +394 -0
  9. package/dist/runtime.js.map +286 -0
  10. package/dist/theme.js +343 -0
  11. package/dist/theme.js.map +286 -0
  12. package/dist/ui/animation.js +3 -0
  13. package/dist/ui/animation.js.map +15 -0
  14. package/dist/ui/ansi.js +3 -0
  15. package/dist/ui/ansi.js.map +10 -0
  16. package/dist/ui/cli.js +8 -0
  17. package/dist/ui/cli.js.map +14 -0
  18. package/dist/ui/display.js +4 -0
  19. package/dist/ui/display.js.map +10 -0
  20. package/dist/ui/image.js +4 -0
  21. package/dist/ui/image.js.map +15 -0
  22. package/dist/ui/input.js +3 -0
  23. package/dist/ui/input.js.map +11 -0
  24. package/dist/ui/progress.js +8 -0
  25. package/dist/ui/progress.js.map +20 -0
  26. package/dist/ui/react.js +3 -0
  27. package/dist/ui/react.js.map +15 -0
  28. package/dist/ui/utils.js +3 -0
  29. package/dist/ui/utils.js.map +10 -0
  30. package/dist/ui/wrappers.js +14 -0
  31. package/dist/ui/wrappers.js.map +19 -0
  32. package/dist/ui.js +17 -0
  33. package/dist/ui.js.map +20 -0
  34. package/package.json +67 -15
  35. package/src/index.ts +67 -1
  36. package/src/runtime.ts +4 -0
  37. package/src/theme.ts +4 -0
  38. package/src/ui/animation.ts +2 -0
  39. package/src/ui/ansi.ts +2 -0
  40. package/src/ui/cli.ts +2 -0
  41. package/src/ui/display.ts +2 -0
  42. package/src/ui/image.ts +2 -0
  43. package/src/ui/input.ts +2 -0
  44. package/src/ui/progress.ts +2 -0
  45. package/src/ui/react.ts +2 -0
  46. package/src/ui/utils.ts +2 -0
  47. package/src/ui/wrappers.ts +2 -0
  48. package/src/ui.ts +4 -0
  49. package/examples/CLAUDE.md +0 -75
  50. package/examples/_banner.tsx +0 -60
  51. package/examples/cli.ts +0 -228
  52. package/examples/index.md +0 -101
  53. package/examples/inline/inline-nontty.tsx +0 -98
  54. package/examples/inline/inline-progress.tsx +0 -79
  55. package/examples/inline/inline-simple.tsx +0 -63
  56. package/examples/inline/scrollback.tsx +0 -185
  57. package/examples/interactive/_input-debug.tsx +0 -110
  58. package/examples/interactive/_stdin-test.ts +0 -71
  59. package/examples/interactive/_textarea-bare.tsx +0 -45
  60. package/examples/interactive/aichat/components.tsx +0 -468
  61. package/examples/interactive/aichat/index.tsx +0 -207
  62. package/examples/interactive/aichat/script.ts +0 -460
  63. package/examples/interactive/aichat/state.ts +0 -326
  64. package/examples/interactive/aichat/types.ts +0 -19
  65. package/examples/interactive/app-todo.tsx +0 -198
  66. package/examples/interactive/async-data.tsx +0 -208
  67. package/examples/interactive/cli-wizard.tsx +0 -332
  68. package/examples/interactive/clipboard.tsx +0 -183
  69. package/examples/interactive/components.tsx +0 -463
  70. package/examples/interactive/data-explorer.tsx +0 -506
  71. package/examples/interactive/dev-tools.tsx +0 -379
  72. package/examples/interactive/explorer.tsx +0 -747
  73. package/examples/interactive/gallery.tsx +0 -652
  74. package/examples/interactive/inline-bench.tsx +0 -136
  75. package/examples/interactive/kanban.tsx +0 -267
  76. package/examples/interactive/layout-ref.tsx +0 -185
  77. package/examples/interactive/outline.tsx +0 -171
  78. package/examples/interactive/paste-demo.tsx +0 -198
  79. package/examples/interactive/scroll.tsx +0 -77
  80. package/examples/interactive/search-filter.tsx +0 -240
  81. package/examples/interactive/task-list.tsx +0 -279
  82. package/examples/interactive/terminal.tsx +0 -798
  83. package/examples/interactive/textarea.tsx +0 -103
  84. package/examples/interactive/theme.tsx +0 -336
  85. package/examples/interactive/transform.tsx +0 -256
  86. package/examples/interactive/virtual-10k.tsx +0 -413
  87. package/examples/kitty/canvas.tsx +0 -519
  88. package/examples/kitty/generate-samples.ts +0 -236
  89. package/examples/kitty/image-component.tsx +0 -273
  90. package/examples/kitty/images.tsx +0 -604
  91. package/examples/kitty/input.tsx +0 -371
  92. package/examples/kitty/keys.tsx +0 -378
  93. package/examples/kitty/paint.tsx +0 -1017
  94. package/examples/layout/dashboard.tsx +0 -551
  95. package/examples/layout/live-resize.tsx +0 -290
  96. package/examples/layout/overflow.tsx +0 -51
  97. package/examples/playground/README.md +0 -69
  98. package/examples/playground/build.ts +0 -61
  99. package/examples/playground/index.html +0 -420
  100. package/examples/playground/playground-app.tsx +0 -416
  101. package/examples/runtime/elm-counter.tsx +0 -206
  102. package/examples/runtime/hello-runtime.tsx +0 -73
  103. package/examples/runtime/pipe-composition.tsx +0 -184
  104. package/examples/runtime/run-counter.tsx +0 -78
  105. package/examples/runtime/runtime-counter.tsx +0 -197
  106. package/examples/screenshots/generate.tsx +0 -563
  107. package/examples/scrollback-perf.tsx +0 -230
  108. package/examples/viewer.tsx +0 -654
  109. package/examples/web/build.ts +0 -365
  110. package/examples/web/canvas-app.tsx +0 -80
  111. package/examples/web/canvas.html +0 -89
  112. package/examples/web/dom-app.tsx +0 -81
  113. package/examples/web/dom.html +0 -113
  114. package/examples/web/showcase-app.tsx +0 -107
  115. package/examples/web/showcase.html +0 -34
  116. package/examples/web/showcases/index.tsx +0 -56
  117. package/examples/web/viewer-app.tsx +0 -555
  118. package/examples/web/viewer.html +0 -30
  119. package/examples/web/xterm-app.tsx +0 -105
  120. package/examples/web/xterm.html +0 -118
@@ -1,207 +0,0 @@
1
- /**
2
- * AI Chat — Coding Agent Demo
3
- *
4
- * Showcases ScrollbackList with streaming, tool calls, context tracking.
5
- * TEA state machine drives all animation; ScrollbackList freezes completed
6
- * exchanges into real terminal scrollback with colors, borders, and hyperlinks.
7
- *
8
- * Flags: --auto (auto-advance) --fast (skip animation) --stress (200 exchanges)
9
- */
10
-
11
- import React, { useEffect, useRef, useMemo } from "react"
12
- import { Box, Text, Spinner, ScrollbackList, useTea } from "silvery"
13
- import { run, useInput, useExit, type Key } from "@silvery/term/runtime"
14
- import type { ExampleMeta } from "../../_banner.js"
15
- import type { ScriptEntry } from "./types.js"
16
- import { SCRIPT, generateStressScript, CONTEXT_WINDOW } from "./script.js"
17
- import {
18
- INIT_STATE,
19
- createDemoUpdate,
20
- computeCumulativeTokens,
21
- getNextMessage,
22
- type DemoState,
23
- type DemoMsg,
24
- } from "./state.js"
25
- import { ExchangeItem, DemoFooter } from "./components.js"
26
- import type { FooterControl } from "./components.js"
27
-
28
- // Re-export for test consumers
29
- export { SCRIPT, generateStressScript, CONTEXT_WINDOW } from "./script.js"
30
- export type { ScriptEntry } from "./types.js"
31
- export type { Exchange, ToolCall } from "./types.js"
32
-
33
- export const meta: ExampleMeta = {
34
- name: "AI Coding Agent",
35
- description: "Coding agent showcase — ScrollbackList, streaming, context tracking",
36
- demo: true,
37
- features: ["ScrollbackList", "auto-freeze", "inline mode", "streaming", "OSC 8 links", "OSC 133 markers"],
38
- }
39
-
40
- // ============================================================================
41
- // AIChat — TEA state machine + ScrollbackList
42
- // ============================================================================
43
-
44
- export function AIChat({
45
- script,
46
- autoStart,
47
- fastMode,
48
- }: {
49
- script: ScriptEntry[]
50
- autoStart: boolean
51
- fastMode: boolean
52
- }): JSX.Element {
53
- const exit = useExit()
54
- const update = useMemo(() => createDemoUpdate(script, fastMode, autoStart), [script, fastMode, autoStart])
55
- const [state, send] = useTea(INIT_STATE, update)
56
- const footerControlRef = useRef<FooterControl>({ submit: () => {} })
57
-
58
- useEffect(() => send({ type: "mount" }), [send])
59
- useAutoCompact(state, send)
60
- useAutoExit(autoStart, state.done, exit)
61
- useKeyBindings(state, send, footerControlRef)
62
-
63
- return (
64
- <Box flexDirection="column" paddingX={1}>
65
- <ScrollbackList
66
- items={state.exchanges}
67
- keyExtractor={(ex) => ex.id}
68
- markers={true}
69
- footer={
70
- <DemoFooter
71
- controlRef={footerControlRef}
72
- onSubmit={(text) => send({ type: "submit", text })}
73
- streamPhase={state.streamPhase}
74
- done={state.done}
75
- compacting={state.compacting}
76
- exchanges={state.exchanges}
77
- contextBaseline={state.contextBaseline}
78
- ctrlDPending={state.ctrlDPending}
79
- nextMessage={getNextMessage(state, script, autoStart)}
80
- autoTypingText={state.autoTyping ? state.autoTyping.full.slice(0, state.autoTyping.revealed) : null}
81
- />
82
- }
83
- >
84
- {(exchange, index) => {
85
- const isLatest = index === state.exchanges.length - 1
86
- return (
87
- <Box flexDirection="column">
88
- {index > 0 && <Text> </Text>}
89
- {state.compacting && isLatest && <CompactingOverlay />}
90
- {state.done && autoStart && isLatest && <SessionComplete />}
91
- <ExchangeItem
92
- exchange={exchange}
93
- streamPhase={state.streamPhase}
94
- revealFraction={state.revealFraction}
95
- pulse={state.pulse}
96
- isLatest={isLatest}
97
- isFirstInGroup={exchange.role !== (index > 0 ? state.exchanges[index - 1]!.role : null)}
98
- isLastInGroup={
99
- exchange.role !== (index < state.exchanges.length - 1 ? state.exchanges[index + 1]!.role : null)
100
- }
101
- />
102
- </Box>
103
- )
104
- }}
105
- </ScrollbackList>
106
- </Box>
107
- )
108
- }
109
-
110
- // ============================================================================
111
- // Main
112
- // ============================================================================
113
-
114
- export async function main() {
115
- const args = process.argv.slice(2)
116
- const script = args.includes("--stress") ? generateStressScript() : SCRIPT
117
- const mode = args.includes("--fullscreen") ? "fullscreen" : "inline"
118
-
119
- using handle = await run(
120
- <AIChat script={script} autoStart={args.includes("--auto")} fastMode={args.includes("--fast")} />,
121
- { mode: mode as "inline" | "fullscreen", focusReporting: true },
122
- )
123
- await handle.waitUntilExit()
124
- }
125
-
126
- if (import.meta.main) {
127
- main().catch(console.error)
128
- }
129
-
130
- // ============================================================================
131
- // Hooks
132
- // ============================================================================
133
-
134
- function useAutoCompact(state: DemoState, send: (msg: DemoMsg) => void) {
135
- useEffect(() => {
136
- if (state.done || state.compacting) return
137
- const cumulative = computeCumulativeTokens(state.exchanges)
138
- const effective = Math.max(0, cumulative.currentContext - state.contextBaseline)
139
- if (effective >= CONTEXT_WINDOW * 0.95) send({ type: "compact" })
140
- }, [state.exchanges, state.done, state.compacting, state.contextBaseline, send])
141
- }
142
-
143
- function useAutoExit(autoStart: boolean, done: boolean, exit: () => void) {
144
- useEffect(() => {
145
- if (!autoStart || !done) return
146
- const timer = setTimeout(exit, 1000)
147
- return () => clearTimeout(timer)
148
- }, [autoStart, done, exit])
149
- }
150
-
151
- function useKeyBindings(
152
- state: DemoState,
153
- send: (msg: DemoMsg) => void,
154
- footerControlRef: React.RefObject<FooterControl>,
155
- ) {
156
- const lastCtrlDRef = useRef(0)
157
-
158
- useInput((input: string, key: Key) => {
159
- if (key.escape) return "exit"
160
- if (key.ctrl && input === "d") {
161
- const now = Date.now()
162
- if (now - lastCtrlDRef.current < 500) return "exit"
163
- lastCtrlDRef.current = now
164
- send({ type: "setCtrlDPending", pending: true })
165
- return
166
- }
167
- if (lastCtrlDRef.current > 0) {
168
- lastCtrlDRef.current = 0
169
- send({ type: "setCtrlDPending", pending: false })
170
- }
171
- if (key.tab) {
172
- if (state.done || state.compacting) return
173
- footerControlRef.current.submit()
174
- return
175
- }
176
- if (key.ctrl && input === "l") {
177
- send({ type: "compact" })
178
- }
179
- })
180
- }
181
-
182
- // ============================================================================
183
- // Inline UI fragments
184
- // ============================================================================
185
-
186
- function CompactingOverlay(): JSX.Element {
187
- return (
188
- <Box flexDirection="column" borderStyle="round" borderColor="$warning" paddingX={1} overflow="hidden">
189
- <Text color="$warning" bold>
190
- <Spinner type="arc" /> Compacting context
191
- </Text>
192
- <Text> </Text>
193
- <Text color="$muted">Freezing exchanges into terminal scrollback. Scroll up to review.</Text>
194
- </Box>
195
- )
196
- }
197
-
198
- function SessionComplete(): JSX.Element {
199
- return (
200
- <Box flexDirection="column" borderStyle="round" borderColor="$success" paddingX={1}>
201
- <Text color="$success" bold>
202
- {"✓"} Session complete
203
- </Text>
204
- <Text color="$muted">Scroll up to review — colors, borders, and hyperlinks preserved in scrollback.</Text>
205
- </Box>
206
- )
207
- }
@@ -1,460 +0,0 @@
1
- /**
2
- * Script data for the AI coding agent demo.
3
- *
4
- * Contains the realistic conversation script, random prompts for off-script
5
- * mode, and the stress test generator for performance testing.
6
- */
7
-
8
- import type { ScriptEntry } from "./types.js"
9
-
10
- // ============================================================================
11
- // Constants
12
- // ============================================================================
13
-
14
- export const MODEL_NAME = "claude-opus-4-6"
15
- export const INPUT_COST_PER_M = 15 // $/M input tokens
16
- export const OUTPUT_COST_PER_M = 75 // $/M output tokens
17
- export const CONTEXT_WINDOW = 200_000
18
-
19
- export const TOOL_COLORS: Record<string, string> = {
20
- Read: "$info",
21
- Edit: "$warning",
22
- Bash: "$error",
23
- Write: "$accent",
24
- Glob: "$muted",
25
- Grep: "$success",
26
- }
27
-
28
- export const TOOL_ICONS: Record<string, string> = {
29
- Read: "📖",
30
- Edit: "✏️",
31
- Bash: "⚡",
32
- Write: "📝",
33
- Glob: "🔍",
34
- Grep: "🔎",
35
- }
36
-
37
- /** Random user commands for Tab-to-inject feature. */
38
- export const RANDOM_USER_COMMANDS = [
39
- "Can you add unit tests for the auth module?",
40
- "Refactor the database queries to use prepared statements.",
41
- "Add TypeScript strict mode and fix any errors.",
42
- "Set up CI/CD with GitHub Actions.",
43
- "The search feature is slow — can you optimize it?",
44
- "Add dark mode support to the UI.",
45
- "We need input validation on the registration form.",
46
- "Create a migration script for the new schema.",
47
- "Add WebSocket support for real-time updates.",
48
- "The CSV export is broken — dates are wrong.",
49
- ]
50
-
51
- /** Random agent responses for Tab-injected turns. */
52
- export const RANDOM_AGENT_RESPONSES: ScriptEntry[] = [
53
- {
54
- role: "agent",
55
- thinking: "Let me analyze the codebase to understand the current structure.",
56
- content: "I'll look at the relevant files and make the changes.",
57
- toolCalls: [
58
- { tool: "Read", args: "src/index.ts", output: ["export function main() { /* ... */ }"] },
59
- { tool: "Edit", args: "src/index.ts", output: ["+ // Updated implementation"] },
60
- ],
61
- tokens: { input: 12400, output: 890 },
62
- },
63
- {
64
- role: "agent",
65
- content: "Done! I've made the changes and verified everything works.",
66
- tokens: { input: 15200, output: 340 },
67
- },
68
- ]
69
-
70
- /** Regex matching https/http URLs in output text. */
71
- export const URL_RE = /https?:\/\/[^\s)]+/g
72
-
73
- // ============================================================================
74
- // Script — Realistic coding agent story with thinking + tokens
75
- // ============================================================================
76
-
77
- export const SCRIPT: ScriptEntry[] = [
78
- {
79
- role: "user",
80
- content: "Fix the login bug in auth.ts — expired tokens throw instead of refreshing.",
81
- tokens: { input: 84, output: 0 },
82
- },
83
- {
84
- role: "agent",
85
- thinking:
86
- "The user reports expired tokens throw instead of refreshing. This is likely in the token validation flow. I should read auth.ts to see the current expiry check logic. The bug is probably comparing jwt.exp (seconds) with Date.now() (milliseconds).",
87
- content: "Let me look at the auth module.",
88
- toolCalls: [
89
- {
90
- tool: "Read",
91
- args: "src/auth.ts",
92
- output: [
93
- "export async function login(token: string) {",
94
- " const decoded = jwt.decode(token)",
95
- " if (decoded.exp < Date.now()) {",
96
- ' throw new Error("Token expired")',
97
- " }",
98
- " return decoded.user",
99
- "}",
100
- ],
101
- },
102
- ],
103
- tokens: { input: 3694, output: 624 },
104
- },
105
- {
106
- role: "agent",
107
- thinking:
108
- "Found it — decoded.exp is in seconds (Unix timestamp) but Date.now() returns milliseconds. Every token appears expired because exp (e.g. 1700000000) is always less than Date.now() (e.g. 1700000000000). I need to divide Date.now() by 1000, and change the throw to a refresh call.",
109
- content: "Found it. The expiry check compares seconds (jwt.exp) to milliseconds (Date.now()). Fixing now.",
110
- toolCalls: [
111
- {
112
- tool: "Edit",
113
- args: "src/auth.ts",
114
- output: [
115
- " const decoded = jwt.decode(token)",
116
- "- if (decoded.exp < Date.now()) {",
117
- '- throw new Error("Token expired")',
118
- "+ if (decoded.exp < Date.now() / 1000) {",
119
- "+ return refreshToken(token)",
120
- " }",
121
- ],
122
- },
123
- ],
124
- tokens: { input: 4312, output: 574 },
125
- },
126
- {
127
- role: "agent",
128
- content: "Running tests to verify the fix.",
129
- toolCalls: [
130
- {
131
- tool: "Bash",
132
- args: "bun test src/auth.test.ts",
133
- output: [
134
- "auth.test.ts",
135
- " ✓ login with valid token (2ms)",
136
- " ✓ login with expired token refreshes (3ms)",
137
- " ✓ login with invalid token rejects (1ms)",
138
- "",
139
- "3 passed | 0 failed",
140
- ],
141
- },
142
- ],
143
- tokens: { input: 4978, output: 312 },
144
- },
145
- {
146
- role: "agent",
147
- content:
148
- "Fixed! The bug was comparing jwt.exp (seconds since epoch) with Date.now() (milliseconds). Expired tokens now trigger a refresh instead of throwing.",
149
- tokens: { input: 5202, output: 178 },
150
- },
151
- {
152
- role: "user",
153
- content: "Nice. Can you also add rate limiting to the login endpoint?",
154
- tokens: { input: 116, output: 0 },
155
- },
156
- {
157
- role: "agent",
158
- thinking:
159
- "The user wants rate limiting on login. I should check if there's existing rate limiting infrastructure before building from scratch. Let me search for rate-limit patterns in the codebase.",
160
- content: "I'll check what rate limiting infrastructure exists.",
161
- toolCalls: [
162
- {
163
- tool: "Grep",
164
- args: "rateLimit|rate-limit",
165
- output: [
166
- "src/middleware/rate-limit.ts",
167
- "src/config.ts",
168
- "See https://docs.example.com/api/rate-limiting for API docs",
169
- ],
170
- },
171
- {
172
- tool: "Read",
173
- args: "src/middleware/rate-limit.ts",
174
- output: [
175
- "import { RateLimiter } from '@lib/rate-limit'",
176
- "",
177
- "export function createLimiter(opts: { window: number; max: number }) {",
178
- " return new RateLimiter(opts)",
179
- "}",
180
- ],
181
- },
182
- ],
183
- tokens: { input: 6490, output: 846 },
184
- },
185
- {
186
- role: "agent",
187
- thinking:
188
- "Good — there's already a RateLimiter abstraction. I just need to create an instance with appropriate settings (5 attempts per 60s window seems reasonable for login) and wire it into the auth route as middleware.",
189
- content: "Good, there's already a rate limiter. I'll wire it into the auth route.",
190
- toolCalls: [
191
- {
192
- tool: "Edit",
193
- args: "src/routes/auth.ts",
194
- output: [
195
- "+ import { createLimiter } from '../middleware/rate-limit'",
196
- "+",
197
- "+ const loginLimiter = createLimiter({ window: 60_000, max: 5 })",
198
- "+",
199
- " router.post('/login', async (req, res) => {",
200
- "+ const limited = await loginLimiter.check(req.ip)",
201
- "+ if (limited) return res.status(429).json({ error: 'Too many attempts' })",
202
- " const { token } = req.body",
203
- ],
204
- },
205
- ],
206
- tokens: { input: 7782, output: 756 },
207
- },
208
- {
209
- role: "agent",
210
- content: "Running the full test suite.",
211
- toolCalls: [
212
- {
213
- tool: "Bash",
214
- args: "bun test",
215
- output: [
216
- "auth.test.ts ........... 3 passed",
217
- "rate-limit.test.ts ..... 5 passed",
218
- "routes/auth.test.ts .... 7 passed",
219
- "",
220
- "15 passed | 0 failed | 3 files",
221
- ],
222
- },
223
- ],
224
- tokens: { input: 8204, output: 334 },
225
- },
226
- {
227
- role: "agent",
228
- content: "Rate limiting added: 5 attempts per minute per IP on the login endpoint. All 15 tests pass.",
229
- tokens: { input: 8468, output: 156 },
230
- },
231
- {
232
- role: "user",
233
- content: "Now add i18n support for error messages. We need 日本語 (Japanese) and Deutsch (German). 🌍",
234
- tokens: { input: 146, output: 0 },
235
- },
236
- {
237
- role: "agent",
238
- thinking:
239
- "i18n for error messages — I need to create translation JSON files for ja and de, then build a simple t() function that resolves message keys by locale. I'll also need to update the error responses in auth.ts to use t() with the request's locale.",
240
- content: "I'll create the translation files and update the error handling.",
241
- toolCalls: [
242
- {
243
- tool: "Write",
244
- args: "src/i18n/ja.json",
245
- output: [
246
- "{",
247
- ' "token_expired": "トークンの有効期限が切れました 🔧",',
248
- ' "rate_limited": "リクエストが多すぎます。後でもう一度お試しください",',
249
- ' "invalid_token": "無効なトークンです 🐛",',
250
- ' "login_success": "ログイン成功 ✅"',
251
- "}",
252
- ],
253
- },
254
- {
255
- tool: "Write",
256
- args: "src/i18n/de.json",
257
- output: [
258
- "{",
259
- ' "token_expired": "Token abgelaufen",',
260
- ' "rate_limited": "Zu viele Anfragen. Bitte versuchen Sie es später",',
261
- ' "invalid_token": "Ungültiges Token",',
262
- ' "login_success": "Anmeldung erfolgreich"',
263
- "}",
264
- ],
265
- },
266
- ],
267
- tokens: { input: 17868, output: 1134 },
268
- },
269
- {
270
- role: "agent",
271
- thinking:
272
- "Now I need the i18n loader module that imports the locale files and exports a t() function. The function should look up the key in the requested locale, falling back to English defaults. Then I'll wire it into the auth error responses.",
273
- content: "Now I'll create the i18n loader and wire it into the error responses.",
274
- toolCalls: [
275
- {
276
- tool: "Write",
277
- args: "src/i18n/index.ts",
278
- output: [
279
- "import ja from './ja.json'",
280
- "import de from './de.json'",
281
- "",
282
- "const locales = { ja, de, en: { /* defaults */ } }",
283
- "",
284
- "export function t(key: string, lang = 'en'): string {",
285
- " return locales[lang]?.[key] ?? locales.en[key] ?? key",
286
- "}",
287
- ],
288
- },
289
- {
290
- tool: "Edit",
291
- args: "src/auth.ts",
292
- output: [
293
- "+ import { t } from './i18n'",
294
- "",
295
- "- return res.status(429).json({ error: 'Too many attempts' })",
296
- "+ return res.status(429).json({ error: t('rate_limited', req.lang) })",
297
- ],
298
- },
299
- ],
300
- tokens: { input: 19752, output: 890 },
301
- },
302
- {
303
- role: "agent",
304
- content: "Running tests with locale coverage.",
305
- toolCalls: [
306
- {
307
- tool: "Bash",
308
- args: "bun test src/i18n/",
309
- output: [
310
- "i18n.test.ts",
311
- " ✓ English fallback for unknown key",
312
- " ✓ Japanese: トークンの有効期限が切れました 🔧",
313
- " ✓ German: Token abgelaufen",
314
- " ✓ Emoji preserved in translations 🐛✅",
315
- "",
316
- "4 passed | 0 failed",
317
- ],
318
- },
319
- ],
320
- tokens: { input: 20468, output: 356 },
321
- },
322
- {
323
- role: "agent",
324
- content:
325
- "i18n support added with Japanese (日本語) and German (Deutsch) translations. Error messages are now locale-aware. 🌍✅",
326
- tokens: { input: 20802, output: 134 },
327
- },
328
- {
329
- role: "user",
330
- content: "Add a health check endpoint at /health that returns the service version.",
331
- tokens: { input: 104, output: 0 },
332
- },
333
- {
334
- role: "agent",
335
- thinking:
336
- "Simple task — read the version from package.json and expose it on GET /health alongside uptime. Quick implementation.",
337
- content: "Simple addition — reading package.json for the version, then adding the route.",
338
- toolCalls: [
339
- {
340
- tool: "Read",
341
- args: "package.json",
342
- output: ["{", ' "name": "auth-service",', ' "version": "2.4.1",', " ...", "}"],
343
- },
344
- {
345
- tool: "Edit",
346
- args: "src/routes/health.ts",
347
- output: [
348
- "+ import { version } from '../../package.json'",
349
- "+",
350
- "+ router.get('/health', (req, res) => {",
351
- "+ res.json({ status: 'ok', version, uptime: process.uptime() })",
352
- "+ })",
353
- ],
354
- },
355
- ],
356
- tokens: { input: 21578, output: 468 },
357
- },
358
- {
359
- role: "agent",
360
- content: "Running final tests.",
361
- toolCalls: [
362
- {
363
- tool: "Bash",
364
- args: "bun test",
365
- output: [
366
- "auth.test.ts ........... 3 passed",
367
- "rate-limit.test.ts ..... 5 passed",
368
- "routes/auth.test.ts .... 7 passed",
369
- "routes/health.test.ts .. 2 passed",
370
- "i18n.test.ts ........... 4 passed",
371
- "",
372
- "21 passed | 0 failed | 5 files",
373
- ],
374
- },
375
- ],
376
- tokens: { input: 22046, output: 290 },
377
- },
378
- {
379
- role: "agent",
380
- content:
381
- "All done! Summary of changes:\n• Fixed token expiry bug (seconds vs milliseconds)\n• Added rate limiting (5 req/min per IP)\n• Added i18n support (日本語 + Deutsch) 🌍\n• Added /health endpoint (v2.4.1)\n\nAll 21 tests pass. Ready to commit?",
382
- tokens: { input: 22468, output: 224 },
383
- },
384
- ]
385
-
386
- // ============================================================================
387
- // Stress test script — 200 programmatically generated exchanges
388
- // ============================================================================
389
-
390
- export function generateStressScript(): ScriptEntry[] {
391
- const exchanges: ScriptEntry[] = []
392
- const tools = ["Read", "Edit", "Bash", "Write", "Grep", "Glob"]
393
- const files = [
394
- "src/auth.ts",
395
- "src/db.ts",
396
- "src/routes/api.ts",
397
- "src/middleware/cors.ts",
398
- "src/utils/crypto.ts",
399
- "src/config.ts",
400
- "tests/integration.test.ts",
401
- "src/i18n/日本語.json",
402
- ]
403
-
404
- let cumulativeInput = 4000
405
-
406
- for (let i = 0; i < 200; i++) {
407
- if (i % 5 === 0) {
408
- const prompts = [
409
- `Fix bug #${100 + i} in ${files[i % files.length]}`,
410
- `Add feature: ${["caching", "logging", "retry", "batching", "バリデーション"][i % 5]}`,
411
- `Refactor ${files[i % files.length]} — it's too complex 🔧`,
412
- `Why is test #${i} failing? 🐛`,
413
- `Add 日本語 translations for module ${i}`,
414
- ]
415
- exchanges.push({
416
- role: "user",
417
- content: prompts[Math.floor(i / 5) % prompts.length]!,
418
- tokens: { input: 40 + (i % 30), output: 0 },
419
- })
420
- } else if (i % 5 === 4) {
421
- exchanges.push({
422
- role: "agent",
423
- content: `Done with batch ${Math.floor(i / 5) + 1}. ${3 + (i % 7)} tests pass. ✅`,
424
- tokens: { input: cumulativeInput, output: 45 + (i % 60) },
425
- })
426
- } else {
427
- const tool = tools[i % tools.length]!
428
- const file = files[i % files.length]!
429
- cumulativeInput += 200 + (i % 300)
430
- exchanges.push({
431
- role: "agent",
432
- thinking: i % 3 === 0 ? `Analyzing ${file} for the reported issue...` : undefined,
433
- content: `Working on ${file}...`,
434
- toolCalls: [
435
- {
436
- tool,
437
- args: tool === "Bash" ? `bun test ${file.replace("src/", "tests/")}` : file,
438
- output: [
439
- `// ${tool} output for ${file}`,
440
- `line ${i * 10 + 1}: processing...`,
441
- tool === "Edit" ? `- old code at line ${i}` : ` existing line ${i}`,
442
- tool === "Edit" ? `+ new code at line ${i}` : ` result: ok`,
443
- i % 10 === 0 ? `✓ テスト合格 🎉` : `✓ done`,
444
- ],
445
- },
446
- ],
447
- tokens: { input: cumulativeInput, output: 120 + (i % 200) },
448
- })
449
- }
450
-
451
- if (i === 80 || i === 160) {
452
- exchanges.push({
453
- role: "system",
454
- content: `📦 Compaction #${i === 80 ? 1 : 2}: context cleared. Scrollback preserved above.`,
455
- })
456
- }
457
- }
458
-
459
- return exchanges
460
- }