drafted 1.10.1 → 1.11.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.
Files changed (2) hide show
  1. package/mcp/server.mjs +49 -21
  2. package/package.json +1 -1
package/mcp/server.mjs CHANGED
@@ -832,6 +832,34 @@ function summarizeSkillForSearch(skill) {
832
832
  };
833
833
  }
834
834
 
835
+ // Ultra-lean catalog entry for browsing large libraries within an agent's token
836
+ // budget: just enough to recognize a skill and load it by slug. Full
837
+ // description/triggerPatterns are only needed on load.
838
+ function compactSkillEntry(skill) {
839
+ if (!skill || typeof skill !== 'object') return skill;
840
+ return { slug: skill.slug, name: skill.name, tags: skill.tags || [] };
841
+ }
842
+
843
+ // Shape a {skills:[...]} catalog result (search or list) with limit/offset
844
+ // pagination and an optional compact mode, so an 82-entry global catalog stays
845
+ // within an agent's token budget. Mirrors how `search` already caps client-side.
846
+ function shapeSkillCatalog(result, { limit, offset = 0, compact = false } = {}) {
847
+ if (!Array.isArray(result?.skills)) return result;
848
+ const total = result.skills.length;
849
+ const start = Math.max(0, Math.floor(Number(offset) || 0));
850
+ const cap = Math.min(Math.max(1, Math.floor(Number(limit) || 25)), 100);
851
+ const page = result.skills.slice(start, start + cap);
852
+ result.totalAvailable = total;
853
+ result.offset = start;
854
+ result.returned = page.length;
855
+ result.truncated = start + page.length < total;
856
+ result.skills = page.map(compact ? compactSkillEntry : summarizeSkillForSearch);
857
+ result.note = compact
858
+ ? 'Compact catalog: {slug,name,tags} only. Load full content with skill(action="load", skill="<slug>"). Pass compact=false for descriptions/triggerPatterns; use offset to page.'
859
+ : 'Search/list returns skill summaries only (no SKILL.md body). Use skill(action="load", skill="<slug-or-id>") for full content. Pass compact=true for a leaner {slug,name,tags} catalog; use limit/offset to page.';
860
+ return result;
861
+ }
862
+
835
863
  // Build the structuredContent shape that the frame-preview widget reads.
836
864
  // Tools that produce or return a frame (read/write/edit) call this so the
837
865
  // model and widget see the same metadata view.
@@ -2651,9 +2679,9 @@ function collectSkillTreeForPush(dir) {
2651
2679
  return out;
2652
2680
  }
2653
2681
 
2654
- tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/guidelines agents can load and follow. Dispatch by `action`: search/load/list for discovery; add/update/remove for org skills; fork/push for source-only skills; attach/detach for project binding; favorite/unfavorite for personal pins; read_file/update_file for supporting files inside a skill directory.', {
2682
+ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/guidelines agents can load and follow. Dispatch by `action`: search/load/list for discovery; history for a skill\'s version git-log; add/update/remove for org skills; fork/push for source-only skills; attach/detach for project binding; favorite/unfavorite for personal pins; read_file/update_file for supporting files inside a skill directory.', {
2655
2683
  action: z.enum([
2656
- 'search', 'load', 'list',
2684
+ 'search', 'load', 'list', 'history',
2657
2685
  'add', 'update', 'remove',
2658
2686
  'fork', 'push',
2659
2687
  'attach', 'detach',
@@ -2663,8 +2691,11 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
2663
2691
  query: z.string().optional().describe('[search] term to match against name/description/content'),
2664
2692
  tags: z.array(z.string()).optional().describe('[search] filter by tags; [add|update] tag list'),
2665
2693
  scope: z.enum(['all', 'org', 'global']).optional().describe('[search|list] library scope (default: all for search; when provided to list, lists the library instead of project/org attachments)'),
2666
- limit: z.number().optional().describe('[search] max results (default 25, max 100)'),
2667
- skill: z.string().optional().describe('[load] skill ID (UUID) or slug'),
2694
+ limit: z.number().optional().describe('[search|list] max results per page (default 25, max 100)'),
2695
+ offset: z.number().optional().describe('[search|list] skip N results for pagination (default 0). Response reports totalAvailable/returned/truncated.'),
2696
+ compact: z.boolean().optional().describe('[search|list] return only {slug,name,tags} per skill instead of full summaries — for browsing large catalogs within token budget'),
2697
+ skill: z.string().optional().describe('[load|history] skill ID (UUID) or slug'),
2698
+ version: z.number().optional().describe('[history] fetch this version number\'s full snapshot (content included); omit for the reverse-chron version list'),
2668
2699
  skillId: z.string().optional().describe('[update|remove|attach|detach|favorite|unfavorite|read_file|update_file] skill ID'),
2669
2700
  projectId: z.string().optional().describe('[list] project to list skills for (defaults to active project; falls back to org-attached skills if no project)'),
2670
2701
  name: z.string().optional().describe('[add|update] skill name'),
@@ -2682,7 +2713,7 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
2682
2713
  const { action } = args;
2683
2714
  switch (action) {
2684
2715
  case 'search': {
2685
- const { query, tags, scope = 'all', limit = 25 } = args;
2716
+ const { query, tags, scope = 'all', limit, offset, compact } = args;
2686
2717
  const params = new URLSearchParams();
2687
2718
  if (query) params.set('q', query);
2688
2719
  if (tags?.length) params.set('tags', tags.join(','));
@@ -2691,17 +2722,7 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
2691
2722
  const endpoint = query ? '/api/skills/search' : '/api/skills';
2692
2723
  const result = await api('GET', `${endpoint}${qs ? '?' + qs : ''}`);
2693
2724
  markSearched(getSessionState().gates, 'skill');
2694
- const cap = Math.min(Math.max(1, limit || 25), 100);
2695
- if (Array.isArray(result?.skills)) {
2696
- if (result.skills.length > cap) {
2697
- result.totalAvailable = result.skills.length;
2698
- result.truncated = true;
2699
- result.skills = result.skills.slice(0, cap);
2700
- }
2701
- result.skills = result.skills.map(summarizeSkillForSearch);
2702
- result.note = 'Search/list returns skill summaries only. Use skill(action="load", skill="<slug-or-id>") to read full SKILL.md content and supporting file list.';
2703
- }
2704
- return ok(result);
2725
+ return ok(shapeSkillCatalog(result, { limit, offset, compact }));
2705
2726
  }
2706
2727
  case 'load': {
2707
2728
  const { skill } = args;
@@ -2712,17 +2733,24 @@ tool('skill', 'Manage the Drafted skill library. Skills are reusable prompts/gui
2712
2733
  if (result?.id) getSessionState().loadedSkillIds.add(result.id);
2713
2734
  return ok(result);
2714
2735
  }
2736
+ case 'history': {
2737
+ // Skill version git-log. Omit `version` for the list; pass it for a
2738
+ // single version's full snapshot (content for view/diff).
2739
+ const { skillId, skill: slugArg, version, org } = args;
2740
+ const extra = org ? { 'X-Drafted-Org': org } : {};
2741
+ let id = skillId;
2742
+ if (!id && slugArg) { const s = await api('GET', `/api/skills/slug/${slugArg}`, undefined, extra); id = s.id; }
2743
+ if (!id) throw new Error('skillId or skill (slug) required for action=history');
2744
+ if (version != null) return ok(await api('GET', `/api/skills/${id}/versions/${version}`, undefined, extra));
2745
+ return ok(await api('GET', `/api/skills/${id}/versions`, undefined, extra));
2746
+ }
2715
2747
  case 'list': {
2716
2748
  if (args.scope || args.tags?.length) {
2717
2749
  const params = new URLSearchParams();
2718
2750
  params.set('scope', args.scope || 'all');
2719
2751
  if (args.tags?.length) params.set('tags', args.tags.join(','));
2720
2752
  const result = await api('GET', `/api/skills?${params.toString()}`);
2721
- if (Array.isArray(result?.skills)) {
2722
- result.skills = result.skills.map(summarizeSkillForSearch);
2723
- result.note = 'Library list returns skill summaries only. Use skill(action="load", skill="<slug-or-id>") to read full SKILL.md content and supporting file list.';
2724
- }
2725
- return ok(result);
2753
+ return ok(shapeSkillCatalog(result, { limit: args.limit, offset: args.offset, compact: args.compact }));
2726
2754
  }
2727
2755
 
2728
2756
  // Prefer the explicit projectId param; otherwise the active project;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
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": [