ethagent 1.1.2 → 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 (268) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +126 -30
  3. package/package.json +7 -2
  4. package/src/app/FirstRun.tsx +190 -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 +35 -15
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +10 -22
  13. package/src/chat/CopyPicker.tsx +0 -1
  14. package/src/chat/MessageList.tsx +62 -45
  15. package/src/chat/PermissionPrompt.tsx +13 -9
  16. package/src/chat/PlanApprovalView.tsx +3 -3
  17. package/src/chat/ResumeView.tsx +1 -4
  18. package/src/chat/RewindView.tsx +2 -2
  19. package/src/chat/chatInputState.ts +1 -1
  20. package/src/chat/chatScreenUtils.ts +22 -11
  21. package/src/chat/chatSessionState.ts +2 -2
  22. package/src/chat/chatTurnOrchestrator.ts +16 -81
  23. package/src/chat/commands.ts +1 -1
  24. package/src/chat/textCursor.ts +1 -1
  25. package/src/chat/transcriptViewport.ts +2 -7
  26. package/src/cli/ResetConfirmView.tsx +1 -1
  27. package/src/cli/main.tsx +9 -3
  28. package/src/cli/preview.tsx +0 -5
  29. package/src/cli/updateNotice.ts +4 -2
  30. package/src/identity/continuity/editor.ts +7 -107
  31. package/src/identity/continuity/envelope.ts +1048 -40
  32. package/src/identity/continuity/history.ts +4 -4
  33. package/src/identity/continuity/localBackup.ts +249 -0
  34. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  35. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  36. package/src/identity/continuity/privateEdit/files.ts +23 -0
  37. package/src/identity/continuity/privateEdit/types.ts +28 -0
  38. package/src/identity/continuity/privateEdit.ts +10 -298
  39. package/src/identity/continuity/publicSkills.ts +8 -9
  40. package/src/identity/continuity/snapshots.ts +17 -6
  41. package/src/identity/continuity/storage/defaults.ts +111 -0
  42. package/src/identity/continuity/storage/files.ts +72 -0
  43. package/src/identity/continuity/storage/markdown.ts +81 -0
  44. package/src/identity/continuity/storage/paths.ts +24 -0
  45. package/src/identity/continuity/storage/scaffold.ts +124 -0
  46. package/src/identity/continuity/storage/status.ts +86 -0
  47. package/src/identity/continuity/storage/types.ts +27 -0
  48. package/src/identity/continuity/storage.ts +32 -507
  49. package/src/identity/continuity/zipWriter.ts +95 -0
  50. package/src/identity/crypto/backupEnvelope.ts +14 -247
  51. package/src/identity/crypto/eth.ts +7 -7
  52. package/src/identity/ens/agentRecords.ts +96 -0
  53. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  54. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  55. package/src/identity/ens/ensAutomation/names.ts +14 -0
  56. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  57. package/src/identity/ens/ensAutomation/read.ts +114 -0
  58. package/src/identity/ens/ensAutomation/root.ts +63 -0
  59. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  60. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  61. package/src/identity/ens/ensAutomation/types.ts +126 -0
  62. package/src/identity/ens/ensAutomation.ts +29 -0
  63. package/src/identity/ens/ensLookup/client.ts +43 -0
  64. package/src/identity/ens/ensLookup/constants.ts +26 -0
  65. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  66. package/src/identity/ens/ensLookup/names.ts +34 -0
  67. package/src/identity/ens/ensLookup/records.ts +45 -0
  68. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  69. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  70. package/src/identity/ens/ensLookup/types.ts +38 -0
  71. package/src/identity/ens/ensLookup/validation.ts +72 -0
  72. package/src/identity/ens/ensLookup.ts +19 -0
  73. package/src/identity/ens/ensRegistration.ts +199 -0
  74. package/src/identity/ens/resolverDelegation.ts +48 -0
  75. package/src/identity/hub/IdentityHub.tsx +13 -817
  76. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  77. package/src/identity/hub/Routes.tsx +361 -0
  78. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  79. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  80. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  81. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  82. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  83. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  84. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  85. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  86. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  87. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  88. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  89. package/src/identity/hub/effects/create.ts +310 -0
  90. package/src/identity/hub/effects/ens/flows.ts +218 -0
  91. package/src/identity/hub/effects/ens/index.ts +11 -0
  92. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  93. package/src/identity/hub/effects/index.ts +74 -0
  94. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  95. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  96. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  97. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  98. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  99. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  100. package/src/identity/hub/effects/receipts.ts +46 -0
  101. package/src/identity/hub/effects/restore/apply.ts +112 -0
  102. package/src/identity/hub/effects/restore/auth.ts +159 -0
  103. package/src/identity/hub/effects/restore/discover.ts +86 -0
  104. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  105. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  106. package/src/identity/hub/effects/restore/index.ts +22 -0
  107. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  108. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  109. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  110. package/src/identity/hub/effects/restore/shared.ts +91 -0
  111. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  112. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  113. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  114. package/src/identity/hub/effects/shared/sync.ts +190 -0
  115. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  116. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  117. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  118. package/src/identity/hub/effects/types.ts +53 -0
  119. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  120. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  121. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  122. package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
  123. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  124. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  125. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  126. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  127. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  128. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  129. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  130. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  131. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  132. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  133. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  134. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  135. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  136. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  137. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  138. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  139. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  140. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  141. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  142. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  143. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  144. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  145. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  146. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  147. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
  148. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  149. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  150. package/src/identity/hub/identityHubReducer.ts +164 -99
  151. package/src/identity/hub/model/continuity.ts +94 -0
  152. package/src/identity/hub/model/copy.ts +35 -0
  153. package/src/identity/hub/model/custody.ts +54 -0
  154. package/src/identity/hub/model/ens.ts +49 -0
  155. package/src/identity/hub/model/errors.ts +140 -0
  156. package/src/identity/hub/model/format.ts +15 -0
  157. package/src/identity/hub/model/identity.ts +94 -0
  158. package/src/identity/hub/model/network.ts +32 -0
  159. package/src/identity/hub/model/transfer.ts +57 -0
  160. package/src/identity/hub/operatorWallets.ts +131 -0
  161. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  162. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  165. package/src/identity/hub/reconciliation/index.ts +21 -0
  166. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  167. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  168. package/src/identity/hub/txGuard.ts +51 -0
  169. package/src/identity/hub/types.ts +17 -0
  170. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  171. package/src/identity/hub/useIdentityHubController.ts +396 -0
  172. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  173. package/src/identity/hub/utils.ts +79 -0
  174. package/src/identity/identityCompat.ts +34 -0
  175. package/src/identity/profile/agentIcon.ts +61 -0
  176. package/src/identity/profile/imagePicker.ts +12 -12
  177. package/src/identity/registry/erc8004/abi.ts +14 -0
  178. package/src/identity/registry/erc8004/chains.ts +150 -0
  179. package/src/identity/registry/erc8004/client.ts +11 -0
  180. package/src/identity/registry/erc8004/discovery.ts +511 -0
  181. package/src/identity/registry/erc8004/metadata.ts +335 -0
  182. package/src/identity/registry/erc8004/ownership.ts +121 -0
  183. package/src/identity/registry/erc8004/preflight.ts +123 -0
  184. package/src/identity/registry/erc8004/transactions.ts +77 -0
  185. package/src/identity/registry/erc8004/types.ts +88 -0
  186. package/src/identity/registry/erc8004/uri.ts +59 -0
  187. package/src/identity/registry/erc8004/utils.ts +58 -0
  188. package/src/identity/registry/erc8004.ts +53 -1106
  189. package/src/identity/registry/fieldParsers.ts +28 -0
  190. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  191. package/src/identity/registry/operatorVault/constants.ts +38 -0
  192. package/src/identity/registry/operatorVault/read.ts +246 -0
  193. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  194. package/src/identity/registry/operatorVault.ts +44 -0
  195. package/src/identity/storage/ipfs.ts +26 -24
  196. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  197. package/src/identity/wallet/browserWallet/html.ts +106 -0
  198. package/src/identity/wallet/browserWallet/http.ts +28 -0
  199. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  200. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  201. package/src/identity/wallet/browserWallet/session.ts +325 -0
  202. package/src/identity/wallet/browserWallet/types.ts +192 -0
  203. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  204. package/src/identity/wallet/browserWallet.ts +30 -393
  205. package/src/identity/wallet/page/constants.ts +5 -0
  206. package/src/identity/wallet/page/controller.ts +251 -0
  207. package/src/identity/wallet/page/copy.ts +340 -0
  208. package/src/identity/wallet/page/grainient.ts +278 -0
  209. package/src/identity/wallet/page/html.ts +28 -0
  210. package/src/identity/wallet/page/markup.ts +50 -0
  211. package/src/identity/wallet/page/state.ts +9 -0
  212. package/src/identity/wallet/page/styles/base.ts +259 -0
  213. package/src/identity/wallet/page/styles/components.ts +262 -0
  214. package/src/identity/wallet/page/styles/index.ts +5 -0
  215. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  216. package/src/identity/wallet/page/types.ts +47 -0
  217. package/src/identity/wallet/page/view.ts +535 -0
  218. package/src/identity/wallet/page/walletProvider.ts +70 -0
  219. package/src/identity/wallet/page.tsx +38 -0
  220. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  221. package/src/mcp/manager.ts +0 -1
  222. package/src/models/ModelPicker.tsx +36 -30
  223. package/src/models/catalog.ts +5 -2
  224. package/src/models/huggingface.ts +9 -9
  225. package/src/models/llamacpp.ts +13 -13
  226. package/src/models/modelDisplay.ts +75 -0
  227. package/src/models/modelPickerOptions.ts +16 -3
  228. package/src/models/modelRecommendation.ts +0 -1
  229. package/src/providers/errors.ts +16 -0
  230. package/src/providers/gemini.ts +252 -39
  231. package/src/providers/registry.ts +2 -2
  232. package/src/providers/retry.ts +1 -1
  233. package/src/runtime/sessionMode.ts +1 -1
  234. package/src/runtime/systemPrompt.ts +2 -0
  235. package/src/runtime/toolExecution.ts +18 -22
  236. package/src/runtime/toolIntent.ts +0 -20
  237. package/src/runtime/turn.ts +0 -92
  238. package/src/storage/atomicWrite.ts +4 -1
  239. package/src/storage/config.ts +181 -5
  240. package/src/storage/identity.ts +9 -3
  241. package/src/storage/secrets.ts +2 -2
  242. package/src/tools/bashSafety.ts +8 -0
  243. package/src/tools/changeDirectoryTool.ts +1 -1
  244. package/src/tools/deleteFileTool.ts +4 -4
  245. package/src/tools/editTool.ts +4 -4
  246. package/src/tools/editUtils.ts +5 -5
  247. package/src/tools/privateContinuityEditTool.ts +4 -5
  248. package/src/tools/privateContinuityReadTool.ts +1 -2
  249. package/src/tools/registry.ts +30 -0
  250. package/src/tools/writeFileTool.ts +5 -5
  251. package/src/ui/BrandSplash.tsx +20 -85
  252. package/src/ui/ProgressBar.tsx +3 -5
  253. package/src/ui/Select.tsx +20 -8
  254. package/src/ui/Spinner.tsx +38 -3
  255. package/src/ui/Surface.tsx +2 -2
  256. package/src/ui/TextInput.tsx +63 -20
  257. package/src/ui/theme.ts +7 -34
  258. package/src/utils/openExternal.ts +21 -0
  259. package/src/utils/withRetry.ts +47 -3
  260. package/src/identity/hub/identityHubEffects.ts +0 -937
  261. package/src/identity/hub/identityHubModel.ts +0 -371
  262. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
  263. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
  264. package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
  265. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  266. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  267. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  268. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -0,0 +1,471 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { getAddress, type Address } from 'viem'
4
+ import { Surface } from '../../../../ui/Surface.js'
5
+ import { Select, type SelectOption } from '../../../../ui/Select.js'
6
+ import { theme } from '../../../../ui/theme.js'
7
+ import {
8
+ formatRecordValue,
9
+ recordLabel,
10
+ type AgentEnsRecords,
11
+ type AgentRecordDiff,
12
+ } from '../../../ens/agentRecords.js'
13
+ import type { EnsValidation } from '../../../ens/ensLookup.js'
14
+ import type {
15
+ EnsSetupBlockedPlan,
16
+ EnsSetupPlan,
17
+ } from '../../../ens/ensAutomation.js'
18
+ import { createErc8004PublicClient, type Erc8004RegistryConfig } from '../../../registry/erc8004.js'
19
+ import {
20
+ displayCustodyMode,
21
+ type CustodyMode,
22
+ } from '../../model/custody.js'
23
+ import { ensValidationReasonText } from '../../model/ens.js'
24
+ import { shortAddress } from '../../model/format.js'
25
+ import {
26
+ manualReasonTitle,
27
+ modeSwitchHeading,
28
+ setupSwitchNotice,
29
+ } from './ensEditCopy.js'
30
+ import {
31
+ EnsSetupRow,
32
+ footerHint,
33
+ renderRecordValue,
34
+ } from './EnsEditShared.js'
35
+ import type { EnsIssueValidation } from './ensEditTypes.js'
36
+
37
+ type SimpleEnsIssueScreenProps = {
38
+ fullName: string
39
+ validation: EnsIssueValidation
40
+ onCreate: () => void
41
+ onCheckAgain: () => void
42
+ onChange: () => void
43
+ onBack: () => void
44
+ }
45
+
46
+ export const SimpleEnsIssueScreen: React.FC<SimpleEnsIssueScreenProps> = ({
47
+ fullName,
48
+ validation,
49
+ onCreate,
50
+ onCheckAgain,
51
+ onChange,
52
+ onBack,
53
+ }) => {
54
+ type Action = 'create' | 'check-again' | 'change' | 'back'
55
+ const reason = ensValidationReasonText(validation.reason)
56
+ const showDetail = validation.detail && validation.detail !== reason
57
+ return (
58
+ <Surface
59
+ title="ENS Name Not Found"
60
+ footer={footerHint('enter select · esc back')}
61
+ >
62
+ <Box flexDirection="column">
63
+ <Text color={theme.dim}>The subdomain is not on Ethereum Mainnet yet. You can create it from here.</Text>
64
+ <Text>
65
+ <Text color={theme.dim}>{'Name'.padEnd(12)}</Text>
66
+ <Text color={theme.text} bold>{fullName}</Text>
67
+ </Text>
68
+ <Text>
69
+ <Text color={theme.dim}>{'Reason'.padEnd(12)}</Text>
70
+ <Text color={theme.accentError}>{reason}</Text>
71
+ </Text>
72
+ {showDetail ? <Text color={theme.dim}>{validation.detail}</Text> : null}
73
+ </Box>
74
+ <Box marginTop={1}>
75
+ <Select<Action>
76
+ options={[
77
+ { value: 'create', role: 'section', label: 'Create Subdomain' },
78
+ { value: 'create', label: 'Create This ENS Name', hint: 'Connected wallet creates it and sets the required records' },
79
+ { value: 'change', label: 'Pick A Different Name', hint: 'Return to subdomain entry with this name still filled in' },
80
+ { value: 'back', role: 'section', label: 'Navigation' },
81
+ { value: 'back', label: 'Back', hint: 'Return to subdomain entry', role: 'utility' },
82
+ ]}
83
+ hintLayout="inline"
84
+ onSubmit={choice => {
85
+ if (choice === 'create') return onCreate()
86
+ if (choice === 'check-again') return onCheckAgain()
87
+ if (choice === 'change') return onChange()
88
+ return onBack()
89
+ }}
90
+ onCancel={onBack}
91
+ />
92
+ </Box>
93
+ </Surface>
94
+ )
95
+ }
96
+
97
+ type EnsSetupReviewScreenProps = {
98
+ setup: EnsSetupPlan
99
+ currentEnsName: string
100
+ currentMode: CustodyMode | undefined
101
+ registry: Erc8004RegistryConfig
102
+ onBegin: () => void
103
+ onBack: () => void
104
+ }
105
+
106
+ export const EnsSetupReviewScreen: React.FC<EnsSetupReviewScreenProps> = ({
107
+ setup,
108
+ currentEnsName,
109
+ currentMode,
110
+ registry,
111
+ onBegin,
112
+ onBack,
113
+ }) => {
114
+ type Action = 'begin' | 'back'
115
+ const isSimple = setup.mode === 'simple'
116
+ const [ownerIsSmartAccount, setOwnerIsSmartAccount] = React.useState(false)
117
+ React.useEffect(() => {
118
+ if (isSimple) return
119
+ let cancelled = false
120
+ const client = createErc8004PublicClient(registry)
121
+ client.getBytecode({ address: getAddress(setup.ownerAddress) })
122
+ .then(code => {
123
+ if (cancelled) return
124
+ if (code && code !== '0x') setOwnerIsSmartAccount(true)
125
+ })
126
+ .catch(() => {})
127
+ return () => { cancelled = true }
128
+ }, [isSimple, registry, setup.ownerAddress])
129
+ const signerLabel = isSimple ? 'Connected wallet' : 'Owner wallet'
130
+ const switchNotice = setupSwitchNotice(currentEnsName, currentMode, setup.fullName, setup.mode)
131
+ const createLabel = setup.registryAction === 'create-subdomain'
132
+ ? 'Create Subdomain'
133
+ : setup.registryAction === 'create-wrapped-subdomain'
134
+ ? 'Create Wrapped Subdomain'
135
+ : setup.registryAction === 'set-resolver'
136
+ ? 'Set Resolver'
137
+ : setup.registryAction === 'set-wrapped-resolver'
138
+ ? 'Set Wrapped Resolver'
139
+ : 'Subdomain Ready'
140
+ const beginHint = setup.registryAction === 'none'
141
+ ? 'Ethereum Mainnet: subdomain already ready'
142
+ : 'Ethereum Mainnet: create or prepare subdomain'
143
+ const reusingExistingSubdomain = setup.registryAction === 'none'
144
+ return (
145
+ <Surface
146
+ title={isSimple ? 'Create Simple ENS Name' : 'Create ENS Name'}
147
+ footer={footerHint('enter select · esc back')}
148
+ >
149
+ {reusingExistingSubdomain ? (
150
+ <Box marginBottom={1}>
151
+ <Text color={theme.accentPeriwinkle}>Subdomain detected from a prior attempt, reusing.</Text>
152
+ </Box>
153
+ ) : null}
154
+ <Box flexDirection="column">
155
+ <Text color={theme.dim}>{modeSwitchHeading(currentEnsName, currentMode, setup.fullName, setup.mode)}</Text>
156
+ {switchNotice ? <Text color={theme.dim}>{switchNotice}</Text> : null}
157
+ <EnsSetupRow label="ENS name" value={setup.fullName} />
158
+ <EnsSetupRow label="Parent root" value={setup.rootName} />
159
+ <EnsSetupRow label="Subdomain label" value={setup.label} />
160
+ <EnsSetupRow label="ENS network" value="Ethereum Mainnet" />
161
+ <EnsSetupRow label="Signer wallet" value={`${shortAddress(setup.ownerAddress)} (${signerLabel.toLowerCase()})`} />
162
+ <EnsSetupRow label="Registry action" value={createLabel} />
163
+ {ownerIsSmartAccount ? (
164
+ <Box marginTop={1}>
165
+ <Text color={theme.accentError}>Owner wallet appears to be a smart account. Resolver delegation may require special handling depending on your wallet provider; if the operator wallet later cannot write ENS records, run "Fix Records" to retry.</Text>
166
+ </Box>
167
+ ) : null}
168
+ </Box>
169
+ <Box marginTop={1}>
170
+ <Select<Action>
171
+ options={[
172
+ { value: 'begin', role: 'section', label: 'Subdomain' },
173
+ { value: 'begin', label: 'Continue Setup', hint: beginHint },
174
+ { value: 'back', role: 'section', label: 'Navigation' },
175
+ { value: 'back', label: 'Back', hint: isSimple ? 'Return to subdomain entry' : 'Return to operator wallet', role: 'utility' },
176
+ ]}
177
+ hintLayout="inline"
178
+ onSubmit={choice => {
179
+ if (choice === 'begin') return onBegin()
180
+ return onBack()
181
+ }}
182
+ onCancel={onBack}
183
+ />
184
+ </Box>
185
+ </Surface>
186
+ )
187
+ }
188
+
189
+ type EnsSetupBlockedScreenProps = {
190
+ fallback: EnsSetupBlockedPlan
191
+ onCheckAgain: () => void
192
+ onBack: () => void
193
+ }
194
+
195
+ export const EnsSetupBlockedScreen: React.FC<EnsSetupBlockedScreenProps> = ({
196
+ fallback,
197
+ onCheckAgain,
198
+ onBack,
199
+ }) => {
200
+ type Action = 'check' | 'back'
201
+ const isSimple = fallback.mode === 'simple'
202
+ return (
203
+ <Surface
204
+ title="ENS Setup Blocked"
205
+ footer={footerHint('enter select · esc back')}
206
+ >
207
+ <Box flexDirection="column">
208
+ <Text color={theme.accentError}>{manualReasonTitle(fallback.reason)}</Text>
209
+ <Text color={theme.dim}>{fallback.detail}</Text>
210
+ <Box marginTop={1} flexDirection="column">
211
+ <EnsSetupRow label="Agent ENS" value={fallback.fullName} />
212
+ {isSimple
213
+ ? <EnsSetupRow label="Wallet" value={fallback.ownerAddress ? shortAddress(fallback.ownerAddress) : shortAddress(fallback.operatorAddress)} />
214
+ : (fallback.ownerAddress ? <EnsSetupRow label="Owner wallet" value={shortAddress(fallback.ownerAddress)} /> : null)}
215
+ {fallback.nextRecords?.token ? <EnsSetupRow label="Token link" value={fallback.nextRecords.token} /> : null}
216
+ <EnsSetupRow label="Address" value={`Set the subdomain address record to the ${isSimple ? 'connected wallet' : 'owner wallet'}.`} />
217
+ {!isSimple
218
+ ? (
219
+ <>
220
+ <Box marginTop={1} flexDirection="column">
221
+ <Text color={theme.text}>To proceed: the owner wallet signs ENS records and must hold this token at setup time. Once setup is done you can deposit the token into the operator delegation vault while the ENS subdomain stays with the owner wallet.</Text>
222
+ </Box>
223
+ <Text color={theme.dim}>Operator wallets have no authority on this name; they only rotate the onchain ERC-8004 URI via the operator delegation vault.</Text>
224
+ </>
225
+ )
226
+ : null}
227
+ </Box>
228
+ </Box>
229
+ <Box marginTop={1}>
230
+ <Select<Action>
231
+ options={[
232
+ { value: 'check', role: 'section', label: 'Automation' },
233
+ { value: 'check', label: 'Check Again', hint: isSimple ? 'Re-run the simple ENS automation check' : 'Re-read ENS after the token is back with the owner wallet' },
234
+ { value: 'back', role: 'section', label: 'Navigation' },
235
+ { value: 'back', label: 'Back', hint: isSimple ? 'Return to subdomain entry' : 'Return to the previous setup step', role: 'utility' },
236
+ ]}
237
+ hintLayout="inline"
238
+ onSubmit={choice => {
239
+ if (choice === 'check') return onCheckAgain()
240
+ return onBack()
241
+ }}
242
+ onCancel={onBack}
243
+ />
244
+ </Box>
245
+ </Surface>
246
+ )
247
+ }
248
+
249
+ type UnlinkEnsReviewScreenProps = {
250
+ fullName: string
251
+ currentMode: CustodyMode | undefined
252
+ registryNetworkLabel: string
253
+ recordsDiff: AgentRecordDiff[]
254
+ onUnlink: () => void
255
+ onBack: () => void
256
+ }
257
+
258
+ export const UnlinkEnsReviewScreen: React.FC<UnlinkEnsReviewScreenProps> = ({
259
+ fullName,
260
+ currentMode,
261
+ registryNetworkLabel,
262
+ recordsDiff,
263
+ onUnlink,
264
+ onBack,
265
+ }) => {
266
+ type Action = 'unlink' | 'back'
267
+ const changedDiffs = recordsDiff.filter(diff => diff.current.trim())
268
+ const recordsAlreadyEmpty = changedDiffs.length === 0
269
+ return (
270
+ <Surface
271
+ title="Unlink ENS"
272
+ footer={footerHint('enter select · esc back')}
273
+ >
274
+ <Box flexDirection="column">
275
+ <EnsSetupRow label="ENS name" value={fullName} />
276
+ <EnsSetupRow label="Custody" value={displayCustodyMode(currentMode)} />
277
+ <Text color={theme.dim}>
278
+ Records on Ethereum Mainnet, then token URI on {registryNetworkLabel}.
279
+ </Text>
280
+ {recordsAlreadyEmpty ? (
281
+ <Box marginTop={1}>
282
+ <Text color={theme.dim}>
283
+ All ethagent ENS records are already empty. Unlink removes only the token URI link.
284
+ </Text>
285
+ </Box>
286
+ ) : (
287
+ <Box marginTop={1} flexDirection="column">
288
+ <Text color={theme.textSubtle}>Will be cleared:</Text>
289
+ {changedDiffs.map(diff => (
290
+ <Text key={diff.key}>
291
+ <Text color={theme.dim}>{` ${diff.key} `}</Text>
292
+ <Text color={theme.accentPeriwinkle}>{formatRecordValue(diff.field, diff.current)}</Text>
293
+ </Text>
294
+ ))}
295
+ </Box>
296
+ )}
297
+ </Box>
298
+ <Box marginTop={1}>
299
+ <Select<Action>
300
+ options={[
301
+ { value: 'unlink', role: 'section', label: 'Unlink' },
302
+ {
303
+ value: 'unlink',
304
+ label: 'Unlink ENS',
305
+ hint: recordsAlreadyEmpty
306
+ ? 'Skip the records transaction and remove only the token URI link'
307
+ : 'Clear ethagent-owned records, then remove the token URI link',
308
+ },
309
+ { value: 'back', role: 'section', label: 'Navigation' },
310
+ { value: 'back', label: 'Back', hint: 'Return to ENS setup', role: 'utility' },
311
+ ]}
312
+ hintLayout="inline"
313
+ onSubmit={choice => {
314
+ if (choice === 'unlink') return onUnlink()
315
+ return onBack()
316
+ }}
317
+ onCancel={onBack}
318
+ />
319
+ </Box>
320
+ </Surface>
321
+ )
322
+ }
323
+
324
+ type ReviewScreenProps = {
325
+ fullName: string
326
+ ownerAddress: Address
327
+ validation: EnsValidation
328
+ recordsDiff: AgentRecordDiff[]
329
+ nextRecords: AgentEnsRecords
330
+ currentEnsName: string
331
+ currentMode: CustodyMode | undefined
332
+ registryNetworkLabel: string
333
+ mode: 'simple' | 'advanced'
334
+ onContinue: () => void
335
+ onCheckAgain: () => void
336
+ onChange: () => void
337
+ onCreate?: () => void
338
+ onBack: () => void
339
+ }
340
+
341
+ export const ReviewScreen: React.FC<ReviewScreenProps> = ({
342
+ fullName,
343
+ ownerAddress,
344
+ validation,
345
+ recordsDiff,
346
+ nextRecords,
347
+ currentEnsName,
348
+ currentMode,
349
+ registryNetworkLabel,
350
+ mode,
351
+ onContinue,
352
+ onCheckAgain,
353
+ onChange,
354
+ onCreate,
355
+ onBack,
356
+ }) => {
357
+ void ownerAddress
358
+ void nextRecords
359
+ type ReviewAction = 'continue' | 'create' | 'check-again' | 'change' | 'back'
360
+ const changedDiffs = recordsDiff.filter(d => d.changed)
361
+ const hasRecordChanges = changedDiffs.length > 0
362
+ const reviewSubtitle = 'Review Ethereum Mainnet ENS address and text records before saving this ENS link. No token approval is requested.'
363
+ const switchNotice = setupSwitchNotice(currentEnsName, currentMode, fullName, mode)
364
+
365
+ if (!validation.ok) {
366
+ const reason = ensValidationReasonText(validation.reason)
367
+ const showDetail = validation.detail && validation.detail !== reason
368
+ return (
369
+ <Surface
370
+ title="ENS Issue"
371
+ subtitle={`${fullName} could not be verified on Ethereum mainnet.`}
372
+ footer={footerHint('enter select · esc back')}
373
+ >
374
+ <Box flexDirection="column">
375
+ <Text>
376
+ <Text color={theme.dim}>{'Name'.padEnd(12)}</Text>
377
+ <Text color={theme.text} bold>{fullName}</Text>
378
+ </Text>
379
+ <Text>
380
+ <Text color={theme.dim}>{'Reason'.padEnd(12)}</Text>
381
+ <Text color={theme.accentError}>{reason}</Text>
382
+ </Text>
383
+ {showDetail
384
+ ? <Text color={theme.dim}>{validation.detail}</Text>
385
+ : null}
386
+ </Box>
387
+ <Box marginTop={1}>
388
+ <Select<ReviewAction>
389
+ options={[
390
+ ...(onCreate
391
+ ? [
392
+ { value: 'create' as ReviewAction, role: 'section' as const, label: 'Create Subdomain' },
393
+ { value: 'create' as ReviewAction, label: 'Create This ENS Name', hint: 'Use ethagent to create it and set the required records' },
394
+ ]
395
+ : []),
396
+ { value: 'check-again', label: 'Check Again', hint: 'Re-verify on Ethereum mainnet' },
397
+ { value: 'change', label: 'Pick A Different Name', hint: 'Return to the name picker' },
398
+ { value: 'back', role: 'section', label: 'Navigation' },
399
+ { value: 'back', label: 'Back', hint: 'Return to subdomain entry', role: 'utility' },
400
+ ]}
401
+ hintLayout="inline"
402
+ onSubmit={choice => {
403
+ if (choice === 'create' && onCreate) return onCreate()
404
+ if (choice === 'check-again') return onCheckAgain()
405
+ if (choice === 'change') return onChange()
406
+ return onBack()
407
+ }}
408
+ onCancel={onBack}
409
+ />
410
+ </Box>
411
+ </Surface>
412
+ )
413
+ }
414
+
415
+ const options: Array<SelectOption<ReviewAction>> = [
416
+ { value: 'continue', role: 'section', label: modeSwitchHeading(currentEnsName, currentMode, fullName, mode) },
417
+ { value: 'continue', label: 'Continue Setup', hint: hasRecordChanges ? `Ethereum Mainnet: sign ${changedDiffs.length} ENS record value${changedDiffs.length === 1 ? '' : 's'}; ${registryNetworkLabel}: save token URI` : `ENS records already match; ${registryNetworkLabel}: save token URI` },
418
+ { value: 'change', label: 'Pick A Different Name', hint: 'Return to the name picker' },
419
+ { value: 'back', role: 'section', label: 'Navigation' },
420
+ { value: 'back', label: 'Back', hint: 'Return to Identity Hub', role: 'utility' },
421
+ ]
422
+
423
+ return (
424
+ <Surface
425
+ title={`Update ENS records for ${fullName}?`}
426
+ subtitle={reviewSubtitle}
427
+ footer={footerHint('enter select · esc back')}
428
+ >
429
+ <Box flexDirection="column">
430
+ {currentEnsName || currentMode
431
+ ? (
432
+ <Box marginBottom={1} flexDirection="column">
433
+ <Text color={theme.dim}>Current: <Text color={currentEnsName ? theme.text : theme.dim}>{currentEnsName || 'None'}</Text></Text>
434
+ <Text color={theme.dim}>Next: <Text color={theme.text}>{fullName}</Text></Text>
435
+ {switchNotice ? <Text color={theme.dim}>{switchNotice}</Text> : null}
436
+ </Box>
437
+ )
438
+ : null}
439
+ {recordsDiff.map(diff => (
440
+ <Text key={diff.key}>
441
+ <Text color={theme.dim}>{`- ${recordLabel(diff.field)}: `}</Text>
442
+ {diff.changed
443
+ ? (
444
+ <>
445
+ {renderRecordValue(diff.field, diff.current)}
446
+ <Text color={theme.dim}>{' → '}</Text>
447
+ {renderRecordValue(diff.field, diff.next)}
448
+ </>
449
+ )
450
+ : renderRecordValue(diff.field, diff.next)}
451
+ </Text>
452
+ ))}
453
+ {!hasRecordChanges
454
+ ? <Box marginTop={1}><Text color={theme.dim}>All ENS records already match. Link this ENS name to the token URI.</Text></Box>
455
+ : null}
456
+ </Box>
457
+ <Box marginTop={1}>
458
+ <Select<ReviewAction>
459
+ options={options}
460
+ hintLayout="inline"
461
+ onSubmit={choice => {
462
+ if (choice === 'continue') return onContinue()
463
+ if (choice === 'change') return onChange()
464
+ return onBack()
465
+ }}
466
+ onCancel={onBack}
467
+ />
468
+ </Box>
469
+ </Surface>
470
+ )
471
+ }
@@ -0,0 +1,198 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import type { Address } from 'viem'
4
+ import { mainnet } from 'viem/chains'
5
+ import { Surface } from '../../../../ui/Surface.js'
6
+ import { useAppInput } from '../../../../app/input/AppInputProvider.js'
7
+ import {
8
+ createMainnetClient,
9
+ } from '../../../ens/ensLookup.js'
10
+ import {
11
+ buildCommitment,
12
+ encodeCommitTransaction,
13
+ encodeRegisterTransaction,
14
+ MIN_COMMIT_AGE_SECONDS,
15
+ ONE_YEAR_SECONDS,
16
+ } from '../../../ens/ensRegistration.js'
17
+ import type { EnsSubdomainDeletePlan } from '../../../ens/ensAutomation.js'
18
+ import {
19
+ sendBrowserWalletTransaction,
20
+ type BrowserWalletReady,
21
+ } from '../../../wallet/browserWallet.js'
22
+ import { WalletApprovalScreen } from '../../components/WalletApprovalScreen.js'
23
+ import { footerHint } from './EnsEditShared.js'
24
+ import type { RegisterCommitPhase } from './ensEditTypes.js'
25
+ import { theme } from '../../../../ui/theme.js'
26
+
27
+ export const EscCancel: React.FC<{ onCancel: () => void }> = ({ onCancel }) => {
28
+ useAppInput((_input, key) => {
29
+ if (key.escape) onCancel()
30
+ })
31
+ return null
32
+ }
33
+
34
+ export const RegisterRootCommitRunner: React.FC<{
35
+ phase: RegisterCommitPhase
36
+ ownerAddress: Address
37
+ walletSession: BrowserWalletReady | null
38
+ onWalletReady: (session: BrowserWalletReady | null) => void
39
+ onCommitted: () => void
40
+ onError: (msg: string) => void
41
+ }> = ({ phase, ownerAddress, walletSession, onWalletReady, onCommitted, onError }) => {
42
+ const startedRef = React.useRef(false)
43
+ React.useEffect(() => {
44
+ if (startedRef.current) return
45
+ startedRef.current = true
46
+ const built = buildCommitment({ label: phase.label, owner: ownerAddress, durationSeconds: BigInt(ONE_YEAR_SECONDS), secret: phase.secret })
47
+ const tx = encodeCommitTransaction(built.commitment)
48
+ sendBrowserWalletTransaction({
49
+ chainId: mainnet.id,
50
+ expectedAccount: ownerAddress,
51
+ to: tx.to,
52
+ data: tx.data,
53
+ purpose: 'register-root-commit',
54
+ onReady: ready => onWalletReady(ready),
55
+ })
56
+ .then(async result => {
57
+ onWalletReady(null)
58
+ const client = createMainnetClient()
59
+ await client.waitForTransactionReceipt({ hash: result.txHash })
60
+ onCommitted()
61
+ })
62
+ .catch((err: unknown) => {
63
+ onWalletReady(null)
64
+ onError(err instanceof Error ? err.message : String(err))
65
+ })
66
+ }, [])
67
+ return (
68
+ <WalletApprovalScreen
69
+ title="Commit ENS Name"
70
+ subtitle={`Submitting the first of two transactions for ${phase.label}.eth on Ethereum mainnet. Wait for confirmation.`}
71
+ walletSession={walletSession}
72
+ label="waiting for connected wallet transaction..."
73
+ onCancel={() => onError('Commit cancelled. Restart from the name input.')}
74
+ />
75
+ )
76
+ }
77
+
78
+ export const RegisterRootWaitScreen: React.FC<{
79
+ phase: RegisterCommitPhase & { commitMinedAt: number }
80
+ onReady: () => void
81
+ onCancel: () => void
82
+ }> = ({ phase, onReady, onCancel }) => {
83
+ const [, setTick] = React.useState(0)
84
+ React.useEffect(() => {
85
+ const interval = setInterval(() => setTick(t => t + 1), 500)
86
+ return () => clearInterval(interval)
87
+ }, [])
88
+ const elapsedSec = Math.floor((Date.now() - phase.commitMinedAt) / 1000)
89
+ const remaining = Math.max(0, MIN_COMMIT_AGE_SECONDS + 1 - elapsedSec)
90
+ React.useEffect(() => {
91
+ if (remaining === 0) onReady()
92
+ }, [remaining])
93
+ return (
94
+ <Surface
95
+ title="Commit Confirmed, Waiting"
96
+ subtitle={`Anti-frontrun delay before registering ${phase.label}.eth. ENS requires a 60-second wait between commit and register.`}
97
+ footer={footerHint('esc cancel registration')}
98
+ >
99
+ <Box marginTop={1} flexDirection="column">
100
+ <Text color={theme.text}>{remaining > 0 ? `${remaining} seconds remaining...` : 'Ready, advancing to the registration transaction...'}</Text>
101
+ <Text color={theme.dim}>Keep this window open. The commit expires after 24 hours if you walk away.</Text>
102
+ </Box>
103
+ <EscCancel onCancel={onCancel} />
104
+ </Surface>
105
+ )
106
+ }
107
+
108
+ export const DeleteSubdomainTxRunner: React.FC<{
109
+ plan: EnsSubdomainDeletePlan
110
+ ownerAddress: Address
111
+ walletSession: BrowserWalletReady | null
112
+ onWalletReady: (session: BrowserWalletReady | null) => void
113
+ onDeleted: () => void
114
+ onError: (msg: string) => void
115
+ }> = ({ plan, ownerAddress, walletSession, onWalletReady, onDeleted, onError }) => {
116
+ const startedRef = React.useRef(false)
117
+ React.useEffect(() => {
118
+ if (startedRef.current) return
119
+ startedRef.current = true
120
+ sendBrowserWalletTransaction({
121
+ chainId: mainnet.id,
122
+ expectedAccount: ownerAddress,
123
+ to: plan.transaction.to,
124
+ data: plan.transaction.data,
125
+ purpose: 'delete-ens-subdomain',
126
+ onReady: ready => onWalletReady(ready),
127
+ })
128
+ .then(async result => {
129
+ onWalletReady(null)
130
+ const client = createMainnetClient()
131
+ await client.waitForTransactionReceipt({ hash: result.txHash })
132
+ onDeleted()
133
+ })
134
+ .catch((err: unknown) => {
135
+ onWalletReady(null)
136
+ onError(err instanceof Error ? err.message : String(err))
137
+ })
138
+ }, [])
139
+ return (
140
+ <WalletApprovalScreen
141
+ title="Delete ENS Subdomain"
142
+ subtitle={`Clearing the subnode for ${plan.fullName} at ${plan.parentName} on Ethereum mainnet.`}
143
+ walletSession={walletSession}
144
+ label="waiting for owner wallet transaction..."
145
+ onCancel={() => onError('Subdomain deletion cancelled.')}
146
+ />
147
+ )
148
+ }
149
+
150
+ export const RegisterRootTxRunner: React.FC<{
151
+ phase: RegisterCommitPhase
152
+ ownerAddress: Address
153
+ walletSession: BrowserWalletReady | null
154
+ onWalletReady: (session: BrowserWalletReady | null) => void
155
+ onRegistered: () => void
156
+ onError: (msg: string) => void
157
+ }> = ({ phase, ownerAddress, walletSession, onWalletReady, onRegistered, onError }) => {
158
+ const startedRef = React.useRef(false)
159
+ React.useEffect(() => {
160
+ if (startedRef.current) return
161
+ startedRef.current = true
162
+ const tx = encodeRegisterTransaction({
163
+ label: phase.label,
164
+ owner: ownerAddress,
165
+ durationSeconds: BigInt(ONE_YEAR_SECONDS),
166
+ secret: phase.secret,
167
+ rentPrice: phase.price,
168
+ })
169
+ sendBrowserWalletTransaction({
170
+ chainId: mainnet.id,
171
+ expectedAccount: ownerAddress,
172
+ to: tx.to,
173
+ data: tx.data,
174
+ value: tx.value,
175
+ purpose: 'register-root-tx',
176
+ onReady: ready => onWalletReady(ready),
177
+ })
178
+ .then(async result => {
179
+ onWalletReady(null)
180
+ const client = createMainnetClient()
181
+ await client.waitForTransactionReceipt({ hash: result.txHash })
182
+ onRegistered()
183
+ })
184
+ .catch((err: unknown) => {
185
+ onWalletReady(null)
186
+ onError(err instanceof Error ? err.message : String(err))
187
+ })
188
+ }, [])
189
+ return (
190
+ <WalletApprovalScreen
191
+ title="Register ENS Name"
192
+ subtitle={`Paying 1 year of rent and registering ${phase.label}.eth on Ethereum mainnet.`}
193
+ walletSession={walletSession}
194
+ label="waiting for connected wallet transaction..."
195
+ onCancel={() => onError('Registration cancelled. The commit will expire in 24 hours; restart from the name input to retry.')}
196
+ />
197
+ )
198
+ }