ethagent 0.2.1 → 1.0.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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -32
  3. package/bin/ethagent.js +11 -2
  4. package/package.json +25 -7
  5. package/src/app/FirstRun.tsx +412 -0
  6. package/src/app/hooks/useCancelRequest.ts +22 -0
  7. package/src/app/hooks/useDoublePress.ts +46 -0
  8. package/src/app/hooks/useExitOnCtrlC.ts +36 -0
  9. package/src/app/input/AppInputProvider.tsx +116 -0
  10. package/src/app/input/appInputParser.ts +279 -0
  11. package/src/app/keybindings/KeybindingProvider.tsx +134 -0
  12. package/src/app/keybindings/resolver.ts +42 -0
  13. package/src/app/keybindings/types.ts +26 -0
  14. package/src/chat/ChatBottomPane.tsx +280 -0
  15. package/src/chat/ChatInput.tsx +722 -0
  16. package/src/chat/ChatScreen.tsx +1575 -0
  17. package/src/chat/ContextLimitView.tsx +95 -0
  18. package/src/chat/ContinuityEditReviewView.tsx +48 -0
  19. package/src/chat/ConversationStack.tsx +47 -0
  20. package/src/chat/CopyPicker.tsx +52 -0
  21. package/src/chat/MessageList.tsx +609 -0
  22. package/src/chat/PermissionPrompt.tsx +153 -0
  23. package/src/chat/PermissionsView.tsx +159 -0
  24. package/src/chat/PlanApprovalView.tsx +91 -0
  25. package/src/chat/ResumeView.tsx +267 -0
  26. package/src/chat/RewindView.tsx +386 -0
  27. package/src/chat/SessionStatus.tsx +51 -0
  28. package/src/chat/TranscriptView.tsx +202 -0
  29. package/src/chat/chatInputState.ts +247 -0
  30. package/src/chat/chatPaste.ts +49 -0
  31. package/src/chat/chatScreenUtils.ts +187 -0
  32. package/src/chat/chatSessionState.ts +142 -0
  33. package/src/chat/chatTurnOrchestrator.ts +701 -0
  34. package/src/chat/commands.ts +673 -0
  35. package/src/chat/textCursor.ts +202 -0
  36. package/src/chat/toolResultDisplay.ts +8 -0
  37. package/src/chat/transcriptViewport.ts +247 -0
  38. package/src/cli/ResetConfirmView.tsx +61 -0
  39. package/src/cli/main.tsx +177 -0
  40. package/src/cli/preview.tsx +19 -0
  41. package/src/cli/reset.ts +106 -0
  42. package/src/identity/continuity/editor.ts +149 -0
  43. package/src/identity/continuity/envelope.ts +345 -0
  44. package/src/identity/continuity/history.ts +153 -0
  45. package/src/identity/continuity/privateEdit.ts +334 -0
  46. package/src/identity/continuity/publicSkills.ts +173 -0
  47. package/src/identity/continuity/snapshots.ts +183 -0
  48. package/src/identity/continuity/storage.ts +507 -0
  49. package/src/identity/crypto/backupEnvelope.ts +486 -0
  50. package/src/identity/crypto/eth.ts +137 -0
  51. package/src/identity/hub/IdentityHub.tsx +845 -0
  52. package/src/identity/hub/identityHubEffects.ts +1100 -0
  53. package/src/identity/hub/identityHubModel.ts +291 -0
  54. package/src/identity/hub/identityHubReducer.ts +209 -0
  55. package/src/identity/hub/screens/BusyScreen.tsx +26 -0
  56. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +139 -0
  57. package/src/identity/hub/screens/CreateFlow.tsx +206 -0
  58. package/src/identity/hub/screens/DetailsScreen.tsx +64 -0
  59. package/src/identity/hub/screens/EditProfileFlow.tsx +145 -0
  60. package/src/identity/hub/screens/ErrorScreen.tsx +35 -0
  61. package/src/identity/hub/screens/IdentitySummary.tsx +70 -0
  62. package/src/identity/hub/screens/MenuScreen.tsx +117 -0
  63. package/src/identity/hub/screens/NetworkScreen.tsx +41 -0
  64. package/src/identity/hub/screens/RebackupStorageScreen.tsx +50 -0
  65. package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +85 -0
  66. package/src/identity/hub/screens/RestoreFlow.tsx +206 -0
  67. package/src/identity/hub/screens/StorageCredentialScreen.tsx +128 -0
  68. package/src/identity/hub/screens/WalletApprovalScreen.tsx +43 -0
  69. package/src/identity/profile/imagePicker.ts +180 -0
  70. package/src/identity/registry/erc8004.ts +1106 -0
  71. package/src/identity/registry/registryConfig.ts +69 -0
  72. package/src/identity/storage/ipfs.ts +212 -0
  73. package/src/identity/storage/pinataJwt.ts +53 -0
  74. package/src/identity/wallet/browserWallet.ts +393 -0
  75. package/src/identity/wallet/wallet-page/wallet.html +1082 -0
  76. package/src/mcp/approvals.ts +113 -0
  77. package/src/mcp/config.ts +235 -0
  78. package/src/mcp/manager.ts +541 -0
  79. package/src/mcp/names.ts +19 -0
  80. package/src/mcp/output.ts +96 -0
  81. package/src/models/ModelPicker.tsx +1446 -0
  82. package/src/models/catalog.ts +296 -0
  83. package/src/models/huggingface.ts +651 -0
  84. package/src/models/llamacpp.ts +810 -0
  85. package/src/models/llamacppPreflight.ts +150 -0
  86. package/src/models/modelDisplay.ts +105 -0
  87. package/src/models/modelPickerOptions.ts +421 -0
  88. package/src/models/modelRecommendation.ts +140 -0
  89. package/src/models/runtimeDetection.ts +81 -0
  90. package/src/models/uncensoredCatalog.ts +86 -0
  91. package/src/providers/anthropic.ts +259 -0
  92. package/src/providers/contracts.ts +62 -0
  93. package/src/providers/errors.ts +62 -0
  94. package/src/providers/gemini.ts +152 -0
  95. package/src/providers/openai-chat.ts +472 -0
  96. package/src/providers/registry.ts +42 -0
  97. package/src/providers/retry.ts +58 -0
  98. package/src/providers/sse.ts +93 -0
  99. package/src/runtime/compaction.ts +389 -0
  100. package/src/runtime/cwd.ts +43 -0
  101. package/src/runtime/sessionMode.ts +55 -0
  102. package/src/runtime/systemPrompt.ts +209 -0
  103. package/src/runtime/toolClaimGuards.ts +143 -0
  104. package/src/runtime/toolExecution.ts +304 -0
  105. package/src/runtime/toolIntent.ts +163 -0
  106. package/src/runtime/turn.ts +858 -0
  107. package/src/storage/atomicWrite.ts +68 -0
  108. package/src/storage/config.ts +189 -0
  109. package/src/storage/factoryReset.ts +130 -0
  110. package/src/storage/history.ts +58 -0
  111. package/src/storage/identity.ts +99 -0
  112. package/src/storage/permissions.ts +76 -0
  113. package/src/storage/rewind.ts +246 -0
  114. package/src/storage/secrets.ts +181 -0
  115. package/src/storage/sessionExport.ts +49 -0
  116. package/src/storage/sessions.ts +482 -0
  117. package/src/tools/bashSafety.ts +174 -0
  118. package/src/tools/bashTool.ts +140 -0
  119. package/src/tools/changeDirectoryTool.ts +213 -0
  120. package/src/tools/contracts.ts +179 -0
  121. package/src/tools/deleteFileTool.ts +111 -0
  122. package/src/tools/editTool.ts +160 -0
  123. package/src/tools/editUtils.ts +170 -0
  124. package/src/tools/listDirectoryTool.ts +55 -0
  125. package/src/tools/mcpResourceTools.ts +95 -0
  126. package/src/tools/permissionRules.ts +85 -0
  127. package/src/tools/privateContinuityEditTool.ts +178 -0
  128. package/src/tools/privateContinuityReadTool.ts +107 -0
  129. package/src/tools/readTool.ts +85 -0
  130. package/src/tools/registry.ts +67 -0
  131. package/src/tools/writeFileTool.ts +142 -0
  132. package/src/ui/BrandSplash.tsx +193 -0
  133. package/src/ui/ProgressBar.tsx +34 -0
  134. package/src/ui/Select.tsx +143 -0
  135. package/src/ui/Spinner.tsx +269 -0
  136. package/src/ui/Surface.tsx +47 -0
  137. package/src/ui/TextInput.tsx +97 -0
  138. package/src/ui/theme.ts +59 -0
  139. package/src/utils/clipboard.ts +216 -0
  140. package/src/utils/markdownSegments.ts +51 -0
  141. package/src/utils/messages.ts +35 -0
  142. package/src/utils/withRetry.ts +280 -0
  143. package/src/cli.tsx +0 -147
@@ -0,0 +1,280 @@
1
+ export type RetryClassification = {
2
+ retryable: boolean
3
+ retryAfterMs?: number
4
+ reason: string
5
+ status?: number
6
+ code?: string
7
+ }
8
+
9
+ export type RetryPolicy = {
10
+ maxRetries: number
11
+ baseDelayMs: number
12
+ maxDelayMs: number
13
+ retryAfterCapMs: number
14
+ jitterRatio: number
15
+ }
16
+
17
+ export type RetryEvent = {
18
+ attempt: number
19
+ nextAttempt: number
20
+ maxRetries: number
21
+ delayMs: number
22
+ reason: string
23
+ retryAfterMs?: number
24
+ status?: number
25
+ code?: string
26
+ }
27
+
28
+ const RETRYABLE_NET_CODES = new Set([
29
+ 'ECONNRESET',
30
+ 'EPIPE',
31
+ 'ETIMEDOUT',
32
+ 'ECONNREFUSED',
33
+ 'EHOSTUNREACH',
34
+ 'ENETUNREACH',
35
+ 'EAI_AGAIN',
36
+ ])
37
+
38
+ const RETRYABLE_STATUS = new Set([408, 409, 425, 429, 500, 502, 503, 504, 529])
39
+ const DEFAULT_BASE_DELAY_MS = 500
40
+ const DEFAULT_MAX_DELAY_MS = 32_000
41
+ const DEFAULT_JITTER_RATIO = 0.25
42
+ const DEFAULT_MAX_RETRIES = 4
43
+
44
+ export const DEFAULT_RETRY_POLICY: RetryPolicy = {
45
+ maxRetries: DEFAULT_MAX_RETRIES,
46
+ baseDelayMs: DEFAULT_BASE_DELAY_MS,
47
+ maxDelayMs: DEFAULT_MAX_DELAY_MS,
48
+ retryAfterCapMs: DEFAULT_MAX_DELAY_MS * 4,
49
+ jitterRatio: DEFAULT_JITTER_RATIO,
50
+ }
51
+
52
+ export function classifyRetryableFetchError(err: unknown): RetryClassification {
53
+ if (!err || typeof err !== 'object') return { retryable: false, reason: 'unknown error' }
54
+ const code = (err as { code?: unknown }).code
55
+ if (typeof code === 'string' && RETRYABLE_NET_CODES.has(code)) {
56
+ return { retryable: true, reason: code, code }
57
+ }
58
+ const cause = (err as { cause?: unknown }).cause
59
+ if (cause && typeof cause === 'object') {
60
+ const causeCode = (cause as { code?: unknown }).code
61
+ if (typeof causeCode === 'string' && RETRYABLE_NET_CODES.has(causeCode)) {
62
+ return { retryable: true, reason: causeCode, code: causeCode }
63
+ }
64
+ }
65
+ const message = (err as { message?: unknown }).message
66
+ if (typeof message === 'string' && /fetch failed|network|socket hang up|ECONNRESET/i.test(message)) {
67
+ return { retryable: true, reason: 'fetch failed' }
68
+ }
69
+ return { retryable: false, reason: 'non-retryable error' }
70
+ }
71
+
72
+ export function classifyRetryableResponse(response: Response, nowMs: number = Date.now()): RetryClassification {
73
+ if (response.ok) return { retryable: false, reason: 'ok', status: response.status }
74
+ if (!RETRYABLE_STATUS.has(response.status)) {
75
+ return { retryable: false, reason: `HTTP ${response.status}`, status: response.status }
76
+ }
77
+ const retryAfter = response.headers.get('retry-after')
78
+ const retryAfterMs = retryAfter ? parseRetryAfter(retryAfter, nowMs) : undefined
79
+ return { retryable: true, retryAfterMs, reason: `HTTP ${response.status}`, status: response.status }
80
+ }
81
+
82
+ export function classifyRetryableProviderResponse(
83
+ response: Response,
84
+ nowMs: number = Date.now(),
85
+ rateLimitResetProvider?: RateLimitResetProvider,
86
+ ): RetryClassification {
87
+ const classification = classifyRetryableResponse(response, nowMs)
88
+ if (
89
+ !classification.retryable ||
90
+ classification.retryAfterMs !== undefined ||
91
+ response.status !== 429 ||
92
+ !rateLimitResetProvider
93
+ ) {
94
+ return classification
95
+ }
96
+ return {
97
+ ...classification,
98
+ retryAfterMs: rateLimitResetDelayMs(response.headers, rateLimitResetProvider, nowMs),
99
+ }
100
+ }
101
+
102
+ export function computeBackoffMs(
103
+ attempt: number,
104
+ retryAfterMs: number | undefined,
105
+ maxDelayMs = DEFAULT_MAX_DELAY_MS,
106
+ baseDelayMs = DEFAULT_BASE_DELAY_MS,
107
+ rng: () => number = Math.random,
108
+ jitterRatio = DEFAULT_JITTER_RATIO,
109
+ retryAfterCapMs = maxDelayMs * 4,
110
+ ): number {
111
+ if (retryAfterMs !== undefined && Number.isFinite(retryAfterMs)) {
112
+ return Math.min(Math.max(retryAfterMs, 0), retryAfterCapMs)
113
+ }
114
+ const expo = Math.min(baseDelayMs * Math.pow(2, attempt - 1), maxDelayMs)
115
+ const jitter = rng() * jitterRatio * expo
116
+ return Math.floor(expo + jitter)
117
+ }
118
+
119
+ export function retryPolicyFromOptions(options: FetchWithRetryOptions = {}): RetryPolicy {
120
+ const maxDelayMs = options.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs
121
+ return {
122
+ maxRetries: options.maxRetries ?? DEFAULT_RETRY_POLICY.maxRetries,
123
+ baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs,
124
+ maxDelayMs,
125
+ retryAfterCapMs: options.retryAfterCapMs ?? maxDelayMs * 4,
126
+ jitterRatio: options.jitterRatio ?? DEFAULT_RETRY_POLICY.jitterRatio,
127
+ }
128
+ }
129
+
130
+ export function parseRetryAfter(headerValue: string, nowMs: number = Date.now()): number | undefined {
131
+ const trimmed = headerValue.trim()
132
+ if (!trimmed) return undefined
133
+ const seconds = Number(trimmed)
134
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000)
135
+ const dateMs = Date.parse(trimmed)
136
+ if (Number.isFinite(dateMs)) return Math.max(0, dateMs - nowMs)
137
+ return undefined
138
+ }
139
+
140
+ export function parseOpenAIRateLimitResetMs(value: string): number | undefined {
141
+ if (!value) return undefined
142
+ const match = /^(?:(\d+)h)?(?:(\d+)m(?!s))?(?:(\d+)s)?(?:(\d+)ms)?$/.exec(value.trim())
143
+ if (!match || match[0] === '') return undefined
144
+ const hours = Number.parseInt(match[1] ?? '0', 10)
145
+ const minutes = Number.parseInt(match[2] ?? '0', 10)
146
+ const seconds = Number.parseInt(match[3] ?? '0', 10)
147
+ const milliseconds = Number.parseInt(match[4] ?? '0', 10)
148
+ const total = hours * 3_600_000 + minutes * 60_000 + seconds * 1000 + milliseconds
149
+ return total > 0 ? total : undefined
150
+ }
151
+
152
+ export type RateLimitResetProvider = 'anthropic' | 'openai-compatible'
153
+
154
+ export function rateLimitResetDelayMs(
155
+ headers: Headers,
156
+ provider: RateLimitResetProvider,
157
+ nowMs: number = Date.now(),
158
+ capMs = DEFAULT_RETRY_POLICY.retryAfterCapMs,
159
+ ): number | undefined {
160
+ if (provider === 'anthropic') {
161
+ const reset = headers.get('anthropic-ratelimit-unified-reset')
162
+ if (!reset) return undefined
163
+ const unixSeconds = Number(reset)
164
+ if (!Number.isFinite(unixSeconds)) return undefined
165
+ const delay = unixSeconds * 1000 - nowMs
166
+ return delay > 0 ? Math.min(delay, capMs) : undefined
167
+ }
168
+
169
+ const requestDelay = parseOpenAIRateLimitResetMs(headers.get('x-ratelimit-reset-requests') ?? '')
170
+ const tokenDelay = parseOpenAIRateLimitResetMs(headers.get('x-ratelimit-reset-tokens') ?? '')
171
+ const delay = Math.max(requestDelay ?? 0, tokenDelay ?? 0)
172
+ return delay > 0 ? Math.min(delay, capMs) : undefined
173
+ }
174
+
175
+ export type FetchWithRetryOptions = {
176
+ maxRetries?: number
177
+ baseDelayMs?: number
178
+ maxDelayMs?: number
179
+ retryAfterCapMs?: number
180
+ jitterRatio?: number
181
+ rateLimitResetProvider?: RateLimitResetProvider
182
+ signal?: AbortSignal
183
+ fetchImpl?: typeof fetch
184
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>
185
+ rng?: () => number
186
+ now?: () => number
187
+ onRetry?: (event: RetryEvent) => void
188
+ }
189
+
190
+ export async function fetchWithRetry(
191
+ input: string,
192
+ init: RequestInit,
193
+ options: FetchWithRetryOptions = {},
194
+ ): Promise<Response> {
195
+ const policy = retryPolicyFromOptions(options)
196
+ const fetchImpl = options.fetchImpl ?? fetch
197
+ const sleepImpl = options.sleep ?? sleep
198
+ const rng = options.rng ?? Math.random
199
+ const now = options.now ?? Date.now
200
+ let lastError: unknown
201
+ for (let attempt = 1; attempt <= policy.maxRetries + 1; attempt += 1) {
202
+ if (options.signal?.aborted) throw new DOMException('aborted', 'AbortError')
203
+
204
+ try {
205
+ const response = await fetchImpl(input, { ...init, signal: options.signal })
206
+ if (response.ok) return response
207
+
208
+ const classification = classifyRetryableProviderResponse(response, now(), options.rateLimitResetProvider)
209
+ if (!classification.retryable || attempt > policy.maxRetries) return response
210
+
211
+ try { await response.body?.cancel() } catch { /* ignore */ }
212
+ const delayMs = computeBackoffMs(
213
+ attempt,
214
+ classification.retryAfterMs,
215
+ policy.maxDelayMs,
216
+ policy.baseDelayMs,
217
+ rng,
218
+ policy.jitterRatio,
219
+ policy.retryAfterCapMs,
220
+ )
221
+ options.onRetry?.(retryEvent(attempt, policy.maxRetries, delayMs, classification))
222
+ await sleepImpl(delayMs, options.signal)
223
+ continue
224
+ } catch (err) {
225
+ lastError = err
226
+ if (options.signal?.aborted) throw err
227
+ const classification = classifyRetryableFetchError(err)
228
+ if (!classification.retryable || attempt > policy.maxRetries) throw err
229
+
230
+ const delayMs = computeBackoffMs(
231
+ attempt,
232
+ classification.retryAfterMs,
233
+ policy.maxDelayMs,
234
+ policy.baseDelayMs,
235
+ rng,
236
+ policy.jitterRatio,
237
+ policy.retryAfterCapMs,
238
+ )
239
+ options.onRetry?.(retryEvent(attempt, policy.maxRetries, delayMs, classification))
240
+ await sleepImpl(delayMs, options.signal)
241
+ }
242
+ }
243
+ throw lastError ?? new Error('fetchWithRetry exhausted')
244
+ }
245
+
246
+ function retryEvent(
247
+ attempt: number,
248
+ maxRetries: number,
249
+ delayMs: number,
250
+ classification: RetryClassification,
251
+ ): RetryEvent {
252
+ return {
253
+ attempt,
254
+ nextAttempt: attempt + 1,
255
+ maxRetries,
256
+ delayMs,
257
+ reason: classification.reason,
258
+ ...(classification.retryAfterMs !== undefined ? { retryAfterMs: classification.retryAfterMs } : {}),
259
+ ...(classification.status !== undefined ? { status: classification.status } : {}),
260
+ ...(classification.code !== undefined ? { code: classification.code } : {}),
261
+ }
262
+ }
263
+
264
+ export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
265
+ return new Promise((resolve, reject) => {
266
+ if (signal?.aborted) {
267
+ reject(new DOMException('aborted', 'AbortError'))
268
+ return
269
+ }
270
+ const timer = setTimeout(() => {
271
+ signal?.removeEventListener('abort', onAbort)
272
+ resolve()
273
+ }, ms)
274
+ const onAbort = () => {
275
+ clearTimeout(timer)
276
+ reject(new DOMException('aborted', 'AbortError'))
277
+ }
278
+ signal?.addEventListener('abort', onAbort, { once: true })
279
+ })
280
+ }
package/src/cli.tsx DELETED
@@ -1,147 +0,0 @@
1
- #!/usr/bin/env node
2
- import React from 'react'
3
- import {render, Text, Box} from 'ink'
4
-
5
- const eth = `░░░░░░░╗░░░░░░░░╗░░╗ ░░╗
6
- ░░╔════╝╚══░░╔══╝░░║ ░░║
7
- ░░░░░╗ ░░║ ░░░░░░░║
8
- ░░╔══╝ ░░║ ░░╔══░░║
9
- ░░░░░░░╗ ░░║ ░░║ ░░║
10
- ╚══════╝ ╚═╝ ╚═╝ ╚═╝`
11
-
12
- const A = [
13
- ` █████╗ `,
14
- `██╔══██╗`,
15
- `███████║`,
16
- `██╔══██║`,
17
- `██║ ██║`,
18
- `╚═╝ ╚═╝`,
19
- ].join('\n')
20
-
21
- const G = [
22
- ` ██████╗ `,
23
- `██╔════╝ `,
24
- `██║ ███╗`,
25
- `██║ ██║`,
26
- `╚██████╔╝`,
27
- ` ╚═════╝ `,
28
- ].join('\n')
29
-
30
- const E = [
31
- `███████╗`,
32
- `██╔════╝`,
33
- `█████╗ `,
34
- `██╔══╝ `,
35
- `███████╗`,
36
- `╚══════╝`,
37
- ].join('\n')
38
-
39
- const N = [
40
- `███╗ ██╗`,
41
- `████╗ ██║`,
42
- `██╔██╗ ██║`,
43
- `██║╚██╗██║`,
44
- `██║ ╚████║`,
45
- `╚═╝ ╚═══╝`,
46
- ].join('\n')
47
-
48
- const T = [
49
- `████████╗`,
50
- `╚══██╔══╝`,
51
- ` ██║ `,
52
- ` ██║ `,
53
- ` ██║ `,
54
- ` ╚═╝ `,
55
- ].join('\n')
56
-
57
- const eyes = `
58
- -+:
59
- :=- -%@@@%.
60
- *@@@@@#- *@@-
61
- +@@. +@
62
- @@= -#=-+++=+:
63
- #% .:===-: -@* +@@@@%
64
- *@-+@@@@@: %@@+ @@@=#@
65
- *@= @@@@@@@- .@.@@@@@@@ :
66
- @@+=@@@@@@@@@@@@: .% *@@@@@*-=
67
- #:-@ -@@@@@@@@@-+% @ -@@@- #
68
- : #+ @@@@@@@- -% =# =
69
- -@: *@ .+%%
70
- :%#: --
71
- .-:
72
- `
73
-
74
- const palette = [
75
- [0xe8, 0xa0, 0xa0],
76
- [0xe8, 0xbe, 0x8f],
77
- [0xe8, 0xdf, 0x9a],
78
- [0x96, 0xd4, 0xa8],
79
- [0x90, 0xb8, 0xe8],
80
- ]
81
-
82
- function gradientColor(t) {
83
- const s = Math.max(0, Math.min(1, t)) * (palette.length - 1)
84
- const i = Math.min(Math.floor(s), palette.length - 2)
85
- const f = s - i
86
- const [r1, g1, b1] = palette[i]
87
- const [r2, g2, b2] = palette[i + 1]
88
- const r = Math.round(r1 + (r2 - r1) * f)
89
- const g = Math.round(g1 + (g2 - g1) * f)
90
- const b = Math.round(b1 + (b2 - b1) * f)
91
- return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
92
- }
93
-
94
- const Eyes = () => {
95
- const lines = eyes.split('\n')
96
- const maxLen = Math.max(...lines.map(l => l.trimEnd().length))
97
- return (
98
- <Box flexDirection="column">
99
- {lines.map((line, li) => (
100
- <Text key={li}>
101
- {line.split('').map((char, ci) => (
102
- <Text key={ci} color={gradientColor(ci / Math.max(maxLen - 1, 1))}>{char}</Text>
103
- ))}
104
- </Text>
105
- ))}
106
- </Box>
107
- )
108
- }
109
-
110
- const App = () => {
111
- const ethLines = eth.split('\n')
112
- const aLines = A.split('\n')
113
- const gLines = G.split('\n')
114
- const eLines = E.split('\n')
115
- const nLines = N.split('\n')
116
- const tLines = T.split('\n')
117
-
118
- const w = 69
119
- const topLabel = ' privacy-first AI agent with a portable ethereum identity '
120
-
121
- return (
122
- <Box flexDirection="column" alignSelf="flex-start" padding={1}>
123
- <Eyes />
124
- <Text>
125
- <Text color="#555555">╔═</Text>
126
- <Text color="#777777">{topLabel}</Text>
127
- <Text color="#555555">{'═'.repeat(w - topLabel.length - 1)}╗</Text>
128
- </Text>
129
- {ethLines.map((line, i) => (
130
- <Box key={i}>
131
- <Text color="#555555">║</Text>
132
- <Text color="#555555">{ethLines[i]}</Text>
133
- <Text color="#555555">{aLines[i]}</Text>
134
- <Text color="#555555">{gLines[i]}</Text>
135
- <Text color="#555555">{eLines[i]}</Text>
136
- <Text color="#555555">{nLines[i]}</Text>
137
- <Text color="#555555">{tLines[i]}</Text>
138
- <Text color="#555555">║</Text>
139
- </Box>
140
- ))}
141
- <Text color="#555555">{'╚' + '═'.repeat(w) + '╝'}</Text>
142
- <Text color="#777777">{'\n coming soon...\n'}</Text>
143
- </Box>
144
- )
145
- }
146
-
147
- render(<App />)