openwriter 0.26.0 → 0.27.1
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-BJMpYpj1.css +1 -0
- package/dist/client/assets/index-DgUPw-v5.js +214 -0
- package/dist/client/index.html +2 -2
- package/dist/plugins/authors-voice/dist/index.d.ts +5 -9
- package/dist/plugins/authors-voice/dist/index.js +17 -130
- package/dist/plugins/authors-voice/package.json +1 -1
- package/dist/plugins/github/dist/blog-tools.d.ts +8 -0
- package/dist/plugins/github/dist/blog-tools.js +792 -0
- package/dist/plugins/github/dist/git-sync.d.ts +36 -0
- package/dist/plugins/github/dist/git-sync.js +276 -0
- package/dist/plugins/github/dist/helpers.d.ts +84 -0
- package/dist/plugins/github/dist/helpers.js +62 -0
- package/dist/plugins/github/dist/index.d.ts +12 -0
- package/dist/plugins/github/dist/index.js +102 -0
- package/dist/plugins/github/package.json +24 -0
- package/dist/server/autoplug-enroll.js +71 -0
- package/dist/server/documents.js +119 -2
- package/dist/server/index.js +49 -13
- package/dist/server/markdown-parse.js +74 -1
- package/dist/server/mcp.js +215 -78
- package/dist/server/pending-metadata.js +65 -0
- package/dist/server/pending-overlay.js +151 -2
- package/dist/server/plugin-manager.js +18 -3
- package/dist/server/state.js +126 -39
- package/dist/server/ws.js +85 -26
- package/package.json +1 -1
- package/skill/SKILL.md +49 -19
- package/dist/client/assets/index-AWIKUHJ_.css +0 -1
- package/dist/client/assets/index-DmHLFNTs.js +0 -212
package/dist/client/index.html
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
12
12
|
<link href="https://fonts.googleapis.com/css2?family=Charter:ital,wght@0,400;0,700;1,400&family=Crimson+Pro:ital,wght@0,300;0,400;0,600;0,700;1,400&family=DM+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=DM+Serif+Display&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600&family=Inter:wght@400;500;600;700&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Literata:ital,opsz,wght@0,7..72,400;0,7..72,600;0,7..72,700;1,7..72,400&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,600;1,6..72,400&family=Playfair+Display:wght@400;600;700;900&family=Source+Serif+4:ital,opsz,wght@0,8..60,400;0,8..60,600;0,8..60,700;1,8..60,400&family=Space+Grotesk:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet" />
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-DgUPw-v5.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BJMpYpj1.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Author's Voice plugin for OpenWriter.
|
|
3
|
-
* Proxies /api/voice/* to the AV backend and adds context
|
|
4
|
-
* for
|
|
5
|
-
*
|
|
3
|
+
* Proxies /api/voice/* to the AV backend and adds editor context-menu items
|
|
4
|
+
* for sub-paragraph text actions (Enhance / Modify / Shrink / Expand / Insert / Fill).
|
|
5
|
+
*
|
|
6
|
+
* Sidebar document transforms (Vary / Shrinkify / Threadify / etc.) live in
|
|
7
|
+
* @openwriter/plugin-publish — they go through the metered platform path.
|
|
6
8
|
*/
|
|
7
9
|
import type { Express } from 'express';
|
|
8
10
|
interface PluginConfigField {
|
|
@@ -22,11 +24,6 @@ interface PluginContextMenuItem {
|
|
|
22
24
|
condition?: 'has-selection' | 'empty-node' | 'always';
|
|
23
25
|
promptForInput?: boolean;
|
|
24
26
|
}
|
|
25
|
-
interface PluginSidebarMenuItem {
|
|
26
|
-
label: string;
|
|
27
|
-
action: string;
|
|
28
|
-
promptForFocus?: boolean;
|
|
29
|
-
}
|
|
30
27
|
interface OpenWriterPlugin {
|
|
31
28
|
name: string;
|
|
32
29
|
version: string;
|
|
@@ -35,7 +32,6 @@ interface OpenWriterPlugin {
|
|
|
35
32
|
configSchema?: Record<string, PluginConfigField>;
|
|
36
33
|
registerRoutes?(ctx: PluginRouteContext): void | Promise<void>;
|
|
37
34
|
contextMenuItems?(): PluginContextMenuItem[];
|
|
38
|
-
sidebarMenuItems?(): PluginSidebarMenuItem[];
|
|
39
35
|
}
|
|
40
36
|
declare const plugin: OpenWriterPlugin;
|
|
41
37
|
export default plugin;
|
|
@@ -1,32 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Author's Voice plugin for OpenWriter.
|
|
3
|
-
* Proxies /api/voice/* to the AV backend and adds context
|
|
4
|
-
* for
|
|
5
|
-
*
|
|
3
|
+
* Proxies /api/voice/* to the AV backend and adds editor context-menu items
|
|
4
|
+
* for sub-paragraph text actions (Enhance / Modify / Shrink / Expand / Insert / Fill).
|
|
5
|
+
*
|
|
6
|
+
* Sidebar document transforms (Vary / Shrinkify / Threadify / etc.) live in
|
|
7
|
+
* @openwriter/plugin-publish — they go through the metered platform path.
|
|
6
8
|
*/
|
|
7
|
-
/** Simple HTML → markdown conversion for document creation */
|
|
8
|
-
function htmlToMarkdown(html) {
|
|
9
|
-
let md = html;
|
|
10
|
-
// <hr> → horizontal rule
|
|
11
|
-
md = md.replace(/<hr\s*\/?>/gi, '\n---\n');
|
|
12
|
-
// <br> → newline
|
|
13
|
-
md = md.replace(/<br\s*\/?>/gi, '\n');
|
|
14
|
-
// <strong>/<b> → **bold**
|
|
15
|
-
md = md.replace(/<(strong|b)>([\s\S]*?)<\/\1>/gi, '**$2**');
|
|
16
|
-
// <em>/<i> → *italic*
|
|
17
|
-
md = md.replace(/<(em|i)>([\s\S]*?)<\/\1>/gi, '*$2*');
|
|
18
|
-
// <p> → paragraph boundaries
|
|
19
|
-
md = md.replace(/<p[^>]*>/gi, '');
|
|
20
|
-
md = md.replace(/<\/p>/gi, '\n\n');
|
|
21
|
-
// Strip remaining tags
|
|
22
|
-
md = md.replace(/<[^>]+>/g, '');
|
|
23
|
-
// Normalize whitespace
|
|
24
|
-
md = md.replace(/\n{3,}/g, '\n\n');
|
|
25
|
-
return md.trim();
|
|
26
|
-
}
|
|
27
9
|
const plugin = {
|
|
28
10
|
name: '@openwriter/plugin-authors-voice',
|
|
29
|
-
version: '0.
|
|
11
|
+
version: '0.4.0',
|
|
30
12
|
description: "Rewrite text in your voice using Author's Voice",
|
|
31
13
|
category: 'writing',
|
|
32
14
|
configSchema: {
|
|
@@ -45,113 +27,31 @@ const plugin = {
|
|
|
45
27
|
registerRoutes(ctx) {
|
|
46
28
|
const backendUrl = ctx.config['backend-url'] || process.env.AV_BACKEND_URL || 'https://authors-voice.com';
|
|
47
29
|
const apiKey = ctx.config['api-key'] || process.env.AV_API_KEY || '';
|
|
30
|
+
const debugEnabled = process.env.AV_DEBUG === '1' || process.env.AV_DEBUG === 'true';
|
|
48
31
|
const authHeaders = () => {
|
|
49
32
|
const h = { 'Content-Type': 'application/json' };
|
|
50
33
|
if (apiKey)
|
|
51
34
|
h['Authorization'] = `Bearer ${apiKey}`;
|
|
52
35
|
return h;
|
|
53
36
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
// Call AV backend transform endpoint
|
|
64
|
-
const transformUrl = `${backendUrl}/api/voice/transform`;
|
|
65
|
-
const upstream = await fetch(transformUrl, {
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: authHeaders(),
|
|
68
|
-
body: JSON.stringify({ action, content, title, instructions }),
|
|
69
|
-
});
|
|
70
|
-
if (!upstream.ok) {
|
|
71
|
-
const errData = await upstream.json().catch(() => ({}));
|
|
72
|
-
console.error('[AV Plugin] Transform failed:', upstream.status, errData);
|
|
73
|
-
res.status(upstream.status).json(errData);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const transformResult = await upstream.json();
|
|
77
|
-
// Convert HTML output to markdown for document creation
|
|
78
|
-
let markdownContent = htmlToMarkdown(transformResult.html);
|
|
79
|
-
// Threadify: always create as tweet template
|
|
80
|
-
const createBody = {
|
|
81
|
-
title: transformResult.newTitle,
|
|
82
|
-
content: markdownContent,
|
|
83
|
-
markPending: true,
|
|
84
|
-
agentCreated: true,
|
|
85
|
-
};
|
|
86
|
-
if (action === 'threadify') {
|
|
87
|
-
// Build TipTap JSON directly to avoid markdown parsing issues.
|
|
88
|
-
// Markdown parser converts "- item" lines to bulletList nodes that the
|
|
89
|
-
// tweet editor can't render (bulletList extension is disabled), causing
|
|
90
|
-
// empty gaps. By building JSON with only paragraph + hardBreak nodes,
|
|
91
|
-
// all tweet text stays as plain text.
|
|
92
|
-
if (transformResult.thread?.tweets?.length) {
|
|
93
|
-
const docContent = [];
|
|
94
|
-
transformResult.thread.tweets.forEach((t, i) => {
|
|
95
|
-
// Single paragraph per tweet. Split on \n only:
|
|
96
|
-
// \n → one hardBreak (tight line), \n\n → two hardBreaks (blank line spacing)
|
|
97
|
-
const lines = t.text.split('\n');
|
|
98
|
-
const nodes = [];
|
|
99
|
-
lines.forEach((line, j) => {
|
|
100
|
-
if (j > 0)
|
|
101
|
-
nodes.push({ type: 'hardBreak' });
|
|
102
|
-
if (line)
|
|
103
|
-
nodes.push({ type: 'text', text: line });
|
|
104
|
-
});
|
|
105
|
-
if (nodes.length) {
|
|
106
|
-
docContent.push({ type: 'paragraph', content: nodes });
|
|
107
|
-
}
|
|
108
|
-
if (i < transformResult.thread.tweets.length - 1) {
|
|
109
|
-
docContent.push({ type: 'horizontalRule' });
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
createBody.content = { type: 'doc', content: docContent };
|
|
113
|
-
}
|
|
114
|
-
createBody.metadata = { tweetContext: { mode: 'tweet' } };
|
|
115
|
-
}
|
|
116
|
-
// Create new document in OpenWriter via internal HTTP call
|
|
117
|
-
const host = req.get('host') || 'localhost:5050';
|
|
118
|
-
const protocol = req.protocol || 'http';
|
|
119
|
-
const createUrl = `${protocol}://${host}/api/documents`;
|
|
120
|
-
const createRes = await fetch(createUrl, {
|
|
121
|
-
method: 'POST',
|
|
122
|
-
headers: { 'Content-Type': 'application/json' },
|
|
123
|
-
body: JSON.stringify(createBody),
|
|
124
|
-
});
|
|
125
|
-
if (!createRes.ok) {
|
|
126
|
-
const errData = await createRes.json().catch(() => ({}));
|
|
127
|
-
console.error('[AV Plugin] Document creation failed:', errData);
|
|
128
|
-
res.status(500).json({ error: 'Failed to create result document' });
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const docResult = await createRes.json();
|
|
132
|
-
res.json({
|
|
133
|
-
success: true,
|
|
134
|
-
action,
|
|
135
|
-
filename: docResult.filename,
|
|
136
|
-
title: transformResult.newTitle,
|
|
137
|
-
metadata: transformResult.metadata,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
console.error('[AV Plugin] Sidebar action error:', err?.message || err);
|
|
142
|
-
res.status(500).json({ error: 'Sidebar action failed' });
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
// Wildcard proxy for all other /api/voice/* routes
|
|
37
|
+
const withDebug = (body) => {
|
|
38
|
+
if (!debugEnabled || !body || typeof body !== 'object')
|
|
39
|
+
return body;
|
|
40
|
+
return { ...body, debug: true };
|
|
41
|
+
};
|
|
42
|
+
// Wildcard proxy for /api/voice/* routes. Pure pass-through: the AV API owns the
|
|
43
|
+
// engine choice (v1/v2) via its own AV_DEFAULT_ENGINE setting, so the plugin injects
|
|
44
|
+
// nothing but the optional owner-only dev debug flag.
|
|
146
45
|
ctx.app.post('/api/voice/*', async (req, res) => {
|
|
147
46
|
try {
|
|
148
47
|
const subPath = req.params[0] || '';
|
|
149
48
|
const targetUrl = `${backendUrl}/api/voice/${subPath}`;
|
|
49
|
+
const body = withDebug(req.body);
|
|
150
50
|
console.log(`[AV Plugin] ${req.method} ${req.path} → ${targetUrl}`);
|
|
151
51
|
const upstream = await fetch(targetUrl, {
|
|
152
52
|
method: 'POST',
|
|
153
53
|
headers: authHeaders(),
|
|
154
|
-
body: JSON.stringify(
|
|
54
|
+
body: JSON.stringify(body),
|
|
155
55
|
});
|
|
156
56
|
res.status(upstream.status);
|
|
157
57
|
const forwardHeaders = ['x-usage-rewrite-count', 'x-usage-rewrite-limit', 'x-usage-resets-at'];
|
|
@@ -189,18 +89,5 @@ const plugin = {
|
|
|
189
89
|
{ label: 'Fill sentence', action: 'av:fill-sentence', condition: 'empty-node' },
|
|
190
90
|
];
|
|
191
91
|
},
|
|
192
|
-
// Sidebar transforms disabled — now handled by publish plugin.
|
|
193
|
-
// Kept commented for reference during transition.
|
|
194
|
-
// sidebarMenuItems() {
|
|
195
|
-
// return [
|
|
196
|
-
// { label: 'Vary', action: 'voice:vary', promptForFocus: true },
|
|
197
|
-
// { label: 'Shrinkify', action: 'voice:shrinkify', promptForFocus: true },
|
|
198
|
-
// { label: 'Expandify', action: 'voice:expandify', promptForFocus: true },
|
|
199
|
-
// { label: 'Threadify', action: 'voice:threadify', promptForFocus: true },
|
|
200
|
-
// { label: 'Storify', action: 'voice:storify', promptForFocus: true },
|
|
201
|
-
// { label: 'Emailify', action: 'voice:emailify', promptForFocus: true },
|
|
202
|
-
// { label: 'Postify', action: 'voice:postify', promptForFocus: true },
|
|
203
|
-
// ];
|
|
204
|
-
// },
|
|
205
92
|
};
|
|
206
93
|
export default plugin;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog publishing MCP tools — local git ops, no Worker, no PAT.
|
|
3
|
+
*
|
|
4
|
+
* Auth: piggybacks on the user's existing `gh auth login`.
|
|
5
|
+
* Persistence: blogSites in plugins['@openwriter/plugin-github'].blogSites.
|
|
6
|
+
*/
|
|
7
|
+
import { type PluginMcpTool } from './helpers.js';
|
|
8
|
+
export declare function blogTools(): PluginMcpTool[];
|