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
@@ -0,0 +1,86 @@
1
+ import type { EthagentConfig } from '../storage/config.js'
2
+ import type { AgentReconciliation } from '../identity/manager/shared/reconciliation/index.js'
3
+
4
+ const DEMO_ENV = 'ETHAGENT_DEMO'
5
+
6
+ export function isDemoMode(): boolean {
7
+ return process.env[DEMO_ENV] === '1'
8
+ }
9
+
10
+ export function enableDemoMode(): void {
11
+ process.env[DEMO_ENV] = '1'
12
+ }
13
+
14
+ const DEMO_OWNER = '0xA1E977e700bF82019beb381F1582575303A389CE' as const
15
+ const DEMO_AGENT_ADDRESS = '0x6bdC52c8e262c6D139e618260400614c2Bfe51d7' as const
16
+ const DEMO_REGISTRY = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432' as const
17
+ const DEMO_AGENT_ID = '42'
18
+ const DEMO_AGENT_URI = 'ipfs://bafkreidemodemodemodemodemodemodemodemodemodemodemo'
19
+ const DEMO_BACKUP_CID = 'bafkreidemodemodemodemodemodemodemodemodemodemodemo'
20
+ const DEMO_AGENT_CARD_CID = 'bafkreidemoagentcarddemoagentcarddemoagentcarddemoag'
21
+ const DEMO_CREATED_AT = '2026-01-01T00:00:00.000Z'
22
+
23
+ export function synthDemoConfig(): EthagentConfig {
24
+ return {
25
+ version: 2,
26
+ firstSeenAt: DEMO_CREATED_AT,
27
+ selectedNetwork: 'base',
28
+ erc8004: {
29
+ chainId: 8453,
30
+ rpcUrl: 'https://mainnet.base.org',
31
+ identityRegistryAddress: DEMO_REGISTRY,
32
+ },
33
+ identity: {
34
+ source: 'erc8004',
35
+ address: DEMO_AGENT_ADDRESS,
36
+ ownerAddress: DEMO_OWNER,
37
+ connectedWallet: DEMO_OWNER,
38
+ createdAt: DEMO_CREATED_AT,
39
+ chainId: 8453,
40
+ rpcUrl: 'https://mainnet.base.org',
41
+ identityRegistryAddress: DEMO_REGISTRY,
42
+ agentId: DEMO_AGENT_ID,
43
+ agentUri: DEMO_AGENT_URI,
44
+ metadataCid: DEMO_AGENT_URI.replace('ipfs://', ''),
45
+ state: { name: 'demo agent', custodyMode: 'simple' },
46
+ backup: {
47
+ cid: DEMO_BACKUP_CID,
48
+ createdAt: DEMO_CREATED_AT,
49
+ envelopeVersion: 'ethagent-continuity-snapshot-v1',
50
+ ipfsApiUrl: 'https://uploads.pinata.cloud/v3/files',
51
+ status: 'pinned',
52
+ ownerAddress: DEMO_OWNER,
53
+ chainId: 8453,
54
+ rpcUrl: 'https://mainnet.base.org',
55
+ identityRegistryAddress: DEMO_REGISTRY,
56
+ agentId: DEMO_AGENT_ID,
57
+ agentUri: DEMO_AGENT_URI,
58
+ metadataCid: DEMO_AGENT_URI.replace('ipfs://', ''),
59
+ },
60
+ agentCard: {
61
+ cid: DEMO_AGENT_CARD_CID,
62
+ updatedAt: DEMO_CREATED_AT,
63
+ status: 'pinned',
64
+ },
65
+ },
66
+ }
67
+ }
68
+
69
+ export function synthDemoReconciliation(): AgentReconciliation {
70
+ return {
71
+ token: 'linked',
72
+ tokenAgentId: DEMO_AGENT_ID,
73
+ onChainOwner: DEMO_OWNER,
74
+ custody: 'simple',
75
+ agentUri: 'in-sync',
76
+ vault: 'unset',
77
+ workingTree: 'clean',
78
+ rpc: 'reachable',
79
+ driftCount: 0,
80
+ lastCheckedAt: new Date().toISOString(),
81
+ }
82
+ }
83
+
84
+ export function demoNotice(label: string): string {
85
+ return `demo mode: would have ${label}`
86
+ }
@@ -0,0 +1,45 @@
1
+ import path from 'node:path'
2
+
3
+ // Shared helpers for the Claude Code hook entrypoints (--sync-on-edit, --memory-guard).
4
+ // A hook receives the harness payload as JSON on stdin and may answer with JSON on stdout.
5
+
6
+ export async function readHookPayload(): Promise<Record<string, unknown> | null> {
7
+ if (process.stdin.isTTY) return null
8
+ let raw = ''
9
+ try {
10
+ process.stdin.setEncoding('utf8')
11
+ for await (const chunk of process.stdin) raw += chunk
12
+ } catch {
13
+ return null
14
+ }
15
+ raw = raw.trim()
16
+ if (!raw) return null
17
+ try {
18
+ const parsed = JSON.parse(raw) as unknown
19
+ return parsed && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : null
20
+ } catch {
21
+ return null
22
+ }
23
+ }
24
+
25
+ export function hookFilePath(payload: Record<string, unknown> | null): string | null {
26
+ const filePath = (payload?.tool_input as { file_path?: unknown } | undefined)?.file_path
27
+ return typeof filePath === 'string' && filePath.length > 0 ? filePath : null
28
+ }
29
+
30
+ export function samePath(a: string, b: string): boolean {
31
+ const na = path.resolve(a)
32
+ const nb = path.resolve(b)
33
+ return process.platform === 'win32' ? na.toLowerCase() === nb.toLowerCase() : na === nb
34
+ }
35
+
36
+ // True when `file` is `dir` itself or sits anywhere beneath it. Compares on a
37
+ // separator boundary so `.../memory` does not match a sibling like `.../memory-notes`.
38
+ export function isWithinDir(dir: string, file: string): boolean {
39
+ const fold = (p: string): string => (process.platform === 'win32' ? p.toLowerCase() : p)
40
+ const nd = fold(path.resolve(dir))
41
+ const nf = fold(path.resolve(file))
42
+ if (nd === nf) return true
43
+ const prefix = nd.endsWith(path.sep) ? nd : nd + path.sep
44
+ return nf.startsWith(prefix)
45
+ }
package/src/cli/main.tsx CHANGED
@@ -5,16 +5,18 @@ import fs from 'node:fs'
5
5
  import path from 'node:path'
6
6
  import { fileURLToPath } from 'node:url'
7
7
  import { theme } from '../ui/theme.js'
8
- import { FirstRun } from '../app/FirstRun.js'
9
- import { ChatScreen } from '../chat/ChatScreen.js'
8
+ import { Spinner } from '../ui/Spinner.js'
10
9
  import { KeybindingProvider } from '../app/keybindings/KeybindingProvider.js'
11
- import { AppInputProvider, useAppInput } from '../app/input/AppInputProvider.js'
12
- import { loadConfig, type EthagentConfig } from '../storage/config.js'
10
+ import { AppInputProvider } from '../app/input/AppInputProvider.js'
11
+ import { IdentityManager } from '../identity/manager/IdentityManager.js'
12
+ import type { IdentityManagerResult } from '../identity/manager/IdentityManager.js'
13
+ import { loadConfig, saveConfig, type EthagentConfig } from '../storage/config.js'
14
+ import { runSync, runSyncList, runSyncOnEdit } from './sync.js'
15
+ import { runMemoryGuard } from './memoryGuard.js'
16
+ import { runSessionStart } from './sessionStart.js'
17
+ import { runStatus } from './status.js'
13
18
  import { runResetCommand } from './reset.js'
14
- import { runPreviewCommand } from './preview.js'
15
- import { checkForUpdates } from './updateNotice.js'
16
- import { Spinner } from '../ui/Spinner.js'
17
- import { TITLE_STATIC, clearTerminalTitle, setTerminalTitle } from '../ui/terminalTitle.js'
19
+ import { enableDemoMode, synthDemoConfig } from './demo.js'
18
20
 
19
21
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
20
22
 
@@ -33,173 +35,142 @@ function printHelp(): void {
33
35
  'ethagent: privacy-first AI agent with a portable Ethereum identity',
34
36
  '',
35
37
  'usage:',
36
- ' ethagent start the agent (first run triggers setup)',
37
- ' ethagent preview show the brand splash and exit',
38
- ' ethagent reset factory reset local data (local LLMs kept)',
39
- ' ethagent reset --yes run reset without the confirm prompt',
40
- ' ethagent --version print version',
41
- ' ethagent --help print this help',
38
+ ' ethagent manage identity',
39
+ ' ethagent reset delete local identity, continuity, and secrets',
40
+ ' ethagent --sync sync soul, memory, and skills to every harness',
41
+ ' ethagent --sync-list list sync adapters and which ones detect here',
42
+ ' ethagent --demo walk identity with synthetic data',
43
+ ' ethagent --status print one-line identity summary',
44
+ ' ethagent --version print version',
45
+ ' ethagent --help print this help',
42
46
  '',
43
- 'inside the agent, type /help for slash commands.',
47
+ 'plugin hooks (invoked automatically, not meant to be run by hand):',
48
+ ' ethagent --session-start sync, then tell the agent where to record memory',
49
+ ' ethagent --memory-guard keep agent memory in the portable markers, not local notes',
44
50
  ]
45
51
  for (const line of lines) process.stdout.write(line + '\n')
46
52
  }
47
53
 
48
- type AppPhase =
54
+ type Phase =
49
55
  | { kind: 'loading' }
50
- | { kind: 'setup' }
51
- | { kind: 'ready'; config: EthagentConfig }
52
- | { kind: 'cancelled' }
56
+ | { kind: 'ready'; config: EthagentConfig | null }
53
57
  | { kind: 'error'; message: string }
54
58
 
55
- const MIN_STARTUP_SPINNER_MS = 480
56
-
57
- function delay(ms: number): Promise<void> {
58
- return new Promise(resolve => setTimeout(resolve, ms))
59
+ type RootProps = {
60
+ setExit: (n: number) => void
61
+ initialConfig: EthagentConfig | null | undefined
59
62
  }
60
63
 
61
- const AppRoot: React.FC<{ setExitCode: (code: number) => void; currentVersion: string }> = ({ setExitCode, currentVersion }) => {
62
- const [phase, setPhase] = useState<AppPhase>({ kind: 'loading' })
63
- const [updateNotice, setUpdateNotice] = useState<string | null>(null)
64
+ const Root: React.FC<RootProps> = ({ setExit, initialConfig }) => {
65
+ const [phase, setPhase] = useState<Phase>(
66
+ initialConfig !== undefined
67
+ ? { kind: 'ready', config: initialConfig }
68
+ : { kind: 'loading' },
69
+ )
64
70
  const { exit } = useApp()
65
71
 
66
72
  useEffect(() => {
67
73
  if (phase.kind !== 'loading') return
68
74
  let cancelled = false
69
- Promise.all([loadConfig(), delay(MIN_STARTUP_SPINNER_MS)])
70
- .then(config => {
71
- if (cancelled) return
72
- setPhase(config[0] ? { kind: 'ready', config: config[0] } : { kind: 'setup' })
73
- })
74
- .catch((err: unknown) => {
75
- if (cancelled) return
76
- setPhase({ kind: 'error', message: (err as Error).message })
77
- })
75
+ loadConfig()
76
+ .then(c => { if (!cancelled) setPhase({ kind: 'ready', config: c }) })
77
+ .catch(err => { if (!cancelled) setPhase({ kind: 'error', message: (err as Error).message }) })
78
78
  return () => { cancelled = true }
79
- }, [phase])
80
-
81
- useEffect(() => {
82
- let cancelled = false
83
- void checkForUpdates(currentVersion)
84
- .then(notice => {
85
- if (!cancelled) setUpdateNotice(notice)
86
- })
87
- .catch(() => {})
88
- return () => { cancelled = true }
89
- }, [currentVersion])
90
-
91
- useEffect(() => {
92
- setTerminalTitle(TITLE_STATIC)
93
- }, [])
94
-
95
- useEffect(() => {
96
- if (phase.kind === 'cancelled') {
97
- setExitCode(1)
98
- const t = setTimeout(() => exit(), 10)
99
- return () => clearTimeout(t)
100
- }
101
- if (phase.kind === 'error') {
102
- setExitCode(1)
103
- const t = setTimeout(() => exit(), 10)
104
- return () => clearTimeout(t)
105
- }
106
- return undefined
107
- }, [phase, exit, setExitCode])
108
-
109
- useAppInput((input, key) => {
110
- if (phase.kind === 'ready') return
111
- if (key.ctrl && (input === 'c' || input === 'd')) {
112
- if (phase.kind === 'setup') {
113
- setPhase({ kind: 'cancelled' })
114
- } else {
115
- exit()
116
- }
117
- }
118
- })
79
+ }, [phase.kind])
119
80
 
120
81
  if (phase.kind === 'loading') {
121
- return (
122
- <Box padding={1}>
123
- <Spinner label="starting ethagent..." showElapsed={false} />
124
- </Box>
125
- )
82
+ return <Box padding={1}><Spinner label="loading identity..." showElapsed={false} /></Box>
126
83
  }
127
- if (phase.kind === 'setup') {
128
- return (
129
- <FirstRun
130
- onComplete={config => setPhase({ kind: 'ready', config })}
131
- onCancel={() => setPhase({ kind: 'cancelled' })}
132
- />
133
- )
84
+ if (phase.kind === 'error') {
85
+ return <Box padding={1}><Text color={theme.accentError}>Error: {phase.message}</Text></Box>
134
86
  }
135
- if (phase.kind === 'cancelled') {
136
- return (
137
- <Box padding={1}>
138
- <Text color={theme.dim}>Setup cancelled.</Text>
139
- </Box>
140
- )
87
+
88
+ const mode = phase.config?.identity ? 'manage' : 'first-run'
89
+ const handleComplete = (result: IdentityManagerResult): void => {
90
+ setExit(result.kind === 'cancel' ? 1 : 0)
91
+ setTimeout(() => exit(), 10)
141
92
  }
142
- if (phase.kind === 'error') {
143
- return (
144
- <Box padding={1}>
145
- <Text color={theme.accentError}>Error: {phase.message}</Text>
146
- </Box>
147
- )
93
+ const handleConfigChange = (next: EthagentConfig): void => {
94
+ setPhase({ kind: 'ready', config: next })
95
+ void saveConfig(next)
148
96
  }
97
+
149
98
  return (
150
- <ChatScreen
151
- config={phase.config}
152
- onReplaceConfig={next => setPhase({ kind: 'ready', config: next })}
153
- updateNotice={updateNotice}
99
+ <IdentityManager
100
+ mode={mode}
101
+ {...(phase.config ? { config: phase.config } : {})}
102
+ onConfigChange={handleConfigChange}
103
+ onComplete={handleComplete}
154
104
  />
155
105
  )
156
106
  }
157
107
 
158
- async function runDefault(currentVersion: string): Promise<number> {
108
+ async function renderHub(initialConfig: EthagentConfig | null | undefined): Promise<number> {
159
109
  let exitCode = 0
160
- setTerminalTitle(TITLE_STATIC)
161
- process.once('exit', clearTerminalTitle)
162
110
  const instance = render(
163
111
  <AppInputProvider>
164
112
  <KeybindingProvider>
165
- <AppRoot setExitCode={code => { exitCode = code }} currentVersion={currentVersion} />
113
+ <Root setExit={n => { exitCode = n }} initialConfig={initialConfig} />
166
114
  </KeybindingProvider>
167
115
  </AppInputProvider>,
168
- {
169
- exitOnCtrlC: false,
170
- },
116
+ { exitOnCtrlC: false },
171
117
  )
118
+ const guard = (reason: unknown): void => {
119
+ const message = reason instanceof Error ? reason.message : String(reason)
120
+ process.stderr.write(`background error: ${message}\n`)
121
+ }
122
+ const onUnhandledRejection = (reason: unknown): void => guard(reason)
123
+ const onUncaughtException = (err: unknown): void => guard(err)
124
+ process.on('unhandledRejection', onUnhandledRejection)
125
+ process.on('uncaughtException', onUncaughtException)
172
126
  try {
173
127
  await instance.waitUntilExit()
174
128
  } catch {
175
129
  exitCode = 1
130
+ } finally {
131
+ process.removeListener('unhandledRejection', onUnhandledRejection)
132
+ process.removeListener('uncaughtException', onUncaughtException)
176
133
  }
177
134
  return exitCode
178
135
  }
179
136
 
180
137
  async function main(): Promise<number> {
181
138
  const argv = process.argv.slice(2)
182
- const [cmd, ...rest] = argv
139
+ const flags = new Set(argv)
140
+ const version = readVersion()
183
141
 
184
- if (!cmd) return runDefault(readVersion())
185
- if (cmd === '--version' || cmd === '-v') {
186
- process.stdout.write(`ethagent ${readVersion()}\n`)
142
+ if (flags.has('--version') || flags.has('-v')) {
143
+ process.stdout.write(`ethagent ${version}\n`)
187
144
  return 0
188
145
  }
189
- if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
146
+ if (flags.has('--help') || flags.has('-h') || argv[0] === 'help') {
190
147
  printHelp()
191
148
  return 0
192
149
  }
150
+ if (flags.has('--sync-on-edit')) return runSyncOnEdit()
151
+ if (flags.has('--session-start')) return runSessionStart()
152
+ if (flags.has('--memory-guard')) return runMemoryGuard()
153
+ if (flags.has('--sync-list')) return runSyncList()
154
+ if (flags.has('--sync')) return runSync()
155
+ if (flags.has('--status')) return runStatus(version)
156
+ if (flags.has('--demo')) {
157
+ enableDemoMode()
158
+ return renderHub(synthDemoConfig())
159
+ }
160
+ if (argv[0] === 'reset') return runResetCommand(argv.slice(1))
193
161
 
194
- switch (cmd) {
195
- case 'preview':
196
- return runPreviewCommand()
197
- case 'reset':
198
- return runResetCommand(rest)
199
- default:
200
- process.stderr.write(`unknown command: ${cmd}\nrun 'ethagent --help' for usage\n`)
201
- return 2
162
+ const unknown = argv.find(a => a.startsWith('-'))
163
+ if (unknown && !flags.has('--demo')) {
164
+ process.stderr.write(`unknown flag: ${unknown}\nrun 'ethagent --help' for usage\n`)
165
+ return 2
202
166
  }
167
+ const positional = argv[0]
168
+ if (positional) {
169
+ process.stderr.write(`unknown command: ${positional}\nrun 'ethagent --help' for usage\n`)
170
+ return 2
171
+ }
172
+
173
+ return renderHub(undefined)
203
174
  }
204
175
 
205
176
  main()
@@ -0,0 +1,49 @@
1
+ import { loadConfig } from '../storage/config.js'
2
+ import { hookFilePath, isWithinDir, readHookPayload } from './hookIo.js'
3
+ import { claudeCodeNativeMemoryDir } from './syncAdapters/claude-code.js'
4
+
5
+ // Shown to the model when it tries to write into the harness-native memory dir.
6
+ // The redirect points at a different file (~/.claude/CLAUDE.md), so the model's
7
+ // next attempt succeeds and there is no deny loop.
8
+ export const MEMORY_REDIRECT_REASON =
9
+ "ethagent manages this agent's portable memory. Don't write to the Claude Code native memory directory; " +
10
+ 'those files stay on this machine and never reach your onchain vault. Record durable facts by editing ' +
11
+ '~/.claude/CLAUDE.md instead: put user and project facts between the `<!-- ethagent:memory:start -->` and ' +
12
+ '`<!-- ethagent:memory:end -->` markers, and persona, voice, and standards between the ' +
13
+ '`<!-- ethagent:soul:start -->` and `<!-- ethagent:soul:end -->` markers. Those edits sync to your vault automatically.'
14
+
15
+ export function decideMemoryGuard(
16
+ filePath: string | null | undefined,
17
+ opts: { identityPresent: boolean },
18
+ ): { deny: boolean; reason?: string } {
19
+ if (!opts.identityPresent) return { deny: false }
20
+ if (!filePath) return { deny: false }
21
+ if (isWithinDir(claudeCodeNativeMemoryDir(), filePath)) {
22
+ return { deny: true, reason: MEMORY_REDIRECT_REASON }
23
+ }
24
+ return { deny: false }
25
+ }
26
+
27
+ // PreToolUse hook for Edit|Write|MultiEdit. Fail-open: any error or missing
28
+ // identity allows the write, so the guard never wedges unrelated projects.
29
+ export async function runMemoryGuard(): Promise<number> {
30
+ try {
31
+ const config = await loadConfig()
32
+ const filePath = hookFilePath(await readHookPayload())
33
+ const decision = decideMemoryGuard(filePath, { identityPresent: !!config?.identity })
34
+ if (decision.deny) {
35
+ process.stdout.write(
36
+ JSON.stringify({
37
+ hookSpecificOutput: {
38
+ hookEventName: 'PreToolUse',
39
+ permissionDecision: 'deny',
40
+ permissionDecisionReason: decision.reason,
41
+ },
42
+ }) + '\n',
43
+ )
44
+ }
45
+ } catch {
46
+ // fail open
47
+ }
48
+ return 0
49
+ }
package/src/cli/reset.ts CHANGED
@@ -1,84 +1,58 @@
1
- import { createInterface } from 'node:readline/promises'
2
- import { stdin as processStdin, stdout as processStdout, stderr as processStderr } from 'node:process'
3
1
  import React from 'react'
4
2
  import { render } from 'ink'
5
- import {
6
- createFactoryResetPlan,
7
- formatFactoryResetPlan,
8
- runFactoryReset,
9
- type FactoryResetPlan,
10
- } from '../storage/factoryReset.js'
3
+ import { stdout, stderr } from 'node:process'
11
4
  import { AppInputProvider } from '../app/input/AppInputProvider.js'
12
5
  import { ResetConfirmView } from './ResetConfirmView.js'
6
+ import { resetPlan, runReset } from '../storage/reset.js'
7
+ import { clearHarnessManagedBlocks } from './syncAdapters/index.js'
13
8
 
14
- export type ResetCommandIO = {
15
- write?: (text: string) => void
16
- writeError?: (text: string) => void
17
- readConfirmation?: () => Promise<string>
18
- clearSecrets?: boolean
19
- input?: NodeJS.ReadableStream
20
- output?: NodeJS.WritableStream
21
- }
22
-
23
- export async function runResetCommand(args: string[] = [], io: ResetCommandIO = {}): Promise<number> {
24
- const write = io.write ?? (text => { processStdout.write(text) })
25
- const writeError = io.writeError ?? (text => { processStderr.write(text) })
9
+ export async function runResetCommand(args: string[] = []): Promise<number> {
26
10
  const yes = args.includes('--yes') || args.includes('-y')
27
- const unknown = args.filter(arg => arg !== '--yes' && arg !== '-y')
11
+ const unknown = args.filter(a => a !== '--yes' && a !== '-y')
28
12
  if (unknown.length > 0) {
29
- writeError(`unknown reset option: ${unknown[0]}\nusage: ethagent reset [--yes]\n`)
13
+ stderr.write(`unknown reset option: ${unknown[0]}\nusage: ethagent reset [--yes]\n`)
30
14
  return 2
31
15
  }
32
16
 
33
- const plan = await createFactoryResetPlan()
17
+ const plan = resetPlan()
34
18
 
35
- if (!yes) {
36
- const confirmed = io.readConfirmation
37
- ? await readTextConfirmation(plan, io)
38
- : await readInkConfirmation(plan, io)
39
- if (!confirmed) {
40
- write('Reset Cancelled.\n')
41
- return 1
42
- }
43
- } else {
44
- write(`${formatFactoryResetPlan(plan)}\n`)
19
+ if (yes) {
20
+ stdout.write(`Resetting ethagent: ${plan.configDir} and ${plan.secretAccounts.length} secrets.\n`)
21
+ await runReset()
22
+ await finishReset()
23
+ return 0
45
24
  }
46
25
 
47
- const result = await runFactoryReset({ clearSecrets: io.clearSecrets })
48
- write([
49
- 'Reset Complete.',
50
- `Deleted ${result.deletedPaths.length} local path${result.deletedPaths.length === 1 ? '' : 's'}.`,
51
- `Cleared ${result.clearedSecretAccounts.length} secret account${result.clearedSecretAccounts.length === 1 ? '' : 's'}.`,
52
- result.preservedPaths.length > 0
53
- ? `Kept ${result.preservedPaths.length} local model path${result.preservedPaths.length === 1 ? '' : 's'}.`
54
- : 'Kept no local model assets.',
55
- '',
56
- ].join('\n'))
26
+ const confirmed = await confirmWithInk(plan.configDir, plan.secretAccounts)
27
+ if (!confirmed) {
28
+ stdout.write('Reset cancelled.\n')
29
+ return 1
30
+ }
31
+ await runReset()
32
+ await finishReset()
57
33
  return 0
58
34
  }
59
35
 
60
- async function readTextConfirmation(plan: FactoryResetPlan, io: ResetCommandIO): Promise<boolean> {
61
- ;(io.write ?? (text => { processStdout.write(text) }))(`${formatFactoryResetPlan(plan)}\n`)
62
- const answer = await readConfirmation(io)
63
- return answer.trim().toLowerCase() === 'confirm'
36
+ async function finishReset(): Promise<void> {
37
+ const cleared = await clearHarnessManagedBlocks()
38
+ if (cleared.length > 0) {
39
+ stdout.write(`Cleared ethagent markers from ${cleared.length} file${cleared.length === 1 ? '' : 's'}.\n`)
40
+ }
41
+ stdout.write('Reset complete. Run ethagent to create or link an agent identity.\n')
64
42
  }
65
43
 
66
- async function readInkConfirmation(plan: FactoryResetPlan, io: ResetCommandIO): Promise<boolean> {
44
+ async function confirmWithInk(configDir: string, secretAccounts: string[]): Promise<boolean> {
67
45
  let confirmed = false
68
46
  const instance = render(
69
47
  React.createElement(
70
48
  AppInputProvider,
71
49
  null,
72
50
  React.createElement(ResetConfirmView, {
73
- plan,
51
+ plan: { configDir, secretAccounts },
74
52
  onDone: (value: boolean) => { confirmed = value },
75
53
  }),
76
54
  ),
77
- {
78
- exitOnCtrlC: false,
79
- stdin: (io.input ?? processStdin) as typeof processStdin,
80
- stdout: (io.output ?? processStdout) as typeof processStdout,
81
- },
55
+ { exitOnCtrlC: false },
82
56
  )
83
57
  try {
84
58
  await instance.waitUntilExit()
@@ -87,19 +61,3 @@ async function readInkConfirmation(plan: FactoryResetPlan, io: ResetCommandIO):
87
61
  }
88
62
  return confirmed
89
63
  }
90
-
91
- async function readConfirmation(io: ResetCommandIO): Promise<string> {
92
- if (io.readConfirmation) {
93
- return io.readConfirmation()
94
- }
95
-
96
- const rl = createInterface({
97
- input: io.input ?? processStdin,
98
- output: io.output ?? processStdout,
99
- })
100
- try {
101
- return await rl.question('Confirm reset: ')
102
- } finally {
103
- rl.close()
104
- }
105
- }
@@ -0,0 +1,33 @@
1
+ import { runSync } from './sync.js'
2
+
3
+ // Injected into the model's context on session start so it records durable facts
4
+ // in the portable markers (which sync to the vault) instead of harness-local files.
5
+ export function buildSessionStartContext(): string {
6
+ return (
7
+ "ethagent portable memory is active. As you converse, keep this agent's soul and memory current by editing " +
8
+ '~/.claude/CLAUDE.md directly: durable user and project facts go between the `<!-- ethagent:memory:start -->` and ' +
9
+ '`<!-- ethagent:memory:end -->` markers; persona, voice, and standards go between the `<!-- ethagent:soul:start -->` ' +
10
+ 'and `<!-- ethagent:soul:end -->` markers. Do NOT create separate files in the Claude Code native memory directory ' +
11
+ '(~/.claude/projects/.../memory/), since they do not travel with the agent. Edits between the markers sync to the ' +
12
+ 'onchain vault automatically.'
13
+ )
14
+ }
15
+
16
+ // SessionStart hook: restore (sync vault -> harness) then remind (inject guidance).
17
+ // runSync is quiet here so only the JSON envelope reaches stdout for the harness to parse.
18
+ export async function runSessionStart(): Promise<number> {
19
+ try {
20
+ await runSync({ quiet: true })
21
+ } catch {
22
+ // still emit guidance even if the sync step failed
23
+ }
24
+ process.stdout.write(
25
+ JSON.stringify({
26
+ hookSpecificOutput: {
27
+ hookEventName: 'SessionStart',
28
+ additionalContext: buildSessionStartContext(),
29
+ },
30
+ }) + '\n',
31
+ )
32
+ return 0
33
+ }