@gethmy/mcp 2.5.1 → 2.5.4

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/dist/cli.js CHANGED
@@ -1315,6 +1315,17 @@ class HarmonyApiClient {
1315
1315
  }
1316
1316
  throw lastError || new Error("Request failed after retries");
1317
1317
  }
1318
+ async fetchSkillsVersion() {
1319
+ return this.request("GET", "/skills/version");
1320
+ }
1321
+ async fetchSkill(name) {
1322
+ return this.request("GET", `/skills/${encodeURIComponent(name)}`);
1323
+ }
1324
+ async recordSkillInvocation(name) {
1325
+ try {
1326
+ await this.request("POST", "/skills/telemetry", { name });
1327
+ } catch {}
1328
+ }
1318
1329
  async listWorkspaces() {
1319
1330
  return this.request("GET", "/workspaces");
1320
1331
  }
@@ -3546,6 +3557,22 @@ var RESOURCES = [
3546
3557
  mimeType: "application/json"
3547
3558
  }
3548
3559
  ];
3560
+ async function listResourcesDynamic(deps) {
3561
+ if (!deps.isConfigured())
3562
+ return RESOURCES;
3563
+ try {
3564
+ const versionInfo = await deps.getClient().fetchSkillsVersion();
3565
+ const skillResources = versionInfo.skills.filter((name) => name !== "hmy-update-check").map((name) => ({
3566
+ uri: `harmony://skills/${name}`,
3567
+ name: `Skill: ${name}`,
3568
+ description: `Harmony skill (SKILL.md) — version ${versionInfo.version}`,
3569
+ mimeType: "text/markdown"
3570
+ }));
3571
+ return [...RESOURCES, ...skillResources];
3572
+ } catch {
3573
+ return RESOURCES;
3574
+ }
3575
+ }
3549
3576
  async function runEndSessionPipeline(client3, _deps, cardId, sessionStatus) {
3550
3577
  try {
3551
3578
  const { card } = await client3.getCard(cardId);
@@ -3613,7 +3640,7 @@ function registerHandlers(server, deps) {
3613
3640
  }
3614
3641
  });
3615
3642
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
3616
- resources: RESOURCES
3643
+ resources: await listResourcesDynamic(deps)
3617
3644
  }));
3618
3645
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
3619
3646
  const { uri } = request.params;
@@ -3632,6 +3659,26 @@ function registerHandlers(server, deps) {
3632
3659
  ]
3633
3660
  };
3634
3661
  }
3662
+ const SKILL_URI_RE = /^harmony:\/\/skills\/([a-z0-9][a-z0-9-]*[a-z0-9])$/;
3663
+ const skillMatch = uri.match(SKILL_URI_RE);
3664
+ if (skillMatch) {
3665
+ const name = skillMatch[1];
3666
+ if (!deps.isConfigured()) {
3667
+ throw new Error(`Cannot read skill "${name}": Harmony MCP server is not configured. Run \`hmy-mcp setup\`.`);
3668
+ }
3669
+ const client3 = deps.getClient();
3670
+ client3.recordSkillInvocation(name);
3671
+ const fetched = await client3.fetchSkill(name);
3672
+ return {
3673
+ contents: [
3674
+ {
3675
+ uri,
3676
+ mimeType: "text/markdown",
3677
+ text: fetched.content
3678
+ }
3679
+ ]
3680
+ };
3681
+ }
3635
3682
  throw new Error(`Unknown resource: ${uri}`);
3636
3683
  });
3637
3684
  }
@@ -4739,8 +4786,6 @@ class HarmonyMCPServer {
4739
4786
  // src/skills.ts
4740
4787
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
4741
4788
  import { dirname } from "node:path";
4742
- var SKILLS_VERSION = "4";
4743
- var VERSION_MARKER_PREFIX = "<!-- skills-version:";
4744
4789
  var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
4745
4790
 
4746
4791
  Start work on a Harmony card. Card reference: $ARGUMENTS
@@ -4816,421 +4861,44 @@ If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
4816
4861
 
4817
4862
  **AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
4818
4863
  `;
4819
- var HMY_SKILL_CONTENT = `# Harmony Card Workflow
4820
-
4821
- User input: $ARGUMENTS
4822
-
4823
- ## 0. Detect Intent
4824
-
4825
- Parse \`$ARGUMENTS\` to determine what the user wants:
4826
-
4827
- | Pattern | Intent | Go to |
4828
- |---|---|---|
4829
- | \`create ...\` or \`new ...\` | **Create** a new card | Step A |
4830
- | \`#42\`, \`42\`, UUID, or card name (no action verb) | **Work on** an existing card | Step B |
4831
- | \`move #42 to Done\`, \`update #42 ...\`, \`assign #42 ...\` | **Quick action** on a card | Step C |
4832
- | \`show #42\`, \`view #42\`, \`status #42\` | **View** card details | Step D |
4833
-
4834
- If ambiguous, ask the user what they'd like to do.
4835
-
4836
- ---
4837
-
4838
- ## Step A: Create Card
4839
-
4840
- Create a card without starting work on it.
4841
-
4842
- 1. Parse the title and any details from the arguments (e.g., \`create Add dark mode toggle\` → title: "Add dark mode toggle")
4843
- 2. Call \`harmony_create_card\` with:
4844
- - \`title\`: extracted title
4845
- - \`description\`: if the user provided details beyond the title
4846
- - \`priority\`, \`columnId\`, \`assigneeId\`: only if explicitly specified
4847
- 3. Show the created card: title, short ID, column, and a link if available.
4848
- 4. **Stop here.** Do not start an agent session or begin implementation.
4849
-
4850
- ---
4851
-
4852
- ## Step B: Work on Existing Card
4853
-
4854
- Start work on a Harmony card.
4855
-
4856
- ### B1. Find & Fetch Card
4857
-
4858
- Parse the reference and fetch the card:
4859
- - \`#42\` or \`42\` → \`harmony_get_card_by_short_id\` with \`shortId: 42\`
4860
- - UUID → \`harmony_get_card\` with \`cardId\`
4861
- - Name/text → \`harmony_search_cards\` with \`query\`
4862
-
4863
- ### B2. Start Agent Session
4864
-
4865
- Call \`harmony_start_agent_session\` with:
4866
- - \`cardId\`: Card UUID
4867
- - \`agentIdentifier\`: Your agent identifier
4868
- - \`agentName\`: Your agent name
4869
- - \`currentTask\`: A specific description of the first thing you'll do (e.g., "Exploring codebase to understand auth flow"), NOT a generic phrase like "Analyzing card requirements"
4870
- - \`moveToColumn\`: "In Progress"
4871
- - \`addLabels\`: ["agent"]
4872
-
4873
- This single call moves the card, adds the label, auto-assigns, and starts the session.
4874
-
4875
- ### B3. Generate Work Prompt
4876
-
4877
- Call \`harmony_generate_prompt\` with:
4878
- - \`cardId\` or \`shortId\` (+ \`projectId\` if using shortId)
4879
- - \`variant\`: Select based on task:
4880
- - \`"execute"\` (default) → Clear tasks, bug fixes, well-defined work
4881
- - \`"analysis"\` → Complex features, unclear requirements
4882
- - \`"draft"\` → Medium complexity, want feedback first
4883
-
4884
- The generated prompt provides role framing, focus areas, subtasks, linked cards, and suggested outputs.
4885
-
4886
- ### B4. Display Card Summary
4887
-
4888
- Show the user: Card title, short ID, role, priority, labels, due date, description, and subtasks.
4889
-
4890
- ### B5. Implement Solution
4891
-
4892
- Work on the card following the generated prompt's guidance.
4893
-
4894
- **REQUIRED: Update progress at each milestone** by calling \`harmony_update_agent_progress\`. This is not optional — the card's live status badge depends on these updates.
4895
-
4896
- | Milestone | \`progressPercent\` | Example \`currentTask\` |
4897
- |---|---|---|
4898
- | After exploring codebase & understanding requirements | 20 | "Reading auth middleware and identifying affected routes" |
4899
- | When starting implementation | 50 | "Refactoring token validation in auth.ts" |
4900
- | When moving to testing/verification | 80 | "Running build and verifying changes compile" |
4901
- | When done, before ending session | 100 | "All changes complete, ready for review" |
4902
-
4903
- Always set \`currentTask\` to a specific description of what you're actually doing — never leave it as "Analyzing card requirements" or other generic text.
4904
-
4905
- ### B6. Complete Work
4906
-
4907
- When finished:
4908
- 1. \`harmony_end_agent_session\` with \`status: "completed"\`, \`progressPercent: 100\`, \`moveToColumn: "Review"\`
4909
- 2. Summarize accomplishments
4910
-
4911
- If pausing: \`harmony_end_agent_session\` with \`status: "paused"\`
4912
-
4913
- ---
4914
-
4915
- ## Step C: Quick Action
4916
-
4917
- Fetch the card first (same as B1), then perform the requested action:
4918
- - **Move:** \`harmony_move_card\` with target column
4919
- - **Update:** \`harmony_update_card\` with changed fields
4920
- - **Assign:** \`harmony_assign_card\`
4921
- - **Label:** \`harmony_add_label_to_card\` / \`harmony_remove_label_from_card\`
4922
- - **Archive:** \`harmony_archive_card\`
4923
-
4924
- Show confirmation and stop. Do not start an agent session.
4925
-
4926
- ---
4927
-
4928
- ## Step D: View Card
4929
-
4930
- Fetch the card (same as B1) and display: title, short ID, column, priority, labels, assignee, due date, description, subtasks, and links.
4931
-
4932
- Do not start an agent session.
4933
-
4934
- ---
4935
-
4936
- ## Step E: Auto-Detect Card for Implementation Tasks
4937
-
4938
- **IMPORTANT:** This step applies when you are about to implement a plan, feature, or fix
4939
- that was NOT started via /hmy. Before writing any code, check if the work maps to an
4940
- existing Harmony card.
4941
-
4942
- ### When to run this check
4943
- - User says "implement this plan", "build this feature", "fix this bug" (without /hmy)
4944
- - You are about to execute a plan file
4945
- - Skip if: the user already started with /hmy, or no Harmony MCP tools are available
4946
-
4947
- ### Detection steps
4948
- 1. Call \`harmony_search_cards\` with keywords from the plan title or task description
4949
- 2. If a card matches (same feature/fix described), tell the user:
4950
- "This maps to card #N — starting a session to track progress."
4951
- 3. Call \`harmony_start_agent_session\` with \`moveToColumn: "In Progress"\`, \`addLabels: ["agent"]\`
4952
- 4. Track progress with \`harmony_update_agent_progress\` at milestones
4953
- 5. When done, call \`harmony_end_agent_session\` with \`status: "completed"\`, \`moveToColumn: "Review"\`
4954
-
4955
- ### If no match
4956
- Proceed normally without a session. No action needed.
4957
-
4958
- ---
4959
-
4960
- ## Key Tools Reference
4961
-
4962
- **Cards:** \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\`, \`harmony_create_card\`, \`harmony_update_card\`, \`harmony_move_card\`, \`harmony_delete_card\`, \`harmony_assign_card\`
4963
-
4964
- **Subtasks:** \`harmony_create_subtask\`, \`harmony_toggle_subtask\`, \`harmony_delete_subtask\`
4965
-
4966
- **Labels:** \`harmony_add_label_to_card\`, \`harmony_remove_label_from_card\`, \`harmony_create_label\`
4967
-
4968
- **Links:** \`harmony_add_link_to_card\`, \`harmony_remove_link_from_card\`, \`harmony_get_card_links\`
4969
-
4970
- **Board:** \`harmony_get_board\`, \`harmony_list_projects\`, \`harmony_get_context\`, \`harmony_set_project_context\`
4971
-
4972
- **Sessions:** \`harmony_start_agent_session\`, \`harmony_update_agent_progress\`, \`harmony_end_agent_session\`, \`harmony_get_agent_session\`
4973
-
4974
- **AI:** \`harmony_generate_prompt\`, \`harmony_process_command\`
4864
+ function buildSkillFile(skill) {
4865
+ if (skill.skillVersion !== undefined && !hasMetadataVersion(skill.content)) {
4866
+ return injectMetadataVersion(skill.content, skill.skillVersion);
4867
+ }
4868
+ return skill.content;
4869
+ }
4870
+ function hasMetadataVersion(content) {
4871
+ return parseSkillVersion(content) !== null;
4872
+ }
4873
+ function injectMetadataVersion(content, version) {
4874
+ const fmMatch = content.match(/^(---\n[\s\S]*?\n)(---\n)([\s\S]*)$/);
4875
+ if (!fmMatch)
4876
+ return content;
4877
+ const [, head, close, body] = fmMatch;
4878
+ const block = `metadata:
4879
+ version: "${version}"
4975
4880
  `;
4976
- var HMY_PLAN_CONTENT = `# Harmony Plan Workflow
4977
-
4978
- Create a new plan or work on an existing one. Argument: $ARGUMENTS
4979
-
4980
- ## Step 1 — Detect Intent
4981
-
4982
- Parse \`$ARGUMENTS\` to determine the workflow:
4983
-
4984
- - **UUID** (contains dashes, 36 chars) → call \`harmony_get_plan\` with \`planId\` directly → **Step 2A**
4985
- - **\`#N\`** (short ID) → call \`harmony_get_card_by_short_id\` to get the card, then \`harmony_get_plan\` with \`cardId\` → **Step 2A**
4986
- - **Text** (anything else) → call \`harmony_list_plans\` with \`search\` set to the text
4987
- - If **one match** → use it → **Step 2A**
4988
- - If **multiple matches** → list them with title, status, phase, and updated date. Ask the user to pick one using \`AskUserQuestion\` → **Step 2A**
4989
- - If **no matches** → ask user: "No existing plans found for '$ARGUMENTS'. Would you like to create a new plan on this topic?" → **Step 2B**
4990
- - **Empty / vague topic** → **Step 2B** (create new plan)
4991
-
4992
- ---
4993
-
4994
- ## Step 2A — Work on Existing Plan
4995
-
4996
- ### 2A.1 — Analyze & Display
4997
-
4998
- Once you have the plan ID, call \`harmony_get_plan\` to fetch the full plan with tasks. Show a structured summary:
4999
-
5000
- \\\`\\\`\\\`
5001
- ## [Plan Title]
5002
- **Status:** draft/active/archived | **Phase:** plan/execute/verify/done
5003
- **Tasks:** N total (X pending, Y in_progress, Z completed)
5004
- **URL:** https://app.gethmy.com/plans/{id}
5005
- \\\`\\\`\\\`
5006
-
5007
- If plan is in execute phase and tasks already have linked cards, note which tasks have cards and which don't.
5008
-
5009
- ### 2A.2 — Recall Context
5010
-
5011
- Call \`harmony_memory_search\` with the plan title to find related knowledge, patterns, or decisions that may inform execution.
5012
-
5013
- ### 2A.3 — Present Options
5014
-
5015
- Use \`AskUserQuestion\` to offer execution choices. Adapt options based on plan state:
5016
-
5017
- #### Default options (plan in \`plan\` phase):
5018
-
5019
- **(A) Single card** — Create one card with plan summary + link to plan. Best for simple plans.
5020
- **(B) Multiple cards** — Create one card per task via \`harmony_advance_plan\`. Best for complex plans with independent tasks.
5021
- **(C) Analyze only** — Review the plan and design an implementation approach. Create cards later.
5022
- **(D) Skip** — Do nothing.
5023
-
5024
- #### If plan has no tasks:
5025
- Only offer **(A) Single card**, **(C) Analyze only**, or **(D) Skip**.
5026
-
5027
- #### If plan is already in \`execute\` phase with existing cards:
5028
- **(A) Work on existing cards** — List cards with short IDs, suggest \`/hmy #N\` to start
5029
- **(B) Create cards for remaining tasks** — Only create cards for tasks without linked cards
5030
- **(C) Analyze progress** — Review what's done vs remaining
5031
- **(D) Skip**
5032
-
5033
- ### 2A.4 — Execute Choice
5034
-
5035
- #### Option A — Single card
5036
- 1. Call \`harmony_create_card\` with:
5037
- - \`title\`: Plan title
5038
- - \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://app.gethmy.com/plans/{planId})\`
5039
- - \`priority\`: based on plan task priorities (use highest)
5040
- 2. Call \`harmony_update_plan\` to set \`status: "active"\`, \`workflowPhase: "execute"\`
5041
-
5042
- #### Option B — Multiple cards (advance plan)
5043
- 1. Call \`harmony_advance_plan\` with:
5044
- - \`planId\`: the plan ID
5045
- - \`phase\`: \`"execute"\`
5046
- - \`summary\`: Brief summary of the plan scope
5047
- 2. Display the created cards with their short IDs
5048
-
5049
- #### Option C — Analyze only
5050
- 1. Present a structured implementation analysis:
5051
- - Suggested order of tasks
5052
- - Dependencies between tasks
5053
- - Key technical considerations from memory search
5054
- - Estimated complexity
5055
- 2. Suggest creating cards when ready
5056
-
5057
- #### Option D — Skip
5058
- Acknowledge and stop.
5059
-
5060
- ### 2A.5 — Summary
5061
-
5062
- After execution, show:
5063
- - Created card(s) with short IDs
5064
- - Current plan phase
5065
- - Suggest next step: \`/hmy #N\` to start working on a card
5066
-
5067
- ---
5068
-
5069
- ## Step 2B — Create New Plan
5070
-
5071
- ### 2B.1 — Context Gathering
5072
-
5073
- Before interviewing, gather existing context:
5074
-
5075
- 1. Call \`harmony_get_board\` for board state (columns, cards, labels)
5076
- 2. Call \`harmony_recall\` with relevant tags to find existing knowledge on the topic
5077
- 3. Call \`harmony_memory_search\` with the topic to find related patterns/decisions/lessons
5078
-
5079
- Note what you learn — reference it during the interview to ask smarter questions.
5080
-
5081
- ### 2B.2 — Structured Interview (3-5 questions)
5082
-
5083
- Interview the user with **3-5 focused questions** using AskUserQuestion. Adapt based on complexity.
5084
-
5085
- #### Core Questions
5086
- 1. **Problem & Audience**: What problem are we solving? Who benefits?
5087
- 2. **Scope**: What's in v1, what's deferred?
5088
- 3. **Technical Constraints**: Any technical preferences, constraints, or existing patterns to follow?
5089
-
5090
- #### Adaptive Follow-ups (if needed)
5091
- - Key user flows or interactions?
5092
- - Integration points with existing systems?
5093
- - Performance, security, or accessibility requirements?
5094
-
5095
- #### Interview Rules
5096
- - Reference what you found in Step 2B.1 (board state, memories) to ask informed questions
5097
- - Skip questions that aren't relevant — simple features need fewer questions
5098
- - Tell the user when you have enough information to draft
5099
-
5100
- ### 2B.3 — Plan Document
5101
-
5102
- Create a structured markdown document:
5103
-
5104
- \\\`\\\`\\\`markdown
5105
- # [Title]
5106
-
5107
- ## Problem
5108
- [What problem we're solving and why]
5109
-
5110
- ## Scope
5111
-
5112
- ### In Scope
5113
- - [Feature/capability 1]
5114
- - [Feature/capability 2]
5115
-
5116
- ### Out of Scope
5117
- - [Deferred items]
5118
-
5119
- ## Approach
5120
- [Technical design, architecture decisions, key patterns]
5121
-
5122
- ### Key Decisions
5123
- | Decision | Choice | Rationale |
5124
- |----------|--------|-----------|
5125
- | ... | ... | ... |
5126
-
5127
- ## Tasks
5128
- 1. **[Task title]** — priority: high
5129
- [Brief description]
5130
-
5131
- 2. **[Task title]** — priority: medium
5132
- [Brief description]
5133
-
5134
- [Continue for all tasks...]
5135
-
5136
- ## Risks & Mitigations
5137
- | Risk | Mitigation |
5138
- |------|------------|
5139
- | ... | ... |
5140
-
5141
- ## Success Criteria
5142
- - [ ] [Measurable criterion]
5143
- \\\`\\\`\\\`
5144
-
5145
- ### 2B.4 — Create Plan
5146
-
5147
- Call \`harmony_create_plan\` with:
5148
- - \`title\`: The plan title
5149
- - \`content\`: Full markdown document from Step 2B.3
5150
- - \`source\`: \`"agent"\`
5151
- - \`workflowPhase\`: \`"plan"\`
5152
- - \`tasks\`: Array of tasks from the Tasks section, each with:
5153
- - \`content\`: Task title + brief description
5154
- - \`priority\`: \`"high"\`, \`"medium"\`, or \`"low"\`
5155
- - \`status\`: \`"pending"\`
5156
-
5157
- ### 2B.5 — User Approval
5158
-
5159
- Show the user:
5160
- - Plan URL: \`https://app.gethmy.com/plans/{id}\`
5161
- - Number of tasks created
5162
- - Brief summary
5163
-
5164
- Then ask: **"Ready to execute? I'll create board cards from these tasks and start the execution phase."**
5165
-
5166
- Options:
5167
- 1. **Yes, advance to Execute** — proceed to Step 2B.6
5168
- 2. **Let me review first** — end here, they can advance later from the UI
5169
- 3. **Make changes** — iterate on the plan
5170
-
5171
- ### 2B.6 — Advance to Execute
5172
-
5173
- Call \`harmony_advance_plan\` with:
5174
- - \`planId\`: The plan ID from Step 2B.4
5175
- - \`phase\`: \`"execute"\`
5176
- - \`summary\`: A 2-3 sentence summary of the key decisions made during planning
5177
-
5178
- Report the result:
5179
- - "Created N cards in 'To Do'. Use \`/hmy #<id>\` to start working on any card."
5180
- - List the created cards with their short IDs
5181
-
5182
- ## Key Tools Reference
5183
-
5184
- **Discovery:** \`harmony_list_plans\`, \`harmony_get_plan\`, \`harmony_get_card_by_short_id\`
5185
- **Context:** \`harmony_get_board\`, \`harmony_recall\`, \`harmony_memory_search\`
5186
- **Planning:** \`harmony_create_plan\`, \`harmony_advance_plan\`, \`harmony_update_plan\`
5187
- **Execution:** \`harmony_create_card\`, \`harmony_update_card\`
5188
- `;
5189
- var SKILL_DEFINITIONS = {
5190
- hmy: {
5191
- name: "hmy",
5192
- description: "Work with Harmony cards — create, view, update, or start working on them. Use when given a card reference like #42, or commands like create, move, update.",
5193
- argumentHint: "<command or card-reference>",
5194
- content: HMY_SKILL_CONTENT
5195
- },
5196
- "hmy-plan": {
5197
- name: "hmy-plan",
5198
- description: "Create a new plan or work on an existing one. Use when asked to plan a feature, execute a plan, review a plan, or given a plan reference.",
5199
- argumentHint: "[plan name, ID, or topic to plan]",
5200
- content: HMY_PLAN_CONTENT
5201
- }
5202
- };
5203
- function buildSkillFile(skillId, agentId) {
5204
- const skill = SKILL_DEFINITIONS[skillId];
5205
- if (!skill) {
5206
- throw new Error(`Unknown skill: ${skillId}`);
5207
- }
5208
- let content = skill.content;
5209
- if (agentId === "claude") {
5210
- content = content.replace("Your agent identifier", "claude-code").replace("Your agent name", "Claude Code");
5211
- }
5212
- const frontmatter = `---
5213
- name: ${skill.name}
5214
- description: ${skill.description}
5215
- argument-hint: ${skill.argumentHint}
5216
- ---`;
5217
- return `${frontmatter}
5218
-
5219
- ${content}
5220
- ${VERSION_MARKER_PREFIX}${SKILLS_VERSION} -->`;
4881
+ return `${head}${block}${close}${body}`;
5221
4882
  }
5222
4883
  function parseSkillVersion(content) {
5223
- const match = content.match(/<!-- skills-version:(\d+) -->/);
5224
- return match ? match[1] : null;
4884
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
4885
+ if (fmMatch) {
4886
+ const fm = fmMatch[1];
4887
+ const verMatch = fm.match(/^metadata:[\s\S]*?\n[ \t]+version:[ \t]*["']?(\d+)["']?\s*$/m);
4888
+ if (verMatch)
4889
+ return verMatch[1];
4890
+ }
4891
+ const legacy = content.match(/<!-- skills-version:(\d+) -->/);
4892
+ return legacy ? legacy[1] : null;
5225
4893
  }
5226
- function findSkillFiles(paths) {
4894
+ function findSkillFiles(paths, knownNames) {
5227
4895
  const results = [];
5228
4896
  for (const filePath of paths) {
5229
4897
  if (!existsSync3(filePath))
5230
4898
  continue;
5231
- for (const skillId of Object.keys(SKILL_DEFINITIONS)) {
5232
- if (filePath.includes(`/${skillId}/`) || filePath.includes(`/${skillId}.md`)) {
5233
- results.push({ skillId, filePath });
4899
+ for (const name of knownNames) {
4900
+ if (filePath.includes(`/${name}/`) || filePath.includes(`/${name}.md`)) {
4901
+ results.push({ name, filePath });
5234
4902
  break;
5235
4903
  }
5236
4904
  }
@@ -5239,51 +4907,57 @@ function findSkillFiles(paths) {
5239
4907
  }
5240
4908
  async function refreshSkills() {
5241
4909
  try {
4910
+ if (!isConfigured())
4911
+ return;
5242
4912
  const status = areSkillsInstalled();
5243
- if (!status.installed) {
4913
+ if (!status.installed)
5244
4914
  return;
5245
- }
5246
- const skillFiles = findSkillFiles(status.paths);
4915
+ const client3 = new HarmonyApiClient;
4916
+ const versionInfo = await client3.fetchSkillsVersion();
4917
+ const skillFiles = findSkillFiles(status.paths, versionInfo.skills);
5247
4918
  if (skillFiles.length > 0) {
5248
4919
  const samplePath = skillFiles[0].filePath;
5249
- for (const skillId of Object.keys(SKILL_DEFINITIONS)) {
5250
- const alreadyFound = skillFiles.some((sf) => sf.skillId === skillId);
5251
- if (alreadyFound)
4920
+ for (const name of versionInfo.skills) {
4921
+ if (skillFiles.some((sf) => sf.name === name))
5252
4922
  continue;
5253
4923
  let siblingPath;
5254
4924
  if (samplePath.endsWith("SKILL.md")) {
5255
4925
  const parentDir = dirname(dirname(samplePath));
5256
- siblingPath = `${parentDir}/${skillId}/SKILL.md`;
4926
+ siblingPath = `${parentDir}/${name}/SKILL.md`;
5257
4927
  } else {
5258
4928
  const parentDir = dirname(samplePath);
5259
- siblingPath = `${parentDir}/${skillId}.md`;
4929
+ siblingPath = `${parentDir}/${name}.md`;
5260
4930
  }
5261
4931
  if (existsSync3(siblingPath)) {
5262
- skillFiles.push({ skillId, filePath: siblingPath });
4932
+ skillFiles.push({ name, filePath: siblingPath });
5263
4933
  }
5264
4934
  }
5265
4935
  }
5266
- if (skillFiles.length === 0) {
4936
+ if (skillFiles.length === 0)
5267
4937
  return;
5268
- }
5269
4938
  let updated = false;
5270
- for (const { skillId, filePath } of skillFiles) {
4939
+ for (const { name, filePath } of skillFiles) {
5271
4940
  try {
5272
4941
  const currentContent = readFileSync3(filePath, "utf-8");
5273
- const currentVersion = parseSkillVersion(currentContent);
5274
- if (currentVersion === null || Number(currentVersion) < Number(SKILLS_VERSION)) {
5275
- const newContent = buildSkillFile(skillId, "claude");
5276
- const dir = dirname(filePath);
5277
- if (!existsSync3(dir)) {
5278
- mkdirSync3(dir, { recursive: true });
5279
- }
5280
- writeFileSync3(filePath, newContent);
5281
- updated = true;
4942
+ const localVersion = parseSkillVersion(currentContent);
4943
+ const fetched = await client3.fetchSkill(name);
4944
+ const remoteVersion = fetched.skillVersion;
4945
+ if (remoteVersion !== undefined && localVersion !== null && Number(localVersion) >= remoteVersion) {
4946
+ continue;
5282
4947
  }
5283
- } catch {}
4948
+ const newContent = buildSkillFile(fetched);
4949
+ const dir = dirname(filePath);
4950
+ if (!existsSync3(dir))
4951
+ mkdirSync3(dir, { recursive: true });
4952
+ writeFileSync3(filePath, newContent);
4953
+ updated = true;
4954
+ } catch (err) {
4955
+ const msg = err instanceof Error ? err.message : String(err);
4956
+ console.error(`Harmony: skill "${name}" refresh failed: ${msg}`);
4957
+ }
5284
4958
  }
5285
4959
  if (updated) {
5286
- console.error(`Harmony: Updated skills to v${SKILLS_VERSION}`);
4960
+ console.error("Harmony: Refreshed skills from server");
5287
4961
  }
5288
4962
  } catch {}
5289
4963
  }
@@ -6325,44 +5999,47 @@ async function fetchProjects(apiKey, workspaceId) {
6325
5999
  const data = await response.json();
6326
6000
  return data.projects || [];
6327
6001
  }
6328
- function getAgentFiles(agentId, cwd, installMode = "global") {
6002
+ async function getAgentFiles(agentId, cwd, installMode = "global") {
6329
6003
  const home = homedir4();
6330
6004
  const files = [];
6331
6005
  const symlinks = [];
6332
6006
  switch (agentId) {
6333
6007
  case "claude": {
6334
- const skillContent = buildSkillFile("hmy", "claude");
6335
- const planSkillContent = buildSkillFile("hmy-plan", "claude");
6336
- if (installMode === "global") {
6337
- files.push({
6338
- path: join5(GLOBAL_SKILLS_DIR, "hmy", "SKILL.md"),
6339
- content: skillContent,
6340
- type: "text"
6341
- });
6342
- symlinks.push({
6343
- target: join5(GLOBAL_SKILLS_DIR, "hmy"),
6344
- link: join5(home, ".claude", "skills", "hmy")
6345
- });
6346
- files.push({
6347
- path: join5(GLOBAL_SKILLS_DIR, "hmy-plan", "SKILL.md"),
6348
- content: planSkillContent,
6349
- type: "text"
6350
- });
6351
- symlinks.push({
6352
- target: join5(GLOBAL_SKILLS_DIR, "hmy-plan"),
6353
- link: join5(home, ".claude", "skills", "hmy-plan")
6354
- });
6355
- } else {
6356
- files.push({
6357
- path: join5(cwd, ".claude", "skills", "hmy", "SKILL.md"),
6358
- content: skillContent,
6359
- type: "text"
6360
- });
6361
- files.push({
6362
- path: join5(cwd, ".claude", "skills", "hmy-plan", "SKILL.md"),
6363
- content: planSkillContent,
6364
- type: "text"
6365
- });
6008
+ const client3 = new HarmonyApiClient;
6009
+ const versionInfo = await client3.fetchSkillsVersion();
6010
+ const installableNames = versionInfo.skills.filter((name) => name !== "hmy-update-check");
6011
+ const skillFailures = [];
6012
+ for (const name of installableNames) {
6013
+ try {
6014
+ const fetched = await client3.fetchSkill(name);
6015
+ const content = buildSkillFile(fetched);
6016
+ if (installMode === "global") {
6017
+ files.push({
6018
+ path: join5(GLOBAL_SKILLS_DIR, name, "SKILL.md"),
6019
+ content,
6020
+ type: "text"
6021
+ });
6022
+ symlinks.push({
6023
+ target: join5(GLOBAL_SKILLS_DIR, name),
6024
+ link: join5(home, ".claude", "skills", name)
6025
+ });
6026
+ } else {
6027
+ files.push({
6028
+ path: join5(cwd, ".claude", "skills", name, "SKILL.md"),
6029
+ content,
6030
+ type: "text"
6031
+ });
6032
+ }
6033
+ } catch (err) {
6034
+ const msg = err instanceof Error ? err.message : String(err);
6035
+ skillFailures.push({ name, error: msg });
6036
+ }
6037
+ }
6038
+ if (skillFailures.length > 0) {
6039
+ const summary = skillFailures.map((f) => ` - ${f.name}: ${f.error}`).join(`
6040
+ `);
6041
+ throw new Error(`Failed to fetch ${skillFailures.length}/${installableNames.length} skill(s) from /v1/skills:
6042
+ ${summary}`);
6366
6043
  }
6367
6044
  break;
6368
6045
  }
@@ -6862,7 +6539,7 @@ async function runSetup(options = {}) {
6862
6539
  }
6863
6540
  if (needsSkills && selectedAgents.length > 0) {
6864
6541
  for (const agentId of selectedAgents) {
6865
- const { files, symlinks } = getAgentFiles(agentId, cwd, installMode);
6542
+ const { files, symlinks } = await getAgentFiles(agentId, cwd, installMode);
6866
6543
  allFiles.push(...files);
6867
6544
  allSymlinks.push(...symlinks);
6868
6545
  }