echoes-vault-opencode 1.1.5 → 1.1.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/index.ts +48 -0
- package/package.json +1 -1
- package/tui.tsx +119 -71
package/index.ts
CHANGED
|
@@ -19,6 +19,12 @@ type VaultPaths = {
|
|
|
19
19
|
assets: string
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
type VaultStats = {
|
|
23
|
+
totalPages: number
|
|
24
|
+
totalDailyLogs: number
|
|
25
|
+
deprecatedPages: number
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
type EchoesState = {
|
|
23
29
|
version: number
|
|
24
30
|
pluginVersion: string
|
|
@@ -29,6 +35,7 @@ type EchoesState = {
|
|
|
29
35
|
lastStart: string | null
|
|
30
36
|
lastSave: string | null
|
|
31
37
|
}
|
|
38
|
+
stats: VaultStats
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
const STATE_FILENAME = ".opencode/echoes-state.json"
|
|
@@ -53,6 +60,11 @@ const defaultState = (): EchoesState => ({
|
|
|
53
60
|
lastStart: null,
|
|
54
61
|
lastSave: null,
|
|
55
62
|
},
|
|
63
|
+
stats: {
|
|
64
|
+
totalPages: 0,
|
|
65
|
+
totalDailyLogs: 0,
|
|
66
|
+
deprecatedPages: 0,
|
|
67
|
+
},
|
|
56
68
|
})
|
|
57
69
|
|
|
58
70
|
const readState = async (directory: string): Promise<EchoesState> => {
|
|
@@ -88,6 +100,36 @@ const ensureVaultDirs = async (paths: VaultPaths): Promise<void> => {
|
|
|
88
100
|
await fs.mkdir(paths.assets, { recursive: true })
|
|
89
101
|
}
|
|
90
102
|
|
|
103
|
+
const collectStats = async (vaultPaths: VaultPaths): Promise<VaultStats> => {
|
|
104
|
+
let totalPages = 0
|
|
105
|
+
let totalDailyLogs = 0
|
|
106
|
+
let deprecatedPages = 0
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const pageFiles = (await fs.readdir(vaultPaths.pages)).filter((f) => f.endsWith(".md"))
|
|
110
|
+
totalPages = pageFiles.length
|
|
111
|
+
for (const file of pageFiles) {
|
|
112
|
+
const content = await fs.readFile(path.join(vaultPaths.pages, file), "utf-8")
|
|
113
|
+
if (content.includes("DEPRECATED")) {
|
|
114
|
+
deprecatedPages++
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch { /* pages dir may not exist yet */ }
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const dailyFiles = (await fs.readdir(vaultPaths.daily)).filter((f) => f.endsWith(".md"))
|
|
121
|
+
totalDailyLogs = dailyFiles.length
|
|
122
|
+
} catch { /* daily dir may not exist yet */ }
|
|
123
|
+
|
|
124
|
+
return { totalPages, totalDailyLogs, deprecatedPages }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const updateStats = async (directory: string, vaultPaths: VaultPaths): Promise<void> => {
|
|
128
|
+
const st = await readState(directory)
|
|
129
|
+
st.stats = await collectStats(vaultPaths)
|
|
130
|
+
await writeState(directory, st)
|
|
131
|
+
}
|
|
132
|
+
|
|
91
133
|
const DEFAULT_INDEX = `# EchoesVault Index
|
|
92
134
|
|
|
93
135
|
Welcome to the EchoesVault knowledge base.
|
|
@@ -375,6 +417,7 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
375
417
|
state.pluginVersion = await getPluginVersion()
|
|
376
418
|
state.session.started = false
|
|
377
419
|
state.session.saved = false
|
|
420
|
+
state.stats = await collectStats(paths)
|
|
378
421
|
await writeState(directory, state)
|
|
379
422
|
|
|
380
423
|
return {
|
|
@@ -479,6 +522,7 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
479
522
|
const st = await readState(directory)
|
|
480
523
|
st.session.saved = true
|
|
481
524
|
st.session.lastSave = new Date().toISOString()
|
|
525
|
+
st.stats = await collectStats(paths)
|
|
482
526
|
await writeState(directory, st)
|
|
483
527
|
|
|
484
528
|
return [
|
|
@@ -506,6 +550,7 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
506
550
|
const timestamp = new Date().toISOString()
|
|
507
551
|
const entry = `### Scratchpad — ${timestamp}\n\n${args.logEntry}\n\n`
|
|
508
552
|
await fs.appendFile(dailyFile, entry)
|
|
553
|
+
await updateStats(directory, paths)
|
|
509
554
|
return `✅ Scratchpad note saved to EchoesVault/daily/${today}.md`
|
|
510
555
|
},
|
|
511
556
|
}),
|
|
@@ -587,6 +632,8 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
587
632
|
}
|
|
588
633
|
}
|
|
589
634
|
|
|
635
|
+
await updateStats(directory, paths)
|
|
636
|
+
|
|
590
637
|
const action = existed ? "updated" : "created"
|
|
591
638
|
const parts = [`✅ Page ${action}: EchoesVault/pages/${fileName}`]
|
|
592
639
|
if (!existed && args.indexDescription) {
|
|
@@ -602,6 +649,7 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
602
649
|
async execute(_args, _ctx) {
|
|
603
650
|
const st = await readState(directory)
|
|
604
651
|
st.initialized = true
|
|
652
|
+
st.stats = await collectStats(paths)
|
|
605
653
|
await writeState(directory, st)
|
|
606
654
|
return "EchoesVault activated."
|
|
607
655
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echoes-vault-opencode",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "EchoesVault — persistent memory plugin for OpenCode. Obsidian-style knowledge base with daily logs, encyclopedia pages, and session resumption.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
package/tui.tsx
CHANGED
|
@@ -4,6 +4,14 @@ import { createSignal, onCleanup } from "solid-js"
|
|
|
4
4
|
import * as fs from "node:fs"
|
|
5
5
|
import * as path from "node:path"
|
|
6
6
|
|
|
7
|
+
console.log("[EchoesVault TUI] Module loaded")
|
|
8
|
+
|
|
9
|
+
type VaultStats = {
|
|
10
|
+
totalPages: number
|
|
11
|
+
totalDailyLogs: number
|
|
12
|
+
deprecatedPages: number
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
type EchoesState = {
|
|
8
16
|
version: number
|
|
9
17
|
pluginVersion: string
|
|
@@ -14,94 +22,134 @@ type EchoesState = {
|
|
|
14
22
|
lastStart: string | null
|
|
15
23
|
lastSave: string | null
|
|
16
24
|
}
|
|
25
|
+
stats?: VaultStats
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
const STATE_PATH = path.join(process.cwd(), ".opencode", "echoes-state.json")
|
|
29
|
+
console.log("[EchoesVault TUI] STATE_PATH:", STATE_PATH)
|
|
20
30
|
|
|
21
31
|
const readState = (): EchoesState | null => {
|
|
22
32
|
try {
|
|
23
33
|
const raw = fs.readFileSync(STATE_PATH, "utf-8")
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
const parsed = JSON.parse(raw) as EchoesState
|
|
35
|
+
console.log("[EchoesVault TUI] State read OK, initialized:", parsed.initialized, "stats:", JSON.stringify(parsed.stats))
|
|
36
|
+
return parsed
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.log("[EchoesVault TUI] State read failed:", e)
|
|
26
39
|
return null
|
|
27
40
|
}
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
const tui: TuiPlugin = async (api) => {
|
|
44
|
+
console.log("[EchoesVault TUI] tui() called, api.theme available:", !!api.theme)
|
|
31
45
|
const theme = api.theme.current
|
|
46
|
+
console.log("[EchoesVault TUI] theme.current loaded")
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
slots
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
48
|
+
try {
|
|
49
|
+
api.slots.register({
|
|
50
|
+
slots: {
|
|
51
|
+
sidebar_content() {
|
|
52
|
+
console.log("[EchoesVault TUI] sidebar_content() rendering")
|
|
53
|
+
const [state, setState] = createSignal<EchoesState | null>(readState())
|
|
54
|
+
|
|
55
|
+
const interval = setInterval(() => setState(readState()), 3000)
|
|
56
|
+
onCleanup(() => clearInterval(interval))
|
|
57
|
+
|
|
58
|
+
const statusColor = () => {
|
|
59
|
+
const s = state()
|
|
60
|
+
if (!s || !s.initialized) return theme.error
|
|
61
|
+
if (s.session.saved) return theme.accent
|
|
62
|
+
if (s.session.started) return theme.success
|
|
63
|
+
return theme.warning
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const statusLabel = () => {
|
|
67
|
+
const s = state()
|
|
68
|
+
if (!s || !s.initialized) return "Not Activated"
|
|
69
|
+
if (s.session.saved) return "Memory Saved"
|
|
70
|
+
if (s.session.started) return "Active"
|
|
71
|
+
return "Session Not Started"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const vaultHealthColor = () => {
|
|
75
|
+
const pages = state()?.stats?.totalPages ?? 0
|
|
76
|
+
if (pages > 200) return theme.error
|
|
77
|
+
if (pages >= 170) return theme.warning
|
|
78
|
+
return theme.success
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const vaultUsagePercent = () => {
|
|
82
|
+
const pages = state()?.stats?.totalPages ?? 0
|
|
83
|
+
return Math.round((pages / 200) * 100)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const statusDesc = () => {
|
|
87
|
+
const s = state()
|
|
88
|
+
if (!s || !s.initialized) return null
|
|
89
|
+
if (s.session.saved) return "Vault memorized session data"
|
|
90
|
+
if (s.session.started) return null
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const statusCmd = () => {
|
|
95
|
+
const s = state()
|
|
96
|
+
if (!s || !s.initialized) return "/echoes-init"
|
|
97
|
+
if (s.session.saved) return null
|
|
98
|
+
if (s.session.started) return "/echoes-end"
|
|
99
|
+
return "/echoes-start"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const statusCmdHint = () => {
|
|
103
|
+
const s = state()
|
|
104
|
+
if (!s || !s.initialized) return "to activate vault"
|
|
105
|
+
if (s.session.saved) return null
|
|
106
|
+
if (s.session.started) return "before closing"
|
|
107
|
+
return "to load context"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log("[EchoesVault TUI] About to return JSX")
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<box flexDirection="column">
|
|
94
114
|
<box flexDirection="row">
|
|
95
|
-
<text fg={
|
|
96
|
-
<text fg={theme.
|
|
97
|
-
<text fg={theme.
|
|
115
|
+
<text fg={statusColor()}><b>• </b></text>
|
|
116
|
+
<text fg={theme.textMuted}><b>Echoes</b></text>
|
|
117
|
+
<text fg={theme.text}><b>Vault</b></text>
|
|
118
|
+
<text fg={theme.textMuted}> v{state()?.pluginVersion ?? ""}</text>
|
|
98
119
|
</box>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
120
|
+
<text fg={statusColor()}>{statusLabel()}</text>
|
|
121
|
+
{statusDesc() && (
|
|
122
|
+
<text fg={theme.textMuted}>{statusDesc()}</text>
|
|
123
|
+
)}
|
|
124
|
+
{statusCmd() && (
|
|
125
|
+
<box flexDirection="row">
|
|
126
|
+
<text fg={theme.textMuted}>Run </text>
|
|
127
|
+
<text fg={theme.text}>{statusCmd()}</text>
|
|
128
|
+
<text fg={theme.textMuted}> {statusCmdHint()}</text>
|
|
129
|
+
</box>
|
|
130
|
+
)}
|
|
131
|
+
{state()?.initialized ? (
|
|
132
|
+
<box flexDirection="column">
|
|
133
|
+
<box flexDirection="row">
|
|
134
|
+
<text fg={vaultHealthColor()}>{"• "}</text>
|
|
135
|
+
<text fg={theme.text}><b>Vault Health</b></text>
|
|
136
|
+
</box>
|
|
137
|
+
<text fg={theme.textMuted}>{" Pages: "}{state()?.stats?.totalPages ?? 0}</text>
|
|
138
|
+
<text fg={theme.textMuted}>{" Usage: "}{vaultUsagePercent()}{"%"}</text>
|
|
139
|
+
{(state()?.stats?.totalPages ?? 0) > 200 ? (
|
|
140
|
+
<text fg={theme.textMuted}>{" Consider migrating to RAG"}</text>
|
|
141
|
+
) : null}
|
|
142
|
+
</box>
|
|
143
|
+
) : null}
|
|
144
|
+
</box>
|
|
145
|
+
)
|
|
146
|
+
},
|
|
102
147
|
},
|
|
103
|
-
}
|
|
104
|
-
|
|
148
|
+
})
|
|
149
|
+
console.log("[EchoesVault TUI] slots.register() OK")
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.error("[EchoesVault TUI] slots.register() FAILED:", e)
|
|
152
|
+
}
|
|
105
153
|
}
|
|
106
154
|
|
|
107
155
|
export default { id: "echoes-vault-ui", tui }
|