drafted 1.13.0 → 1.14.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/install-mcp.sh +14 -4
- package/mcp/server.mjs +76 -1
- package/package.json +1 -1
package/install-mcp.sh
CHANGED
|
@@ -227,7 +227,17 @@ if [ -n "$STALE_DRAFTED_BIN" ]; then
|
|
|
227
227
|
fi
|
|
228
228
|
fi
|
|
229
229
|
|
|
230
|
-
npm config set prefix
|
|
230
|
+
# Do NOT `npm config set prefix` — that repoints the user's GLOBAL npm prefix, so every
|
|
231
|
+
# `npm install -g <pkg>` they ever run lands in our dir AND our uninstall (`rm -rf ~/.drafted`)
|
|
232
|
+
# would wipe all their other globals. Install drafted with an explicit per-command --prefix
|
|
233
|
+
# instead (below), and HEAL any global prefix pin an older version of this installer wrote so
|
|
234
|
+
# the user's default is restored. Preserve every other ~/.npmrc line (auth tokens, etc.).
|
|
235
|
+
NPMRC="${npm_config_userconfig:-$HOME/.npmrc}"
|
|
236
|
+
if [ -f "$NPMRC" ] && grep -Eq '^[[:space:]]*prefix[[:space:]]*=.*/\.drafted/npm-global/?[[:space:]]*$' "$NPMRC"; then
|
|
237
|
+
tmp_npmrc="$(mktemp)"
|
|
238
|
+
grep -Ev '^[[:space:]]*prefix[[:space:]]*=.*/\.drafted/npm-global/?[[:space:]]*$' "$NPMRC" > "$tmp_npmrc" && cat "$tmp_npmrc" > "$NPMRC"
|
|
239
|
+
rm -f "$tmp_npmrc"
|
|
240
|
+
fi
|
|
231
241
|
export PATH="$NPM_GLOBAL_PREFIX/bin:$PATH"
|
|
232
242
|
|
|
233
243
|
# Persist the prefix's bin dir at the FRONT of PATH for future shells — without
|
|
@@ -262,7 +272,7 @@ step "Installing Drafted"
|
|
|
262
272
|
install_drafted_pkg() {
|
|
263
273
|
attempts=5; delay=4; n=1
|
|
264
274
|
while :; do
|
|
265
|
-
if npm install -g drafted@latest --force; then return 0; fi
|
|
275
|
+
if npm install -g drafted@latest --force --prefix "$NPM_GLOBAL_PREFIX"; then return 0; fi
|
|
266
276
|
if [ "$n" -ge "$attempts" ]; then return 1; fi
|
|
267
277
|
echo -e " ${YELLOW}npm install failed (attempt $n/$attempts) — retrying in ${delay}s (a new release may still be propagating to the npm CDN)...${RESET}"
|
|
268
278
|
sleep "$delay"
|
|
@@ -274,7 +284,7 @@ if ! install_drafted_pkg; then
|
|
|
274
284
|
exit 1
|
|
275
285
|
fi
|
|
276
286
|
hash -r 2>/dev/null || true
|
|
277
|
-
NPM_ROOT="$(npm root -g 2>/dev/null || true)"
|
|
287
|
+
NPM_ROOT="$(npm root -g --prefix "$NPM_GLOBAL_PREFIX" 2>/dev/null || true)"
|
|
278
288
|
MCP_SERVER_MODULE="$NPM_ROOT/drafted/mcp/server.mjs"
|
|
279
289
|
if [ -n "$NPM_ROOT" ] && [ -f "$MCP_SERVER_MODULE" ]; then
|
|
280
290
|
node -e "import('node:url').then(({ pathToFileURL }) => import(pathToFileURL(process.argv[1]).href)).then(() => process.exit(0), (err) => { console.error(err); process.exit(1); })" "$MCP_SERVER_MODULE"
|
|
@@ -1288,7 +1298,7 @@ echo ""
|
|
|
1288
1298
|
echo -e " ${DIM}MCP name:${RESET} ${BOLD}$INSTALL_NAME${RESET}"
|
|
1289
1299
|
echo -e " ${DIM}Server:${RESET} ${BOLD}$INSTALL_SERVER${RESET}"
|
|
1290
1300
|
echo -e " ${DIM}To update production:${RESET} rerun curl -fsSL https://drafted.live/install.sh | bash"
|
|
1291
|
-
echo -e " ${DIM}To uninstall:${RESET} npm uninstall -g drafted && rm -rf ~/.drafted"
|
|
1301
|
+
echo -e " ${DIM}To uninstall:${RESET} npm uninstall -g drafted --prefix ~/.drafted/npm-global && rm -rf ~/.drafted"
|
|
1292
1302
|
echo ""
|
|
1293
1303
|
echo -e "${YELLOW}${BOLD}"
|
|
1294
1304
|
echo " ┌─────────────────────────────────────────────────────────┐"
|
package/mcp/server.mjs
CHANGED
|
@@ -1462,10 +1462,85 @@ async function consumePendingDeviceCode() {
|
|
|
1462
1462
|
// meaningless and disruptive — it returns spurious sign-in URLs and, on login,
|
|
1463
1463
|
// spawns a server-side browser-open and blocks polling until timeout. Register
|
|
1464
1464
|
// it only on stdio.
|
|
1465
|
-
|
|
1465
|
+
// --- Local-install auth surface: the desktop app, never a device link ---
|
|
1466
|
+
// The stdio installer ALWAYS installs the Drafted desktop app alongside the MCP, so on a
|
|
1467
|
+
// local install the app IS the sign-in surface. Spawning it hands off to the always-running
|
|
1468
|
+
// instance (single-instance plugin), which opens the sign-in window and starts the native
|
|
1469
|
+
// cookie->auth.json capture that this MCP reads via getBootstrapSessionId(). Only the web MCP
|
|
1470
|
+
// is app-less, and that path authenticates via OAuth2 in the browser — never this tool. The
|
|
1471
|
+
// device-code flow below is kept ONLY as a fallback for platforms without the desktop app.
|
|
1472
|
+
function desktopAppBinary() {
|
|
1473
|
+
try {
|
|
1474
|
+
if (process.platform === 'darwin') {
|
|
1475
|
+
const p = '/Applications/Drafted.app/Contents/MacOS/drafted-desktop';
|
|
1476
|
+
return existsSync(p) ? p : null;
|
|
1477
|
+
}
|
|
1478
|
+
if (process.platform === 'win32') {
|
|
1479
|
+
const p = join(process.env.LOCALAPPDATA || '', 'Programs', 'Drafted', 'Drafted.exe');
|
|
1480
|
+
return existsSync(p) ? p : null;
|
|
1481
|
+
}
|
|
1482
|
+
} catch { /* fall through to no-app */ }
|
|
1483
|
+
return null; // e.g. Linux — no desktop app → device-code fallback
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
async function launchDesktopSignin() {
|
|
1487
|
+
const bin = desktopAppBinary();
|
|
1488
|
+
if (!bin) return false;
|
|
1489
|
+
try {
|
|
1490
|
+
const { spawn } = await import('child_process');
|
|
1491
|
+
// If the app is already running (macOS KeepAlive normally guarantees it), the single-instance
|
|
1492
|
+
// handler opens the sign-in window and DRAFTED_OPEN_LOGIN is ignored. If it isn't running,
|
|
1493
|
+
// the fresh primary instance honors DRAFTED_OPEN_LOGIN=1 and opens sign-in on boot.
|
|
1494
|
+
const child = spawn(bin, [], {
|
|
1495
|
+
detached: true,
|
|
1496
|
+
stdio: 'ignore',
|
|
1497
|
+
env: { ...process.env, DRAFTED_OPEN_LOGIN: '1' },
|
|
1498
|
+
});
|
|
1499
|
+
child.unref();
|
|
1500
|
+
return true;
|
|
1501
|
+
} catch { return false; }
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// Poll auth.json (written by the desktop app's cookie->auth.json bridge on in-app sign-in)
|
|
1505
|
+
// until a valid session id lands or the deadline passes.
|
|
1506
|
+
async function waitForBootstrapAuth(deadline) {
|
|
1507
|
+
while (Date.now() < deadline) {
|
|
1508
|
+
const sid = getBootstrapSessionId();
|
|
1509
|
+
if (sid) return sid;
|
|
1510
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
1511
|
+
}
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if (!isRemote) tool('auth', 'Sign in to Drafted. On a local install the DESKTOP APP is the sign-in surface: both actions open the Drafted app to its sign-in window — no device link is shown. `action=login` opens the app and waits for the in-app sign-in to complete; `action=get_link` opens the app and returns immediately (the next Drafted tool call picks up the captured session). A device-code link is used ONLY as a fallback on platforms without the desktop app (the web MCP uses OAuth2, not this tool).', {
|
|
1466
1516
|
action: z.enum(['get_link', 'login']).describe('Operation to perform.'),
|
|
1467
1517
|
}, async ({ action }) => {
|
|
1468
1518
|
try {
|
|
1519
|
+
// Local install → open the desktop app's sign-in window (no link). Falls through to the
|
|
1520
|
+
// device-code flow below only when no desktop app is installed on this platform.
|
|
1521
|
+
{
|
|
1522
|
+
const existing = getState().sessionId || getBootstrapSessionId();
|
|
1523
|
+
if (existing) {
|
|
1524
|
+
try {
|
|
1525
|
+
const meRes = await serverFetch(`${getServerUrl()}/auth/me`, { headers: { Cookie: `gc_session=${existing}` } });
|
|
1526
|
+
if (meRes.ok) {
|
|
1527
|
+
const me = await meRes.json();
|
|
1528
|
+
return ok({ status: 'already_authenticated', userId: me.userId, email: me.userEmail, org: me.currentOrg?.name });
|
|
1529
|
+
}
|
|
1530
|
+
} catch { /* stale session — continue to sign-in */ }
|
|
1531
|
+
}
|
|
1532
|
+
if (await launchDesktopSignin()) {
|
|
1533
|
+
if (action === 'get_link') {
|
|
1534
|
+
return ok('Opening the Drafted app to sign in — approve in the app window, then retry your request.');
|
|
1535
|
+
}
|
|
1536
|
+
const sid = await waitForBootstrapAuth(Date.now() + 180000);
|
|
1537
|
+
if (!sid) throw new Error('Timed out waiting for sign-in. Complete sign-in in the Drafted app window, then retry.');
|
|
1538
|
+
getState().sessionId = null;
|
|
1539
|
+
await cloneSession();
|
|
1540
|
+
connectAgentWs();
|
|
1541
|
+
return ok({ status: 'logged_in', via: 'desktop-app' });
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1469
1544
|
if (action === 'get_link') {
|
|
1470
1545
|
const codeRes = await serverFetch(`${getServerUrl()}/auth/device/code`, { method: 'POST' });
|
|
1471
1546
|
if (!codeRes.ok) throw new Error(`Failed to start device authorization (HTTP ${codeRes.status})`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|