ethagent 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +127 -29
  3. package/package.json +16 -9
  4. package/src/app/FirstRun.tsx +192 -146
  5. package/src/app/FirstRunTimeline.tsx +47 -0
  6. package/src/app/input/AppInputProvider.tsx +1 -1
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -1
  8. package/src/chat/ChatBottomPane.tsx +0 -1
  9. package/src/chat/ChatInput.tsx +6 -6
  10. package/src/chat/ChatScreen.tsx +43 -18
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +11 -17
  13. package/src/chat/ConversationStack.tsx +3 -0
  14. package/src/chat/CopyPicker.tsx +0 -1
  15. package/src/chat/MessageList.tsx +62 -45
  16. package/src/chat/PermissionPrompt.tsx +13 -9
  17. package/src/chat/PlanApprovalView.tsx +3 -3
  18. package/src/chat/ResumeView.tsx +1 -4
  19. package/src/chat/RewindView.tsx +2 -2
  20. package/src/chat/TranscriptView.tsx +6 -0
  21. package/src/chat/chatInputState.ts +1 -1
  22. package/src/chat/chatScreenUtils.ts +22 -11
  23. package/src/chat/chatSessionState.ts +2 -2
  24. package/src/chat/chatTurnOrchestrator.ts +16 -81
  25. package/src/chat/commands.ts +1 -1
  26. package/src/chat/textCursor.ts +1 -1
  27. package/src/chat/transcriptViewport.ts +2 -7
  28. package/src/cli/ResetConfirmView.tsx +1 -1
  29. package/src/cli/main.tsx +9 -3
  30. package/src/cli/preview.tsx +0 -5
  31. package/src/cli/updateNotice.ts +5 -3
  32. package/src/identity/continuity/editor.ts +7 -107
  33. package/src/identity/continuity/envelope.ts +1048 -40
  34. package/src/identity/continuity/history.ts +4 -4
  35. package/src/identity/continuity/localBackup.ts +249 -0
  36. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  37. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  38. package/src/identity/continuity/privateEdit/files.ts +23 -0
  39. package/src/identity/continuity/privateEdit/types.ts +28 -0
  40. package/src/identity/continuity/privateEdit.ts +10 -298
  41. package/src/identity/continuity/publicSkills.ts +8 -9
  42. package/src/identity/continuity/snapshots.ts +17 -6
  43. package/src/identity/continuity/storage/defaults.ts +111 -0
  44. package/src/identity/continuity/storage/files.ts +72 -0
  45. package/src/identity/continuity/storage/markdown.ts +81 -0
  46. package/src/identity/continuity/storage/paths.ts +24 -0
  47. package/src/identity/continuity/storage/scaffold.ts +124 -0
  48. package/src/identity/continuity/storage/status.ts +86 -0
  49. package/src/identity/continuity/storage/types.ts +27 -0
  50. package/src/identity/continuity/storage.ts +32 -507
  51. package/src/identity/continuity/zipWriter.ts +95 -0
  52. package/src/identity/crypto/backupEnvelope.ts +14 -247
  53. package/src/identity/crypto/eth.ts +7 -7
  54. package/src/identity/ens/agentRecords.ts +96 -0
  55. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  56. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  57. package/src/identity/ens/ensAutomation/names.ts +14 -0
  58. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  59. package/src/identity/ens/ensAutomation/read.ts +114 -0
  60. package/src/identity/ens/ensAutomation/root.ts +63 -0
  61. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  62. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  63. package/src/identity/ens/ensAutomation/types.ts +126 -0
  64. package/src/identity/ens/ensAutomation.ts +29 -0
  65. package/src/identity/ens/ensLookup/client.ts +43 -0
  66. package/src/identity/ens/ensLookup/constants.ts +26 -0
  67. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  68. package/src/identity/ens/ensLookup/names.ts +34 -0
  69. package/src/identity/ens/ensLookup/records.ts +45 -0
  70. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  71. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  72. package/src/identity/ens/ensLookup/types.ts +38 -0
  73. package/src/identity/ens/ensLookup/validation.ts +72 -0
  74. package/src/identity/ens/ensLookup.ts +19 -0
  75. package/src/identity/ens/ensRegistration.ts +199 -0
  76. package/src/identity/ens/resolverDelegation.ts +48 -0
  77. package/src/identity/hub/IdentityHub.tsx +13 -815
  78. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  79. package/src/identity/hub/Routes.tsx +361 -0
  80. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  81. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  82. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  83. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  84. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  85. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  86. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  87. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  88. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  89. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  90. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  91. package/src/identity/hub/effects/create.ts +310 -0
  92. package/src/identity/hub/effects/ens/flows.ts +218 -0
  93. package/src/identity/hub/effects/ens/index.ts +11 -0
  94. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  95. package/src/identity/hub/effects/index.ts +74 -0
  96. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  97. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  98. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  99. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  100. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  101. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  102. package/src/identity/hub/effects/receipts.ts +46 -0
  103. package/src/identity/hub/effects/restore/apply.ts +112 -0
  104. package/src/identity/hub/effects/restore/auth.ts +159 -0
  105. package/src/identity/hub/effects/restore/discover.ts +86 -0
  106. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  107. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  108. package/src/identity/hub/effects/restore/index.ts +22 -0
  109. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  110. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  111. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  112. package/src/identity/hub/effects/restore/shared.ts +91 -0
  113. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  114. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  115. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  116. package/src/identity/hub/effects/shared/sync.ts +190 -0
  117. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  118. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  119. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  120. package/src/identity/hub/effects/types.ts +53 -0
  121. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  122. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  123. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  124. package/src/identity/hub/flows/continuity/RecoveryConfirmScreen.tsx +104 -0
  125. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  126. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  127. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  128. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  129. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  130. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  131. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  132. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  133. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  134. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  135. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  136. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  137. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  138. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  139. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  140. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  141. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  142. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  143. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  144. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  145. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  146. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  147. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  148. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  149. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +25 -43
  150. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  151. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  152. package/src/identity/hub/identityHubReducer.ts +166 -101
  153. package/src/identity/hub/model/continuity.ts +94 -0
  154. package/src/identity/hub/model/copy.ts +35 -0
  155. package/src/identity/hub/model/custody.ts +54 -0
  156. package/src/identity/hub/model/ens.ts +49 -0
  157. package/src/identity/hub/model/errors.ts +140 -0
  158. package/src/identity/hub/model/format.ts +15 -0
  159. package/src/identity/hub/model/identity.ts +94 -0
  160. package/src/identity/hub/model/network.ts +32 -0
  161. package/src/identity/hub/model/transfer.ts +57 -0
  162. package/src/identity/hub/operatorWallets.ts +131 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  165. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  166. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  167. package/src/identity/hub/reconciliation/index.ts +21 -0
  168. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  169. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  170. package/src/identity/hub/txGuard.ts +51 -0
  171. package/src/identity/hub/types.ts +17 -0
  172. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  173. package/src/identity/hub/useIdentityHubController.ts +396 -0
  174. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  175. package/src/identity/hub/utils.ts +79 -0
  176. package/src/identity/identityCompat.ts +34 -0
  177. package/src/identity/profile/agentIcon.ts +61 -0
  178. package/src/identity/profile/imagePicker.ts +12 -12
  179. package/src/identity/registry/erc8004/abi.ts +14 -0
  180. package/src/identity/registry/erc8004/chains.ts +150 -0
  181. package/src/identity/registry/erc8004/client.ts +11 -0
  182. package/src/identity/registry/erc8004/discovery.ts +511 -0
  183. package/src/identity/registry/erc8004/metadata.ts +335 -0
  184. package/src/identity/registry/erc8004/ownership.ts +121 -0
  185. package/src/identity/registry/erc8004/preflight.ts +123 -0
  186. package/src/identity/registry/erc8004/transactions.ts +77 -0
  187. package/src/identity/registry/erc8004/types.ts +88 -0
  188. package/src/identity/registry/erc8004/uri.ts +59 -0
  189. package/src/identity/registry/erc8004/utils.ts +58 -0
  190. package/src/identity/registry/erc8004.ts +53 -1106
  191. package/src/identity/registry/fieldParsers.ts +28 -0
  192. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  193. package/src/identity/registry/operatorVault/constants.ts +38 -0
  194. package/src/identity/registry/operatorVault/read.ts +246 -0
  195. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  196. package/src/identity/registry/operatorVault.ts +44 -0
  197. package/src/identity/storage/ipfs.ts +26 -24
  198. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  199. package/src/identity/wallet/browserWallet/html.ts +106 -0
  200. package/src/identity/wallet/browserWallet/http.ts +28 -0
  201. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  202. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  203. package/src/identity/wallet/browserWallet/session.ts +325 -0
  204. package/src/identity/wallet/browserWallet/types.ts +192 -0
  205. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  206. package/src/identity/wallet/browserWallet.ts +30 -393
  207. package/src/identity/wallet/page/constants.ts +5 -0
  208. package/src/identity/wallet/page/controller.ts +251 -0
  209. package/src/identity/wallet/page/copy.ts +340 -0
  210. package/src/identity/wallet/page/grainient.ts +278 -0
  211. package/src/identity/wallet/page/html.ts +28 -0
  212. package/src/identity/wallet/page/markup.ts +50 -0
  213. package/src/identity/wallet/page/state.ts +9 -0
  214. package/src/identity/wallet/page/styles/base.ts +259 -0
  215. package/src/identity/wallet/page/styles/components.ts +262 -0
  216. package/src/identity/wallet/page/styles/index.ts +5 -0
  217. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  218. package/src/identity/wallet/page/types.ts +47 -0
  219. package/src/identity/wallet/page/view.ts +535 -0
  220. package/src/identity/wallet/page/walletProvider.ts +70 -0
  221. package/src/identity/wallet/page.tsx +38 -0
  222. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  223. package/src/mcp/manager.ts +0 -1
  224. package/src/models/ModelPicker.tsx +36 -30
  225. package/src/models/catalog.ts +5 -2
  226. package/src/models/huggingface.ts +9 -9
  227. package/src/models/llamacpp.ts +13 -13
  228. package/src/models/modelDisplay.ts +75 -0
  229. package/src/models/modelPickerOptions.ts +16 -3
  230. package/src/models/modelRecommendation.ts +0 -1
  231. package/src/providers/errors.ts +16 -0
  232. package/src/providers/gemini.ts +252 -39
  233. package/src/providers/registry.ts +2 -2
  234. package/src/providers/retry.ts +1 -1
  235. package/src/runtime/sessionMode.ts +1 -1
  236. package/src/runtime/systemPrompt.ts +2 -0
  237. package/src/runtime/toolExecution.ts +18 -22
  238. package/src/runtime/toolIntent.ts +0 -20
  239. package/src/runtime/turn.ts +0 -92
  240. package/src/storage/atomicWrite.ts +4 -1
  241. package/src/storage/config.ts +181 -5
  242. package/src/storage/identity.ts +9 -3
  243. package/src/storage/secrets.ts +2 -2
  244. package/src/tools/bashSafety.ts +8 -0
  245. package/src/tools/changeDirectoryTool.ts +1 -1
  246. package/src/tools/deleteFileTool.ts +4 -4
  247. package/src/tools/editTool.ts +4 -4
  248. package/src/tools/editUtils.ts +5 -5
  249. package/src/tools/privateContinuityEditTool.ts +4 -5
  250. package/src/tools/privateContinuityReadTool.ts +1 -2
  251. package/src/tools/registry.ts +30 -0
  252. package/src/tools/writeFileTool.ts +5 -5
  253. package/src/ui/BrandSplash.tsx +20 -85
  254. package/src/ui/ProgressBar.tsx +3 -5
  255. package/src/ui/Select.tsx +21 -9
  256. package/src/ui/Spinner.tsx +38 -3
  257. package/src/ui/Surface.tsx +3 -3
  258. package/src/ui/TextInput.tsx +191 -29
  259. package/src/ui/theme.ts +7 -34
  260. package/src/utils/openExternal.ts +21 -0
  261. package/src/utils/withRetry.ts +47 -3
  262. package/src/identity/hub/identityHubEffects.ts +0 -937
  263. package/src/identity/hub/identityHubModel.ts +0 -291
  264. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -144
  265. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -145
  266. package/src/identity/hub/screens/IdentitySummary.tsx +0 -90
  267. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  268. package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +0 -87
  269. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  270. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  271. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -3,7 +3,6 @@ import { z } from 'zod'
3
3
  import {
4
4
  continuityVaultRef,
5
5
  ensureContinuityFiles,
6
- type PrivateContinuityFile,
7
6
  } from '../identity/continuity/storage.js'
8
7
  import type { Tool } from './contracts.js'
9
8
 
@@ -71,7 +70,7 @@ function preparePrivateContinuityRead(
71
70
  ) {
72
71
  const identity = config?.identity
73
72
  if (!identity) {
74
- throw new Error('no active identity; create or load an identity before reading private continuity files')
73
+ throw new Error('No active identity; create or load an identity before reading private continuity files')
75
74
  }
76
75
  const ref = continuityVaultRef(identity)
77
76
  const fullPath = input.file === 'SOUL.md' ? ref.soulPath : ref.memoryPath
@@ -1,4 +1,5 @@
1
1
  import type { AnthropicToolDefinition } from '../providers/anthropic.js'
2
+ import type { GeminiToolDefinition } from '../providers/gemini.js'
2
3
  import type { OpenAIToolDefinition } from '../providers/openai-chat.js'
3
4
  import type { Tool } from './contracts.js'
4
5
  import { modePolicy, type SessionMode } from '../runtime/sessionMode.js'
@@ -65,3 +66,32 @@ export function openAITools(mode: SessionMode = 'chat', context: ToolAvailabilit
65
66
  },
66
67
  }))
67
68
  }
69
+
70
+ const GEMINI_DROP_KEYS = new Set([
71
+ 'additionalProperties',
72
+ '$schema',
73
+ '$ref',
74
+ '$defs',
75
+ 'definitions',
76
+ ])
77
+
78
+ function sanitizeForGemini(schema: unknown): unknown {
79
+ if (Array.isArray(schema)) return schema.map(sanitizeForGemini)
80
+ if (schema && typeof schema === 'object') {
81
+ const out: Record<string, unknown> = {}
82
+ for (const [k, v] of Object.entries(schema as Record<string, unknown>)) {
83
+ if (GEMINI_DROP_KEYS.has(k)) continue
84
+ out[k] = sanitizeForGemini(v)
85
+ }
86
+ return out
87
+ }
88
+ return schema
89
+ }
90
+
91
+ export function geminiTools(mode: SessionMode = 'chat', context: ToolAvailabilityContext = {}): GeminiToolDefinition[] {
92
+ return toolsForMode(mode, context).map(tool => ({
93
+ name: tool.name,
94
+ description: tool.description,
95
+ parameters: sanitizeForGemini(tool.inputSchemaJson) as GeminiToolDefinition['parameters'],
96
+ }))
97
+ }
@@ -78,7 +78,7 @@ async function prepareWrite(
78
78
  assertSafeWritePath(input.path)
79
79
  assertNotPrivateContinuityWorkspacePath(input.path, context.config, 'write_file')
80
80
  if (input.content.length === 0) {
81
- throw new Error('write_file content is empty; provide non-empty file contents')
81
+ throw new Error('Tool write_file content is empty; provide non-empty file contents')
82
82
  }
83
83
 
84
84
  const fullPath = resolveWorkspacePath(context.workspaceRoot, input.path)
@@ -91,7 +91,7 @@ async function prepareWrite(
91
91
  async function readExistingFile(fullPath: string): Promise<{ before: string; existedBefore: boolean }> {
92
92
  try {
93
93
  const stats = await fs.stat(fullPath)
94
- if (stats.isDirectory()) throw new Error('write_file path points to a directory; provide a file path')
94
+ if (stats.isDirectory()) throw new Error('Tool write_file path points to a directory; provide a file path')
95
95
  return { before: await fs.readFile(fullPath, 'utf8'), existedBefore: true }
96
96
  } catch (error: unknown) {
97
97
  if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { before: '', existedBefore: false }
@@ -113,13 +113,13 @@ async function tryRecordRewindSnapshot(
113
113
  function assertSafeWritePath(requestedPath: string): void {
114
114
  const trimmed = requestedPath.trim()
115
115
  if (trimmed !== requestedPath || trimmed.length === 0) {
116
- throw new Error('write_file path must be a clean workspace-relative file path')
116
+ throw new Error('Tool write_file path must be a clean workspace-relative file path')
117
117
  }
118
118
  if (/[|;&<>`]/.test(trimmed)) {
119
- throw new Error('write_file path must not contain shell operators')
119
+ throw new Error('Tool write_file path must not contain shell operators')
120
120
  }
121
121
  if (/^(?:rm|del|erase|rmdir|remove-item|mkdir|type|cat|echo|copy|move|mv|cp)\b/i.test(trimmed)) {
122
- throw new Error('write_file path looks like a shell command; pass only the file path')
122
+ throw new Error('Tool write_file path looks like a shell command; pass only the file path')
123
123
  }
124
124
  }
125
125
 
@@ -1,56 +1,14 @@
1
1
  import React, { useEffect, useState } from 'react'
2
2
  import { Text, Box } from 'ink'
3
- import { eyeGradientColor, theme } from './theme.js'
3
+ import { theme } from './theme.js'
4
4
 
5
5
  const glyphs = {
6
- ethagent: {
7
- eth: `░░░░░░░╗░░░░░░░░╗░░╗ ░░╗
8
- ░░╔════╝╚══░░╔══╝░░║ ░░║
9
- ░░░░░╗ ░░║ ░░░░░░░║
10
- ░░╔══╝ ░░║ ░░╔══░░║
11
- ░░░░░░░╗ ░░║ ░░║ ░░║
12
- ╚══════╝ ╚═╝ ╚═╝ ╚═╝`,
13
- a: [
14
- ` █████╗ `,
15
- `██╔══██╗`,
16
- `███████║`,
17
- `██╔══██║`,
18
- `██║ ██║`,
19
- `╚═╝ ╚═╝`,
20
- ].join('\n'),
21
- g: [
22
- ` ██████╗ `,
23
- `██╔════╝ `,
24
- `██║ ███╗`,
25
- `██║ ██║`,
26
- `╚██████╔╝`,
27
- ` ╚═════╝ `,
28
- ].join('\n'),
29
- e: [
30
- `███████╗`,
31
- `██╔════╝`,
32
- `█████╗ `,
33
- `██╔══╝ `,
34
- `███████╗`,
35
- `╚══════╝`,
36
- ].join('\n'),
37
- n: [
38
- `███╗ ██╗`,
39
- `████╗ ██║`,
40
- `██╔██╗ ██║`,
41
- `██║╚██╗██║`,
42
- `██║ ╚████║`,
43
- `╚═╝ ╚═══╝`,
44
- ].join('\n'),
45
- t: [
46
- `████████╗`,
47
- `╚══██╔══╝`,
48
- ` ██║ `,
49
- ` ██║ `,
50
- ` ██║ `,
51
- ` ╚═╝ `,
52
- ].join('\n'),
53
- },
6
+ ethagent: `░░░░░░░╗░░░░░░░░╗░░╗ ░░╗ █████╗ ██████╗ ███████╗███╗ ██╗████████╗
7
+ ░░╔════╝╚══░░╔══╝░░║ ░░║██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝
8
+ ░░░░░╗ ░░║ ░░░░░░░║███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║
9
+ ░░╔══╝ ░░║ ░░╔══░░║██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║
10
+ ░░░░░░░╗ ░░║ ░░║ ░░║██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║
11
+ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ `,
54
12
  eyes: `
55
13
  -+:
56
14
  :=- -%@@@%.
@@ -79,33 +37,13 @@ const glyphs = {
79
37
  },
80
38
  } as const
81
39
 
82
- const ethagentGlyphOrder = ['eth', 'a', 'g', 'e', 'n', 't'] as const
83
-
84
40
  const Eyes = () => {
85
41
  const lines = glyphs.eyes.split('\n')
86
42
  return (
87
43
  <Box flexDirection="column">
88
- {lines.map((line, li) => {
89
- const glyphPositions = [...line]
90
- .map((char, index) => ({ char, index }))
91
- .filter(entry => entry.char.trim().length > 0)
92
- .map(entry => entry.index)
93
- const firstGlyph = glyphPositions[0] ?? 0
94
- const lastGlyph = glyphPositions[glyphPositions.length - 1] ?? firstGlyph
95
- const span = Math.max(lastGlyph - firstGlyph, 1)
96
-
97
- return (
98
- <Text key={li}>
99
- {[...line].map((char, ci) => {
100
- if (!char.trim()) {
101
- return <Text key={ci}>{char}</Text>
102
- }
103
- const t = (ci - firstGlyph) / span
104
- return <Text key={ci} color={eyeGradientColor(t)}>{char}</Text>
105
- })}
106
- </Text>
107
- )
108
- })}
44
+ {lines.map((line, li) => (
45
+ <Text key={li} color={theme.text}>{line}</Text>
46
+ ))}
109
47
  </Box>
110
48
  )
111
49
  }
@@ -135,19 +73,18 @@ export const BrandSplash: React.FC<SplashProps> = ({ contextLine, tipLine, updat
135
73
  return (
136
74
  <Box flexDirection="column" alignSelf="flex-start" padding={1}>
137
75
  <Eyes />
138
- <Text bold color={theme.accentPrimary}>ethagent</Text>
139
- <Text color={theme.dim}>{glyphs.tagline.trim()}</Text>
76
+ <Text bold color={theme.accentWhite}>ethagent</Text>
77
+ <Text color={theme.dim}>privacy-first AI agent with a portable <Text color={theme.accentPeriwinkle}>Ethereum</Text> identity</Text>
140
78
  {contextLine ? <Text color={theme.dim}>{contextLine}</Text> : null}
141
79
  {tipLine ? <Text color={theme.dim}>{tipLine}</Text> : null}
142
- {updateNotice ? <Text color={theme.accentPeach}>{updateNotice}</Text> : null}
80
+ {updateNotice ? <Text color={theme.accentPeriwinkle}>{updateNotice}</Text> : null}
143
81
  </Box>
144
82
  )
145
83
  }
146
84
 
147
- const logoLines = ethagentGlyphOrder.map(key => glyphs.ethagent[key].split('\n'))
148
- const rowCount = Math.max(...logoLines.map(lines => lines.length))
149
-
150
85
  const w = 69
86
+ const logoLines = glyphs.ethagent.split('\n').map(line => line.padEnd(w, ' '))
87
+
151
88
  const topPad = Math.max(0, w - glyphs.tagline.length - 1)
152
89
 
153
90
  const bottomInline = contextLine ? ` ${truncateToFit(contextLine, w - 4)} ` : ''
@@ -158,22 +95,20 @@ export const BrandSplash: React.FC<SplashProps> = ({ contextLine, tipLine, updat
158
95
  <Eyes />
159
96
  <Text>
160
97
  <Text color={theme.border}>{glyphs.frame.topLeft}</Text>
161
- <Text color={theme.dim}>{glyphs.tagline}</Text>
98
+ <Text color={theme.dim}>{' privacy-first AI agent with a portable '}<Text color={theme.accentPeriwinkle}>Ethereum</Text>{' identity '}</Text>
162
99
  <Text color={theme.border}>{glyphs.frame.horizontal.repeat(topPad)}{glyphs.frame.topRight}</Text>
163
100
  </Text>
164
- {Array.from({ length: rowCount }, (_, i) => (
101
+ {logoLines.map((line, i) => (
165
102
  <Box key={i}>
166
103
  <Text color={theme.border}>{glyphs.frame.side}</Text>
167
- {logoLines.map((lines, index) => (
168
- <Text key={ethagentGlyphOrder[index]} color={theme.border}>{lines[i] ?? ''}</Text>
169
- ))}
104
+ <Text color={theme.border}>{line}</Text>
170
105
  <Text color={theme.border}>{glyphs.frame.side}</Text>
171
106
  </Box>
172
107
  ))}
173
108
  {bottomInline ? (
174
109
  <Text>
175
110
  <Text color={theme.border}>{glyphs.frame.bottomLeft}</Text>
176
- <Text color={theme.accentMint}>{bottomInline}</Text>
111
+ <Text color={theme.accentPeriwinkle}>{bottomInline}</Text>
177
112
  <Text color={theme.border}>{glyphs.frame.horizontal.repeat(bottomPad)}{glyphs.frame.bottomRight}</Text>
178
113
  </Text>
179
114
  ) : (
@@ -182,7 +117,7 @@ export const BrandSplash: React.FC<SplashProps> = ({ contextLine, tipLine, updat
182
117
  {tipLine || updateNotice ? (
183
118
  <Box marginTop={1} flexDirection="column">
184
119
  {tipLine ? <Text color={theme.dim}>{tipLine}</Text> : null}
185
- {updateNotice ? <Text color={theme.accentPeach}>{updateNotice}</Text> : null}
120
+ {updateNotice ? <Text color={theme.accentPeriwinkle}>{updateNotice}</Text> : null}
186
121
  </Box>
187
122
  ) : null}
188
123
  </Box>
@@ -1,24 +1,22 @@
1
1
  import React from 'react'
2
2
  import { Box, Text } from 'ink'
3
- import { theme, gradientColor, eyeGradientColor } from './theme.js'
3
+ import { theme, gradientColor } from './theme.js'
4
4
 
5
5
  type ProgressBarProps = {
6
6
  progress: number
7
7
  width?: number
8
8
  label?: string
9
9
  suffix?: string
10
- variant?: 'default' | 'rainbow'
11
10
  }
12
11
 
13
- export const ProgressBar: React.FC<ProgressBarProps> = ({ progress, width = 40, label, suffix, variant = 'default' }) => {
12
+ export const ProgressBar: React.FC<ProgressBarProps> = ({ progress, width = 40, label, suffix }) => {
14
13
  const p = Math.max(0, Math.min(1, progress))
15
14
  const filled = Math.round(p * width)
16
15
  const empty = Math.max(0, width - filled)
17
- const colorFor = variant === 'rainbow' ? eyeGradientColor : gradientColor
18
16
  const cells: React.ReactElement[] = []
19
17
  for (let i = 0; i < filled; i++) {
20
18
  cells.push(
21
- <Text key={`f-${i}`} color={colorFor(i / Math.max(width - 1, 1))}>█</Text>,
19
+ <Text key={`f-${i}`} color={gradientColor(i / Math.max(width - 1, 1))}>█</Text>,
22
20
  )
23
21
  }
24
22
  for (let i = 0; i < empty; i++) {
package/src/ui/Select.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react'
1
+ import React, { useEffect, useMemo, useState } from 'react'
2
2
  import { Box, Text } from 'ink'
3
3
  import { theme } from './theme.js'
4
4
  import { useAppInput } from '../app/input/AppInputProvider.js'
@@ -39,9 +39,18 @@ export function Select<T>({
39
39
  onCancel,
40
40
  onHighlight,
41
41
  }: SelectProps<T>) {
42
+ const optionsSignature = useMemo(
43
+ () => options.map(option => `${String(option.value)}:${option.disabled ? 'disabled' : 'enabled'}:${option.role ?? 'option'}`).join('|'),
44
+ [options],
45
+ )
42
46
  const firstEnabled = Math.max(0, options.findIndex(isSelectableOption))
43
47
  const start = isSelectableOption(options[initialIndex]) ? initialIndex : firstEnabled
44
48
  const [index, setIndex] = useState(start === -1 ? 0 : start)
49
+
50
+ useEffect(() => {
51
+ setIndex(start === -1 ? 0 : start)
52
+ }, [optionsSignature, start])
53
+
45
54
  const visibleCount = Math.max(1, maxVisible ?? options.length)
46
55
  const windowStart = Math.max(0, Math.min(
47
56
  index - Math.floor(visibleCount / 2),
@@ -73,7 +82,7 @@ export function Select<T>({
73
82
  else if (key.return) {
74
83
  const selected = options[index]
75
84
  if (isSelectableOption(selected)) onSubmit(selected.value)
76
- } else if (key.escape) {
85
+ } else if (key.escape || (key.ctrl && input === 'c')) {
77
86
  onCancel?.()
78
87
  }
79
88
  })
@@ -88,24 +97,27 @@ export function Select<T>({
88
97
  const absoluteIndex = windowStart + visibleIndex
89
98
  const isActive = absoluteIndex === index
90
99
  const selectable = isSelectableOption(option)
100
+ const disabled = !!option.disabled
91
101
  const cursor = !selectable ? ' ' : isActive ? '>' : ' '
92
102
  const isSection = option.role === 'section' || option.role === 'group'
93
103
  const prefix = option.prefix && !isSection ? `${option.prefix} ` : ''
94
104
  const rowIndent = option.indent ?? (usesInlineSections ? isSection ? 1 : 3 : 0)
95
- const prefixColor = option.disabled
105
+ const prefixColor = disabled
96
106
  ? option.labelColor ?? theme.border
97
107
  : isActive && selectable
98
- ? theme.accentPrimary
108
+ ? theme.accentPeriwinkle
99
109
  : option.labelColor ?? theme.dim
100
110
  const labelColor = isSection
101
- ? option.labelColor ?? theme.dim
111
+ ? option.labelColor ?? theme.textSubtle
102
112
  : isActive && selectable
103
- ? theme.accentPrimary
104
- : option.labelColor ?? (option.disabled ? theme.dim : theme.text)
113
+ ? theme.accentPeriwinkle
114
+ : option.labelColor ?? (disabled ? theme.dim : theme.text)
105
115
  const hintColor = isActive && selectable
106
116
  ? theme.textSubtle
107
- : option.hintColor ?? theme.dim
108
- const subtextColor = option.subtextColor ?? theme.dim
117
+ : disabled
118
+ ? theme.border
119
+ : option.hintColor ?? theme.dim
120
+ const subtextColor = disabled ? theme.border : option.subtextColor ?? theme.dim
109
121
  const bold = option.bold ?? (isSection || (isActive && selectable))
110
122
  const inlineHint = Boolean(option.hint && hintLayout === 'inline' && !isSection)
111
123
  const belowHint = Boolean(option.hint && (!inlineHint || isSection))
@@ -193,6 +193,15 @@ export function pickVerb(): string {
193
193
  return SPINNER_VERBS[idx] ?? 'thinking'
194
194
  }
195
195
 
196
+ export function spinnerText(value: string): string {
197
+ const text = restoreSpinnerTerms(value.toLowerCase())
198
+ return text.replace(/^(\s*)([a-z])/, (_match, prefix: string, letter: string) => `${prefix}${letter.toUpperCase()}`)
199
+ }
200
+
201
+ export function spinnerHintText(value: string): string {
202
+ return restoreSpinnerTerms(value.toLowerCase())
203
+ }
204
+
196
205
  type SpinnerProps = {
197
206
  active?: boolean
198
207
  hint?: string
@@ -210,7 +219,7 @@ export const Spinner: React.FC<SpinnerProps> = ({
210
219
  hint: rawHint,
211
220
  label,
212
221
  verb,
213
- color = theme.accentSecondary,
222
+ color = theme.accentPeriwinkle,
214
223
  startedAt,
215
224
  showElapsed = true,
216
225
  }) => {
@@ -246,11 +255,11 @@ export const Spinner: React.FC<SpinnerProps> = ({
246
255
  if (!active) return null
247
256
 
248
257
  const autoLabel = stickyVerbRef.current ?? verb ?? 'thinking'
249
- const text = label ?? `${autoLabel}…`
258
+ const text = spinnerText(label ?? `${autoLabel}…`)
250
259
  const glyph = FRAMES[frame] ?? 'o'
251
260
  const elapsed = showElapsed ? formatElapsedSeconds(Date.now() - (startedAt ?? internalStartedAtRef.current)) : null
252
261
  const renderedHint = [rawHint, elapsed].filter(Boolean).join(' · ')
253
- const hint = renderedHint
262
+ const hint = renderedHint ? spinnerHintText(renderedHint) : ''
254
263
 
255
264
  return (
256
265
  <Text>
@@ -267,3 +276,29 @@ function formatElapsedSeconds(milliseconds: number): string {
267
276
  const minutes = Math.floor(seconds / 60)
268
277
  return `${minutes}:${(seconds % 60).toString().padStart(2, '0')}`
269
278
  }
279
+
280
+ function restoreSpinnerTerms(value: string): string {
281
+ return value
282
+ .replace(/\bapi\b/g, 'API')
283
+ .replace(/\bens\b/g, 'ENS')
284
+ .replace(/\berc-8004\b/g, 'ERC-8004')
285
+ .replace(/\bgguf\b/g, 'GGUF')
286
+ .replace(/\bhugging face\b/g, 'Hugging Face')
287
+ .replace(/\bipfs\b/g, 'IPFS')
288
+ .replace(/\bjson\b/g, 'JSON')
289
+ .replace(/\bjwt\b/g, 'JWT')
290
+ .replace(/\bmemory\.md\b/g, 'MEMORY.md')
291
+ .replace(/\bopenai\b/g, 'OpenAI')
292
+ .replace(/\banthropic\b/g, 'Anthropic')
293
+ .replace(/\bgemini\b/g, 'Gemini')
294
+ .replace(/\bos\b/g, 'OS')
295
+ .replace(/\brpc\b/g, 'RPC')
296
+ .replace(/\bsoul\.md\b/g, 'SOUL.md')
297
+ .replace(/\buri\b/g, 'URI')
298
+ .replace(/\burl\b/g, 'URL')
299
+ .replace(/\bbase\b/g, 'Base')
300
+ .replace(/\bethereum mainnet\b/g, 'Ethereum Mainnet')
301
+ .replace(/\bethereum\b/g, 'Ethereum')
302
+ .replace(/\bsepolia\b/g, 'Sepolia')
303
+ .replace(/\bbase sepolia\b/g, 'Base Sepolia')
304
+ }
@@ -13,9 +13,9 @@ type SurfaceProps = {
13
13
  }
14
14
 
15
15
  const toneColor: Record<SurfaceTone, string> = {
16
- primary: theme.accentPrimary,
16
+ primary: theme.accentPeriwinkle,
17
17
  muted: theme.border,
18
- error: '#e87070',
18
+ error: theme.accentError,
19
19
  }
20
20
 
21
21
  export const Surface: React.FC<SurfaceProps> = ({
@@ -27,7 +27,7 @@ export const Surface: React.FC<SurfaceProps> = ({
27
27
  }) => {
28
28
  const borderColor = toneColor[tone]
29
29
  return (
30
- <Box flexDirection="column" borderStyle="round" borderColor={borderColor} paddingX={2} paddingY={0}>
30
+ <Box flexDirection="column" borderStyle="round" borderColor={borderColor} paddingX={2} paddingY={0} width="100%">
31
31
  <Box flexDirection="column">
32
32
  <Text color={borderColor} bold>{title}</Text>
33
33
  {subtitle ? (