drafted 1.3.0 → 1.4.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.
Files changed (2) hide show
  1. package/mcp/server.mjs +43 -2
  2. package/package.json +1 -1
package/mcp/server.mjs CHANGED
@@ -1772,7 +1772,7 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
1772
1772
  // skill gate; mutations require org-level wiki-maintainer skills loaded.
1773
1773
 
1774
1774
  tool('wiki', 'Per-org wiki. Markdown pages with paths as hierarchy. You and other agents/humans share maintenance — every edit broadcasts live, and edits from others appear in `recent` and on `read`.\n\nBefore mutating: check `recent` and `search` for relevant existing pages. Before mv/rm: check `links` (or pass `dryRun=true`). After completing a logical session of work, append a `log` entry.\n\nThe tool handles bookkeeping you\'d otherwise forget: `mv` rewrites inbound references via the link index, `read` shows who edited last and when. Use `health` to find unlinked pages and broken links.\n\n**Skill gate:** the org may attach a `wiki-maintainer` skill that you MUST load before mutations. If you get a skill-gate error, run skill(action="load", skill="wiki-maintainer") then retry.', {
1775
- action: z.enum(['ls', 'recent', 'read', 'search', 'links', 'log', 'health', 'write', 'edit', 'mv', 'rm', 'source-register', 'source-list', 'source-get']).describe('Operation to perform.'),
1775
+ action: z.enum(['ls', 'recent', 'read', 'search', 'links', 'log', 'health', 'write', 'edit', 'mv', 'rm', 'source-register', 'source-list', 'source-get', 'bulk-write']).describe('Operation to perform.'),
1776
1776
  path: z.string().optional().describe('[ls|read|links] wiki path. For ls: default / (root). For read: required. For links: required.'),
1777
1777
  recursive: z.boolean().optional().describe('[ls] list recursively with depth indicators'),
1778
1778
  limit: z.number().optional().describe('[recent|search] max results (recent default 10, search default 25)'),
@@ -1797,6 +1797,8 @@ tool('wiki', 'Per-org wiki. Markdown pages with paths as hierarchy. You and othe
1797
1797
  contentType: z.string().optional().describe('[source-register] MIME type (informational)'),
1798
1798
  size: z.number().optional().describe('[source-register] byte size (informational)'),
1799
1799
  sourceId: z.string().optional().describe('[source-get] source ID returned from source-register'),
1800
+ manifest_path: z.string().optional().describe('[bulk-write] absolute path to a JSON file containing {pages: [{path, title, content, type?, frontmatter?}, ...]}. The MCP wrapper reads the file locally and posts the array to the server in a single request — avoids round-tripping every page through tool args.'),
1801
+ pages: z.array(z.any()).optional().describe('[bulk-write] alternative to manifest_path: pass the pages array inline.'),
1800
1802
  }, async (args) => {
1801
1803
  try {
1802
1804
  const { action } = args;
@@ -1813,7 +1815,7 @@ tool('wiki', 'Per-org wiki. Markdown pages with paths as hierarchy. You and othe
1813
1815
  // Ensure the wiki-maintainer skill is attached to this org BEFORE the
1814
1816
  // gate check, so the gate fires reliably on the very first wiki call —
1815
1817
  // not just after the org has visited /wiki in a browser. Idempotent.
1816
- const MUTATING = new Set(['write', 'edit', 'mv', 'rm', 'log', 'source-register']);
1818
+ const MUTATING = new Set(['write', 'edit', 'mv', 'rm', 'log', 'source-register', 'bulk-write']);
1817
1819
  if (MUTATING.has(action)) {
1818
1820
  try { await api('POST', '/api/wiki/_ensure-skill'); } catch { /* non-fatal */ }
1819
1821
  const skillErr = await checkOrgSkills(orgId, action);
@@ -2049,6 +2051,45 @@ tool('wiki', 'Per-org wiki. Markdown pages with paths as hierarchy. You and othe
2049
2051
  return ok(withOrg({ deleted: true, path: normalized, id: page.id, brokenReferences: broken }));
2050
2052
  }
2051
2053
 
2054
+ // ── bulk-write ──────────────────────────────────────────────
2055
+ // Commit many pages in a single transaction. The MCP wrapper reads
2056
+ // the manifest file locally and posts the pages array inline to
2057
+ // /api/wiki/pages/bulk — avoids paying a tool-call round-trip per
2058
+ // page and keeps the orchestrator's context clean.
2059
+ case 'bulk-write': {
2060
+ const { manifest_path, pages: inlinePages } = args;
2061
+ let pages;
2062
+ if (manifest_path) {
2063
+ if (!existsSync(manifest_path)) throw new Error(`manifest not found: ${manifest_path}`);
2064
+ const text = readFileSync(manifest_path, 'utf8');
2065
+ let parsed;
2066
+ try { parsed = JSON.parse(text); } catch (e) { throw new Error(`invalid JSON in ${manifest_path}: ${e.message}`); }
2067
+ // Accept either {pages: [...]} or a bare array
2068
+ pages = Array.isArray(parsed) ? parsed : parsed.pages;
2069
+ } else if (Array.isArray(inlinePages)) {
2070
+ pages = inlinePages;
2071
+ } else {
2072
+ throw new Error('bulk-write requires either manifest_path or pages array');
2073
+ }
2074
+ if (!Array.isArray(pages) || pages.length === 0) throw new Error('pages array is empty');
2075
+ // Strip surplus fields the server ignores; keep payload tight.
2076
+ const payload = pages.map((p) => ({
2077
+ path: p.path,
2078
+ title: p.title,
2079
+ content: p.content,
2080
+ type: p.type,
2081
+ frontmatter: p.frontmatter,
2082
+ }));
2083
+ const result = await api('POST', '/api/wiki/pages/bulk', { pages: payload });
2084
+ return ok(withOrg({
2085
+ created: result.created?.length ?? 0,
2086
+ updated: result.updated?.length ?? 0,
2087
+ createdPaths: (result.created ?? []).map((r) => r.path),
2088
+ updatedPaths: (result.updated ?? []).map((r) => r.path),
2089
+ errors: result.errors ?? [],
2090
+ }));
2091
+ }
2092
+
2052
2093
  // ── source-register ────────────────────────────────────────
2053
2094
  // Register an external source (paper, article, transcript) by content
2054
2095
  // hash. Idempotent: same hash + same org returns the existing source
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Drafted CLI - Design preview server for Claude agents",
5
5
  "type": "module",
6
6
  "files": [