echoes-vault-opencode 1.0.33 → 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/index.ts +76 -0
- package/package.json +1 -1
- package/tui.tsx +113 -143
package/index.ts
CHANGED
|
@@ -58,6 +58,72 @@ const toPageFilename = (name: string): string => {
|
|
|
58
58
|
|
|
59
59
|
const toPageSlug = (filename: string): string => filename.replace(/\.md$/, "")
|
|
60
60
|
|
|
61
|
+
// --------------- State file management ---------------
|
|
62
|
+
|
|
63
|
+
type SessionState = {
|
|
64
|
+
started: boolean
|
|
65
|
+
saved: boolean
|
|
66
|
+
lastStart: string | null
|
|
67
|
+
lastSave: string | null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type VaultState = {
|
|
71
|
+
version: number
|
|
72
|
+
initialized: boolean
|
|
73
|
+
session: SessionState
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const STATE_FILE = ".state.json"
|
|
77
|
+
|
|
78
|
+
const defaultState = (): VaultState => ({
|
|
79
|
+
version: 1,
|
|
80
|
+
initialized: true,
|
|
81
|
+
session: {
|
|
82
|
+
started: false,
|
|
83
|
+
saved: false,
|
|
84
|
+
lastStart: null,
|
|
85
|
+
lastSave: null,
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
async function readState(vaultDir: string): Promise<VaultState> {
|
|
90
|
+
try {
|
|
91
|
+
const raw = await fs.readFile(path.join(vaultDir, STATE_FILE), "utf-8")
|
|
92
|
+
return JSON.parse(raw) as VaultState
|
|
93
|
+
} catch {
|
|
94
|
+
return defaultState()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function writeState(vaultDir: string, state: VaultState): Promise<void> {
|
|
99
|
+
await fs.mkdir(vaultDir, { recursive: true })
|
|
100
|
+
await fs.writeFile(path.join(vaultDir, STATE_FILE), JSON.stringify(state, null, 2))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function markStarted(vaultDir: string): Promise<void> {
|
|
104
|
+
const state = await readState(vaultDir)
|
|
105
|
+
state.initialized = true
|
|
106
|
+
state.session.started = true
|
|
107
|
+
state.session.lastStart = new Date().toISOString()
|
|
108
|
+
await writeState(vaultDir, state)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function markSaved(vaultDir: string): Promise<void> {
|
|
112
|
+
const state = await readState(vaultDir)
|
|
113
|
+
state.session.saved = true
|
|
114
|
+
state.session.lastSave = new Date().toISOString()
|
|
115
|
+
await writeState(vaultDir, state)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function resetSession(vaultDir: string): Promise<VaultState> {
|
|
119
|
+
const state = await readState(vaultDir)
|
|
120
|
+
state.session.started = false
|
|
121
|
+
state.session.saved = false
|
|
122
|
+
// preserve timestamps for display
|
|
123
|
+
await writeState(vaultDir, state)
|
|
124
|
+
return state
|
|
125
|
+
}
|
|
126
|
+
|
|
61
127
|
const ECHOES_INIT_COMMAND = `---
|
|
62
128
|
description: Initialize EchoesVault — create directory structure and index.md
|
|
63
129
|
agent: build
|
|
@@ -317,6 +383,9 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
317
383
|
await ensureCommands(directory)
|
|
318
384
|
await ensureSkills(directory)
|
|
319
385
|
|
|
386
|
+
// Reset session state on every plugin init (new OpenCode session)
|
|
387
|
+
await resetSession(paths.vault)
|
|
388
|
+
|
|
320
389
|
return {
|
|
321
390
|
config: async (input) => {
|
|
322
391
|
const cmds: Record<string, string> = {
|
|
@@ -416,6 +485,8 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
416
485
|
|
|
417
486
|
await fs.writeFile(idxFile, indexContent)
|
|
418
487
|
|
|
488
|
+
await markSaved(paths.vault)
|
|
489
|
+
|
|
419
490
|
return [
|
|
420
491
|
`✅ Memory committed to EchoesVault.`,
|
|
421
492
|
`- Daily log: EchoesVault/daily/${today}.md`,
|
|
@@ -441,6 +512,9 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
441
512
|
const timestamp = new Date().toISOString()
|
|
442
513
|
const entry = `### Scratchpad — ${timestamp}\n\n${args.logEntry}\n\n`
|
|
443
514
|
await fs.appendFile(dailyFile, entry)
|
|
515
|
+
|
|
516
|
+
await markStarted(paths.vault)
|
|
517
|
+
|
|
444
518
|
return `✅ Scratchpad note saved to EchoesVault/daily/${today}.md`
|
|
445
519
|
},
|
|
446
520
|
}),
|
|
@@ -455,6 +529,7 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
455
529
|
async execute(args, _ctx) {
|
|
456
530
|
await ensureVaultDirs(paths)
|
|
457
531
|
const results: string[] = []
|
|
532
|
+
await markStarted(paths.vault)
|
|
458
533
|
try {
|
|
459
534
|
const files = (await fs.readdir(paths.pages)).filter((f) =>
|
|
460
535
|
f.endsWith(".md")
|
|
@@ -501,6 +576,7 @@ const OpenCodeEchoes: Plugin = async ({ directory }) => {
|
|
|
501
576
|
},
|
|
502
577
|
async execute(args, _ctx) {
|
|
503
578
|
await ensureVaultDirs(paths)
|
|
579
|
+
await markStarted(paths.vault)
|
|
504
580
|
const fileName = toPageFilename(args.filename)
|
|
505
581
|
const pageFile = path.join(paths.pages, fileName)
|
|
506
582
|
|
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,150 +1,120 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
|
-
import { createSignal } from "solid-js"
|
|
3
2
|
import type { TuiPlugin } from "@opencode-ai/plugin/tui"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
colorKey: "error",
|
|
14
|
-
},
|
|
15
|
-
idle: {
|
|
16
|
-
label: "Vault idle",
|
|
17
|
-
description: 'Run /echoes-start to restore context from vault',
|
|
18
|
-
colorKey: "warning",
|
|
19
|
-
},
|
|
20
|
-
working: {
|
|
21
|
-
label: "Vault active",
|
|
22
|
-
description: 'Run /echoes-end before closing to save session memory',
|
|
23
|
-
colorKey: "success",
|
|
24
|
-
},
|
|
25
|
-
saved: {
|
|
26
|
-
label: "Session saved",
|
|
27
|
-
description: 'Memory committed to EchoesVault',
|
|
28
|
-
colorKey: "accent",
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function computeStatus(init: boolean, started: boolean, ended: boolean): VaultStatus {
|
|
33
|
-
if (!init) return "uninitialized"
|
|
34
|
-
if (ended) return "saved"
|
|
35
|
-
if (started) return "working"
|
|
36
|
-
return "idle"
|
|
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
|
|
37
12
|
}
|
|
38
13
|
|
|
39
14
|
const tui: TuiPlugin = async (api) => {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
</box>
|
|
144
|
-
)
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
})
|
|
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
|
+
})
|
|
148
118
|
}
|
|
149
119
|
|
|
150
|
-
export default { id: "echoes-vault-
|
|
120
|
+
export default { id: "echoes-vault-ui", tui }
|