drafted 1.17.10 → 1.17.12

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 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 <urlOrPath>')
2128
- .description('Register a git repo (URL or local path) and ingest its .agents/ skills/identities into the org index')
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 (urlOrPath, opts) => {
2134
+ .action(async (url, opts) => {
2135
2135
  requireLogin();
2136
2136
  const orgHeaders = opts.org ? { 'X-Drafted-Org': opts.org } : {};
2137
- // Resolve the source dir to scan + the gitUrl to store.
2138
- let dir;
2139
- let cleanup = () => {};
2140
- let gitUrl;
2141
- let defaultBranch;
2142
- if (isGitUrl(urlOrPath)) {
2143
- try {
2144
- const c = cloneToTemp(urlOrPath);
2145
- dir = c.dir; cleanup = c.cleanup;
2146
- } catch (err) {
2147
- console.error(`❌ Failed to clone ${urlOrPath}: ${err.message}`);
2148
- process.exit(1);
2149
- }
2150
- gitUrl = urlOrPath;
2151
- defaultBranch = opts.branch || detectDefaultBranch(dir);
2152
- } else if (existsSync(urlOrPath)) {
2153
- dir = resolve(urlOrPath);
2154
- gitUrl = opts.slug ? undefined : detectRemoteUrl(dir);
2155
- defaultBranch = opts.branch || detectDefaultBranch(dir);
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.error(`❌ Not a git URL and not a local path: ${urlOrPath}`);
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 registered repos with their .agents/ index counts')
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.gitUrl, r.defaultBranch, `skills:${r.skillCount ?? 0}`, `identities:${r.identityCount ?? 0}`, r.description || ''].join('\t'));
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
@@ -2581,7 +2581,7 @@ tool('fs', 'Navigate Drafted like a local filesystem. An org is the top folder:
2581
2581
  base64: z.string().optional().describe('[write] base64-encoded binary content'),
2582
2582
  googleType: z.enum(['google-doc', 'google-sheet', 'google-slide']).optional().describe('[write] explicit Google Workspace type (also derived from .google-* filename extension)'),
2583
2583
  title: z.string().optional().describe('[write + googleType] title for a new native file'),
2584
- ops: z.array(z.any()).optional().describe('[edit] hashline ops for text frames ({type,lineHash,newContent}), element ops for excalidraw ({id,x,y,...}), or office ops'),
2584
+ 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'),
2585
2585
  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.'),
2586
2586
  recursive: z.boolean().optional().describe('[ls] recurse into subdirectories'),
2587
2587
  lines: z.string().optional().describe('[read] line range (e.g. "1-50") — partial read; content is hashline-annotated so a later edit stays surgical'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.17.10",
3
+ "version": "1.17.12",
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": [
@@ -40,7 +40,7 @@ const BLOCKED_KEYS = new Set([
40
40
  'content', 'html', 'markdown', 'body', 'prompt', 'token', 'cookie', 'authorization',
41
41
  'password', 'secret', 'apiKey', 'request', 'headers', 'file', 'buffer', 'dataUrl',
42
42
  ]);
43
- const ID_KEYS = new Set(['userId', 'orgId', 'projectId', 'projectSlug', 'frameId', 'shareId', 'skillId', 'templateId', 'templateSlug', 'userRole', 'role', 'buildId', 'pageType', 'tool', 'action', 'source', 'layer', 'lane', 'mode', 'installId', 'schemaVersion', 'installerVersion', 'cliVersion', 'osFamily', 'osVersion', 'arch', 'nodeVersion', 'npmVersion', 'claudeDesktop', 'claudeCode', 'codex', 'cursor', 'updateHelperStatus', 'mcpMode', 'errorCode']);
43
+ const ID_KEYS = new Set(['userId', 'orgId', 'projectId', 'projectSlug', 'frameId', 'shareId', 'skillId', 'templateId', 'templateSlug', 'userRole', 'role', 'buildId', 'pageType', 'tool', 'action', 'source', 'layer', 'lane', 'mode', 'installId', 'schemaVersion', 'installerVersion', 'cliVersion', 'osFamily', 'osVersion', 'arch', 'nodeVersion', 'npmVersion', 'claudeDesktop', 'claudeCode', 'codex', 'cursor', 'updateHelperStatus', 'mcpMode', 'errorCode', 'matchKey']);
44
44
 
45
45
  export function getUmamiConfig(config = {}) {
46
46
  const hostUrl = (config.umamiHostUrl || process.env.UMAMI_HOST_URL || '').replace(/\/$/, '');