ethagent 4.1.0 → 4.1.1
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/plugin.json +1 -1
- package/package.json +1 -1
- package/src/cli/hookIo.ts +0 -5
- package/src/cli/memoryGuard.ts +1 -8
- package/src/cli/sessionStart.ts +1 -7
- package/src/cli/syncAdapters/claude-code.ts +0 -6
- package/src/identity/continuity/storage/scaffold.ts +0 -1
- package/src/identity/manager/shared/components/Wordmark.tsx +51 -21
- package/src/identity/manager/shared/reconciliation/agentReconciliation/run.ts +0 -1
- package/src/identity/wallet/page/resize.ts +1 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ethagent",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Portable Ethereum identity for your AI agent. Its soul, memory, and skills live onchain via ERC-8004 + IPFS and snap back into any session.",
|
|
5
5
|
"author": { "name": "bairon.dev" },
|
|
6
6
|
"homepage": "https://github.com/baairon/ethagent",
|
package/package.json
CHANGED
package/src/cli/hookIo.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
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
3
|
export async function readHookPayload(): Promise<Record<string, unknown> | null> {
|
|
7
4
|
if (process.stdin.isTTY) return null
|
|
8
5
|
let raw = ''
|
|
@@ -33,8 +30,6 @@ export function samePath(a: string, b: string): boolean {
|
|
|
33
30
|
return process.platform === 'win32' ? na.toLowerCase() === nb.toLowerCase() : na === nb
|
|
34
31
|
}
|
|
35
32
|
|
|
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
33
|
export function isWithinDir(dir: string, file: string): boolean {
|
|
39
34
|
const fold = (p: string): string => (process.platform === 'win32' ? p.toLowerCase() : p)
|
|
40
35
|
const nd = fold(path.resolve(dir))
|
package/src/cli/memoryGuard.ts
CHANGED
|
@@ -2,9 +2,6 @@ import { loadConfig } from '../storage/config.js'
|
|
|
2
2
|
import { hookFilePath, isWithinDir, readHookPayload } from './hookIo.js'
|
|
3
3
|
import { claudeCodeNativeMemoryDir } from './syncAdapters/claude-code.js'
|
|
4
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
5
|
export const MEMORY_REDIRECT_REASON =
|
|
9
6
|
"ethagent manages this agent's portable memory. Don't write to the Claude Code native memory directory; " +
|
|
10
7
|
'those files stay on this machine and never reach your onchain vault. Record durable facts by editing ' +
|
|
@@ -24,8 +21,6 @@ export function decideMemoryGuard(
|
|
|
24
21
|
return { deny: false }
|
|
25
22
|
}
|
|
26
23
|
|
|
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
24
|
export async function runMemoryGuard(): Promise<number> {
|
|
30
25
|
try {
|
|
31
26
|
const config = await loadConfig()
|
|
@@ -42,8 +37,6 @@ export async function runMemoryGuard(): Promise<number> {
|
|
|
42
37
|
}) + '\n',
|
|
43
38
|
)
|
|
44
39
|
}
|
|
45
|
-
} catch {
|
|
46
|
-
// fail open
|
|
47
|
-
}
|
|
40
|
+
} catch {}
|
|
48
41
|
return 0
|
|
49
42
|
}
|
package/src/cli/sessionStart.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { runSync } from './sync.js'
|
|
2
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
3
|
export function buildSessionStartContext(): string {
|
|
6
4
|
return (
|
|
7
5
|
"ethagent portable memory is active. As you converse, keep this agent's soul and memory current by editing " +
|
|
@@ -13,14 +11,10 @@ export function buildSessionStartContext(): string {
|
|
|
13
11
|
)
|
|
14
12
|
}
|
|
15
13
|
|
|
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
14
|
export async function runSessionStart(): Promise<number> {
|
|
19
15
|
try {
|
|
20
16
|
await runSync({ quiet: true })
|
|
21
|
-
} catch {
|
|
22
|
-
// still emit guidance even if the sync step failed
|
|
23
|
-
}
|
|
17
|
+
} catch {}
|
|
24
18
|
process.stdout.write(
|
|
25
19
|
JSON.stringify({
|
|
26
20
|
hookSpecificOutput: {
|
|
@@ -22,16 +22,10 @@ function claudeProjectMemoryMdPath(): string {
|
|
|
22
22
|
return path.join(claudeDir(), 'projects', slug, 'memory', 'MEMORY.md')
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// The Claude Code native per-project memory directory for the current project.
|
|
26
|
-
// ethagent's portable memory supersedes this; the --memory-guard hook redirects
|
|
27
|
-
// the model away from writing here so nothing siloes on one machine.
|
|
28
25
|
export function claudeCodeNativeMemoryDir(): string {
|
|
29
26
|
return path.dirname(claudeProjectMemoryMdPath())
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
// Every project's mirrored MEMORY.md under a given ~/.claude root, across all
|
|
33
|
-
// directories the agent has ever been synced in, not just the current cwd.
|
|
34
|
-
// Reset uses this so no project is left whispering a stale ethagent block.
|
|
35
29
|
export async function projectMemoryMirrorsUnder(claudeRoot: string): Promise<string[]> {
|
|
36
30
|
const projectsDir = path.join(claudeRoot, 'projects')
|
|
37
31
|
let slugs: string[]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Box, Text } from 'ink'
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import { Box, Text, useStdout } from 'ink'
|
|
3
3
|
import { theme, gradientColor } from '../../../../ui/theme.js'
|
|
4
4
|
|
|
5
5
|
export const LINES = [
|
|
@@ -31,24 +31,54 @@ export const RIGHT_DECOR = [
|
|
|
31
31
|
' ',
|
|
32
32
|
]
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
const WORDMARK_WIDTH = Math.max(...LINES.map(line => line.length))
|
|
35
|
+
const DECOR_WIDTH = 12
|
|
36
|
+
|
|
37
|
+
export type WordmarkLayout = 'full' | 'bare' | 'hidden'
|
|
38
|
+
|
|
39
|
+
export function wordmarkLayout(columns: number): WordmarkLayout {
|
|
40
|
+
if (columns >= WORDMARK_WIDTH + DECOR_WIDTH * 2) return 'full'
|
|
41
|
+
if (columns >= WORDMARK_WIDTH) return 'bare'
|
|
42
|
+
return 'hidden'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const Banner: React.FC = () => (
|
|
46
|
+
<Box flexDirection="column">
|
|
47
|
+
{LINES.map((line, i) => {
|
|
48
|
+
const eth = line.slice(0, SPLIT)
|
|
49
|
+
const agent = line.slice(SPLIT)
|
|
50
|
+
const maxAgent = Math.max(1, agent.length - 1)
|
|
51
|
+
return (
|
|
52
|
+
<Text key={i}>
|
|
53
|
+
<Text color={theme.wordmarkEth}>{eth}</Text>
|
|
54
|
+
{[...agent].map((ch, j) => (
|
|
55
|
+
<Text key={j} color={gradientColor(j / maxAgent)}>{ch}</Text>
|
|
56
|
+
))}
|
|
57
|
+
</Text>
|
|
58
|
+
)
|
|
59
|
+
})}
|
|
53
60
|
</Box>
|
|
54
61
|
)
|
|
62
|
+
|
|
63
|
+
export const Wordmark: React.FC = () => {
|
|
64
|
+
const { stdout } = useStdout()
|
|
65
|
+
const [columns, setColumns] = useState<number>(() => Math.floor(stdout?.columns ?? 80))
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!stdout) return
|
|
68
|
+
const handleResize = () => setColumns(Math.floor(stdout.columns ?? 80))
|
|
69
|
+
stdout.on('resize', handleResize)
|
|
70
|
+
return () => { stdout.off('resize', handleResize) }
|
|
71
|
+
}, [stdout])
|
|
72
|
+
|
|
73
|
+
const layout = wordmarkLayout(columns)
|
|
74
|
+
if (layout === 'hidden') return null
|
|
75
|
+
if (layout === 'bare') return <Banner />
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Box flexDirection="row">
|
|
79
|
+
<Text color={theme.wordmarkEth}>{LEFT_DECOR.join('\n')}</Text>
|
|
80
|
+
<Banner />
|
|
81
|
+
<Text color={theme.wordmarkEth}>{RIGHT_DECOR.join('\n')}</Text>
|
|
82
|
+
</Box>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// Smoothly animates the card's height whenever its content changes, so the
|
|
2
|
-
// surface glides between states instead of snapping. The animation is the
|
|
3
|
-
// native Web Animations API, with no external library, so it stays lightweight
|
|
4
|
-
// and runs the same on every machine. It falls back to an instant resize when
|
|
5
|
-
// the API is unavailable or the user prefers reduced motion.
|
|
6
1
|
|
|
7
2
|
let cardEl: HTMLElement | null = null;
|
|
8
3
|
let cardInnerEl: HTMLElement | null = null;
|
|
@@ -11,7 +6,6 @@ let trackedHeight = 0;
|
|
|
11
6
|
let measuredOnce = false;
|
|
12
7
|
let cardResizeReady = false;
|
|
13
8
|
|
|
14
|
-
// Snappy ease-out (mirrors --ease-out in css.ts): quick to start, gentle to settle.
|
|
15
9
|
const RESIZE_DURATION_MS = 200;
|
|
16
10
|
const RESIZE_EASING = "cubic-bezier(0.16, 1, 0.3, 1)";
|
|
17
11
|
|
|
@@ -57,11 +51,9 @@ function animateHeight(from: number, to: number): void {
|
|
|
57
51
|
const el = cardEl;
|
|
58
52
|
if (!el) return;
|
|
59
53
|
if (resizeAnim) {
|
|
60
|
-
try { resizeAnim.cancel(); } catch (_) {
|
|
54
|
+
try { resizeAnim.cancel(); } catch (_) {}
|
|
61
55
|
resizeAnim = null;
|
|
62
56
|
}
|
|
63
|
-
// The rounded inline height is the source of truth: set it first so the card
|
|
64
|
-
// rests at the final size once the animation ends (default fill is none).
|
|
65
57
|
applyHeight(to);
|
|
66
58
|
if (typeof el.animate !== "function" || prefersReducedMotion() || Math.abs(to - from) < 1) return;
|
|
67
59
|
const anim = el.animate(
|