openwriter 0.40.0 → 0.40.2
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/dist/client/assets/{index-vmEPerKn.js → index-Dxbv2n2m.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/plugins/github/dist/blog-tools.d.ts +19 -0
- package/dist/plugins/github/dist/blog-tools.js +28 -9
- package/package.json +1 -1
- package/dist/server/blog-routes.js +0 -160
- package/dist/server/git-sync.js +0 -273
- package/dist/server/marks.js +0 -182
- package/dist/server/sync-routes.js +0 -75
package/dist/server/marks.js
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Marks: sidecar JSON storage for inline user feedback.
|
|
3
|
-
* Each document gets a sidecar file at DATA_DIR/_marks/{filename}.json.
|
|
4
|
-
*/
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, unlinkSync, renameSync } from 'fs';
|
|
7
|
-
import { randomUUID } from 'crypto';
|
|
8
|
-
import { getDataDir, ensureDataDir } from './helpers.js';
|
|
9
|
-
function getMarksDir() { return join(getDataDir(), '_marks'); }
|
|
10
|
-
function ensureMarksDir() {
|
|
11
|
-
ensureDataDir();
|
|
12
|
-
if (!existsSync(getMarksDir()))
|
|
13
|
-
mkdirSync(getMarksDir(), { recursive: true });
|
|
14
|
-
}
|
|
15
|
-
function markFilePath(filename) {
|
|
16
|
-
// Sanitize: replace path separators to avoid nested paths
|
|
17
|
-
const safe = filename.replace(/[/\\]/g, '_');
|
|
18
|
-
return join(getMarksDir(), `${safe}.json`);
|
|
19
|
-
}
|
|
20
|
-
function readMarkFile(filename) {
|
|
21
|
-
const path = markFilePath(filename);
|
|
22
|
-
if (!existsSync(path))
|
|
23
|
-
return { marks: [] };
|
|
24
|
-
try {
|
|
25
|
-
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return { marks: [] };
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function writeMarkFile(filename, data) {
|
|
32
|
-
ensureMarksDir();
|
|
33
|
-
const path = markFilePath(filename);
|
|
34
|
-
if (data.marks.length === 0) {
|
|
35
|
-
// Clean up empty sidecar files
|
|
36
|
-
if (existsSync(path))
|
|
37
|
-
unlinkSync(path);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
41
|
-
}
|
|
42
|
-
export function addMark(filename, text, note, nodeId, nodeIds) {
|
|
43
|
-
const data = readMarkFile(filename);
|
|
44
|
-
const mark = {
|
|
45
|
-
id: randomUUID().slice(0, 8),
|
|
46
|
-
text,
|
|
47
|
-
note,
|
|
48
|
-
nodeId,
|
|
49
|
-
...(nodeIds && nodeIds.length > 1 ? { nodeIds } : {}),
|
|
50
|
-
createdAt: new Date().toISOString(),
|
|
51
|
-
};
|
|
52
|
-
data.marks.push(mark);
|
|
53
|
-
writeMarkFile(filename, data);
|
|
54
|
-
return mark;
|
|
55
|
-
}
|
|
56
|
-
export function getMarks(filename) {
|
|
57
|
-
if (filename) {
|
|
58
|
-
const data = readMarkFile(filename);
|
|
59
|
-
if (data.marks.length === 0)
|
|
60
|
-
return {};
|
|
61
|
-
return { [filename]: data.marks };
|
|
62
|
-
}
|
|
63
|
-
// All docs: scan _marks directory
|
|
64
|
-
ensureMarksDir();
|
|
65
|
-
const result = {};
|
|
66
|
-
try {
|
|
67
|
-
const files = readdirSync(getMarksDir());
|
|
68
|
-
for (const file of files) {
|
|
69
|
-
if (!file.endsWith('.json'))
|
|
70
|
-
continue;
|
|
71
|
-
const docFilename = file.replace(/\.json$/, '').replace(/_/g, ' ');
|
|
72
|
-
// Read raw to avoid filename roundtrip issues
|
|
73
|
-
const path = join(getMarksDir(), file);
|
|
74
|
-
try {
|
|
75
|
-
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
76
|
-
if (data.marks.length > 0)
|
|
77
|
-
result[docFilename] = data.marks;
|
|
78
|
-
}
|
|
79
|
-
catch { /* skip corrupt files */ }
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch { /* dir doesn't exist yet */ }
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
export function getMarkCount(filename) {
|
|
86
|
-
return readMarkFile(filename).marks.length;
|
|
87
|
-
}
|
|
88
|
-
/** Count marks across all documents, optionally excluding one filename. */
|
|
89
|
-
export function getGlobalMarkSummary(excludeFilename) {
|
|
90
|
-
ensureMarksDir();
|
|
91
|
-
let totalMarks = 0;
|
|
92
|
-
let docCount = 0;
|
|
93
|
-
try {
|
|
94
|
-
const files = readdirSync(getMarksDir());
|
|
95
|
-
for (const file of files) {
|
|
96
|
-
if (!file.endsWith('.json'))
|
|
97
|
-
continue;
|
|
98
|
-
if (excludeFilename) {
|
|
99
|
-
const safe = excludeFilename.replace(/[/\\]/g, '_');
|
|
100
|
-
if (file === `${safe}.json`)
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
const path = join(getMarksDir(), file);
|
|
104
|
-
try {
|
|
105
|
-
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
106
|
-
if (data.marks.length > 0) {
|
|
107
|
-
totalMarks += data.marks.length;
|
|
108
|
-
docCount++;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
catch { /* skip */ }
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
catch { /* dir doesn't exist */ }
|
|
115
|
-
return { totalMarks, docCount };
|
|
116
|
-
}
|
|
117
|
-
export function editMark(filename, id, note) {
|
|
118
|
-
const data = readMarkFile(filename);
|
|
119
|
-
const mark = data.marks.find((m) => m.id === id);
|
|
120
|
-
if (!mark)
|
|
121
|
-
return null;
|
|
122
|
-
mark.note = note;
|
|
123
|
-
writeMarkFile(filename, data);
|
|
124
|
-
return mark;
|
|
125
|
-
}
|
|
126
|
-
export function resolveMarks(ids) {
|
|
127
|
-
const idSet = new Set(ids);
|
|
128
|
-
const resolved = [];
|
|
129
|
-
ensureMarksDir();
|
|
130
|
-
try {
|
|
131
|
-
const files = readdirSync(getMarksDir());
|
|
132
|
-
for (const file of files) {
|
|
133
|
-
if (!file.endsWith('.json'))
|
|
134
|
-
continue;
|
|
135
|
-
const filePath = join(getMarksDir(), file);
|
|
136
|
-
try {
|
|
137
|
-
const data = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
138
|
-
const before = data.marks.length;
|
|
139
|
-
data.marks = data.marks.filter((m) => {
|
|
140
|
-
if (idSet.has(m.id)) {
|
|
141
|
-
resolved.push(m.id);
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
return true;
|
|
145
|
-
});
|
|
146
|
-
if (data.marks.length !== before) {
|
|
147
|
-
const docFilename = file.replace(/\.json$/, '').replace(/_/g, ' ');
|
|
148
|
-
writeMarkFile(docFilename, data);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
catch { /* skip */ }
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch { /* dir doesn't exist */ }
|
|
155
|
-
return resolved;
|
|
156
|
-
}
|
|
157
|
-
export function pruneStaleMarks(filename, validNodeIds) {
|
|
158
|
-
const data = readMarkFile(filename);
|
|
159
|
-
if (data.marks.length === 0)
|
|
160
|
-
return 0;
|
|
161
|
-
const validSet = new Set(validNodeIds);
|
|
162
|
-
const before = data.marks.length;
|
|
163
|
-
data.marks = data.marks.filter((m) => {
|
|
164
|
-
// Multi-node mark: keep if ANY nodeId is still valid
|
|
165
|
-
if (m.nodeIds && m.nodeIds.length > 0) {
|
|
166
|
-
return m.nodeIds.some((id) => validSet.has(id));
|
|
167
|
-
}
|
|
168
|
-
return validSet.has(m.nodeId);
|
|
169
|
-
});
|
|
170
|
-
const pruned = before - data.marks.length;
|
|
171
|
-
if (pruned > 0)
|
|
172
|
-
writeMarkFile(filename, data);
|
|
173
|
-
return pruned;
|
|
174
|
-
}
|
|
175
|
-
/** Rename a mark sidecar file when a document is renamed. */
|
|
176
|
-
export function renameMark(oldFilename, newFilename) {
|
|
177
|
-
const oldPath = markFilePath(oldFilename);
|
|
178
|
-
if (!existsSync(oldPath))
|
|
179
|
-
return;
|
|
180
|
-
const newPath = markFilePath(newFilename);
|
|
181
|
-
renameSync(oldPath, newPath);
|
|
182
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Express routes for GitHub sync.
|
|
3
|
-
* Mounted in index.ts — follows version-routes.ts pattern.
|
|
4
|
-
*/
|
|
5
|
-
import { Router } from 'express';
|
|
6
|
-
import { getSyncStatus, getCapabilities, getPendingFiles, setupWithGh, setupWithPat, connectExisting, pushSync, } from './git-sync.js';
|
|
7
|
-
export function createSyncRouter(broadcastSyncStatus) {
|
|
8
|
-
const router = Router();
|
|
9
|
-
router.get('/api/sync/status', async (_req, res) => {
|
|
10
|
-
try {
|
|
11
|
-
res.json(await getSyncStatus());
|
|
12
|
-
}
|
|
13
|
-
catch (err) {
|
|
14
|
-
res.status(500).json({ state: 'error', error: err.message });
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
router.get('/api/sync/capabilities', async (_req, res) => {
|
|
18
|
-
try {
|
|
19
|
-
res.json(await getCapabilities());
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
res.status(500).json({ error: err.message });
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
router.get('/api/sync/pending', async (_req, res) => {
|
|
26
|
-
try {
|
|
27
|
-
res.json(await getPendingFiles());
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
res.status(500).json({ error: err.message });
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
router.post('/api/sync/setup', async (req, res) => {
|
|
34
|
-
try {
|
|
35
|
-
const { method, repoName, remoteUrl, pat, isPrivate } = req.body;
|
|
36
|
-
if (method === 'gh') {
|
|
37
|
-
await setupWithGh(repoName || 'openwriter-docs', isPrivate !== false);
|
|
38
|
-
}
|
|
39
|
-
else if (method === 'pat') {
|
|
40
|
-
if (!pat) {
|
|
41
|
-
res.status(400).json({ error: 'PAT is required' });
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
await setupWithPat(pat, repoName || 'openwriter-docs', isPrivate !== false);
|
|
45
|
-
}
|
|
46
|
-
else if (method === 'connect') {
|
|
47
|
-
if (!remoteUrl) {
|
|
48
|
-
res.status(400).json({ error: 'Remote URL is required' });
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
await connectExisting(remoteUrl, pat);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
res.status(400).json({ error: 'Invalid method. Use: gh, pat, or connect' });
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const status = await getSyncStatus();
|
|
58
|
-
broadcastSyncStatus(status);
|
|
59
|
-
res.json({ success: true, status });
|
|
60
|
-
}
|
|
61
|
-
catch (err) {
|
|
62
|
-
res.status(500).json({ error: err.message });
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
router.post('/api/sync/push', async (_req, res) => {
|
|
66
|
-
try {
|
|
67
|
-
const result = await pushSync(broadcastSyncStatus);
|
|
68
|
-
res.json(result);
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
res.status(500).json({ state: 'error', error: err.message });
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
return router;
|
|
75
|
-
}
|