ethagent 3.3.4 → 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 -260
  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
@@ -14,7 +14,7 @@ import {
14
14
  import { networkLabel } from '../shared/model/network.js'
15
15
  import { shortAddress } from '../shared/model/format.js'
16
16
  import { registryConfigFromConfig } from '../../registry/registryConfig.js'
17
- import type { Step } from '../identityHubReducer.js'
17
+ import type { Step } from '../reducer.js'
18
18
  import { WalletApprovalScreen } from '../shared/components/WalletApprovalScreen.js'
19
19
  import { BusyScreen } from '../shared/components/BusyScreen.js'
20
20
  import type { BrowserWalletReady } from '../../wallet/browserWallet.js'
@@ -92,7 +92,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
92
92
  <BusyScreen
93
93
  title={isSwitch ? 'Finding Agents' : 'Finding Agents'}
94
94
  subtitle={step.ownerHandle}
95
- label="checking tokens this wallet directly owns..."
95
+ label="checking owned tokens..."
96
96
  onCancel={onBack}
97
97
  />
98
98
  )
@@ -102,17 +102,14 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
102
102
  return (
103
103
  <Surface
104
104
  title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
105
- subtitle="The connected wallet doesn't directly own an agent token on this network."
105
+ subtitle="This wallet doesn't directly own an agent token."
106
106
  footer={footerHint('enter select · esc back')}
107
107
  >
108
- <Text color={theme.dim}>If this wallet is an approved operator wallet, choose how to find the agent token.</Text>
109
108
  <Select<'ens' | 'token-id' | 'back'>
110
109
  options={[
111
- { value: 'ens', role: 'section', label: 'Recovery Key' },
112
- { value: 'ens', label: 'Enter ENS Name', hint: 'Resolve the agent via its ENS subdomain' },
113
- { value: 'token-id', label: 'Enter Token ID', hint: 'Look up the agent directly by ERC-8004 token ID' },
114
- { value: 'back', role: 'section', label: 'Navigation' },
115
- { value: 'back', label: 'Back', hint: 'Pick a different network', role: 'utility' },
110
+ { value: 'ens', label: 'Enter ENS Name', hint: 'Via ENS subdomain' },
111
+ { value: 'token-id', label: 'Enter Token ID', hint: 'By token ID' },
112
+ { value: 'back', label: 'Back', role: 'utility' },
116
113
  ]}
117
114
  hintLayout="inline"
118
115
  onSubmit={value => value === 'back' ? onBack() : onPickRecoveryMethod(value)}
@@ -131,7 +128,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
131
128
  footer={footerHint('esc cancels')}
132
129
  >
133
130
  <Box marginTop={1}>
134
- <Spinner label="looking up ENS name onchain..." />
131
+ <Spinner label="looking up ENS..." />
135
132
  </Box>
136
133
  </Surface>
137
134
  )
@@ -139,10 +136,9 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
139
136
  return (
140
137
  <Surface
141
138
  title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
142
- subtitle="Enter the agent's ENS name to decrypt with an authorized operator wallet."
139
+ subtitle="Enter the agent ENS name."
143
140
  footer={footerHint('enter continue · esc back')}
144
141
  >
145
- <Text color={theme.dim}>The full agent subdomain, e.g. agent.example.eth.</Text>
146
142
  <TextInput
147
143
  placeholder="agent.example.eth"
148
144
  onSubmit={value => onEnsSubmit(value.trim())}
@@ -167,7 +163,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
167
163
  footer={footerHint('esc cancels')}
168
164
  >
169
165
  <Box marginTop={1}>
170
- <Spinner label="looking up token onchain..." />
166
+ <Spinner label="looking up token..." />
171
167
  </Box>
172
168
  </Surface>
173
169
  )
@@ -175,10 +171,9 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
175
171
  return (
176
172
  <Surface
177
173
  title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
178
- subtitle={`Enter the ERC-8004 token ID on ${networkLabelForRegistry(step.registry)}.`}
174
+ subtitle={`Token ID on ${networkLabelForRegistry(step.registry)}.`}
179
175
  footer={footerHint('enter continue · esc back')}
180
176
  >
181
- <Text color={theme.dim}>The integer token ID assigned at mint.</Text>
182
177
  <TextInput
183
178
  placeholder="45744"
184
179
  onSubmit={value => onTokenIdSubmit(value.trim())}
@@ -205,9 +200,8 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
205
200
  <Text color={theme.dim}>{view.detail}</Text>
206
201
  <Select<'retry' | 'network'>
207
202
  options={[
208
- { value: 'retry', role: 'section', label: 'Next' },
209
- { value: 'retry', label: 'Retry Search', hint: 'Search token ownership and ERC-8004 restore access again' },
210
- { value: 'network', label: 'Choose Network', hint: 'Search a different ERC-8004 network' },
203
+ { value: 'retry', label: 'Retry Search' },
204
+ { value: 'network', label: 'Choose Network' },
211
205
  ]}
212
206
  hintLayout="inline"
213
207
  onSubmit={choice => {
@@ -238,11 +232,9 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
238
232
  hint: tokenCandidateHint(candidate),
239
233
  }
240
234
  }),
241
- { value: '__ens__', role: 'section', label: 'Recovery Key' },
242
- { value: '__ens__', label: 'Enter ENS Name', hint: 'Resolve the agent via its ENS subdomain' },
243
- { value: '__token-id__', label: 'Enter Token ID', hint: 'Look up the agent directly by ERC-8004 token ID' },
244
- { value: '__back__', role: 'section', label: 'Navigation' },
245
- { value: '__back__', label: 'Back', hint: 'Return to the previous step', role: 'utility' },
235
+ { value: '__ens__', label: 'Enter ENS Name', hint: 'Via ENS subdomain' },
236
+ { value: '__token-id__', label: 'Enter Token ID', hint: 'By token ID' },
237
+ { value: '__back__', label: 'Back', role: 'utility' },
246
238
  ]}
247
239
  hintLayout="inline"
248
240
  onSubmit={value => {
@@ -262,7 +254,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
262
254
  <BusyScreen
263
255
  title={isSwitch ? 'Switching Agent' : 'Restoring Your Agent'}
264
256
  subtitle="IPFS"
265
- label="opening encrypted state from IPFS..."
257
+ label="opening from IPFS..."
266
258
  onCancel={onBack}
267
259
  />
268
260
  )
@@ -14,7 +14,7 @@ import { syncAgentCardManifest } from '../../continuity/skills/publicSkillsSync.
14
14
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
15
15
  import { requestBrowserWalletSignature } from '../../wallet/browserWallet.js'
16
16
  import { setVaultAddressField } from '../../identityCompat.js'
17
- import type { Step } from '../identityHubReducer.js'
17
+ import type { Step } from '../reducer.js'
18
18
  import type { EffectCallbacks } from '../shared/effects/types.js'
19
19
  import { isContinuitySnapshotEnvelope } from './envelopes.js'
20
20
  import { restoreSignatureRequestForStep } from './auth.js'
@@ -10,7 +10,7 @@ import {
10
10
  import { assertAgentStateBackupOwner } from '../../crypto/backupEnvelope.js'
11
11
  import type { Erc8004AgentCandidate } from '../../registry/erc8004.js'
12
12
  import { requestBrowserWalletAccount, type WalletPurpose } from '../../wallet/browserWallet.js'
13
- import type { Step } from '../identityHubReducer.js'
13
+ import type { Step } from '../reducer.js'
14
14
  import type { EffectCallbacks } from '../shared/effects/types.js'
15
15
  import { isContinuitySnapshotEnvelope, parseRestorableEnvelope } from './envelopes.js'
16
16
  import {
@@ -7,7 +7,7 @@ import {
7
7
  type Erc8004AgentCandidate,
8
8
  type Erc8004RegistryConfig,
9
9
  } from '../../registry/erc8004.js'
10
- import type { RestorePurpose, Step } from '../identityHubReducer.js'
10
+ import type { RestorePurpose, Step } from '../reducer.js'
11
11
  import type { EffectCallbacks } from '../shared/effects/types.js'
12
12
  import { isAbortError, isAuthorizedOperatorAddress, requesterAddressFromHandle } from './helpers.js'
13
13
  import type { Address } from 'viem'
@@ -1,5 +1,5 @@
1
1
  import { catFromIpfs } from '../../storage/ipfs.js'
2
- import type { Step } from '../identityHubReducer.js'
2
+ import type { Step } from '../reducer.js'
3
3
  import type { EffectCallbacks } from '../shared/effects/types.js'
4
4
  import { parseRestorableEnvelope } from './envelopes.js'
5
5
  import { assertCandidateCanReadEnvelope, restoreSignatureRequestForStep } from './auth.js'
@@ -2,7 +2,7 @@ import type { EthagentConfig } from '../../../storage/config.js'
2
2
  import { saveConfig } from '../../../storage/config.js'
3
3
  import { normalizeErc8004RegistryConfig } from '../../registry/erc8004.js'
4
4
  import { registryConfigFromConfig } from '../../registry/registryConfig.js'
5
- import type { Step } from '../identityHubReducer.js'
5
+ import type { Step } from '../reducer.js'
6
6
  import type { EffectCallbacks } from '../shared/effects/types.js'
7
7
 
8
8
  export async function runRestoreRegistrySubmit(
@@ -5,15 +5,8 @@ import { runRestoreConnectWallet } from './auth.js'
5
5
  import { runRestoreDiscover } from './discover.js'
6
6
  import { runRestoreFetch } from './fetch.js'
7
7
  import type { EffectCallbacks } from '../shared/effects/types.js'
8
- import type { Step } from '../identityHubReducer.js'
9
-
10
- const MIN_BUSY_ERROR_MS = 2000
11
-
12
- function waitForMinimumBusyTime(startedAt: number): Promise<void> {
13
- const elapsed = Date.now() - startedAt
14
- if (elapsed >= MIN_BUSY_ERROR_MS) return Promise.resolve()
15
- return new Promise(resolve => setTimeout(resolve, MIN_BUSY_ERROR_MS - elapsed))
16
- }
8
+ import type { Step } from '../reducer.js'
9
+ import { waitForMinimumBusyTime } from '../shared/utils.js'
17
10
 
18
11
  type RestoreFlowEffectsArgs = {
19
12
  step: Step
@@ -4,17 +4,10 @@ import { Surface } from '../../../ui/Surface.js'
4
4
  import { Select } from '../../../ui/Select.js'
5
5
  import { theme } from '../../../ui/theme.js'
6
6
  import { PinataJwtInput } from '../shared/components/PinataJwtInput.js'
7
- import type { Step } from '../identityHubReducer.js'
7
+ import type { Step } from '../reducer.js'
8
8
 
9
9
  type StorageCredentialAction = 'edit' | 'forget' | 'back'
10
10
 
11
- export const STORAGE_CREDENTIAL_FORGET_COPY = [
12
- 'removes the saved IPFS storage token from this machine.',
13
- 'existing pinned IPFS backups are not deleted.',
14
- 'new encrypted snapshots cannot be pinned with that account until you save a token again.',
15
- 'agent identity and sessions stay on this machine.',
16
- ] as const
17
-
18
11
  type StorageCredentialScreenProps = {
19
12
  step: Extract<Step, { kind: 'storage-credential' | 'storage-credential-input' | 'storage-credential-forget-confirm' }>
20
13
  hasCredential: boolean
@@ -53,24 +46,18 @@ export const StorageCredentialScreen: React.FC<StorageCredentialScreenProps> = (
53
46
  return (
54
47
  <Surface
55
48
  title="Forget IPFS Storage?"
56
- subtitle="This only removes the local token used for pinning."
49
+ subtitle="Local token only. Pinned files are not deleted."
57
50
  footer={footer}
58
51
  >
59
52
  <Box flexDirection="column">
60
- {STORAGE_CREDENTIAL_FORGET_COPY.map(line => (
61
- <Text key={line} color={theme.dim}>- {line}</Text>
62
- ))}
63
- </Box>
64
- <Box marginTop={1}>
65
- <Text color={theme.accentPeriwinkle}>Remove the token from this machine?</Text>
53
+ <Text color={theme.dim}>Removes the saved pinning token from this machine.</Text>
54
+ <Text color={theme.dim}>Existing IPFS backups and agent data are not affected.</Text>
66
55
  </Box>
67
56
  <Box marginTop={1}>
68
57
  <Select<StorageCredentialAction>
69
58
  options={[
70
- { value: 'forget', role: 'section', label: 'Credential' },
71
- { value: 'forget', label: 'Forget Credential', hint: 'Remove local IPFS pinning token' },
72
- { value: 'back', role: 'section', label: 'Navigation' },
73
- { value: 'back', label: 'Keep Credential', hint: 'Return without changing storage access', role: 'utility' },
59
+ { value: 'forget', label: 'Forget Credential' },
60
+ { value: 'back', label: 'Keep Credential', role: 'utility' },
74
61
  ]}
75
62
  hintLayout="inline"
76
63
  onSubmit={choice => choice === 'forget' ? onConfirmForget() : onCancel()}
@@ -84,17 +71,15 @@ export const StorageCredentialScreen: React.FC<StorageCredentialScreenProps> = (
84
71
  return (
85
72
  <Surface
86
73
  title="IPFS Storage"
87
- subtitle="Manage the credential used to pin encrypted snapshots from this machine."
74
+ subtitle="Pin encrypted snapshots from this machine."
88
75
  footer={footer}
89
76
  >
90
77
  <Box marginTop={1}>
91
78
  <Select<StorageCredentialAction>
92
79
  options={[
93
- { value: 'edit', role: 'section', label: 'Credential' },
94
- { value: 'edit', label: hasCredential ? 'Replace Credential' : 'Save Credential', hint: 'Store Pinata JWT for IPFS pinning' },
95
- { value: 'forget', label: 'Forget Credential', hint: 'Remove the local pinning token. Existing pins remain', disabled: !hasCredential },
96
- { value: 'back', role: 'section', label: 'Navigation' },
97
- { value: 'back', label: 'Back', hint: 'Return to Identity Hub menu', role: 'utility' },
80
+ { value: 'edit', label: hasCredential ? 'Replace Credential' : 'Save Credential' },
81
+ { value: 'forget', label: 'Forget Credential', disabled: !hasCredential },
82
+ { value: 'back', label: 'Back', role: 'utility' },
98
83
  ]}
99
84
  hintLayout="inline"
100
85
  onSubmit={choice => {
@@ -4,7 +4,7 @@ import { theme } from '../../../../ui/theme.js'
4
4
  import { Surface } from '../../../../ui/Surface.js'
5
5
  import { Select, type SelectOption } from '../../../../ui/Select.js'
6
6
  import type { EthagentConfig, EthagentIdentity } from '../../../../storage/config.js'
7
- import { copyableIdentityFields, identityValuesCopyHint } from '../model/copy.js'
7
+ import { copyableIdentityFields } from '../model/copy.js'
8
8
  import { IdentitySummary } from './IdentitySummary.js'
9
9
  import type { ContinuityWorkingTreeStatus } from '../../../continuity/storage.js'
10
10
 
@@ -35,19 +35,17 @@ export const DetailsScreen: React.FC<DetailsScreenProps> = ({
35
35
  }) => {
36
36
  const copyable = copyableIdentityFields(identity, config)
37
37
  const options: Array<SelectOption<CopyAction>> = [
38
- ...(copyable.length > 0 ? [{ value: 'back' as const, role: 'section' as const, label: 'Values' }] : []),
39
38
  ...copyable.map(field => ({
40
39
  value: `copy:${field.label}` as const,
41
40
  label: field.label,
42
41
  hint: shortPreview(field.value),
43
42
  })),
44
43
  ...(copyable.length === 0 ? [{ value: 'back' as const, role: 'notice' as const, label: 'No Values Available Yet' }] : []),
45
- { value: 'back', role: 'section', label: 'Navigation' },
46
- { value: 'back', label: 'Back', hint: 'Return to Identity Hub menu', role: 'utility' },
44
+ { value: 'back', label: 'Back', role: 'utility' },
47
45
  ]
48
46
 
49
47
  return (
50
- <Surface title="Token Values" subtitle={unlinked ? 'Token no longer linked to this wallet, values retained for reference.' : `${identityValuesCopyHint(identity)}.`} footer={footer}>
48
+ <Surface title="Token Values" subtitle={unlinked ? 'Token unlinked; values kept for reference.' : undefined} footer={footer}>
51
49
  <IdentitySummary identity={identity} config={config} workingStatus={workingStatus} onchainOwner={onchainOwner} />
52
50
  {copyNotice ? <Text color={theme.accentPeriwinkle} bold>{copyNotice}</Text> : null}
53
51
  <Box marginTop={1}>
@@ -68,6 +66,6 @@ export const DetailsScreen: React.FC<DetailsScreenProps> = ({
68
66
  }
69
67
 
70
68
  function shortPreview(value: string): string {
71
- if (value.length <= 42) return value
72
- return `${value.slice(0, 18)}...${value.slice(-14)}`
69
+ if (value.length <= 22) return value
70
+ return `${value.slice(0, 11)}...${value.slice(-8)}`
73
71
  }
@@ -3,15 +3,14 @@ import { Text } from 'ink'
3
3
  import { Surface } from '../../../../ui/Surface.js'
4
4
  import { Select } from '../../../../ui/Select.js'
5
5
  import { theme } from '../../../../ui/theme.js'
6
- import type { IdentityHubErrorView } from '../model/errors.js'
7
- import type { Step } from '../../identityHubReducer.js'
6
+ import type { IdentityManagerErrorView } from '../model/errors.js'
7
+ import type { Step } from '../../reducer.js'
8
8
 
9
9
  type ErrorScreenProps = {
10
- error: IdentityHubErrorView
10
+ error: IdentityManagerErrorView
11
11
  back: Step
12
12
  footer: React.ReactNode
13
13
  closeLabel?: string
14
- closeHint?: string
15
14
  onBack: (back: Step) => void
16
15
  onClose: () => void
17
16
  }
@@ -20,8 +19,7 @@ export const ErrorScreen: React.FC<ErrorScreenProps> = ({
20
19
  error,
21
20
  back,
22
21
  footer,
23
- closeLabel = 'Close Identity Hub',
24
- closeHint = 'Return to chat without retrying',
22
+ closeLabel = 'Close',
25
23
  onBack,
26
24
  onClose,
27
25
  }) => (
@@ -29,10 +27,8 @@ export const ErrorScreen: React.FC<ErrorScreenProps> = ({
29
27
  {error.hint ? <Text color={theme.dim}>{error.hint}</Text> : null}
30
28
  <Select<'back' | 'close'>
31
29
  options={[
32
- { value: 'back', role: 'section', label: 'Recovery' },
33
- { value: 'back', label: 'Go Back', hint: 'Return to the previous identity step' },
34
- { value: 'close', role: 'section', label: 'Exit' },
35
- { value: 'close', label: closeLabel, hint: closeHint, role: 'utility' },
30
+ { value: 'back', label: 'Back' },
31
+ { value: 'close', label: closeLabel, role: 'utility' },
36
32
  ]}
37
33
  hintLayout="inline"
38
34
  onSubmit={choice => {
@@ -13,15 +13,16 @@ export const FlowTimeline: React.FC<FlowTimelineProps> = ({ steps, current }) =>
13
13
  const n = index + 1
14
14
  const active = n === current
15
15
  const done = n < current
16
- const glyph = done ? '' : active ? '●' : '○'
16
+ const glyph = active ? '' : done ? '●' : '○'
17
17
  return (
18
18
  <React.Fragment key={`${index}:${step}`}>
19
- {index > 0 ? <Text> </Text> : null}
19
+ {index > 0 ? <Text> </Text> : null}
20
20
  <Text color={active ? theme.accentPeriwinkle : done ? theme.dim : theme.textSubtle} bold={active}>
21
- {glyph} {step}
21
+ {glyph}
22
22
  </Text>
23
23
  </React.Fragment>
24
24
  )
25
25
  })}
26
+ <Text color={theme.dim}>{` ${current}/${steps.length}`}</Text>
26
27
  </Text>
27
28
  )
@@ -8,11 +8,7 @@ import {
8
8
  readCustodyMode,
9
9
  readIdentityStateString,
10
10
  } from '../../custody/state.js'
11
- import {
12
- hasPendingPublish,
13
- localChangeStatusView,
14
- type LocalChangeStatusView,
15
- } from '../../continuity/state.js'
11
+ import { hasPendingPublish } from '../../continuity/state.js'
16
12
  import { ensValidationReasonText, selectEnsStatus } from '../../ens/state.js'
17
13
  import { shortAddress } from '../model/format.js'
18
14
  import { identitySummaryRows, lastBackupLabel } from '../../profile/identity.js'
@@ -24,14 +20,13 @@ interface IdentitySummaryProps {
24
20
  identity?: EthagentIdentity
25
21
  config?: EthagentConfig
26
22
  workingStatus?: ContinuityWorkingTreeStatus | null
27
- hideLocalChanges?: boolean
28
23
  hideHeader?: boolean
29
24
  tokenLinked?: boolean
30
25
  onchainOwner?: string
31
26
  compact?: boolean
32
27
  }
33
28
 
34
- export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, config, workingStatus, hideLocalChanges = false, hideHeader = false, tokenLinked = true, onchainOwner, compact = false }) => {
29
+ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, config, hideHeader = false, tokenLinked = true, onchainOwner, compact = false }) => {
35
30
  if (!identity) {
36
31
  return (
37
32
  <Text color={theme.dim}>No agent yet. Create or load one.</Text>
@@ -43,7 +38,6 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
43
38
  const stateName = readIdentityStateString(identity.state, 'name')
44
39
 
45
40
  const row = (label: string) => rows.find(item => item.label === label)
46
- const localChangeStatus = localChangeStatusView(workingStatus)
47
41
 
48
42
  const ensStatus = selectEnsStatus(identity)
49
43
  const custodyMode = readCustodyMode(identity.state)
@@ -61,9 +55,10 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
61
55
  : displayValue(tokenValue)
62
56
 
63
57
  if (compact) {
64
- const name = stateName || 'Active Agent'
58
+ const rawName = stateName || 'Active Agent'
59
+ const name = rawName.length > 16 ? `${rawName.slice(0, 15)}…` : rawName
65
60
  const tokenSegment = identity.agentId ? `#${identity.agentId}` : null
66
- const networkSegment = identity.agentId ? displayValue(networkValue) : null
61
+ const networkSegment = identity.agentId ? networkValue : null
67
62
  const ensSegment = ensStatus.kind === 'linked'
68
63
  ? ensStatus.name
69
64
  : ensStatus.kind === 'issue'
@@ -71,9 +66,9 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
71
66
  : null
72
67
  return (
73
68
  <Text>
74
- <Text color={theme.accentPeriwinkle} bold>{name}</Text>
75
- {tokenSegment ? <><Text color={theme.dim}> · </Text><Text color={theme.text}>{tokenSegment}</Text></> : null}
76
- {networkSegment ? <><Text color={theme.dim}> · </Text><Text color={theme.text}>{networkSegment}</Text></> : null}
69
+ <Text color={theme.textSubtle}>{name}</Text>
70
+ {tokenSegment ? <><Text color={theme.dim}> · </Text><Text color={theme.dim}>{tokenSegment}</Text></> : null}
71
+ {networkSegment ? <><Text color={theme.dim}> · </Text><Text color={theme.dim}>{networkSegment}</Text></> : null}
77
72
  {ensSegment ? <><Text color={theme.dim}> · </Text><Text color={ensStatus.kind === 'issue' ? theme.accentError : theme.accentPeriwinkle}>{ensSegment}</Text></> : null}
78
73
  </Text>
79
74
  )
@@ -153,11 +148,6 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
153
148
  <TransferSnapshotStatus status={transferSnapshot} />
154
149
  </Box>
155
150
  ) : null}
156
- {!hideLocalChanges && (
157
- <Box marginTop={1}>
158
- <LocalChangeStatusLine status={localChangeStatus} />
159
- </Box>
160
- )}
161
151
  </Box>
162
152
  )
163
153
  }
@@ -165,32 +155,22 @@ export const IdentitySummary: React.FC<IdentitySummaryProps> = ({ identity, conf
165
155
  type SummaryCell = { label: string; value: React.ReactNode }
166
156
 
167
157
  const LEFT_LABEL_WIDTH = 12
168
- const LEFT_VALUE_WIDTH = 30
169
- const RIGHT_CELL_WIDTH = 36
158
+
159
+ const SummaryCellLine: React.FC<{ cell: SummaryCell }> = ({ cell }) => (
160
+ <Text>
161
+ <Text color={theme.dim}>{cell.label.padEnd(LEFT_LABEL_WIDTH)}</Text>
162
+ {cell.value}
163
+ </Text>
164
+ )
170
165
 
171
166
  const SummaryRow: React.FC<{ left: SummaryCell; right?: SummaryCell }> = ({ left, right }) => {
172
167
  if (!right) {
173
- return (
174
- <Text>
175
- <Text color={theme.dim}>{left.label.padEnd(LEFT_LABEL_WIDTH)}</Text>
176
- {left.value}
177
- </Text>
178
- )
168
+ return <SummaryCellLine cell={left} />
179
169
  }
180
170
  return (
181
- <Box flexDirection="row">
182
- <Box width={LEFT_LABEL_WIDTH + LEFT_VALUE_WIDTH}>
183
- <Text>
184
- <Text color={theme.dim}>{left.label.padEnd(LEFT_LABEL_WIDTH)}</Text>
185
- {left.value}
186
- </Text>
187
- </Box>
188
- <Box width={RIGHT_CELL_WIDTH}>
189
- <Text>
190
- <Text color={theme.dim}>{right.label.padEnd(LEFT_LABEL_WIDTH)}</Text>
191
- {right.value}
192
- </Text>
193
- </Box>
171
+ <Box flexDirection="column">
172
+ <SummaryCellLine cell={left} />
173
+ <SummaryCellLine cell={right} />
194
174
  </Box>
195
175
  )
196
176
  }
@@ -221,26 +201,6 @@ const TransferSnapshotStatus: React.FC<{ status: NonNullable<TransferSnapshotVie
221
201
  )
222
202
  }
223
203
 
224
- const LocalChangeStatusLine: React.FC<{ status: LocalChangeStatusView }> = ({ status }) => {
225
- if (status.hasLocalChanges) {
226
- return (
227
- <Box flexDirection="column">
228
- <Text color={theme.accentError} bold>
229
- Local changes detected
230
- {status.files.length > 0 ? `: ${status.files.join(', ')}` : ''}
231
- </Text>
232
- <Text color={theme.dim}>Save Snapshot Now to publish.</Text>
233
- </Box>
234
- )
235
- }
236
-
237
- if (!status.detail) return null
238
-
239
- const color = status.tone === 'ok' || status.tone === 'warn' ? theme.accentPeriwinkle : theme.dim
240
- const label = status.detail === 'None detected' ? 'No local changes detected' : status.detail
241
- return <Text color={color}>{label}</Text>
242
- }
243
-
244
204
  function displayValue(value: string): string {
245
205
  const mapped = DISPLAY_VALUES[value]
246
206
  return mapped ?? value
@@ -0,0 +1,147 @@
1
+ import React, { useEffect, useMemo, useState } from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { theme, gradientColor, PANEL_WIDTH } from '../../../../ui/theme.js'
4
+ import { useAppInput } from '../../../../app/input/AppInputProvider.js'
5
+
6
+ export type LazyMenuItem<T> = {
7
+ kind?: 'item'
8
+ value: T
9
+ label: string
10
+ shortcut?: string
11
+ disabled?: boolean
12
+ hint?: string
13
+ note?: string
14
+ noteColor?: string
15
+ inlineNote?: string
16
+ inlineNoteColor?: string
17
+ }
18
+
19
+ export type LazyMenuSection = {
20
+ kind: 'section'
21
+ label: string
22
+ }
23
+
24
+ export type LazyMenuRow<T> = LazyMenuItem<T> | LazyMenuSection
25
+
26
+ function isItem<T>(row: LazyMenuRow<T>): row is LazyMenuItem<T> {
27
+ return row.kind !== 'section'
28
+ }
29
+
30
+ function rainbowColor(index: number, total: number): string {
31
+ return gradientColor(total <= 1 ? 0 : index / (total - 1))
32
+ }
33
+
34
+ type Props<T> = {
35
+ rows: Array<LazyMenuRow<T>>
36
+ width?: number
37
+ onSubmit: (value: T) => void
38
+ onCancel?: () => void
39
+ }
40
+
41
+ export function LazyMenu<T>({ rows, width = PANEL_WIDTH, onSubmit, onCancel }: Props<T>) {
42
+ const firstSelectable = Math.max(0, rows.findIndex(r => isItem(r) && !r.disabled))
43
+ const [index, setIndex] = useState(firstSelectable)
44
+
45
+ const sig = useMemo(
46
+ () => rows.map(r => isItem(r) ? `i:${r.shortcut ?? ''}${r.disabled ? '!' : ''}` : `s:${r.label}`).join('|'),
47
+ [rows],
48
+ )
49
+ useEffect(() => {
50
+ setIndex(prev => {
51
+ const at = rows[prev]
52
+ if (at && isItem(at) && !at.disabled) return prev
53
+ const next = rows.findIndex(r => isItem(r) && !r.disabled)
54
+ return next === -1 ? 0 : next
55
+ })
56
+ }, [sig, rows])
57
+
58
+ const moveBy = (delta: number) => {
59
+ if (rows.length === 0) return
60
+ let next = index
61
+ for (let i = 0; i < rows.length; i += 1) {
62
+ next = (next + delta + rows.length) % rows.length
63
+ const candidate = rows[next]
64
+ if (candidate && isItem(candidate) && !candidate.disabled) { setIndex(next); return }
65
+ }
66
+ }
67
+
68
+ useAppInput((input, key) => {
69
+ if (key.upArrow || input === 'k') moveBy(-1)
70
+ else if (key.downArrow || input === 'j') moveBy(1)
71
+ else if (key.return) {
72
+ const r = rows[index]
73
+ if (r && isItem(r) && !r.disabled) onSubmit(r.value)
74
+ } else if (key.escape || (key.ctrl && input === 'c')) {
75
+ onCancel?.()
76
+ } else if (input && !key.ctrl && !key.meta) {
77
+ const lower = input.toLowerCase()
78
+ const hit = rows.findIndex(r => isItem(r) && !r.disabled && r.shortcut?.toLowerCase() === lower)
79
+ if (hit >= 0) {
80
+ const candidate = rows[hit]!
81
+ if (isItem(candidate)) { setIndex(hit); onSubmit(candidate.value) }
82
+ }
83
+ }
84
+ })
85
+
86
+ return (
87
+ <Box flexDirection="column">
88
+ {rows.map((row, i) => {
89
+ if (!isItem(row)) {
90
+ return (
91
+ <React.Fragment key={i}>
92
+ <Box flexDirection="row" width={width}>
93
+ <Text color={theme.menuStatus} bold>{row.label}</Text>
94
+ </Box>
95
+ </React.Fragment>
96
+ )
97
+ }
98
+ const active = i === index
99
+ const disabled = !!row.disabled
100
+ const cursorColor = disabled ? theme.border : active ? theme.accentPeriwinkle : theme.dim
101
+ const shortcutColor = disabled ? theme.border : theme.menuShortcut
102
+ const chars = row.label.split('')
103
+ const shortcut = row.shortcut
104
+ const inline = row.inlineNote
105
+ const inlineWidth = inline ? inline.length + 2 : 0
106
+ const pad = shortcut ? Math.max(2, width - (2 + row.label.length + inlineWidth + shortcut.length)) : 0
107
+ return (
108
+ <React.Fragment key={i}>
109
+ <Box flexDirection="row" {...(shortcut ? { width } : {})}>
110
+ <Text color={cursorColor}>{active ? '> ' : ' '}</Text>
111
+ <Text>
112
+ {active && !disabled
113
+ ? chars.map((ch, ci) => (
114
+ <Text key={ci} color={rainbowColor(ci, chars.length)}>{ch}</Text>
115
+ ))
116
+ : <Text color={disabled ? theme.border : theme.text}>{row.label}</Text>
117
+ }
118
+ </Text>
119
+ {inline ? (
120
+ <>
121
+ <Text>{' '}</Text>
122
+ <Text color={row.inlineNoteColor ?? theme.dim}>{inline}</Text>
123
+ </>
124
+ ) : null}
125
+ {shortcut ? (
126
+ <>
127
+ <Text>{' '.repeat(pad)}</Text>
128
+ <Text color={shortcutColor}>{shortcut}</Text>
129
+ </>
130
+ ) : null}
131
+ </Box>
132
+ {row.hint && disabled ? (
133
+ <Box paddingLeft={2}>
134
+ <Text color={theme.dim}>{row.hint}</Text>
135
+ </Box>
136
+ ) : null}
137
+ {row.note ? (
138
+ <Box paddingLeft={2}>
139
+ <Text color={row.noteColor ?? theme.dim}>{row.note}</Text>
140
+ </Box>
141
+ ) : null}
142
+ </React.Fragment>
143
+ )
144
+ })}
145
+ </Box>
146
+ )
147
+ }