drafted 1.7.22 → 1.7.23

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/cli/drafted.mjs CHANGED
@@ -11,7 +11,7 @@ import { program } from 'commander';
11
11
  import { spawn, execSync } from 'child_process';
12
12
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, unlinkSync } from 'fs';
13
13
  import { join, dirname, basename, resolve } from 'path';
14
- import { homedir, tmpdir } from 'os';
14
+ import { homedir, tmpdir, platform } from 'os';
15
15
  import { fileURLToPath } from 'url';
16
16
  import { DESIGN_SYSTEM_PROMPT, buildDesignPrompt } from './prompts.mjs';
17
17
 
@@ -140,6 +140,24 @@ function getServerUrl() {
140
140
  return `http://localhost:${process.env.DRAFTED_PORT || DEFAULT_PORT}`;
141
141
  }
142
142
 
143
+ function buildUpdateCommand() {
144
+ const server = getServerUrl().replace(/\/$/, '');
145
+ if (platform() === 'win32') {
146
+ const script = `$tmp = Join-Path $env:TEMP "drafted-install.ps1"; Invoke-WebRequest -UseBasicParsing "${server}/install.ps1" -OutFile $tmp; powershell -NoProfile -ExecutionPolicy Bypass -File $tmp`;
147
+ return {
148
+ shell: 'powershell.exe',
149
+ args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', script],
150
+ manualCommand: script,
151
+ };
152
+ }
153
+ const script = `tmp=$(mktemp); curl -fsSL ${server}/install.sh -o "$tmp" && bash "$tmp"`;
154
+ return {
155
+ shell: 'sh',
156
+ args: ['-lc', script],
157
+ manualCommand: script,
158
+ };
159
+ }
160
+
143
161
  // --- Auth helpers ---
144
162
 
145
163
  function readAuth() {
@@ -347,6 +365,47 @@ program
347
365
  process.exit(1);
348
366
  });
349
367
 
368
+ // Command: update
369
+ program
370
+ .command('update')
371
+ .description('Update the npm-installed Drafted MCP daemon')
372
+ .option('--dry-run', 'Print update instructions without starting the updater')
373
+ .option('--yes', 'Start the updater out-of-process')
374
+ .action((options) => {
375
+ const update = buildUpdateCommand();
376
+ const data = {
377
+ started: false,
378
+ command: 'drafted update --yes',
379
+ dryRunCommand: 'drafted update --dry-run',
380
+ manualCommand: update.manualCommand,
381
+ restartRequired: true,
382
+ restartGuidance: 'Restart your agent/editor after updating so it starts the new drafted-mcp.',
383
+ };
384
+
385
+ if (options.yes && !options.dryRun) {
386
+ const child = spawn(update.shell, update.args, {
387
+ detached: true,
388
+ stdio: 'ignore',
389
+ });
390
+ child.unref();
391
+ data.started = true;
392
+ }
393
+
394
+ jsonOut(true, 'update', data);
395
+
396
+ if (data.started) {
397
+ console.log('Drafted updater started in the background.');
398
+ } else {
399
+ console.log('Drafted updater was not started.');
400
+ console.log('Run this command to update:');
401
+ console.log(` ${data.command}`);
402
+ console.log('');
403
+ console.log('Manual updater command:');
404
+ console.log(` ${data.manualCommand}`);
405
+ }
406
+ console.log(data.restartGuidance);
407
+ });
408
+
350
409
  // Command: logout
351
410
  program
352
411
  .command('logout')
package/mcp/server.mjs CHANGED
@@ -186,6 +186,56 @@ const TOOL_ANNOTATIONS = {
186
186
  wiki: { title: 'Wiki', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Per-org wiki. Markdown pages with paths as hierarchy. Dispatch by `action`.' },
187
187
  };
188
188
 
189
+ function isMutatingToolCall(name, args = {}) {
190
+ const action = args?.action;
191
+ switch (name) {
192
+ case 'project':
193
+ return ['create', 'update', 'move'].includes(action);
194
+ case 'template':
195
+ return ['create', 'update', 'delete', 'fork'].includes(action);
196
+ case 'layer':
197
+ return ['add', 'update', 'remove', 'reorder'].includes(action);
198
+ case 'frame':
199
+ return ![
200
+ 'read', 'search', 'versions', 'read_version',
201
+ 'get_sheet', 'read_sheet_values',
202
+ 'get_doc', 'read_doc_content',
203
+ 'get_slide', 'read_slide_content',
204
+ 'get_excel', 'read_excel_range',
205
+ ].includes(action);
206
+ case 'asset':
207
+ return ['upload', 'rm'].includes(action);
208
+ case 'skill':
209
+ return ['add', 'update', 'remove', 'attach', 'detach', 'favorite', 'unfavorite', 'update_file'].includes(action);
210
+ case 'wiki':
211
+ return ['log', 'write', 'edit', 'mv', 'rm', 'source-register', 'bulk-write'].includes(action);
212
+ case 'rm':
213
+ case 'shape':
214
+ case 'group':
215
+ case 'connector':
216
+ case 'layout':
217
+ return true;
218
+ default:
219
+ return false;
220
+ }
221
+ }
222
+
223
+ async function getRequiredMcpUpdateError(name, args = {}) {
224
+ if (mcpMode() !== 'stdio') return null;
225
+ if (!isMutatingToolCall(name, args)) return null;
226
+ const updateMetadata = await getMcpUpdateMetadata();
227
+ if (!updateMetadata?.required) return null;
228
+ const instructions = buildInstalledMcpUpdateInstructions(updateMetadata);
229
+ return [
230
+ `Drafted MCP update required before running ${name}${args?.action ? `.${args.action}` : ''}.`,
231
+ `Current version: ${instructions.currentVersion || PACKAGE_VERSION}.`,
232
+ instructions.latestVersion ? `Latest version: ${instructions.latestVersion}.` : null,
233
+ instructions.minimumRequiredVersion ? `Minimum required version: ${instructions.minimumRequiredVersion}.` : null,
234
+ `Run: ${instructions.command}`,
235
+ `Then restart your agent/editor so it starts the updated drafted-mcp.`,
236
+ ].filter(Boolean).join(' ');
237
+ }
238
+
189
239
  function tool(name, descOrSchema, schemaOrHandler, handler) {
190
240
  const ann = TOOL_ANNOTATIONS[name];
191
241
  if (!ann) throw new Error(`MCP tool "${name}" missing entry in TOOL_ANNOTATIONS`);
@@ -221,6 +271,8 @@ function tool(name, descOrSchema, schemaOrHandler, handler) {
221
271
  trackUmamiEvent(UMAMI_EVENTS.MCP_TOOL_CALLED, { tool: name, projectId: state.projectId || undefined, source: 'mcp' });
222
272
  reportInstallationEvent(UMAMI_EVENTS.DRAFTED_MCP_REQUEST, { tool: name });
223
273
  try {
274
+ const requiredUpdateError = await getRequiredMcpUpdateError(name, args?.[0] || {});
275
+ if (requiredUpdateError) return err(new Error(requiredUpdateError));
224
276
  return await cb(...args);
225
277
  } finally {
226
278
  state.currentTool = previousTool;
@@ -1559,6 +1611,62 @@ function normalizeMcpUpdatePolicy(policy) {
1559
1611
  };
1560
1612
  }
1561
1613
 
1614
+ function buildInstalledMcpUpdateInstructions(updateMetadata = null) {
1615
+ const mode = updateMetadata?.mode || mcpMode();
1616
+ const restart = updateMetadata?.restart || {
1617
+ required: mode === 'stdio',
1618
+ guidance: 'Restart agents after updating the npm-installed Drafted MCP daemon.',
1619
+ };
1620
+
1621
+ if (mode !== 'stdio') {
1622
+ return {
1623
+ action: 'update_mcp',
1624
+ started: false,
1625
+ updateSupported: false,
1626
+ mode,
1627
+ currentVersion: updateMetadata?.currentVersion || PACKAGE_VERSION,
1628
+ latestVersion: updateMetadata?.latestVersion || null,
1629
+ updateAvailable: false,
1630
+ required: false,
1631
+ command: null,
1632
+ dryRunCommand: null,
1633
+ manualCommand: null,
1634
+ restart: {
1635
+ required: false,
1636
+ guidance: restart.guidance || 'Hosted HTTP MCP updates with the Drafted server deploy.',
1637
+ },
1638
+ note: 'This session is using hosted HTTP MCP, so there is no npm-installed stdio daemon to update on this machine.',
1639
+ };
1640
+ }
1641
+
1642
+ const server = getServerUrl().replace(/\/$/, '');
1643
+ const manualCommand = platform() === 'win32'
1644
+ ? `$tmp = Join-Path $env:TEMP "drafted-install.ps1"; Invoke-WebRequest -UseBasicParsing "${server}/install.ps1" -OutFile $tmp; powershell -NoProfile -ExecutionPolicy Bypass -File $tmp`
1645
+ : `tmp=$(mktemp); curl -fsSL ${server}/install.sh -o "$tmp" && bash "$tmp"`;
1646
+
1647
+ return {
1648
+ action: 'update_mcp',
1649
+ started: false,
1650
+ updateSupported: true,
1651
+ mode: 'stdio',
1652
+ currentVersion: updateMetadata?.currentVersion || PACKAGE_VERSION,
1653
+ latestVersion: updateMetadata?.latestVersion || null,
1654
+ recommendedVersion: updateMetadata?.recommendedVersion || null,
1655
+ minimumRequiredVersion: updateMetadata?.minimumRequiredVersion || null,
1656
+ updateAvailable: !!updateMetadata?.updateAvailable,
1657
+ required: !!updateMetadata?.required,
1658
+ command: 'drafted update --yes',
1659
+ dryRunCommand: 'drafted update --dry-run',
1660
+ manualCommand,
1661
+ restart: {
1662
+ required: true,
1663
+ guidance: restart.guidance || 'Restart agents after updating the npm-installed Drafted MCP daemon.',
1664
+ },
1665
+ note: 'This action is intentionally advisory: it does not replace the currently running MCP process. Run the command, then restart your agent/editor so it starts the updated drafted-mcp.',
1666
+ mcpUpdate: updateMetadata || null,
1667
+ };
1668
+ }
1669
+
1562
1670
  async function getMcpUpdateMetadata() {
1563
1671
  const mode = mcpMode();
1564
1672
  try {
@@ -1592,12 +1700,17 @@ async function getMcpUpdateMetadata() {
1592
1700
 
1593
1701
 
1594
1702
  tool('get_org', {
1595
- action: z.enum(['get', 'switch']).optional().describe('Default: "get" returns active org and member info. Use "switch" with orgId to change the active org without opening a project.'),
1703
+ action: z.enum(['get', 'switch', 'update_mcp']).optional().describe('Default: "get" returns active org and member info. Use "switch" with orgId to change the active org without opening a project. Use "update_mcp" to get explicit installed stdio MCP update instructions.'),
1596
1704
  orgId: z.string().optional().describe('[switch] target org ID to switch to. Must be one of the orgs the user is a member of.'),
1597
1705
  }, async (args = {}) => {
1598
1706
  try {
1599
1707
  const action = args.action || 'get';
1600
1708
 
1709
+ if (action === 'update_mcp') {
1710
+ const mcpUpdate = await getMcpUpdateMetadata();
1711
+ return ok(buildInstalledMcpUpdateInstructions(mcpUpdate));
1712
+ }
1713
+
1601
1714
  if (action === 'switch') {
1602
1715
  if (!args.orgId) throw new Error('orgId is required for action=switch');
1603
1716
  await api('POST', '/auth/switch-org', { orgId: args.orgId });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.7.22",
3
+ "version": "1.7.23",
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": [