drafted 1.17.9 → 1.17.11
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 +30 -60
- package/mcp/server.mjs +33 -2
- package/package.json +1 -1
package/cli/drafted.mjs
CHANGED
|
@@ -2124,83 +2124,53 @@ minionCmd
|
|
|
2124
2124
|
const repoCmd = program.command('repo').description('Registered git repos (org index of .agents/ skills/identities)');
|
|
2125
2125
|
|
|
2126
2126
|
repoCmd
|
|
2127
|
-
.command('add <
|
|
2128
|
-
.description('
|
|
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)')
|
|
2129
2129
|
.option('--org <org>', 'resolve against this Drafted org (id or name); scopes per-request without switching the session')
|
|
2130
|
+
.option('--folder <name>', 'folder to link the repo into (required)')
|
|
2130
2131
|
.option('--slug <slug>', 'repo slug (defaults to the repo name from the URL)')
|
|
2131
2132
|
.option('--description <desc>', 'short description')
|
|
2132
|
-
.option('--branch <branch>', 'default branch (defaults to the checked-out branch, then main)')
|
|
2133
2133
|
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
2134
|
-
.action(async (
|
|
2134
|
+
.action(async (url, opts) => {
|
|
2135
2135
|
requireLogin();
|
|
2136
2136
|
const orgHeaders = opts.org ? { 'X-Drafted-Org': opts.org } : {};
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2137
|
+
const server = getServerUrl().replace(/\/$/, '');
|
|
2138
|
+
const fail = (msg) => { if (opts.format === 'json') console.log(JSON.stringify({ ok: false, command: 'repo:add', error: msg })); else console.log(['error', msg].join('\t')); process.exit(1); };
|
|
2139
|
+
const folderName = String(opts.folder || '').trim();
|
|
2140
|
+
if (!folderName) return fail('--folder <name> is required — repos are linked to a folder, never to the org directly');
|
|
2141
|
+
// Resolve folder name -> id in the target org.
|
|
2142
|
+
const foldersRes = await authFetch(`${server}/api/folders`, { headers: orgHeaders });
|
|
2143
|
+
const foldersJson = await foldersRes.json().catch(() => ({}));
|
|
2144
|
+
if (!foldersRes.ok) return fail(foldersJson.error || `HTTP ${foldersRes.status}`);
|
|
2145
|
+
const folders = Array.isArray(foldersJson) ? foldersJson : foldersJson.folders || [];
|
|
2146
|
+
const folder = folders.find((f) => f.name === folderName);
|
|
2147
|
+
if (!folder) return fail(`no folder named "${folderName}" in this org`);
|
|
2148
|
+
// 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 res = await authFetch(`${server}/api/repos`, {
|
|
2151
|
+
method: 'POST',
|
|
2152
|
+
headers: { 'Content-Type': 'application/json', ...orgHeaders },
|
|
2153
|
+
body: JSON.stringify(payload),
|
|
2154
|
+
});
|
|
2155
|
+
const data = await res.json().catch(() => ({}));
|
|
2156
|
+
if (!res.ok) return fail(data.error || `HTTP ${res.status}`);
|
|
2157
|
+
const created = res.status === 201;
|
|
2158
|
+
if (opts.format === 'json') {
|
|
2159
|
+
console.log(JSON.stringify({ ok: true, command: 'repo:add', data: { ...data, created } }));
|
|
2156
2160
|
} else {
|
|
2157
|
-
console.
|
|
2158
|
-
process.exit(1);
|
|
2159
|
-
}
|
|
2160
|
-
try {
|
|
2161
|
-
const { skills, identities } = scanRepoAgents(dir);
|
|
2162
|
-
const entries = [...skills, ...identities];
|
|
2163
|
-
const payload = {
|
|
2164
|
-
gitUrl: gitUrl || urlOrPath,
|
|
2165
|
-
slug: opts.slug,
|
|
2166
|
-
description: opts.description,
|
|
2167
|
-
defaultBranch,
|
|
2168
|
-
entries,
|
|
2169
|
-
};
|
|
2170
|
-
const server = getServerUrl().replace(/\/$/, '');
|
|
2171
|
-
const res = await authFetch(`${server}/api/repos`, {
|
|
2172
|
-
method: 'POST',
|
|
2173
|
-
headers: { 'Content-Type': 'application/json', ...orgHeaders },
|
|
2174
|
-
body: JSON.stringify(payload),
|
|
2175
|
-
});
|
|
2176
|
-
const data = await res.json().catch(() => ({}));
|
|
2177
|
-
if (!res.ok) {
|
|
2178
|
-
const err = data.error || `HTTP ${res.status}`;
|
|
2179
|
-
if (opts.format === 'json') console.log(JSON.stringify({ ok: false, command: 'repo:add', error: err }));
|
|
2180
|
-
else console.log(['error', err].join('\t'));
|
|
2181
|
-
process.exit(1);
|
|
2182
|
-
}
|
|
2183
|
-
const created = res.status === 201;
|
|
2184
|
-
if (opts.format === 'json') {
|
|
2185
|
-
console.log(JSON.stringify({ ok: true, command: 'repo:add', data: { ...data, created, skills: skills.length, identities: identities.length } }));
|
|
2186
|
-
} else {
|
|
2187
|
-
console.log([created ? 'added' : 'updated', data.slug || '', data.gitUrl || gitUrl || '', data.defaultBranch || defaultBranch, `skills:${skills.length}`, `identities:${identities.length}`].join('\t'));
|
|
2188
|
-
}
|
|
2189
|
-
} finally {
|
|
2190
|
-
cleanup();
|
|
2161
|
+
console.log([created ? 'added' : 'updated', data.slug || '', data.gitUrl || url, folderName, `skills:${data.skills ?? 0}`, `identities:${data.identities ?? 0}`].join('\t'));
|
|
2191
2162
|
}
|
|
2192
2163
|
});
|
|
2193
|
-
|
|
2194
2164
|
repoCmd
|
|
2195
2165
|
.command('list')
|
|
2196
|
-
.description('List the org\'s
|
|
2166
|
+
.description('List the org\'s linked repos with their folder + .agents/ index counts')
|
|
2197
2167
|
.option('--org <org>', 'resolve against this Drafted org (id or name); scopes per-request without switching the session')
|
|
2198
2168
|
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
2199
2169
|
.action(async (opts) => {
|
|
2200
2170
|
const data = await readApiGet('repo:list', '/api/repos', opts.org);
|
|
2201
2171
|
const rows = data.repos || [];
|
|
2202
2172
|
if (opts.format === 'json') { console.log(JSON.stringify(rows)); return; }
|
|
2203
|
-
for (const r of rows) console.log([r.slug, r.
|
|
2173
|
+
for (const r of rows) console.log([r.slug, r.folderName || '-', r.gitUrl, `skills:${r.skillCount ?? 0}`, `identities:${r.identityCount ?? 0}`].join('\t'));
|
|
2204
2174
|
});
|
|
2205
2175
|
|
|
2206
2176
|
repoCmd
|
package/mcp/server.mjs
CHANGED
|
@@ -2729,9 +2729,16 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
2729
2729
|
return ok({ archived: true, from: canonPath, to: result?.path || archivePath, ...(result?.referrersUpdated ? { referrersUpdated: result.referrersUpdated } : {}) });
|
|
2730
2730
|
}
|
|
2731
2731
|
case 'search': {
|
|
2732
|
+
// The gate means "consult the wiki before project writes". Only an
|
|
2733
|
+
// actual search with a real query counts — not the call itself.
|
|
2734
|
+
const q = String(query || '').trim();
|
|
2735
|
+
if (!q) return ok('(provide a query — e.g. fs(search, path="/wiki", query="<terms>"))');
|
|
2736
|
+
const pages = await api('GET', `/api/wiki/search?q=${encodeURIComponent(q)}`, undefined, orgHeader);
|
|
2737
|
+
// Server responds { hits: [...] } — earlier code read .pages/.results,
|
|
2738
|
+
// which are never present, so every MCP wiki search reported empty.
|
|
2739
|
+
const hits = pages?.hits || pages?.pages || pages?.results || [];
|
|
2732
2740
|
markSearched(gs, 'wiki');
|
|
2733
|
-
|
|
2734
|
-
return ok(formatWikiIndex(pages?.pages || pages?.results || []));
|
|
2741
|
+
return ok(formatWikiIndex(hits));
|
|
2735
2742
|
}
|
|
2736
2743
|
default:
|
|
2737
2744
|
return err(new Error(`fs ${action} not supported for /wiki`));
|
|
@@ -2746,6 +2753,11 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
2746
2753
|
}
|
|
2747
2754
|
switch (action) {
|
|
2748
2755
|
case 'ls': {
|
|
2756
|
+
// A specific slug is a targeted ls: just that skill, not the whole root.
|
|
2757
|
+
if (slug) {
|
|
2758
|
+
const s = await api('GET', `/api/skills/slug/${slug}`, undefined, orgHeader).catch(() => null);
|
|
2759
|
+
return ok(s ? [{ slug: s.slug, name: s.name, description: s.description }] : []);
|
|
2760
|
+
}
|
|
2749
2761
|
const list = await api('GET', '/api/skills', undefined, orgHeader);
|
|
2750
2762
|
const skills = Array.isArray(list) ? list : (list?.skills || []);
|
|
2751
2763
|
return ok(skills.map(s => ({ slug: s.slug, name: s.name, description: s.description })));
|
|
@@ -2842,6 +2854,25 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
|
|
|
2842
2854
|
projectRef = parts[0]; // /projects/<project>
|
|
2843
2855
|
}
|
|
2844
2856
|
|
|
2857
|
+
// fs grammar: the project comes from the PATH, not the shared session
|
|
2858
|
+
// binding — scope the whole call to the path's project (request-local, per
|
|
2859
|
+
// the DRAFT-36 addressing invariant). Without this, /api/fs falls back to
|
|
2860
|
+
// the session's active project (the user's shared row any browser tab or
|
|
2861
|
+
// parallel agent can rewrite), so ls/read/write silently touched the wrong
|
|
2862
|
+
// project and "fixed itself" when something re-bound — the under-reporting
|
|
2863
|
+
// churn reported from the MJ Directive org.
|
|
2864
|
+
if (projectRef) {
|
|
2865
|
+
const pathProject = await resolveProjectRef(projectRef).catch(() => null);
|
|
2866
|
+
if (!pathProject?.id) return err(new Error(`project not found: ${projectRef}`));
|
|
2867
|
+
// An org-scoped path must name a project of that org — never a same-named
|
|
2868
|
+
// project from another org (the path is both address and guardrail).
|
|
2869
|
+
if (orgFromPath && !(await pathOrgMatches(orgFromPath, pathProject))) {
|
|
2870
|
+
return err(new Error(`project ${projectRef} is not in org ${orgFromPath}`));
|
|
2871
|
+
}
|
|
2872
|
+
getState().projectId = pathProject.id;
|
|
2873
|
+
getState().projectMeta = pathProject;
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2845
2876
|
const run = async () => {
|
|
2846
2877
|
const hasFile = layer && filename;
|
|
2847
2878
|
// Layer-root file (lane null) hits the server's 2-segment route.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.11",
|
|
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": [
|