openwriter 0.10.0 → 0.12.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/bin/pad.js +146 -101
- package/dist/client/assets/index-CNmzNvB_.js +211 -0
- package/dist/client/assets/index-CRImKlcp.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/documents.js +2 -0
- package/dist/server/index.js +46 -6
- package/dist/server/markdown-parse.js +11 -0
- package/dist/server/markdown-serialize.js +4 -1
- package/dist/server/mcp.js +89 -13
- package/dist/server/state.js +98 -29
- package/dist/server/ws.js +68 -15
- package/package.json +1 -1
- package/skill/SKILL.md +43 -1
- package/skill/docs/anti-ai.md +71 -0
- package/skill/docs/voices.md +88 -0
- package/skill/voices/authority.md +102 -0
- package/skill/voices/business.md +103 -0
- package/skill/voices/logical.md +104 -0
- package/skill/voices/provocateur.md +101 -0
- package/skill/voices/storyteller.md +104 -0
- package/dist/client/assets/index-CuPYxtxy.css +0 -1
- package/dist/client/assets/index-deMuWDiP.js +0 -211
- package/dist/plugins/authors-voice/dist/index.d.ts +0 -41
- package/dist/plugins/authors-voice/dist/index.js +0 -206
- package/dist/plugins/authors-voice/package.json +0 -23
- package/dist/plugins/image-gen/dist/index.d.ts +0 -35
- package/dist/plugins/image-gen/dist/index.js +0 -141
- package/dist/plugins/image-gen/package.json +0 -26
- package/dist/plugins/publish/dist/helpers.d.ts +0 -66
- package/dist/plugins/publish/dist/helpers.js +0 -199
- package/dist/plugins/publish/dist/index.d.ts +0 -3
- package/dist/plugins/publish/dist/index.js +0 -1130
- package/dist/plugins/publish/dist/newsletter-tools.d.ts +0 -2
- package/dist/plugins/publish/dist/newsletter-tools.js +0 -394
- package/dist/plugins/publish/package.json +0 -31
- package/dist/plugins/x-api/dist/index.d.ts +0 -27
- package/dist/plugins/x-api/dist/index.js +0 -240
- package/dist/plugins/x-api/package.json +0 -27
- package/dist/server/prompt-debug.js +0 -58
- package/dist/server/workspace-tags.js +0 -30
package/dist/bin/pad.js
CHANGED
|
@@ -49,117 +49,162 @@ process.stdin.on('close', () => {
|
|
|
49
49
|
});
|
|
50
50
|
// Only light imports here — helpers.js uses fs/path/os/crypto (all Node stdlib)
|
|
51
51
|
import { createConnection } from 'net';
|
|
52
|
+
import { existsSync, readFileSync } from 'fs';
|
|
53
|
+
import { dirname, join, resolve } from 'path';
|
|
54
|
+
import { fileURLToPath } from 'url';
|
|
52
55
|
import { readConfig, saveConfig } from '../server/helpers.js';
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
// ── Worktree-aware re-exec ──────────────────────────────────────────────────
|
|
57
|
+
// The `openwriter` global command is npm-linked to a single repo, but during
|
|
58
|
+
// development we use worktrees for branches. Walking up from cwd to find the
|
|
59
|
+
// nearest openwriter repo lets each worktree's freshly-built dist take effect
|
|
60
|
+
// automatically — no manual `npm link` swap between trees.
|
|
61
|
+
//
|
|
62
|
+
// Detection: walk up from process.cwd() looking for a packages/openwriter
|
|
63
|
+
// package.json named "openwriter". If found AND it's not the same repo as the
|
|
64
|
+
// one we're running from, dynamic-import its dist/bin/pad.js (which runs its
|
|
65
|
+
// own server) and skip our own startup. Outside any openwriter repo (e.g. a
|
|
66
|
+
// book project), we fall through and run our own dist normally.
|
|
67
|
+
function findLocalOpenwriterBin() {
|
|
68
|
+
let dir = process.cwd();
|
|
69
|
+
while (true) {
|
|
70
|
+
const pkgPath = join(dir, 'packages', 'openwriter', 'package.json');
|
|
71
|
+
if (existsSync(pkgPath)) {
|
|
72
|
+
try {
|
|
73
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
74
|
+
if (pkg.name === 'openwriter') {
|
|
75
|
+
const bin = join(dir, 'packages', 'openwriter', 'dist', 'bin', 'pad.js');
|
|
76
|
+
if (existsSync(bin))
|
|
77
|
+
return bin;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { /* keep walking */ }
|
|
81
|
+
}
|
|
82
|
+
const parent = dirname(dir);
|
|
83
|
+
if (parent === dir)
|
|
84
|
+
return null;
|
|
85
|
+
dir = parent;
|
|
86
|
+
}
|
|
57
87
|
}
|
|
58
|
-
|
|
59
|
-
|
|
88
|
+
const myBin = resolve(fileURLToPath(import.meta.url));
|
|
89
|
+
const localBin = findLocalOpenwriterBin();
|
|
90
|
+
let handedOff = false;
|
|
91
|
+
if (localBin && resolve(localBin) !== myBin) {
|
|
92
|
+
console.error(`[OpenWriter] cwd is inside an openwriter repo — running ${localBin}`);
|
|
93
|
+
await import(localBin);
|
|
94
|
+
handedOff = true;
|
|
60
95
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
let plugins = [];
|
|
67
|
-
for (let i = 0; i < args.length; i++) {
|
|
68
|
-
if (args[i] === '--port' && args[i + 1]) {
|
|
69
|
-
port = parseInt(args[i + 1], 10);
|
|
70
|
-
i++;
|
|
71
|
-
}
|
|
72
|
-
if (args[i] === '--no-open') {
|
|
73
|
-
noOpen = true;
|
|
74
|
-
}
|
|
75
|
-
if (args[i] === '--api-key' && args[i + 1]) {
|
|
76
|
-
cliApiKey = args[i + 1];
|
|
77
|
-
i++;
|
|
78
|
-
}
|
|
79
|
-
if (args[i] === '--av-url' && args[i + 1]) {
|
|
80
|
-
cliAvUrl = args[i + 1];
|
|
81
|
-
i++;
|
|
82
|
-
}
|
|
83
|
-
if (args[i] === '--plugins' && args[i + 1]) {
|
|
84
|
-
plugins = args[i + 1].split(',').map((s) => s.trim()).filter(Boolean);
|
|
85
|
-
i++;
|
|
86
|
-
}
|
|
96
|
+
if (!handedOff) {
|
|
97
|
+
const args = process.argv.slice(2);
|
|
98
|
+
// Subcommands (run and exit, don't start server)
|
|
99
|
+
if (args[0] === 'install-skill') {
|
|
100
|
+
import('../server/install-skill.js').then(m => m.installSkill());
|
|
87
101
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// Restore active profile from config
|
|
91
|
-
const { setActiveProfile } = await import('../server/helpers.js');
|
|
92
|
-
setActiveProfile(config.activeProfile || 'Default');
|
|
93
|
-
const avApiKey = cliApiKey || process.env.AV_API_KEY || config.avApiKey || '';
|
|
94
|
-
const avBackendUrl = cliAvUrl || process.env.AV_BACKEND_URL || config.avBackendUrl;
|
|
95
|
-
// Persist new values to config so future starts don't need them
|
|
96
|
-
const updates = {};
|
|
97
|
-
if (cliApiKey && cliApiKey !== config.avApiKey)
|
|
98
|
-
updates.avApiKey = cliApiKey;
|
|
99
|
-
if (cliAvUrl && cliAvUrl !== config.avBackendUrl)
|
|
100
|
-
updates.avBackendUrl = cliAvUrl;
|
|
101
|
-
if (Object.keys(updates).length > 0) {
|
|
102
|
-
saveConfig(updates);
|
|
103
|
-
console.log('Config saved to ~/.openwriter/config.json');
|
|
102
|
+
else if (args[0] === 'plugin') {
|
|
103
|
+
import('../server/plugin-install.js').then(m => m.handlePluginCommand(args.slice(1)));
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
105
|
+
else {
|
|
106
|
+
let port = 5050;
|
|
107
|
+
let noOpen = false;
|
|
108
|
+
let cliApiKey;
|
|
109
|
+
let cliAvUrl;
|
|
110
|
+
let plugins = [];
|
|
111
|
+
for (let i = 0; i < args.length; i++) {
|
|
112
|
+
if (args[i] === '--port' && args[i + 1]) {
|
|
113
|
+
port = parseInt(args[i + 1], 10);
|
|
114
|
+
i++;
|
|
115
|
+
}
|
|
116
|
+
if (args[i] === '--no-open') {
|
|
117
|
+
noOpen = true;
|
|
118
|
+
}
|
|
119
|
+
if (args[i] === '--api-key' && args[i + 1]) {
|
|
120
|
+
cliApiKey = args[i + 1];
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
if (args[i] === '--av-url' && args[i + 1]) {
|
|
124
|
+
cliAvUrl = args[i + 1];
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
if (args[i] === '--plugins' && args[i + 1]) {
|
|
128
|
+
plugins = args[i + 1].split(',').map((s) => s.trim()).filter(Boolean);
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
123
131
|
}
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
// Resolve API key: CLI flag → env var → saved config
|
|
133
|
+
const config = readConfig();
|
|
134
|
+
// Restore active profile from config
|
|
135
|
+
const { setActiveProfile } = await import('../server/helpers.js');
|
|
136
|
+
setActiveProfile(config.activeProfile || 'Default');
|
|
137
|
+
const avApiKey = cliApiKey || process.env.AV_API_KEY || config.avApiKey || '';
|
|
138
|
+
const avBackendUrl = cliAvUrl || process.env.AV_BACKEND_URL || config.avBackendUrl;
|
|
139
|
+
// Persist new values to config so future starts don't need them
|
|
140
|
+
const updates = {};
|
|
141
|
+
if (cliApiKey && cliApiKey !== config.avApiKey)
|
|
142
|
+
updates.avApiKey = cliApiKey;
|
|
143
|
+
if (cliAvUrl && cliAvUrl !== config.avBackendUrl)
|
|
144
|
+
updates.avBackendUrl = cliAvUrl;
|
|
145
|
+
if (Object.keys(updates).length > 0) {
|
|
146
|
+
saveConfig(updates);
|
|
147
|
+
console.log('Config saved to ~/.openwriter/config.json');
|
|
126
148
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
// Set env vars for downstream code (plugins read process.env)
|
|
150
|
+
if (avApiKey)
|
|
151
|
+
process.env.AV_API_KEY = avApiKey;
|
|
152
|
+
if (avBackendUrl)
|
|
153
|
+
process.env.AV_BACKEND_URL = avBackendUrl;
|
|
154
|
+
// Port check with health verification — detects orphaned servers
|
|
155
|
+
async function checkPort() {
|
|
156
|
+
const taken = await new Promise((resolve) => {
|
|
157
|
+
const socket = createConnection({ port, host: '127.0.0.1' });
|
|
158
|
+
socket.once('connect', () => { socket.destroy(); resolve(true); });
|
|
159
|
+
socket.once('error', () => { resolve(false); });
|
|
160
|
+
});
|
|
161
|
+
if (!taken)
|
|
162
|
+
return 'free';
|
|
163
|
+
// Port is taken — verify it's a healthy OpenWriter server
|
|
164
|
+
try {
|
|
165
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/status`, { signal: AbortSignal.timeout(2000) });
|
|
166
|
+
return res.ok ? 'healthy' : 'orphaned';
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return 'orphaned';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
let portState = await checkPort();
|
|
173
|
+
// Orphaned server: wait for it to die, then claim primary mode
|
|
134
174
|
if (portState === 'orphaned') {
|
|
135
|
-
|
|
175
|
+
console.error(`[OpenWriter] Port ${port} held by unresponsive process — waiting for release...`);
|
|
136
176
|
await new Promise(r => setTimeout(r, 3000));
|
|
137
177
|
portState = await checkPort();
|
|
178
|
+
if (portState === 'orphaned') {
|
|
179
|
+
// Still held — wait once more
|
|
180
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
181
|
+
portState = await checkPort();
|
|
182
|
+
}
|
|
183
|
+
if (portState !== 'free') {
|
|
184
|
+
console.error(`[OpenWriter] Port ${port} still unavailable — entering client mode`);
|
|
185
|
+
}
|
|
138
186
|
}
|
|
139
|
-
if (portState
|
|
140
|
-
|
|
187
|
+
if (portState === 'healthy') {
|
|
188
|
+
// Client mode: proxy MCP calls to existing primary server via HTTP
|
|
189
|
+
console.error(`[OpenWriter] Port ${port} in use by healthy server — entering client mode`);
|
|
190
|
+
const { startMcpClientServer } = await import('../server/mcp-client.js');
|
|
191
|
+
startMcpClientServer(port).catch((err) => {
|
|
192
|
+
console.error('[MCP-Client] Failed to start:', err);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Primary mode: start MCP stdio FIRST, then lazy-load Express
|
|
197
|
+
const { load } = await import('../server/state.js');
|
|
198
|
+
load();
|
|
199
|
+
const { startMcpServer } = await import('../server/mcp.js');
|
|
200
|
+
startMcpServer().catch((err) => {
|
|
201
|
+
console.error('[MCP] Failed to start:', err);
|
|
202
|
+
});
|
|
203
|
+
// Deferred: load Express + plugins (heavy deps) after MCP is connecting
|
|
204
|
+
const { startHttpServer } = await import('../server/index.js');
|
|
205
|
+
startHttpServer({ port, noOpen, plugins }).catch((err) => {
|
|
206
|
+
console.error('[HTTP] Failed to start:', err);
|
|
207
|
+
});
|
|
141
208
|
}
|
|
142
209
|
}
|
|
143
|
-
|
|
144
|
-
// Client mode: proxy MCP calls to existing primary server via HTTP
|
|
145
|
-
console.error(`[OpenWriter] Port ${port} in use by healthy server — entering client mode`);
|
|
146
|
-
const { startMcpClientServer } = await import('../server/mcp-client.js');
|
|
147
|
-
startMcpClientServer(port).catch((err) => {
|
|
148
|
-
console.error('[MCP-Client] Failed to start:', err);
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
// Primary mode: start MCP stdio FIRST, then lazy-load Express
|
|
153
|
-
const { load } = await import('../server/state.js');
|
|
154
|
-
load();
|
|
155
|
-
const { startMcpServer } = await import('../server/mcp.js');
|
|
156
|
-
startMcpServer().catch((err) => {
|
|
157
|
-
console.error('[MCP] Failed to start:', err);
|
|
158
|
-
});
|
|
159
|
-
// Deferred: load Express + plugins (heavy deps) after MCP is connecting
|
|
160
|
-
const { startHttpServer } = await import('../server/index.js');
|
|
161
|
-
startHttpServer({ port, noOpen, plugins }).catch((err) => {
|
|
162
|
-
console.error('[HTTP] Failed to start:', err);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
210
|
+
} // end if (!handedOff)
|