ethagent 0.2.1 → 1.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.
- package/LICENSE +21 -0
- package/README.md +114 -32
- package/bin/ethagent.js +11 -2
- package/package.json +25 -7
- package/src/app/FirstRun.tsx +412 -0
- package/src/app/hooks/useCancelRequest.ts +22 -0
- package/src/app/hooks/useDoublePress.ts +46 -0
- package/src/app/hooks/useExitOnCtrlC.ts +36 -0
- package/src/app/input/AppInputProvider.tsx +116 -0
- package/src/app/input/appInputParser.ts +279 -0
- package/src/app/keybindings/KeybindingProvider.tsx +134 -0
- package/src/app/keybindings/resolver.ts +42 -0
- package/src/app/keybindings/types.ts +26 -0
- package/src/chat/ChatBottomPane.tsx +280 -0
- package/src/chat/ChatInput.tsx +722 -0
- package/src/chat/ChatScreen.tsx +1575 -0
- package/src/chat/ContextLimitView.tsx +95 -0
- package/src/chat/ContinuityEditReviewView.tsx +48 -0
- package/src/chat/ConversationStack.tsx +47 -0
- package/src/chat/CopyPicker.tsx +52 -0
- package/src/chat/MessageList.tsx +609 -0
- package/src/chat/PermissionPrompt.tsx +153 -0
- package/src/chat/PermissionsView.tsx +159 -0
- package/src/chat/PlanApprovalView.tsx +91 -0
- package/src/chat/ResumeView.tsx +267 -0
- package/src/chat/RewindView.tsx +386 -0
- package/src/chat/SessionStatus.tsx +51 -0
- package/src/chat/TranscriptView.tsx +202 -0
- package/src/chat/chatInputState.ts +247 -0
- package/src/chat/chatPaste.ts +49 -0
- package/src/chat/chatScreenUtils.ts +187 -0
- package/src/chat/chatSessionState.ts +142 -0
- package/src/chat/chatTurnOrchestrator.ts +701 -0
- package/src/chat/commands.ts +673 -0
- package/src/chat/textCursor.ts +202 -0
- package/src/chat/toolResultDisplay.ts +8 -0
- package/src/chat/transcriptViewport.ts +247 -0
- package/src/cli/ResetConfirmView.tsx +61 -0
- package/src/cli/main.tsx +177 -0
- package/src/cli/preview.tsx +19 -0
- package/src/cli/reset.ts +106 -0
- package/src/identity/continuity/editor.ts +149 -0
- package/src/identity/continuity/envelope.ts +345 -0
- package/src/identity/continuity/history.ts +153 -0
- package/src/identity/continuity/privateEdit.ts +334 -0
- package/src/identity/continuity/publicSkills.ts +173 -0
- package/src/identity/continuity/snapshots.ts +183 -0
- package/src/identity/continuity/storage.ts +507 -0
- package/src/identity/crypto/backupEnvelope.ts +486 -0
- package/src/identity/crypto/eth.ts +137 -0
- package/src/identity/hub/IdentityHub.tsx +868 -0
- package/src/identity/hub/identityHubEffects.ts +1146 -0
- package/src/identity/hub/identityHubModel.ts +291 -0
- package/src/identity/hub/identityHubReducer.ts +212 -0
- package/src/identity/hub/screens/BusyScreen.tsx +26 -0
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +144 -0
- package/src/identity/hub/screens/CreateFlow.tsx +206 -0
- package/src/identity/hub/screens/DetailsScreen.tsx +64 -0
- package/src/identity/hub/screens/EditProfileFlow.tsx +145 -0
- package/src/identity/hub/screens/ErrorScreen.tsx +35 -0
- package/src/identity/hub/screens/IdentitySummary.tsx +70 -0
- package/src/identity/hub/screens/MenuScreen.tsx +117 -0
- package/src/identity/hub/screens/NetworkScreen.tsx +41 -0
- package/src/identity/hub/screens/RebackupStorageScreen.tsx +50 -0
- package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +85 -0
- package/src/identity/hub/screens/RestoreFlow.tsx +206 -0
- package/src/identity/hub/screens/StorageCredentialScreen.tsx +128 -0
- package/src/identity/hub/screens/WalletApprovalScreen.tsx +43 -0
- package/src/identity/profile/imagePicker.ts +180 -0
- package/src/identity/registry/erc8004.ts +1106 -0
- package/src/identity/registry/registryConfig.ts +69 -0
- package/src/identity/storage/ipfs.ts +212 -0
- package/src/identity/storage/pinataJwt.ts +53 -0
- package/src/identity/wallet/browserWallet.ts +393 -0
- package/src/identity/wallet/wallet-page/wallet.html +1082 -0
- package/src/mcp/approvals.ts +113 -0
- package/src/mcp/config.ts +235 -0
- package/src/mcp/manager.ts +541 -0
- package/src/mcp/names.ts +19 -0
- package/src/mcp/output.ts +96 -0
- package/src/models/ModelPicker.tsx +1446 -0
- package/src/models/catalog.ts +296 -0
- package/src/models/huggingface.ts +651 -0
- package/src/models/llamacpp.ts +810 -0
- package/src/models/llamacppPreflight.ts +150 -0
- package/src/models/modelDisplay.ts +105 -0
- package/src/models/modelPickerOptions.ts +421 -0
- package/src/models/modelRecommendation.ts +140 -0
- package/src/models/runtimeDetection.ts +81 -0
- package/src/models/uncensoredCatalog.ts +86 -0
- package/src/providers/anthropic.ts +259 -0
- package/src/providers/contracts.ts +62 -0
- package/src/providers/errors.ts +62 -0
- package/src/providers/gemini.ts +152 -0
- package/src/providers/openai-chat.ts +472 -0
- package/src/providers/registry.ts +42 -0
- package/src/providers/retry.ts +58 -0
- package/src/providers/sse.ts +93 -0
- package/src/runtime/compaction.ts +389 -0
- package/src/runtime/cwd.ts +43 -0
- package/src/runtime/sessionMode.ts +55 -0
- package/src/runtime/systemPrompt.ts +209 -0
- package/src/runtime/toolClaimGuards.ts +143 -0
- package/src/runtime/toolExecution.ts +304 -0
- package/src/runtime/toolIntent.ts +163 -0
- package/src/runtime/turn.ts +858 -0
- package/src/storage/atomicWrite.ts +68 -0
- package/src/storage/config.ts +189 -0
- package/src/storage/factoryReset.ts +130 -0
- package/src/storage/history.ts +58 -0
- package/src/storage/identity.ts +99 -0
- package/src/storage/permissions.ts +76 -0
- package/src/storage/rewind.ts +246 -0
- package/src/storage/secrets.ts +181 -0
- package/src/storage/sessionExport.ts +49 -0
- package/src/storage/sessions.ts +482 -0
- package/src/tools/bashSafety.ts +174 -0
- package/src/tools/bashTool.ts +140 -0
- package/src/tools/changeDirectoryTool.ts +213 -0
- package/src/tools/contracts.ts +179 -0
- package/src/tools/deleteFileTool.ts +111 -0
- package/src/tools/editTool.ts +160 -0
- package/src/tools/editUtils.ts +170 -0
- package/src/tools/listDirectoryTool.ts +55 -0
- package/src/tools/mcpResourceTools.ts +95 -0
- package/src/tools/permissionRules.ts +85 -0
- package/src/tools/privateContinuityEditTool.ts +178 -0
- package/src/tools/privateContinuityReadTool.ts +107 -0
- package/src/tools/readTool.ts +85 -0
- package/src/tools/registry.ts +67 -0
- package/src/tools/writeFileTool.ts +142 -0
- package/src/ui/BrandSplash.tsx +193 -0
- package/src/ui/ProgressBar.tsx +34 -0
- package/src/ui/Select.tsx +143 -0
- package/src/ui/Spinner.tsx +269 -0
- package/src/ui/Surface.tsx +47 -0
- package/src/ui/TextInput.tsx +97 -0
- package/src/ui/theme.ts +59 -0
- package/src/utils/clipboard.ts +216 -0
- package/src/utils/markdownSegments.ts +51 -0
- package/src/utils/messages.ts +35 -0
- package/src/utils/withRetry.ts +280 -0
- package/src/cli.tsx +0 -147
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import type { EthagentConfig, EthagentIdentity, SelectableNetwork } from '../../storage/config.js'
|
|
2
|
+
import {
|
|
3
|
+
RegisterAgentPreflightError,
|
|
4
|
+
supportedErc8004ChainForId,
|
|
5
|
+
type Erc8004AgentCandidate,
|
|
6
|
+
} from '../registry/erc8004.js'
|
|
7
|
+
import { AgentStateOwnerMismatchError } from '../crypto/backupEnvelope.js'
|
|
8
|
+
import { ContinuitySnapshotOwnerMismatchError } from '../continuity/envelope.js'
|
|
9
|
+
import { resolveSelectedNetwork } from '../registry/registryConfig.js'
|
|
10
|
+
|
|
11
|
+
export const PREFLIGHT_AGENT_URI = 'ipfs://bafybeigdyrztma2dbfczw7q6ooozbxlqzyw5r7w4f3qw2axvvxqg3w6y7q'
|
|
12
|
+
|
|
13
|
+
export type IdentityHubErrorView = {
|
|
14
|
+
title: string
|
|
15
|
+
detail?: string
|
|
16
|
+
hint?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function initialAgentState(name: string, description: string, ownerAddress: string): Record<string, unknown> {
|
|
20
|
+
return {
|
|
21
|
+
version: 1,
|
|
22
|
+
name,
|
|
23
|
+
description,
|
|
24
|
+
ownerAddress,
|
|
25
|
+
createdAt: new Date().toISOString(),
|
|
26
|
+
preferences: {},
|
|
27
|
+
memory: {},
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function identityHubErrorView(err: unknown): IdentityHubErrorView {
|
|
32
|
+
if (err instanceof RegisterAgentPreflightError) {
|
|
33
|
+
return {
|
|
34
|
+
title: err.title,
|
|
35
|
+
detail: err.detail,
|
|
36
|
+
hint: err.hint,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (err instanceof AgentStateOwnerMismatchError) {
|
|
40
|
+
return {
|
|
41
|
+
title: 'backup locked to another wallet',
|
|
42
|
+
detail: `wallet ${shortAddress(err.currentOwner)} cannot read state encrypted for ${shortAddress(err.backupOwner)}.`,
|
|
43
|
+
hint: 'Use the wallet that created this backup.',
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (err instanceof ContinuitySnapshotOwnerMismatchError) {
|
|
47
|
+
return {
|
|
48
|
+
title: 'continuity locked to another wallet',
|
|
49
|
+
detail: `wallet ${shortAddress(err.currentOwner)} cannot read SOUL.md or MEMORY.md encrypted for ${shortAddress(err.snapshotOwner)}.`,
|
|
50
|
+
hint: 'Use the wallet that created this continuity snapshot.',
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
54
|
+
if (message === 'fetch failed') {
|
|
55
|
+
return {
|
|
56
|
+
title: 'storage unavailable',
|
|
57
|
+
detail: 'could not reach storage.',
|
|
58
|
+
hint: 'check the connection, then try again.',
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
title: 'identity error',
|
|
63
|
+
detail: message,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function pinataErrorText(err: unknown): string {
|
|
68
|
+
const view = identityHubErrorView(err)
|
|
69
|
+
return view.detail ?? view.title
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isRegistrationPreflightError(err: unknown): boolean {
|
|
73
|
+
return err instanceof RegisterAgentPreflightError
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function shortCid(cid: string): string {
|
|
77
|
+
if (cid.length <= 18) return cid
|
|
78
|
+
return `${cid.slice(0, 10)}...${cid.slice(-6)}`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function shortAddress(address: string): string {
|
|
82
|
+
if (address.length <= 14) return address
|
|
83
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function shortHash(value: string): string {
|
|
87
|
+
if (value.length <= 18) return value
|
|
88
|
+
return `${value.slice(0, 8)}...${value.slice(-6)}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function tokenLabel(candidate: Erc8004AgentCandidate): string {
|
|
92
|
+
return tokenCandidateLabel(candidate)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function tokenCandidateLabel(candidate: Erc8004AgentCandidate): string {
|
|
96
|
+
return candidate.name?.trim() || `agent token #${candidate.agentId.toString()}`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function tokenCandidateSelectLabel(
|
|
100
|
+
candidate: Erc8004AgentCandidate,
|
|
101
|
+
current = false,
|
|
102
|
+
): string {
|
|
103
|
+
return `${tokenCandidateLabel(candidate)}${current ? ' *' : ''}`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function tokenCandidateHint(candidate: Erc8004AgentCandidate): string {
|
|
107
|
+
const chain = supportedErc8004ChainForId(candidate.chainId)
|
|
108
|
+
const network = chain?.network ? networkLabel(chain.network) : chain?.name ?? `chain ${candidate.chainId}`
|
|
109
|
+
const parts = [
|
|
110
|
+
candidate.name?.trim() ? `token #${candidate.agentId.toString()}` : null,
|
|
111
|
+
network,
|
|
112
|
+
candidate.backup?.createdAt ? `backup ${formatDate(candidate.backup.createdAt)}` : null,
|
|
113
|
+
].filter((part): part is string => Boolean(part))
|
|
114
|
+
return parts.join(' · ')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function isCurrentAgentCandidate(
|
|
118
|
+
identity: EthagentIdentity | undefined,
|
|
119
|
+
candidate: Erc8004AgentCandidate,
|
|
120
|
+
): boolean {
|
|
121
|
+
if (!identity?.agentId) return false
|
|
122
|
+
if (identity.agentId !== candidate.agentId.toString()) return false
|
|
123
|
+
|
|
124
|
+
const owner = identity.ownerAddress ?? identity.address
|
|
125
|
+
if (owner && owner.toLowerCase() !== candidate.ownerAddress.toLowerCase()) return false
|
|
126
|
+
if (identity.chainId !== undefined && identity.chainId !== candidate.chainId) return false
|
|
127
|
+
if (
|
|
128
|
+
identity.identityRegistryAddress
|
|
129
|
+
&& identity.identityRegistryAddress.toLowerCase() !== candidate.identityRegistryAddress.toLowerCase()
|
|
130
|
+
) {
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function storageLabel(apiUrl: string): string {
|
|
137
|
+
void apiUrl
|
|
138
|
+
return 'IPFS'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const NETWORK_LABELS: Record<SelectableNetwork, string> = {
|
|
142
|
+
mainnet: 'ethereum mainnet',
|
|
143
|
+
arbitrum: 'arbitrum one',
|
|
144
|
+
base: 'base',
|
|
145
|
+
optimism: 'optimism',
|
|
146
|
+
polygon: 'polygon',
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function networkLabel(network: SelectableNetwork): string {
|
|
150
|
+
return NETWORK_LABELS[network]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const NETWORK_SUBTITLES: Record<SelectableNetwork, string> = {
|
|
154
|
+
mainnet: 'agent tokens on ethereum mainnet.',
|
|
155
|
+
arbitrum: 'agent tokens on arbitrum one.',
|
|
156
|
+
base: 'agent tokens on base.',
|
|
157
|
+
optimism: 'agent tokens on optimism.',
|
|
158
|
+
polygon: 'agent tokens on polygon.',
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function networkSubtitle(network: SelectableNetwork): string {
|
|
162
|
+
return NETWORK_SUBTITLES[network]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function networkMenuTagline(): string {
|
|
166
|
+
return 'choose where your agent token is created or found.'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function currentNetworkLine(config?: EthagentConfig): string {
|
|
170
|
+
return networkLabel(resolveSelectedNetwork(config))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function chainSummaryRow(config?: EthagentConfig, identity?: EthagentIdentity): {
|
|
174
|
+
label: string
|
|
175
|
+
value: string
|
|
176
|
+
tone: 'ok' | 'dim'
|
|
177
|
+
} {
|
|
178
|
+
const network = resolveSelectedNetwork(config)
|
|
179
|
+
const fromIdentity = identity?.chainId ? supportedErc8004ChainForId(identity.chainId)?.name.toLowerCase() : undefined
|
|
180
|
+
const value = fromIdentity ?? networkLabel(network)
|
|
181
|
+
return { label: 'chain', value, tone: identity?.chainId ? 'ok' : 'dim' }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function lastBackupLabel(identity?: EthagentIdentity): string {
|
|
185
|
+
const created = identity?.backup?.createdAt
|
|
186
|
+
return created ? formatDate(created) : 'never'
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function identitySummaryRows(
|
|
190
|
+
identity: EthagentIdentity | undefined,
|
|
191
|
+
config?: EthagentConfig,
|
|
192
|
+
): Array<{
|
|
193
|
+
label: string
|
|
194
|
+
value: string
|
|
195
|
+
tone: 'ok' | 'dim'
|
|
196
|
+
}> {
|
|
197
|
+
const backup = identity?.backup
|
|
198
|
+
const owner = identity?.ownerAddress ?? identity?.address
|
|
199
|
+
const ownerValue = owner ? shortAddress(owner) : 'not connected'
|
|
200
|
+
const tokenValue = identity?.agentId ? `#${identity.agentId}` : 'not created'
|
|
201
|
+
const chain = chainSummaryRow(config, identity)
|
|
202
|
+
const stateValue = backup?.cid ? shortCid(backup.cid) : 'not saved yet'
|
|
203
|
+
const skillsValue = identity?.publicSkills?.cid ? shortCid(identity.publicSkills.cid) : 'not published'
|
|
204
|
+
const cardValue = identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'not published'
|
|
205
|
+
const imageValue = typeof identity?.state?.imageUrl === 'string' && identity.state.imageUrl.trim() ? 'attached' : 'not attached'
|
|
206
|
+
return [
|
|
207
|
+
{ label: 'owner', value: ownerValue, tone: identity ? 'ok' : 'dim' },
|
|
208
|
+
{ label: 'token', value: tokenValue, tone: identity?.agentId ? 'ok' : 'dim' },
|
|
209
|
+
{ label: 'network', value: chain.value, tone: chain.tone },
|
|
210
|
+
{ label: 'state', value: stateValue, tone: backup ? 'ok' : 'dim' },
|
|
211
|
+
{ label: 'skills', value: skillsValue, tone: identity?.publicSkills?.cid ? 'ok' : 'dim' },
|
|
212
|
+
{ label: 'card', value: cardValue, tone: identity?.publicSkills?.agentCardCid ? 'ok' : 'dim' },
|
|
213
|
+
{ label: 'image', value: imageValue, tone: imageValue === 'attached' ? 'ok' : 'dim' },
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export type IdentityDetailSection = {
|
|
218
|
+
title: string
|
|
219
|
+
rows: Array<{
|
|
220
|
+
label: string
|
|
221
|
+
value: string
|
|
222
|
+
tone: 'ok' | 'dim'
|
|
223
|
+
}>
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function identityDetailSections(
|
|
227
|
+
identity: EthagentIdentity | undefined,
|
|
228
|
+
config?: EthagentConfig,
|
|
229
|
+
): IdentityDetailSection[] {
|
|
230
|
+
const backup = identity?.backup
|
|
231
|
+
const owner = identity?.ownerAddress ?? identity?.address
|
|
232
|
+
const chain = chainSummaryRow(config, identity)
|
|
233
|
+
const stateCid = backup?.cid ?? 'not saved yet'
|
|
234
|
+
const registrationCid = identity?.metadataCid ?? 'not saved yet'
|
|
235
|
+
const publicSkillsCid = identity?.publicSkills?.cid ?? 'not published'
|
|
236
|
+
const agentCardCid = identity?.publicSkills?.agentCardCid ?? 'not published'
|
|
237
|
+
|
|
238
|
+
return [
|
|
239
|
+
{
|
|
240
|
+
title: 'Agent',
|
|
241
|
+
rows: [
|
|
242
|
+
{ label: 'token', value: identity?.agentId ? `#${identity.agentId}` : 'not created', tone: identity?.agentId ? 'ok' : 'dim' },
|
|
243
|
+
{ label: 'network', value: chain.value, tone: chain.tone },
|
|
244
|
+
{ label: 'registration', value: registrationCid, tone: identity?.metadataCid ? 'ok' : 'dim' },
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
title: 'Owner',
|
|
249
|
+
rows: [
|
|
250
|
+
{ label: 'wallet', value: owner ?? 'not connected', tone: owner ? 'ok' : 'dim' },
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
title: 'Recovery',
|
|
255
|
+
rows: [
|
|
256
|
+
{ label: 'state CID', value: stateCid, tone: backup?.cid ? 'ok' : 'dim' },
|
|
257
|
+
{ label: 'skills CID', value: publicSkillsCid, tone: identity?.publicSkills?.cid ? 'ok' : 'dim' },
|
|
258
|
+
{ label: 'agent card CID', value: agentCardCid, tone: identity?.publicSkills?.agentCardCid ? 'ok' : 'dim' },
|
|
259
|
+
{ label: 'storage', value: backup?.ipfsApiUrl ? storageLabel(backup.ipfsApiUrl) : 'not saved yet', tone: backup?.ipfsApiUrl ? 'ok' : 'dim' },
|
|
260
|
+
{ label: 'created', value: identity?.createdAt ? formatDate(identity.createdAt) : 'not created', tone: identity?.createdAt ? 'ok' : 'dim' },
|
|
261
|
+
{ label: 'last backup', value: backup?.createdAt ? formatDate(backup.createdAt) : 'never', tone: backup?.createdAt ? 'ok' : 'dim' },
|
|
262
|
+
{ label: 'status', value: backup?.status ?? 'unknown', tone: backup?.status ? 'ok' : 'dim' },
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export type CopyableField = {
|
|
269
|
+
label: string
|
|
270
|
+
value: string
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function copyableIdentityFields(identity?: EthagentIdentity): CopyableField[] {
|
|
274
|
+
if (!identity) return []
|
|
275
|
+
const fields: CopyableField[] = []
|
|
276
|
+
if (identity.backup?.cid) fields.push({ label: 'state CID', value: identity.backup.cid })
|
|
277
|
+
if (identity.publicSkills?.cid) fields.push({ label: 'skills CID', value: identity.publicSkills.cid })
|
|
278
|
+
if (identity.publicSkills?.agentCardCid) fields.push({ label: 'agent card CID', value: identity.publicSkills.agentCardCid })
|
|
279
|
+
if (identity.metadataCid) fields.push({ label: 'registration CID', value: identity.metadataCid })
|
|
280
|
+
if (identity.agentUri) fields.push({ label: 'agent URI', value: identity.agentUri })
|
|
281
|
+
const owner = identity.ownerAddress ?? identity.address
|
|
282
|
+
if (owner) fields.push({ label: 'owner address', value: owner })
|
|
283
|
+
if (identity.agentId) fields.push({ label: 'token id', value: identity.agentId })
|
|
284
|
+
return fields
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function formatDate(input: string): string {
|
|
288
|
+
const date = new Date(input)
|
|
289
|
+
if (Number.isNaN(date.getTime())) return input
|
|
290
|
+
return date.toISOString().slice(0, 10)
|
|
291
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { EthagentConfig, EthagentIdentity, SelectableNetwork } from '../../storage/config.js'
|
|
2
|
+
import type { Erc8004AgentCandidate, Erc8004RegistryConfig } from '../registry/erc8004.js'
|
|
3
|
+
import type { RegistryResolution } from '../registry/registryConfig.js'
|
|
4
|
+
import type { AgentStateBackupEnvelope } from '../crypto/backupEnvelope.js'
|
|
5
|
+
import type { ContinuitySnapshotEnvelope } from '../continuity/envelope.js'
|
|
6
|
+
import type { IdentityHubErrorView } from './identityHubModel.js'
|
|
7
|
+
|
|
8
|
+
export type RestorePurpose = 'restore' | 'switch'
|
|
9
|
+
export type DetailsView = Extract<Step, { kind: 'details' }>
|
|
10
|
+
export type ProfileUpdates = { name?: string; description?: string; imagePath?: string }
|
|
11
|
+
export type RestorableBackupEnvelope = AgentStateBackupEnvelope | ContinuitySnapshotEnvelope
|
|
12
|
+
|
|
13
|
+
export type Step =
|
|
14
|
+
| { kind: 'menu' }
|
|
15
|
+
| { kind: 'replace-confirm'; next: 'create' | 'restore' }
|
|
16
|
+
| { kind: 'create-name'; error?: string }
|
|
17
|
+
| { kind: 'create-description'; name: string }
|
|
18
|
+
| { kind: 'create-network'; name: string; description: string }
|
|
19
|
+
| { kind: 'create-preflight'; name: string; description: string; network?: SelectableNetwork }
|
|
20
|
+
| { kind: 'create-registry'; name: string; description: string; resolution: RegistryResolution; error?: string }
|
|
21
|
+
| { kind: 'create-signing'; name: string; description: string; registry: Erc8004RegistryConfig; pinataJwt?: string }
|
|
22
|
+
| { kind: 'create-storage'; name: string; description: string; registry: Erc8004RegistryConfig; error?: string; pinataJwt?: string }
|
|
23
|
+
| { kind: 'restore-owner'; purpose?: RestorePurpose }
|
|
24
|
+
| { kind: 'restore-wallet'; purpose?: RestorePurpose }
|
|
25
|
+
| { kind: 'restore-network'; ownerHandle: string; purpose?: RestorePurpose }
|
|
26
|
+
| { kind: 'restore-registry'; ownerHandle: string; error?: string; purpose?: RestorePurpose }
|
|
27
|
+
| { kind: 'restore-discovering'; ownerHandle: string; registry: Erc8004RegistryConfig; purpose?: RestorePurpose }
|
|
28
|
+
| { kind: 'restore-token-id'; ownerHandle: string; registry: Erc8004RegistryConfig; error?: string; purpose?: RestorePurpose }
|
|
29
|
+
| { kind: 'restore-select-token'; ownerHandle: string; registry: Erc8004RegistryConfig; candidates: Erc8004AgentCandidate[]; purpose?: RestorePurpose }
|
|
30
|
+
| { kind: 'restore-fetching'; cid: string; apiUrl: string; candidate: Erc8004AgentCandidate; purpose?: RestorePurpose }
|
|
31
|
+
| { kind: 'restore-authorizing'; cid: string; apiUrl: string; envelope: RestorableBackupEnvelope; candidate: Erc8004AgentCandidate; purpose?: RestorePurpose }
|
|
32
|
+
| { kind: 'rebackup-signing'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; pinataJwt?: string; profileUpdates?: ProfileUpdates; returnTo?: Step }
|
|
33
|
+
| { kind: 'rebackup-storage'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; error?: string; pinataJwt?: string; profileUpdates?: ProfileUpdates; returnTo?: Step }
|
|
34
|
+
| { kind: 'public-profile-signing'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; pinataJwt?: string; profileUpdates?: ProfileUpdates; returnTo?: Step }
|
|
35
|
+
| { kind: 'public-profile-storage'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; error?: string; pinataJwt?: string; profileUpdates?: ProfileUpdates; returnTo?: Step }
|
|
36
|
+
| { kind: 'continuity-private'; notice?: string }
|
|
37
|
+
| { kind: 'continuity-public'; notice?: string }
|
|
38
|
+
| { kind: 'continuity-unlocking'; identity: EthagentIdentity; cid?: string; publicSkillsCid?: string; returnTo?: 'private' }
|
|
39
|
+
| { kind: 'rebackup-confirm' }
|
|
40
|
+
| { kind: 'recovery-refetch-confirm' }
|
|
41
|
+
| { kind: 'recovery-refetching'; identity: EthagentIdentity; registry: Erc8004RegistryConfig }
|
|
42
|
+
| { kind: 'rebackup-start'; back: Step }
|
|
43
|
+
| { kind: 'edit-profile-name'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; returnTo?: Step }
|
|
44
|
+
| { kind: 'edit-profile-description'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; name: string; returnTo?: Step }
|
|
45
|
+
| { kind: 'edit-profile-image'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; name: string; description: string; error?: string; returnTo?: Step }
|
|
46
|
+
| { kind: 'storage-credential' }
|
|
47
|
+
| { kind: 'storage-credential-input'; error?: string }
|
|
48
|
+
| { kind: 'storage-credential-forget-confirm' }
|
|
49
|
+
| { kind: 'details' }
|
|
50
|
+
| { kind: 'busy'; label: string }
|
|
51
|
+
| { kind: 'error'; error: IdentityHubErrorView; back: Step }
|
|
52
|
+
|
|
53
|
+
export type Action =
|
|
54
|
+
| { type: 'goMenu' }
|
|
55
|
+
| { type: 'startCreate'; hasIdentity: boolean }
|
|
56
|
+
| { type: 'confirmReplace' }
|
|
57
|
+
| { type: 'cancelReplace' }
|
|
58
|
+
| { type: 'nameSubmitted'; name: string }
|
|
59
|
+
| { type: 'descriptionSubmitted'; name: string; description: string }
|
|
60
|
+
| { type: 'preflightResolved'; step: Step }
|
|
61
|
+
| { type: 'registrySubmitted'; step: Step }
|
|
62
|
+
| { type: 'storageSubmitted'; step: Step }
|
|
63
|
+
| { type: 'walletSigned'; step: Step }
|
|
64
|
+
| { type: 'pinned'; step: Step }
|
|
65
|
+
| { type: 'registered'; step: Step }
|
|
66
|
+
| { type: 'startRestore' }
|
|
67
|
+
| { type: 'ownerSubmitted'; step: Step }
|
|
68
|
+
| { type: 'restoreRegistrySubmitted'; step: Step }
|
|
69
|
+
| { type: 'discovered'; step: Step }
|
|
70
|
+
| { type: 'tokenSelected'; step: Step }
|
|
71
|
+
| { type: 'fetched'; step: Step }
|
|
72
|
+
| { type: 'authorized' }
|
|
73
|
+
| { type: 'openDetails' }
|
|
74
|
+
| { type: 'openCopyPicker' }
|
|
75
|
+
| { type: 'closeCopyPicker' }
|
|
76
|
+
| { type: 'error'; error: IdentityHubErrorView; back: Step }
|
|
77
|
+
| { type: 'back'; from: Step }
|
|
78
|
+
|
|
79
|
+
export function identityHubReducer(state: Step, action: Action): Step {
|
|
80
|
+
switch (action.type) {
|
|
81
|
+
case 'goMenu':
|
|
82
|
+
return { kind: 'menu' }
|
|
83
|
+
case 'startCreate':
|
|
84
|
+
if (action.hasIdentity) return { kind: 'replace-confirm', next: 'create' }
|
|
85
|
+
return { kind: 'create-name' }
|
|
86
|
+
case 'confirmReplace':
|
|
87
|
+
return { kind: 'create-name' }
|
|
88
|
+
case 'cancelReplace':
|
|
89
|
+
return { kind: 'menu' }
|
|
90
|
+
case 'nameSubmitted':
|
|
91
|
+
return { kind: 'create-description', name: action.name }
|
|
92
|
+
case 'descriptionSubmitted':
|
|
93
|
+
return { kind: 'create-network', name: action.name, description: action.description }
|
|
94
|
+
case 'preflightResolved':
|
|
95
|
+
case 'registrySubmitted':
|
|
96
|
+
case 'storageSubmitted':
|
|
97
|
+
case 'walletSigned':
|
|
98
|
+
case 'pinned':
|
|
99
|
+
case 'registered':
|
|
100
|
+
case 'ownerSubmitted':
|
|
101
|
+
case 'restoreRegistrySubmitted':
|
|
102
|
+
case 'discovered':
|
|
103
|
+
case 'tokenSelected':
|
|
104
|
+
case 'fetched':
|
|
105
|
+
return action.step
|
|
106
|
+
case 'startRestore':
|
|
107
|
+
return { kind: 'restore-wallet' }
|
|
108
|
+
case 'openDetails':
|
|
109
|
+
return { kind: 'details' }
|
|
110
|
+
case 'openCopyPicker':
|
|
111
|
+
if (state.kind === 'details') return { kind: 'details' }
|
|
112
|
+
return state
|
|
113
|
+
case 'closeCopyPicker':
|
|
114
|
+
if (state.kind === 'details') return { kind: 'details' }
|
|
115
|
+
return state
|
|
116
|
+
case 'error':
|
|
117
|
+
return { kind: 'error', error: action.error, back: action.back }
|
|
118
|
+
case 'back':
|
|
119
|
+
return backStep(action.from)
|
|
120
|
+
default:
|
|
121
|
+
return state
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function backStep(from: Step): Step {
|
|
126
|
+
switch (from.kind) {
|
|
127
|
+
case 'create-name':
|
|
128
|
+
return { kind: 'menu' }
|
|
129
|
+
case 'create-description':
|
|
130
|
+
return { kind: 'create-name' }
|
|
131
|
+
case 'create-network':
|
|
132
|
+
return { kind: 'create-description', name: from.name }
|
|
133
|
+
case 'create-preflight':
|
|
134
|
+
return { kind: 'create-network', name: from.name, description: from.description }
|
|
135
|
+
case 'create-registry':
|
|
136
|
+
return { kind: 'create-network', name: from.name, description: from.description }
|
|
137
|
+
case 'create-signing':
|
|
138
|
+
return { kind: 'create-network', name: from.name, description: from.description }
|
|
139
|
+
case 'create-storage':
|
|
140
|
+
return { kind: 'create-network', name: from.name, description: from.description }
|
|
141
|
+
case 'restore-owner':
|
|
142
|
+
return { kind: 'menu' }
|
|
143
|
+
case 'restore-wallet':
|
|
144
|
+
return { kind: 'restore-owner', purpose: from.purpose }
|
|
145
|
+
case 'restore-network':
|
|
146
|
+
return { kind: 'restore-owner', purpose: from.purpose }
|
|
147
|
+
case 'restore-registry':
|
|
148
|
+
return { kind: 'restore-network', ownerHandle: from.ownerHandle, purpose: from.purpose }
|
|
149
|
+
case 'restore-discovering':
|
|
150
|
+
return { kind: 'restore-network', ownerHandle: from.ownerHandle, purpose: from.purpose }
|
|
151
|
+
case 'restore-token-id':
|
|
152
|
+
return { kind: 'restore-network', ownerHandle: from.ownerHandle, purpose: from.purpose }
|
|
153
|
+
case 'restore-select-token':
|
|
154
|
+
return { kind: 'restore-network', ownerHandle: from.ownerHandle, purpose: from.purpose }
|
|
155
|
+
case 'restore-fetching':
|
|
156
|
+
return { kind: 'restore-network', ownerHandle: from.candidate.ownerAddress, purpose: from.purpose }
|
|
157
|
+
case 'restore-authorizing':
|
|
158
|
+
return { kind: 'restore-network', ownerHandle: from.candidate.ownerAddress, purpose: from.purpose }
|
|
159
|
+
case 'details':
|
|
160
|
+
return { kind: 'menu' }
|
|
161
|
+
case 'rebackup-signing':
|
|
162
|
+
case 'rebackup-storage':
|
|
163
|
+
case 'rebackup-start':
|
|
164
|
+
return from.kind === 'rebackup-start' ? from.back : from.returnTo ?? { kind: 'menu' }
|
|
165
|
+
case 'public-profile-signing':
|
|
166
|
+
case 'public-profile-storage':
|
|
167
|
+
return from.returnTo ?? { kind: 'continuity-public' }
|
|
168
|
+
case 'continuity-private':
|
|
169
|
+
case 'continuity-public':
|
|
170
|
+
return { kind: 'menu' }
|
|
171
|
+
case 'continuity-unlocking':
|
|
172
|
+
return { kind: 'continuity-private' }
|
|
173
|
+
case 'rebackup-confirm':
|
|
174
|
+
case 'recovery-refetch-confirm':
|
|
175
|
+
case 'recovery-refetching':
|
|
176
|
+
return { kind: 'menu' }
|
|
177
|
+
case 'edit-profile-name':
|
|
178
|
+
return from.returnTo ?? { kind: 'continuity-public' }
|
|
179
|
+
case 'edit-profile-description':
|
|
180
|
+
return { kind: 'edit-profile-name', identity: from.identity, registry: from.registry, returnTo: from.returnTo }
|
|
181
|
+
case 'edit-profile-image':
|
|
182
|
+
return { kind: 'edit-profile-description', identity: from.identity, registry: from.registry, name: from.name, returnTo: from.returnTo }
|
|
183
|
+
case 'storage-credential':
|
|
184
|
+
case 'storage-credential-input':
|
|
185
|
+
case 'storage-credential-forget-confirm':
|
|
186
|
+
return { kind: 'menu' }
|
|
187
|
+
case 'error':
|
|
188
|
+
return from.back
|
|
189
|
+
default:
|
|
190
|
+
return { kind: 'menu' }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const CREATE_STEP_LABELS = ['name', 'describe', 'network', 'create']
|
|
195
|
+
|
|
196
|
+
export function createStepNumber(step: Step): number {
|
|
197
|
+
switch (step.kind) {
|
|
198
|
+
case 'create-name':
|
|
199
|
+
return 1
|
|
200
|
+
case 'create-description':
|
|
201
|
+
return 2
|
|
202
|
+
case 'create-network':
|
|
203
|
+
return 3
|
|
204
|
+
case 'create-preflight':
|
|
205
|
+
case 'create-storage':
|
|
206
|
+
case 'create-registry':
|
|
207
|
+
case 'create-signing':
|
|
208
|
+
return 4
|
|
209
|
+
default:
|
|
210
|
+
return 0
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text } from 'ink'
|
|
3
|
+
import { Surface } from '../../../ui/Surface.js'
|
|
4
|
+
import { Spinner } from '../../../ui/Spinner.js'
|
|
5
|
+
import { theme } from '../../../ui/theme.js'
|
|
6
|
+
import { useAppInput } from '../../../app/input/AppInputProvider.js'
|
|
7
|
+
|
|
8
|
+
type BusyScreenProps = {
|
|
9
|
+
title: string
|
|
10
|
+
subtitle?: React.ReactNode
|
|
11
|
+
label: string
|
|
12
|
+
footer?: React.ReactNode
|
|
13
|
+
onCancel?: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const BusyScreen: React.FC<BusyScreenProps> = ({ title, subtitle, label, footer, onCancel }) => {
|
|
17
|
+
useAppInput((_input, key) => {
|
|
18
|
+
if (key.escape && onCancel) onCancel()
|
|
19
|
+
}, { isActive: Boolean(onCancel) })
|
|
20
|
+
const resolvedFooter = footer ?? (onCancel ? <Text color={theme.dim}>esc cancels</Text> : undefined)
|
|
21
|
+
return (
|
|
22
|
+
<Surface title={title} subtitle={subtitle} footer={resolvedFooter}>
|
|
23
|
+
<Spinner label={label} />
|
|
24
|
+
</Surface>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
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 { IdentitySummary } from './IdentitySummary.js'
|
|
8
|
+
import { shortCid } from '../identityHubModel.js'
|
|
9
|
+
|
|
10
|
+
type PrivateAction = 'restore' | 'soul' | 'memory' | 'backup' | 'back'
|
|
11
|
+
type PublicAction = 'edit' | 'skills' | 'publish' | 'back'
|
|
12
|
+
|
|
13
|
+
type CommonProps = {
|
|
14
|
+
identity?: EthagentIdentity
|
|
15
|
+
config?: EthagentConfig
|
|
16
|
+
ready: boolean
|
|
17
|
+
notice?: string
|
|
18
|
+
footer: React.ReactNode
|
|
19
|
+
onBack: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const PrivateContinuityScreen: React.FC<CommonProps & {
|
|
23
|
+
canBackup: boolean
|
|
24
|
+
onRestore: () => void
|
|
25
|
+
onOpenSoul: () => void
|
|
26
|
+
onOpenMemory: () => void
|
|
27
|
+
onBackup: () => void
|
|
28
|
+
}> = ({
|
|
29
|
+
identity,
|
|
30
|
+
config,
|
|
31
|
+
ready,
|
|
32
|
+
notice,
|
|
33
|
+
footer,
|
|
34
|
+
canBackup,
|
|
35
|
+
onRestore,
|
|
36
|
+
onOpenSoul,
|
|
37
|
+
onOpenMemory,
|
|
38
|
+
onBackup,
|
|
39
|
+
onBack,
|
|
40
|
+
}) => (
|
|
41
|
+
<Surface title="Private Memory Files" subtitle={notice ?? privateSubtitle(ready)} footer={footer}>
|
|
42
|
+
<IdentitySummary identity={identity} config={config} compact />
|
|
43
|
+
<PrivateRows identity={identity} ready={ready} />
|
|
44
|
+
<Box marginTop={1}>
|
|
45
|
+
<Select<PrivateAction>
|
|
46
|
+
options={[
|
|
47
|
+
{ value: 'restore', role: 'section', prefix: '--', label: 'Restore' },
|
|
48
|
+
{ value: 'restore', label: 'restore snapshot', hint: 'decrypt latest IPFS backup with owner wallet' },
|
|
49
|
+
{ value: 'soul', role: 'section', prefix: '--', label: 'Open local files' },
|
|
50
|
+
{ value: 'soul', label: 'open SOUL.md', hint: 'edit persona and operating preferences', disabled: !ready },
|
|
51
|
+
{ value: 'memory', label: 'open MEMORY.md', hint: 'edit private working memory for this agent', disabled: !ready },
|
|
52
|
+
{ value: 'backup', role: 'section', prefix: '--', label: 'Publish' },
|
|
53
|
+
{ value: 'backup', label: 'save snapshot', hint: 'save editor changes first; then encrypt and pin', disabled: !ready || !canBackup },
|
|
54
|
+
{ value: 'back', role: 'section', prefix: '--', label: 'Navigation' },
|
|
55
|
+
{ value: 'back', label: 'back to identity hub', hint: 'return without changing private files', role: 'utility' },
|
|
56
|
+
]}
|
|
57
|
+
hintLayout="inline"
|
|
58
|
+
onSubmit={choice => {
|
|
59
|
+
if (choice === 'restore') return onRestore()
|
|
60
|
+
if (choice === 'soul') return onOpenSoul()
|
|
61
|
+
if (choice === 'memory') return onOpenMemory()
|
|
62
|
+
if (choice === 'backup') return onBackup()
|
|
63
|
+
return onBack()
|
|
64
|
+
}}
|
|
65
|
+
onCancel={onBack}
|
|
66
|
+
/>
|
|
67
|
+
</Box>
|
|
68
|
+
</Surface>
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
export const PublicSkillsScreen: React.FC<CommonProps & {
|
|
72
|
+
canPublish: boolean
|
|
73
|
+
onEditProfile: () => void
|
|
74
|
+
onOpenSkills: () => void
|
|
75
|
+
onPublish: () => void
|
|
76
|
+
}> = ({ identity, config, notice, footer, canPublish, onEditProfile, onOpenSkills, onPublish, onBack }) => (
|
|
77
|
+
<Surface title="Public Profile" subtitle={notice ?? 'Public token metadata only. Private SOUL.md and MEMORY.md are not touched here.'} footer={footer}>
|
|
78
|
+
<IdentitySummary identity={identity} config={config} compact />
|
|
79
|
+
<PublicProfileRows identity={identity} />
|
|
80
|
+
<Box marginTop={1}>
|
|
81
|
+
<Select<PublicAction>
|
|
82
|
+
options={[
|
|
83
|
+
{ value: 'edit', role: 'section', prefix: '--', label: 'Profile' },
|
|
84
|
+
{ value: 'edit', label: 'edit name, description, image', hint: 'upload a local image to IPFS automatically' },
|
|
85
|
+
{ value: 'skills', role: 'section', prefix: '--', label: 'Capabilities' },
|
|
86
|
+
{ value: 'skills', label: 'open skills.json', hint: 'edit public capabilities and notes' },
|
|
87
|
+
{ value: 'publish', role: 'section', prefix: '--', label: 'Publish' },
|
|
88
|
+
{ value: 'publish', label: 'publish public profile', hint: 'save skills.json first; pins metadata and updates tokenURI', disabled: !canPublish },
|
|
89
|
+
{ value: 'back', role: 'section', prefix: '--', label: 'Navigation' },
|
|
90
|
+
{ value: 'back', label: 'back to identity hub', hint: 'return without changing public metadata', role: 'utility' },
|
|
91
|
+
]}
|
|
92
|
+
hintLayout="inline"
|
|
93
|
+
onSubmit={choice => {
|
|
94
|
+
if (choice === 'edit') return onEditProfile()
|
|
95
|
+
if (choice === 'skills') return onOpenSkills()
|
|
96
|
+
if (choice === 'publish') return onPublish()
|
|
97
|
+
return onBack()
|
|
98
|
+
}}
|
|
99
|
+
onCancel={onBack}
|
|
100
|
+
/>
|
|
101
|
+
</Box>
|
|
102
|
+
</Surface>
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const PrivateRows: React.FC<{ identity?: EthagentIdentity; ready: boolean }> = ({ identity, ready }) => (
|
|
106
|
+
<Box flexDirection="column" marginTop={1}>
|
|
107
|
+
<Text>
|
|
108
|
+
<Text color={theme.dim}>{'local files'.padEnd(13)}</Text>
|
|
109
|
+
<Text color={ready ? theme.text : theme.dim}>{ready ? 'SOUL.md and MEMORY.md ready' : 'restore local working files'}</Text>
|
|
110
|
+
</Text>
|
|
111
|
+
<Text>
|
|
112
|
+
<Text color={theme.dim}>{'snapshot'.padEnd(13)}</Text>
|
|
113
|
+
<Text color={identity?.backup?.cid ? theme.text : theme.dim}>{identity?.backup?.cid ? shortCid(identity.backup.cid) : 'not saved yet'}</Text>
|
|
114
|
+
</Text>
|
|
115
|
+
</Box>
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const PublicProfileRows: React.FC<{ identity?: EthagentIdentity }> = ({ identity }) => (
|
|
119
|
+
<Box flexDirection="column" marginTop={1}>
|
|
120
|
+
<Text>
|
|
121
|
+
<Text color={theme.dim}>{'skills.json'.padEnd(13)}</Text>
|
|
122
|
+
<Text color={identity?.publicSkills?.cid ? theme.text : theme.dim}>{identity?.publicSkills?.cid ? shortCid(identity.publicSkills.cid) : 'not published'}</Text>
|
|
123
|
+
</Text>
|
|
124
|
+
<Text>
|
|
125
|
+
<Text color={theme.dim}>{'agent card'.padEnd(13)}</Text>
|
|
126
|
+
<Text color={identity?.publicSkills?.agentCardCid ? theme.text : theme.dim}>{identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'not published'}</Text>
|
|
127
|
+
</Text>
|
|
128
|
+
<Text>
|
|
129
|
+
<Text color={theme.dim}>{'image'.padEnd(13)}</Text>
|
|
130
|
+
<Text color={readStateString(identity?.state, 'imageUrl') ? theme.text : theme.dim}>{readStateString(identity?.state, 'imageUrl') ? 'attached' : 'not attached'}</Text>
|
|
131
|
+
</Text>
|
|
132
|
+
</Box>
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
function privateSubtitle(ready: boolean): string {
|
|
136
|
+
return ready
|
|
137
|
+
? 'SOUL.md and MEMORY.md are private local files on this machine.'
|
|
138
|
+
: 'Restore requires the wallet that owns the encrypted snapshot.'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readStateString(state: Record<string, unknown> | undefined, key: string): string {
|
|
142
|
+
const value = state?.[key]
|
|
143
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
144
|
+
}
|