openwriter 0.31.0 → 0.33.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/dist/client/assets/{index-C-eMDCqj.css → index-BFw23tzV.css} +1 -1
- package/dist/client/assets/index-yRAovDFi.js +214 -0
- package/dist/client/index.html +2 -2
- package/dist/plugins/authors-voice/dist/index.js +33 -4
- package/dist/plugins/image-gen/dist/index.js +11 -3
- package/dist/plugins/publish/dist/index.js +5 -4
- package/dist/server/index.js +7 -2
- package/dist/server/mcp.js +5 -1
- package/dist/server/update-check.js +30 -3
- package/package.json +1 -1
- package/dist/client/assets/index-CaFrYDJP.js +0 -214
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-yRAovDFi.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BFw23tzV.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -53,10 +53,10 @@ const plugin = {
|
|
|
53
53
|
description: 'Writing model',
|
|
54
54
|
options: [
|
|
55
55
|
{ value: '', label: 'Default (Strongest)' },
|
|
56
|
-
{ value: 'strongest', label: 'Strongest — Claude Opus (best quality)' },
|
|
57
|
-
{ value: 'balanced', label: 'Balanced — Claude Sonnet' },
|
|
58
|
-
{ value: 'fast-plus', label: 'Fast+ — Gemini 3.5 Flash (newest, great)' },
|
|
59
|
-
{ value: 'fast', label: 'Fast — Gemini 2.5 Flash (
|
|
56
|
+
{ value: 'strongest', label: 'Strongest — Claude Opus (best quality, ~20¢/edit)' },
|
|
57
|
+
{ value: 'balanced', label: 'Balanced — Claude Sonnet (~3¢/edit)' },
|
|
58
|
+
{ value: 'fast-plus', label: 'Fast+ — Gemini 3.5 Flash (newest, great, ~1¢/edit)' },
|
|
59
|
+
{ value: 'fast', label: 'Fast — Gemini 2.5 Flash (free)' },
|
|
60
60
|
],
|
|
61
61
|
},
|
|
62
62
|
},
|
|
@@ -121,6 +121,35 @@ const plugin = {
|
|
|
121
121
|
res.status(502).json({ error: 'AV backend unreachable' });
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
|
+
// Wildcard GET proxy for /api/voice/* — same key-injection + base-URL pattern as the POST
|
|
125
|
+
// proxy above, no body. Covers the wallet/top-up reads (GET /api/voice/billing and
|
|
126
|
+
// /api/voice/billing/topup-options); the matching POST /api/voice/billing/topup rides the
|
|
127
|
+
// POST wildcard. Query string is forwarded so any future paginated read just works.
|
|
128
|
+
ctx.app.get('/api/voice/*', async (req, res) => {
|
|
129
|
+
try {
|
|
130
|
+
const subPath = req.params[0] || '';
|
|
131
|
+
const qs = req.originalUrl.includes('?') ? req.originalUrl.slice(req.originalUrl.indexOf('?')) : '';
|
|
132
|
+
const targetUrl = `${backendUrl}/api/voice/${subPath}${qs}`;
|
|
133
|
+
console.log(`[AV Plugin] ${req.method} ${req.path} → ${targetUrl}`);
|
|
134
|
+
const upstream = await fetch(targetUrl, {
|
|
135
|
+
method: 'GET',
|
|
136
|
+
headers: authHeaders(),
|
|
137
|
+
});
|
|
138
|
+
res.status(upstream.status);
|
|
139
|
+
const responseText = await upstream.text();
|
|
140
|
+
try {
|
|
141
|
+
res.json(JSON.parse(responseText));
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
console.error('[AV Plugin] Non-JSON response:', responseText.substring(0, 500));
|
|
145
|
+
res.status(502).json({ error: 'AV backend returned non-JSON response' });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
console.error('[AV Plugin] Backend error:', err?.message || err);
|
|
150
|
+
res.status(502).json({ error: 'AV backend unreachable' });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
124
153
|
},
|
|
125
154
|
contextMenuItems() {
|
|
126
155
|
return [
|
|
@@ -8,7 +8,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
|
8
8
|
import { randomUUID } from 'crypto';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
/** Fallback: generate image via the publish platform API, download and save locally */
|
|
11
|
-
async function generateViaPlatform(prompt, dataDir, aspectRatio = '16:9') {
|
|
11
|
+
async function generateViaPlatform(prompt, dataDir, imageApiKey, aspectRatio = '16:9') {
|
|
12
12
|
const configPath = join(homedir(), '.openwriter', 'config.json');
|
|
13
13
|
if (!existsSync(configPath))
|
|
14
14
|
return null;
|
|
@@ -26,6 +26,8 @@ async function generateViaPlatform(prompt, dataDir, aspectRatio = '16:9') {
|
|
|
26
26
|
'Content-Type': 'application/json',
|
|
27
27
|
Authorization: `Bearer ${platformKey}`,
|
|
28
28
|
'X-Profile': profile,
|
|
29
|
+
// BYO image key → worker skips the shared-key allotment and bills the user's own key (uncapped).
|
|
30
|
+
...(imageApiKey ? { 'X-Image-Key': imageApiKey } : {}),
|
|
29
31
|
},
|
|
30
32
|
body: JSON.stringify({ prompt, aspect_ratio: aspectRatio }),
|
|
31
33
|
});
|
|
@@ -57,6 +59,11 @@ const plugin = {
|
|
|
57
59
|
required: false,
|
|
58
60
|
description: 'Google Gemini API key for image generation (optional — falls back to publish platform)',
|
|
59
61
|
},
|
|
62
|
+
imageApiKey: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
required: false,
|
|
65
|
+
description: "Your own image API key (Gemini) — unlimited generations at your cost. Leave blank to use OpenWriter's included allotment.",
|
|
66
|
+
},
|
|
60
67
|
},
|
|
61
68
|
registerRoutes(ctx) {
|
|
62
69
|
ctx.app.post('/api/image-gen/generate', async (req, res) => {
|
|
@@ -97,8 +104,9 @@ const plugin = {
|
|
|
97
104
|
}
|
|
98
105
|
}
|
|
99
106
|
else {
|
|
100
|
-
// Fallback: generate via publish platform API
|
|
101
|
-
|
|
107
|
+
// Fallback: generate via publish platform API.
|
|
108
|
+
// Pass the user's own image key (if set) so the worker bills it instead of the shared allotment.
|
|
109
|
+
const platformResult = await generateViaPlatform(prompt, ctx.dataDir, ctx.config['imageApiKey']);
|
|
102
110
|
if (!platformResult) {
|
|
103
111
|
res.status(400).json({ success: false, error: 'No GEMINI_API_KEY and publish platform not configured. Set GEMINI_API_KEY or log in to the publish plugin.' });
|
|
104
112
|
return;
|
|
@@ -976,7 +976,7 @@ const plugin = {
|
|
|
976
976
|
// --- Billing tools ---
|
|
977
977
|
{
|
|
978
978
|
name: 'get_billing',
|
|
979
|
-
description: 'Get current subscription plan,
|
|
979
|
+
description: 'Get current subscription plan, capabilities, and billing status. Shows the plan tier (Publish or Publish+Email), which publishing channels are enabled, and whether payment is active.',
|
|
980
980
|
inputSchema: { type: 'object', properties: {} },
|
|
981
981
|
handler: async () => {
|
|
982
982
|
const res = await publishFetch(config, '/billing');
|
|
@@ -989,11 +989,11 @@ const plugin = {
|
|
|
989
989
|
},
|
|
990
990
|
{
|
|
991
991
|
name: 'upgrade_plan',
|
|
992
|
-
description: 'Get a Stripe Checkout URL to subscribe or upgrade. Opens in browser for payment. Plans: creator ($19/mo), growth ($49/mo
|
|
992
|
+
description: 'Get a Stripe Checkout URL to subscribe or upgrade. Opens in browser for payment. Plans (wire value = label): "creator" = Publish ($19/mo, $190/yr) — the scheduler + publishing to every channel except email (X, LinkedIn, WordPress, Ghost, Beehiiv, blog); "growth" = Publish+Email ($49/mo, $490/yr) — everything in Publish plus newsletter/email. Period: monthly or annual (2 months free).',
|
|
993
993
|
inputSchema: {
|
|
994
994
|
type: 'object',
|
|
995
995
|
properties: {
|
|
996
|
-
plan: { type: 'string', enum: ['creator', 'growth'
|
|
996
|
+
plan: { type: 'string', enum: ['creator', 'growth'], description: 'Plan to subscribe to: "creator" = Publish ($19/mo), "growth" = Publish+Email ($49/mo)' },
|
|
997
997
|
period: { type: 'string', enum: ['monthly', 'annual'], description: 'Billing period (default: monthly). Annual saves 2 months.' },
|
|
998
998
|
},
|
|
999
999
|
required: ['plan'],
|
|
@@ -1011,9 +1011,10 @@ const plugin = {
|
|
|
1011
1011
|
return { error: `Checkout failed: ${err.error || res.statusText}` };
|
|
1012
1012
|
}
|
|
1013
1013
|
const data = await res.json();
|
|
1014
|
+
const planLabel = params.plan === 'growth' ? 'Publish+Email' : 'Publish';
|
|
1014
1015
|
return {
|
|
1015
1016
|
url: data.url,
|
|
1016
|
-
message: `Open this URL to complete your ${
|
|
1017
|
+
message: `Open this URL to complete your ${planLabel} subscription: ${data.url}`,
|
|
1017
1018
|
};
|
|
1018
1019
|
},
|
|
1019
1020
|
},
|
package/dist/server/index.js
CHANGED
|
@@ -33,7 +33,7 @@ import { createBillingRouter } from './billing-routes.js';
|
|
|
33
33
|
import { createTaskRouter } from './task-routes.js';
|
|
34
34
|
import { platformFetch, isAuthenticated } from './connections.js';
|
|
35
35
|
import { PluginManager } from './plugin-manager.js';
|
|
36
|
-
import { checkForUpdate, getUpdateInfo, getCurrentVersion } from './update-check.js';
|
|
36
|
+
import { checkForUpdate, getUpdateInfo, getCurrentVersion, getInstallType, getUpdateCommand } from './update-check.js';
|
|
37
37
|
import { addComment, getComments, resolveComments, unresolveComments, deleteComments, editComment } from './comments.js';
|
|
38
38
|
import { initLogger, logger, generateRequestId, withRequestId } from './logger.js';
|
|
39
39
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -101,7 +101,12 @@ export async function startHttpServer(options = {}) {
|
|
|
101
101
|
});
|
|
102
102
|
app.get('/api/update-info', (_req, res) => {
|
|
103
103
|
const latestVersion = getUpdateInfo();
|
|
104
|
-
res.json({
|
|
104
|
+
res.json({
|
|
105
|
+
updateAvailable: latestVersion,
|
|
106
|
+
currentVersion: getCurrentVersion(),
|
|
107
|
+
installType: getInstallType(),
|
|
108
|
+
updateCommand: getUpdateCommand(),
|
|
109
|
+
});
|
|
105
110
|
});
|
|
106
111
|
app.get('/api/document', (_req, res) => {
|
|
107
112
|
res.json({ document: getDocument(), title: getTitle(), metadata: getMetadata() });
|
package/dist/server/mcp.js
CHANGED
|
@@ -9,7 +9,7 @@ import { randomUUID } from 'crypto';
|
|
|
9
9
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
-
import { getDataDir, ensureDataDir, resolveDocPath, generateNodeId, atomicWriteFileSync } from './helpers.js';
|
|
12
|
+
import { getDataDir, ensureDataDir, resolveDocPath, generateNodeId, atomicWriteFileSync, readConfig } from './helpers.js';
|
|
13
13
|
import { getDocument, getWordCount, getPendingChangeCount, getTitle, getStatus, getNodesByIds, findNodesByIds, getMetadata, setMetadata, mergeMetadataUpdates, applyChanges, applyTextEdits, updateDocument, save, markAllNodesAsPending, setAgentLock, setAgentLockActive, updatePendingCacheForActiveDoc, populateDocumentFile, applyChangesToFile, applyTextEditsToFile, getDocId, getFilePath, getIsTemp, extractText, countPending, addDocTag, removeDocTag, getCachedDocument, invalidateDocCache, isAutoAcceptActive, removePendingCacheEntry, getExternalMtimeDrift, reloadActiveDocFromDisk, getCanonical, cloneWithPendingReverted, bumpDocVersion, setSortProposalOnFile, clearSortRequestOnFile, } from './state.js';
|
|
14
14
|
import { tiptapToBlocks } from './node-blocks.js';
|
|
15
15
|
import { outline, peek, searchInDoc, truncateRead } from './peek-outline.js';
|
|
@@ -1594,9 +1594,13 @@ export const TOOL_REGISTRY = [
|
|
|
1594
1594
|
}
|
|
1595
1595
|
return { content: [{ type: 'text', text: 'Error: No GEMINI_API_KEY and publish platform not configured. Set GEMINI_API_KEY or log in to the publish plugin.' }] };
|
|
1596
1596
|
}
|
|
1597
|
+
// BYO image key (image-gen plugin config) → worker uses the user's own key, uncapped.
|
|
1598
|
+
// Blank → shared-key allotment applies. Key is never logged or echoed.
|
|
1599
|
+
const userImageKey = readConfig().plugins?.['@openwriter/plugin-image-gen']?.config?.['imageApiKey'] || '';
|
|
1597
1600
|
const res = await platformFetch('/images/generate', {
|
|
1598
1601
|
method: 'POST',
|
|
1599
1602
|
body: JSON.stringify({ prompt, aspect_ratio: aspect_ratio || '16:9' }),
|
|
1603
|
+
...(userImageKey ? { headers: { 'X-Image-Key': userImageKey } } : {}),
|
|
1600
1604
|
});
|
|
1601
1605
|
if (!res.ok) {
|
|
1602
1606
|
const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Uses Node's built-in fetch + existing config system.
|
|
4
4
|
* Fire-and-forget: never blocks startup, never throws to caller.
|
|
5
5
|
*/
|
|
6
|
-
import { readFileSync } from 'fs';
|
|
6
|
+
import { readFileSync, existsSync } from 'fs';
|
|
7
7
|
import { join, dirname } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { readConfig, saveConfig } from './helpers.js';
|
|
@@ -38,6 +38,33 @@ export function getCurrentVersion() {
|
|
|
38
38
|
return '0.0.0';
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Detect how this instance was installed.
|
|
43
|
+
* - 'git' — running from a git checkout (dev / dogfood): a `.git` dir exists
|
|
44
|
+
* above the package. Update path is `git pull && npm run build`.
|
|
45
|
+
* - 'npm' — packaged global install (npm tarball extract, no `.git`).
|
|
46
|
+
* Update path is `npm update -g openwriter`.
|
|
47
|
+
* Walks up from the package dir; npm tarballs never ship `.git`, so its
|
|
48
|
+
* absence is a reliable signal.
|
|
49
|
+
*/
|
|
50
|
+
export function getInstallType() {
|
|
51
|
+
let dir = __dirname;
|
|
52
|
+
for (let i = 0; i < 8; i++) {
|
|
53
|
+
if (existsSync(join(dir, '.git')))
|
|
54
|
+
return 'git';
|
|
55
|
+
const parent = dirname(dir);
|
|
56
|
+
if (parent === dir)
|
|
57
|
+
break; // hit filesystem root
|
|
58
|
+
dir = parent;
|
|
59
|
+
}
|
|
60
|
+
return 'npm';
|
|
61
|
+
}
|
|
62
|
+
/** The shell command this user should run to update, given their install type. */
|
|
63
|
+
export function getUpdateCommand() {
|
|
64
|
+
return getInstallType() === 'git'
|
|
65
|
+
? 'git pull && npm run build'
|
|
66
|
+
: 'npm update -g openwriter';
|
|
67
|
+
}
|
|
41
68
|
/**
|
|
42
69
|
* Check npm registry for a newer version. Fire-and-forget.
|
|
43
70
|
* - Respects NO_UPDATE_NOTIFIER env var
|
|
@@ -56,7 +83,7 @@ export async function checkForUpdate() {
|
|
|
56
83
|
if (now - lastCheck < CHECK_INTERVAL_MS) {
|
|
57
84
|
if (compareVersions(currentVersion, config.latestVersion) < 0) {
|
|
58
85
|
cachedLatestVersion = config.latestVersion;
|
|
59
|
-
console.error(`[OpenWriter] Update available: ${currentVersion} → ${config.latestVersion} — run:
|
|
86
|
+
console.error(`[OpenWriter] Update available: ${currentVersion} → ${config.latestVersion} — run: ${getUpdateCommand()}`);
|
|
60
87
|
}
|
|
61
88
|
return;
|
|
62
89
|
}
|
|
@@ -82,7 +109,7 @@ export async function checkForUpdate() {
|
|
|
82
109
|
});
|
|
83
110
|
if (compareVersions(currentVersion, latestVersion) < 0) {
|
|
84
111
|
cachedLatestVersion = latestVersion;
|
|
85
|
-
console.error(`[OpenWriter] Update available: ${currentVersion} → ${latestVersion} — run:
|
|
112
|
+
console.error(`[OpenWriter] Update available: ${currentVersion} → ${latestVersion} — run: ${getUpdateCommand()}`);
|
|
86
113
|
}
|
|
87
114
|
}
|
|
88
115
|
catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openwriter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "The open-source writing surface for AI agents. Markdown-native editor with pending change review — your agent writes, you accept or reject.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|