ethagent 3.3.3 → 4.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 (322) hide show
  1. package/.claude-plugin/marketplace.json +11 -0
  2. package/.claude-plugin/plugin.json +35 -0
  3. package/LICENSE +1 -1
  4. package/README.md +64 -104
  5. package/commands/ethagent.md +40 -0
  6. package/package.json +16 -16
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -6
  8. package/src/app/keybindings/types.ts +1 -6
  9. package/src/cli/ResetConfirmView.tsx +54 -53
  10. package/src/cli/demo.ts +86 -0
  11. package/src/cli/hookIo.ts +45 -0
  12. package/src/cli/main.tsx +94 -123
  13. package/src/cli/memoryGuard.ts +49 -0
  14. package/src/cli/reset.ts +28 -70
  15. package/src/cli/sessionStart.ts +33 -0
  16. package/src/cli/status.ts +46 -0
  17. package/src/cli/sync.ts +167 -0
  18. package/src/cli/syncAdapters/claude-code.ts +86 -0
  19. package/src/cli/syncAdapters/codex.ts +66 -0
  20. package/src/cli/syncAdapters/index.ts +45 -0
  21. package/src/cli/syncAdapters/managedBlock.ts +175 -0
  22. package/src/cli/syncAdapters/shared.ts +63 -0
  23. package/src/identity/continuity/envelopeParse.ts +20 -1
  24. package/src/identity/continuity/publicSkills.ts +3 -1
  25. package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
  26. package/src/identity/continuity/skills/scaffold.ts +5 -2
  27. package/src/identity/continuity/snapshots.ts +12 -5
  28. package/src/identity/continuity/storage/defaults.ts +20 -19
  29. package/src/identity/continuity/storage/status.ts +1 -1
  30. package/src/identity/ens/ensLookup/constants.ts +1 -1
  31. package/src/identity/manager/IdentityManager.tsx +33 -0
  32. package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
  33. package/src/identity/{hub → manager}/Routes.tsx +48 -34
  34. package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
  35. package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
  36. package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
  37. package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
  38. package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
  39. package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
  40. package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
  41. package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
  42. package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
  43. package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
  44. package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
  45. package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
  46. package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
  47. package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
  48. package/src/identity/{hub → manager}/create/effects.ts +19 -10
  49. package/src/identity/manager/create/importScan.ts +122 -0
  50. package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
  51. package/src/identity/{hub → manager}/custody/actions.ts +1 -15
  52. package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
  53. package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
  54. package/src/identity/{hub → manager}/custody/types.ts +1 -2
  55. package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
  56. package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
  57. package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
  58. package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
  59. package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
  60. package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
  61. package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
  62. package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
  63. package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
  64. package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
  65. package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
  66. package/src/identity/{hub → manager}/profile/effects.ts +1 -3
  67. package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
  68. package/src/identity/{hub → manager}/profile/state.ts +1 -1
  69. package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
  70. package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
  71. package/src/identity/{hub → manager}/restore/apply.ts +1 -1
  72. package/src/identity/{hub → manager}/restore/auth.ts +1 -1
  73. package/src/identity/{hub → manager}/restore/discover.ts +1 -1
  74. package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
  75. package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
  76. package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
  77. package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
  78. package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
  79. package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
  80. package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
  81. package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
  82. package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
  83. package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
  84. package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
  85. package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
  86. package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
  87. package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
  88. package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
  89. package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
  90. package/src/identity/manager/shared/effects/types.ts +30 -0
  91. package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
  92. package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
  93. package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
  94. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
  95. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
  96. package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
  97. package/src/identity/{hub → manager}/shared/utils.ts +6 -10
  98. package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
  99. package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
  100. package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
  101. package/src/identity/{hub → manager}/types.ts +5 -6
  102. package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
  103. package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
  104. package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
  105. package/src/identity/registry/erc8004/discovery.ts +3 -17
  106. package/src/identity/registry/erc8004/utils.ts +1 -1
  107. package/src/identity/storage/ipfs.ts +21 -1
  108. package/src/identity/wallet/browserWallet/html.ts +10 -2
  109. package/src/identity/wallet/browserWallet/http.ts +18 -0
  110. package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
  111. package/src/identity/wallet/browserWallet/requests.ts +10 -28
  112. package/src/identity/wallet/browserWallet/session.ts +26 -33
  113. package/src/identity/wallet/browserWallet/validation.ts +14 -0
  114. package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
  115. package/src/identity/wallet/page/boot.ts +43 -0
  116. package/src/identity/wallet/page/config.ts +59 -0
  117. package/src/identity/wallet/page/constants.ts +12 -0
  118. package/src/identity/wallet/page/copy.ts +47 -68
  119. package/src/identity/wallet/page/css.ts +638 -0
  120. package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
  121. package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
  122. package/src/identity/wallet/page/markup.ts +44 -34
  123. package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
  124. package/src/identity/wallet/page/resize.ts +95 -0
  125. package/src/identity/wallet/page/state.ts +135 -8
  126. package/src/identity/wallet/page/timeline.ts +161 -0
  127. package/src/identity/wallet/page/view.ts +22 -302
  128. package/src/storage/config.ts +30 -80
  129. package/src/storage/reset.ts +31 -0
  130. package/src/storage/secrets.ts +1 -16
  131. package/src/ui/Select.tsx +27 -5
  132. package/src/ui/Spinner.tsx +16 -15
  133. package/src/ui/Surface.tsx +21 -17
  134. package/src/ui/TextArea.tsx +173 -0
  135. package/src/ui/TextInput.tsx +31 -133
  136. package/src/ui/theme.ts +22 -13
  137. package/src/utils/clipboard.ts +0 -140
  138. package/src/app/FirstRun.tsx +0 -577
  139. package/src/app/FirstRunTimeline.tsx +0 -51
  140. package/src/app/firstRunConfig.ts +0 -26
  141. package/src/app/hooks/useCancelRequest.ts +0 -22
  142. package/src/app/hooks/useDoublePress.ts +0 -46
  143. package/src/app/hooks/useExitOnCtrlC.ts +0 -36
  144. package/src/auth/openaiOAuth/credentials.ts +0 -47
  145. package/src/auth/openaiOAuth/crypto.ts +0 -23
  146. package/src/auth/openaiOAuth/index.ts +0 -238
  147. package/src/auth/openaiOAuth/landingPage.ts +0 -116
  148. package/src/auth/openaiOAuth/listener.ts +0 -151
  149. package/src/auth/openaiOAuth/refresh.ts +0 -70
  150. package/src/auth/openaiOAuth/shared.ts +0 -115
  151. package/src/chat/ChatBottomPane.tsx +0 -296
  152. package/src/chat/ChatScreen.tsx +0 -1685
  153. package/src/chat/ConversationStack.tsx +0 -56
  154. package/src/chat/MessageList.tsx +0 -638
  155. package/src/chat/SessionStatus.tsx +0 -53
  156. package/src/chat/chatEnvironment.ts +0 -16
  157. package/src/chat/chatScreenUtils.ts +0 -194
  158. package/src/chat/chatSessionState.ts +0 -146
  159. package/src/chat/chatTurnContext.ts +0 -50
  160. package/src/chat/chatTurnOrchestrator.ts +0 -603
  161. package/src/chat/chatTurnRows.ts +0 -64
  162. package/src/chat/commands.ts +0 -494
  163. package/src/chat/continuityEditReview.ts +0 -42
  164. package/src/chat/display/DiffView.tsx +0 -193
  165. package/src/chat/display/SyntaxText.tsx +0 -192
  166. package/src/chat/display/toolCallDisplay.ts +0 -103
  167. package/src/chat/display/toolResultDisplay.ts +0 -19
  168. package/src/chat/input/ChatInput.tsx +0 -625
  169. package/src/chat/input/chatInputHelpers.ts +0 -62
  170. package/src/chat/input/chatInputState.ts +0 -247
  171. package/src/chat/input/chatPaste.ts +0 -49
  172. package/src/chat/input/imageRefs.ts +0 -30
  173. package/src/chat/input/inputRendering.tsx +0 -93
  174. package/src/chat/input/textCursor.ts +0 -212
  175. package/src/chat/messageMarkdown.ts +0 -220
  176. package/src/chat/messageRows.ts +0 -43
  177. package/src/chat/planImplementation.ts +0 -62
  178. package/src/chat/slashCommandHandlers.ts +0 -122
  179. package/src/chat/slashCommandViews.ts +0 -120
  180. package/src/chat/transcript/TranscriptView.tsx +0 -184
  181. package/src/chat/transcript/transcriptViewport.ts +0 -295
  182. package/src/chat/views/ContextLimitView.tsx +0 -95
  183. package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
  184. package/src/chat/views/CopyPicker.tsx +0 -50
  185. package/src/chat/views/PermissionPrompt.tsx +0 -156
  186. package/src/chat/views/PermissionsView.tsx +0 -165
  187. package/src/chat/views/PlanApprovalView.tsx +0 -91
  188. package/src/chat/views/ResumeView.tsx +0 -273
  189. package/src/chat/views/RewindView.tsx +0 -412
  190. package/src/cli/preview.tsx +0 -14
  191. package/src/cli/updateNotice.ts +0 -54
  192. package/src/identity/continuity/privateEdit/apply.ts +0 -170
  193. package/src/identity/continuity/privateEdit/diff.ts +0 -6
  194. package/src/identity/continuity/privateEdit/files.ts +0 -23
  195. package/src/identity/continuity/privateEdit/types.ts +0 -28
  196. package/src/identity/continuity/privateEdit.ts +0 -46
  197. package/src/identity/hub/IdentityHub.tsx +0 -14
  198. package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
  199. package/src/identity/hub/ens/effects.ts +0 -218
  200. package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
  201. package/src/identity/hub/shared/effects/types.ts +0 -53
  202. package/src/identity/hub/shared/reconciliation/index.ts +0 -14
  203. package/src/identity/wallet/page/grainient.ts +0 -278
  204. package/src/identity/wallet/page/html.ts +0 -28
  205. package/src/identity/wallet/page/styles/base.ts +0 -259
  206. package/src/identity/wallet/page/styles/components.ts +0 -262
  207. package/src/identity/wallet/page/styles/index.ts +0 -5
  208. package/src/identity/wallet/page/styles/responsive.ts +0 -247
  209. package/src/identity/wallet/page.tsx +0 -38
  210. package/src/mcp/approvals.ts +0 -113
  211. package/src/mcp/config.ts +0 -235
  212. package/src/mcp/manager.ts +0 -482
  213. package/src/mcp/managerHelpers.ts +0 -70
  214. package/src/mcp/names.ts +0 -19
  215. package/src/mcp/output.ts +0 -96
  216. package/src/models/ModelPicker.tsx +0 -1009
  217. package/src/models/catalog.ts +0 -327
  218. package/src/models/huggingface.ts +0 -712
  219. package/src/models/huggingfaceStorage.ts +0 -136
  220. package/src/models/llamacpp.ts +0 -848
  221. package/src/models/llamacppCommands.ts +0 -44
  222. package/src/models/llamacppConfig.ts +0 -34
  223. package/src/models/llamacppDiscovery.ts +0 -176
  224. package/src/models/llamacppOutput.ts +0 -65
  225. package/src/models/llamacppPreflight.ts +0 -158
  226. package/src/models/modelDisplay.ts +0 -180
  227. package/src/models/modelPickerCatalogFlow.ts +0 -56
  228. package/src/models/modelPickerCredentials.ts +0 -166
  229. package/src/models/modelPickerData.ts +0 -41
  230. package/src/models/modelPickerDisplay.tsx +0 -132
  231. package/src/models/modelPickerHfFlow.ts +0 -192
  232. package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
  233. package/src/models/modelPickerOptions.ts +0 -457
  234. package/src/models/modelPickerTypes.ts +0 -69
  235. package/src/models/modelPickerUninstallFlow.ts +0 -48
  236. package/src/models/modelPickerViewHelpers.ts +0 -174
  237. package/src/models/modelRecommendation.ts +0 -139
  238. package/src/models/providerDisplay.ts +0 -16
  239. package/src/models/runtimeDetection.ts +0 -81
  240. package/src/models/uncensoredCatalog.ts +0 -86
  241. package/src/providers/anthropic.ts +0 -290
  242. package/src/providers/contracts.ts +0 -71
  243. package/src/providers/errors.ts +0 -80
  244. package/src/providers/gemini.ts +0 -391
  245. package/src/providers/openai-chat.ts +0 -474
  246. package/src/providers/openai-responses-format.ts +0 -177
  247. package/src/providers/openai-responses.ts +0 -306
  248. package/src/providers/openaiChatWire.ts +0 -124
  249. package/src/providers/registry.ts +0 -120
  250. package/src/providers/retry.ts +0 -58
  251. package/src/providers/sse.ts +0 -93
  252. package/src/runtime/compaction.ts +0 -395
  253. package/src/runtime/cwd.ts +0 -43
  254. package/src/runtime/providerTurn.ts +0 -38
  255. package/src/runtime/sessionMode.ts +0 -55
  256. package/src/runtime/systemPrompt.ts +0 -213
  257. package/src/runtime/textToolParser.ts +0 -161
  258. package/src/runtime/toolClaimGuards.ts +0 -143
  259. package/src/runtime/toolExecution.ts +0 -304
  260. package/src/runtime/toolIntent.ts +0 -143
  261. package/src/runtime/turn.ts +0 -369
  262. package/src/runtime/turnNudges.ts +0 -223
  263. package/src/runtime/turnTypes.ts +0 -86
  264. package/src/storage/factoryReset.ts +0 -127
  265. package/src/storage/history.ts +0 -58
  266. package/src/storage/permissions.ts +0 -76
  267. package/src/storage/rewind.ts +0 -266
  268. package/src/storage/sessionExport.ts +0 -49
  269. package/src/storage/sessions.ts +0 -495
  270. package/src/tools/bashSafety.ts +0 -186
  271. package/src/tools/bashTool.ts +0 -140
  272. package/src/tools/changeDirectoryTool.ts +0 -213
  273. package/src/tools/contracts.ts +0 -192
  274. package/src/tools/deleteFileTool.ts +0 -116
  275. package/src/tools/editTool.ts +0 -165
  276. package/src/tools/editUtils.ts +0 -170
  277. package/src/tools/fileDiff.ts +0 -261
  278. package/src/tools/listDirectoryTool.ts +0 -55
  279. package/src/tools/listSkillFilesTool.ts +0 -77
  280. package/src/tools/listSkillsTool.ts +0 -68
  281. package/src/tools/mcpResourceTools.ts +0 -95
  282. package/src/tools/permissionRules.ts +0 -85
  283. package/src/tools/privateContinuityEditTool.ts +0 -187
  284. package/src/tools/privateContinuityReadTool.ts +0 -106
  285. package/src/tools/readSkillTool.ts +0 -107
  286. package/src/tools/readTool.ts +0 -85
  287. package/src/tools/registry.ts +0 -103
  288. package/src/tools/writeFileTool.ts +0 -167
  289. package/src/ui/BrandSplash.tsx +0 -133
  290. package/src/ui/terminalTitle.ts +0 -30
  291. package/src/utils/images.ts +0 -140
  292. package/src/utils/markdownSegments.ts +0 -51
  293. package/src/utils/messages.ts +0 -37
  294. package/src/utils/withRetry.ts +0 -324
  295. /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
  296. /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
  297. /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
  298. /package/src/identity/{hub → manager}/custody/state.ts +0 -0
  299. /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
  300. /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
  301. /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
  302. /package/src/identity/{hub → manager}/ens/state.ts +0 -0
  303. /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
  304. /package/src/identity/{hub → manager}/ens/types.ts +0 -0
  305. /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
  306. /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
  307. /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
  308. /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
  309. /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
  310. /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
  311. /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
  312. /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
  313. /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
  314. /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
  315. /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
  316. /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
  317. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
  318. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
  319. /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
  320. /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
  321. /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
  322. /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
@@ -1,30 +1,10 @@
1
- import { config } from './state.js'
2
- import type { WalletErrorPayload } from './types.js'
3
- import { glyphs } from './html.js'
4
- import {
5
- accountCopy,
6
- chainLabel,
7
- FLOW_COPY,
8
- isTransactionFlow,
9
- purposeCopy,
10
- signCopy,
11
- STATE_TITLES,
12
- shortAddr,
13
- transactionCopy,
14
- transactionPurposeTitle,
15
- type FlowCopy,
16
- } from './copy.js'
17
- import { walletErrorHtml } from './errorView.js'
18
-
19
1
  let card: HTMLElement;
20
- let promptText: HTMLElement;
21
2
  let flowTitle: HTMLElement;
22
- let networkRow: HTMLElement;
3
+ let flowSubtitle: HTMLElement;
23
4
  let flowDetail: HTMLElement;
24
5
  let detailsBlock: HTMLElement;
25
6
  let detailKey: HTMLElement;
26
7
  let detailVal: HTMLElement;
27
- let netVal: HTMLElement;
28
8
  let statusBlock: HTMLElement;
29
9
  let statusMarker: HTMLElement;
30
10
  export let statusText: HTMLElement;
@@ -41,14 +21,12 @@ function requiredElement<T extends HTMLElement>(id: string): T {
41
21
 
42
22
  export function initializeViewElements(): void {
43
23
  card = requiredElement("card");
44
- promptText = requiredElement("prompt-text");
45
24
  flowTitle = requiredElement("flow-title");
46
- networkRow = requiredElement("network-row");
25
+ flowSubtitle = requiredElement("flow-subtitle");
47
26
  flowDetail = requiredElement("flow-detail");
48
27
  detailsBlock = requiredElement("details-block");
49
28
  detailKey = requiredElement("detail-key");
50
29
  detailVal = requiredElement("detail-val");
51
- netVal = requiredElement("net-val");
52
30
  statusBlock = requiredElement("status-block");
53
31
  statusMarker = requiredElement("status-marker");
54
32
  statusText = requiredElement("status-text");
@@ -58,17 +36,27 @@ export function initializeViewElements(): void {
58
36
  cancel = requiredElement("cancel");
59
37
  }
60
38
 
39
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
40
+ const SPINNER_INTERVAL_MS = 80;
61
41
  let spinning = false;
42
+ let spinnerTimer: ReturnType<typeof setInterval> | null = null;
43
+ let spinnerIndex = 0;
62
44
  function startSpinner(): void {
63
45
  spinning = true;
64
- statusMarker.innerHTML = '<span class="spinner" aria-hidden="true"></span>';
65
- statusMarker.style.background = "transparent";
46
+ spinnerIndex = 0;
47
+ statusMarker.innerHTML = '<span class="spinner" aria-hidden="true">' + SPINNER_FRAMES[0] + '</span>';
48
+ const glyph = statusMarker.firstElementChild as HTMLElement | null;
49
+ if (spinnerTimer) clearInterval(spinnerTimer);
50
+ spinnerTimer = setInterval(() => {
51
+ spinnerIndex = (spinnerIndex + 1) % SPINNER_FRAMES.length;
52
+ if (glyph) glyph.textContent = SPINNER_FRAMES[spinnerIndex]!;
53
+ }, SPINNER_INTERVAL_MS);
66
54
  }
67
55
  function stopSpinner(): void {
56
+ if (spinnerTimer) { clearInterval(spinnerTimer); spinnerTimer = null; }
68
57
  if (!spinning) return;
69
58
  spinning = false;
70
59
  statusMarker.innerHTML = "";
71
- statusMarker.style.background = "";
72
60
  }
73
61
  function setMarker(text: string): void { stopSpinner(); statusMarker.textContent = text; }
74
62
  export function flowCopy(): FlowCopy { return FLOW_COPY[config.kind] || FLOW_COPY.sign!; }
@@ -79,6 +67,7 @@ export function tabTitleForState(state: string): string {
79
67
  if (state === "preparing-transaction") return STATE_TITLES.preparingTransaction;
80
68
  if (state === "approve-transaction") return STATE_TITLES.approveTransaction;
81
69
  if (state === "error") return STATE_TITLES.error;
70
+ if (state === "cancelled") return STATE_TITLES.cancelled;
82
71
  if (state === "approve") {
83
72
  if (config.kind === "account") return accountCopy().text;
84
73
  if (config.kind === "sign") return STATE_TITLES.approveSign;
@@ -101,7 +90,7 @@ export function setTabTitle(title?: string): void {
101
90
  const t = title || flowCopy().tabTitle || STATE_TITLES.default;
102
91
  document.title = t;
103
92
  const chromeTitle = document.getElementById("chrome-title");
104
- if (chromeTitle) chromeTitle.textContent = "ethagent · " + t;
93
+ if (chromeTitle) chromeTitle.textContent = t;
105
94
  }
106
95
 
107
96
  function messagePreview(message?: string): string {
@@ -118,6 +107,7 @@ export function showPreparedMessage(message: string): void {
118
107
  if (copy.detail !== "message") return;
119
108
  const preview = messagePreview(message);
120
109
  detailKey.textContent = copy.detail;
110
+ flowDetail.dataset.detail = copy.detail;
121
111
  detailVal.textContent = preview;
122
112
  flowDetail.hidden = preview.length === 0;
123
113
  detailsBlock.hidden = flowDetail.hidden;
@@ -126,7 +116,6 @@ export function showPreparedMessage(message: string): void {
126
116
  export function applyFlowChrome(): void {
127
117
  const copy = flowCopy();
128
118
  card.dataset.flow = copy.accent;
129
- promptText.textContent = copy.label;
130
119
  flowTitle.textContent =
131
120
  config.kind === "account" && config.purpose
132
121
  ? purposeCopy().flowTitle
@@ -135,290 +124,21 @@ export function applyFlowChrome(): void {
135
124
  : config.kind === "transaction"
136
125
  ? transactionPurposeTitle()
137
126
  : copy.title;
127
+ const subtitle = (config.purpose && purposeSubtitle()) || copy.subtitle || "";
128
+ flowSubtitle.textContent = subtitle;
129
+ flowSubtitle.hidden = subtitle.length === 0;
138
130
  setTabTitle(copy.tabTitle);
139
131
  applyTransferTimeline();
140
132
  if (!copy.detail) {
141
- networkRow.hidden = true;
142
133
  flowDetail.hidden = true;
143
134
  detailsBlock.hidden = true;
144
135
  } else {
145
- networkRow.hidden = false;
146
136
  flowDetail.hidden = false;
147
137
  detailsBlock.hidden = false;
148
- netVal.textContent = chainLabel(config.chainIdHex);
149
138
  detailKey.textContent = copy.detail;
139
+ flowDetail.dataset.detail = copy.detail;
150
140
  detailVal.textContent = detailPreview(copy);
151
141
  flowDetail.hidden = detailVal.textContent.length === 0;
152
142
  if (flowDetail.hidden) detailsBlock.hidden = true;
153
143
  }
154
144
  }
155
-
156
- type LifecycleId =
157
- | "ens-clear"
158
- | "ens-link"
159
- | "ens-update"
160
- | "ens-register"
161
- | "custody-switch"
162
- | "public-profile-vault";
163
-
164
- const LIFECYCLE_DEFINITIONS: Record<LifecycleId, { steps: string[] }> = {
165
- "ens-clear": { steps: ["Clear Records on Mainnet", "Save Cleared Snapshot"] },
166
- "ens-link": { steps: ["Create Subdomain", "Set Records", "Save Snapshot"] },
167
- "ens-update": { steps: ["Update Records on Mainnet", "Save Updated Snapshot"] },
168
- "ens-register": { steps: ["Commit ENS Name", "Register ENS Name"] },
169
- "custody-switch": { steps: ["Deploy Vault", "Deposit Token", "Reconcile Operators"] },
170
- "public-profile-vault": { steps: ["Sign Profile", "Save Through Vault"] },
171
- };
172
-
173
- const FLOW_LIFECYCLE: Record<string, LifecycleId> = {
174
- "ens-clear": "ens-clear",
175
- "ens-link": "ens-link",
176
- "ens-update": "ens-update",
177
- "ens-register": "ens-register",
178
- "custody-switch": "custody-switch",
179
- "public-profile-vault": "public-profile-vault",
180
- };
181
-
182
- const PURPOSE_TIMELINE: Record<string, readonly [string, string]> = {
183
- "create-agent": ["Sign Recovery Access", "Mint Token"],
184
- "update-snapshot-owner": ["Sign Snapshot", "Save Onchain"],
185
- "update-snapshot-operator": ["Sign Snapshot", "Save Onchain"],
186
- "update-snapshot-connected": ["Sign Snapshot", "Save Onchain"],
187
- "update-profile-owner": ["Sign Profile", "Save Onchain"],
188
- "update-profile-operator": ["Sign Profile", "Save Onchain"],
189
- "update-profile-connected": ["Sign Profile", "Save Onchain"],
190
- "update-operators": ["Sign Operator List", "Publish List"],
191
- "create-simple-ens-subdomain": ["Sign Request", "Create Subdomain"],
192
- "set-simple-ens-records": ["Sign Records", "Write Records"],
193
- "create-agent-ens-subdomain": ["Sign Request", "Create Subdomain"],
194
- "set-agent-ens-records": ["Sign Records", "Write Records"],
195
- "rotate-agent-uri-vault-owner": ["Sign Update", "Save Through Vault"],
196
- "rotate-agent-uri-vault-operator": ["Sign Update", "Save Through Vault"],
197
- "update-ens": ["Sign Snapshot", "Save Onchain"],
198
- "clear-ens": ["Sign Snapshot", "Save Onchain"],
199
- };
200
-
201
- function activeLifecycle(): LifecycleId | undefined {
202
- if (config.flowId && FLOW_LIFECYCLE[config.flowId]) return FLOW_LIFECYCLE[config.flowId];
203
- return undefined;
204
- }
205
-
206
- let currentTimelineKey: string | null = null;
207
-
208
- function lifecycleStepIndex(lifecycle: LifecycleId, state: string | null): number {
209
- const flowStep = typeof config.flowStep === "number" ? config.flowStep : 1;
210
- const def = LIFECYCLE_DEFINITIONS[lifecycle];
211
- const steps = def.steps.length;
212
- if (state === "done") return Math.min(flowStep, steps);
213
- return Math.max(0, Math.min(flowStep - 1, steps - 1));
214
- }
215
-
216
- function hasNextLifecyclePrompt(): boolean {
217
- const lifecycle = activeLifecycle();
218
- if (!lifecycle) return false;
219
- const flowStep = typeof config.flowStep === "number" ? config.flowStep : 1;
220
- return flowStep < LIFECYCLE_DEFINITIONS[lifecycle].steps.length;
221
- }
222
-
223
- function nextLifecycleHint(): string {
224
- return "Keep this page open. The next wallet step will appear here.";
225
- }
226
-
227
- function purposeStepIndex(state: string | null): number {
228
- if (state === "approve-transaction" || state === "submitting") return 1;
229
- if (state === "done") return 2;
230
- return 0;
231
- }
232
-
233
- export function applyTransferTimeline(): void {
234
- const timeline = document.getElementById("timeline") as HTMLElement | null;
235
- if (!timeline) return;
236
-
237
- const lifecycle = activeLifecycle();
238
- let steps: string[] | null = null;
239
- let activeIndex = 0;
240
- let key = "";
241
-
242
- if (lifecycle) {
243
- steps = LIFECYCLE_DEFINITIONS[lifecycle].steps;
244
- activeIndex = lifecycleStepIndex(lifecycle, currentState);
245
- key = "flow:" + lifecycle;
246
- } else if (config.kind === "sign-transaction") {
247
- const tuple = PURPOSE_TIMELINE[config.purpose || ""] || (["Sign", "Submit"] as const);
248
- steps = [tuple[0], tuple[1]];
249
- activeIndex = purposeStepIndex(currentState);
250
- key = "purpose:" + (config.purpose || "");
251
- }
252
-
253
- if (!steps) {
254
- if (!timeline.hidden) timeline.hidden = true;
255
- currentTimelineKey = null;
256
- return;
257
- }
258
-
259
- if (currentTimelineKey !== key) {
260
- timeline.innerHTML = "";
261
- for (const action of steps) {
262
- const li = document.createElement("li");
263
- li.className = "timeline-step";
264
- li.dataset.state = "pending";
265
- const marker = document.createElement("span");
266
- marker.className = "timeline-marker";
267
- marker.setAttribute("aria-hidden", "true");
268
- const label = document.createElement("span");
269
- label.className = "timeline-label";
270
- const actionEl = document.createElement("span");
271
- actionEl.className = "timeline-action";
272
- actionEl.textContent = action;
273
- label.append(actionEl);
274
- li.append(marker, label);
275
- timeline.append(li);
276
- }
277
- timeline.setAttribute("aria-label", "Wallet popup steps");
278
- currentTimelineKey = key;
279
- }
280
-
281
- timeline.hidden = false;
282
- const stepEls = timeline.querySelectorAll<HTMLElement>(".timeline-step");
283
- stepEls.forEach((step, i) => {
284
- const next = i < activeIndex ? "done" : i === activeIndex ? "active" : "pending";
285
- if (step.dataset.state !== next) step.dataset.state = next;
286
- });
287
- }
288
-
289
- function markActiveTimelineStepDone(): void {
290
- const timeline = document.getElementById("timeline") as HTMLElement | null;
291
- if (!timeline || timeline.hidden) return;
292
- timeline.querySelectorAll<HTMLElement>('.timeline-step[data-state="active"]').forEach((step) => {
293
- step.dataset.state = "done";
294
- });
295
- }
296
-
297
- function spinnerText(value: string): string {
298
- const text = preserveProtocolCaps(value);
299
- return text.replace(/^(\s*)([a-z])/, (_match, prefix, letter) => `${prefix}${letter.toUpperCase()}`);
300
- }
301
- function spinnerHintText(value: string): string { return preserveProtocolCaps(value); }
302
- function preserveProtocolCaps(value: string): string {
303
- return String(value || "")
304
- .replace(/\bapi\b/gi, "API")
305
- .replace(/\bens\b/gi, "ENS")
306
- .replace(/\berc-8004\b/gi, "ERC-8004")
307
- .replace(/\bgguf\b/gi, "GGUF")
308
- .replace(/\bhugging face\b/gi, "Hugging Face")
309
- .replace(/\bipfs\b/gi, "IPFS")
310
- .replace(/\bjson\b/gi, "JSON")
311
- .replace(/\bjwt\b/gi, "JWT")
312
- .replace(/\bmemory\.md\b/gi, "MEMORY.md")
313
- .replace(/\bopenai\b/gi, "OpenAI")
314
- .replace(/\banthropic\b/gi, "Anthropic")
315
- .replace(/\bgemini\b/gi, "Gemini")
316
- .replace(/\bos\b/gi, "OS")
317
- .replace(/\brpc\b/gi, "RPC")
318
- .replace(/\bsoul\.md\b/gi, "SOUL.md")
319
- .replace(/\buri\b/gi, "URI")
320
- .replace(/\burl\b/gi, "URL");
321
- }
322
-
323
- function setStatus(marker: string, text: string, hint: string, spin: boolean): void {
324
- const lineEl = statusText.parentElement as HTMLElement;
325
- const hintEl = statusHint;
326
- const displayText = spin ? spinnerText(text) : text;
327
- const displayHint = spin ? spinnerHintText(hint) : hint;
328
- const apply = () => {
329
- if (spin) startSpinner();
330
- else setMarker(marker);
331
- statusText.textContent = displayText;
332
- hintEl.textContent = displayHint;
333
- requestAnimationFrame(() => {
334
- lineEl.classList.remove("is-changing");
335
- hintEl.classList.remove("is-changing");
336
- });
337
- };
338
- if (statusText.textContent === displayText && hintEl.textContent === displayHint) {
339
- if (spin) startSpinner();
340
- else setMarker(marker);
341
- return;
342
- }
343
- lineEl.classList.add("is-changing");
344
- hintEl.classList.add("is-changing");
345
- setTimeout(apply, 220);
346
- }
347
-
348
- export let currentState: string | null = null;
349
-
350
- export function setState(state: string, payload?: any): void {
351
- payload = payload || {};
352
- currentState = state;
353
- errorSlot.innerHTML = "";
354
- statusBlock.style.display = "flex";
355
- setTabTitle(tabTitleForState(state));
356
- applyTransferTimeline();
357
- switch (state) {
358
- case "connecting":
359
- setStatus("·", "Connecting To Wallet...", "Open your wallet if needed.", true);
360
- break;
361
- case "approve":
362
- if (config.kind === "account") {
363
- const copy = accountCopy();
364
- setStatus("·", copy.text, copy.hint, true);
365
- } else if (config.kind === "sign") {
366
- const sigCopy = signCopy();
367
- setStatus("·", sigCopy.text, sigCopy.hint, true);
368
- } else {
369
- const txCopy = transactionCopy();
370
- setStatus("·", txCopy.text, txCopy.hint, true);
371
- }
372
- break;
373
- case "approve-sign":
374
- {
375
- const sigCopy = signCopy();
376
- setStatus("·", sigCopy.text, sigCopy.hint, true);
377
- }
378
- break;
379
- case "preparing-transaction":
380
- setStatus("·", purposeCopy().prepare!.text, purposeCopy().prepare!.hint, true);
381
- break;
382
- case "approve-transaction":
383
- {
384
- const txCopy = transactionCopy();
385
- setStatus("·", txCopy.text, txCopy.hint, true);
386
- }
387
- break;
388
- case "submitting":
389
- if (config.kind === "account") setStatus("·", "Connecting Wallet...", "Returning to terminal.", true);
390
- else if (config.kind === "sign") setStatus("·", "Verifying Signature...", hasNextLifecyclePrompt() ? nextLifecycleHint() : "Returning to terminal.", true);
391
- else setStatus("·", "Submitted · Waiting For Confirmation...", "Your wallet accepted the transaction.", true);
392
- break;
393
- case "done":
394
- stopSpinner();
395
- statusMarker.innerHTML =
396
- '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor"' +
397
- ' stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">' +
398
- '<polyline points="20 6 9 17 4 12"></polyline></svg>';
399
- statusText.textContent =
400
- config.kind === "account" ? "Connected · Returning"
401
- : config.kind === "sign" ? (hasNextLifecyclePrompt() ? "Signed · Waiting" : "Signed · Returning")
402
- : "Submitted · Returning";
403
- statusHint.textContent = payload.txHash
404
- ? (isTransactionFlow() ? "Transaction submitted. Returning." : "This tab will close shortly.")
405
- : hasNextLifecyclePrompt() ? nextLifecycleHint() : "This tab will close shortly.";
406
- markActiveTimelineStepDone();
407
- break;
408
- case "error":
409
- stopSpinner();
410
- statusBlock.style.display = "none";
411
- renderError(payload);
412
- break;
413
- }
414
- }
415
-
416
- let lastWalletError: WalletErrorPayload | null = null;
417
-
418
- function renderError(payload: WalletErrorPayload): void {
419
- errorSlot.innerHTML = walletErrorHtml(payload);
420
- lastWalletError = payload;
421
- }
422
-
423
- export function getLastWalletError(): WalletErrorPayload | null { return lastWalletError }
424
- export function clearLastWalletError(): void { lastWalletError = null }
@@ -4,10 +4,6 @@ import os from 'node:os'
4
4
  import { z } from 'zod'
5
5
  import { atomicWriteText } from './atomicWrite.js'
6
6
 
7
- export const PROVIDERS = ['llamacpp', 'openai', 'anthropic', 'gemini'] as const
8
- export type ProviderId = (typeof PROVIDERS)[number]
9
- const LEGACY_PROVIDERS = ['ollama', ...PROVIDERS] as const
10
-
11
7
  export const SELECTABLE_NETWORKS = ['mainnet', 'base'] as const
12
8
  export type SelectableNetwork = (typeof SELECTABLE_NETWORKS)[number]
13
9
 
@@ -75,12 +71,8 @@ const IdentitySchema = z.object({
75
71
  })
76
72
 
77
73
  const ConfigSchema = z.object({
78
- version: z.literal(1),
79
- provider: z.enum(PROVIDERS),
80
- model: z.string().min(1),
81
- baseUrl: z.string().url().optional(),
82
- localMmprojPath: z.string().min(1).optional(),
83
- firstRunAt: z.string(),
74
+ version: z.literal(2),
75
+ firstSeenAt: z.string(),
84
76
  identity: IdentitySchema.optional(),
85
77
  erc8004: z.object({
86
78
  chainId: z.number().int().positive(),
@@ -96,16 +88,17 @@ const ConfigSchema = z.object({
96
88
  configVersion: z.number().int().nonnegative().optional(),
97
89
  })
98
90
 
99
- const LEGACY_OLLAMA_BASE_URL = 'http://localhost:11434/v1'
100
- const LegacyConfigSchema = ConfigSchema.extend({
101
- provider: z.enum(LEGACY_PROVIDERS),
102
- })
103
-
104
- type LegacyConfig = z.infer<typeof LegacyConfigSchema>
91
+ const LegacyV1Schema = z.object({
92
+ version: z.literal(1),
93
+ firstRunAt: z.string().optional(),
94
+ identity: IdentitySchema.optional(),
95
+ erc8004: ConfigSchema.shape.erc8004,
96
+ selectedNetwork: z.enum(SELECTABLE_NETWORKS).optional(),
97
+ configVersion: z.number().int().nonnegative().optional(),
98
+ }).passthrough()
105
99
 
106
100
  export type EthagentIdentity = z.infer<typeof IdentitySchema>
107
101
  export type TransferSnapshotMetadata = NonNullable<NonNullable<EthagentIdentity['backup']>['transferSnapshot']>
108
-
109
102
  export type EthagentConfig = z.infer<typeof ConfigSchema>
110
103
 
111
104
  export function getConfigDir(): string {
@@ -133,14 +126,23 @@ export async function loadConfig(): Promise<EthagentConfig | null> {
133
126
  const parsed = JSON.parse(raw)
134
127
  const active = ConfigSchema.safeParse(parsed)
135
128
  if (active.success) return normalizeConfig(active.data)
136
- const legacy = LegacyConfigSchema.safeParse(parsed)
137
- if (legacy.success) return migrateLegacyConfig(legacy.data)
129
+ const legacy = LegacyV1Schema.safeParse(parsed)
130
+ if (legacy.success) return normalizeConfig(migrateLegacyV1(legacy.data))
131
+ await preserveUnreadableConfig(raw)
138
132
  return null
139
133
  } catch {
134
+ await preserveUnreadableConfig(raw)
140
135
  return null
141
136
  }
142
137
  }
143
138
 
139
+ async function preserveUnreadableConfig(raw: string): Promise<void> {
140
+ try {
141
+ await fs.writeFile(`${getConfigPath()}.corrupt`, raw, { encoding: 'utf8', mode: 0o600 })
142
+ } catch {
143
+ }
144
+ }
145
+
144
146
  export async function saveConfig(config: EthagentConfig): Promise<void> {
145
147
  await ensureConfigDir()
146
148
  const bumped: EthagentConfig = {
@@ -210,10 +212,8 @@ export function buildSeedConfigForIdentity(args: {
210
212
  identityRegistryAddress: string
211
213
  }): EthagentConfig {
212
214
  return {
213
- version: 1,
214
- provider: 'llamacpp',
215
- model: defaultModelFor('llamacpp'),
216
- firstRunAt: new Date().toISOString(),
215
+ version: 2,
216
+ firstSeenAt: new Date().toISOString(),
217
217
  identity: { ...args.identity, source: 'erc8004' },
218
218
  erc8004: {
219
219
  chainId: args.chainId,
@@ -288,34 +288,8 @@ export async function deleteConfig(): Promise<void> {
288
288
  }
289
289
  }
290
290
 
291
- export function defaultModelFor(provider: ProviderId): string {
292
- switch (provider) {
293
- case 'openai': return 'gpt-5.2'
294
- case 'anthropic': return 'claude-sonnet-4-5'
295
- case 'gemini': return 'gemini-2.0-flash'
296
- case 'llamacpp': return 'huggingface-link'
297
- }
298
- }
299
-
300
- export function defaultBaseUrlFor(provider: ProviderId): string | undefined {
301
- if (provider === 'llamacpp') return 'http://localhost:8080/v1'
302
- return undefined
303
- }
304
-
305
- export type LocalProviderId = Extract<ProviderId, 'llamacpp'>
306
-
307
- export function localProviderBaseUrlFor(provider: LocalProviderId, baseUrl?: string): string {
308
- const fallback = defaultBaseUrlFor(provider) ?? ''
309
- if (!baseUrl) return fallback
310
- return isDefaultBaseUrlFor(baseUrl, LEGACY_OLLAMA_BASE_URL) ? fallback : baseUrl
311
- }
312
-
313
291
  export function normalizeConfig(config: EthagentConfig): EthagentConfig {
314
292
  let next = config
315
- if (next.provider === 'llamacpp') {
316
- const baseUrl = localProviderBaseUrlFor(next.provider, next.baseUrl)
317
- if (next.baseUrl !== baseUrl) next = { ...next, baseUrl }
318
- }
319
293
  if (!next.erc8004 && next.identity?.chainId && next.identity.identityRegistryAddress && next.identity.rpcUrl) {
320
294
  next = {
321
295
  ...next,
@@ -329,37 +303,13 @@ export function normalizeConfig(config: EthagentConfig): EthagentConfig {
329
303
  return next
330
304
  }
331
305
 
332
- function migrateLegacyConfig(config: LegacyConfig): EthagentConfig {
333
- if (config.provider !== 'ollama') return normalizeConfig(ConfigSchema.parse(config))
306
+ function migrateLegacyV1(input: z.infer<typeof LegacyV1Schema>): EthagentConfig {
334
307
  return {
335
- ...config,
336
- provider: 'llamacpp',
337
- model: defaultModelFor('llamacpp'),
338
- baseUrl: defaultBaseUrlFor('llamacpp'),
339
- }
340
- }
341
-
342
- function isDefaultBaseUrlFor(value: string, fallback: string | undefined): boolean {
343
- if (!fallback) return false
344
- try {
345
- const url = new URL(value)
346
- const defaultUrl = new URL(fallback)
347
- if (url.protocol !== defaultUrl.protocol) return false
348
- if (url.hostname.toLowerCase() !== defaultUrl.hostname.toLowerCase()) return false
349
- if (effectivePort(url) !== effectivePort(defaultUrl)) return false
350
- const path = stripTrailingSlash(url.pathname) || '/'
351
- const defaultPath = stripTrailingSlash(defaultUrl.pathname) || '/'
352
- return path === '/' || path === defaultPath
353
- } catch {
354
- return stripTrailingSlash(value) === stripTrailingSlash(fallback)
308
+ version: 2,
309
+ firstSeenAt: input.firstRunAt ?? new Date().toISOString(),
310
+ ...(input.identity ? { identity: input.identity } : {}),
311
+ ...(input.erc8004 ? { erc8004: input.erc8004 } : {}),
312
+ ...(input.selectedNetwork ? { selectedNetwork: input.selectedNetwork } : {}),
313
+ ...(input.configVersion !== undefined ? { configVersion: input.configVersion } : {}),
355
314
  }
356
315
  }
357
-
358
- function effectivePort(url: URL): string {
359
- if (url.port) return url.port
360
- return url.protocol === 'https:' ? '443' : '80'
361
- }
362
-
363
- function stripTrailingSlash(value: string): string {
364
- return value.replace(/\/+$/, '')
365
- }
@@ -0,0 +1,31 @@
1
+ import fs from 'node:fs/promises'
2
+ import { getConfigDir } from './config.js'
3
+ import { rmSecret } from './secrets.js'
4
+
5
+ const RESET_SECRET_ACCOUNTS = ['ethereum:default', 'pinata:jwt'] as const
6
+
7
+ export type ResetPlan = {
8
+ configDir: string
9
+ secretAccounts: string[]
10
+ }
11
+
12
+ export type ResetDeps = {
13
+ rm?: (dir: string) => Promise<void>
14
+ removeSecret?: (account: string) => Promise<void>
15
+ }
16
+
17
+ export function resetPlan(): ResetPlan {
18
+ return {
19
+ configDir: getConfigDir(),
20
+ secretAccounts: [...RESET_SECRET_ACCOUNTS],
21
+ }
22
+ }
23
+
24
+ export async function runReset(deps: ResetDeps = {}): Promise<ResetPlan> {
25
+ const plan = resetPlan()
26
+ const rm = deps.rm ?? (dir => fs.rm(dir, { recursive: true, force: true }))
27
+ const removeSecret = deps.removeSecret ?? rmSecret
28
+ await rm(plan.configDir)
29
+ for (const account of plan.secretAccounts) await removeSecret(account)
30
+ return plan
31
+ }
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
  import os from 'node:os'
4
4
  import crypto from 'node:crypto'
5
- import { getConfigDir, ensureConfigDir, type ProviderId } from './config.js'
5
+ import { getConfigDir, ensureConfigDir } from './config.js'
6
6
  import { atomicWriteText } from './atomicWrite.js'
7
7
 
8
8
  const KEYTAR_SERVICE = 'ethagent'
@@ -167,18 +167,3 @@ export async function whichBackend(): Promise<KeyBackend> {
167
167
  return (await loadKeytar()) ? 'keyring' : 'encrypted-file'
168
168
  }
169
169
 
170
- export function getKey(provider: ProviderId): Promise<string | null> {
171
- return getSecret(provider)
172
- }
173
-
174
- export function setKey(provider: ProviderId, value: string): Promise<KeyBackend> {
175
- return setSecret(provider, value)
176
- }
177
-
178
- export function rmKey(provider: ProviderId): Promise<void> {
179
- return rmSecret(provider)
180
- }
181
-
182
- export function hasKey(provider: ProviderId): Promise<boolean> {
183
- return hasSecret(provider)
184
- }