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,170 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { Surface } from '../../../../ui/Surface.js'
4
+ import { Select } from '../../../../ui/Select.js'
5
+ import { theme } from '../../../../ui/theme.js'
6
+ import type { EthagentConfig, EthagentIdentity } from '../../../../storage/config.js'
7
+ import type { ContinuityWorkingTreeStatus } from '../../../continuity/storage.js'
8
+ import { IdentitySummary } from '../../components/IdentitySummary.js'
9
+ import { changedContinuitySnapshotFiles } from '../../model/continuity.js'
10
+ import { readIdentityStateString } from '../../model/custody.js'
11
+ import { shortCid } from '../../model/format.js'
12
+
13
+ type PrivateAction = 'soul' | 'memory' | 'skills' | 'export-backup' | 'back'
14
+ type PublicAction = 'edit' | 'back'
15
+
16
+ interface CommonProps {
17
+ identity?: EthagentIdentity
18
+ config?: EthagentConfig
19
+ workingStatus?: ContinuityWorkingTreeStatus | null
20
+ ready: boolean
21
+ notice?: string
22
+ editorOpened?: boolean
23
+ footer: React.ReactNode
24
+ onBack: () => void
25
+ }
26
+
27
+ const SaveFromHubHint: React.FC<{ workingStatus?: ContinuityWorkingTreeStatus | null }> = ({ workingStatus }) => {
28
+ const needsBackup = workingStatus?.publishState === 'local-changes'
29
+ || workingStatus?.publishState === 'not-published'
30
+ || workingStatus?.publishState === 'verify-needed'
31
+ if (!needsBackup) return null
32
+ const files = changedContinuitySnapshotFiles(workingStatus)
33
+ return (
34
+ <Box marginTop={1} flexDirection="column">
35
+ <Text color={theme.accentError} bold>
36
+ Unsaved changes
37
+ {files.length > 0 ? `: ${files.join(', ')}` : ''}
38
+ </Text>
39
+ <Text color={theme.dim}>Use Save Snapshot Now in the Identity Hub menu, or relaunch ethagent.</Text>
40
+ </Box>
41
+ )
42
+ }
43
+
44
+ export const PrivateContinuityScreen: React.FC<CommonProps & {
45
+ onOpenSoul: () => void
46
+ onOpenMemory: () => void
47
+ onOpenSkills: () => void
48
+ onExportBackup: () => void
49
+ }> = ({
50
+ identity,
51
+ config,
52
+ workingStatus,
53
+ ready,
54
+ notice,
55
+ editorOpened,
56
+ footer,
57
+ onOpenSoul,
58
+ onOpenMemory,
59
+ onOpenSkills,
60
+ onExportBackup,
61
+ onBack,
62
+ }) => (
63
+ <Surface title="Soul, Memory, and Skills" subtitle={notice ?? privateSubtitle(ready)} footer={footer}>
64
+ <IdentitySummary identity={identity} config={config} workingStatus={workingStatus} hideLocalChanges />
65
+ <PrivateRows identity={identity} ready={ready} />
66
+ <SaveFromHubHint workingStatus={workingStatus} />
67
+ {editorOpened && (
68
+ <Box marginTop={1}>
69
+ <Text color={theme.accentPeriwinkle}>Save with ctrl+s in your editor</Text>
70
+ </Box>
71
+ )}
72
+ <Box marginTop={1}>
73
+ <Select<PrivateAction>
74
+ options={[
75
+ { value: 'soul', role: 'section', label: 'Private Continuity' },
76
+ { value: 'soul', label: 'Edit Soul', hint: 'Voice, behavior, and operating preferences', disabled: !ready },
77
+ { value: 'memory', label: 'Edit Memory', hint: 'Durable owner memory for this agent', disabled: !ready },
78
+ { value: 'skills', role: 'section', label: 'Public Capabilities' },
79
+ { value: 'skills', label: 'Edit skills.json', hint: 'Machine-readable capabilities for discovery', disabled: !ready },
80
+ { value: 'export-backup', role: 'section', label: 'Local Backup' },
81
+ { value: 'export-backup', label: 'Save Local Backup', hint: 'Bundle SOUL.md, MEMORY.md, skills.json into a zip you can keep anywhere', disabled: !ready },
82
+ { value: 'back', role: 'section', label: 'Navigation' },
83
+ { value: 'back', label: 'Back', hint: 'Return to Identity Hub menu', role: 'utility' },
84
+ ]}
85
+ hintLayout="inline"
86
+ onSubmit={choice => {
87
+ if (choice === 'soul') return onOpenSoul()
88
+ if (choice === 'memory') return onOpenMemory()
89
+ if (choice === 'skills') return onOpenSkills()
90
+ if (choice === 'export-backup') return onExportBackup()
91
+ return onBack()
92
+ }}
93
+ onCancel={onBack}
94
+ />
95
+ </Box>
96
+ </Surface>
97
+ )
98
+
99
+ export const PublicProfileScreen: React.FC<CommonProps & {
100
+ onEditProfile: () => void
101
+ }> = ({ identity, config, workingStatus, notice, editorOpened, footer, onEditProfile, onBack }) => {
102
+ return (
103
+ <Surface title="Public Profile" subtitle={notice ?? 'Manage the public name, description, icon, and Agent Card.'} footer={footer}>
104
+ <IdentitySummary identity={identity} config={config} workingStatus={workingStatus} hideLocalChanges />
105
+ <PublicProfileRows identity={identity} />
106
+ <SaveFromHubHint workingStatus={workingStatus} />
107
+ {editorOpened && (
108
+ <Box marginTop={1}>
109
+ <Text color={theme.accentPeriwinkle}>Save with ctrl+s in your editor</Text>
110
+ </Box>
111
+ )}
112
+ <Box marginTop={1}>
113
+ <Select<PublicAction>
114
+ options={[
115
+ { value: 'edit', role: 'section', label: 'Profile' },
116
+ { value: 'edit', label: 'Edit Name, Description, Icon', hint: 'Update public profile fields used in the Agent Card' },
117
+ { value: 'back', role: 'section', label: 'Navigation' },
118
+ { value: 'back', label: 'Back', hint: 'Return to Identity Hub menu', role: 'utility' },
119
+ ]}
120
+ hintLayout="inline"
121
+ onSubmit={choice => {
122
+ if (choice === 'edit') return onEditProfile()
123
+ return onBack()
124
+ }}
125
+ onCancel={onBack}
126
+ />
127
+ </Box>
128
+ </Surface>
129
+ )
130
+ }
131
+
132
+ const PrivateRows: React.FC<{ identity?: EthagentIdentity; ready: boolean }> = ({ identity, ready }) => (
133
+ <Box flexDirection="column" marginTop={1}>
134
+ <Text>
135
+ <Text color={theme.dim}>{'Private'.padEnd(13)}</Text>
136
+ <Text color={ready ? theme.text : theme.dim}>{ready ? 'Local Files Ready' : 'Missing Local Working Files'}</Text>
137
+ </Text>
138
+ <Text>
139
+ <Text color={theme.dim}>{'Snapshot'.padEnd(13)}</Text>
140
+ <Text color={identity?.backup?.cid ? theme.text : theme.dim}>{identity?.backup?.cid ? shortCid(identity.backup.cid) : 'Not Saved Yet'}</Text>
141
+ </Text>
142
+ <Text>
143
+ <Text color={theme.dim}>{'Skills'.padEnd(13)}</Text>
144
+ <Text color={identity?.publicSkills?.cid ? theme.text : theme.dim}>{identity?.publicSkills?.cid ? shortCid(identity.publicSkills.cid) : 'Not Saved'}</Text>
145
+ </Text>
146
+ </Box>
147
+ )
148
+
149
+ const PublicProfileRows: React.FC<{ identity?: EthagentIdentity }> = ({ identity }) => (
150
+ <Box flexDirection="column" marginTop={1}>
151
+ <Text>
152
+ <Text color={theme.dim}>{publicProfileLabel('Agent Card')}</Text>
153
+ <Text color={identity?.publicSkills?.agentCardCid ? theme.text : theme.dim}>{identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'Not Saved'}</Text>
154
+ </Text>
155
+ <Text>
156
+ <Text color={theme.dim}>{publicProfileLabel('Agent Icon')}</Text>
157
+ <Text color={readIdentityStateString(identity?.state, 'imageUrl') ? theme.text : theme.dim}>{readIdentityStateString(identity?.state, 'imageUrl') ? 'Attached' : 'Not Attached'}</Text>
158
+ </Text>
159
+ </Box>
160
+ )
161
+
162
+ function publicProfileLabel(label: string): string {
163
+ return label.padEnd(16)
164
+ }
165
+
166
+ function privateSubtitle(ready: boolean): string {
167
+ return ready
168
+ ? 'Edit local continuity files. Public skills are saved with the same snapshot.'
169
+ : 'Use "Refetch Latest Snapshot" from the Identity Hub menu to recover files.'
170
+ }
@@ -0,0 +1,28 @@
1
+ import React from 'react'
2
+ import { PinataJwtInput } from '../../components/PinataJwtInput.js'
3
+ import type { Step } from '../../identityHubReducer.js'
4
+
5
+ interface RebackupStorageScreenProps {
6
+ step: Extract<Step, { kind: 'rebackup-storage' | 'public-profile-storage' }>
7
+ footer: React.ReactNode
8
+ title?: string
9
+ subtitle?: string
10
+ onSubmit: (input: string) => void
11
+ onCancel: () => void
12
+ }
13
+
14
+ export const RebackupStorageScreen: React.FC<RebackupStorageScreenProps> = ({ step, footer, title, subtitle, onSubmit, onCancel }) => {
15
+ const publicOnly = step.kind === 'public-profile-storage'
16
+ return (
17
+ <PinataJwtInput
18
+ inputKey="rebackup-storage"
19
+ title={title}
20
+ subtitle={step.error ?? subtitle ?? (publicOnly
21
+ ? 'Save a Pinata JWT so ethagent can pin the public profile to IPFS.'
22
+ : 'Save a Pinata JWT so ethagent can pin encrypted state to IPFS.')}
23
+ footer={footer}
24
+ onSubmit={onSubmit}
25
+ onCancel={onCancel}
26
+ />
27
+ )
28
+ }
@@ -1,35 +1,36 @@
1
1
  import React from 'react'
2
2
  import { Box, Text } from 'ink'
3
- import { Surface } from '../../../ui/Surface.js'
4
- import { Select } from '../../../ui/Select.js'
5
- import { theme } from '../../../ui/theme.js'
6
- import { localChangeStatusView, type LocalChangeStatusView } from '../identityHubModel.js'
3
+ import { Surface } from '../../../../ui/Surface.js'
4
+ import { Select } from '../../../../ui/Select.js'
5
+ import { theme } from '../../../../ui/theme.js'
6
+ import { localChangeStatusView, type LocalChangeStatusView } from '../../model/continuity.js'
7
7
 
8
- import type { ContinuityWorkingTreeStatus } from '../../continuity/storage.js'
8
+ import type { ContinuityWorkingTreeStatus } from '../../../continuity/storage.js'
9
9
 
10
- export type RecoveryConfirmMode = 'publish' | 'refetch'
10
+ type RecoveryConfirmMode = 'publish' | 'refetch'
11
11
 
12
- type RecoveryConfirmScreenProps = {
12
+ interface RecoveryConfirmScreenProps {
13
13
  mode: RecoveryConfirmMode
14
14
  workingStatus?: ContinuityWorkingTreeStatus | null
15
+ pendingPublish?: boolean
15
16
  footer: React.ReactNode
16
17
  onConfirm: () => void
17
18
  onBack: () => void
18
19
  }
19
20
 
20
- export const RecoveryConfirmScreen: React.FC<RecoveryConfirmScreenProps> = ({ mode, workingStatus, footer, onConfirm, onBack }) => {
21
+ export const RecoveryConfirmScreen: React.FC<RecoveryConfirmScreenProps> = ({ mode, workingStatus, pendingPublish, footer, onConfirm, onBack }) => {
21
22
  const isPublish = mode === 'publish'
22
23
  const title = isPublish ? 'Save Snapshot?' : 'Refetch Latest From Chain?'
23
24
  const subtitle = isPublish
24
- ? 'Saves any local edits to SOUL.md, MEMORY.md, skills.json, and public profile.'
25
- : 'This overwrites local files with the on-chain version.'
25
+ ? 'Saves SOUL.md, MEMORY.md, skills.json, and profile changes.'
26
+ : 'This overwrites local files with the onchain version.'
26
27
 
27
- const headlineColor = isPublish ? theme.accentPeach : theme.accentMint
28
+ const headlineColor = theme.accentPeriwinkle
28
29
  const headline = isPublish
29
- ? 'Saving updates the on-chain pointer for this agent.'
30
- : 'Refetching replaces local SOUL.md, MEMORY.md, and skills.json with what is on chain.'
30
+ ? 'Saving updates the onchain pointer for this agent.'
31
+ : 'Refetching replaces SOUL.md, MEMORY.md, and skills.json with what is onchain.'
31
32
  const detail = isPublish
32
- ? 'Any local edits to SOUL.md, MEMORY.md, skills.json, and public profile become the saved state. The previous snapshot pointer is overwritten.'
33
+ ? 'Your local continuity files and profile edits become the saved state. The previous snapshot pointer is overwritten.'
33
34
  : 'Unsaved local edits will be lost. Use this when local files are missing or out of sync with the latest saved snapshot.'
34
35
 
35
36
  const localChangeStatus = localChangeStatusView(workingStatus)
@@ -44,22 +45,28 @@ export const RecoveryConfirmScreen: React.FC<RecoveryConfirmScreenProps> = ({ mo
44
45
  <SaveSnapshotStatusLine status={localChangeStatus} />
45
46
  </Box>
46
47
  )}
48
+ {!isPublish && pendingPublish ? (
49
+ <Box marginTop={1} flexDirection="column">
50
+ <Text color={theme.accentError} bold>Local snapshot is ahead of chain.</Text>
51
+ <Text color={theme.textSubtle}>Local edits have not yet been rotated to the onchain pointer. Refetching discards them and reverts to the last published snapshot.</Text>
52
+ </Box>
53
+ ) : null}
47
54
  {!isPublish && (
48
55
  <Box marginTop={1}>
49
- <Text color={theme.accentPeach}>Overwrite your local files?</Text>
56
+ <Text color={theme.accentPeriwinkle}>Overwrite your local files?</Text>
50
57
  </Box>
51
58
  )}
52
59
  </Box>
53
60
  <Box marginTop={1}>
54
61
  <Select<'confirm' | 'back'>
55
62
  options={[
56
- { value: 'confirm', role: 'section', prefix: '--', label: isPublish ? 'Save' : 'Refetch' },
63
+ { value: 'confirm', role: 'section', label: isPublish ? 'Save' : 'Refetch' },
57
64
  {
58
65
  value: 'confirm',
59
66
  label: isPublish ? 'Yes, Save Snapshot Now' : 'Yes, Refetch From Chain',
60
67
  hint: isPublish ? 'Sign and save the encrypted snapshot' : 'Wallet decrypts and overwrites local files',
61
68
  },
62
- { value: 'back', role: 'section', prefix: '--', label: 'Cancel' },
69
+ { value: 'back', role: 'section', label: 'Cancel' },
63
70
  {
64
71
  value: 'back',
65
72
  label: 'No, Go Back',
@@ -84,12 +91,14 @@ const SaveSnapshotStatusLine: React.FC<{ status: LocalChangeStatusView }> = ({ s
84
91
  return (
85
92
  <Text>
86
93
  <Text color={theme.textSubtle}>Local changes detected: </Text>
87
- <Text color="#e87070" bold>{status.files.length > 0 ? status.files.join(', ') : 'local files differ from saved snapshot'}</Text>
94
+ <Text color={theme.accentError} bold>{status.files.length > 0 ? status.files.join(', ') : 'local files differ from saved snapshot'}</Text>
88
95
  </Text>
89
96
  )
90
97
  }
91
98
 
92
- const color = status.tone === 'ok' ? theme.accentMint : status.tone === 'warn' ? theme.accentPeach : theme.dim
99
+ if (!status.detail) return null
100
+
101
+ const color = status.tone === 'ok' || status.tone === 'warn' ? theme.accentPeriwinkle : theme.dim
93
102
  const label = status.detail === 'None detected' ? 'No local changes detected.' : status.detail
94
103
  return <Text color={color}>{label}</Text>
95
104
  }
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { Surface } from '../../../../ui/Surface.js'
4
+ import { Select } from '../../../../ui/Select.js'
5
+ import { theme } from '../../../../ui/theme.js'
6
+ import type { ContinuityWorkingTreeStatus } from '../../../continuity/storage.js'
7
+ import { changedContinuitySnapshotFiles } from '../../model/continuity.js'
8
+
9
+ type SavePromptAction = 'save-now' | 'later'
10
+
11
+ interface SavePromptScreenProps {
12
+ workingStatus?: ContinuityWorkingTreeStatus | null
13
+ footer: React.ReactNode
14
+ onSelect: (action: SavePromptAction) => void
15
+ onCancel: () => void
16
+ }
17
+
18
+ export const SavePromptScreen: React.FC<SavePromptScreenProps> = ({ workingStatus, footer, onSelect, onCancel }) => {
19
+ const files = changedContinuitySnapshotFiles(workingStatus)
20
+
21
+ return (
22
+ <Surface
23
+ title="Save your identity changes?"
24
+ subtitle="Unsaved local edits since the last snapshot."
25
+ footer={footer}
26
+ tone="primary"
27
+ >
28
+ <Box flexDirection="column">
29
+ {files.length > 0 ? (
30
+ <Text color={theme.textSubtle}>Changed: <Text color={theme.accentError} bold>{files.join(', ')}</Text></Text>
31
+ ) : (
32
+ <Text color={theme.accentPeriwinkle}>Local files differ from the saved snapshot.</Text>
33
+ )}
34
+ </Box>
35
+ <Box marginTop={1}>
36
+ <Select<SavePromptAction>
37
+ options={[
38
+ { value: 'save-now', role: 'section', label: 'Save' },
39
+ { value: 'save-now', label: 'Save now', hint: 'Sign once and save the encrypted snapshot' },
40
+ { value: 'later', label: 'Not now', hint: 'Ask again on the next ethagent launch', role: 'utility' },
41
+ ]}
42
+ hintLayout="inline"
43
+ onSubmit={onSelect}
44
+ onCancel={onCancel}
45
+ />
46
+ </Box>
47
+ </Surface>
48
+ )
49
+ }
@@ -1,34 +1,18 @@
1
1
  import React from 'react'
2
2
  import { Box, Text } from 'ink'
3
- import { Surface } from '../../../ui/Surface.js'
4
- import { Select } from '../../../ui/Select.js'
5
- import { TextInput } from '../../../ui/TextInput.js'
6
- import { theme } from '../../../ui/theme.js'
7
- import { extractPinataJwt } from '../../storage/ipfs.js'
8
- import { normalizeErc8004RegistryConfig } from '../../registry/erc8004.js'
9
- import { networkLabel } from '../identityHubModel.js'
10
- import type { Step } from '../identityHubReducer.js'
11
- import { createStepNumber, CREATE_STEP_LABELS } from '../identityHubReducer.js'
12
- import { WalletApprovalScreen } from './WalletApprovalScreen.js'
13
- import { BusyScreen } from './BusyScreen.js'
14
- import type { BrowserWalletReady } from '../../wallet/browserWallet.js'
15
-
16
- const PINATA_API_KEYS_URL = 'https://app.pinata.cloud/developers/api-keys'
17
-
18
- type StepIndicatorProps = {
19
- steps: string[]
20
- current: number
21
- }
22
-
23
- const StepIndicator: React.FC<StepIndicatorProps> = ({ steps, current }) => {
24
- const parts = steps.map((step, index) => {
25
- const n = index + 1
26
- const active = n === current
27
- const done = n < current
28
- return `${done ? 'done' : n}. ${step}${active ? ' <' : ''}`
29
- })
30
- return <Text color={theme.dim}>{parts.join(' · ')}</Text>
31
- }
3
+ import { Surface } from '../../../../ui/Surface.js'
4
+ import { Select } from '../../../../ui/Select.js'
5
+ import { TextInput } from '../../../../ui/TextInput.js'
6
+ import { theme } from '../../../../ui/theme.js'
7
+ import { normalizeErc8004RegistryConfig } from '../../../registry/erc8004.js'
8
+ import { networkLabel } from '../../model/network.js'
9
+ import type { Step } from '../../identityHubReducer.js'
10
+ import { createStepNumber, CREATE_STEP_LABELS } from '../../identityHubReducer.js'
11
+ import { WalletApprovalScreen } from '../../components/WalletApprovalScreen.js'
12
+ import { BusyScreen } from '../../components/BusyScreen.js'
13
+ import { FlowTimeline } from '../../components/FlowTimeline.js'
14
+ import { PinataJwtInput } from '../../components/PinataJwtInput.js'
15
+ import type { BrowserWalletReady } from '../../../wallet/browserWallet.js'
32
16
 
33
17
  type CreateFlowProps = {
34
18
  step: Extract<Step, {
@@ -36,6 +20,7 @@ type CreateFlowProps = {
36
20
  | 'replace-confirm'
37
21
  | 'create-name'
38
22
  | 'create-description'
23
+ | 'create-custody'
39
24
  | 'create-preflight'
40
25
  | 'create-registry'
41
26
  | 'create-signing'
@@ -45,9 +30,9 @@ type CreateFlowProps = {
45
30
  onSetStep: (step: Step) => void
46
31
  onNameSubmit: (name: string) => void
47
32
  onDescriptionSubmit: (name: string, description: string) => void
33
+ onCustodySubmit: (custodyMode: 'simple' | 'advanced') => void
48
34
  onRegistrySubmit: (value: string) => void
49
35
  onStorageSubmit: (input: string) => void
50
- onStorageError: (error: string) => void
51
36
  onBack: () => void
52
37
  onMenu: () => void
53
38
  }
@@ -58,6 +43,7 @@ export const CreateFlow: React.FC<CreateFlowProps> = ({
58
43
  onSetStep,
59
44
  onNameSubmit,
60
45
  onDescriptionSubmit,
46
+ onCustodySubmit,
61
47
  onRegistrySubmit,
62
48
  onStorageSubmit,
63
49
  onBack,
@@ -65,7 +51,7 @@ export const CreateFlow: React.FC<CreateFlowProps> = ({
65
51
  }) => {
66
52
  const stepNum = createStepNumber(step)
67
53
  const indicator = stepNum > 0
68
- ? <StepIndicator steps={CREATE_STEP_LABELS} current={stepNum} />
54
+ ? <FlowTimeline steps={[...CREATE_STEP_LABELS]} current={stepNum} />
69
55
  : null
70
56
 
71
57
  if (step.kind === 'replace-confirm') {
@@ -81,9 +67,9 @@ export const CreateFlow: React.FC<CreateFlowProps> = ({
81
67
  </Box>
82
68
  <Select<'replace' | 'back'>
83
69
  options={[
84
- { value: 'back', role: 'section', prefix: '--', label: 'Current Identity' },
70
+ { value: 'back', role: 'section', label: 'Current Identity' },
85
71
  { value: 'back', label: 'Keep Current Agent', hint: 'Return without minting anything', role: 'utility' },
86
- { value: 'replace', role: 'section', prefix: '--', label: 'New Identity' },
72
+ { value: 'replace', role: 'section', label: 'New Identity' },
87
73
  { value: 'replace', label: 'Mint and Use New Agent', hint: 'Create separate token and make it active' },
88
74
  ]}
89
75
  hintLayout="inline"
@@ -100,9 +86,10 @@ export const CreateFlow: React.FC<CreateFlowProps> = ({
100
86
  if (step.kind === 'create-name') {
101
87
  return (
102
88
  <Surface title="Name Your Agent" subtitle={indicator} footer="enter continues · esc back">
103
- {step.error ? <Text color="#e87070">{step.error}</Text> : null}
89
+ {step.error ? <Text color={theme.accentError}>{step.error}</Text> : null}
104
90
  <TextInput
105
91
  key="agent-name"
92
+ initialValue={step.name ?? ''}
106
93
  placeholder="agent name"
107
94
  validate={value => value.trim().length >= 2 ? null : 'name must be at least 2 characters'}
108
95
  onSubmit={name => onNameSubmit(name.trim())}
@@ -114,11 +101,14 @@ export const CreateFlow: React.FC<CreateFlowProps> = ({
114
101
 
115
102
  if (step.kind === 'create-description') {
116
103
  return (
117
- <Surface title="Describe Your Agent" subtitle={indicator} footer="enter continues · esc back">
104
+ <Surface title="Describe Your Agent" subtitle={indicator} footer="enter next · shift+enter newline · esc back">
118
105
  <Text color={theme.dim}>Optional. One short sentence is enough.</Text>
119
106
  <TextInput
120
107
  key="agent-description"
108
+ initialValue={step.description ?? ''}
121
109
  placeholder="description"
110
+ allowEmpty
111
+ multiline
122
112
  onSubmit={description => onDescriptionSubmit(step.name, description.trim())}
123
113
  onCancel={onBack}
124
114
  />
@@ -126,6 +116,28 @@ export const CreateFlow: React.FC<CreateFlowProps> = ({
126
116
  )
127
117
  }
128
118
 
119
+ if (step.kind === 'create-custody') {
120
+ return (
121
+ <Surface title="Pick Custody Mode" subtitle={indicator} footer="enter continues · esc back">
122
+ <Box flexDirection="column" marginBottom={1}>
123
+ <Text color={theme.dim}>Custody decides who controls the ERC-8004 token and who can rotate the URI pointer.</Text>
124
+ <Text color={theme.dim}>You can switch later from Identity Hub.</Text>
125
+ </Box>
126
+ <Select<'simple' | 'advanced'>
127
+ options={[
128
+ { value: 'simple', role: 'section', label: 'Simple (Recommended)' },
129
+ { value: 'simple', label: 'Simple', hint: 'One wallet owns the token, signs every save, and rotates the URI directly' },
130
+ { value: 'advanced', role: 'section', label: 'Advanced' },
131
+ { value: 'advanced', label: 'Advanced', hint: 'Operator delegation vault holds the token; owner wallet controls vault, operator wallets get URI-rotation permission' },
132
+ ]}
133
+ hintLayout="inline"
134
+ onSubmit={onCustodySubmit}
135
+ onCancel={onBack}
136
+ />
137
+ </Surface>
138
+ )
139
+ }
140
+
129
141
  if (step.kind === 'create-preflight') {
130
142
  return (
131
143
  <BusyScreen
@@ -164,43 +176,30 @@ export const CreateFlow: React.FC<CreateFlowProps> = ({
164
176
  }
165
177
 
166
178
  if (step.kind === 'create-signing') {
179
+ const isAdvanced = step.custodyMode === 'advanced'
167
180
  return (
168
181
  <WalletApprovalScreen
169
- title="Approve in Wallet"
170
- subtitle="One browser flow signs, saves the IPFS backup, and submits the token transaction."
182
+ title={isAdvanced ? 'Connect Owner Wallet' : 'Sign in Wallet'}
183
+ subtitle={
184
+ isAdvanced
185
+ ? 'This wallet will own the agent token and control the delegation vault. Operator wallets are configured after minting.'
186
+ : 'One browser flow signs, saves the IPFS backup, and submits the token transaction.'
187
+ }
171
188
  walletSession={walletSession}
172
- label="waiting for wallet approval..."
189
+ label={isAdvanced ? 'waiting for owner wallet...' : 'waiting for wallet signature...'}
173
190
  onCancel={onBack}
174
191
  />
175
192
  )
176
193
  }
177
194
 
178
195
  return (
179
- <Surface
196
+ <PinataJwtInput
197
+ inputKey="create-storage"
180
198
  title="Connect IPFS Storage"
181
- subtitle={step.error ?? 'Save a Pinata JWT so ethagent can pin encrypted state to IPFS.'}
199
+ subtitle={step.error ?? undefined}
182
200
  footer="enter continues · esc back"
183
- >
184
- <Text>
185
- <Text color={theme.dim}>Paste your Pinata JWT. Get one at </Text>
186
- <Text color={theme.accentPrimary} underline>{PINATA_API_KEYS_URL}</Text>
187
- </Text>
188
- <Text color={theme.dim}>Saved encrypted on this device · used only for IPFS pinning</Text>
189
- <TextInput
190
- key="create-storage"
191
- isSecret
192
- placeholder="Pinata JWT"
193
- validate={v => {
194
- try {
195
- extractPinataJwt(v)
196
- return null
197
- } catch (err: unknown) {
198
- return (err as Error).message
199
- }
200
- }}
201
- onSubmit={onStorageSubmit}
202
- onCancel={onBack}
203
- />
204
- </Surface>
201
+ onSubmit={onStorageSubmit}
202
+ onCancel={onBack}
203
+ />
205
204
  )
206
205
  }