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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/bin/echoctl.js +2 -0
  4. package/package.json +56 -0
  5. package/scripts/annotate.js +73 -0
  6. package/scripts/build-docs.js +805 -0
  7. package/scripts/cli/commands/capture.js +20 -0
  8. package/scripts/cli/commands/constants.js +70 -0
  9. package/scripts/cli/commands/doctor.js +10 -0
  10. package/scripts/cli/commands/helpers.js +27 -0
  11. package/scripts/cli/commands/hook.js +48 -0
  12. package/scripts/cli/commands/import_cmd.js +184 -0
  13. package/scripts/cli/commands/init.js +45 -0
  14. package/scripts/cli/commands/mcp.js +16 -0
  15. package/scripts/cli/commands/migrate.js +65 -0
  16. package/scripts/cli/commands/pipeline.js +26 -0
  17. package/scripts/cli/commands/project.js +35 -0
  18. package/scripts/cli/commands/refresh.js +14 -0
  19. package/scripts/cli/commands/search.js +28 -0
  20. package/scripts/cli/commands/serve.js +73 -0
  21. package/scripts/cli/commands/status.js +11 -0
  22. package/scripts/cli/commands/stop.js +136 -0
  23. package/scripts/cli/commands/tag.js +89 -0
  24. package/scripts/cli/echoctl.js +44 -0
  25. package/scripts/convert.js +55 -0
  26. package/scripts/import-sessions.js +213 -0
  27. package/scripts/index.js +92 -0
  28. package/scripts/lib/cli/names.js +33 -0
  29. package/scripts/lib/domain/anchor.js +78 -0
  30. package/scripts/lib/domain/echo-format.js +265 -0
  31. package/scripts/lib/domain/errors.js +8 -0
  32. package/scripts/lib/domain/validation.js +126 -0
  33. package/scripts/lib/hooks/capture.js +401 -0
  34. package/scripts/lib/hooks/status.js +78 -0
  35. package/scripts/lib/i18n/format.js +183 -0
  36. package/scripts/lib/i18n/messages/en.js +41 -0
  37. package/scripts/lib/i18n/messages/zh-CN.js +40 -0
  38. package/scripts/lib/import/manifest.js +87 -0
  39. package/scripts/lib/import/providers/claude-code.js +272 -0
  40. package/scripts/lib/import/scanner.js +128 -0
  41. package/scripts/lib/infra/config.js +36 -0
  42. package/scripts/lib/infra/echo-paths.js +44 -0
  43. package/scripts/lib/infra/markdown-store.js +161 -0
  44. package/scripts/lib/infra/query-log.js +27 -0
  45. package/scripts/lib/infra/read-stdin.js +11 -0
  46. package/scripts/lib/infra/workspace.js +93 -0
  47. package/scripts/lib/interfaces/mcp/server.js +151 -0
  48. package/scripts/lib/interfaces/mcp/tools.js +152 -0
  49. package/scripts/lib/mcp-server.js +3 -0
  50. package/scripts/lib/usecases/aggregate-all-projects.js +45 -0
  51. package/scripts/lib/usecases/convert-buffer.js +43 -0
  52. package/scripts/lib/usecases/discover-claude-imports.js +80 -0
  53. package/scripts/lib/usecases/import-claude-project.js +89 -0
  54. package/scripts/lib/usecases/init-workspace.js +52 -0
  55. package/scripts/lib/usecases/install-claude-hook.js +139 -0
  56. package/scripts/lib/usecases/legacy-candidates.js +134 -0
  57. package/scripts/lib/usecases/live-session-state.js +109 -0
  58. package/scripts/lib/usecases/migrate-legacy-buffer.js +209 -0
  59. package/scripts/lib/usecases/project-registry.js +170 -0
  60. package/scripts/lib/usecases/query-articles.js +380 -0
  61. package/scripts/lib/usecases/refresh-serve.js +77 -0
  62. package/scripts/lib/usecases/run-doctor.js +213 -0
  63. package/scripts/lib/usecases/run-pipeline.js +104 -0
  64. package/scripts/lib/usecases/snapshot-manifest.js +48 -0
  65. package/scripts/lib/usecases/status-collector.js +142 -0
  66. package/scripts/lib/usecases/strip-comments.js +7 -0
  67. package/scripts/lib/usecases/write-comment.js +122 -0
  68. package/scripts/resolve.js +65 -0
  69. package/scripts/search.js +98 -0
  70. package/scripts/serve.js +778 -0
  71. 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("../scripts/cli/echoctl.js");
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 };