ethagent 1.0.4 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ethagent",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "A privacy-first AI agent with a portable Ethereum identity",
5
5
  "type": "module",
6
6
  "main": "bin/ethagent.js",
@@ -56,4 +56,4 @@
56
56
  "@types/react": "^19.2.14",
57
57
  "typescript": "^5.9.3"
58
58
  }
59
- }
59
+ }
package/src/cli/main.tsx CHANGED
@@ -12,13 +12,29 @@ import { AppInputProvider, useAppInput } from '../app/input/AppInputProvider.js'
12
12
  import { loadConfig, type EthagentConfig } from '../storage/config.js'
13
13
  import { runResetCommand } from './reset.js'
14
14
  import { runPreviewCommand } from './preview.js'
15
- import updateNotifier from 'update-notifier'
16
15
 
17
16
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
18
17
  const pkgPath = path.resolve(__dirname, '..', '..', 'package.json')
19
18
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
20
19
 
21
- updateNotifier({ pkg, updateCheckInterval: 0 }).notify()
20
+ async function checkForUpdates() {
21
+ try {
22
+ const res = await fetch('https://registry.npmjs.org/ethagent/latest', { signal: AbortSignal.timeout(1200) })
23
+ const { version: latest } = await res.json() as { version: string }
24
+ if (latest && latest !== pkg.version) {
25
+ const line1 = ` Update available: ${theme.accentPrimary}${pkg.version}${theme.text} -> ${theme.accentMint}${latest}${theme.text} `
26
+ const line2 = ` Run ${theme.accentPeach}npm install -g ethagent${theme.text} to update `
27
+ const border = theme.dim + '─'.repeat(Math.max(line1.length - 20, line2.length - 20) + 10) + theme.text
28
+
29
+ process.stdout.write(`\n${theme.dim}┌${border}┐${theme.text}\n`)
30
+ process.stdout.write(`${theme.dim}│${theme.text} ${line1} ${theme.dim}│${theme.text}\n`)
31
+ process.stdout.write(`${theme.dim}│${theme.text} ${line2} ${theme.dim}│${theme.text}\n`)
32
+ process.stdout.write(`${theme.dim}└${border}┘${theme.text}\n\n`)
33
+ }
34
+ } catch {
35
+ // Silent fail for offline/timeout
36
+ }
37
+ }
22
38
 
23
39
  function readVersion(): string {
24
40
  try {
@@ -153,6 +169,8 @@ async function main(): Promise<number> {
153
169
  const argv = process.argv.slice(2)
154
170
  const [cmd, ...rest] = argv
155
171
 
172
+ await checkForUpdates()
173
+
156
174
  if (!cmd) return runDefault()
157
175
  if (cmd === '--version' || cmd === '-v') {
158
176
  process.stdout.write(`ethagent ${readVersion()}\n`)
@@ -406,8 +406,8 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
406
406
  setCopyNotice(null)
407
407
  setStep({ kind: 'restore-wallet', purpose: identity ? 'switch' : 'restore' })
408
408
  }}
409
- onBackupNow={() => setStep({ kind: 'rebackup-confirm' })}
410
- onRefetchLatest={() => setStep({ kind: 'recovery-refetch-confirm' })}
409
+ onBackupNow={() => setStep({ kind: 'rebackup-confirm', back: { kind: 'menu' } })}
410
+ onRefetchLatest={() => setStep({ kind: 'recovery-refetch-confirm', back: { kind: 'menu' } })}
411
411
  onPublicProfile={() => setStep({ kind: 'continuity-public' })}
412
412
  onPrivateMemory={() => setStep({ kind: 'continuity-private' })}
413
413
  onCopyValues={() => setStep({ kind: 'details' })}
@@ -561,7 +561,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
561
561
  mode="publish"
562
562
  workingStatus={workingStatus}
563
563
  footer={footer}
564
- onConfirm={() => triggerRebackup({ kind: 'menu' })}
564
+ onConfirm={() => triggerRebackup(step.back)}
565
565
  onBack={back}
566
566
  />
567
567
  )
@@ -577,10 +577,10 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
577
577
  if (!identity) return
578
578
  const registry = resolveRegistryForIdentity(identity)
579
579
  if (!registry) {
580
- errorStep(new Error('no agent registry configured for this identity'), { kind: 'menu' })
580
+ errorStep(new Error('no agent registry configured for this identity'), step.back)
581
581
  return
582
582
  }
583
- setStep({ kind: 'recovery-refetching', identity, registry })
583
+ setStep({ kind: 'recovery-refetching', identity, registry, back: step.back })
584
584
  }}
585
585
  onBack={back}
586
586
  />
@@ -594,7 +594,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
594
594
  subtitle="Wallet approval decrypts the latest published snapshot and overwrites local SOUL.md, MEMORY.md, and skills.json."
595
595
  walletSession={walletSession}
596
596
  label={restoreProgress?.label ?? 'fetching latest snapshot from chain...'}
597
- onCancel={() => setStep({ kind: 'menu' })}
597
+ onCancel={() => setStep(step.back)}
598
598
  />
599
599
  )
600
600
  }
@@ -610,7 +610,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
610
610
  footer={footer}
611
611
  onOpenSoul={() => { void openContinuityFile('soul') }}
612
612
  onOpenMemory={() => { void openContinuityFile('memory') }}
613
- onBackup={() => triggerRebackup({ kind: 'continuity-private' })}
613
+ onBackup={() => setStep({ kind: 'rebackup-confirm', back: { kind: 'continuity-private' } })}
614
614
  onBack={back}
615
615
  />
616
616
  )
@@ -627,7 +627,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
627
627
  footer={footer}
628
628
  onEditProfile={() => openPublicProfileEdit({ kind: 'continuity-public' })}
629
629
  onOpenSkills={() => { void openContinuityFile('skills') }}
630
- onPublish={() => triggerRebackup({ kind: 'continuity-public' })}
630
+ onPublish={() => setStep({ kind: 'rebackup-confirm', back: { kind: 'continuity-public' } })}
631
631
  onBack={back}
632
632
  />
633
633
  )
@@ -31,13 +31,11 @@ export type Step =
31
31
  | { kind: 'restore-authorizing'; cid: string; apiUrl: string; envelope: RestorableBackupEnvelope; candidate: Erc8004AgentCandidate; purpose?: RestorePurpose }
32
32
  | { kind: 'rebackup-signing'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; pinataJwt?: string; profileUpdates?: ProfileUpdates; returnTo?: Step }
33
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
34
  | { kind: 'continuity-private'; notice?: string }
37
35
  | { kind: 'continuity-public'; notice?: string }
38
- | { kind: 'rebackup-confirm' }
39
- | { kind: 'recovery-refetch-confirm' }
40
- | { kind: 'recovery-refetching'; identity: EthagentIdentity; registry: Erc8004RegistryConfig }
36
+ | { kind: 'rebackup-confirm'; back: Step }
37
+ | { kind: 'recovery-refetch-confirm'; back: Step }
38
+ | { kind: 'recovery-refetching'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; back: Step }
41
39
  | { kind: 'rebackup-start'; back: Step }
42
40
  | { kind: 'edit-profile-name'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; returnTo?: Step }
43
41
  | { kind: 'edit-profile-description'; identity: EthagentIdentity; registry: Erc8004RegistryConfig; name: string; returnTo?: Step }
@@ -46,7 +46,7 @@ export const PrivateContinuityScreen: React.FC<CommonProps & {
46
46
  { value: 'soul', label: 'open SOUL.md', hint: 'edit persona and operating preferences', disabled: !ready },
47
47
  { value: 'memory', label: 'open MEMORY.md', hint: 'edit private working memory for this agent', disabled: !ready },
48
48
  { value: 'backup', role: 'section', prefix: '--', label: 'Recovery' },
49
- { value: 'backup', label: 'publish snapshot now', hint: 'publishes SOUL.md, MEMORY.md, skills.json, and metadata', disabled: !ready || !canBackup },
49
+ { value: 'backup', label: 'save snapshot now', hint: 'encrypts and publishes local SOUL.md and MEMORY.md changes, as well as skills.json and public metadata', disabled: !ready || !canBackup },
50
50
  { value: 'back', role: 'section', prefix: '--', label: 'Navigation' },
51
51
  { value: 'back', label: 'back to identity hub', hint: 'return without changing private files', role: 'utility' },
52
52
  ]}
@@ -80,7 +80,7 @@ export const PublicSkillsScreen: React.FC<CommonProps & {
80
80
  { value: 'skills', role: 'section', prefix: '--', label: 'Capabilities' },
81
81
  { value: 'skills', label: 'open skills.json', hint: 'edit public capabilities and notes' },
82
82
  { value: 'publish', role: 'section', prefix: '--', label: 'Recovery' },
83
- { value: 'publish', label: 'publish snapshot now', hint: 'publishes SOUL.md, MEMORY.md, skills.json, and metadata', disabled: !canPublish },
83
+ { value: 'publish', label: 'save snapshot now', hint: 'publishes local memory, skills, and metadata', disabled: !canPublish },
84
84
  { value: 'back', role: 'section', prefix: '--', label: 'Navigation' },
85
85
  { value: 'back', label: 'back to identity hub', hint: 'return without changing public metadata', role: 'utility' },
86
86
  ]}
@@ -39,7 +39,7 @@ export const IdentitySummary: React.FC<{
39
39
  }
40
40
 
41
41
  const lastSavedRow = needsBackup
42
- ? { label: 'to publish', value: changedFiles.length > 0 ? changedFiles.join(', ') : 'markdown files', tone: 'warn' as const, highlight: true }
42
+ ? { label: 'unsaved changes', value: changedFiles.length > 0 ? changedFiles.join(', ') : 'markdown files', tone: 'warn' as const, highlight: true }
43
43
  : { label: 'last saved', value: lastBackup, tone: lastBackup === 'never' ? 'dim' as const : 'ok' as const }
44
44
 
45
45
  const summaryRows = [
@@ -69,7 +69,7 @@ export const MenuScreen: React.FC<MenuScreenProps> = ({
69
69
  { value: 'private-memory', role: 'section', prefix: '--', label: 'Private local files' },
70
70
  { value: 'private-memory', label: 'memory and persona', hint: 'SOUL.md and MEMORY.md only on this device' },
71
71
  { value: 'backup', role: 'section', prefix: '--', label: 'Recovery' },
72
- { value: 'backup', label: 'publish snapshot now', hint: 'publishes SOUL.md, MEMORY.md, skills.json, and metadata', disabled: !canRebackup },
72
+ { value: 'backup', label: 'save snapshot now', hint: 'publishes SOUL.md, MEMORY.md, skills.json, and metadata', disabled: !canRebackup },
73
73
  { value: 'refetch', label: 'refetch latest snapshot', hint: 'restore local files from the latest published snapshot', disabled: !canRefetch },
74
74
  { value: 'storage-credential', role: 'section', prefix: '--', label: 'Storage' },
75
75
  { value: 'storage-credential', label: 'IPFS credential', hint: 'save, replace, or forget Pinata JWT' },
@@ -18,17 +18,17 @@ type RecoveryConfirmScreenProps = {
18
18
 
19
19
  export const RecoveryConfirmScreen: React.FC<RecoveryConfirmScreenProps> = ({ mode, workingStatus, footer, onConfirm, onBack }) => {
20
20
  const isPublish = mode === 'publish'
21
- const title = isPublish ? 'Publish Snapshot?' : 'Refetch Latest From Chain?'
21
+ const title = isPublish ? 'Save Snapshot?' : 'Refetch Latest From Chain?'
22
22
  const subtitle = isPublish
23
23
  ? 'This replaces the current on-chain snapshot pointer.'
24
24
  : 'This overwrites local files with the on-chain version.'
25
25
 
26
26
  const headlineColor = isPublish ? theme.accentPeach : theme.accentMint
27
27
  const headline = isPublish
28
- ? 'Publishing replaces the on-chain pointer for this agent.'
28
+ ? 'Saving replaces the on-chain pointer for this agent.'
29
29
  : 'Refetching replaces local SOUL.md, MEMORY.md, and skills.json with what is on chain.'
30
30
  const detail = isPublish
31
- ? 'The old snapshot pointer is overwritten. Local edits become the published state.'
31
+ ? 'The old snapshot pointer is overwritten. Local edits become the saved state.'
32
32
  : 'Unsaved local edits will be lost. Use this when local files are missing or out of sync with the latest published snapshot.'
33
33
 
34
34
  const needsBackup = workingStatus?.publishState === 'local-changes' || workingStatus?.publishState === 'not-published' || workingStatus?.publishState === 'verify-needed'
@@ -50,7 +50,7 @@ export const RecoveryConfirmScreen: React.FC<RecoveryConfirmScreenProps> = ({ mo
50
50
  <Text color={theme.textSubtle}>{detail}</Text>
51
51
  {isPublish && changedFiles.length > 0 && (
52
52
  <Box marginTop={1}>
53
- <Text color={theme.textSubtle}>to publish: </Text>
53
+ <Text color={theme.textSubtle}>unsaved changes: </Text>
54
54
  <Text color="red" bold>{changedFiles.join(', ')}</Text>
55
55
  </Box>
56
56
  )}
@@ -58,10 +58,10 @@ export const RecoveryConfirmScreen: React.FC<RecoveryConfirmScreenProps> = ({ mo
58
58
  <Box marginTop={1}>
59
59
  <Select<'confirm' | 'back'>
60
60
  options={[
61
- { value: 'confirm', role: 'section', prefix: '--', label: isPublish ? 'Publish' : 'Refetch' },
61
+ { value: 'confirm', role: 'section', prefix: '--', label: isPublish ? 'Save' : 'Refetch' },
62
62
  {
63
63
  value: 'confirm',
64
- label: isPublish ? 'Yes, Publish Snapshot Now' : 'Yes, Refetch From Chain',
64
+ label: isPublish ? 'Yes, Save Snapshot Now' : 'Yes, Refetch From Chain',
65
65
  hint: isPublish ? 'Sign and overwrite the on-chain pointer' : 'Wallet decrypts and overwrites local files',
66
66
  },
67
67
  { value: 'back', role: 'section', prefix: '--', label: 'Cancel' },