openwriter 0.12.0 → 0.13.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 +101 -146
- package/dist/client/assets/index-BlLnLdoc.js +212 -0
- package/dist/client/assets/{index-CRImKlcp.css → index-OV13QtgQ.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/backlinks.js +323 -0
- package/dist/server/index.js +130 -1
- package/dist/server/markdown-parse.js +45 -6
- package/dist/server/markdown-serialize.js +10 -2
- package/dist/server/marks.js +9 -0
- package/dist/server/mcp.js +148 -6
- package/dist/server/state.js +47 -6
- package/dist/server/workspace-routes.js +31 -3
- package/dist/server/workspaces.js +85 -0
- package/package.json +1 -1
- package/skill/SKILL.md +3 -7
- package/dist/client/assets/index-CNmzNvB_.js +0 -211
- package/skill/docs/anti-ai.md +0 -71
- package/skill/docs/voices.md +0 -88
- package/skill/voices/authority.md +0 -102
- package/skill/voices/business.md +0 -103
- package/skill/voices/logical.md +0 -104
- package/skill/voices/provocateur.md +0 -101
- package/skill/voices/storyteller.md +0 -104
package/dist/bin/pad.js
CHANGED
|
@@ -49,162 +49,117 @@ 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';
|
|
55
52
|
import { readConfig, saveConfig } from '../server/helpers.js';
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
}
|
|
53
|
+
const args = process.argv.slice(2);
|
|
54
|
+
// Subcommands (run and exit, don't start server)
|
|
55
|
+
if (args[0] === 'install-skill') {
|
|
56
|
+
import('../server/install-skill.js').then(m => m.installSkill());
|
|
87
57
|
}
|
|
88
|
-
|
|
89
|
-
|
|
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;
|
|
58
|
+
else if (args[0] === 'plugin') {
|
|
59
|
+
import('../server/plugin-install.js').then(m => m.handlePluginCommand(args.slice(1)));
|
|
95
60
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
61
|
+
else {
|
|
62
|
+
let port = 5050;
|
|
63
|
+
let noOpen = false;
|
|
64
|
+
let cliApiKey;
|
|
65
|
+
let cliAvUrl;
|
|
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
|
+
}
|
|
101
87
|
}
|
|
102
|
-
|
|
103
|
-
|
|
88
|
+
// Resolve API key: CLI flag → env var → saved config
|
|
89
|
+
const config = readConfig();
|
|
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');
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
}
|
|
105
|
+
// Set env vars for downstream code (plugins read process.env)
|
|
106
|
+
if (avApiKey)
|
|
107
|
+
process.env.AV_API_KEY = avApiKey;
|
|
108
|
+
if (avBackendUrl)
|
|
109
|
+
process.env.AV_BACKEND_URL = avBackendUrl;
|
|
110
|
+
// Port check with health verification — detects orphaned servers
|
|
111
|
+
async function checkPort() {
|
|
112
|
+
const taken = await new Promise((resolve) => {
|
|
113
|
+
const socket = createConnection({ port, host: '127.0.0.1' });
|
|
114
|
+
socket.once('connect', () => { socket.destroy(); resolve(true); });
|
|
115
|
+
socket.once('error', () => { resolve(false); });
|
|
116
|
+
});
|
|
117
|
+
if (!taken)
|
|
118
|
+
return 'free';
|
|
119
|
+
// Port is taken — verify it's a healthy OpenWriter server
|
|
120
|
+
try {
|
|
121
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/status`, { signal: AbortSignal.timeout(2000) });
|
|
122
|
+
return res.ok ? 'healthy' : 'orphaned';
|
|
131
123
|
}
|
|
132
|
-
|
|
133
|
-
|
|
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');
|
|
124
|
+
catch {
|
|
125
|
+
return 'orphaned';
|
|
148
126
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
127
|
+
}
|
|
128
|
+
let portState = await checkPort();
|
|
129
|
+
// Orphaned server: wait for it to die, then claim primary mode
|
|
130
|
+
if (portState === 'orphaned') {
|
|
131
|
+
console.error(`[OpenWriter] Port ${port} held by unresponsive process — waiting for release...`);
|
|
132
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
133
|
+
portState = await checkPort();
|
|
174
134
|
if (portState === 'orphaned') {
|
|
175
|
-
|
|
135
|
+
// Still held — wait once more
|
|
176
136
|
await new Promise(r => setTimeout(r, 3000));
|
|
177
137
|
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
|
-
}
|
|
186
138
|
}
|
|
187
|
-
if (portState
|
|
188
|
-
|
|
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
|
-
});
|
|
139
|
+
if (portState !== 'free') {
|
|
140
|
+
console.error(`[OpenWriter] Port ${port} still unavailable — entering client mode`);
|
|
208
141
|
}
|
|
209
142
|
}
|
|
210
|
-
|
|
143
|
+
if (portState === 'healthy') {
|
|
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
|
+
}
|