drafted 1.19.8 → 1.19.11

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 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
- curl -fsSL https://drafted.live/install.sh | bash # macOS / Linux
22
- irm https://drafted.live/install.ps1 | iex # Windows
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
- const script = `$tmp = Join-Path $env:TEMP "drafted-install.ps1"; Invoke-WebRequest -UseBasicParsing "${server}/install.ps1" -OutFile $tmp; powershell -NoProfile -ExecutionPolicy Bypass -File $tmp`;
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', '-Command', script],
183
- manualCommand: script,
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 | bash
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 | bash"
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
@@ -391,6 +391,7 @@ const TOOL_ANNOTATIONS = {
391
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
+ action: { title: 'Propose an action', readOnlyHint: false, destructiveHint: false, openWorldHint: false, description: 'Propose an outward effect for a HUMAN to approve — sharing a project or file, changing someone\'s role, minting a public link, or @mentioning someone in a comment. These are the operations that reach OUTSIDE the org: they grant access to a person or contact them, and they cannot be undone by editing a frame. You can propose and read; you cannot approve. Approving is the send, it happens server-side from a frozen snapshot, and there is no verb here that would let an agent trigger it. Everything INSIDE the org — writing frames, wiki pages, skills, task status — needs no proposal: just do it.' },
394
395
  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.' },
395
396
 
396
397
  // Canvas / view
@@ -2547,6 +2548,58 @@ tool('tour', {
2547
2548
  } catch (error) { return err(error); }
2548
2549
  });
2549
2550
 
2551
+ // ── action: propose an outward effect; a HUMAN approving is what executes ──
2552
+ //
2553
+ // There is deliberately NO execute/approve verb here, on any transport. The gate
2554
+ // is not "the agent may send once approved" — approving IS the send, server-side,
2555
+ // from a frozen snapshot. A spendable permission is a capability that can later be
2556
+ // re-pointed at content the human never read. See SECURITY.md.
2557
+ tool('action', {
2558
+ action: z.enum(['propose', 'list', 'read']).describe('propose: ask a human to authorise one outward effect, or a chain of them approved together. list: your org\'s pending proposals. read: one chain with every payload as the approver will see it.'),
2559
+ type: z.string().optional().describe('[propose] One of: share_project, share_frame, change_share_role, create_public_frame_link, create_public_lane_link, comment_mention. Anything else is refused — the list is fixed in source, not data.'),
2560
+ payload: z.string().optional().describe('[propose] JSON object for this type. share_project {projectId,email,role}; share_frame {frameId,email,role}; change_share_role {shareId,role}; create_public_frame_link {frameId}; create_public_lane_link {projectId,layer,lane}; comment_mention {frameId,body,email}. Optional expiresInHours on the share types.'),
2561
+ actions: z.string().optional().describe('[propose] JSON array of {type,payload} to approve TOGETHER as one chain — e.g. share with three people, then comment tagging them. One screen, one decision. Execution is sequential and stops at the first failure; a sent email cannot be rolled back, so it is never all-or-nothing.'),
2562
+ groupId: z.string().optional().describe('[read] The chain to read.'),
2563
+ reason: z.string().optional().describe('[propose] One line telling the approver WHY. They see it next to what the action does.'),
2564
+ limit: z.number().optional().describe('[list] Default 25.'),
2565
+ offset: z.number().optional().describe('[list] Default 0.'),
2566
+ compact: z.boolean().optional().describe('[list] Identity fields only.'),
2567
+ }, async ({ action, type, payload, actions, groupId, reason, limit, offset, compact }) => {
2568
+ try {
2569
+ if (action === 'propose') {
2570
+ let items;
2571
+ if (actions) {
2572
+ const parsed = JSON.parse(actions);
2573
+ if (!Array.isArray(parsed) || !parsed.length) throw new Error('actions must be a non-empty JSON array of {type,payload}');
2574
+ items = parsed;
2575
+ } else {
2576
+ if (!type) throw new Error('type is required for action propose (or pass `actions` for a chain)');
2577
+ items = [{ type, payload: payload ? JSON.parse(payload) : {} }];
2578
+ }
2579
+ const result = await api('POST', '/api/actions', {
2580
+ actions: items,
2581
+ ...(reason ? { reason: String(reason) } : {}),
2582
+ });
2583
+ return ok({
2584
+ ...result,
2585
+ next: 'Proposed. A person must approve this in Drafted before anything is sent — you cannot approve it yourself, and there is no verb here that would let you.',
2586
+ });
2587
+ }
2588
+ if (action === 'read') {
2589
+ if (!groupId) throw new Error('groupId is required for action read');
2590
+ return ok(await api('GET', `/api/actions/groups/${encodeURIComponent(groupId)}`));
2591
+ }
2592
+ const qs = new URLSearchParams();
2593
+ if (limit != null) qs.set('limit', String(limit));
2594
+ if (offset != null) qs.set('offset', String(offset));
2595
+ if (compact) qs.set('compact', '1');
2596
+ const q = qs.toString();
2597
+ return ok(await api('GET', `/api/actions${q ? '?' + q : ''}`));
2598
+ } catch (e) {
2599
+ return err(e);
2600
+ }
2601
+ });
2602
+
2550
2603
  tool('comment', {
2551
2604
  action: z.enum(['list', 'add', 'resolve', 'reopen', 'delete']).describe('list: read comments (paginated). add: post a comment or a reply. resolve/reopen: flip a comment\'s state. delete: remove one.'),
2552
2605
  target: z.string().optional().describe('[list, add] The frame: a path (/projects/<project>/<layer>/<lane>/<file>), a frame URL, or a frame UUID. Required for add. For list, omit it to read the whole project.'),
@@ -3808,7 +3861,7 @@ function buildInstalledMcpUpdateInstructions(updateMetadata = null) {
3808
3861
 
3809
3862
  const server = getServerUrl().replace(/\/$/, '');
3810
3863
  const manualCommand = platform() === 'win32'
3811
- ? `$tmp = Join-Path $env:TEMP "drafted-install.ps1"; Invoke-WebRequest -UseBasicParsing "${server}/install.ps1" -OutFile $tmp; powershell -NoProfile -ExecutionPolicy Bypass -File $tmp`
3864
+ ? `Invoke-WebRequest -UseBasicParsing "${server}/install.ps1" -OutFile "$env:TEMP\drafted-install.ps1"\npowershell -NoProfile -ExecutionPolicy Bypass -File "$env:TEMP\drafted-install.ps1"`
3812
3865
  : `tmp=$(mktemp); curl -fsSL ${server}/install.sh -o "$tmp" && bash "$tmp"`;
3813
3866
 
3814
3867
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.19.8",
3
+ "version": "1.19.11",
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": [