ethagent 3.3.4 → 4.1.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.
- package/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +35 -0
- package/LICENSE +1 -1
- package/README.md +67 -102
- package/commands/ethagent.md +40 -0
- package/package.json +14 -16
- package/src/app/keybindings/KeybindingProvider.tsx +1 -6
- package/src/app/keybindings/types.ts +1 -6
- package/src/cli/ResetConfirmView.tsx +54 -53
- package/src/cli/demo.ts +86 -0
- package/src/cli/hookIo.ts +45 -0
- package/src/cli/main.tsx +94 -123
- package/src/cli/memoryGuard.ts +49 -0
- package/src/cli/reset.ts +28 -70
- package/src/cli/sessionStart.ts +33 -0
- package/src/cli/status.ts +46 -0
- package/src/cli/sync.ts +167 -0
- package/src/cli/syncAdapters/claude-code.ts +86 -0
- package/src/cli/syncAdapters/codex.ts +66 -0
- package/src/cli/syncAdapters/index.ts +45 -0
- package/src/cli/syncAdapters/managedBlock.ts +175 -0
- package/src/cli/syncAdapters/shared.ts +63 -0
- package/src/identity/continuity/envelopeParse.ts +20 -1
- package/src/identity/continuity/publicSkills.ts +3 -1
- package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
- package/src/identity/continuity/skills/scaffold.ts +5 -2
- package/src/identity/continuity/snapshots.ts +12 -5
- package/src/identity/continuity/storage/defaults.ts +20 -19
- package/src/identity/continuity/storage/status.ts +1 -1
- package/src/identity/ens/ensLookup/constants.ts +1 -1
- package/src/identity/manager/IdentityManager.tsx +33 -0
- package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
- package/src/identity/{hub → manager}/Routes.tsx +48 -34
- package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
- package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
- package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
- package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
- package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
- package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
- package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
- package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
- package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
- package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
- package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
- package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
- package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
- package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
- package/src/identity/{hub → manager}/create/effects.ts +19 -10
- package/src/identity/manager/create/importScan.ts +122 -0
- package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
- package/src/identity/{hub → manager}/custody/actions.ts +1 -15
- package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
- package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
- package/src/identity/{hub → manager}/custody/types.ts +1 -2
- package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
- package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
- package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
- package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
- package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
- package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
- package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
- package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
- package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
- package/src/identity/{hub → manager}/profile/effects.ts +1 -3
- package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
- package/src/identity/{hub → manager}/profile/state.ts +1 -1
- package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
- package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
- package/src/identity/{hub → manager}/restore/apply.ts +1 -1
- package/src/identity/{hub → manager}/restore/auth.ts +1 -1
- package/src/identity/{hub → manager}/restore/discover.ts +1 -1
- package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
- package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
- package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
- package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
- package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
- package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
- package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
- package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
- package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
- package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
- package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
- package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
- package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
- package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
- package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
- package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
- package/src/identity/manager/shared/effects/types.ts +30 -0
- package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
- package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
- package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
- package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
- package/src/identity/{hub → manager}/shared/utils.ts +6 -10
- package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
- package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
- package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
- package/src/identity/{hub → manager}/types.ts +5 -6
- package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
- package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
- package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
- package/src/identity/registry/erc8004/discovery.ts +3 -17
- package/src/identity/registry/erc8004/utils.ts +1 -1
- package/src/identity/storage/ipfs.ts +21 -1
- package/src/identity/wallet/browserWallet/html.ts +10 -2
- package/src/identity/wallet/browserWallet/http.ts +18 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
- package/src/identity/wallet/browserWallet/requests.ts +10 -28
- package/src/identity/wallet/browserWallet/session.ts +26 -33
- package/src/identity/wallet/browserWallet/validation.ts +14 -0
- package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
- package/src/identity/wallet/page/boot.ts +43 -0
- package/src/identity/wallet/page/config.ts +59 -0
- package/src/identity/wallet/page/constants.ts +12 -0
- package/src/identity/wallet/page/copy.ts +47 -68
- package/src/identity/wallet/page/css.ts +638 -0
- package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
- package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
- package/src/identity/wallet/page/markup.ts +44 -34
- package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
- package/src/identity/wallet/page/resize.ts +95 -0
- package/src/identity/wallet/page/state.ts +135 -8
- package/src/identity/wallet/page/timeline.ts +161 -0
- package/src/identity/wallet/page/view.ts +22 -302
- package/src/storage/config.ts +30 -80
- package/src/storage/reset.ts +31 -0
- package/src/storage/secrets.ts +1 -16
- package/src/ui/Select.tsx +27 -5
- package/src/ui/Spinner.tsx +16 -15
- package/src/ui/Surface.tsx +21 -17
- package/src/ui/TextArea.tsx +173 -0
- package/src/ui/TextInput.tsx +31 -133
- package/src/ui/theme.ts +22 -13
- package/src/utils/clipboard.ts +0 -140
- package/src/app/FirstRun.tsx +0 -577
- package/src/app/FirstRunTimeline.tsx +0 -51
- package/src/app/firstRunConfig.ts +0 -26
- package/src/app/hooks/useCancelRequest.ts +0 -22
- package/src/app/hooks/useDoublePress.ts +0 -46
- package/src/app/hooks/useExitOnCtrlC.ts +0 -36
- package/src/auth/openaiOAuth/credentials.ts +0 -47
- package/src/auth/openaiOAuth/crypto.ts +0 -23
- package/src/auth/openaiOAuth/index.ts +0 -238
- package/src/auth/openaiOAuth/landingPage.ts +0 -116
- package/src/auth/openaiOAuth/listener.ts +0 -151
- package/src/auth/openaiOAuth/refresh.ts +0 -70
- package/src/auth/openaiOAuth/shared.ts +0 -115
- package/src/chat/ChatBottomPane.tsx +0 -296
- package/src/chat/ChatScreen.tsx +0 -1685
- package/src/chat/ConversationStack.tsx +0 -56
- package/src/chat/MessageList.tsx +0 -638
- package/src/chat/SessionStatus.tsx +0 -53
- package/src/chat/chatEnvironment.ts +0 -16
- package/src/chat/chatScreenUtils.ts +0 -194
- package/src/chat/chatSessionState.ts +0 -146
- package/src/chat/chatTurnContext.ts +0 -50
- package/src/chat/chatTurnOrchestrator.ts +0 -603
- package/src/chat/chatTurnRows.ts +0 -64
- package/src/chat/commands.ts +0 -494
- package/src/chat/continuityEditReview.ts +0 -42
- package/src/chat/display/DiffView.tsx +0 -193
- package/src/chat/display/SyntaxText.tsx +0 -192
- package/src/chat/display/toolCallDisplay.ts +0 -103
- package/src/chat/display/toolResultDisplay.ts +0 -19
- package/src/chat/input/ChatInput.tsx +0 -625
- package/src/chat/input/chatInputHelpers.ts +0 -62
- package/src/chat/input/chatInputState.ts +0 -247
- package/src/chat/input/chatPaste.ts +0 -49
- package/src/chat/input/imageRefs.ts +0 -30
- package/src/chat/input/inputRendering.tsx +0 -93
- package/src/chat/input/textCursor.ts +0 -212
- package/src/chat/messageMarkdown.ts +0 -220
- package/src/chat/messageRows.ts +0 -43
- package/src/chat/planImplementation.ts +0 -62
- package/src/chat/slashCommandHandlers.ts +0 -122
- package/src/chat/slashCommandViews.ts +0 -120
- package/src/chat/transcript/TranscriptView.tsx +0 -184
- package/src/chat/transcript/transcriptViewport.ts +0 -295
- package/src/chat/views/ContextLimitView.tsx +0 -95
- package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
- package/src/chat/views/CopyPicker.tsx +0 -50
- package/src/chat/views/PermissionPrompt.tsx +0 -156
- package/src/chat/views/PermissionsView.tsx +0 -165
- package/src/chat/views/PlanApprovalView.tsx +0 -91
- package/src/chat/views/ResumeView.tsx +0 -273
- package/src/chat/views/RewindView.tsx +0 -412
- package/src/cli/preview.tsx +0 -14
- package/src/cli/updateNotice.ts +0 -54
- package/src/identity/continuity/privateEdit/apply.ts +0 -170
- package/src/identity/continuity/privateEdit/diff.ts +0 -6
- package/src/identity/continuity/privateEdit/files.ts +0 -23
- package/src/identity/continuity/privateEdit/types.ts +0 -28
- package/src/identity/continuity/privateEdit.ts +0 -46
- package/src/identity/hub/IdentityHub.tsx +0 -14
- package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
- package/src/identity/hub/ens/effects.ts +0 -218
- package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
- package/src/identity/hub/shared/effects/types.ts +0 -53
- package/src/identity/hub/shared/reconciliation/index.ts +0 -14
- package/src/identity/wallet/page/grainient.ts +0 -278
- package/src/identity/wallet/page/html.ts +0 -28
- package/src/identity/wallet/page/styles/base.ts +0 -260
- package/src/identity/wallet/page/styles/components.ts +0 -262
- package/src/identity/wallet/page/styles/index.ts +0 -5
- package/src/identity/wallet/page/styles/responsive.ts +0 -247
- package/src/identity/wallet/page.tsx +0 -38
- package/src/mcp/approvals.ts +0 -113
- package/src/mcp/config.ts +0 -235
- package/src/mcp/manager.ts +0 -482
- package/src/mcp/managerHelpers.ts +0 -70
- package/src/mcp/names.ts +0 -19
- package/src/mcp/output.ts +0 -96
- package/src/models/ModelPicker.tsx +0 -1009
- package/src/models/catalog.ts +0 -327
- package/src/models/huggingface.ts +0 -712
- package/src/models/huggingfaceStorage.ts +0 -136
- package/src/models/llamacpp.ts +0 -848
- package/src/models/llamacppCommands.ts +0 -44
- package/src/models/llamacppConfig.ts +0 -34
- package/src/models/llamacppDiscovery.ts +0 -176
- package/src/models/llamacppOutput.ts +0 -65
- package/src/models/llamacppPreflight.ts +0 -158
- package/src/models/modelDisplay.ts +0 -180
- package/src/models/modelPickerCatalogFlow.ts +0 -56
- package/src/models/modelPickerCredentials.ts +0 -166
- package/src/models/modelPickerData.ts +0 -41
- package/src/models/modelPickerDisplay.tsx +0 -132
- package/src/models/modelPickerHfFlow.ts +0 -192
- package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
- package/src/models/modelPickerOptions.ts +0 -457
- package/src/models/modelPickerTypes.ts +0 -69
- package/src/models/modelPickerUninstallFlow.ts +0 -48
- package/src/models/modelPickerViewHelpers.ts +0 -174
- package/src/models/modelRecommendation.ts +0 -139
- package/src/models/providerDisplay.ts +0 -16
- package/src/models/runtimeDetection.ts +0 -81
- package/src/models/uncensoredCatalog.ts +0 -86
- package/src/providers/anthropic.ts +0 -290
- package/src/providers/contracts.ts +0 -71
- package/src/providers/errors.ts +0 -80
- package/src/providers/gemini.ts +0 -391
- package/src/providers/openai-chat.ts +0 -474
- package/src/providers/openai-responses-format.ts +0 -177
- package/src/providers/openai-responses.ts +0 -306
- package/src/providers/openaiChatWire.ts +0 -124
- package/src/providers/registry.ts +0 -120
- package/src/providers/retry.ts +0 -58
- package/src/providers/sse.ts +0 -93
- package/src/runtime/compaction.ts +0 -395
- package/src/runtime/cwd.ts +0 -43
- package/src/runtime/providerTurn.ts +0 -38
- package/src/runtime/sessionMode.ts +0 -55
- package/src/runtime/systemPrompt.ts +0 -213
- package/src/runtime/textToolParser.ts +0 -161
- package/src/runtime/toolClaimGuards.ts +0 -143
- package/src/runtime/toolExecution.ts +0 -304
- package/src/runtime/toolIntent.ts +0 -143
- package/src/runtime/turn.ts +0 -369
- package/src/runtime/turnNudges.ts +0 -223
- package/src/runtime/turnTypes.ts +0 -86
- package/src/storage/factoryReset.ts +0 -127
- package/src/storage/history.ts +0 -58
- package/src/storage/permissions.ts +0 -76
- package/src/storage/rewind.ts +0 -266
- package/src/storage/sessionExport.ts +0 -49
- package/src/storage/sessions.ts +0 -495
- package/src/tools/bashSafety.ts +0 -186
- package/src/tools/bashTool.ts +0 -140
- package/src/tools/changeDirectoryTool.ts +0 -213
- package/src/tools/contracts.ts +0 -192
- package/src/tools/deleteFileTool.ts +0 -116
- package/src/tools/editTool.ts +0 -165
- package/src/tools/editUtils.ts +0 -170
- package/src/tools/fileDiff.ts +0 -261
- package/src/tools/listDirectoryTool.ts +0 -55
- package/src/tools/listSkillFilesTool.ts +0 -77
- package/src/tools/listSkillsTool.ts +0 -68
- package/src/tools/mcpResourceTools.ts +0 -95
- package/src/tools/permissionRules.ts +0 -85
- package/src/tools/privateContinuityEditTool.ts +0 -187
- package/src/tools/privateContinuityReadTool.ts +0 -106
- package/src/tools/readSkillTool.ts +0 -107
- package/src/tools/readTool.ts +0 -85
- package/src/tools/registry.ts +0 -103
- package/src/tools/writeFileTool.ts +0 -167
- package/src/ui/BrandSplash.tsx +0 -133
- package/src/ui/terminalTitle.ts +0 -30
- package/src/utils/images.ts +0 -140
- package/src/utils/markdownSegments.ts +0 -51
- package/src/utils/messages.ts +0 -37
- package/src/utils/withRetry.ts +0 -324
- /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
- /package/src/identity/{hub → manager}/custody/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
- /package/src/identity/{hub → manager}/ens/state.ts +0 -0
- /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
- /package/src/identity/{hub → manager}/ens/types.ts +0 -0
- /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
- /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
- /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
- /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
- /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
- /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
- /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
- /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
- /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
package/src/cli/demo.ts
ADDED
|
@@ -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 {
|
|
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
|
|
12
|
-
import {
|
|
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 {
|
|
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
|
|
37
|
-
' ethagent
|
|
38
|
-
' ethagent
|
|
39
|
-
' ethagent
|
|
40
|
-
' ethagent --
|
|
41
|
-
' ethagent --
|
|
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
|
-
'
|
|
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
|
|
54
|
+
type Phase =
|
|
49
55
|
| { kind: 'loading' }
|
|
50
|
-
| { kind: '
|
|
51
|
-
| { kind: 'ready'; config: EthagentConfig }
|
|
52
|
-
| { kind: 'cancelled' }
|
|
56
|
+
| { kind: 'ready'; config: EthagentConfig | null }
|
|
53
57
|
| { kind: 'error'; message: string }
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
62
|
-
const [phase, setPhase] = useState<
|
|
63
|
-
|
|
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
|
-
|
|
70
|
-
.then(
|
|
71
|
-
|
|
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 === '
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
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
|
-
<
|
|
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
|
|
139
|
+
const flags = new Set(argv)
|
|
140
|
+
const version = readVersion()
|
|
183
141
|
|
|
184
|
-
if (
|
|
185
|
-
|
|
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 (
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
|
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(
|
|
11
|
+
const unknown = args.filter(a => a !== '--yes' && a !== '-y')
|
|
28
12
|
if (unknown.length > 0) {
|
|
29
|
-
|
|
13
|
+
stderr.write(`unknown reset option: ${unknown[0]}\nusage: ethagent reset [--yes]\n`)
|
|
30
14
|
return 2
|
|
31
15
|
}
|
|
32
16
|
|
|
33
|
-
const plan =
|
|
17
|
+
const plan = resetPlan()
|
|
34
18
|
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
'Reset
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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
|
+
}
|