drafted 1.17.18 → 1.18.1
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 +16 -28
- package/cli/drafted.mjs +34 -6
- package/mcp/server.mjs +89 -5
- package/package.json +16 -80
- package/skills/import-website-to-drafted.md +317 -0
- package/agent-instructions/global.md +0 -3
- package/cli/repo-scan.mjs +0 -146
- package/install-mcp.sh +0 -1269
- package/mcp/active-project-store.mjs +0 -81
- package/mcp/gates.mjs +0 -124
- package/mcp/test-file-path-text.mjs +0 -58
- package/mcp/test-org-guards.mjs +0 -172
- package/mcp/widgets/canvas-overview.html +0 -229
- package/mcp/widgets/frame-preview.html +0 -162
- package/plugin/commands/create-project.md +0 -20
- package/plugin/commands/create-skill.md +0 -18
- package/plugin/commands/extract.md +0 -16
- package/plugin/commands/improve-project-harness.md +0 -16
- package/plugin/commands/improve-skill.md +0 -14
- package/plugin/commands/improve-wiki.md +0 -14
- package/plugin/commands/ingest.md +0 -20
- package/plugin/commands/onboard-drafted.md +0 -17
- package/plugin/skills/drafted/SKILL.md +0 -88
- package/server/lib/umami.mjs +0 -162
- package/src/shared/excalidraw.mjs +0 -84
- package/src/shared/gate-budget.mjs +0 -30
- package/src/shared/minion-presets.mjs +0 -67
- package/src/shared/okf-log.mjs +0 -62
- package/src/shared/record-conformance.mjs +0 -167
- package/src/shared/test-excalidraw-merge.mjs +0 -53
- package/src/shared/wiki-excalidraw.mjs +0 -90
- /package/{src/shared → shared}/constants.mjs +0 -0
package/README.md
CHANGED
|
@@ -1,44 +1,32 @@
|
|
|
1
1
|
# Drafted
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
22
|
-
irm https://drafted.live/install.ps1 | iex # Windows
|
|
8
|
+
npm install -g drafted
|
|
23
9
|
```
|
|
24
10
|
|
|
25
|
-
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### As an MCP server (for Claude Desktop, Claude Code, Codex, Cursor)
|
|
26
14
|
|
|
27
|
-
|
|
15
|
+
The MCP server is automatically available after install:
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
21
|
+
### As a CLI
|
|
35
22
|
|
|
36
23
|
```bash
|
|
37
|
-
|
|
24
|
+
drafted login
|
|
25
|
+
drafted ls
|
|
26
|
+
drafted write designs/default/hero.html < design.html
|
|
38
27
|
```
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
- Email inbox (magic links): http://localhost:8025
|
|
42
|
-
- MinIO console: http://localhost:9001
|
|
29
|
+
## Links
|
|
43
30
|
|
|
44
|
-
|
|
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 '../
|
|
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; EVERY repo connected to ANY folder in the org is searchable and usable org-wide (no org-level repo — the union is the library). Skills from connected repos are readable via fs(read, path="/skills/<slug>") — fetched from git at read time, always fresh. 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
|
|
@@ -2925,8 +2927,15 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
2925
2927
|
}
|
|
2926
2928
|
case 'read': {
|
|
2927
2929
|
if (!slug) return err(new Error('read /skills/<slug>'));
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
+
let s = null;
|
|
2931
|
+
try { s = await api('GET', `/api/skills/slug/${slug}`, undefined, orgHeader); } catch { /* not in the skills table — fall through to the repo index */ }
|
|
2932
|
+
if (s) return ok(s?.content || '');
|
|
2933
|
+
// Phase 5 resolution: any repo connected to any folder in the org is
|
|
2934
|
+
// readable org-wide. Content is fetched from git at read time (never
|
|
2935
|
+
// stored) — always fresh from the tracked branch.
|
|
2936
|
+
const gitSkill = await api('GET', `/api/repos/skill-content/${encodeURIComponent(slug)}`, undefined, orgHeader).catch(() => null);
|
|
2937
|
+
if (gitSkill?.content != null) return ok(gitSkill.content);
|
|
2938
|
+
return err(new Error(`skill not found: ${slug} — not in the Drafted library and not indexed from any connected repo (repo(action="entries", kind="skill") lists what is indexed)`));
|
|
2930
2939
|
}
|
|
2931
2940
|
case 'write': {
|
|
2932
2941
|
if (!slug) return err(new Error('write /skills/<slug> requires a slug'));
|
|
@@ -2946,7 +2955,7 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
2946
2955
|
const description = args.description || existing?.description || `Reusable procedure: ${name.toLowerCase()}`;
|
|
2947
2956
|
const result = existing
|
|
2948
2957
|
? await api('PUT', `/api/skills/${existing.id}`, { content: text }, orgHeader)
|
|
2949
|
-
: await api('POST', '/api/skills', { slug, name, content: text, description }, orgHeader);
|
|
2958
|
+
: await api('POST', '/api/skills', { slug, name, content: text, description, ...(readme ? { readme } : {}) }, orgHeader);
|
|
2950
2959
|
// Writing an archived skill restores it (update flow un-archives).
|
|
2951
2960
|
if (result?.id && existing?.archived) {
|
|
2952
2961
|
try { await api('POST', `/api/skills/${result.id}/restore`, undefined, orgHeader); } catch { /* best-effort */ }
|
|
@@ -3234,6 +3243,81 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
3234
3243
|
|
|
3235
3244
|
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
3245
|
});
|
|
3246
|
+
|
|
3247
|
+
// ── repo tool: registered git repos (org substrate, Phase 4) ──────────
|
|
3248
|
+
// Agents reach the repos table through this tool. Content stays in git;
|
|
3249
|
+
// Drafted keeps a read-only index. `add`/`rescan` mutate; `list`/`entries`
|
|
3250
|
+
// are read-only and paginated with a compact mode (collection rule).
|
|
3251
|
+
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.', {
|
|
3252
|
+
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.'),
|
|
3253
|
+
gitUrl: z.string().optional().describe('[add] GitHub repo URL to link (e.g. https://github.com/owner/repo). Required for add.'),
|
|
3254
|
+
folder: z.string().optional().describe('[add] folder name to link the repo into (required for add; repos are folder-scoped).'),
|
|
3255
|
+
branch: z.string().optional().describe('[add] git branch to track (default: the repo default; fails loudly if the branch is missing).'),
|
|
3256
|
+
slug: z.string().optional().describe('[add] repo slug (defaults to the repo name from the URL); [rescan] the repo slug or UUID.'),
|
|
3257
|
+
repoId: z.string().optional().describe('[rescan] repo UUID (alternative to slug).'),
|
|
3258
|
+
query: z.string().optional().describe('[entries] search term (matches name/description/slug).'),
|
|
3259
|
+
kind: z.enum(['skill', 'identity']).optional().describe('[entries] filter to skill or identity.'),
|
|
3260
|
+
limit: z.number().int().min(1).max(100).optional().describe('page size (default 25, max 100).'),
|
|
3261
|
+
offset: z.number().int().min(0).optional().describe('page offset (default 0).'),
|
|
3262
|
+
compact: z.boolean().optional().describe('list/entries: return only identity fields {slug,name,gitUrl} / {slug,name,kind} (default false).'),
|
|
3263
|
+
org: z.string().optional().describe('Org (id or name) to scope this call; defaults to the session\'s working org.'),
|
|
3264
|
+
}, async (args) => {
|
|
3265
|
+
const { action, gitUrl, folder, branch, slug, repoId, query, kind, limit, offset, compact, org } = args;
|
|
3266
|
+
const orgHeader = org ? { 'X-Drafted-Org': org } : {};
|
|
3267
|
+
const cap = Math.min(Math.max(1, Math.floor(Number(limit) || 25)), 100);
|
|
3268
|
+
const start = Math.max(0, Math.floor(Number(offset) || 0));
|
|
3269
|
+
if (action === 'list') {
|
|
3270
|
+
const data = await api('GET', '/api/repos', undefined, orgHeader);
|
|
3271
|
+
const rows = Array.isArray(data?.repos) ? data.repos : [];
|
|
3272
|
+
const page = rows.slice(start, start + cap);
|
|
3273
|
+
return ok({
|
|
3274
|
+
totalAvailable: rows.length, offset: start, returned: page.length, truncated: start + page.length < rows.length,
|
|
3275
|
+
repos: page.map(compact ? (r => ({ slug: r.slug, gitUrl: r.gitUrl, branch: r.branch })) : (r => ({
|
|
3276
|
+
slug: r.slug, gitUrl: r.gitUrl, branch: r.branch, folder: r.folderName, defaultBranch: r.defaultBranch,
|
|
3277
|
+
skills: r.skillCount, identities: r.identityCount, lastScannedAt: r.lastScannedAt, lastScannedSha: r.lastScannedSha,
|
|
3278
|
+
}))),
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
if (action === 'add') {
|
|
3282
|
+
if (!gitUrl) return err(new Error('add requires gitUrl (e.g. https://github.com/owner/repo)'));
|
|
3283
|
+
if (!folder) return err(new Error('add requires folder (the folder name to link the repo into — repos are folder-scoped)'));
|
|
3284
|
+
// Resolve folder name -> id in the target org.
|
|
3285
|
+
const foldersRes = await api('GET', '/api/folders', undefined, orgHeader);
|
|
3286
|
+
const folders = Array.isArray(foldersRes) ? foldersRes : (foldersRes?.folders || []);
|
|
3287
|
+
const f = folders.find((x) => x.name === folder);
|
|
3288
|
+
if (!f) return err(new Error(`no folder named "${folder}" in this org`));
|
|
3289
|
+
const result = await api('POST', '/api/repos', { gitUrl, folderId: f.id, branch: branch || undefined, slug: slug || undefined }, orgHeader);
|
|
3290
|
+
return ok({ slug: result.slug, gitUrl: result.gitUrl, branch: result.branch, skills: result.skills, identities: result.identities });
|
|
3291
|
+
}
|
|
3292
|
+
if (action === 'rescan') {
|
|
3293
|
+
const id = repoId || await (async () => {
|
|
3294
|
+
if (!slug) return null;
|
|
3295
|
+
const data = await api('GET', '/api/repos', undefined, orgHeader);
|
|
3296
|
+
const rows = Array.isArray(data?.repos) ? data.repos : [];
|
|
3297
|
+
const found = rows.find((r) => r.slug === slug);
|
|
3298
|
+
return found?.id || null;
|
|
3299
|
+
})();
|
|
3300
|
+
if (!id) return err(new Error('rescan requires slug or repoId'));
|
|
3301
|
+
const result = await api('POST', `/api/repos/${encodeURIComponent(id)}/rescan`, undefined, orgHeader);
|
|
3302
|
+
return ok({ slug: result.slug, gitUrl: result.gitUrl, branch: result.branch, skills: result.skills, identities: result.identities, lastScannedSha: result.lastScannedSha, lastScannedAt: result.lastScannedAt });
|
|
3303
|
+
}
|
|
3304
|
+
if (action === 'entries') {
|
|
3305
|
+
const qs = new URLSearchParams();
|
|
3306
|
+
if (query) qs.set('q', query);
|
|
3307
|
+
if (kind) qs.set('kind', kind);
|
|
3308
|
+
const data = await api('GET', `/api/repos/entries${qs.toString() ? '?' + qs : ''}`, undefined, orgHeader);
|
|
3309
|
+
const rows = Array.isArray(data?.entries) ? data.entries : [];
|
|
3310
|
+
const page = rows.slice(start, start + cap);
|
|
3311
|
+
return ok({
|
|
3312
|
+
totalAvailable: rows.length, offset: start, returned: page.length, truncated: start + page.length < rows.length,
|
|
3313
|
+
entries: page.map(compact ? (e => ({ slug: e.slug, name: e.name, kind: e.kind })) : (e => ({
|
|
3314
|
+
slug: e.slug, name: e.name, kind: e.kind, description: e.description, sourcePath: e.sourcePath,
|
|
3315
|
+
readmePath: e.readmePath, status: e.status, statusReason: e.statusReason, repoSlug: e.repoSlug,
|
|
3316
|
+
}))),
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3319
|
+
return err(new Error(`unknown repo action: ${action}`));
|
|
3320
|
+
});
|
|
3237
3321
|
function normalizeMcpUpdatePolicy(policy) {
|
|
3238
3322
|
const severity = policy?.policy?.severity || 'unknown';
|
|
3239
3323
|
const updateAvailable = !!policy?.policy?.updateAvailable;
|
package/package.json
CHANGED
|
@@ -1,102 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.18.1",
|
|
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
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
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
|
}
|