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.
- package/package.json +1 -1
- 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.
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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-
|
|
120
|
+
export default { id: "echoes-vault-ui", tui }
|