echoes-vault-opencode 1.1.2 → 1.1.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.
Files changed (3) hide show
  1. package/index.ts +37 -29
  2. package/package.json +1 -1
  3. package/tui.tsx +55 -46
package/index.ts CHANGED
@@ -29,13 +29,13 @@ type EchoesState = {
29
29
  version: number
30
30
  pluginVersion: string
31
31
  initialized: boolean
32
- stats: VaultStats
33
32
  session: {
34
33
  started: boolean
35
34
  saved: boolean
36
35
  lastStart: string | null
37
36
  lastSave: string | null
38
37
  }
38
+ stats: VaultStats
39
39
  }
40
40
 
41
41
  const STATE_FILENAME = ".opencode/echoes-state.json"
@@ -54,41 +54,19 @@ const defaultState = (): EchoesState => ({
54
54
  version: 1,
55
55
  pluginVersion: "0.0.0",
56
56
  initialized: false,
57
- stats: { totalPages: 0, totalDailyLogs: 0, deprecatedPages: 0 },
58
57
  session: {
59
58
  started: false,
60
59
  saved: false,
61
60
  lastStart: null,
62
61
  lastSave: null,
63
62
  },
63
+ stats: {
64
+ totalPages: 0,
65
+ totalDailyLogs: 0,
66
+ deprecatedPages: 0,
67
+ },
64
68
  })
65
69
 
66
- const collectStats = async (vaultPaths: VaultPaths): Promise<VaultStats> => {
67
- let totalPages = 0
68
- let deprecatedPages = 0
69
- let totalDailyLogs = 0
70
- try {
71
- const pageFiles = (await fs.readdir(vaultPaths.pages)).filter((f) => f.endsWith(".md"))
72
- totalPages = pageFiles.length
73
- for (const file of pageFiles) {
74
- const content = await fs.readFile(path.join(vaultPaths.pages, file), "utf-8")
75
- if (content.includes("> [!warning] DEPRECATED")) {
76
- deprecatedPages++
77
- }
78
- }
79
- } catch { /* pages dir may not exist */ }
80
- try {
81
- totalDailyLogs = (await fs.readdir(vaultPaths.daily)).filter((f) => f.endsWith(".md")).length
82
- } catch { /* daily dir may not exist */ }
83
- return { totalPages, totalDailyLogs, deprecatedPages }
84
- }
85
-
86
- const updateStats = async (directory: string, vaultPaths: VaultPaths): Promise<void> => {
87
- const st = await readState(directory)
88
- st.stats = await collectStats(vaultPaths)
89
- await writeState(directory, st)
90
- }
91
-
92
70
  const readState = async (directory: string): Promise<EchoesState> => {
93
71
  try {
94
72
  const raw = await fs.readFile(path.join(directory, STATE_FILENAME), "utf-8")
@@ -122,6 +100,36 @@ const ensureVaultDirs = async (paths: VaultPaths): Promise<void> => {
122
100
  await fs.mkdir(paths.assets, { recursive: true })
123
101
  }
124
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
+
125
133
  const DEFAULT_INDEX = `# EchoesVault Index
126
134
 
127
135
  Welcome to the EchoesVault knowledge base.
@@ -407,9 +415,9 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
407
415
 
408
416
  const state = await readState(directory)
409
417
  state.pluginVersion = await getPluginVersion()
410
- state.stats = await collectStats(paths)
411
418
  state.session.started = false
412
419
  state.session.saved = false
420
+ state.stats = await collectStats(paths)
413
421
  await writeState(directory, state)
414
422
 
415
423
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echoes-vault-opencode",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
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
@@ -1,5 +1,6 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
2
  import type { TuiPlugin } from "@opencode-ai/plugin/tui"
3
+ import { createSignal, onCleanup } from "solid-js"
3
4
  import * as fs from "node:fs"
4
5
  import * as path from "node:path"
5
6
 
@@ -13,22 +14,20 @@ type EchoesState = {
13
14
  version: number
14
15
  pluginVersion: string
15
16
  initialized: boolean
16
- stats: VaultStats
17
17
  session: {
18
18
  started: boolean
19
19
  saved: boolean
20
20
  lastStart: string | null
21
21
  lastSave: string | null
22
22
  }
23
+ stats: VaultStats
23
24
  }
24
25
 
25
- const CAPACITY_LIMIT = 200
26
- const CAPACITY_WARNING = 170
26
+ const STATE_PATH = path.join(process.cwd(), ".opencode", "echoes-state.json")
27
27
 
28
28
  const readState = (): EchoesState | null => {
29
29
  try {
30
- const statePath = path.join(process.cwd(), ".opencode", "echoes-state.json")
31
- const raw = fs.readFileSync(statePath, "utf-8")
30
+ const raw = fs.readFileSync(STATE_PATH, "utf-8")
32
31
  return JSON.parse(raw) as EchoesState
33
32
  } catch {
34
33
  return null
@@ -41,82 +40,92 @@ const tui: TuiPlugin = async (api) => {
41
40
  api.slots.register({
42
41
  slots: {
43
42
  sidebar_content() {
44
- const s = readState()
43
+ const [state, setState] = createSignal<EchoesState | null>(readState())
44
+
45
+ const interval = setInterval(() => setState(readState()), 3000)
46
+ onCleanup(() => clearInterval(interval))
45
47
 
46
- const statusColor = (() => {
48
+ const statusColor = () => {
49
+ const s = state()
47
50
  if (!s || !s.initialized) return theme.error
48
51
  if (s.session.saved) return theme.accent
49
52
  if (s.session.started) return theme.success
50
53
  return theme.warning
51
- })()
54
+ }
52
55
 
53
- const statusLabel = (() => {
56
+ const statusLabel = () => {
57
+ const s = state()
54
58
  if (!s || !s.initialized) return "Not Activated"
55
59
  if (s.session.saved) return "Memory Saved"
56
60
  if (s.session.started) return "Active"
57
61
  return "Session Not Started"
58
- })()
62
+ }
63
+
64
+ const vaultHealthColor = () => {
65
+ const pages = state()?.stats?.totalPages ?? 0
66
+ if (pages > 200) return theme.error
67
+ if (pages >= 170) return theme.warning
68
+ return theme.success
69
+ }
70
+
71
+ const vaultUsagePercent = () => {
72
+ const pages = state()?.stats?.totalPages ?? 0
73
+ return Math.round((pages / 200) * 100)
74
+ }
75
+
76
+ const statusDesc = () => {
77
+ const s = state()
78
+ if (!s || !s.initialized) return null
79
+ if (s.session.saved) return "Vault memorized session data"
80
+ if (s.session.started) return null
81
+ return null
82
+ }
59
83
 
60
- const statusCmd = (() => {
84
+ const statusCmd = () => {
85
+ const s = state()
61
86
  if (!s || !s.initialized) return "/echoes-init"
62
87
  if (s.session.saved) return null
63
88
  if (s.session.started) return "/echoes-end"
64
89
  return "/echoes-start"
65
- })()
90
+ }
66
91
 
67
- const statusCmdHint = (() => {
92
+ const statusCmdHint = () => {
93
+ const s = state()
68
94
  if (!s || !s.initialized) return "to activate vault"
69
95
  if (s.session.saved) return null
70
96
  if (s.session.started) return "before closing"
71
97
  return "to load context"
72
- })()
73
-
74
- const statusDesc = (() => {
75
- if (!s || !s.initialized) return null
76
- if (s.session.saved) return "Vault memorized session data"
77
- return null
78
- })()
79
-
80
- const pages = s?.stats?.totalPages ?? 0
81
-
82
- const capacityColor = (() => {
83
- if (pages > CAPACITY_LIMIT) return theme.error
84
- if (pages > CAPACITY_WARNING) return theme.warning
85
- return theme.success
86
- })()
87
-
88
- const capacityPercent = Math.round((pages / CAPACITY_LIMIT) * 100)
98
+ }
89
99
 
90
100
  return (
91
101
  <box flexDirection="column">
92
102
  <box flexDirection="row">
93
- <text fg={statusColor}><b>• </b></text>
103
+ <text fg={statusColor()}><b>• </b></text>
94
104
  <text fg={theme.textMuted}><b>Echoes</b></text>
95
105
  <text fg={theme.text}><b>Vault</b></text>
96
- <text fg={theme.textMuted}> v{s?.pluginVersion ?? ""}</text>
106
+ <text fg={theme.textMuted}> v{state()?.pluginVersion ?? ""}</text>
97
107
  </box>
98
- <text fg={statusColor}>{statusLabel}</text>
99
- {statusDesc && (
100
- <text fg={theme.textMuted}>{statusDesc}</text>
108
+ <text fg={statusColor()}>{statusLabel()}</text>
109
+ {statusDesc() && (
110
+ <text fg={theme.textMuted}>{statusDesc()}</text>
101
111
  )}
102
- {statusCmd && (
112
+ {statusCmd() && (
103
113
  <box flexDirection="row">
104
114
  <text fg={theme.textMuted}>Run </text>
105
- <text fg={theme.text}>{statusCmd}</text>
106
- <text fg={theme.textMuted}> {statusCmdHint}</text>
115
+ <text fg={theme.text}>{statusCmd()}</text>
116
+ <text fg={theme.textMuted}> {statusCmdHint()}</text>
107
117
  </box>
108
118
  )}
109
- {s?.initialized && (
119
+ {state()?.initialized && (
110
120
  <box flexDirection="column">
111
- <text> </text>
112
121
  <box flexDirection="row">
113
- <text fg={capacityColor}><b>• </b></text>
114
- <text fg={theme.text}><b>Vault Capacity</b></text>
122
+ <text fg={vaultHealthColor()}>• </text>
123
+ <text fg={theme.text}><b>Vault Health</b></text>
115
124
  </box>
116
- <text fg={theme.textMuted}>Pages: {pages}</text>
117
- <text fg={theme.textMuted}>Usage: {capacityPercent}%</text>
118
- {pages > CAPACITY_LIMIT && (
119
- <text fg={theme.textMuted}>Consider migrating to RAG</text>
125
+ <text fg={theme.textMuted}> Pages: {state()?.stats?.totalPages ?? 0}</text>
126
+ <text fg={theme.textMuted}> Usage: {vaultUsagePercent()}%</text>
127
+ {(state()?.stats?.totalPages ?? 0) > 200 && (
128
+ <text fg={theme.textMuted}> Consider migrating to RAG</text>
120
129
  )}
121
130
  </box>
122
131
  )}