echoctl 0.1.0
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/LICENSE +21 -0
- package/README.md +171 -0
- package/bin/echoctl.js +2 -0
- package/package.json +56 -0
- package/scripts/annotate.js +73 -0
- package/scripts/build-docs.js +805 -0
- package/scripts/cli/commands/capture.js +20 -0
- package/scripts/cli/commands/constants.js +70 -0
- package/scripts/cli/commands/doctor.js +10 -0
- package/scripts/cli/commands/helpers.js +27 -0
- package/scripts/cli/commands/hook.js +48 -0
- package/scripts/cli/commands/import_cmd.js +184 -0
- package/scripts/cli/commands/init.js +45 -0
- package/scripts/cli/commands/mcp.js +16 -0
- package/scripts/cli/commands/migrate.js +65 -0
- package/scripts/cli/commands/pipeline.js +26 -0
- package/scripts/cli/commands/project.js +35 -0
- package/scripts/cli/commands/refresh.js +14 -0
- package/scripts/cli/commands/search.js +28 -0
- package/scripts/cli/commands/serve.js +73 -0
- package/scripts/cli/commands/status.js +11 -0
- package/scripts/cli/commands/stop.js +136 -0
- package/scripts/cli/commands/tag.js +89 -0
- package/scripts/cli/echoctl.js +44 -0
- package/scripts/convert.js +55 -0
- package/scripts/import-sessions.js +213 -0
- package/scripts/index.js +92 -0
- package/scripts/lib/cli/names.js +33 -0
- package/scripts/lib/domain/anchor.js +78 -0
- package/scripts/lib/domain/echo-format.js +265 -0
- package/scripts/lib/domain/errors.js +8 -0
- package/scripts/lib/domain/validation.js +126 -0
- package/scripts/lib/hooks/capture.js +401 -0
- package/scripts/lib/hooks/status.js +78 -0
- package/scripts/lib/i18n/format.js +183 -0
- package/scripts/lib/i18n/messages/en.js +41 -0
- package/scripts/lib/i18n/messages/zh-CN.js +40 -0
- package/scripts/lib/import/manifest.js +87 -0
- package/scripts/lib/import/providers/claude-code.js +272 -0
- package/scripts/lib/import/scanner.js +128 -0
- package/scripts/lib/infra/config.js +36 -0
- package/scripts/lib/infra/echo-paths.js +44 -0
- package/scripts/lib/infra/markdown-store.js +161 -0
- package/scripts/lib/infra/query-log.js +27 -0
- package/scripts/lib/infra/read-stdin.js +11 -0
- package/scripts/lib/infra/workspace.js +93 -0
- package/scripts/lib/interfaces/mcp/server.js +151 -0
- package/scripts/lib/interfaces/mcp/tools.js +152 -0
- package/scripts/lib/mcp-server.js +3 -0
- package/scripts/lib/usecases/aggregate-all-projects.js +45 -0
- package/scripts/lib/usecases/convert-buffer.js +43 -0
- package/scripts/lib/usecases/discover-claude-imports.js +80 -0
- package/scripts/lib/usecases/import-claude-project.js +89 -0
- package/scripts/lib/usecases/init-workspace.js +52 -0
- package/scripts/lib/usecases/install-claude-hook.js +139 -0
- package/scripts/lib/usecases/legacy-candidates.js +134 -0
- package/scripts/lib/usecases/live-session-state.js +109 -0
- package/scripts/lib/usecases/migrate-legacy-buffer.js +209 -0
- package/scripts/lib/usecases/project-registry.js +170 -0
- package/scripts/lib/usecases/query-articles.js +380 -0
- package/scripts/lib/usecases/refresh-serve.js +77 -0
- package/scripts/lib/usecases/run-doctor.js +213 -0
- package/scripts/lib/usecases/run-pipeline.js +104 -0
- package/scripts/lib/usecases/snapshot-manifest.js +48 -0
- package/scripts/lib/usecases/status-collector.js +142 -0
- package/scripts/lib/usecases/strip-comments.js +7 -0
- package/scripts/lib/usecases/write-comment.js +122 -0
- package/scripts/resolve.js +65 -0
- package/scripts/search.js +98 -0
- package/scripts/serve.js +778 -0
- package/scripts/validate.js +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VincentHuang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Echo — 本地优先的 AI 对话知识论坛
|
|
2
|
+
|
|
3
|
+
Echo 把 Claude Code / AI 编程会话捕获成 Markdown 文章,并提供本地网页用于浏览、搜索、打标签和评论。
|
|
4
|
+
|
|
5
|
+
核心原则:**文章正文不可变**。AI 对话一旦转成文章,就作为源记录保存;后续整理通过标签、评论、标注和派生内容完成。
|
|
6
|
+
|
|
7
|
+
## 当前使用方式
|
|
8
|
+
|
|
9
|
+
> [!TIP]
|
|
10
|
+
> **AI 时代的安装方法:**可以将当前页面交给 AI,让它帮你安装和配置 Echo。
|
|
11
|
+
|
|
12
|
+
### 1. 安装开发版 CLI
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd /Users/vincenthuang/myNote/echo-prototype
|
|
16
|
+
npm install
|
|
17
|
+
npm link
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
确认命令可用:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
echoctl --version
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. 在你的项目目录初始化
|
|
27
|
+
|
|
28
|
+
每个要被 Echo 收集的文件夹都需要注册一次。未注册目录里的对话会落到 legacy buffer,不会自动显示在网页“项目”分组里。
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
mkdir -p ~/echo-notes
|
|
32
|
+
cd ~/echo-notes
|
|
33
|
+
|
|
34
|
+
echoctl init
|
|
35
|
+
echoctl init project
|
|
36
|
+
echoctl doctor
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 3. 安装 Claude Code hook
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
echoctl hook install claude --write
|
|
43
|
+
echoctl hook doctor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
之后你在已注册项目目录里和 AI 聊天,hook 会把会话写入:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
~/.echo-workspace/projects/<project-id>/session-buffer/
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 4. 启动本地网页
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
echoctl serve
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`serve` 默认后台运行,并在启动时自动执行一次:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
convert → validate → index → resolve → build docs
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
启动后会显示类似:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
Echo服务在后台运行 / Echo serve started in background
|
|
68
|
+
|
|
69
|
+
Docs / 访问地址 http://127.0.0.1:5173/
|
|
70
|
+
API / 接口地址 http://127.0.0.1:8787/
|
|
71
|
+
State / 状态文件 ~/.echo-workspace/.serve.json
|
|
72
|
+
Log / 日志文件 ~/.echo-workspace/.serve.log
|
|
73
|
+
|
|
74
|
+
echoctl serve # 后台启动 / Start in background
|
|
75
|
+
echoctl serve --foreground # 前台调试 / Run in foreground for debugging
|
|
76
|
+
echoctl stop # 停止服务 / Stop Echo serve
|
|
77
|
+
echoctl capture on/off # 控制 AI 聊天记录收集 / Toggle AI chat logging
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
打开 `Docs / 访问地址` 即可浏览文章。
|
|
81
|
+
|
|
82
|
+
### 5. 日常命令
|
|
83
|
+
|
|
84
|
+
| 命令 | 用途 |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `echoctl serve` | 后台启动本地网页和 API |
|
|
87
|
+
| `echoctl serve --foreground` | 前台启动,方便调试 |
|
|
88
|
+
| `echoctl stop` | 停止后台服务 |
|
|
89
|
+
| `echoctl capture status` | 查看是否正在收集 AI 聊天记录 |
|
|
90
|
+
| `echoctl capture on` | 开启收集 |
|
|
91
|
+
| `echoctl capture off` | 关闭收集 |
|
|
92
|
+
| `echoctl project list` | 查看已注册项目 |
|
|
93
|
+
| `echoctl project find <id>` | 查看项目详情 |
|
|
94
|
+
| `echoctl all` | 手动跑完整管线 |
|
|
95
|
+
| `echoctl search -- --keyword "关键词"` | 搜索文章 |
|
|
96
|
+
| `echoctl tag list` | 查看标签 |
|
|
97
|
+
| `echoctl tag add <article-id> <tag>` | 给文章加标签 |
|
|
98
|
+
| `echoctl tag remove <article-id> <tag>` | 移除标签 |
|
|
99
|
+
|
|
100
|
+
## 重要边界
|
|
101
|
+
|
|
102
|
+
### 空文件夹不会自动变成项目
|
|
103
|
+
|
|
104
|
+
如果你新建一个空文件夹后直接聊天,但没有执行:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
echoctl init project
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
那么会话会降级写入:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
~/.echo-workspace/session-buffer/
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
这个 legacy buffer 不会自动出现在网页项目列表里。正确流程是:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
cd ~/new-project
|
|
120
|
+
echoctl init project
|
|
121
|
+
echoctl serve
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 当前还不是实时刷新
|
|
125
|
+
|
|
126
|
+
`echoctl serve` 会在启动时自动跑一次管线,但当前版本还没有持续监听新的 `session-buffer`。
|
|
127
|
+
|
|
128
|
+
也就是说:
|
|
129
|
+
|
|
130
|
+
| 场景 | 网页是否立刻出现新文章 |
|
|
131
|
+
|---|---|
|
|
132
|
+
| 启动 `serve` 前已经有 buffer | 会显示 |
|
|
133
|
+
| `serve` 已在后台运行,然后继续聊天 | 不会立刻自动出现 |
|
|
134
|
+
| 聊完后重启 `echoctl serve` 或手动 `echoctl all` 后刷新 | 会显示 |
|
|
135
|
+
|
|
136
|
+
后续计划:给 `serve` 增加 watcher,监听新会话并自动重建页面。
|
|
137
|
+
|
|
138
|
+
## 数据目录
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
~/.echo-workspace/
|
|
142
|
+
registry.json
|
|
143
|
+
.serve.json
|
|
144
|
+
.serve.log
|
|
145
|
+
session-buffer/ # 未注册目录的 legacy fallback
|
|
146
|
+
projects/
|
|
147
|
+
<project-id>/
|
|
148
|
+
session-buffer/
|
|
149
|
+
articles/
|
|
150
|
+
comments/
|
|
151
|
+
index/
|
|
152
|
+
.site/ # echoctl serve 生成的 VitePress 站点
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 开发命令
|
|
156
|
+
|
|
157
|
+
这些命令主要给 Echo 项目开发者使用,普通使用优先用 `echoctl`。
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
cd /Users/vincenthuang/myNote/echo-prototype
|
|
161
|
+
|
|
162
|
+
npm test
|
|
163
|
+
npm run all
|
|
164
|
+
npm run docs:generate
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 更多文档
|
|
168
|
+
|
|
169
|
+
- [USAGE_GUIDE_V3.md](USAGE_GUIDE_V3.md) — 多项目模型和手动验证流程
|
|
170
|
+
- [ENGINEERING_BOUNDARIES.md](ENGINEERING_BOUNDARIES.md) — 工程边界和路径模型
|
|
171
|
+
- [ECHO_STATUS.md](ECHO_STATUS.md) — 当前进度和已知问题
|
package/bin/echoctl.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "echoctl",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Local-first AI conversation knowledge forum — capture, search, and annotate Claude Code sessions as Markdown articles with MCP server support",
|
|
6
|
+
"keywords": ["echo", "knowledge-forum", "ai-conversations", "markdown", "local-first", "mcp", "claude-code", "cli"],
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.0.0"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/daxiguaguagua-hash/echo.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/daxiguaguagua-hash/echo/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/daxiguaguagua-hash/echo#readme",
|
|
18
|
+
"files": [
|
|
19
|
+
"bin/",
|
|
20
|
+
"scripts/",
|
|
21
|
+
"docs/.vitepress/",
|
|
22
|
+
"docs/index.md",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"prepublishOnly": "npm test && npm run all",
|
|
28
|
+
"test": "node --test",
|
|
29
|
+
"verify": "npm run test && npm run validate && npm run resolve",
|
|
30
|
+
"validate": "node scripts/validate.js",
|
|
31
|
+
"index": "node scripts/index.js",
|
|
32
|
+
"resolve": "node scripts/resolve.js",
|
|
33
|
+
"convert": "node scripts/convert.js",
|
|
34
|
+
"annotate": "node scripts/annotate.js",
|
|
35
|
+
"import": "node scripts/import-sessions.js",
|
|
36
|
+
"search": "node scripts/search.js",
|
|
37
|
+
"mcp": "node bin/echoctl.js mcp",
|
|
38
|
+
"all": "node bin/echoctl.js all",
|
|
39
|
+
"docs:generate": "node scripts/build-docs.js",
|
|
40
|
+
"docs:dev": "npm run docs:generate && vitepress dev ~/.echo-workspace/.site",
|
|
41
|
+
"docs:build": "npm run docs:generate && vitepress build ~/.echo-workspace/.site",
|
|
42
|
+
"docs:preview": "npm run docs:generate && vitepress preview ~/.echo-workspace/.site",
|
|
43
|
+
"serve": "node bin/echoctl.js serve",
|
|
44
|
+
"prepare": "mkdir -p bin && printf '#!/usr/bin/env node\\nrequire(\"../scripts/cli/echoctl.js\");\\n' > bin/echoctl.js && chmod +x bin/echoctl.js"
|
|
45
|
+
},
|
|
46
|
+
"bin": {
|
|
47
|
+
"echoctl": "./bin/echoctl.js",
|
|
48
|
+
"echo-mcp": "./bin/echoctl.js"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"gray-matter": "^4.0.3",
|
|
52
|
+
"vitepress": "^1.6.4"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { resolveDataDirs } = require("./lib/infra/echo-paths");
|
|
4
|
+
const store = require("./lib/infra/markdown-store");
|
|
5
|
+
const { writeComment } = require("./lib/usecases/write-comment");
|
|
6
|
+
|
|
7
|
+
function runAnnotate(opts = {}) {
|
|
8
|
+
const dirs = opts.dirs || resolveDataDirs();
|
|
9
|
+
const evOf = opts.evolutionOf ? opts.evolutionOf.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
10
|
+
|
|
11
|
+
const result = writeComment({
|
|
12
|
+
articleId: opts.articleId,
|
|
13
|
+
quote: opts.quote,
|
|
14
|
+
comment: opts.commentText,
|
|
15
|
+
author: opts.author,
|
|
16
|
+
evolutionKind: opts.evolutionKind || "null",
|
|
17
|
+
evolutionOf: evOf,
|
|
18
|
+
status: opts.status || "open",
|
|
19
|
+
dirs,
|
|
20
|
+
store,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
console.log(`Created: comments/${result.id}.md`);
|
|
24
|
+
console.log(` article: ${opts.articleId}`);
|
|
25
|
+
if (opts.quote) console.log(` quote: ${opts.quote.slice(0, 60)}${opts.quote.length > 60 ? "..." : ""}`);
|
|
26
|
+
if (evOf.length > 0) console.log(` replies: ${evOf.join(", ")}`);
|
|
27
|
+
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (require.main === module) {
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
function flag(name) {
|
|
34
|
+
const i = args.indexOf(`--${name}`);
|
|
35
|
+
return i >= 0 ? args[i + 1] : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const articleId = flag("article");
|
|
39
|
+
const quote = flag("quote");
|
|
40
|
+
const commentText = flag("comment");
|
|
41
|
+
const author = flag("author") || "vincent";
|
|
42
|
+
const evolutionKind = flag("evolution-kind") || "null";
|
|
43
|
+
const evolutionOf = flag("evolution-of");
|
|
44
|
+
const status = flag("status") || "open";
|
|
45
|
+
|
|
46
|
+
if (!articleId || !quote || !commentText) {
|
|
47
|
+
console.log("Usage: node scripts/annotate.js --article <id> --quote \"<text>\" --comment \"<text>\" [--author <name>] [--evolution-of <id>] [--evolution-kind <kind>]");
|
|
48
|
+
console.log("");
|
|
49
|
+
console.log(" --article Article ID (from frontmatter)");
|
|
50
|
+
console.log(" --quote Exact text to annotate");
|
|
51
|
+
console.log(" --comment Your comment text");
|
|
52
|
+
console.log(" --author Default: vincent");
|
|
53
|
+
console.log(" --evolution-of Comma-separated annotation IDs this replies to");
|
|
54
|
+
console.log(" --evolution-kind refines | contradicts | expands | supersedes | null");
|
|
55
|
+
console.log(" --status Default: open");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
runAnnotate({ articleId, quote, commentText, author, evolutionKind, evolutionOf, status });
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(`Error: ${err.message}`);
|
|
63
|
+
if (err.availableArticles) {
|
|
64
|
+
console.log("Available articles:");
|
|
65
|
+
for (const a of err.availableArticles) {
|
|
66
|
+
console.log(` ${a.id} (${a.relPath})`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { runAnnotate };
|