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.
Files changed (3) hide show
  1. package/index.ts +48 -0
  2. package/package.json +1 -1
  3. 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.5",
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
- return JSON.parse(raw) as EchoesState
25
- } catch {
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
- api.slots.register({
34
- slots: {
35
- sidebar_content() {
36
- const [state, setState] = createSignal<EchoesState | null>(readState())
37
-
38
- const interval = setInterval(() => setState(readState()), 3000)
39
- onCleanup(() => clearInterval(interval))
40
-
41
- const statusColor = () => {
42
- const s = state()
43
- if (!s || !s.initialized) return theme.error
44
- if (s.session.saved) return theme.accent
45
- if (s.session.started) return theme.success
46
- return theme.warning
47
- }
48
-
49
- const statusLabel = () => {
50
- const s = state()
51
- if (!s || !s.initialized) return "Not Activated"
52
- if (s.session.saved) return "Memory Saved"
53
- if (s.session.started) return "Active"
54
- return "Session Not Started"
55
- }
56
-
57
- const statusDesc = () => {
58
- const s = state()
59
- if (!s || !s.initialized) return null
60
- if (s.session.saved) return "Vault memorized session data"
61
- if (s.session.started) return null
62
- return null
63
- }
64
-
65
- const statusCmd = () => {
66
- const s = state()
67
- if (!s || !s.initialized) return "/echoes-init"
68
- if (s.session.saved) return null
69
- if (s.session.started) return "/echoes-end"
70
- return "/echoes-start"
71
- }
72
-
73
- const statusCmdHint = () => {
74
- const s = state()
75
- if (!s || !s.initialized) return "to activate vault"
76
- if (s.session.saved) return null
77
- if (s.session.started) return "before closing"
78
- return "to load context"
79
- }
80
-
81
- return (
82
- <box flexDirection="column">
83
- <box flexDirection="row">
84
- <text fg={statusColor()}><b>• </b></text>
85
- <text fg={theme.textMuted}><b>Echoes</b></text>
86
- <text fg={theme.text}><b>Vault</b></text>
87
- <text fg={theme.textMuted}> v{state()?.pluginVersion ?? ""}</text>
88
- </box>
89
- <text fg={statusColor()}>{statusLabel()}</text>
90
- {statusDesc() && (
91
- <text fg={theme.textMuted}>{statusDesc()}</text>
92
- )}
93
- {statusCmd() && (
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={theme.textMuted}>Run </text>
96
- <text fg={theme.text}>{statusCmd()}</text>
97
- <text fg={theme.textMuted}> {statusCmdHint()}</text>
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
- </box>
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 }