drafted 1.17.17 → 1.18.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.
package/README.md CHANGED
@@ -1,44 +1,32 @@
1
1
  # Drafted
2
2
 
3
- Multi-tenant shared surface for AI-human collaboration. Drafted organizes agent-produced work into **projects** (frames on a zoomable real-time surface), **skills** (reusable operating procedures agents load), and a **wiki** (durable org knowledge) — with MCP tools for agents and a browser UI for humans.
4
-
5
- - Product vision and positioning: [PRODUCT.md](PRODUCT.md)
6
- - Architecture, development, and operations: [AGENTS.md](AGENTS.md)
7
- - Live: [drafted.live](https://drafted.live)
8
-
9
- ## OKF-native
10
-
11
- Drafted is a native producer and consumer of the **Open Knowledge Format (OKF) v0.1** ([GoogleCloudPlatform/knowledge-catalog](https://github.com/GoogleCloudPlatform/knowledge-catalog)):
12
-
13
- - **The org wiki is an OKF v0.1 bundle.** Every page carries conformant YAML frontmatter (required `type`, one-line `description`, `tags`, synthesized `timestamp`; unknown keys preserved). `index.md` at every level is synthesized, `log.md` keeps the date-grouped change history, links resolve with or without `.md`, and broken links are legal. The wiki UI shows a live OKF conformance badge.
14
- - **Bundle exchange everywhere.** Export/import the wiki (`GET /api/wiki/export.tar.gz`, `POST /api/wiki/import`), the skill library (`skills/<slug>/SKILL.md` layout), and whole projects (`<layer>/<lane>/<file>.md` concepts; markdown links round-trip as connectors) — via HTTP or the MCP `wiki`, `skill`, and `project` tools, with dry-run reports.
3
+ Shared surface for AI-human collaboration. MCP server and CLI for organizing agent work into projects, skills, and durable knowledge.
15
4
 
16
5
  ## Install
17
6
 
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
-
20
7
  ```bash
21
- curl -fsSL https://drafted.live/install.sh | bash # macOS / Linux
22
- irm https://drafted.live/install.ps1 | iex # Windows
8
+ npm install -g drafted
23
9
  ```
24
10
 
25
- > Adding the MCP server on its own (e.g. `claude mcp add`) gives you the tools without the skill or the commands.
11
+ ## Usage
12
+
13
+ ### As an MCP server (for Claude Desktop, Claude Code, Codex, Cursor)
26
14
 
27
- **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.
15
+ The MCP server is automatically available after install:
28
16
 
29
- > 1. Settings → Customize plugins → Add → Add marketplace → `https://github.com/ddfourtwo/drafted-web` → install **Drafted**.
30
- > 2. Settings → Connectors → find **Drafted** → **Connect**, then approve the sign-in.
31
- >
32
- > Both halves matter: the plugin carries the skill and commands, the connector carries the tools. Installing the plugin does not sign you in to the connector.
17
+ ```bash
18
+ drafted-mcp
19
+ ```
33
20
 
34
- ## Quick start (developing Drafted itself)
21
+ ### As a CLI
35
22
 
36
23
  ```bash
37
- npm install && npm run dev # Docker services + schema + server with hot reload
24
+ drafted login
25
+ drafted ls
26
+ drafted write designs/default/hero.html < design.html
38
27
  ```
39
28
 
40
- - App: http://localhost:3477
41
- - Email inbox (magic links): http://localhost:8025
42
- - MinIO console: http://localhost:9001
29
+ ## Links
43
30
 
44
- Sign in with any email and grab the magic link from Mailpit.
31
+ - **App**: https://drafted.live
32
+ - **Docs**: https://drafted.live
package/cli/drafted.mjs CHANGED
@@ -1679,7 +1679,7 @@ function emitSkillResult(format, obj) {
1679
1679
 
1680
1680
  skillCmd
1681
1681
  .command('add')
1682
- .description('Create a skill in Drafted from stdin JSON {name,description,content,tags?,triggerPatterns?}')
1682
+ .description('Create a skill in Drafted from stdin JSON {name,description,content,readme,tags?,triggerPatterns?} — a README.md (readme field) is required (Phase 1 gate)')
1683
1683
  .option('--format <fmt>', 'output format: json or text', 'text')
1684
1684
  .action(async (opts) => {
1685
1685
  requireLogin();
@@ -1688,7 +1688,7 @@ skillCmd
1688
1688
  const server = getServerUrl().replace(/\/$/, '');
1689
1689
  const res = await authFetch(`${server}/api/skills`, {
1690
1690
  method: 'POST', headers: { 'Content-Type': 'application/json' },
1691
- body: JSON.stringify({ name: p.name, description: p.description, content: p.content, tags: p.tags, triggerPatterns: p.triggerPatterns, setup: p.setup }),
1691
+ body: JSON.stringify({ name: p.name, description: p.description, content: p.content, tags: p.tags, triggerPatterns: p.triggerPatterns, setup: p.setup, readme: p.readme }),
1692
1692
  });
1693
1693
  const data = await res.json().catch(() => ({}));
1694
1694
  if (!res.ok) {
@@ -2125,10 +2125,11 @@ const repoCmd = program.command('repo').description('Registered git repos (org i
2125
2125
 
2126
2126
  repoCmd
2127
2127
  .command('add <url>')
2128
- .description('Link a GitHub repo to an org FOLDER — the server fetches it and indexes .agents/ skills/identities (repos are folder-scoped, never org-level)')
2128
+ .description('Link a GitHub repo to an org FOLDER — the server fetches it and indexes .agents/ skills/identities (repos are folder-scoped, never org-level). --branch points at a named branch (fails loudly if missing instead of guessing).')
2129
2129
  .option('--org <org>', 'resolve against this Drafted org (id or name); scopes per-request without switching the session')
2130
2130
  .option('--folder <name>', 'folder to link the repo into (required)')
2131
2131
  .option('--slug <slug>', 'repo slug (defaults to the repo name from the URL)')
2132
+ .option('--branch <name>', 'git branch to track (default: the repo default; fails loudly if the branch is missing)')
2132
2133
  .option('--description <desc>', 'short description')
2133
2134
  .option('--format <fmt>', 'output format: json or text', 'text')
2134
2135
  .action(async (url, opts) => {
@@ -2146,7 +2147,7 @@ repoCmd
2146
2147
  const folder = folders.find((f) => f.name === folderName);
2147
2148
  if (!folder) return fail(`no folder named "${folderName}" in this org`);
2148
2149
  // The server fetches + scans; the CLI only resolves the association.
2149
- const payload = { gitUrl: url, folderId: folder.id, slug: opts.slug, description: opts.description };
2150
+ const payload = { gitUrl: url, folderId: folder.id, slug: opts.slug, description: opts.description, branch: opts.branch };
2150
2151
  const res = await authFetch(`${server}/api/repos`, {
2151
2152
  method: 'POST',
2152
2153
  headers: { 'Content-Type': 'application/json', ...orgHeaders },
@@ -2158,7 +2159,7 @@ repoCmd
2158
2159
  if (opts.format === 'json') {
2159
2160
  console.log(JSON.stringify({ ok: true, command: 'repo:add', data: { ...data, created } }));
2160
2161
  } else {
2161
- console.log([created ? 'added' : 'updated', data.slug || '', data.gitUrl || url, folderName, `skills:${data.skills ?? 0}`, `identities:${data.identities ?? 0}`].join('\t'));
2162
+ console.log([created ? 'added' : 'updated', data.slug || '', data.gitUrl || url, folderName, `branch:${data.branch || '-'}`, `skills:${data.skills ?? 0}`, `identities:${data.identities ?? 0}`].join('\t'));
2162
2163
  }
2163
2164
  });
2164
2165
  repoCmd
@@ -2170,7 +2171,34 @@ repoCmd
2170
2171
  const data = await readApiGet('repo:list', '/api/repos', opts.org);
2171
2172
  const rows = data.repos || [];
2172
2173
  if (opts.format === 'json') { console.log(JSON.stringify(rows)); return; }
2173
- for (const r of rows) console.log([r.slug, r.folderName || '-', r.gitUrl, `skills:${r.skillCount ?? 0}`, `identities:${r.identityCount ?? 0}`].join('\t'));
2174
+ for (const r of rows) console.log([r.slug, r.folderName || '-', r.gitUrl, `branch:${r.branch || '-'}`, `skills:${r.skillCount ?? 0}`, `identities:${r.identityCount ?? 0}`].join('\t'));
2175
+ });
2176
+
2177
+ repoCmd
2178
+ .command('rescan <slugOrId>')
2179
+ .description('Re-fetch the tracked branch and refresh a repo\'s .agents/ index (idempotent; reports staleness via lastScannedAt/Sha)')
2180
+ .option('--org <org>', 'resolve against this Drafted org (id or name); scopes per-request without switching the session')
2181
+ .option('--format <fmt>', 'output format: json or text', 'text')
2182
+ .action(async (slugOrId, opts) => {
2183
+ requireLogin();
2184
+ const orgHeaders = opts.org ? { 'X-Drafted-Org': opts.org } : {};
2185
+ const server = getServerUrl().replace(/\/$/, '');
2186
+ // Resolve slug -> id via the list (same pattern as remove).
2187
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(slugOrId);
2188
+ let id = isUuid ? slugOrId : null;
2189
+ if (!id) {
2190
+ const lookup = await authFetch(`${server}/api/repos`, { headers: orgHeaders });
2191
+ const lj = await lookup.json().catch(() => ({}));
2192
+ if (!lookup.ok) { const m = lj.error || `HTTP ${lookup.status}`; if (opts.format === 'json') console.log(JSON.stringify({ ok: false, command: 'repo:rescan', error: m })); else console.log(['error', m].join('\t')); process.exit(1); }
2193
+ const found = (lj.repos || []).find((r) => r.slug === slugOrId);
2194
+ if (!found) { const m = `no repo with slug "${slugOrId}"`; if (opts.format === 'json') console.log(JSON.stringify({ ok: false, command: 'repo:rescan', error: m })); else console.log(['error', m].join('\t')); process.exit(1); }
2195
+ id = found.id;
2196
+ }
2197
+ const res = await authFetch(`${server}/api/repos/${encodeURIComponent(id)}/rescan`, { method: 'POST', headers: orgHeaders });
2198
+ const data = await res.json().catch(() => ({}));
2199
+ if (!res.ok) { const m = data.error || `HTTP ${res.status}`; if (opts.format === 'json') console.log(JSON.stringify({ ok: false, command: 'repo:rescan', error: m })); else console.log(['error', m].join('\t')); process.exit(1); }
2200
+ if (opts.format === 'json') console.log(JSON.stringify({ ok: true, command: 'repo:rescan', data }));
2201
+ else console.log(['rescaned', data.slug || '', data.branch || '-', `skills:${data.skills ?? 0}`, `identities:${data.identities ?? 0}`].join('\t'));
2174
2202
  });
2175
2203
 
2176
2204
  repoCmd
package/mcp/server.mjs CHANGED
@@ -18,7 +18,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
18
18
  import { z } from 'zod';
19
19
  import { registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
20
20
  import WebSocket from 'ws';
21
- import { LAYERS } from '../src/shared/constants.mjs';
21
+ import { LAYERS } from '../shared/constants.mjs';
22
22
  import { emptyExcalidrawScene, stringifyExcalidrawScene } from '../src/shared/excalidraw.mjs';
23
23
  import { formatOkfLogEntry, appendOkfLogEntry } from '../src/shared/okf-log.mjs';
24
24
  import { createGateState, markSearched, g1Block, g2Block, g3Block, selectWithinBudget, wouldExceedBudget, budgetError, formatWikiIndex, PROJECT_CONTEXT_BUDGET_CHARS } from './gates.mjs';
@@ -350,6 +350,7 @@ const TOOL_ANNOTATIONS = {
350
350
  minion: { title: 'Minions', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Manage Minions: checklist-driven intake surfaces that guide a consumer through a checklist (via a shareable /c/<slug> link) and write a producible into the project. Dispatch by `action`: meta, list, get, create, update, enable, disable, delete. QA your own Minions with test_start/test_say/test_resolve — drive the checklist conversation yourself (works even when disabled). Requires the agent allowlist.' },
351
351
  trigger: { title: 'Inbound triggers', readOnlyHint: false, destructiveHint: true, openWorldHint: true, description: 'Manage inbound webhook triggers for the ACTIVE PROJECT: an external system (AppSheet bot, GitHub, form tool) POSTs to the trigger URL and the server runs an agent conversation in the project from the stored prompt template + payload. Dispatch by `action`: create (returns URL + secret token ONCE — relay it to the user immediately, not retrievable later), list, update (enable/disable, edit template, daily limit, executor), rotate (new token), test (fire a synthetic delivery), deliveries (audit log), delete; for executor="queue" triggers, pending/claim/complete let a LOCAL agent poll and work queued deliveries. Requires the agent allowlist.' },
352
352
  fs: { title: 'Filesystem', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Navigate Drafted like a local filesystem: /wiki/<path> pages, /skills/<slug> procedures, /projects/<folder?>/<project>/<layer>/<lane>/<file> frames. Verbs: ls, read, write, edit, mv, rm, search.' },
353
+ repo: { title: 'Git repos', readOnlyHint: false, destructiveHint: true, openWorldHint: true, description: 'Registered git repos — the org index of .agents/ skills + identities. A connected repo is the source of truth for its skills; authoring a Drafted skill whose slug collides with a repo-indexed skill returns 409 repo_owned pointing at the repo. Dispatch by `action`: list (paginated, compact mode), add (link a repo to a folder, --branch optional), rescan (re-fetch the tracked branch), entries (search the index). Content stays in git; Drafted keeps a read-only index.' },
353
354
  };
354
355
 
355
356
  function isMutatingToolCall(name, args = {}) {
@@ -2735,13 +2736,14 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
2735
2736
  ops: z.array(z.any()).optional().describe('[edit] hashline ops for text frames. Apply ONLY against a fresh fs(read) of the frame: each op = {type, lineHash, newContent} where type is one of replace | delete | insertAfter | insertBefore, and lineHash is the FULL anchor from the read output (line number + hash, e.g. the token left of the |, like 42srt) — NOT a bare hash. delete/replace consume the target line; at most one replace/delete per line per edit (combine into one replace). If an op targets a line that changed since read it is rejected — re-read and retry. Element ops for excalidraw ({id,x,y,...}), or structured ops for office'),
2736
2737
  state: z.any().optional().describe('[write] app-frame state (JSON) to persist for a deployed windowType:"app" frame; the canvas hydrates the app from it on load. Max 64KB.'),
2737
2738
  metadata: z.any().optional().describe('[write] JSON metadata to attach to the written frame (e.g. {"tour": {title, auto?, steps}} to define a guided tour on this frame). Max 64KB.'),
2739
+ readme: z.string().optional().describe('[write] README.md markdown — required when authoring a NEW skill at /o/<org>/skills/<slug> (the Phase 1 README gate rejects a new skill without one). Stored as a supporting skill file so the skill directory is self-documenting.'),
2738
2740
  recursive: z.boolean().optional().describe('[ls] recurse into subdirectories'),
2739
2741
  lines: z.string().optional().describe('[read] line range (e.g. "1-50") — partial read; content is hashline-annotated so a later edit stays surgical'),
2740
2742
  pattern: z.string().optional().describe('[ls] filter filenames (e.g. "*.html")'),
2741
2743
  org: z.string().optional().describe('Org (id or name) for bare /wiki and /skills paths (org-scoped /o/<org>/... paths don\'t need it). Per-request only; project paths self-derive org from the project row.'),
2742
2744
  projectId: PROJECT_OVERRIDE_PARAM,
2743
2745
  }, async (args) => {
2744
- const { action, path: rawPath, to: rawTo, query, content, file_path, base64, googleType, title, ops, state, metadata, recursive, pattern, lines, org } = args;
2746
+ const { action, path: rawPath, to: rawTo, query, content, file_path, base64, googleType, title, ops, state, metadata, readme, recursive, pattern, lines, org } = args;
2745
2747
  // Org-scoped path (Shape A): /o/<org>/<root>/... — strip the org segment here,
2746
2748
  // carry it as the per-request org scope (orgHeader), validate per-root below.
2747
2749
  // The PATH is the address; the org= param is legacy for bare roots only, and a
@@ -2946,7 +2948,7 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
2946
2948
  const description = args.description || existing?.description || `Reusable procedure: ${name.toLowerCase()}`;
2947
2949
  const result = existing
2948
2950
  ? await api('PUT', `/api/skills/${existing.id}`, { content: text }, orgHeader)
2949
- : await api('POST', '/api/skills', { slug, name, content: text, description }, orgHeader);
2951
+ : await api('POST', '/api/skills', { slug, name, content: text, description, ...(readme ? { readme } : {}) }, orgHeader);
2950
2952
  // Writing an archived skill restores it (update flow un-archives).
2951
2953
  if (result?.id && existing?.archived) {
2952
2954
  try { await api('POST', `/api/skills/${result.id}/restore`, undefined, orgHeader); } catch { /* best-effort */ }
@@ -3234,6 +3236,81 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
3234
3236
 
3235
3237
  return err(new Error(`unknown fs path: ${rawPath} — expected /o/<org>/wiki/..., /o/<org>/skills/..., or /o/<org>/projects/... (bare /wiki, /skills, /projects still work)`));
3236
3238
  });
3239
+
3240
+ // ── repo tool: registered git repos (org substrate, Phase 4) ──────────
3241
+ // Agents reach the repos table through this tool. Content stays in git;
3242
+ // Drafted keeps a read-only index. `add`/`rescan` mutate; `list`/`entries`
3243
+ // are read-only and paginated with a compact mode (collection rule).
3244
+ tool('repo', 'Registered git repos — the org index of .agents/ skills + identities. A connected repo is the source of truth for its skills (git owns content, Drafted renders + indexes). Dispatch by `action`:\n- `list` — the org\'s linked repos (paginated, compact mode).\n- `add` — link a GitHub repo to a folder; the server fetches + indexes .agents/. `branch` points at a named branch (fails loudly if missing).\n- `rescan` — re-fetch the tracked branch and refresh the index.\n- `entries` — search the index across all the org\'s repos (skills + identities).\n\nAuthoring a Drafted skill whose slug collides with a repo-indexed skill returns 409 repo_owned pointing at the repo — commit to git instead.', {
3245
+ action: z.enum(['list', 'add', 'rescan', 'entries']).describe('list: org repos; add: link a repo; rescan: re-fetch the tracked branch; entries: search the index.'),
3246
+ gitUrl: z.string().optional().describe('[add] GitHub repo URL to link (e.g. https://github.com/owner/repo). Required for add.'),
3247
+ folder: z.string().optional().describe('[add] folder name to link the repo into (required for add; repos are folder-scoped).'),
3248
+ branch: z.string().optional().describe('[add] git branch to track (default: the repo default; fails loudly if the branch is missing).'),
3249
+ slug: z.string().optional().describe('[add] repo slug (defaults to the repo name from the URL); [rescan] the repo slug or UUID.'),
3250
+ repoId: z.string().optional().describe('[rescan] repo UUID (alternative to slug).'),
3251
+ query: z.string().optional().describe('[entries] search term (matches name/description/slug).'),
3252
+ kind: z.enum(['skill', 'identity']).optional().describe('[entries] filter to skill or identity.'),
3253
+ limit: z.number().int().min(1).max(100).optional().describe('page size (default 25, max 100).'),
3254
+ offset: z.number().int().min(0).optional().describe('page offset (default 0).'),
3255
+ compact: z.boolean().optional().describe('list/entries: return only identity fields {slug,name,gitUrl} / {slug,name,kind} (default false).'),
3256
+ org: z.string().optional().describe('Org (id or name) to scope this call; defaults to the session\'s working org.'),
3257
+ }, async (args) => {
3258
+ const { action, gitUrl, folder, branch, slug, repoId, query, kind, limit, offset, compact, org } = args;
3259
+ const orgHeader = org ? { 'X-Drafted-Org': org } : {};
3260
+ const cap = Math.min(Math.max(1, Math.floor(Number(limit) || 25)), 100);
3261
+ const start = Math.max(0, Math.floor(Number(offset) || 0));
3262
+ if (action === 'list') {
3263
+ const data = await api('GET', '/api/repos', undefined, orgHeader);
3264
+ const rows = Array.isArray(data?.repos) ? data.repos : [];
3265
+ const page = rows.slice(start, start + cap);
3266
+ return ok({
3267
+ totalAvailable: rows.length, offset: start, returned: page.length, truncated: start + page.length < rows.length,
3268
+ repos: page.map(compact ? (r => ({ slug: r.slug, gitUrl: r.gitUrl, branch: r.branch })) : (r => ({
3269
+ slug: r.slug, gitUrl: r.gitUrl, branch: r.branch, folder: r.folderName, defaultBranch: r.defaultBranch,
3270
+ skills: r.skillCount, identities: r.identityCount, lastScannedAt: r.lastScannedAt, lastScannedSha: r.lastScannedSha,
3271
+ }))),
3272
+ });
3273
+ }
3274
+ if (action === 'add') {
3275
+ if (!gitUrl) return err(new Error('add requires gitUrl (e.g. https://github.com/owner/repo)'));
3276
+ if (!folder) return err(new Error('add requires folder (the folder name to link the repo into — repos are folder-scoped)'));
3277
+ // Resolve folder name -> id in the target org.
3278
+ const foldersRes = await api('GET', '/api/folders', undefined, orgHeader);
3279
+ const folders = Array.isArray(foldersRes) ? foldersRes : (foldersRes?.folders || []);
3280
+ const f = folders.find((x) => x.name === folder);
3281
+ if (!f) return err(new Error(`no folder named "${folder}" in this org`));
3282
+ const result = await api('POST', '/api/repos', { gitUrl, folderId: f.id, branch: branch || undefined, slug: slug || undefined }, orgHeader);
3283
+ return ok({ slug: result.slug, gitUrl: result.gitUrl, branch: result.branch, skills: result.skills, identities: result.identities });
3284
+ }
3285
+ if (action === 'rescan') {
3286
+ const id = repoId || await (async () => {
3287
+ if (!slug) return null;
3288
+ const data = await api('GET', '/api/repos', undefined, orgHeader);
3289
+ const rows = Array.isArray(data?.repos) ? data.repos : [];
3290
+ const found = rows.find((r) => r.slug === slug);
3291
+ return found?.id || null;
3292
+ })();
3293
+ if (!id) return err(new Error('rescan requires slug or repoId'));
3294
+ const result = await api('POST', `/api/repos/${encodeURIComponent(id)}/rescan`, undefined, orgHeader);
3295
+ return ok({ slug: result.slug, gitUrl: result.gitUrl, branch: result.branch, skills: result.skills, identities: result.identities, lastScannedSha: result.lastScannedSha, lastScannedAt: result.lastScannedAt });
3296
+ }
3297
+ if (action === 'entries') {
3298
+ const qs = new URLSearchParams();
3299
+ if (query) qs.set('q', query);
3300
+ if (kind) qs.set('kind', kind);
3301
+ const data = await api('GET', `/api/repos/entries${qs.toString() ? '?' + qs : ''}`, undefined, orgHeader);
3302
+ const rows = Array.isArray(data?.entries) ? data.entries : [];
3303
+ const page = rows.slice(start, start + cap);
3304
+ return ok({
3305
+ totalAvailable: rows.length, offset: start, returned: page.length, truncated: start + page.length < rows.length,
3306
+ entries: page.map(compact ? (e => ({ slug: e.slug, name: e.name, kind: e.kind })) : (e => ({
3307
+ slug: e.slug, name: e.name, kind: e.kind, description: e.description, sourcePath: e.sourcePath,
3308
+ readmePath: e.readmePath, status: e.status, statusReason: e.statusReason, repoSlug: e.repoSlug,
3309
+ }))),
3310
+ });
3311
+ }
3312
+ return err(new Error(`unknown repo action: ${action}`));
3313
+ });
3237
3314
  function normalizeMcpUpdatePolicy(policy) {
3238
3315
  const severity = policy?.policy?.severity || 'unknown';
3239
3316
  const updateAvailable = !!policy?.policy?.updateAvailable;
package/package.json CHANGED
@@ -1,102 +1,38 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.17.17",
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.",
3
+ "version": "1.18.0",
4
+ "description": "Shared surface for AI-human collaboration MCP server and CLI",
5
5
  "type": "module",
6
- "files": [
7
- "cli/",
8
- "mcp/",
9
- "server/lib/umami.mjs",
10
- "src/shared/",
11
- "agent-instructions/",
12
- "plugin/skills/",
13
- "plugin/commands/",
14
- "install-mcp.sh"
15
- ],
16
6
  "bin": {
17
7
  "drafted": "./cli/drafted.mjs",
18
8
  "drafted-mcp": "./mcp/server.mjs"
19
9
  },
20
- "scripts": {
21
- "dev": "./dev.sh",
22
- "dev:server": "tsx watch --import ./server/instrument.mjs server/server.mjs",
23
- "dev:local": "node server/server-local.mjs",
24
- "install-global": "npm install -g .",
25
- "db:migrate": "psql $DATABASE_URL -f drizzle/migrate.sql",
26
- "db:push": "drizzle-kit push",
27
- "db:studio": "drizzle-kit studio",
28
- "migrate-fs": "tsx server/migrate-fs.ts",
29
- "db:clean-sessions": "tsx src/auth/cleanup.ts",
30
- "import:legacy": "tsx scripts/import-legacy.mts",
31
- "test": "vitest run",
32
- "test:watch": "vitest",
33
- "version": "node scripts/sync-versions.mjs && git add plugin/.claude-plugin/plugin.json .claude-plugin/marketplace.json web-plugin/plugin/.claude-plugin/plugin.json web-plugin/.claude-plugin/marketplace.json",
34
- "version:check": "node scripts/sync-versions.mjs",
35
- "postpublish": "bash scripts/sync-plugin.sh \"chore: sync plugin to v$npm_package_version\" && bash scripts/sync-web-plugin.sh \"chore: sync web-plugin to v$npm_package_version\"",
36
- "deploy:check:google": "node scripts/check-google-drive-deploy.mjs",
37
- "build:excalidraw": "node scripts/build-excalidraw-editor.mjs",
38
- "build:office-viewer": "node scripts/build-office-viewer.mjs",
39
- "prepublishOnly": "node scripts/check-npm-package.mjs",
40
- "test:marketing": "node server/lib/marketing/test-marketing.mjs"
41
- },
10
+ "files": [
11
+ "mcp/",
12
+ "cli/",
13
+ "shared/",
14
+ "skills/",
15
+ "README.md"
16
+ ],
42
17
  "dependencies": {
43
- "@aws-sdk/client-s3": "^3.1007.0",
44
- "@aws-sdk/s3-request-presigner": "^3.1008.0",
45
- "@clerk/backend": "^3.2.12",
46
- "@clerk/express": "^2.1.4",
47
- "@clerk/mcp-tools": "^0.3.1",
48
- "@excalidraw/excalidraw": "^0.18.1",
49
- "@modelcontextprotocol/ext-apps": "^1.6.0",
50
18
  "@modelcontextprotocol/sdk": "^1.27.1",
51
- "@sentry/browser": "^10.53.1",
52
- "@sentry/node": "^10.53.1",
53
- "@xmldom/xmldom": "^0.9.10",
54
19
  "commander": "^11.1.0",
55
- "dagre": "^0.8.5",
56
- "docx": "^9.7.1",
57
- "dotenv": "^17.3.1",
58
- "drizzle-orm": "^0.45.1",
59
- "esbuild": "^0.28.0",
60
- "exceljs": "^4.4.0",
61
- "express": "^4.18.2",
62
- "jszip": "^3.10.1",
63
- "lucide": "^1.16.0",
64
- "mdast-util-from-markdown": "^2.0.3",
65
- "multer": "^2.1.1",
66
- "nodemailer": "^8.0.2",
67
- "parse5": "^8.0.1",
68
- "pg": "^8.20.0",
69
- "playwright": "^1.58.2",
70
- "puppeteer": "^24.37.5",
71
20
  "qrcode-terminal": "^0.12.0",
72
- "react": "^18.3.1",
73
- "react-dom": "^18.3.1",
74
- "sharp": "^0.34.5",
75
- "ws": "^8.21.3",
76
- "yaml": "^2.8.3",
21
+ "ws": "^8.16.0",
77
22
  "zod": "^4.3.6"
78
23
  },
79
24
  "keywords": [
25
+ "drafted",
80
26
  "design",
81
- "canvas",
82
- "preview",
83
- "html",
84
- "claude"
27
+ "mcp",
28
+ "claude",
29
+ "ai",
30
+ "canvas"
85
31
  ],
86
32
  "author": "ddfourtwo",
87
33
  "repository": {
88
34
  "type": "git",
89
35
  "url": "git+https://github.com/ddfourtwo/drafted.git"
90
36
  },
91
- "license": "MIT",
92
- "devDependencies": {
93
- "@types/express": "^5.0.6",
94
- "@types/nodemailer": "^7.0.11",
95
- "@types/pg": "^8.18.0",
96
- "docx-preview": "^0.3.7",
97
- "drizzle-kit": "^0.31.9",
98
- "tsx": "^4.19.0",
99
- "typescript": "^5.9.3",
100
- "vitest": "^3.2.4"
101
- }
37
+ "license": "MIT"
102
38
  }