drafted 1.7.20 → 1.7.22
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 +10 -2
- package/mcp/server.mjs +229 -13
- package/package.json +1 -1
package/install-mcp.sh
CHANGED
|
@@ -217,8 +217,16 @@ step "Installing Drafted"
|
|
|
217
217
|
|
|
218
218
|
npm install -g drafted@latest --force
|
|
219
219
|
hash -r 2>/dev/null || true
|
|
220
|
-
NPM_ROOT="$(npm root -g)"
|
|
221
|
-
|
|
220
|
+
NPM_ROOT="$(npm root -g 2>/dev/null || true)"
|
|
221
|
+
MCP_SERVER_MODULE="$NPM_ROOT/drafted/mcp/server.mjs"
|
|
222
|
+
if [ -n "$NPM_ROOT" ] && [ -f "$MCP_SERVER_MODULE" ]; then
|
|
223
|
+
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"
|
|
224
|
+
elif [ "${DRAFTED_HEADLESS:-}" = "1" ]; then
|
|
225
|
+
echo -e " ${DIM}Skipping package import check in headless installer test.${RESET}"
|
|
226
|
+
else
|
|
227
|
+
echo -e " ${RED}Could not find installed MCP server module at $MCP_SERVER_MODULE${RESET}"
|
|
228
|
+
exit 1
|
|
229
|
+
fi
|
|
222
230
|
ok "Installed $(drafted --version 2>/dev/null || echo 'drafted') via npm"
|
|
223
231
|
|
|
224
232
|
# ── Configure ─────────────────────────────────────────────────────
|
package/mcp/server.mjs
CHANGED
|
@@ -164,7 +164,7 @@ const TOOL_ANNOTATIONS = {
|
|
|
164
164
|
|
|
165
165
|
// Frames — filesystem
|
|
166
166
|
ls: { title: 'List frames', readOnlyHint: true, destructiveHint: false, openWorldHint: false, widgetUri: 'ui://widget/drafted-canvas-overview.html' },
|
|
167
|
-
frame: { title: 'Frames', readOnlyHint: false, destructiveHint: true, openWorldHint:
|
|
167
|
+
frame: { title: 'Frames', readOnlyHint: false, destructiveHint: true, openWorldHint: true, widgetUri: 'ui://widget/drafted-frame-preview.html', description: 'Read, write, edit, move, anchor, search, restore frame versions, create Google Workspace frames, or write values into Google Sheet frames in the ACTIVE PROJECT. Dispatch by `action`. Use `ls` to browse, `rm` to delete.' },
|
|
168
168
|
rm: { title: 'Delete frame', readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
169
169
|
// batch: { title: 'Batch operations', readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
170
170
|
|
|
@@ -1528,6 +1528,69 @@ async function getGoogleDriveAvailability() {
|
|
|
1528
1528
|
}
|
|
1529
1529
|
}
|
|
1530
1530
|
|
|
1531
|
+
function normalizeMcpUpdatePolicy(policy) {
|
|
1532
|
+
const severity = policy?.policy?.severity || 'unknown';
|
|
1533
|
+
const updateAvailable = !!policy?.policy?.updateAvailable;
|
|
1534
|
+
const required = !!policy?.policy?.required;
|
|
1535
|
+
return {
|
|
1536
|
+
status: required ? 'required' : updateAvailable ? 'stale' : severity === 'unknown' ? 'unknown' : 'current',
|
|
1537
|
+
currentVersion: policy?.versions?.client || PACKAGE_VERSION,
|
|
1538
|
+
latestVersion: policy?.versions?.latest || null,
|
|
1539
|
+
recommendedVersion: policy?.versions?.recommended || null,
|
|
1540
|
+
minimumRequiredVersion: policy?.versions?.minimumRequired || null,
|
|
1541
|
+
severity,
|
|
1542
|
+
updateAvailable,
|
|
1543
|
+
stale: updateAvailable,
|
|
1544
|
+
required,
|
|
1545
|
+
reason: policy?.policy?.reason || null,
|
|
1546
|
+
enabled: policy?.policy?.enabled !== false,
|
|
1547
|
+
mode: policy?.package?.mcpMode || mcpMode(),
|
|
1548
|
+
distribution: policy?.package?.distribution || (mcpMode() === 'stdio' ? 'npm-stdio' : 'hosted-http'),
|
|
1549
|
+
update: {
|
|
1550
|
+
command: policy?.update?.command || null,
|
|
1551
|
+
helper: policy?.update?.helper || null,
|
|
1552
|
+
packageManager: policy?.update?.packageManager || 'npm',
|
|
1553
|
+
},
|
|
1554
|
+
restart: {
|
|
1555
|
+
required: !!policy?.restart?.required,
|
|
1556
|
+
guidance: policy?.restart?.guidance || null,
|
|
1557
|
+
},
|
|
1558
|
+
checkedAt: policy?.serverTimestamp || null,
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
async function getMcpUpdateMetadata() {
|
|
1563
|
+
const mode = mcpMode();
|
|
1564
|
+
try {
|
|
1565
|
+
const query = new URLSearchParams({
|
|
1566
|
+
cliVersion: PACKAGE_VERSION,
|
|
1567
|
+
mcpMode: mode,
|
|
1568
|
+
});
|
|
1569
|
+
const policy = await api('GET', `/api/installations/latest?${query.toString()}`);
|
|
1570
|
+
return normalizeMcpUpdatePolicy(policy);
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
return {
|
|
1573
|
+
status: 'unknown',
|
|
1574
|
+
currentVersion: PACKAGE_VERSION,
|
|
1575
|
+
latestVersion: null,
|
|
1576
|
+
recommendedVersion: null,
|
|
1577
|
+
minimumRequiredVersion: null,
|
|
1578
|
+
severity: 'unknown',
|
|
1579
|
+
updateAvailable: false,
|
|
1580
|
+
stale: false,
|
|
1581
|
+
required: false,
|
|
1582
|
+
reason: 'latest_check_failed',
|
|
1583
|
+
enabled: false,
|
|
1584
|
+
mode,
|
|
1585
|
+
distribution: mode === 'stdio' ? 'npm-stdio' : 'hosted-http',
|
|
1586
|
+
update: { command: null, helper: null, packageManager: 'npm' },
|
|
1587
|
+
restart: { required: false, guidance: 'Drafted MCP update status is unavailable; get_org still succeeded.' },
|
|
1588
|
+
checkedAt: null,
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
|
|
1531
1594
|
tool('get_org', {
|
|
1532
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.'),
|
|
1533
1596
|
orgId: z.string().optional().describe('[switch] target org ID to switch to. Must be one of the orgs the user is a member of.'),
|
|
@@ -1549,7 +1612,8 @@ tool('get_org', {
|
|
|
1549
1612
|
const orgs = (await api('GET', '/api/orgs')).orgs || [];
|
|
1550
1613
|
const activeOrg = (orgs || []).map(o => ({ id: o.orgId || o.id, name: o.orgName || o.name })).find(o => o.id === me?.orgId) || null;
|
|
1551
1614
|
const googleDrive = await getGoogleDriveAvailability();
|
|
1552
|
-
|
|
1615
|
+
const mcpUpdate = await getMcpUpdateMetadata();
|
|
1616
|
+
return ok({ switched: true, activeOrg, googleDrive, mcpVersion: PACKAGE_VERSION, mcpUpdate, note: 'Active org switched. Wiki and skill calls now target this org. Active project cleared — open a project (or stay org-scoped for wiki/skill). If googleDrive.connected is true, prefer Google Workspace frames for docs, sheets, and slides.' });
|
|
1553
1617
|
}
|
|
1554
1618
|
|
|
1555
1619
|
// Source of truth = THIS MCP session's bound org (what mutations will actually
|
|
@@ -1563,6 +1627,7 @@ tool('get_org', {
|
|
|
1563
1627
|
const activeOrg = sessionOrgId ? (orgs.find(o => o.id === sessionOrgId) || null) : null;
|
|
1564
1628
|
|
|
1565
1629
|
const googleDrive = await getGoogleDriveAvailability();
|
|
1630
|
+
const mcpUpdate = await getMcpUpdateMetadata();
|
|
1566
1631
|
|
|
1567
1632
|
let members = [];
|
|
1568
1633
|
if (sessionOrgId) {
|
|
@@ -1577,6 +1642,7 @@ tool('get_org', {
|
|
|
1577
1642
|
members: members.map(m => ({ id: m.userId, name: m.username, email: m.email, role: m.role })),
|
|
1578
1643
|
googleDrive,
|
|
1579
1644
|
mcpVersion: PACKAGE_VERSION,
|
|
1645
|
+
mcpUpdate,
|
|
1580
1646
|
note: "activeOrg is the org bound to THIS MCP session — what mutations will actually target. To switch orgs without creating a project, call get_org(action=\"switch\", orgId=\"...\"). Browser tabs and other MCP sessions for the same user can be on different orgs. If googleDrive.connected is true, strongly prefer Google Workspace frames for docs, sheets, and slides.",
|
|
1581
1647
|
});
|
|
1582
1648
|
} catch (error) { return err(error); }
|
|
@@ -1584,18 +1650,41 @@ tool('get_org', {
|
|
|
1584
1650
|
|
|
1585
1651
|
// ── Filesystem tools (direct HTTP to /api/fs) ─────────────────────
|
|
1586
1652
|
|
|
1587
|
-
tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by path, frame URL, or UUID), write (new frame or overwrite), write_excalidraw (native editable Excalidraw diagram), edit (hashline ops), mv (rename/move), anchor (mark as required-read for the layer), search (match frame names). Use project(action="open") first. For listing use `ls`, for deletion use `rm`.\n\n**Write — content,
|
|
1588
|
-
action: z.enum(['read', 'write', 'write_excalidraw', 'edit', 'mv', 'anchor', 'search', 'versions', 'read_version', 'restore_version']).describe('Operation to perform.'),
|
|
1653
|
+
tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by path, frame URL, or UUID), write (new frame or overwrite), Google Sheet actions (`get_sheet`, `read_sheet_values`, `write_sheet_values`, `append_sheet_rows`, `clear_sheet_range`, `update_sheet`), Google Doc actions (`get_doc`, `read_doc_content`, `write_doc_content`, `append_doc_content`, `clear_doc_content`, `update_doc`), Google Slide actions (`get_slide`, `read_slide_content`, `write_slide_content`, `append_slides`, `clear_slides`, `update_slide`), write_excalidraw (native editable Excalidraw diagram), edit (hashline ops), mv (rename/move), anchor (mark as required-read for the layer), search (match frame names). Use project(action="open") first. For listing use `ls`, for deletion use `rm`.\n\n**Google Workspace native content:** Create or attach Google Docs/Sheets/Slides with `frame(action="write", googleType=...)`. After that, populate native Google Docs with `write_doc_content`/`append_doc_content` and native Google Slides with `write_slide_content`/`append_slides`; read them with `read_doc_content`/`read_slide_content`. Do NOT use inline `frame.write(content)` or hashline `frame.edit` to populate Google Doc/Slide frames — those actions are for Drafted inline frame files, not native Workspace document bodies.\n\n**Write — content, binary, or Google Workspace frame:** Provide exactly one of `content` (HTML/markdown/text), `file_path` (absolute local file), `base64` (base64-encoded binary with optional `content_type`), or `googleType` (`google-doc`, `google-sheet`, `google-slide`). Call get_org first; when `googleDrive.connected` is true, strongly prefer Google Workspace frames for docs, sheets, and slides in that org. For inline content, filename extension matters: use `.html` for complete HTML documents and `.md` for Markdown. Never place a full HTML document in a `.md` or extensionless frame. For a new Google file, pass `googleType` and optional `title`; for an existing Google file, pass `googleType` plus `url` or `googleId`. For binary frames (images, PDFs, videos), use `file_path` when the file is local to the MCP host, or `base64` when the caller already has binary bytes.\n\n**Write — dimensions:** By default, frames use the layer\'s default size (e.g. 1440×900 for designs, 1440×3000 for wireframes). Often too large for small content. Use `autoSize: true` to measure HTML content and size to fit, or pass explicit `width`/`height`.', {
|
|
1654
|
+
action: z.enum(['read', 'write', 'write_sheet_values', 'read_sheet_values', 'append_sheet_rows', 'clear_sheet_range', 'get_sheet', 'update_sheet', 'get_doc', 'read_doc_content', 'write_doc_content', 'append_doc_content', 'clear_doc_content', 'update_doc', 'get_slide', 'read_slide_content', 'write_slide_content', 'append_slides', 'clear_slides', 'update_slide', 'write_excalidraw', 'edit', 'mv', 'anchor', 'search', 'versions', 'read_version', 'restore_version']).describe('Operation to perform. Use native Doc/Slide actions for Google Docs/Slides; do not use inline write/edit for native Workspace content.'),
|
|
1589
1655
|
path: z.string().optional().describe('[read] /{layer}/{lane}/{filename}, frame URL, or UUID. [write|edit|anchor] /{layer}/{lane}/{filename}.'),
|
|
1590
1656
|
lines: z.string().optional().describe('[read] line range (e.g. "1-50"). Omit to read all.'),
|
|
1591
|
-
content: z.string().optional().describe('[write] HTML/markdown/text
|
|
1657
|
+
content: z.string().optional().describe('[write] HTML/markdown/text for Drafted inline frames. [write_doc_content|append_doc_content] native Google Doc body text. Do not use action=write content to populate Google Doc/Slide frames.'),
|
|
1592
1658
|
excalidraw_data: z.any().optional().describe('[write_excalidraw] Excalidraw scene JSON object or JSON string. Defaults to an empty scene.'),
|
|
1593
1659
|
mermaid: z.string().optional().describe('[write_excalidraw] Mermaid source to convert into an editable Excalidraw scene.'),
|
|
1594
|
-
file_path: z.string().optional().describe('[write] absolute path to a local file to upload. Mutually exclusive with content.'),
|
|
1660
|
+
file_path: z.string().optional().describe('[write] absolute path to a local file to upload. Mutually exclusive with content/base64/googleType.'),
|
|
1661
|
+
base64: z.string().optional().describe('[write] base64-encoded binary content. Mutually exclusive with content/file_path/googleType. Use with content_type when known.'),
|
|
1662
|
+
content_type: z.string().optional().describe('[write + base64] MIME type for base64 binary content, e.g. image/png, application/pdf. Defaults from the path extension or application/octet-stream.'),
|
|
1595
1663
|
googleType: z.enum(['google-doc', 'google-sheet', 'google-slide']).optional().describe('[write] Create or attach a native Google Workspace frame. Use with title to create new, or url/googleId to attach existing.'),
|
|
1596
|
-
title: z.string().optional().describe('[write + googleType] Title for a new
|
|
1597
|
-
url: z.string().optional().describe('[write + googleType] Existing Google
|
|
1598
|
-
googleId: z.string().optional().describe('[write + googleType] Existing Google file ID to attach.'),
|
|
1664
|
+
title: z.string().optional().describe('[write + googleType] Title for a new native file. Defaults to filename from path.'),
|
|
1665
|
+
url: z.string().optional().describe('[write + googleType] Existing Google file URL to attach.'),
|
|
1666
|
+
googleId: z.string().optional().describe('[write + googleType] Existing Google file ID to attach. [Sheet/Doc/Slide actions] Native Google file ID to use when not resolving from path/frame.'),
|
|
1667
|
+
format: z.enum(['plain_text', 'markdown']).optional().describe('[write_doc_content|append_doc_content] Source format hint. Currently plain_text and minimal markdown are accepted as text.'),
|
|
1668
|
+
mode: z.enum(['replace', 'append', 'clear']).optional().describe('[Doc/Slide content actions] Optional mode hint for clients; prefer the explicit write/append/clear action names.'),
|
|
1669
|
+
slides: z.array(z.object({
|
|
1670
|
+
title: z.string().optional(),
|
|
1671
|
+
bullets: z.array(z.string()).optional(),
|
|
1672
|
+
speakerNotes: z.string().optional(),
|
|
1673
|
+
layout: z.string().optional(),
|
|
1674
|
+
}).passthrough()).optional().describe('[write_slide_content|append_slides] Structured slide spec: [{ title, bullets, speakerNotes?, layout? }].'),
|
|
1675
|
+
requests: z.array(z.any()).optional().describe('[update_doc|update_slide] Raw Google Docs/Slides batchUpdate requests for advanced updates only; common Doc/Slide population should use write_doc_content/append_doc_content/write_slide_content/append_slides.'),
|
|
1676
|
+
slideObjectIds: z.array(z.string()).optional().describe('[clear_slides] Optional slide object IDs to delete. Omit to clear all slides.'),
|
|
1677
|
+
range: z.string().optional().describe('[Sheet value actions] A1 range, e.g. Sheet1!A1 or Data!A:Z.'),
|
|
1678
|
+
values: z.array(z.array(z.any())).optional().describe('[write_sheet_values|append_sheet_rows] 2D array of row values.'),
|
|
1679
|
+
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).optional().describe('[write_sheet_values|append_sheet_rows] Google Sheets value input option. Defaults to USER_ENTERED.'),
|
|
1680
|
+
majorDimension: z.enum(['ROWS', 'COLUMNS']).optional().describe('[Sheet value actions] Major dimension for values. Defaults to ROWS when writing/appending.'),
|
|
1681
|
+
valueRenderOption: z.enum(['FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA']).optional().describe('[read_sheet_values] How values should be rendered. Defaults to Google Sheets API default.'),
|
|
1682
|
+
dateTimeRenderOption: z.enum(['SERIAL_NUMBER', 'FORMATTED_STRING']).optional().describe('[read_sheet_values] How dates/times should be rendered.'),
|
|
1683
|
+
insertDataOption: z.enum(['OVERWRITE', 'INSERT_ROWS']).optional().describe('[append_sheet_rows] How new rows are inserted. Defaults to INSERT_ROWS.'),
|
|
1684
|
+
operation: z.enum(['add_sheet', 'rename_sheet']).optional().describe('[update_sheet] Sheet tab operation.'),
|
|
1685
|
+
sheetTitle: z.string().optional().describe('[update_sheet add_sheet] Title for the new sheet tab.'),
|
|
1686
|
+
sheetId: z.number().optional().describe('[update_sheet rename_sheet] Numeric sheet/tab id.'),
|
|
1687
|
+
newTitle: z.string().optional().describe('[update_sheet rename_sheet] New title for the sheet tab.'),
|
|
1599
1688
|
autoSize: z.boolean().optional().describe('[write] measure HTML content and size frame to fit. Content only, not file_path.'),
|
|
1600
1689
|
width: z.number().optional().describe('[write] explicit width in pixels. Overrides layer default. Ignored if autoSize=true.'),
|
|
1601
1690
|
height: z.number().optional().describe('[write] explicit height in pixels. Overrides layer default. Ignored if autoSize=true.'),
|
|
@@ -1621,6 +1710,26 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
1621
1710
|
// write landed before it can develop a wrong assumption.
|
|
1622
1711
|
const projectCtx = getCurrentProjectContext();
|
|
1623
1712
|
const withProject = (result) => ({ ...result, project: projectCtx });
|
|
1713
|
+
const workspaceBody = (kind) => {
|
|
1714
|
+
const { path, googleId } = args;
|
|
1715
|
+
const body = {};
|
|
1716
|
+
const frameUrlMatch = path?.match(/\/f\/([a-f0-9-]{36})/);
|
|
1717
|
+
const uuidMatch = path?.match(/^[a-f0-9-]{36}$/);
|
|
1718
|
+
if (googleId) {
|
|
1719
|
+
if (kind === 'sheet') body.spreadsheetId = googleId;
|
|
1720
|
+
else if (kind === 'doc') body.documentId = googleId;
|
|
1721
|
+
else body.presentationId = googleId;
|
|
1722
|
+
} else if (frameUrlMatch?.[1] || uuidMatch) {
|
|
1723
|
+
body.frameId = frameUrlMatch?.[1] || path;
|
|
1724
|
+
} else if (path) {
|
|
1725
|
+
body.path = path;
|
|
1726
|
+
body.projectId = getState().projectId;
|
|
1727
|
+
} else {
|
|
1728
|
+
const label = kind === 'sheet' ? 'Google Sheet' : kind === 'doc' ? 'Google Doc' : 'Google Slide';
|
|
1729
|
+
throw new Error(`Provide path to a ${label} frame, frame ID/URL, or googleId/native file ID`);
|
|
1730
|
+
}
|
|
1731
|
+
return body;
|
|
1732
|
+
};
|
|
1624
1733
|
switch (action) {
|
|
1625
1734
|
case 'read': {
|
|
1626
1735
|
const { path, lines } = args;
|
|
@@ -1660,12 +1769,117 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
1660
1769
|
_meta: { frameHtml: result.content },
|
|
1661
1770
|
});
|
|
1662
1771
|
}
|
|
1772
|
+
case 'get_sheet':
|
|
1773
|
+
case 'read_sheet_values':
|
|
1774
|
+
case 'write_sheet_values':
|
|
1775
|
+
case 'append_sheet_rows':
|
|
1776
|
+
case 'clear_sheet_range':
|
|
1777
|
+
case 'update_sheet': {
|
|
1778
|
+
const { path, googleId, range, values, valueInputOption, majorDimension, valueRenderOption, dateTimeRenderOption, insertDataOption, operation, sheetTitle, sheetId, newTitle } = args;
|
|
1779
|
+
const body = workspaceBody('sheet');
|
|
1780
|
+
if (range) body.range = range;
|
|
1781
|
+
if (majorDimension) body.majorDimension = majorDimension;
|
|
1782
|
+
if (action === 'write_sheet_values' || action === 'append_sheet_rows') {
|
|
1783
|
+
if (!values || !Array.isArray(values)) throw new Error(`values required for action=${action}`);
|
|
1784
|
+
body.values = values;
|
|
1785
|
+
body.valueInputOption = valueInputOption || 'USER_ENTERED';
|
|
1786
|
+
body.majorDimension = majorDimension || 'ROWS';
|
|
1787
|
+
}
|
|
1788
|
+
if (action === 'read_sheet_values') {
|
|
1789
|
+
if (valueRenderOption) body.valueRenderOption = valueRenderOption;
|
|
1790
|
+
if (dateTimeRenderOption) body.dateTimeRenderOption = dateTimeRenderOption;
|
|
1791
|
+
}
|
|
1792
|
+
if (action === 'append_sheet_rows') body.insertDataOption = insertDataOption || 'INSERT_ROWS';
|
|
1793
|
+
if (action === 'update_sheet') {
|
|
1794
|
+
body.operation = operation;
|
|
1795
|
+
if (sheetTitle) body.sheetTitle = sheetTitle;
|
|
1796
|
+
if (sheetId != null) body.sheetId = sheetId;
|
|
1797
|
+
if (newTitle) body.newTitle = newTitle;
|
|
1798
|
+
}
|
|
1799
|
+
const endpoint = action === 'get_sheet'
|
|
1800
|
+
? '/api/google/workspace/sheet'
|
|
1801
|
+
: action === 'read_sheet_values'
|
|
1802
|
+
? '/api/google/workspace/sheet-values/read'
|
|
1803
|
+
: action === 'write_sheet_values'
|
|
1804
|
+
? '/api/google/workspace/sheet-values'
|
|
1805
|
+
: action === 'append_sheet_rows'
|
|
1806
|
+
? '/api/google/workspace/sheet-values/append'
|
|
1807
|
+
: action === 'clear_sheet_range'
|
|
1808
|
+
? '/api/google/workspace/sheet-values/clear'
|
|
1809
|
+
: '/api/google/workspace/sheet-update';
|
|
1810
|
+
const result = await api('POST', endpoint, body);
|
|
1811
|
+
return ok(withProject(result));
|
|
1812
|
+
}
|
|
1813
|
+
case 'get_doc':
|
|
1814
|
+
case 'read_doc_content':
|
|
1815
|
+
case 'write_doc_content':
|
|
1816
|
+
case 'append_doc_content':
|
|
1817
|
+
case 'clear_doc_content':
|
|
1818
|
+
case 'update_doc': {
|
|
1819
|
+
const { content, format, mode, requests } = args;
|
|
1820
|
+
const body = workspaceBody('doc');
|
|
1821
|
+
if (format) body.format = format;
|
|
1822
|
+
if (mode) body.mode = mode;
|
|
1823
|
+
if (action === 'write_doc_content' || action === 'append_doc_content') {
|
|
1824
|
+
if (typeof content !== 'string') throw new Error(`content string required for action=${action}`);
|
|
1825
|
+
body.content = content;
|
|
1826
|
+
}
|
|
1827
|
+
if (action === 'update_doc') {
|
|
1828
|
+
if (!Array.isArray(requests)) throw new Error('requests array required for action=update_doc');
|
|
1829
|
+
body.requests = requests;
|
|
1830
|
+
}
|
|
1831
|
+
const endpoint = action === 'get_doc'
|
|
1832
|
+
? '/api/google/workspace/doc'
|
|
1833
|
+
: action === 'read_doc_content'
|
|
1834
|
+
? '/api/google/workspace/doc-content/read'
|
|
1835
|
+
: action === 'write_doc_content'
|
|
1836
|
+
? '/api/google/workspace/doc-content'
|
|
1837
|
+
: action === 'append_doc_content'
|
|
1838
|
+
? '/api/google/workspace/doc-content/append'
|
|
1839
|
+
: action === 'clear_doc_content'
|
|
1840
|
+
? '/api/google/workspace/doc-content/clear'
|
|
1841
|
+
: '/api/google/workspace/doc-update';
|
|
1842
|
+
const result = await api('POST', endpoint, body);
|
|
1843
|
+
return ok(withProject(result));
|
|
1844
|
+
}
|
|
1845
|
+
case 'get_slide':
|
|
1846
|
+
case 'read_slide_content':
|
|
1847
|
+
case 'write_slide_content':
|
|
1848
|
+
case 'append_slides':
|
|
1849
|
+
case 'clear_slides':
|
|
1850
|
+
case 'update_slide': {
|
|
1851
|
+
const { slides, requests, slideObjectIds, mode } = args;
|
|
1852
|
+
const body = workspaceBody('slide');
|
|
1853
|
+
if (mode) body.mode = mode;
|
|
1854
|
+
if (action === 'write_slide_content' || action === 'append_slides') {
|
|
1855
|
+
if (!Array.isArray(slides)) throw new Error(`slides array required for action=${action}`);
|
|
1856
|
+
body.slides = slides;
|
|
1857
|
+
}
|
|
1858
|
+
if (action === 'clear_slides' && Array.isArray(slideObjectIds)) body.slideObjectIds = slideObjectIds;
|
|
1859
|
+
if (action === 'update_slide') {
|
|
1860
|
+
if (!Array.isArray(requests)) throw new Error('requests array required for action=update_slide');
|
|
1861
|
+
body.requests = requests;
|
|
1862
|
+
}
|
|
1863
|
+
const endpoint = action === 'get_slide'
|
|
1864
|
+
? '/api/google/workspace/slide'
|
|
1865
|
+
: action === 'read_slide_content'
|
|
1866
|
+
? '/api/google/workspace/slide-content/read'
|
|
1867
|
+
: action === 'write_slide_content'
|
|
1868
|
+
? '/api/google/workspace/slide-content'
|
|
1869
|
+
: action === 'append_slides'
|
|
1870
|
+
? '/api/google/workspace/slides/append'
|
|
1871
|
+
: action === 'clear_slides'
|
|
1872
|
+
? '/api/google/workspace/slides/clear'
|
|
1873
|
+
: '/api/google/workspace/slide-update';
|
|
1874
|
+
const result = await api('POST', endpoint, body);
|
|
1875
|
+
return ok(withProject(result));
|
|
1876
|
+
}
|
|
1663
1877
|
case 'write': {
|
|
1664
|
-
const { path, content, file_path, autoSize, width, height, color, googleType, title, url, googleId } = args;
|
|
1878
|
+
const { path, content, file_path, base64, content_type, autoSize, width, height, color, googleType, title, url, googleId } = args;
|
|
1665
1879
|
if (!path) throw new Error('path required for action=write');
|
|
1666
|
-
const writeSources = [content != null, !!file_path, !!googleType].filter(Boolean).length;
|
|
1667
|
-
if (writeSources > 1) throw new Error('Provide only one of content, file_path, or googleType');
|
|
1668
|
-
if (writeSources === 0) throw new Error('Provide content, file_path, or googleType');
|
|
1880
|
+
const writeSources = [content != null, !!file_path, base64 != null, !!googleType].filter(Boolean).length;
|
|
1881
|
+
if (writeSources > 1) throw new Error('Provide only one of content, file_path, base64, or googleType');
|
|
1882
|
+
if (writeSources === 0) throw new Error('Provide content, file_path, base64, or googleType');
|
|
1669
1883
|
const skillErr = await checkProjectSkills(getState().projectId);
|
|
1670
1884
|
if (skillErr) return err(new Error(skillErr));
|
|
1671
1885
|
const anchorErr = await checkAnchors(parseLayer(path));
|
|
@@ -1712,6 +1926,8 @@ tool('frame', 'Frame CRUD in the ACTIVE PROJECT. Dispatch by `action`: read (by
|
|
|
1712
1926
|
const MIME = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.pdf': 'application/pdf', '.mp4': 'video/mp4', '.webm': 'video/webm', '.mov': 'video/quicktime', '.m4v': 'video/x-m4v' };
|
|
1713
1927
|
body = { base64: buffer.toString('base64'), contentType: MIME[ext] || 'application/octet-stream' };
|
|
1714
1928
|
}
|
|
1929
|
+
} else if (base64 != null) {
|
|
1930
|
+
body = { base64, contentType: content_type || mimeFromExt(extname(parts[2])) };
|
|
1715
1931
|
} else {
|
|
1716
1932
|
body = { content };
|
|
1717
1933
|
if (autoSize) body.autoSize = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.22",
|
|
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": [
|