drafted 1.19.7 → 1.19.9
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/README.md +9 -2
- package/cli/drafted.mjs +26 -4
- package/install-mcp.sh +4 -3
- package/mcp/server.mjs +100 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,10 +18,17 @@ Drafted is a native producer and consumer of the **Open Knowledge Format (OKF) v
|
|
|
18
18
|
**Claude Code, Codex, or any MCP client** — installs the MCP server, the Drafted skill, *and* the eight slash commands (into `~/.claude/commands/drafted/` and `~/.codex/prompts/drafted/`):
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
# macOS / Linux — download, then run the local file:
|
|
22
|
+
curl -fsSL https://drafted.live/install.sh -o /tmp/install-drafted.sh
|
|
23
|
+
bash /tmp/install-drafted.sh
|
|
24
|
+
|
|
25
|
+
# Windows (PowerShell) — download, then run the local file:
|
|
26
|
+
Invoke-WebRequest https://drafted.live/install.ps1 -OutFile $env:TEMP\drafted-install.ps1
|
|
27
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File $env:TEMP\drafted-install.ps1
|
|
23
28
|
```
|
|
24
29
|
|
|
30
|
+
> **Why not `curl | bash` / `irm | iex`?** Heuristic antivirus (Norton IDP, SmartScreen) flags the pipe-to-shell pattern as download-and-execute malware. Downloading the installer to a local file and running it avoids the false-positive block.
|
|
31
|
+
|
|
25
32
|
> Adding the MCP server on its own (e.g. `claude mcp add`) gives you the tools without the skill or the commands.
|
|
26
33
|
|
|
27
34
|
**Claude on the web (claude.ai) and Cowork** — install the plugin, not the bare connector. The plugin bundles the Drafted connector *plus* the Drafted skill and the eight slash commands (`/drafted:onboard-drafted`, `/drafted:create-project`, `/drafted:create-skill`, `/drafted:ingest`, `/drafted:extract`, `/drafted:improve-wiki`, `/drafted:improve-skill`, `/drafted:improve-project-harness`); adding the connector URL on its own gives you the tools with none of the guidance.
|
package/cli/drafted.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { program } from 'commander';
|
|
11
11
|
import { spawn, execSync, execFileSync } from 'child_process';
|
|
12
12
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, unlinkSync, chmodSync } from 'fs';
|
|
13
|
+
import { writeFile } from 'fs/promises';
|
|
13
14
|
import { join, dirname, basename, resolve } from 'path';
|
|
14
15
|
import { homedir, tmpdir, platform } from 'os';
|
|
15
16
|
import { fileURLToPath } from 'url';
|
|
@@ -176,11 +177,18 @@ function getServerUrl() {
|
|
|
176
177
|
function buildUpdateCommand() {
|
|
177
178
|
const server = getServerUrl().replace(/\/$/, '');
|
|
178
179
|
if (platform() === 'win32') {
|
|
179
|
-
|
|
180
|
+
// Download the installer with Node, then spawn powershell -File on the LOCAL
|
|
181
|
+
// copy. Never `powershell -Command "Invoke-WebRequest ...; powershell -File
|
|
182
|
+
// ..."` — a fetch-and-execute trampoline on the command line is exactly what
|
|
183
|
+
// heuristic AV (Norton IDP.HEUR) flags as malware.
|
|
184
|
+
const tmp = path.join(os.tmpdir(), 'drafted-install.ps1');
|
|
185
|
+
const src = `${server}/install.ps1`;
|
|
180
186
|
return {
|
|
181
187
|
shell: 'powershell.exe',
|
|
182
|
-
args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-
|
|
183
|
-
manualCommand:
|
|
188
|
+
args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', tmp],
|
|
189
|
+
manualCommand: `Invoke-WebRequest -UseBasicParsing "${src}" -OutFile "${tmp}"
|
|
190
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File "${tmp}"`,
|
|
191
|
+
fetch: { url: src, dest: tmp },
|
|
184
192
|
};
|
|
185
193
|
}
|
|
186
194
|
const script = `tmp=$(mktemp); curl -fsSL ${server}/install.sh -o "$tmp" && bash "$tmp"`;
|
|
@@ -505,7 +513,7 @@ program
|
|
|
505
513
|
.description('Update the npm-installed Drafted MCP daemon')
|
|
506
514
|
.option('--dry-run', 'Print update instructions without starting the updater')
|
|
507
515
|
.option('--yes', 'Start the updater out-of-process')
|
|
508
|
-
.action((options) => {
|
|
516
|
+
.action(async (options) => {
|
|
509
517
|
const update = buildUpdateCommand();
|
|
510
518
|
const data = {
|
|
511
519
|
started: false,
|
|
@@ -517,6 +525,20 @@ program
|
|
|
517
525
|
};
|
|
518
526
|
|
|
519
527
|
if (options.yes && !options.dryRun) {
|
|
528
|
+
if (update.fetch) {
|
|
529
|
+
const { url, dest } = update.fetch;
|
|
530
|
+
try {
|
|
531
|
+
const resp = await fetch(url);
|
|
532
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
533
|
+
await writeFile(dest, Buffer.from(await resp.arrayBuffer()));
|
|
534
|
+
} catch (err) {
|
|
535
|
+
console.error(`Could not download the installer: ${err.message}. Run the manual command below instead.`);
|
|
536
|
+
jsonOut(true, 'update', { ...data, started: false, error: String(err.message || err) });
|
|
537
|
+
console.log('No automatic update was started. Run the manual updater command:');
|
|
538
|
+
console.log(` ${update.manualCommand}`);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
520
542
|
const child = spawn(update.shell, update.args, {
|
|
521
543
|
detached: true,
|
|
522
544
|
stdio: 'ignore',
|
package/install-mcp.sh
CHANGED
|
@@ -5,8 +5,9 @@ set -e
|
|
|
5
5
|
# This script installs the Drafted MCP server and CLI globally via npm,
|
|
6
6
|
# then registers it with Claude Desktop, Claude Code, Codex, and Cursor.
|
|
7
7
|
#
|
|
8
|
-
# Run with:
|
|
9
|
-
# curl -fsSL https://drafted.live/install.sh
|
|
8
|
+
# Run with (download first, then run the local file):
|
|
9
|
+
# curl -fsSL https://drafted.live/install.sh -o ~/Downloads/install-drafted.sh
|
|
10
|
+
# bash ~/Downloads/install-drafted.sh
|
|
10
11
|
|
|
11
12
|
SERVER="https://drafted.live"
|
|
12
13
|
INSTALLER_VERSION="1"
|
|
@@ -1254,7 +1255,7 @@ echo -e "${GREEN}${BOLD}You're all set!${RESET}"
|
|
|
1254
1255
|
echo ""
|
|
1255
1256
|
echo -e " ${DIM}MCP name:${RESET} ${BOLD}$INSTALL_NAME${RESET}"
|
|
1256
1257
|
echo -e " ${DIM}Server:${RESET} ${BOLD}$INSTALL_SERVER${RESET}"
|
|
1257
|
-
echo -e " ${DIM}To update production:${RESET} rerun curl -fsSL https://drafted.live/install.sh
|
|
1258
|
+
echo -e " ${DIM}To update production:${RESET} rerun the installer from drafted.live/install (curl -fsSL https://drafted.live/install.sh -o /tmp/install-drafted.sh && bash /tmp/install-drafted.sh)"
|
|
1258
1259
|
echo -e " ${DIM}To uninstall:${RESET} npm uninstall -g drafted --prefix ~/.drafted/npm-global && rm -rf ~/.drafted"
|
|
1259
1260
|
echo ""
|
|
1260
1261
|
echo -e "${YELLOW}${BOLD}"
|
package/mcp/server.mjs
CHANGED
|
@@ -384,11 +384,11 @@ const TOOL_ANNOTATIONS = {
|
|
|
384
384
|
auth: { title: 'Sign in', readOnlyHint: false, destructiveHint: false, openWorldHint: true, description: 'Sign in to Drafted. `action=get_link` returns a URL immediately and starts background approval polling; after the user opens the link, later Drafted tool calls also auto-consume the approved login. `action=login` opens a browser when needed and explicitly waits/polls for approval.' },
|
|
385
385
|
|
|
386
386
|
// Identity — read-only introspection of THIS agent's session
|
|
387
|
-
whoami: { title: 'Session identity', readOnlyHint: true, destructiveHint: false, openWorldHint: false, description: 'Return THIS agent session\'s identity: its server-assigned human-readable name (the correlation key between an agent window and its web-app session tab), sessionId, userId, orgId, active projectId, editor label, server URL, and surfaced/alive state — PLUS server health, the installed MCP version/update status (cached ~5min), and `googleDrive` — whether the working org has Google Drive connected: when `googleDrive.connected` is true, strongly prefer Google Workspace frames (.google-doc/.google-sheet/.google-slide) for docs, sheets, and decks; when false they cannot be created at all. Call once per session, right after starting, so a required update surfaces before you act on stale tool behavior. Read-only. Use this — not guesses from the host environment — to report which session you are.' },
|
|
387
|
+
whoami: { title: 'Session identity', readOnlyHint: true, destructiveHint: false, openWorldHint: false, description: 'Return THIS agent session\'s identity: its server-assigned human-readable name (the correlation key between an agent window and its web-app session tab), sessionId, userId, orgId, active projectId, editor label, server URL, and surfaced/alive state (`authState` says whether this session is signed in, and `authNote` names the exact call that fixes it when it is not — never diagnose a null userId yourself) — PLUS server health, the installed MCP version/update status (cached ~5min), and `googleDrive` — whether the working org has Google Drive connected: when `googleDrive.connected` is true, strongly prefer Google Workspace frames (.google-doc/.google-sheet/.google-slide) for docs, sheets, and decks; when false they cannot be created at all. Call once per session, right after starting, so a required update surfaces before you act on stale tool behavior. Read-only. Use this — not guesses from the host environment — to report which session you are.' },
|
|
388
388
|
|
|
389
389
|
// Session naming — the name-before-work gate: every agent session must set a short
|
|
390
390
|
// name describing the work before any other tool call succeeds.
|
|
391
|
-
session: { title: 'Session', readOnlyHint: false, destructiveHint: false, openWorldHint: false, description: 'Name THIS agent session (and rename it later). The name is what the user sees on your surface tab — pick a short 2-3 word description of the work (e.g. "beoflow backend", "drafted fs work"). The name persists across reconnects and restarts; you only set it once unless the work changes. Dispatch by `action`: `name` (set/rename with the `name` param).' },
|
|
391
|
+
session: { title: 'Session', readOnlyHint: false, destructiveHint: false, openWorldHint: false, description: 'Name THIS agent session (and rename it later). The name is what the user sees on your surface tab — pick a short 2-3 word description of the work (e.g. "beoflow backend", "drafted fs work"). The name persists across reconnects and restarts; you only set it once unless the work changes. Dispatch by `action`: `name` (set/rename with the `name` param) or `update` (update the npm-installed Drafted MCP on THIS machine when whoami reports mcpUpdate.stale — run it yourself rather than handing the user a shell command; `dryRun` reports without starting it).' },
|
|
392
392
|
|
|
393
393
|
// Comments — the review loop agents could previously only reach over raw HTTP
|
|
394
394
|
comment: { title: 'Comments', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Read and write review comments on frames. Comments are notes ABOUT THE WORK: they attach to a frame, optionally to one element inside it, and anyone who can see the frame sees them. Dispatch by `action`: list (paginated, compact mode), add (with an optional element anchor or a reply), resolve, reopen, delete. Use this to leave findings a human can action in place, and to read the feedback they left you.' },
|
|
@@ -2204,6 +2204,8 @@ async function sessionSurfaceBlock() {
|
|
|
2204
2204
|
if (agentSurface) {
|
|
2205
2205
|
return {
|
|
2206
2206
|
sessionId,
|
|
2207
|
+
authenticated: !!agentSurface.userId,
|
|
2208
|
+
authState: agentSurface.userId ? 'authenticated' : 'signed_out',
|
|
2207
2209
|
userId: agentSurface.userId ?? null,
|
|
2208
2210
|
orgId: agentSurface.orgId ?? null,
|
|
2209
2211
|
projectId: agentSurface.projectId ?? null,
|
|
@@ -2232,6 +2234,7 @@ async function sessionSurfaceBlock() {
|
|
|
2232
2234
|
try {
|
|
2233
2235
|
const res = await serverFetch(`${getServerUrl()}/auth/me`, { headers: { Cookie: `gc_session=${cookieSid}` } });
|
|
2234
2236
|
if (res.ok) me = await res.json();
|
|
2237
|
+
// else: the server ANSWERED and rejected this session — a real sign-out, not a blip.
|
|
2235
2238
|
} catch (e) {
|
|
2236
2239
|
// Transport failure, NOT a sign-out. Conflating the two made agents tell
|
|
2237
2240
|
// users "you're signed out" during a network blip and start needless
|
|
@@ -2242,8 +2245,26 @@ async function sessionSurfaceBlock() {
|
|
|
2242
2245
|
}
|
|
2243
2246
|
}
|
|
2244
2247
|
const nameRequired = !!me?.agentClone && !me?.surfaceName;
|
|
2248
|
+
// A null userId used to come back BARE — no reason, no next step — and agents filled the
|
|
2249
|
+
// gap by inventing one: a real report has an agent telling the user to look for a pending
|
|
2250
|
+
// "device/session approval prompt" in the desktop app (no such prompt exists) and then
|
|
2251
|
+
// waiting on it. Name which of the four states this is and the exact call that ends it.
|
|
2252
|
+
const pending = (!me && !unreachable) ? getPendingDeviceCode() : null;
|
|
2253
|
+
const authState = me?.userId ? 'authenticated'
|
|
2254
|
+
: unreachable ? 'server_unreachable'
|
|
2255
|
+
: pending ? 'awaiting_approval'
|
|
2256
|
+
: cookieSid ? 'session_rejected'
|
|
2257
|
+
: 'signed_out';
|
|
2258
|
+
const AUTH_NOTES = {
|
|
2259
|
+
awaiting_approval: `Sign-in started but NOT yet approved. Give the user this link and ask them to open it: ${pending?.verificationUrl || '(run auth(action="get_link") for a fresh link)'} — this session authenticates itself once they approve; nothing to approve inside the desktop app.`,
|
|
2260
|
+
session_rejected: 'Not signed in: this machine has a stored Drafted session but the server rejected it (expired or revoked). Call auth(action="get_link") and give the user the returned URL. Do NOT ask them to look for an approval prompt in the desktop app — being signed in to the app does not sign THIS agent in.',
|
|
2261
|
+
signed_out: 'Not signed in: no Drafted session on this machine. Call auth(action="get_link") and give the user the returned URL. Do NOT ask them to look for an approval prompt in the desktop app — being signed in to the app does not sign THIS agent in.',
|
|
2262
|
+
};
|
|
2245
2263
|
return {
|
|
2246
2264
|
sessionId: cookieSid,
|
|
2265
|
+
authenticated: !!me?.userId,
|
|
2266
|
+
authState,
|
|
2267
|
+
...(AUTH_NOTES[authState] ? { authNote: AUTH_NOTES[authState] } : {}),
|
|
2247
2268
|
userId: me?.userId ?? null,
|
|
2248
2269
|
orgId: me?.currentOrg?.id ?? null,
|
|
2249
2270
|
projectId: getState().projectId ?? null,
|
|
@@ -2254,6 +2275,9 @@ async function sessionSurfaceBlock() {
|
|
|
2254
2275
|
color: null,
|
|
2255
2276
|
surfaced: false,
|
|
2256
2277
|
alive: false,
|
|
2278
|
+
// `alive` is bare here where it isn't in the acked branch, and a bare false reads as
|
|
2279
|
+
// "something is broken" — it only means the surface WebSocket hasn't acked yet.
|
|
2280
|
+
aliveMeaning: 'The surface WebSocket has not acked this session yet, so no canvas is known to be open. Frame, wiki, and skill writes all persist normally — only focus/presence have nothing to draw on. Do not change what you write because of this, and do not report it as an error.',
|
|
2257
2281
|
...(unreachable ? {
|
|
2258
2282
|
serverUnreachable: true,
|
|
2259
2283
|
note: unreachableWhy || `Could not reach ${getServerUrl()} — identity UNKNOWN, not signed out. This is a network/transport failure: do not tell the user they are logged out and do not start a new login; retry when connectivity is back.`,
|
|
@@ -2285,7 +2309,7 @@ async function getGoogleDriveAvailability() {
|
|
|
2285
2309
|
// reachability, and installed-MCP staleness in ONE bootstrap call. The update data is
|
|
2286
2310
|
// cached (5min), so repeat `whoami` calls are free; the server-side update gate still
|
|
2287
2311
|
// blocks mutating calls on its own, independent of this tool. Read-only — no state changed.
|
|
2288
|
-
tool('whoami', 'Return THIS agent session\'s identity: its server-assigned human-readable name (the correlation key between an agent window and its web-app session tab), sessionId, userId, orgId, active projectId, editor label, server URL, and surfaced/alive state — PLUS server health, the installed MCP version/update status (cached ~5min), and `googleDrive` — whether the working org has Google Drive connected: when `googleDrive.connected` is true, strongly prefer Google Workspace frames (.google-doc/.google-sheet/.google-slide) for docs, sheets, and decks; when false they cannot be created at all. Call once per session, right after starting, so a required update surfaces before you act on stale tool behavior. Read-only.', {}, async () => {
|
|
2312
|
+
tool('whoami', 'Return THIS agent session\'s identity: its server-assigned human-readable name (the correlation key between an agent window and its web-app session tab), sessionId, userId, orgId, active projectId, editor label, server URL, and surfaced/alive state (`authState` says whether this session is signed in, and `authNote` names the exact call that fixes it when it is not — never diagnose a null userId yourself) — PLUS server health, the installed MCP version/update status (cached ~5min), and `googleDrive` — whether the working org has Google Drive connected: when `googleDrive.connected` is true, strongly prefer Google Workspace frames (.google-doc/.google-sheet/.google-slide) for docs, sheets, and decks; when false they cannot be created at all. Call once per session, right after starting, so a required update surfaces before you act on stale tool behavior. Read-only.', {}, async () => {
|
|
2289
2313
|
try {
|
|
2290
2314
|
// Ensure the child clone exists BEFORE reading identity — otherwise the /auth/me
|
|
2291
2315
|
// fallback (pre-WS-ack) queries the ROOT session and reports the wrong naming state.
|
|
@@ -2295,7 +2319,19 @@ tool('whoami', 'Return THIS agent session\'s identity: its server-assigned human
|
|
|
2295
2319
|
// Tell the agent to actually surface its name to the user — returning `name` in the JSON isn't
|
|
2296
2320
|
// enough; without an explicit instruction agents rarely say which session they are, so users
|
|
2297
2321
|
// can't match them to their tab on the Drafted surface.
|
|
2298
|
-
|
|
2322
|
+
// Auth outranks naming: an unauthenticated session has nothing to name, and leaving the
|
|
2323
|
+
// instruction empty is what let agents invent their own explanation for the null ids.
|
|
2324
|
+
// A stale install can now fix ITSELF — say so here, since this is where staleness is
|
|
2325
|
+
// reported and an agent that only reads `stale: true` goes back to the user with a shell
|
|
2326
|
+
// command they didn't ask for.
|
|
2327
|
+
const staleNote = mcpUpdate.stale && mcpUpdate.mode === 'stdio'
|
|
2328
|
+
? ` This machine's installed Drafted MCP is out of date (${mcpUpdate.currentVersion} → ${mcpUpdate.latestVersion || 'latest'})${mcpUpdate.required ? ' and BELOW the minimum required version, so mutating tools are blocked' : ''}. Call session(action="update") to update it yourself — do not hand the user a shell command.`
|
|
2329
|
+
: '';
|
|
2330
|
+
const instruction = block.authNote
|
|
2331
|
+
? block.authNote + staleNote
|
|
2332
|
+
: staleNote && !block.nameRequired
|
|
2333
|
+
? (block.name ? `You are the session named "${block.name}". Tell the user you're "${block.name}" in your reply so they can match you to your tab on the Drafted surface.` : '') + staleNote
|
|
2334
|
+
: block.nameRequired
|
|
2299
2335
|
? `You have NO session name yet — the name-before-work gate blocks all other tools until you set one. Name this session by the work you're doing with the user: a short 2-3 word description (suggested: "${block.suggestedName || '...'}"). Set it now with session(action="name", name="<your name>"), then tell the user you're "<name>".`
|
|
2300
2336
|
: block.name
|
|
2301
2337
|
? `You are the session named "${block.name}". Tell the user you're "${block.name}" in your reply so they can match you to your tab on the Drafted surface.`
|
|
@@ -2314,13 +2350,69 @@ tool('whoami', 'Return THIS agent session\'s identity: its server-assigned human
|
|
|
2314
2350
|
} catch (error) { return err(error); }
|
|
2315
2351
|
});
|
|
2316
2352
|
|
|
2353
|
+
// Self-update: run the installed CLI's own updater so THIS machine's stale stdio MCP can be
|
|
2354
|
+
// refreshed from inside a tool call. Before this, the only path was telling the user to run a
|
|
2355
|
+
// shell command — which a web/Cowork agent cannot do at all, and which a stale install needs
|
|
2356
|
+
// most (an out-of-date MCP is exactly the session least able to ask for help). The updater
|
|
2357
|
+
// mechanics live in ONE place, `drafted update --yes` (cli/drafted.mjs) — do not reimplement
|
|
2358
|
+
// the installer command here, that's the two-writers drift.
|
|
2359
|
+
//
|
|
2360
|
+
// The `drafted` bin is resolved at the FIXED prefix first: the stdio MCP resolves from
|
|
2361
|
+
// ~/.drafted/npm-global, which a non-login shell's PATH does not carry, so bare `drafted`
|
|
2362
|
+
// finds nothing (or worse, a different install) on many machines.
|
|
2363
|
+
function installedDraftedBin() {
|
|
2364
|
+
const prefixed = platform() === 'win32'
|
|
2365
|
+
? join(homedir(), '.drafted', 'npm-global', 'drafted.cmd')
|
|
2366
|
+
: join(homedir(), '.drafted', 'npm-global', 'bin', 'drafted');
|
|
2367
|
+
return existsSync(prefixed) ? prefixed : 'drafted';
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
async function updateInstalledMcp({ dryRun = false } = {}) {
|
|
2371
|
+
try {
|
|
2372
|
+
const metadata = await getMcpUpdateMetadata();
|
|
2373
|
+
const instructions = buildInstalledMcpUpdateInstructions(metadata);
|
|
2374
|
+
// Hosted HTTP MCP has no local daemon — instructions already say so.
|
|
2375
|
+
if (!instructions.updateSupported) return ok({ ...instructions, action: 'update' });
|
|
2376
|
+
if (dryRun) return ok({ ...instructions, action: 'update', started: false, dryRun: true });
|
|
2377
|
+
|
|
2378
|
+
const bin = installedDraftedBin();
|
|
2379
|
+
const result = await new Promise((resolve) => {
|
|
2380
|
+
execFile(bin, ['update', '--yes', '--json'], { timeout: 60_000 }, (error, stdout) => {
|
|
2381
|
+
if (error) return resolve({ ok: false, error: error.code === 'ENOENT' ? `Drafted CLI not found (looked for ${bin})` : (error.killed ? 'Updater did not start within 60s' : error.message) });
|
|
2382
|
+
try { resolve({ ok: true, data: JSON.parse(stdout) }); }
|
|
2383
|
+
catch { resolve({ ok: true, data: null }); }
|
|
2384
|
+
});
|
|
2385
|
+
});
|
|
2386
|
+
|
|
2387
|
+
if (!result.ok) {
|
|
2388
|
+
return ok({
|
|
2389
|
+
...instructions,
|
|
2390
|
+
action: 'update',
|
|
2391
|
+
started: false,
|
|
2392
|
+
error: result.error,
|
|
2393
|
+
note: `Could not start the updater from this process (${result.error}). Give the user this command to run in a terminal instead: ${instructions.manualCommand}`,
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
return ok({
|
|
2397
|
+
...instructions,
|
|
2398
|
+
action: 'update',
|
|
2399
|
+
started: true,
|
|
2400
|
+
// Be exact about what did and did not happen: the installer runs DETACHED and does not
|
|
2401
|
+
// replace the already-loaded process, so "updated" would be a lie until a restart.
|
|
2402
|
+
note: 'The updater is running in the background (~30-60s). It does NOT replace this already-running MCP process: tell the user to restart their agent/editor once it finishes, then call whoami to confirm the new mcpVersion.',
|
|
2403
|
+
});
|
|
2404
|
+
} catch (error) { return err(error); }
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2317
2407
|
// Session naming: set/rename THIS agent session's name — the name-before-work gate stays
|
|
2318
2408
|
// closed until this succeeds. The name persists server-side (sessions.surface_name), so
|
|
2319
2409
|
// reconnects and server restarts keep it and the gate never re-fires for a named session.
|
|
2320
|
-
tool('session', '
|
|
2321
|
-
action: z.enum(['name']).describe('Operation
|
|
2410
|
+
tool('session', 'Manage THIS agent session. `action="name"` sets/renames it — the name is what the user sees on your surface tab, so pick a short 2-3 word description of the work (e.g. "beoflow backend"); it persists across reconnects and restarts. `action="update"` updates the npm-installed Drafted MCP on THIS machine when whoami reports mcpUpdate.stale — run it yourself instead of asking the user to run a shell command.', {
|
|
2411
|
+
action: z.enum(['name', 'update']).describe('Operation: `name` (set/rename this session) or `update` (update the installed Drafted MCP on this machine).'),
|
|
2322
2412
|
name: z.string().optional().describe('[name] the session name — a short 2-3 word description of the work (e.g. "beoflow backend"). Max 5 words / 50 chars.'),
|
|
2323
|
-
|
|
2413
|
+
dryRun: z.boolean().optional().describe('[update] report what the update would do without starting it.'),
|
|
2414
|
+
}, async ({ action, name, dryRun }) => {
|
|
2415
|
+
if (action === 'update') return updateInstalledMcp({ dryRun });
|
|
2324
2416
|
if (action !== 'name') return err(new Error(`unknown session action: ${action}`));
|
|
2325
2417
|
if (!name || !String(name).trim()) return err(new Error('name required — a short 2-3 word description of the work, e.g. session(action="name", name="beoflow backend")'));
|
|
2326
2418
|
try {
|
|
@@ -3716,7 +3808,7 @@ function buildInstalledMcpUpdateInstructions(updateMetadata = null) {
|
|
|
3716
3808
|
|
|
3717
3809
|
const server = getServerUrl().replace(/\/$/, '');
|
|
3718
3810
|
const manualCommand = platform() === 'win32'
|
|
3719
|
-
?
|
|
3811
|
+
? `Invoke-WebRequest -UseBasicParsing "${server}/install.ps1" -OutFile "$env:TEMP\drafted-install.ps1"\npowershell -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\drafted-install.ps1"`
|
|
3720
3812
|
: `tmp=$(mktemp); curl -fsSL ${server}/install.sh -o "$tmp" && bash "$tmp"`;
|
|
3721
3813
|
|
|
3722
3814
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.9",
|
|
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": [
|