samectx 1.0.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/.env.example ADDED
@@ -0,0 +1,15 @@
1
+ # GitHub Token Configuration
2
+ # Generate a token at: https://github.com/settings/tokens
3
+ # Required scope: gist
4
+
5
+ GITHUB_TOKEN=your_github_token_here
6
+
7
+ # Local Notes Directory (Optional)
8
+ # Specify a custom directory for storing notes locally
9
+ # Can be absolute path or relative path (relative to skill directory)
10
+ # Examples:
11
+ # LOCAL_NOTES_DIR=/Users/ethanhuang/Documents/Notes
12
+ # LOCAL_NOTES_DIR=./notes
13
+ # LOCAL_NOTES_DIR=../../data/notes
14
+
15
+ # LOCAL_NOTES_DIR=./notes
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # samectx
2
+
3
+ Context sync tool - 整理对话上下文并同步到 GitHub Gist
4
+
5
+ 用于多台设备使用 TRAE、Cursor、Claude Code 等 AI 工具时,整理对话上下文并同步到工程本地文件夹和 GitHub Gist 中。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ # 全局安装
11
+ npm install -g samectx
12
+
13
+ # 或使用 npx(无需安装)
14
+ npx samectx --help
15
+ ```
16
+
17
+ ## 快速开始
18
+
19
+ ```bash
20
+ # 1. 配置 GitHub Token
21
+ samectx config --token ghp_your_token_here
22
+
23
+ # 2. 同步上下文
24
+ samectx sync
25
+
26
+ # 3. 查看笔记列表
27
+ samectx list
28
+ ```
29
+
30
+ ## 命令
31
+
32
+ ### `sync` (别名: `s`) - 同步上下文
33
+
34
+ ```bash
35
+ samectx sync [options]
36
+ # 或简写
37
+ samectx s
38
+
39
+ 选项:
40
+ -p, --project <name> 指定项目名称(默认从当前目录提取)
41
+ -d, --dir <path> 指定笔记存储目录
42
+ ```
43
+
44
+ ### `list` (别名: `l`) - 列出笔记
45
+
46
+ ```bash
47
+ samectx list [options]
48
+ # 或简写
49
+ samectx l
50
+
51
+ 选项:
52
+ -p, --project <name> 筛选指定项目的笔记
53
+ -l, --local 只显示本地笔记
54
+ ```
55
+
56
+ ### `config` (别名: `c`) - 配置
57
+
58
+ ```bash
59
+ samectx config [options]
60
+ # 或简写
61
+ samectx c
62
+
63
+ 选项:
64
+ -t, --token <token> 设置 GitHub Token
65
+ -s, --show 显示当前配置
66
+ ```
67
+
68
+ ### `init` (别名: `i`) - 初始化项目
69
+
70
+ ```bash
71
+ samectx init
72
+ # 或简写
73
+ samectx i
74
+ ```
75
+
76
+ ## 获取 GitHub Token
77
+
78
+ 1. 访问 https://github.com/settings/tokens/new
79
+ 2. Note: `samectx`
80
+ 3. Expiration: 选择 `Custom` → 设置 1 年后
81
+ 4. Select scopes: ✅ `gist`
82
+ 5. 点击 Generate token,复制保存
83
+
84
+ ## 目录结构
85
+
86
+ ```
87
+ ~/.samectx/ # 全局配置目录
88
+ ├── config.json # 配置文件(Token)
89
+ └── gist-mapping.json # 项目-Gist 映射
90
+
91
+ your-project/
92
+ └── samectx-notes/ # 本地笔记
93
+ ├── context_2026-03-20.json
94
+ └── ...
95
+ ```
96
+
97
+ ## 跨工具支持
98
+
99
+ | 工具 | 使用方式 |
100
+ |------|---------|
101
+ | TRAE CN | `npx samectx sync` |
102
+ | Cursor | `npx samectx sync` |
103
+ | Claude Code | `npx samectx sync` |
104
+ | 终端 | `samectx sync` |
105
+
106
+ ## 故障排除
107
+
108
+ | 问题 | 解决方案 |
109
+ |------|---------|
110
+ | Token 未配置 | 运行 `samectx config --token <your-token>` |
111
+ | 笔记位置错误 | 检查项目根目录下的 `samectx-notes/` |
112
+ | Gist 上传失败 | 检查 Token 权限和有效期;本地笔记仍可用 |
113
+
114
+ ---
115
+
116
+ **GitHub**: https://github.com/ethanhuangcst/gctx
package/bin/samectx ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const pkg = require('../package.json');
5
+ const cli = require('../src/cli');
6
+
7
+ program
8
+ .name('samectx')
9
+ .description('Context sync tool - 整理对话上下文并同步到 GitHub Gist')
10
+ .version(pkg.version);
11
+
12
+ program
13
+ .command('sync')
14
+ .alias('s')
15
+ .description('同步当前项目的对话上下文到本地和 GitHub Gist')
16
+ .option('-p, --project <name>', '指定项目名称(默认从当前目录提取)')
17
+ .option('-d, --dir <path>', '指定笔记存储目录')
18
+ .action(async (options) => {
19
+ await cli.sync(options);
20
+ });
21
+
22
+ program
23
+ .command('list')
24
+ .alias('l')
25
+ .description('列出所有笔记')
26
+ .option('-p, --project <name>', '筛选指定项目的笔记')
27
+ .option('-l, --local', '只显示本地笔记')
28
+ .action(async (options) => {
29
+ await cli.list(options);
30
+ });
31
+
32
+ program
33
+ .command('config')
34
+ .alias('c')
35
+ .description('配置 GitHub Token')
36
+ .option('-t, --token <token>', '设置 GitHub Token')
37
+ .option('-s, --show', '显示当前配置')
38
+ .action(async (options) => {
39
+ await cli.config(options);
40
+ });
41
+
42
+ program
43
+ .command('init')
44
+ .alias('i')
45
+ .description('初始化项目配置')
46
+ .action(async () => {
47
+ await cli.init();
48
+ });
49
+
50
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "samectx",
3
+ "version": "1.0.0",
4
+ "description": "Context sync tool - 整理对话上下文并同步到 GitHub Gist",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "samectx": "./bin/samectx"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ ".env.example"
13
+ ],
14
+ "scripts": {
15
+ "test": "echo \"Error: no test specified\" && exit 1"
16
+ },
17
+ "keywords": [
18
+ "context",
19
+ "gist",
20
+ "sync",
21
+ "cli",
22
+ "trae"
23
+ ],
24
+ "author": "",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "chalk": "^4.1.2",
28
+ "commander": "^11.0.0",
29
+ "node-fetch": "^2.7.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=14.0.0"
33
+ }
34
+ }
package/src/cli.js ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * CLI 命令实现
3
+ */
4
+
5
+ const chalk = require('chalk');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const core = require('./index');
9
+
10
+ async function sync(options) {
11
+ console.log(chalk.blue('📦 正在同步上下文...'));
12
+
13
+ const result = await core.sync(options);
14
+
15
+ if (result.success) {
16
+ console.log(chalk.green('✅ 同步成功!'));
17
+ console.log(` 项目名称: ${result.projectName}`);
18
+ console.log(` 本地路径: ${result.localPath}`);
19
+
20
+ if (result.gistUrl) {
21
+ console.log(` Gist URL: ${result.gistUrl}`);
22
+ } else if (result.gistError) {
23
+ console.log(chalk.yellow(` ⚠️ Gist 同步失败: ${result.gistError}`));
24
+ }
25
+
26
+ if (result.analysis) {
27
+ console.log(` 分析: ${result.analysis.taskCount} 个任务, ${result.analysis.keyPointCount} 个关键点, ${result.analysis.decisionCount} 个决策`);
28
+ }
29
+ } else {
30
+ console.log(chalk.red(`❌ 同步失败: ${result.error}`));
31
+ process.exit(1);
32
+ }
33
+ }
34
+
35
+ async function list(options) {
36
+ console.log(chalk.blue('📋 笔记列表:\n'));
37
+
38
+ const notes = core.listNotes(options);
39
+
40
+ if (notes.length === 0) {
41
+ console.log(chalk.yellow(' 没有找到笔记'));
42
+ return;
43
+ }
44
+
45
+ notes.forEach((note, index) => {
46
+ console.log(`${index + 1}. ${chalk.cyan(note.fileName)}`);
47
+ console.log(` 项目: ${note.projectName}`);
48
+ console.log(` 时间: ${note.timestamp || '未知'}`);
49
+ if (note.summary) {
50
+ console.log(` 摘要: ${note.summary}`);
51
+ }
52
+ console.log('');
53
+ });
54
+
55
+ console.log(chalk.gray(`共 ${notes.length} 条笔记`));
56
+ }
57
+
58
+ async function config(options) {
59
+ if (options.show) {
60
+ const currentConfig = core.loadConfig();
61
+ console.log(chalk.blue('📋 当前配置:\n'));
62
+ console.log(` 配置目录: ${core.CONFIG_DIR}`);
63
+ console.log(` GitHub Token: ${currentConfig.githubToken ? '已配置' : chalk.red('未配置')}`);
64
+ return;
65
+ }
66
+
67
+ if (options.token) {
68
+ const currentConfig = core.loadConfig();
69
+ currentConfig.githubToken = options.token;
70
+ core.saveConfig(currentConfig);
71
+ console.log(chalk.green('✅ GitHub Token 已保存'));
72
+ console.log(` 配置文件: ${core.CONFIG_FILE}`);
73
+ return;
74
+ }
75
+
76
+ console.log(chalk.yellow('请指定配置选项:'));
77
+ console.log(' --token <token> 设置 GitHub Token');
78
+ console.log(' --show 显示当前配置');
79
+ }
80
+
81
+ async function init() {
82
+ console.log(chalk.blue('🚀 初始化项目配置...\n'));
83
+
84
+ const notesDir = core.getProjectNotesDir();
85
+ console.log(` 笔记目录: ${notesDir}`);
86
+
87
+ if (!fs.existsSync(notesDir)) {
88
+ fs.mkdirSync(notesDir, { recursive: true });
89
+ console.log(chalk.green(' ✅ 已创建笔记目录'));
90
+ } else {
91
+ console.log(chalk.gray(' 笔记目录已存在'));
92
+ }
93
+
94
+ const envExample = path.join(process.cwd(), '.env.example');
95
+ if (!fs.existsSync(envExample)) {
96
+ fs.writeFileSync(envExample, `# GitHub Token Configuration
97
+ GITHUB_TOKEN=your_github_token_here
98
+ `);
99
+ console.log(chalk.green(' ✅ 已创建 .env.example'));
100
+ }
101
+
102
+ console.log(chalk.green('\n✅ 初始化完成!'));
103
+ console.log(chalk.yellow('\n下一步:'));
104
+ console.log(' 1. 获取 GitHub Token: https://github.com/settings/tokens/new');
105
+ console.log(' 2. 配置 Token: samectx config --token <your-token>');
106
+ console.log(' 3. 同步上下文: samectx sync');
107
+ }
108
+
109
+ module.exports = {
110
+ sync,
111
+ list,
112
+ config,
113
+ init
114
+ };
package/src/index.js ADDED
@@ -0,0 +1,406 @@
1
+ /**
2
+ * gctx - Context sync tool
3
+ * 整理对话上下文并同步到 GitHub Gist
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ const CONFIG_DIR = path.join(os.homedir(), '.samectx');
11
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
12
+ const GIST_MAPPING_FILE = path.join(CONFIG_DIR, 'gist-mapping.json');
13
+
14
+ function ensureConfigDir() {
15
+ if (!fs.existsSync(CONFIG_DIR)) {
16
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
17
+ }
18
+ }
19
+
20
+ function loadConfig() {
21
+ ensureConfigDir();
22
+ if (fs.existsSync(CONFIG_FILE)) {
23
+ try {
24
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
25
+ } catch (e) {
26
+ return {};
27
+ }
28
+ }
29
+ return {};
30
+ }
31
+
32
+ function saveConfig(config) {
33
+ ensureConfigDir();
34
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
35
+ }
36
+
37
+ function getGistMapping() {
38
+ ensureConfigDir();
39
+ if (fs.existsSync(GIST_MAPPING_FILE)) {
40
+ try {
41
+ return JSON.parse(fs.readFileSync(GIST_MAPPING_FILE, 'utf8'));
42
+ } catch (e) {
43
+ return {};
44
+ }
45
+ }
46
+ return {};
47
+ }
48
+
49
+ function saveGistMapping(mapping) {
50
+ ensureConfigDir();
51
+ fs.writeFileSync(GIST_MAPPING_FILE, JSON.stringify(mapping, null, 2));
52
+ }
53
+
54
+ function extractProjectName(customName) {
55
+ if (customName) {
56
+ return customName.replace(/[^a-zA-Z0-9_-]/g, '_');
57
+ }
58
+ const cwd = process.cwd();
59
+ let projectName = path.basename(cwd);
60
+ return projectName.replace(/[^a-zA-Z0-9_-]/g, '_');
61
+ }
62
+
63
+ function getProjectNotesDir(customDir) {
64
+ if (customDir) {
65
+ if (path.isAbsolute(customDir)) {
66
+ return customDir;
67
+ }
68
+ return path.join(process.cwd(), customDir);
69
+ }
70
+
71
+ if (process.env.LOCAL_NOTES_DIR) {
72
+ const notesDir = process.env.LOCAL_NOTES_DIR;
73
+ if (path.isAbsolute(notesDir)) {
74
+ return notesDir;
75
+ }
76
+ return path.join(process.cwd(), notesDir);
77
+ }
78
+
79
+ return path.join(process.cwd(), 'samectx-notes');
80
+ }
81
+
82
+ function analyzeContext(conversationHistory) {
83
+ const keyPoints = [];
84
+ const tasks = [];
85
+ const decisions = [];
86
+
87
+ try {
88
+ const jsonData = JSON.parse(conversationHistory);
89
+
90
+ if (jsonData.completedTasks) {
91
+ jsonData.completedTasks.forEach(task => {
92
+ if (typeof task === 'string') tasks.push(task);
93
+ });
94
+ }
95
+
96
+ if (jsonData.pendingTasks || jsonData.tasks) {
97
+ const pendingTasks = jsonData.pendingTasks || jsonData.tasks;
98
+ pendingTasks.forEach(task => {
99
+ if (typeof task === 'string') tasks.push(task);
100
+ });
101
+ }
102
+
103
+ if (jsonData.keyDecisions || jsonData.decisions) {
104
+ const decs = jsonData.keyDecisions || jsonData.decisions;
105
+ decs.forEach(dec => {
106
+ if (typeof dec === 'string') decisions.push(dec);
107
+ });
108
+ }
109
+
110
+ if (jsonData.importantNotes || jsonData.keyPoints) {
111
+ const notes = jsonData.importantNotes || jsonData.keyPoints;
112
+ notes.forEach(note => {
113
+ if (typeof note === 'string') keyPoints.push(note);
114
+ });
115
+ }
116
+
117
+ if (tasks.length > 0 || decisions.length > 0 || keyPoints.length > 0) {
118
+ const summary = `对话包含 ${tasks.length} 个任务、${keyPoints.length} 个关键点和 ${decisions.length} 个决策`;
119
+
120
+ return {
121
+ summary,
122
+ keyPoints,
123
+ tasks,
124
+ decisions,
125
+ rawContent: conversationHistory,
126
+ timestamp: new Date().toISOString(),
127
+ conversationLength: JSON.stringify(jsonData).split('\n').length,
128
+ metadata: {
129
+ version: '1.0.0',
130
+ tool: 'samectx',
131
+ format: 'json'
132
+ }
133
+ };
134
+ }
135
+ } catch (e) {
136
+ }
137
+
138
+ const lines = conversationHistory.split('\n');
139
+ let currentSection = '';
140
+
141
+ lines.forEach(line => {
142
+ const trimmedLine = line.trim();
143
+
144
+ if (trimmedLine.startsWith('### ')) {
145
+ currentSection = trimmedLine.replace('### ', '');
146
+ return;
147
+ }
148
+
149
+ if (trimmedLine.match(/^\d+\./) ||
150
+ trimmedLine.startsWith('- ') ||
151
+ trimmedLine.includes('任务') ||
152
+ trimmedLine.includes('需要') ||
153
+ trimmedLine.includes('要') ||
154
+ trimmedLine.includes('计划') ||
155
+ trimmedLine.includes('完成')) {
156
+ if (trimmedLine.length > 5 && !trimmedLine.startsWith('###')) {
157
+ tasks.push(trimmedLine);
158
+ }
159
+ }
160
+
161
+ if (trimmedLine.includes('重要') || trimmedLine.includes('关键') ||
162
+ trimmedLine.includes('注意') || trimmedLine.includes('记住') ||
163
+ (currentSection && currentSection.includes('关键'))) {
164
+ if (trimmedLine.length > 5) {
165
+ keyPoints.push(trimmedLine);
166
+ }
167
+ }
168
+
169
+ if (trimmedLine.includes('决定') || trimmedLine.includes('确定') ||
170
+ trimmedLine.includes('选择') || trimmedLine.includes('采用') ||
171
+ trimmedLine.startsWith('- ') && currentSection && currentSection.includes('决策')) {
172
+ if (trimmedLine.length > 5) {
173
+ decisions.push(trimmedLine);
174
+ }
175
+ }
176
+ });
177
+
178
+ const summary = `对话包含 ${tasks.length} 个任务、${keyPoints.length} 个关键点和 ${decisions.length} 个决策`;
179
+
180
+ return {
181
+ summary,
182
+ keyPoints,
183
+ tasks,
184
+ decisions,
185
+ rawContent: conversationHistory,
186
+ timestamp: new Date().toISOString(),
187
+ conversationLength: lines.length,
188
+ metadata: {
189
+ version: '1.0.0',
190
+ tool: 'trae-context-gist',
191
+ format: 'text'
192
+ }
193
+ };
194
+ }
195
+
196
+ function saveNoteToLocal(content, fileName, notesDir) {
197
+ if (!fs.existsSync(notesDir)) {
198
+ fs.mkdirSync(notesDir, { recursive: true });
199
+ }
200
+
201
+ const notePath = path.join(notesDir, fileName);
202
+ fs.writeFileSync(notePath, JSON.stringify(content, null, 2));
203
+
204
+ return {
205
+ fileName,
206
+ localPath: notePath,
207
+ savedAt: new Date().toISOString()
208
+ };
209
+ }
210
+
211
+ const MAX_GIST_FILES = 50;
212
+
213
+ async function uploadToGist(content, fileName, projectName, token) {
214
+ let fetchFn;
215
+ if (typeof fetch !== 'undefined') {
216
+ fetchFn = fetch;
217
+ } else {
218
+ try {
219
+ fetchFn = require('node-fetch');
220
+ } catch (e) {
221
+ throw new Error('无法找到 fetch 函数,请安装 node-fetch: npm install node-fetch');
222
+ }
223
+ }
224
+
225
+ const description = `TRAE CN - ${projectName}`;
226
+ const mapping = getGistMapping();
227
+ const existingGistId = mapping[projectName];
228
+
229
+ if (existingGistId) {
230
+ try {
231
+ const getResponse = await fetchFn(`https://api.github.com/gists/${existingGistId}`, {
232
+ headers: {
233
+ 'Authorization': `token ${token}`,
234
+ 'User-Agent': 'TRAE-CN-ContextManager'
235
+ }
236
+ });
237
+
238
+ if (getResponse.ok) {
239
+ const existingGist = await getResponse.json();
240
+ const existingFiles = Object.keys(existingGist.files || {});
241
+
242
+ const files = {};
243
+
244
+ if (existingFiles.length >= MAX_GIST_FILES) {
245
+ const filesToDelete = existingFiles.slice(0, existingFiles.length - MAX_GIST_FILES + 1);
246
+ filesToDelete.forEach(f => {
247
+ files[f] = null;
248
+ });
249
+ }
250
+
251
+ files[fileName] = {
252
+ content: JSON.stringify(content, null, 2)
253
+ };
254
+
255
+ const updateResponse = await fetchFn(`https://api.github.com/gists/${existingGistId}`, {
256
+ method: 'PATCH',
257
+ headers: {
258
+ 'Authorization': `token ${token}`,
259
+ 'Content-Type': 'application/json',
260
+ 'User-Agent': 'TRAE-CN-ContextManager'
261
+ },
262
+ body: JSON.stringify({
263
+ description: description,
264
+ files: files
265
+ })
266
+ });
267
+
268
+ if (updateResponse.ok) {
269
+ return await updateResponse.json();
270
+ }
271
+ }
272
+ } catch (e) {
273
+ console.warn(`更新现有 Gist 失败,将创建新 Gist: ${e.message}`);
274
+ }
275
+ }
276
+
277
+ const files = {};
278
+ files[fileName] = {
279
+ content: JSON.stringify(content, null, 2)
280
+ };
281
+
282
+ const response = await fetchFn('https://api.github.com/gists', {
283
+ method: 'POST',
284
+ headers: {
285
+ 'Authorization': `token ${token}`,
286
+ 'Content-Type': 'application/json',
287
+ 'User-Agent': 'TRAE-CN-ContextManager'
288
+ },
289
+ body: JSON.stringify({
290
+ description: description,
291
+ public: false,
292
+ files: files
293
+ })
294
+ });
295
+
296
+ if (!response.ok) {
297
+ const errorText = await response.text();
298
+ throw new Error(`上传失败 (${response.status}): ${errorText}`);
299
+ }
300
+
301
+ const gistInfo = await response.json();
302
+ mapping[projectName] = gistInfo.id;
303
+ saveGistMapping(mapping);
304
+
305
+ return gistInfo;
306
+ }
307
+
308
+ async function sync(options = {}) {
309
+ const config = loadConfig();
310
+ const token = config.githubToken;
311
+
312
+ if (!token) {
313
+ return {
314
+ success: false,
315
+ error: 'GitHub Token 未配置,请运行: trae-context-gist config --token <your-token>'
316
+ };
317
+ }
318
+
319
+ const projectName = extractProjectName(options.project);
320
+ const notesDir = getProjectNotesDir(options.dir);
321
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
322
+ const fileName = `context_${timestamp}.json`;
323
+
324
+ let content;
325
+ if (options.content) {
326
+ content = analyzeContext(options.content);
327
+ } else {
328
+ content = {
329
+ projectName,
330
+ timestamp: new Date().toISOString(),
331
+ summary: '手动同步',
332
+ metadata: {
333
+ version: '1.0.0',
334
+ tool: 'trae-context-gist'
335
+ }
336
+ };
337
+ }
338
+
339
+ const localInfo = saveNoteToLocal(content, fileName, notesDir);
340
+
341
+ let gistInfo = null;
342
+ let gistError = null;
343
+
344
+ try {
345
+ gistInfo = await uploadToGist(content, fileName, projectName, token);
346
+ } catch (error) {
347
+ gistError = error.message;
348
+ }
349
+
350
+ return {
351
+ success: true,
352
+ projectName,
353
+ localPath: localInfo.localPath,
354
+ gistUrl: gistInfo ? gistInfo.html_url : null,
355
+ gistError,
356
+ analysis: {
357
+ taskCount: content.tasks ? content.tasks.length : 0,
358
+ keyPointCount: content.keyPoints ? content.keyPoints.length : 0,
359
+ decisionCount: content.decisions ? content.decisions.length : 0
360
+ }
361
+ };
362
+ }
363
+
364
+ function listNotes(options = {}) {
365
+ const notesDir = getProjectNotesDir(options.dir);
366
+
367
+ if (!fs.existsSync(notesDir)) {
368
+ return [];
369
+ }
370
+
371
+ const files = fs.readdirSync(notesDir).filter(f => f.endsWith('.json'));
372
+ const notes = [];
373
+
374
+ files.forEach(file => {
375
+ const filePath = path.join(notesDir, file);
376
+ try {
377
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
378
+ notes.push({
379
+ fileName: file,
380
+ localPath: filePath,
381
+ projectName: content.projectName || 'unknown',
382
+ timestamp: content.timestamp,
383
+ summary: content.summary
384
+ });
385
+ } catch (e) {
386
+ }
387
+ });
388
+
389
+ if (options.project) {
390
+ return notes.filter(n => n.projectName === options.project);
391
+ }
392
+
393
+ return notes.sort((a, b) => (b.timestamp || '').localeCompare(a.timestamp || ''));
394
+ }
395
+
396
+ module.exports = {
397
+ sync,
398
+ listNotes,
399
+ loadConfig,
400
+ saveConfig,
401
+ extractProjectName,
402
+ getProjectNotesDir,
403
+ analyzeContext,
404
+ CONFIG_DIR,
405
+ CONFIG_FILE
406
+ };