echoes-vault-opencode 1.0.34 → 1.0.35

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 (2) hide show
  1. package/package.json +1 -1
  2. package/tui.tsx +113 -152
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echoes-vault-opencode",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
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,159 +1,120 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
- import { readFileSync } from "node:fs"
3
- import { join } from "node:path"
4
2
  import type { TuiPlugin } from "@opencode-ai/plugin/tui"
5
-
6
- type SessionState = {
7
- started: boolean
8
- saved: boolean
9
- lastStart: string | null
10
- lastSave: string | null
11
- }
12
-
13
- type VaultState = {
14
- version: number
15
- initialized: boolean
16
- session: SessionState
17
- }
18
-
19
- type VaultStatus = "uninitialized" | "idle" | "working" | "saved"
20
-
21
- function readVaultState(dir: string): VaultState | null {
22
- try {
23
- return JSON.parse(readFileSync(join(dir, "EchoesVault", ".state.json"), "utf-8"))
24
- } catch {
25
- return null
26
- }
27
- }
28
-
29
- function getStatus(state: VaultState | null): VaultStatus {
30
- if (!state || !state.initialized) return "uninitialized"
31
- if (state.session.saved) return "saved"
32
- if (state.session.started) return "working"
33
- return "idle"
34
- }
35
-
36
- function timeAgo(iso: string | null): string {
37
- if (!iso) return ""
38
- const diff = Date.now() - new Date(iso).getTime()
39
- const mins = Math.floor(diff / 60000)
40
- if (mins < 1) return "just now"
41
- if (mins < 60) return `${mins}m ago`
42
- const hours = Math.floor(mins / 60)
43
- if (hours < 24) return `${hours}h ago`
44
- const days = Math.floor(hours / 24)
45
- return `${days}d ago`
46
- }
47
-
48
- function formatTime(iso: string | null): string {
49
- if (!iso) return ""
50
- return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
51
- }
52
-
53
- const STATUS_META: Record<VaultStatus, { label: string; description: string }> = {
54
- uninitialized: {
55
- label: "Vault not initialized",
56
- description: 'Run /echoes-init to create EchoesVault',
57
- },
58
- idle: {
59
- label: "Vault idle",
60
- description: 'Run /echoes-start to restore context',
61
- },
62
- working: {
63
- label: "Vault active",
64
- description: 'Run /echoes-end before closing to save session',
65
- },
66
- saved: {
67
- label: "Session saved",
68
- description: 'Memory committed to EchoesVault',
69
- },
70
- }
71
-
72
- const STATUS_COLOR: Record<VaultStatus, string> = {
73
- uninitialized: "error",
74
- idle: "warning",
75
- working: "success",
76
- saved: "accent",
3
+ import { createSignal, onMount, onCleanup } from "@opentui/solid"
4
+ import * as fs from "node:fs/promises"
5
+ import * as path from "node:path"
6
+
7
+ // Тип для нашего внутреннего состояния
8
+ type VaultStatus = {
9
+ title: string
10
+ desc: string
11
+ color: string
77
12
  }
78
13
 
79
14
  const tui: TuiPlugin = async (api) => {
80
- const theme = api.theme.current
81
- const dir = api.state.path.directory
82
-
83
- api.slots.register({
84
- slots: {
85
- sidebar_content() {
86
- const state = readVaultState(dir)
87
- const status = getStatus(state)
88
- const meta = STATUS_META[status]
89
- const colorKey = STATUS_COLOR[status]
90
- const color = (theme as any)[colorKey] ?? theme.text
91
-
92
- const startedAgo = timeAgo(state?.session.lastStart ?? null)
93
- const savedTime = formatTime(state?.session.lastSave ?? null)
94
- const savedAgo = timeAgo(state?.session.lastSave ?? null)
95
-
96
- return (
97
- <box flexDirection="column">
98
- <box border="all" borderStyle="single" paddingX={1} paddingY={0}>
99
- <text fg={theme.text}>
100
- <b>EchoesVault</b>
101
- </text>
102
- </box>
103
-
104
- <box paddingX={1} paddingY={0} marginTop={1}>
105
- <text fg={color}>
106
- <b>{meta.label}</b>
107
- </text>
108
- </box>
109
-
110
- <box paddingX={1} paddingY={0}>
111
- <text fg={theme.textMuted}>{meta.description}</text>
112
- </box>
113
-
114
- {startedAgo && (
115
- <box paddingX={1} paddingY={0}>
116
- <text fg={theme.textMuted}>Started {startedAgo}</text>
117
- </box>
118
- )}
119
-
120
- {savedTime && (
121
- <box paddingX={1} paddingY={0}>
122
- <text fg={theme.textMuted}>Last save {savedTime}</text>
123
- {savedAgo && <text fg={theme.textMuted}> ({savedAgo})</text>}
124
- </box>
125
- )}
126
-
127
- <box paddingX={1} paddingY={0} marginTop={1}>
128
- <text fg={theme.textMuted}>
129
- <b>Commands</b>
130
- </text>
131
- </box>
132
-
133
- <box paddingX={1} paddingY={0}>
134
- <text fg={theme.text}>/echoes-init</text>
135
- <text fg={theme.textMuted}> Create vault</text>
136
- </box>
137
-
138
- <box paddingX={1} paddingY={0}>
139
- <text fg={theme.text}>/echoes-start</text>
140
- <text fg={theme.textMuted}> Restore context</text>
141
- </box>
142
-
143
- <box paddingX={1} paddingY={0}>
144
- <text fg={theme.text}>/echoes-end</text>
145
- <text fg={theme.textMuted}> Save session</text>
146
- </box>
147
-
148
- <box paddingX={1} paddingY={0}>
149
- <text fg={theme.text}>/echoes-status</text>
150
- <text fg={theme.textMuted}> Vault health</text>
151
- </box>
152
- </box>
153
- )
154
- },
155
- },
156
- })
15
+ const theme = api.theme.current
16
+
17
+ api.slots.register({
18
+ slots: {
19
+ sidebar_content() {
20
+ // Создаем реактивный сигнал
21
+ const [status, setStatus] = createSignal<VaultStatus>({
22
+ title: "Checking...",
23
+ desc: "Verifying vault state...",
24
+ color: theme.textMuted,
25
+ })
26
+
27
+ onMount(async () => {
28
+ // Функция проверки статуса
29
+ const checkStatus = async () => {
30
+ // В OpenCode TUI обычно рабочая директория — это process.cwd()
31
+ const cwd = process.cwd()
32
+ const vaultPath = path.join(cwd, "EchoesVault")
33
+ const statePath = path.join(vaultPath, ".state.json")
34
+
35
+ // СТАТУС 1: Хранилище не активировано (папки нет)
36
+ try {
37
+ await fs.access(vaultPath)
38
+ } catch {
39
+ setStatus({
40
+ title: "Not Initialized",
41
+ desc: "Run /echoes-init to create the vault.",
42
+ color: theme.error,
43
+ })
44
+ return
45
+ }
46
+
47
+ // Читаем стейт
48
+ let stateData: any = null
49
+ try {
50
+ const raw = await fs.readFile(statePath, "utf-8")
51
+ stateData = JSON.parse(raw)
52
+ } catch {
53
+ // Файл стейта отсутствует (например, только что сделали init, но еще не start)
54
+ }
55
+
56
+ const session = stateData?.session
57
+
58
+ // СТАТУС 4: Данные сохранены в этой сессии
59
+ if (session?.saved) {
60
+ setStatus({
61
+ title: "Saved",
62
+ desc: "Vault remembered the data.",
63
+ color: theme.accent,
64
+ })
65
+ return
66
+ }
67
+
68
+ // СТАТУС 3: Хранилище работает (вызван /echoes-start)
69
+ if (session?.started) {
70
+ setStatus({
71
+ title: "Active",
72
+ desc: "Working. Call /echoes-end before closing to save.",
73
+ color: theme.success,
74
+ })
75
+ return
76
+ }
77
+
78
+ // СТАТУС 2: Необходимо вызвать /echoes-start
79
+ setStatus({
80
+ title: "Idle",
81
+ desc: "Call /echoes-start to load knowledge.",
82
+ color: theme.warning, // Используем warning, так как это призыв к действию
83
+ })
84
+ }
85
+
86
+ // Запускаем проверку сразу
87
+ await checkStatus()
88
+
89
+ // Запускаем поллинг каждые 2 секунды для обновления UI
90
+ const interval = setInterval(checkStatus, 2000)
91
+
92
+ // Чистим интервал при размонтировании компонента
93
+ onCleanup(() => clearInterval(interval))
94
+ })
95
+
96
+ return (
97
+ <box flexDirection="column">
98
+ <text fg={theme.text}>
99
+ <b>EchoesVault</b>
100
+ </text>
101
+
102
+ {/* Отступ для красоты */}
103
+ <text> </text>
104
+
105
+ {/* Вывод реактивного статуса */}
106
+ <text fg={status().color}>
107
+ <b>• {status().title}</b>
108
+ </text>
109
+
110
+ <text fg={theme.textMuted}>
111
+ {status().desc}
112
+ </text>
113
+ </box>
114
+ )
115
+ },
116
+ },
117
+ })
157
118
  }
158
119
 
159
- export default { id: "echoes-vault-sidebar", tui }
120
+ export default { id: "echoes-vault-ui", tui }