drafted 1.11.13 → 1.11.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/drafted.mjs +128 -0
- package/mcp/server.mjs +142 -3
- package/package.json +1 -1
package/cli/drafted.mjs
CHANGED
|
@@ -1899,4 +1899,132 @@ skillCmd
|
|
|
1899
1899
|
else console.log(`ok\t${id}\t${data.hash}\tpushed ${data.count}${data.stripped ? `, stripped ${data.stripped}` : ''}${gitignored ? ', gitignored .skillinstall/' : ''}`);
|
|
1900
1900
|
});
|
|
1901
1901
|
|
|
1902
|
+
// ── Collectors: management seam (parity with the MCP `collector` tool) ──
|
|
1903
|
+
// Collectors are checklist-driven intake surfaces. Management is behind the
|
|
1904
|
+
// agent allowlist (same gate as Minion) and scoped to the session's active org.
|
|
1905
|
+
function emitCollectorResult(format, obj) {
|
|
1906
|
+
if (format === 'json') { console.log(JSON.stringify(obj)); return; }
|
|
1907
|
+
console.log([obj.status, obj.id || '', obj.slug || '', obj.name || '', obj.error || ''].join('\t'));
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
async function collectorSetEnabled(id, enabled, format) {
|
|
1911
|
+
requireLogin();
|
|
1912
|
+
const server = getServerUrl().replace(/\/$/, '');
|
|
1913
|
+
const res = await authFetch(`${server}/api/collectors/${encodeURIComponent(id)}`, {
|
|
1914
|
+
method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled }),
|
|
1915
|
+
});
|
|
1916
|
+
const data = await res.json().catch(() => ({}));
|
|
1917
|
+
if (!res.ok) { emitCollectorResult(format, { status: res.status === 404 ? 'not-found' : 'error', error: data.error || `HTTP ${res.status}` }); process.exit(1); }
|
|
1918
|
+
const c = data.collector || {};
|
|
1919
|
+
emitCollectorResult(format, { status: 'ok', id: c.id, slug: c.slug, name: c.name });
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
const collectorCmd = program.command('collector').description('Collector management (checklist-driven intake surfaces). Requires the agent allowlist.');
|
|
1923
|
+
|
|
1924
|
+
collectorCmd
|
|
1925
|
+
.command('list')
|
|
1926
|
+
.description('List collectors (scoped to --project or your active project; all org collectors if neither)')
|
|
1927
|
+
.option('--project <id>', 'project to scope to (defaults to your active project)')
|
|
1928
|
+
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
1929
|
+
.action(async (opts) => {
|
|
1930
|
+
const pid = opts.project || getActiveProject()?.id;
|
|
1931
|
+
const data = await readApiGet('collector:list', withQuery('/api/collectors', { projectId: pid }));
|
|
1932
|
+
const rows = data.collectors || [];
|
|
1933
|
+
if (opts.format === 'json') { console.log(JSON.stringify(rows)); return; }
|
|
1934
|
+
for (const c of rows) console.log(`${c.id}\t${c.slug}\t${c.enabled ? 'enabled' : 'disabled'}\t${c.name}`);
|
|
1935
|
+
});
|
|
1936
|
+
|
|
1937
|
+
collectorCmd
|
|
1938
|
+
.command('get <id>')
|
|
1939
|
+
.description('Get a collector by id')
|
|
1940
|
+
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
1941
|
+
.action(async (id, opts) => {
|
|
1942
|
+
const data = await readApiGet('collector:get', `/api/collectors/${encodeURIComponent(id)}`);
|
|
1943
|
+
if (opts.format === 'json') { console.log(JSON.stringify(data.collector || data)); return; }
|
|
1944
|
+
const c = data.collector || {};
|
|
1945
|
+
console.log(`${c.id}\t${c.slug}\t${c.enabled ? 'enabled' : 'disabled'}\t${c.name}`);
|
|
1946
|
+
});
|
|
1947
|
+
|
|
1948
|
+
collectorCmd
|
|
1949
|
+
.command('meta')
|
|
1950
|
+
.description('Project layers/lanes/frames for building a collector target/output')
|
|
1951
|
+
.option('--project <id>', 'project (defaults to your active project)')
|
|
1952
|
+
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
1953
|
+
.action(async (opts) => {
|
|
1954
|
+
const pid = opts.project || getActiveProject()?.id;
|
|
1955
|
+
if (!pid) { console.error('No active project. Pass --project <id> or run `drafted use <project>`.'); process.exit(1); }
|
|
1956
|
+
const data = await readApiGet('collector:meta', withQuery('/api/collectors/meta', { projectId: pid }));
|
|
1957
|
+
console.log(JSON.stringify(data));
|
|
1958
|
+
});
|
|
1959
|
+
|
|
1960
|
+
collectorCmd
|
|
1961
|
+
.command('create')
|
|
1962
|
+
.description('Create a collector from stdin JSON {name,description?,target,checklist,output,enabled?}')
|
|
1963
|
+
.option('--project <id>', 'project to bind to (defaults to your active project)')
|
|
1964
|
+
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
1965
|
+
.action(async (opts) => {
|
|
1966
|
+
requireLogin();
|
|
1967
|
+
const pid = opts.project || getActiveProject()?.id;
|
|
1968
|
+
if (!pid) { emitCollectorResult(opts.format, { status: 'error', error: 'no project — pass --project <id> or run `drafted use <project>`' }); process.exit(1); }
|
|
1969
|
+
let p;
|
|
1970
|
+
try { p = readStdinJSON(); } catch { console.error('invalid JSON on stdin'); process.exit(1); }
|
|
1971
|
+
const server = getServerUrl().replace(/\/$/, '');
|
|
1972
|
+
const res = await authFetch(`${server}/api/collectors`, {
|
|
1973
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1974
|
+
body: JSON.stringify({ projectId: pid, name: p.name, description: p.description, target: p.target, checklist: p.checklist, output: p.output, enabled: p.enabled }),
|
|
1975
|
+
});
|
|
1976
|
+
const data = await res.json().catch(() => ({}));
|
|
1977
|
+
if (!res.ok) { emitCollectorResult(opts.format, { status: res.status === 403 ? 'forbidden' : 'error', error: data.error || `HTTP ${res.status}` }); process.exit(1); }
|
|
1978
|
+
const c = data.collector || {};
|
|
1979
|
+
emitCollectorResult(opts.format, { status: 'ok', id: c.id, slug: c.slug, name: c.name });
|
|
1980
|
+
});
|
|
1981
|
+
|
|
1982
|
+
collectorCmd
|
|
1983
|
+
.command('update <id>')
|
|
1984
|
+
.description('Update a collector from stdin JSON {name?,description?,target?,checklist?,output?,enabled?}')
|
|
1985
|
+
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
1986
|
+
.action(async (id, opts) => {
|
|
1987
|
+
requireLogin();
|
|
1988
|
+
let p;
|
|
1989
|
+
try { p = readStdinJSON(); } catch { console.error('invalid JSON on stdin'); process.exit(1); }
|
|
1990
|
+
const body = {};
|
|
1991
|
+
for (const k of ['name', 'description', 'target', 'checklist', 'output', 'enabled']) {
|
|
1992
|
+
if (p[k] !== undefined) body[k] = p[k];
|
|
1993
|
+
}
|
|
1994
|
+
if (Object.keys(body).length === 0) { emitCollectorResult(opts.format, { status: 'error', error: 'no fields to update' }); process.exit(1); }
|
|
1995
|
+
const server = getServerUrl().replace(/\/$/, '');
|
|
1996
|
+
const res = await authFetch(`${server}/api/collectors/${encodeURIComponent(id)}`, {
|
|
1997
|
+
method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
1998
|
+
});
|
|
1999
|
+
const data = await res.json().catch(() => ({}));
|
|
2000
|
+
if (!res.ok) { emitCollectorResult(opts.format, { status: res.status === 404 ? 'not-found' : 'error', error: data.error || `HTTP ${res.status}` }); process.exit(1); }
|
|
2001
|
+
const c = data.collector || {};
|
|
2002
|
+
emitCollectorResult(opts.format, { status: 'ok', id: c.id, slug: c.slug, name: c.name });
|
|
2003
|
+
});
|
|
2004
|
+
|
|
2005
|
+
collectorCmd
|
|
2006
|
+
.command('enable <id>')
|
|
2007
|
+
.description('Make a collector live')
|
|
2008
|
+
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
2009
|
+
.action((id, opts) => collectorSetEnabled(id, true, opts.format));
|
|
2010
|
+
|
|
2011
|
+
collectorCmd
|
|
2012
|
+
.command('disable <id>')
|
|
2013
|
+
.description('Take a collector offline')
|
|
2014
|
+
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
2015
|
+
.action((id, opts) => collectorSetEnabled(id, false, opts.format));
|
|
2016
|
+
|
|
2017
|
+
collectorCmd
|
|
2018
|
+
.command('delete <id>')
|
|
2019
|
+
.description('Delete a collector (past submissions are kept as history)')
|
|
2020
|
+
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
2021
|
+
.action(async (id, opts) => {
|
|
2022
|
+
requireLogin();
|
|
2023
|
+
const server = getServerUrl().replace(/\/$/, '');
|
|
2024
|
+
const res = await authFetch(`${server}/api/collectors/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
2025
|
+
const data = await res.json().catch(() => ({}));
|
|
2026
|
+
if (!res.ok) { emitCollectorResult(opts.format, { status: res.status === 404 ? 'not-found' : 'error', error: data.error || `HTTP ${res.status}` }); process.exit(1); }
|
|
2027
|
+
emitCollectorResult(opts.format, { status: 'ok', id: data.id || id });
|
|
2028
|
+
});
|
|
2029
|
+
|
|
1902
2030
|
program.parse();
|
package/mcp/server.mjs
CHANGED
|
@@ -139,6 +139,7 @@ const REMOTE_JSON_STRING_PARAMS = {
|
|
|
139
139
|
wiki: ['frontmatter', 'pages'],
|
|
140
140
|
project: ['layers'],
|
|
141
141
|
template: ['layers'],
|
|
142
|
+
collector: ['target', 'checklist', 'output'],
|
|
142
143
|
};
|
|
143
144
|
|
|
144
145
|
// Remove sentences that reference local-file params from a tool description,
|
|
@@ -242,6 +243,9 @@ const TOOL_ANNOTATIONS = {
|
|
|
242
243
|
// Skills
|
|
243
244
|
skill: { title: 'Skills', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Manage the Drafted skill library: search, load, add, update, remove, attach/detach from projects, favorite, and edit skill files.' },
|
|
244
245
|
wiki: { title: 'Wiki', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Per-org wiki. Markdown pages with paths as hierarchy. Dispatch by `action`.' },
|
|
246
|
+
|
|
247
|
+
// Collectors — checklist-driven, Minion-run intake surfaces bound to a project
|
|
248
|
+
collector: { title: 'Collectors', readOnlyHint: false, destructiveHint: true, openWorldHint: false, description: 'Manage Collectors: checklist-driven intake surfaces that guide a consumer through a checklist (via a shareable /c/<slug> link) and then write a producible into the project. Dispatch by `action`: meta (discover layers/lanes/frames), list, get, create, update, enable, disable, delete. Requires the agent allowlist (same gate as Minion).' },
|
|
245
249
|
};
|
|
246
250
|
|
|
247
251
|
function isMutatingToolCall(name, args = {}) {
|
|
@@ -267,6 +271,8 @@ function isMutatingToolCall(name, args = {}) {
|
|
|
267
271
|
return ['add', 'update', 'remove', 'attach', 'detach', 'favorite', 'unfavorite', 'update_file'].includes(action);
|
|
268
272
|
case 'wiki':
|
|
269
273
|
return ['log', 'write', 'edit', 'mv', 'rm', 'source-register', 'bulk-write'].includes(action);
|
|
274
|
+
case 'collector':
|
|
275
|
+
return ['create', 'update', 'enable', 'disable', 'delete'].includes(action);
|
|
270
276
|
case 'rm':
|
|
271
277
|
case 'shape':
|
|
272
278
|
case 'group':
|
|
@@ -361,7 +367,7 @@ function tool(name, descOrSchema, schemaOrHandler, handler) {
|
|
|
361
367
|
const v = args[0][k];
|
|
362
368
|
if (typeof v === 'string' && v.trim()) {
|
|
363
369
|
try { args[0][k] = JSON.parse(v); }
|
|
364
|
-
catch { return err(new Error(`Param "${k}" must be a JSON-encoded ${
|
|
370
|
+
catch { return err(new Error(`Param "${k}" must be a JSON-encoded ${['excalidraw_data', 'frontmatter', 'target', 'output'].includes(k) ? 'object' : 'array'} string; JSON.parse failed.`)); }
|
|
365
371
|
}
|
|
366
372
|
}
|
|
367
373
|
}
|
|
@@ -1224,7 +1230,13 @@ async function consumePendingDeviceCode() {
|
|
|
1224
1230
|
return false;
|
|
1225
1231
|
}
|
|
1226
1232
|
|
|
1227
|
-
|
|
1233
|
+
// Device-flow sign-in is stdio-only. Web/Cowork connectors authenticate via
|
|
1234
|
+
// OAuth at the transport layer (the hosted /mcp handler injects a Drafted
|
|
1235
|
+
// session per request), so advertising a device-flow auth tool there is
|
|
1236
|
+
// meaningless and disruptive — it returns spurious sign-in URLs and, on login,
|
|
1237
|
+
// spawns a server-side browser-open and blocks polling until timeout. Register
|
|
1238
|
+
// it only on stdio.
|
|
1239
|
+
if (!isRemote) tool('auth', 'Sign in to Drafted. `action=get_link` returns a verification URL immediately (use for SSH/headless/tmux where a browser may not open) and starts background polling; after the user opens the link, later Drafted tool calls also auto-consume the approved login. `action=login` opens a browser when needed and explicitly waits/polls for approval. If get_link was called first, login reuses that pending code instead of opening a new browser.', {
|
|
1228
1240
|
action: z.enum(['get_link', 'login']).describe('Operation to perform.'),
|
|
1229
1241
|
}, async ({ action }) => {
|
|
1230
1242
|
try {
|
|
@@ -1236,7 +1248,11 @@ tool('auth', 'Sign in to Drafted. `action=get_link` returns a verification URL i
|
|
|
1236
1248
|
return ok(data.verificationUrl);
|
|
1237
1249
|
}
|
|
1238
1250
|
if (action === 'login') {
|
|
1239
|
-
|
|
1251
|
+
// Prefer the active request session (injected by runWithRequestState on
|
|
1252
|
+
// remote, or cloneSession on stdio) over the on-disk bootstrap session, so
|
|
1253
|
+
// an already-authenticated caller short-circuits to already_authenticated
|
|
1254
|
+
// instead of starting a needless device flow.
|
|
1255
|
+
const existing = getState().sessionId || getBootstrapSessionId();
|
|
1240
1256
|
if (existing) {
|
|
1241
1257
|
try {
|
|
1242
1258
|
const res = await fetch(`${getServerUrl()}/auth/me`, {
|
|
@@ -3423,6 +3439,129 @@ tool('wiki', 'Per-org wiki. Markdown pages with paths as hierarchy. You and othe
|
|
|
3423
3439
|
} catch (error) { return err(error); }
|
|
3424
3440
|
});
|
|
3425
3441
|
|
|
3442
|
+
// ── Collectors ────────────────────────────────────────────────────
|
|
3443
|
+
|
|
3444
|
+
function compactCollectorEntry(c) {
|
|
3445
|
+
if (!c || typeof c !== 'object') return c;
|
|
3446
|
+
return { id: c.id, slug: c.slug, name: c.name, enabled: c.enabled, projectId: c.projectId };
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
// Shape a {collectors:[...]} list with limit/offset pagination + optional compact
|
|
3450
|
+
// mode, mirroring shapeSkillCatalog so large lists stay within token budget.
|
|
3451
|
+
function shapeCollectorList(result, { limit, offset = 0, compact = false } = {}) {
|
|
3452
|
+
if (!Array.isArray(result?.collectors)) return result;
|
|
3453
|
+
const total = result.collectors.length;
|
|
3454
|
+
const start = Math.max(0, Math.floor(Number(offset) || 0));
|
|
3455
|
+
const cap = Math.min(Math.max(1, Math.floor(Number(limit) || 25)), 100);
|
|
3456
|
+
const page = result.collectors.slice(start, start + cap);
|
|
3457
|
+
result.totalAvailable = total;
|
|
3458
|
+
result.offset = start;
|
|
3459
|
+
result.returned = page.length;
|
|
3460
|
+
result.truncated = start + page.length < total;
|
|
3461
|
+
result.collectors = compact ? page.map(compactCollectorEntry) : page;
|
|
3462
|
+
result.note = compact
|
|
3463
|
+
? 'Compact list: {id,slug,name,enabled,projectId} only. Use collector(action="get", id="<id>") for full config; limit/offset to page.'
|
|
3464
|
+
: 'Collectors are scoped to the active project (all org collectors when no project is open). Use limit/offset to page; compact=true for a leaner list.';
|
|
3465
|
+
return result;
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
// Friendlier message when the agent allowlist gate (requireAgentAccess) rejects.
|
|
3469
|
+
function collectorGateError(e) {
|
|
3470
|
+
if (e?.status === 403 || e?.code === 'agent_disabled') {
|
|
3471
|
+
return new Error('Collector management is not enabled for this org/account (agent allowlist). Ask an admin to add your org or email to DRAFTED_AGENT_ALLOWED_ORGS / DRAFTED_AGENT_ALLOWED_EMAILS.');
|
|
3472
|
+
}
|
|
3473
|
+
return e;
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
tool('collector', {
|
|
3477
|
+
action: z.enum(['meta', 'list', 'get', 'create', 'update', 'enable', 'disable', 'delete']).describe('Operation to perform.'),
|
|
3478
|
+
id: z.string().optional().describe('[get|update|enable|disable|delete] collector ID (UUID)'),
|
|
3479
|
+
projectId: z.string().optional().describe('[create|meta] project to bind/scope to (defaults to the active project). Operates on the session’s active org — open the target project first via project(action="open"), or get_org(action="switch") to change org.'),
|
|
3480
|
+
name: z.string().optional().describe('[create|update] collector name'),
|
|
3481
|
+
description: z.string().optional().describe('[create|update] one-line description shown to the consumer'),
|
|
3482
|
+
enabled: z.boolean().optional().describe('[create|update] whether the collector is live; a disabled collector 404s on its /c/<slug> link. enable/disable set this directly.'),
|
|
3483
|
+
target: z.object({
|
|
3484
|
+
type: z.enum(['new-record', 'layer', 'frame']).describe('what the run writes against'),
|
|
3485
|
+
layer: z.string().optional(),
|
|
3486
|
+
frameId: z.string().optional(),
|
|
3487
|
+
}).optional().describe('[create|update] where the run writes: a new record, into a layer, or updating a specific frame'),
|
|
3488
|
+
checklist: z.array(z.object({
|
|
3489
|
+
id: z.string().describe('stable item id'),
|
|
3490
|
+
label: z.string().describe('what the consumer is asked for'),
|
|
3491
|
+
description: z.string().optional(),
|
|
3492
|
+
evidence: z.enum(['text', 'file', 'photo', 'none']).optional().describe('evidence required (default none)'),
|
|
3493
|
+
required: z.boolean().optional(),
|
|
3494
|
+
})).optional().describe('[create|update] ordered checklist the Minion guides the consumer through'),
|
|
3495
|
+
output: z.object({
|
|
3496
|
+
mode: z.enum(['generate', 'update', 'annotate']).describe('how the producible is written'),
|
|
3497
|
+
layer: z.string().optional(),
|
|
3498
|
+
lane: z.string().optional(),
|
|
3499
|
+
filenameTemplate: z.string().optional().describe('e.g. "<id>.md"'),
|
|
3500
|
+
skillSlug: z.string().optional().describe('skill that shapes the produced frame'),
|
|
3501
|
+
grouping: z.string().optional().describe('"lane" gives each submission its own lane'),
|
|
3502
|
+
}).optional().describe('[create|update] where/how the producible lands'),
|
|
3503
|
+
limit: z.number().optional().describe('[list] max results per page (default 25, max 100)'),
|
|
3504
|
+
offset: z.number().optional().describe('[list] skip N results for pagination (default 0)'),
|
|
3505
|
+
compact: z.boolean().optional().describe('[list] return only {id,slug,name,enabled,projectId} per collector'),
|
|
3506
|
+
}, async (args) => {
|
|
3507
|
+
try {
|
|
3508
|
+
const { action } = args;
|
|
3509
|
+
switch (action) {
|
|
3510
|
+
case 'meta': {
|
|
3511
|
+
const active = getState().projectId;
|
|
3512
|
+
const pid = active || args.projectId;
|
|
3513
|
+
if (!pid) throw new Error('No active project. Call project(action="open") first, or pass projectId.');
|
|
3514
|
+
// api() auto-appends the active project; add projectId explicitly only when none is active.
|
|
3515
|
+
const path = active ? '/api/collectors/meta' : `/api/collectors/meta?projectId=${encodeURIComponent(pid)}`;
|
|
3516
|
+
return ok(await api('GET', path));
|
|
3517
|
+
}
|
|
3518
|
+
case 'list': {
|
|
3519
|
+
// api() auto-appends the active project as ?projectId — so this lists the
|
|
3520
|
+
// active project's collectors, or all org collectors when none is open.
|
|
3521
|
+
const result = await api('GET', '/api/collectors');
|
|
3522
|
+
return ok(shapeCollectorList(result, { limit: args.limit, offset: args.offset, compact: args.compact }));
|
|
3523
|
+
}
|
|
3524
|
+
case 'get': {
|
|
3525
|
+
if (!args.id) throw new Error('id is required for action=get');
|
|
3526
|
+
return ok(await api('GET', `/api/collectors/${args.id}`));
|
|
3527
|
+
}
|
|
3528
|
+
case 'create': {
|
|
3529
|
+
// projectId lives in the BODY (the POST route reads body, ignores the query).
|
|
3530
|
+
const projectId = args.projectId || getState().projectId;
|
|
3531
|
+
if (!projectId) throw new Error('No active project. Call project(action="open") first, or pass projectId.');
|
|
3532
|
+
const { name, description, target, checklist, output, enabled } = args;
|
|
3533
|
+
if (!name || !target || !Array.isArray(checklist) || !output) {
|
|
3534
|
+
throw new Error('name, target, checklist[], and output are required for action=create');
|
|
3535
|
+
}
|
|
3536
|
+
const body = { projectId, name, target, checklist, output };
|
|
3537
|
+
if (description !== undefined) body.description = description;
|
|
3538
|
+
if (enabled !== undefined) body.enabled = enabled;
|
|
3539
|
+
return ok(await api('POST', '/api/collectors', body));
|
|
3540
|
+
}
|
|
3541
|
+
case 'update': {
|
|
3542
|
+
if (!args.id) throw new Error('id is required for action=update');
|
|
3543
|
+
const body = {};
|
|
3544
|
+
for (const k of ['name', 'description', 'target', 'checklist', 'output', 'enabled']) {
|
|
3545
|
+
if (args[k] !== undefined) body[k] = args[k];
|
|
3546
|
+
}
|
|
3547
|
+
if (Object.keys(body).length === 0) throw new Error('At least one field is required for action=update');
|
|
3548
|
+
return ok(await api('PATCH', `/api/collectors/${args.id}`, body));
|
|
3549
|
+
}
|
|
3550
|
+
case 'enable':
|
|
3551
|
+
case 'disable': {
|
|
3552
|
+
if (!args.id) throw new Error(`id is required for action=${action}`);
|
|
3553
|
+
return ok(await api('PATCH', `/api/collectors/${args.id}`, { enabled: action === 'enable' }));
|
|
3554
|
+
}
|
|
3555
|
+
case 'delete': {
|
|
3556
|
+
if (!args.id) throw new Error('id is required for action=delete');
|
|
3557
|
+
return ok(await api('DELETE', `/api/collectors/${args.id}`));
|
|
3558
|
+
}
|
|
3559
|
+
default:
|
|
3560
|
+
throw new Error(`Unknown collector action: ${action}`);
|
|
3561
|
+
}
|
|
3562
|
+
} catch (error) { return err(collectorGateError(error)); }
|
|
3563
|
+
});
|
|
3564
|
+
|
|
3426
3565
|
// ── Resource: canvas info ─────────────────────────────────────────
|
|
3427
3566
|
|
|
3428
3567
|
server.resource('info', 'drafted://info', {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.15",
|
|
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": [
|