ethagent 1.0.1 → 1.0.3

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.1",
3
+ "version": "1.0.3",
4
4
  "description": "A privacy-first AI agent with a portable Ethereum identity",
5
5
  "type": "module",
6
6
  "main": "bin/ethagent.js",
@@ -47,6 +47,7 @@
47
47
  "ink": "^6.8.0",
48
48
  "react": "^19.2.4",
49
49
  "tsx": "^4.21.0",
50
+ "update-notifier": "^7.3.1",
50
51
  "viem": "^2.48.4",
51
52
  "zod": "^3.25.76"
52
53
  },
package/src/cli/main.tsx CHANGED
@@ -12,8 +12,13 @@ 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'
15
16
 
16
17
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
18
+ const pkgPath = path.resolve(__dirname, '..', '..', 'package.json')
19
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
20
+
21
+ updateNotifier({ pkg }).notify()
17
22
 
18
23
  function readVersion(): string {
19
24
  try {
@@ -24,9 +24,6 @@ import {
24
24
  runRebackupPreflight,
25
25
  runRebackupSigning,
26
26
  runRebackupStorageSubmit,
27
- runPublicProfilePreflight,
28
- runPublicProfileSigning,
29
- runPublicProfileStorageSubmit,
30
27
  runRecoveryRefetch,
31
28
  isAgentTokenIdRequiredError,
32
29
  type EffectCallbacks,
@@ -190,16 +187,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
190
187
  .catch((err: unknown) => errorStep(err, backStep))
191
188
  }
192
189
 
193
- const triggerPublicProfilePublish = (backStep: Step, profileUpdates?: ProfileUpdates): void => {
194
- if (!identity) return
195
- const registry = resolveRegistryForIdentity(identity)
196
- if (!registry) {
197
- errorStep(new Error('no agent registry configured for this identity'), backStep)
198
- return
199
- }
200
- runPublicProfilePreflight(identity, registry, callbacks, profileUpdates, backStep)
201
- .catch((err: unknown) => errorStep(err, backStep))
202
- }
190
+
203
191
 
204
192
  useEffect(() => {
205
193
  if (step.kind !== 'rebackup-start') return
@@ -639,7 +627,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
639
627
  footer={footer}
640
628
  onEditProfile={() => openPublicProfileEdit({ kind: 'continuity-public' })}
641
629
  onOpenSkills={() => { void openContinuityFile('skills') }}
642
- onPublish={() => triggerPublicProfilePublish({ kind: 'continuity-public' })}
630
+ onPublish={() => triggerRebackup({ kind: 'continuity-public' })}
643
631
  onBack={back}
644
632
  />
645
633
  )
@@ -689,8 +677,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
689
677
  onImageSubmit={imagePath => {
690
678
  if (step.kind !== 'edit-profile-image') return
691
679
  const updates: ProfileUpdates = { name: step.name, description: step.description, ...(imagePath ? { imagePath } : {}) }
692
- runPublicProfilePreflight(step.identity, step.registry, callbacks, updates, step.returnTo ?? { kind: 'continuity-public' })
693
- .catch((err: unknown) => errorStep(err, step.returnTo ?? { kind: 'continuity-public' }))
680
+ triggerRebackup(step.returnTo ?? { kind: 'continuity-public' }, updates)
694
681
  }}
695
682
  onImagePick={() => {
696
683
  if (step.kind !== 'edit-profile-image') return
@@ -702,8 +689,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
702
689
  return
703
690
  }
704
691
  const updates: ProfileUpdates = { name: imageStep.name, description: imageStep.description, imagePath: result.file }
705
- runPublicProfilePreflight(imageStep.identity, imageStep.registry, callbacks, updates, imageStep.returnTo ?? { kind: 'continuity-public' })
706
- .catch((err: unknown) => errorStep(err, imageStep.returnTo ?? { kind: 'continuity-public' }))
692
+ triggerRebackup(imageStep.returnTo ?? { kind: 'continuity-public' }, updates)
707
693
  })
708
694
  .catch((err: unknown) => {
709
695
  setStep({ ...imageStep, error: `${(err as Error).message}. enter a path manually if needed.` })
@@ -727,17 +713,7 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
727
713
  )
728
714
  }
729
715
 
730
- if (step.kind === 'public-profile-signing') {
731
- return (
732
- <WalletApprovalScreen
733
- title="Approve Public Profile"
734
- subtitle="Pins skills.json and the Agent Card, then updates tokenURI. Private files are not read."
735
- walletSession={walletSession}
736
- label="waiting for wallet approval..."
737
- onCancel={() => setStep(step.returnTo ?? { kind: 'continuity-public' })}
738
- />
739
- )
740
- }
716
+
741
717
 
742
718
  if (step.kind === 'rebackup-start') {
743
719
  return (
@@ -752,18 +728,14 @@ export const IdentityHub: React.FC<IdentityHubProps> = ({ mode, config, initialA
752
728
 
753
729
 
754
730
 
755
- if (step.kind === 'rebackup-storage' || step.kind === 'public-profile-storage') {
731
+ if (step.kind === 'rebackup-storage') {
756
732
  return (
757
733
  <RebackupStorageScreen
758
734
  step={step}
759
735
  footer={footer}
760
736
  onSubmit={async input => {
761
737
  try {
762
- if (step.kind === 'rebackup-storage') {
763
- await runRebackupStorageSubmit(input, step, callbacks)
764
- } else {
765
- await runPublicProfileStorageSubmit(input, step, callbacks)
766
- }
738
+ await runRebackupStorageSubmit(input, step, callbacks)
767
739
  } catch (err: unknown) {
768
740
  setStep({ ...step, error: (err as Error).message })
769
741
  }
@@ -751,85 +751,7 @@ export async function runRebackupStorageSubmit(
751
751
  callbacks.onStep({ kind: 'rebackup-signing', identity: step.identity, registry: step.registry, pinataJwt, profileUpdates: step.profileUpdates, returnTo: step.returnTo })
752
752
  }
753
753
 
754
- export async function runPublicProfilePreflight(
755
- identity: EthagentIdentity,
756
- registry: Erc8004RegistryConfig,
757
- callbacks: EffectCallbacks,
758
- profileUpdates?: ProfileUpdates,
759
- returnTo: Step = { kind: 'continuity-public' },
760
- ): Promise<void> {
761
- const apiUrl = DEFAULT_IPFS_API_URL
762
- let jwt: string | undefined
763
- try {
764
- jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
765
- } catch (err: unknown) {
766
- callbacks.onStep({ kind: 'public-profile-storage', identity, registry, error: (err as Error).message, profileUpdates, returnTo })
767
- return
768
- }
769
- if (isPinataUploadUrl(apiUrl) && !jwt) {
770
- callbacks.onStep({ kind: 'public-profile-storage', identity, registry, profileUpdates, returnTo })
771
- return
772
- }
773
- callbacks.onStep({ kind: 'public-profile-signing', identity, registry, pinataJwt: jwt, profileUpdates, returnTo })
774
- }
775
754
 
776
- export async function runPublicProfileSigning(
777
- step: Extract<Step, { kind: 'public-profile-signing' }>,
778
- callbacks: EffectCallbacks,
779
- ): Promise<void> {
780
- const expectedOwner = getAddress(step.identity.ownerAddress ?? step.identity.address)
781
- if (!step.identity.agentId) throw new Error('cannot publish public profile: identity is missing an agent token id')
782
-
783
- const prepared = await preparePublicProfileTransaction(step, expectedOwner)
784
- const agentId = BigInt(step.identity.agentId)
785
- await preflightSetAgentUri({
786
- ...step.registry,
787
- account: expectedOwner,
788
- agentId,
789
- newUri: prepared.agentUri,
790
- })
791
-
792
- const tx = await sendBrowserWalletTransaction({
793
- chainId: step.registry.chainId,
794
- expectedAccount: expectedOwner,
795
- to: step.registry.identityRegistryAddress,
796
- data: encodeSetAgentUri({ agentId, newUri: prepared.agentUri }),
797
- onReady: callbacks.onWalletReady,
798
- })
799
- const client = createErc8004PublicClient(step.registry)
800
- await client.waitForTransactionReceipt({ hash: tx.txHash })
801
-
802
- const nextIdentity: EthagentIdentity = {
803
- ...prepared.identity,
804
- source: 'erc8004',
805
- address: getAddress(prepared.ownerAddress),
806
- ownerAddress: getAddress(prepared.ownerAddress),
807
- chainId: step.registry.chainId,
808
- rpcUrl: step.registry.rpcUrl,
809
- identityRegistryAddress: step.registry.identityRegistryAddress,
810
- agentUri: prepared.agentUri,
811
- metadataCid: prepared.metadataCid,
812
- publicSkills: prepared.publicSkills,
813
- }
814
- await writePublicSkillsFile(nextIdentity, prepared.publicSkillsJson)
815
- await callbacks.onIdentityComplete(nextIdentity, step.profileUpdates ? 'public profile updated' : 'public profile published')
816
- }
817
-
818
- export async function runPublicProfileStorageSubmit(
819
- input: string,
820
- step: Extract<Step, { kind: 'public-profile-storage' }>,
821
- callbacks: EffectCallbacks,
822
- ): Promise<void> {
823
- const { jwt: pinataJwt } = await savePinataJwt(input)
824
- callbacks.onStep({
825
- kind: 'public-profile-signing',
826
- identity: step.identity,
827
- registry: step.registry,
828
- pinataJwt,
829
- profileUpdates: step.profileUpdates,
830
- returnTo: step.returnTo,
831
- })
832
- }
833
755
 
834
756
 
835
757
  export async function runRecoveryRefetch(
@@ -915,92 +837,7 @@ export async function runRecoveryRefetch(
915
837
  }
916
838
 
917
839
 
918
- async function preparePublicProfileTransaction(
919
- step: Extract<Step, { kind: 'public-profile-signing' }>,
920
- ownerAddress: Address,
921
- ): Promise<PublicProfilePreparedTransaction> {
922
- const baseState = (step.identity.state ?? {}) as Record<string, unknown>
923
- const profile = step.profileUpdates ?? {}
924
- const nextName = typeof profile.name === 'string' && profile.name.trim()
925
- ? profile.name.trim()
926
- : (typeof baseState.name === 'string' && baseState.name.trim() ? baseState.name.trim() : deriveAgentName(step.identity))
927
- const nextDescription = profile.description !== undefined
928
- ? profile.description.trim()
929
- : (typeof baseState.description === 'string' ? baseState.description : '')
930
- const uploadedImageUri = profile.imagePath === 'delete'
931
- ? ''
932
- : profile.imagePath
933
- ? await uploadAgentImage(profile.imagePath, step.pinataJwt)
934
- : typeof baseState.imageUrl === 'string' && baseState.imageUrl.trim()
935
- ? baseState.imageUrl.trim()
936
- : undefined
937
- const updatedAt = new Date().toISOString()
938
- const state: Record<string, unknown> = {
939
- ...baseState,
940
- name: nextName,
941
- description: nextDescription,
942
- publicProfileUpdatedAt: updatedAt,
943
- }
944
- if (uploadedImageUri === '') {
945
- delete state.imageUrl
946
- } else if (uploadedImageUri) {
947
- state.imageUrl = uploadedImageUri
948
- }
949
- const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
950
- const publicSkillsJson = step.profileUpdates
951
- ? await prepareSyncedPublicSkillsJson(nextIdentityForFiles)
952
- : await ensurePublicSkillsFile(nextIdentityForFiles)
953
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
954
- assertVerifiedPin(publicSkillsPin)
955
- const agentCardPin = await addToIpfs(
956
- DEFAULT_IPFS_API_URL,
957
- serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
958
- fetch,
959
- { pinataJwt: step.pinataJwt },
960
- )
961
- assertVerifiedPin(agentCardPin)
962
- const publicSkills: PublicSkillsMetadata = {
963
- cid: publicSkillsPin.cid,
964
- agentCardCid: agentCardPin.cid,
965
- updatedAt,
966
- status: 'pinned',
967
- }
968
- const backup = step.identity.backup
969
- const registration = withEthagentPointers({
970
- type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
971
- name: nextName,
972
- ...(nextDescription ? { description: nextDescription } : {}),
973
- ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
974
- }, {
975
- ...(backup ? {
976
- backup: {
977
- cid: backup.cid,
978
- envelopeVersion: backup.envelopeVersion,
979
- createdAt: backup.createdAt,
980
- },
981
- } : {}),
982
- publicDiscovery: {
983
- skillsCid: publicSkills.cid,
984
- agentCardCid: publicSkills.agentCardCid,
985
- updatedAt,
986
- },
987
- registration: {
988
- chainId: step.registry.chainId,
989
- identityRegistryAddress: step.registry.identityRegistryAddress,
990
- agentId: step.identity.agentId,
991
- },
992
- })
993
- const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
994
- assertVerifiedPin(metadataPin)
995
- return {
996
- ownerAddress,
997
- agentUri: `ipfs://${metadataPin.cid}`,
998
- metadataCid: metadataPin.cid,
999
- publicSkills,
1000
- identity: { ...step.identity, state },
1001
- publicSkillsJson,
1002
- }
1003
- }
840
+
1004
841
 
1005
842
  function deriveAgentName(identity: EthagentIdentity): string {
1006
843
  const state = (identity.state ?? {}) as Record<string, unknown>
@@ -45,8 +45,8 @@ export const PrivateContinuityScreen: React.FC<CommonProps & {
45
45
  { value: 'soul', role: 'section', prefix: '--', label: 'Open local files' },
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
- { value: 'backup', role: 'section', prefix: '--', label: 'Publish' },
49
- { value: 'backup', label: 'save snapshot', hint: 'save editor changes first; then encrypt and pin', disabled: !ready || !canBackup },
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 },
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
  ]}
@@ -69,7 +69,7 @@ export const PublicSkillsScreen: React.FC<CommonProps & {
69
69
  onOpenSkills: () => void
70
70
  onPublish: () => void
71
71
  }> = ({ identity, config, notice, footer, canPublish, onEditProfile, onOpenSkills, onPublish, onBack }) => (
72
- <Surface title="Public Profile" subtitle={notice ?? 'Public token metadata only. Private SOUL.md and MEMORY.md are not touched here.'} footer={footer}>
72
+ <Surface title="Public Profile" subtitle={notice ?? 'Manage public metadata, skills.json, and the agent card.'} footer={footer}>
73
73
  <IdentitySummary identity={identity} config={config} compact />
74
74
  <PublicProfileRows identity={identity} />
75
75
  <Box marginTop={1}>
@@ -79,8 +79,8 @@ export const PublicSkillsScreen: React.FC<CommonProps & {
79
79
  { value: 'edit', label: 'edit name, description, image', hint: 'upload a local image to IPFS automatically' },
80
80
  { value: 'skills', role: 'section', prefix: '--', label: 'Capabilities' },
81
81
  { value: 'skills', label: 'open skills.json', hint: 'edit public capabilities and notes' },
82
- { value: 'publish', role: 'section', prefix: '--', label: 'Publish' },
83
- { value: 'publish', label: 'publish public profile', hint: 'save skills.json first; pins metadata and updates tokenURI', disabled: !canPublish },
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 },
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
  ]}
@@ -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: 'encrypts and publishes local SOUL.md and MEMORY.md changes', disabled: !canRebackup },
72
+ { value: 'backup', label: 'publish 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' },