@phnx-labs/agents-cli 1.20.59 → 1.20.60

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.
@@ -274,6 +274,39 @@ export function transformSubagentForDroid(subagentDir) {
274
274
  * See GitHub docs for custom agents.
275
275
  */
276
276
  export const transformSubagentForCopilot = transformSubagentForDroid;
277
+ /**
278
+ * Transform a subagent into Antigravity's custom-agent markdown shape.
279
+ *
280
+ * Antigravity exposes custom agents as Markdown files with YAML frontmatter,
281
+ * close to Gemini CLI subagents. Keep portable frontmatter fields and flatten
282
+ * sibling markdown files into the prompt body like the other markdown-backed
283
+ * agents.
284
+ */
285
+ export function transformSubagentForAntigravity(subagentDir) {
286
+ const agentMd = path.join(subagentDir, 'AGENT.md');
287
+ const frontmatter = parseSubagentFrontmatter(agentMd);
288
+ const body = getSubagentBody(agentMd);
289
+ if (!frontmatter) {
290
+ throw new Error(`Invalid AGENT.md in ${subagentDir}`);
291
+ }
292
+ const frontmatterYaml = yaml.stringify({
293
+ name: frontmatter.name,
294
+ description: frontmatter.description,
295
+ kind: 'local',
296
+ ...(frontmatter.model && { model: frontmatter.model }),
297
+ }).trim();
298
+ let result = `---\n${frontmatterYaml}\n---\n\n${body}`;
299
+ const files = fs.readdirSync(subagentDir)
300
+ .filter(f => f.endsWith('.md') && f !== 'AGENT.md')
301
+ .sort();
302
+ for (const file of files) {
303
+ const content = fs.readFileSync(path.join(subagentDir, file), 'utf-8').trim();
304
+ const sectionName = file.replace('.md', '');
305
+ const title = sectionName.charAt(0).toUpperCase() + sectionName.slice(1).toLowerCase();
306
+ result += `\n\n## ${title}\n\n${content}`;
307
+ }
308
+ return `${result.trim()}\n`;
309
+ }
277
310
  /**
278
311
  * Transform a subagent into an OpenCode agent markdown file.
279
312
  *
@@ -489,10 +522,10 @@ export function syncSubagentToOpenclaw(subagentDir, targetDir) {
489
522
  * Install a subagent to a specific agent's home
490
523
  */
491
524
  export function installSubagentToAgent(subagentDir, subagentName, agent, agentHome) {
492
- if (agent === 'claude' || agent === 'grok') {
493
- // Claude / Grok: flatten to single .md under ~/.claude/agents or ~/.grok/agents
494
- // Grok discovers user agent defs from ~/.grok/agents/*.md (same shape as Claude).
495
- const agentsDir = path.join(agentHome, agent === 'grok' ? '.grok' : '.claude', 'agents');
525
+ if (agent === 'claude' || agent === 'gemini' || agent === 'grok') {
526
+ // Claude / Gemini / Grok: flatten to single .md under the native agents dir.
527
+ const agentsRoot = agent === 'grok' ? '.grok' : agent === 'gemini' ? '.gemini' : '.claude';
528
+ const agentsDir = path.join(agentHome, agentsRoot, 'agents');
496
529
  if (!fs.existsSync(agentsDir)) {
497
530
  fs.mkdirSync(agentsDir, { recursive: true });
498
531
  }
@@ -543,6 +576,19 @@ export function installSubagentToAgent(subagentDir, subagentName, agent, agentHo
543
576
  return { success: false, error: String(err) };
544
577
  }
545
578
  }
579
+ else if (agent === 'antigravity') {
580
+ // Antigravity: custom-agent markdown under ~/.gemini/config/agents/<name>/agent.md.
581
+ const agentDir = safeJoin(path.join(agentHome, '.gemini', 'config', 'agents'), subagentName);
582
+ if (!fs.existsSync(agentDir))
583
+ fs.mkdirSync(agentDir, { recursive: true });
584
+ try {
585
+ fs.writeFileSync(safeJoin(agentDir, 'agent.md'), transformSubagentForAntigravity(subagentDir));
586
+ return { success: true };
587
+ }
588
+ catch (err) {
589
+ return { success: false, error: String(err) };
590
+ }
591
+ }
546
592
  else if (agent === 'openclaw') {
547
593
  // OpenClaw: copy full directory
548
594
  const targetDir = safeJoin(path.join(agentHome, '.openclaw'), subagentName);
@@ -573,8 +619,8 @@ export function installSubagentToAgent(subagentDir, subagentName, agent, agentHo
573
619
  */
574
620
  export function removeSubagentFromAgent(subagentName, agent, agentHome) {
575
621
  try {
576
- if (agent === 'claude' || agent === 'grok') {
577
- const agentsRoot = agent === 'grok' ? '.grok' : '.claude';
622
+ if (agent === 'claude' || agent === 'gemini' || agent === 'grok') {
623
+ const agentsRoot = agent === 'grok' ? '.grok' : agent === 'gemini' ? '.gemini' : '.claude';
578
624
  const targetPath = safeJoin(path.join(agentHome, agentsRoot, 'agents'), `${subagentName}.md`);
579
625
  if (fs.existsSync(targetPath)) {
580
626
  fs.unlinkSync(targetPath);
@@ -604,6 +650,12 @@ export function removeSubagentFromAgent(subagentName, agent, agentHome) {
604
650
  fs.unlinkSync(targetPath);
605
651
  return { success: true };
606
652
  }
653
+ else if (agent === 'antigravity') {
654
+ const targetDir = safeJoin(path.join(agentHome, '.gemini', 'config', 'agents'), subagentName);
655
+ if (fs.existsSync(targetDir))
656
+ fs.rmSync(targetDir, { recursive: true, force: true });
657
+ return { success: true };
658
+ }
607
659
  else if (agent === 'openclaw') {
608
660
  const targetDir = safeJoin(path.join(agentHome, '.openclaw'), subagentName);
609
661
  if (fs.existsSync(targetDir)) {
@@ -655,16 +707,17 @@ export function subagentContentMatches(installedDir, sourceDir) {
655
707
  // source of truth.
656
708
  /**
657
709
  * List subagents installed to a specific agent's home
658
- * Claude: scans ~/.claude/agents/{name}.md
710
+ * Claude/Gemini/Grok: scans ~/.{agent}/agents/{name}.md
659
711
  * Kimi: scans ~/.kimi-code/agents/{name}.yaml (+ sibling .system.md)
660
712
  * Kiro: scans ~/.kiro/agents/{name}.json
661
713
  * OpenClaw: scans ~/.openclaw/{name}/AGENTS.md
662
714
  */
663
715
  export function listSubagentsForAgent(agentId, home) {
664
716
  const subagents = [];
665
- if (agentId === 'claude' || agentId === 'grok') {
666
- // Claude / Grok: flat .md files in agents/
667
- const agentsDir = path.join(home, agentId === 'grok' ? '.grok' : '.claude', 'agents');
717
+ if (agentId === 'claude' || agentId === 'gemini' || agentId === 'grok') {
718
+ // Claude / Gemini / Grok: flat .md files in agents/
719
+ const agentsRoot = agentId === 'grok' ? '.grok' : agentId === 'gemini' ? '.gemini' : '.claude';
720
+ const agentsDir = path.join(home, agentsRoot, 'agents');
668
721
  if (!fs.existsSync(agentsDir))
669
722
  return subagents;
670
723
  for (const file of fs.readdirSync(agentsDir)) {
@@ -730,6 +783,20 @@ export function listSubagentsForAgent(agentId, home) {
730
783
  subagents.push({ name, path: filePath, files: [file], frontmatter });
731
784
  }
732
785
  }
786
+ else if (agentId === 'antigravity') {
787
+ const agentsDir = path.join(home, '.gemini', 'config', 'agents');
788
+ if (!fs.existsSync(agentsDir))
789
+ return subagents;
790
+ for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
791
+ if (!entry.isDirectory())
792
+ continue;
793
+ const filePath = path.join(agentsDir, entry.name, 'agent.md');
794
+ if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile())
795
+ continue;
796
+ const frontmatter = parseSubagentFrontmatter(filePath) ?? { name: entry.name, description: '' };
797
+ subagents.push({ name: entry.name, path: filePath, files: ['agent.md'], frontmatter });
798
+ }
799
+ }
733
800
  else if (agentId === 'copilot') {
734
801
  // Copilot: flat `<name>.agent.md` files under ~/.copilot/agents/
735
802
  const agentsDir = path.join(home, '.copilot', 'agents');
@@ -840,8 +907,9 @@ export function diffVersionSubagents(agent, version) {
840
907
  }
841
908
  }
842
909
  // Check what's installed
843
- if (agent === 'claude' || agent === 'grok') {
844
- const agentsDir = path.join(versionHome, agent === 'grok' ? '.grok' : '.claude', 'agents');
910
+ if (agent === 'claude' || agent === 'gemini' || agent === 'grok') {
911
+ const agentsRoot = agent === 'grok' ? '.grok' : agent === 'gemini' ? '.gemini' : '.claude';
912
+ const agentsDir = path.join(versionHome, agentsRoot, 'agents');
845
913
  if (fs.existsSync(agentsDir)) {
846
914
  for (const file of fs.readdirSync(agentsDir)) {
847
915
  if (!file.endsWith('.md'))
@@ -878,6 +946,19 @@ export function diffVersionSubagents(agent, version) {
878
946
  }
879
947
  }
880
948
  }
949
+ else if (agent === 'antigravity') {
950
+ const agentsDir = path.join(versionHome, '.gemini', 'config', 'agents');
951
+ if (fs.existsSync(agentsDir)) {
952
+ for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
953
+ if (!entry.isDirectory())
954
+ continue;
955
+ if (!fs.existsSync(path.join(agentsDir, entry.name, 'agent.md')))
956
+ continue;
957
+ if (!discovered.has(entry.name))
958
+ orphans.push(entry.name);
959
+ }
960
+ }
961
+ }
881
962
  else if (agent === 'openclaw') {
882
963
  const openclawDir = path.join(versionHome, '.openclaw');
883
964
  if (fs.existsSync(openclawDir)) {
@@ -932,8 +1013,8 @@ export function removeSubagentFromVersion(agent, version, subagentName) {
932
1013
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
933
1014
  const trashDir = path.join(getTrashSubagentsDir(), agent, version, subagentName);
934
1015
  try {
935
- if (agent === 'claude' || agent === 'grok') {
936
- const agentsRoot = agent === 'grok' ? '.grok' : '.claude';
1016
+ if (agent === 'claude' || agent === 'gemini' || agent === 'grok') {
1017
+ const agentsRoot = agent === 'grok' ? '.grok' : agent === 'gemini' ? '.gemini' : '.claude';
937
1018
  const targetPath = path.join(versionHome, agentsRoot, 'agents', `${subagentName}.md`);
938
1019
  if (fs.existsSync(targetPath)) {
939
1020
  fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
@@ -961,6 +1042,13 @@ export function removeSubagentFromVersion(agent, version, subagentName) {
961
1042
  fs.renameSync(targetPath, path.join(trashDir, `${subagentName}.md.${stamp}`));
962
1043
  }
963
1044
  }
1045
+ else if (agent === 'antigravity') {
1046
+ const targetDir = path.join(versionHome, '.gemini', 'config', 'agents', subagentName);
1047
+ if (fs.existsSync(targetDir)) {
1048
+ fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
1049
+ fs.renameSync(targetDir, path.join(trashDir, stamp));
1050
+ }
1051
+ }
964
1052
  else if (agent === 'copilot') {
965
1053
  const targetPath = path.join(versionHome, '.copilot', 'agents', `${subagentName}.agent.md`);
966
1054
  if (fs.existsSync(targetPath)) {
@@ -2462,9 +2462,14 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
2462
2462
  // reads only user + system layers (project excluded for the same defense
2463
2463
  // as commands/skills/hooks).
2464
2464
  const subagentsWriter = getWriter('subagents', agent);
2465
- const subagentsToSync = selection
2465
+ const subagentsGate = supports(agent, 'subagents', version);
2466
+ const subagentsRequested = selection
2466
2467
  ? resolveSelection(selection.subagents, available.subagents)
2467
2468
  : (subagentsWriter ? available.subagents : []);
2469
+ const subagentsToSync = subagentsGate.ok ? subagentsRequested : [];
2470
+ if (subagentsRequested.length > 0 && !subagentsGate.ok) {
2471
+ console.warn(explainSkip(agent, 'subagents', subagentsGate, version) + ' -- skipped');
2472
+ }
2468
2473
  if (subagentsToSync.length > 0 && subagentsWriter) {
2469
2474
  const r = subagentsWriter.write({ version, versionHome, selection: subagentsToSync, cwd });
2470
2475
  result.subagents.push(...r.synced);
@@ -2499,9 +2504,14 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
2499
2504
  }
2500
2505
  // Sync workflows — dispatch through WRITERS.workflows.
2501
2506
  const workflowsWriter = getWriter('workflows', agent);
2502
- const workflowsToSync = selection
2507
+ const workflowsGate = supports(agent, 'workflows', version);
2508
+ const workflowsRequested = selection
2503
2509
  ? resolveSelection(selection.workflows, available.workflows)
2504
2510
  : (workflowsWriter ? available.workflows : []);
2511
+ const workflowsToSync = workflowsGate.ok ? workflowsRequested : [];
2512
+ if (workflowsRequested.length > 0 && !workflowsGate.ok) {
2513
+ console.warn(explainSkip(agent, 'workflows', workflowsGate, version) + ' -- skipped');
2514
+ }
2505
2515
  if (workflowsToSync.length > 0 && workflowsWriter) {
2506
2516
  const r = workflowsWriter.write({ version, versionHome, selection: workflowsToSync, cwd });
2507
2517
  result.workflows.push(...r.synced);
@@ -263,6 +263,8 @@ export declare function resolveAllowedSubagents(available: string[], allowedAgen
263
263
  export declare function pruneStaleWorkflowSubagents(sharedAgentsDir: string, workflowSubagentFiles: string[], allowedStems: string[]): string[];
264
264
  /** Count subagent .md files in a workflow's subagents/ directory. */
265
265
  export declare function countWorkflowSubagents(workflowDir: string): number;
266
+ /** Convert a canonical agents-cli workflow bundle into a Kimi flow skill. */
267
+ export declare function transformWorkflowForKimi(workflowPath: string, name: string): string;
266
268
  /**
267
269
  * Resolve an `agents run <workflow>` reference.
268
270
  *
@@ -291,10 +293,10 @@ export declare function removeWorkflow(name: string): {
291
293
  success: boolean;
292
294
  error?: string;
293
295
  };
294
- /** List workflow names synced into a specific agent version home (at {versionHome}/workflows/). */
295
- export declare function listWorkflowsForAgent(_agent: AgentId, versionHome: string): string[];
296
+ /** List workflow names synced into a specific agent version home. */
297
+ export declare function listWorkflowsForAgent(agent: AgentId, versionHome: string): string[];
296
298
  /** Copy a workflow directory into a version home at {versionHome}/workflows/<name>/. */
297
- export declare function syncWorkflowToVersion(workflowPath: string, name: string, _agent: AgentId, versionHome: string): {
299
+ export declare function syncWorkflowToVersion(workflowPath: string, name: string, agent: AgentId, versionHome: string): {
298
300
  success: boolean;
299
301
  error?: string;
300
302
  };
@@ -56,6 +56,19 @@ export function parseWorkflowFrontmatter(workflowDir) {
56
56
  return null;
57
57
  }
58
58
  }
59
+ function readWorkflowBody(workflowDir) {
60
+ const workflowMdPath = path.join(workflowDir, 'WORKFLOW.md');
61
+ if (!fs.existsSync(workflowMdPath))
62
+ return '';
63
+ const content = fs.readFileSync(workflowMdPath, 'utf-8');
64
+ const lines = content.split('\n');
65
+ if (lines[0] !== '---')
66
+ return content.trim();
67
+ const endIndex = lines.slice(1).findIndex(l => l === '---');
68
+ if (endIndex < 0)
69
+ return content.trim();
70
+ return lines.slice(endIndex + 2).join('\n').trim();
71
+ }
59
72
  /**
60
73
  * Defensively coerce a frontmatter `loop:` value into a LoopConfigRaw.
61
74
  *
@@ -315,6 +328,47 @@ export function countWorkflowSubagents(workflowDir) {
315
328
  return 0;
316
329
  }
317
330
  }
331
+ function getWorkflowBody(workflowDir) {
332
+ const workflowMdPath = path.join(workflowDir, 'WORKFLOW.md');
333
+ if (!fs.existsSync(workflowMdPath))
334
+ return '';
335
+ const content = fs.readFileSync(workflowMdPath, 'utf-8');
336
+ const lines = content.split('\n');
337
+ if (lines[0] === '---') {
338
+ const endIndex = lines.slice(1).findIndex(l => l === '---');
339
+ if (endIndex >= 0)
340
+ return lines.slice(endIndex + 2).join('\n').trim();
341
+ }
342
+ return content.trim();
343
+ }
344
+ function indentD2BlockString(content) {
345
+ return content
346
+ .split('\n')
347
+ .map(line => ` ${line}`)
348
+ .join('\n');
349
+ }
350
+ function containsFlowDiagram(content) {
351
+ return /```(?:mermaid|d2)\b/i.test(content);
352
+ }
353
+ const KIMI_WORKFLOW_MARKER = 'agents_workflow';
354
+ /** Convert a canonical agents-cli workflow bundle into a Kimi flow skill. */
355
+ export function transformWorkflowForKimi(workflowPath, name) {
356
+ const fm = parseWorkflowFrontmatter(workflowPath);
357
+ if (!fm)
358
+ throw new Error(`Invalid WORKFLOW.md in ${workflowPath}`);
359
+ const body = getWorkflowBody(workflowPath);
360
+ const frontmatter = yaml.stringify({
361
+ name,
362
+ description: fm.description,
363
+ type: 'flow',
364
+ [KIMI_WORKFLOW_MARKER]: name,
365
+ }).trim();
366
+ if (containsFlowDiagram(body)) {
367
+ return `---\n${frontmatter}\n---\n\n${body.trim()}\n`;
368
+ }
369
+ const instructions = (body || fm.description).trim();
370
+ return `---\n${frontmatter}\n---\n\n\`\`\`d2\nBEGIN -> step -> END\nstep: |md\n${indentD2BlockString(instructions)}\n|\n\`\`\`\n`;
371
+ }
318
372
  function expandWorkflowPath(ref) {
319
373
  if (ref === '~')
320
374
  return process.env.HOME ?? ref;
@@ -473,9 +527,36 @@ export function removeWorkflow(name) {
473
527
  return { success: false, error: err.message };
474
528
  }
475
529
  }
476
- /** List workflow names synced into a specific agent version home (at {versionHome}/workflows/). */
477
- export function listWorkflowsForAgent(_agent, versionHome) {
478
- const workflowsDir = path.join(versionHome, 'workflows');
530
+ function workflowTargetRoot(agent, versionHome) {
531
+ if (agent === 'kimi')
532
+ return path.join(versionHome, '.kimi-code', 'skills');
533
+ return path.join(versionHome, 'workflows');
534
+ }
535
+ /** List workflow names synced into a specific agent version home. */
536
+ export function listWorkflowsForAgent(agent, versionHome) {
537
+ if (agent === 'kimi') {
538
+ const skillsDir = workflowTargetRoot(agent, versionHome);
539
+ if (!fs.existsSync(skillsDir))
540
+ return [];
541
+ return fs.readdirSync(skillsDir, { withFileTypes: true })
542
+ .filter(d => d.isDirectory() && fs.existsSync(path.join(skillsDir, d.name, 'SKILL.md')))
543
+ .filter(d => kimiWorkflowMarker(path.join(skillsDir, d.name, 'SKILL.md')) === d.name)
544
+ .map(d => d.name);
545
+ }
546
+ if (agent === 'goose') {
547
+ const recipesDir = path.join(versionHome, '.config', 'goose', 'recipes');
548
+ if (!fs.existsSync(recipesDir))
549
+ return [];
550
+ try {
551
+ return fs.readdirSync(recipesDir, { withFileTypes: true })
552
+ .filter(d => d.isFile() && d.name.endsWith('.yaml') && !d.name.startsWith('.'))
553
+ .map(d => d.name.slice(0, -'.yaml'.length));
554
+ }
555
+ catch {
556
+ return [];
557
+ }
558
+ }
559
+ const workflowsDir = workflowTargetRoot(agent, versionHome);
479
560
  if (!fs.existsSync(workflowsDir))
480
561
  return [];
481
562
  try {
@@ -487,11 +568,120 @@ export function listWorkflowsForAgent(_agent, versionHome) {
487
568
  return [];
488
569
  }
489
570
  }
571
+ function parseSubrecipeFrontmatter(filePath) {
572
+ const content = fs.readFileSync(filePath, 'utf-8');
573
+ const lines = content.split('\n');
574
+ if (lines[0] !== '---')
575
+ return { body: content.trim() };
576
+ const endIndex = lines.slice(1).findIndex(l => l === '---');
577
+ if (endIndex < 0)
578
+ return { body: content.trim() };
579
+ const frontmatter = lines.slice(1, endIndex + 1).join('\n');
580
+ let parsed = {};
581
+ try {
582
+ const value = yaml.parse(frontmatter);
583
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
584
+ parsed = value;
585
+ }
586
+ }
587
+ catch { /* ignore malformed subagent frontmatter */ }
588
+ return {
589
+ name: typeof parsed.name === 'string' ? parsed.name : undefined,
590
+ description: typeof parsed.description === 'string' ? parsed.description : undefined,
591
+ body: lines.slice(endIndex + 2).join('\n').trim(),
592
+ };
593
+ }
594
+ function selectedWorkflowSubagents(workflowPath, allowedAgents) {
595
+ const subagentsDir = path.join(workflowPath, 'subagents');
596
+ if (!fs.existsSync(subagentsDir))
597
+ return [];
598
+ const allowed = allowedAgents ? new Set(allowedAgents) : null;
599
+ return fs.readdirSync(subagentsDir, { withFileTypes: true })
600
+ .filter(e => e.isFile() && e.name.endsWith('.md') && !e.name.startsWith('.'))
601
+ .map(e => e.name.slice(0, -'.md'.length))
602
+ .filter(name => !allowed || allowed.has(name))
603
+ .sort();
604
+ }
605
+ function writeGooseSubrecipe(workflowPath, subrecipeName, destDir) {
606
+ const sourcePath = path.join(workflowPath, 'subagents', `${subrecipeName}.md`);
607
+ const parsed = parseSubrecipeFrontmatter(sourcePath);
608
+ const body = parsed.body || parsed.description || subrecipeName;
609
+ const recipe = {
610
+ version: '1.0.0',
611
+ title: parsed.name || subrecipeName,
612
+ description: parsed.description || `Subrecipe for ${subrecipeName}`,
613
+ instructions: body,
614
+ prompt: body,
615
+ };
616
+ fs.mkdirSync(destDir, { recursive: true });
617
+ fs.writeFileSync(path.join(destDir, `${subrecipeName}.yaml`), yaml.stringify(recipe), 'utf-8');
618
+ }
619
+ function syncWorkflowToGooseRecipe(workflowPath, name, versionHome) {
620
+ const frontmatter = parseWorkflowFrontmatter(workflowPath);
621
+ if (!frontmatter) {
622
+ return { success: false, error: `Workflow '${name}' has invalid WORKFLOW.md frontmatter` };
623
+ }
624
+ const recipesDir = path.join(versionHome, '.config', 'goose', 'recipes');
625
+ const recipePath = path.join(recipesDir, `${name}.yaml`);
626
+ const subrecipesDir = path.join(recipesDir, `${name}.subrecipes`);
627
+ const body = readWorkflowBody(workflowPath) || frontmatter.description || name;
628
+ const subagents = selectedWorkflowSubagents(workflowPath, frontmatter.allowedAgents);
629
+ const recipe = {
630
+ version: '1.0.0',
631
+ title: frontmatter.name || name,
632
+ description: frontmatter.description || name,
633
+ instructions: body,
634
+ prompt: body,
635
+ };
636
+ if (frontmatter.model) {
637
+ recipe.settings = { goose_model: frontmatter.model };
638
+ }
639
+ if (subagents.length > 0) {
640
+ recipe.sub_recipes = subagents.map(subagentName => ({
641
+ name: subagentName,
642
+ path: `./${name}.subrecipes/${subagentName}.yaml`,
643
+ description: `Workflow subrecipe ${subagentName}`,
644
+ }));
645
+ }
646
+ try {
647
+ fs.mkdirSync(recipesDir, { recursive: true });
648
+ if (fs.existsSync(subrecipesDir)) {
649
+ fs.rmSync(subrecipesDir, { recursive: true, force: true });
650
+ }
651
+ for (const subagentName of subagents) {
652
+ writeGooseSubrecipe(workflowPath, subagentName, subrecipesDir);
653
+ }
654
+ fs.writeFileSync(recipePath, yaml.stringify(recipe), 'utf-8');
655
+ return { success: true };
656
+ }
657
+ catch (err) {
658
+ return { success: false, error: err.message };
659
+ }
660
+ }
490
661
  /** Copy a workflow directory into a version home at {versionHome}/workflows/<name>/. */
491
- export function syncWorkflowToVersion(workflowPath, name, _agent, versionHome) {
492
- const targetDir = path.join(versionHome, 'workflows', name);
662
+ export function syncWorkflowToVersion(workflowPath, name, agent, versionHome) {
663
+ if (agent === 'goose') {
664
+ return syncWorkflowToGooseRecipe(workflowPath, name, versionHome);
665
+ }
493
666
  try {
494
- fs.mkdirSync(path.join(versionHome, 'workflows'), { recursive: true });
667
+ if (agent === 'kimi') {
668
+ const targetDir = path.join(workflowTargetRoot(agent, versionHome), name);
669
+ const targetFile = path.join(targetDir, 'SKILL.md');
670
+ if (fs.existsSync(targetFile)) {
671
+ const marker = kimiWorkflowMarker(targetFile);
672
+ if (marker !== name) {
673
+ return { success: false, error: `Kimi skill '${name}' already exists and is not managed by agents-cli` };
674
+ }
675
+ }
676
+ fs.mkdirSync(targetDir, { recursive: true });
677
+ fs.writeFileSync(targetFile, transformWorkflowForKimi(workflowPath, name), 'utf-8');
678
+ return { success: true };
679
+ }
680
+ if (agent === 'antigravity') {
681
+ return { success: false, error: 'Antigravity workflow sync is not supported for version homes' };
682
+ }
683
+ const targetDir = path.join(workflowTargetRoot(agent, versionHome), name);
684
+ fs.mkdirSync(workflowTargetRoot(agent, versionHome), { recursive: true });
495
685
  if (fs.existsSync(targetDir)) {
496
686
  fs.rmSync(targetDir, { recursive: true, force: true });
497
687
  }
@@ -505,18 +695,65 @@ export function syncWorkflowToVersion(workflowPath, name, _agent, versionHome) {
505
695
  /** Remove a workflow from a specific agent version home. */
506
696
  export function removeWorkflowFromVersion(agent, version, name) {
507
697
  const versionHome = getVersionHomePath(agent, version);
508
- const targetDir = path.join(versionHome, 'workflows', name);
509
- if (!fs.existsSync(targetDir)) {
698
+ if (agent === 'antigravity') {
699
+ return { success: false, error: 'Antigravity workflow sync is not supported for version homes' };
700
+ }
701
+ if (agent === 'goose') {
702
+ const recipePath = path.join(versionHome, '.config', 'goose', 'recipes', `${name}.yaml`);
703
+ const subrecipesDir = path.join(versionHome, '.config', 'goose', 'recipes', `${name}.subrecipes`);
704
+ if (!fs.existsSync(recipePath) && !fs.existsSync(subrecipesDir)) {
705
+ return { success: false, error: `Workflow '${name}' not synced to ${agent}@${version}` };
706
+ }
707
+ try {
708
+ if (fs.existsSync(recipePath))
709
+ fs.rmSync(recipePath, { force: true });
710
+ if (fs.existsSync(subrecipesDir))
711
+ fs.rmSync(subrecipesDir, { recursive: true, force: true });
712
+ return { success: true };
713
+ }
714
+ catch (err) {
715
+ return { success: false, error: err.message };
716
+ }
717
+ }
718
+ const targetPath = path.join(workflowTargetRoot(agent, versionHome), name);
719
+ if (!fs.existsSync(targetPath)) {
510
720
  return { success: false, error: `Workflow '${name}' not synced to ${agent}@${version}` };
511
721
  }
512
722
  try {
513
- fs.rmSync(targetDir, { recursive: true, force: true });
723
+ if (agent === 'kimi') {
724
+ const targetFile = path.join(targetPath, 'SKILL.md');
725
+ if (kimiWorkflowMarker(targetFile) !== name) {
726
+ return { success: false, error: `Kimi skill '${name}' is not managed by agents-cli` };
727
+ }
728
+ }
729
+ fs.rmSync(targetPath, { recursive: true, force: true });
514
730
  return { success: true };
515
731
  }
516
732
  catch (err) {
517
733
  return { success: false, error: err.message };
518
734
  }
519
735
  }
736
+ function parseSkillFrontmatter(filePath) {
737
+ const content = fs.readFileSync(filePath, 'utf-8');
738
+ const lines = content.split('\n');
739
+ if (lines[0] !== '---')
740
+ return null;
741
+ const endIndex = lines.slice(1).findIndex(l => l === '---');
742
+ if (endIndex < 0)
743
+ return null;
744
+ const frontmatter = lines.slice(1, endIndex + 1).join('\n');
745
+ const parsed = yaml.parse(frontmatter);
746
+ return parsed && typeof parsed === 'object' ? parsed : null;
747
+ }
748
+ function kimiWorkflowMarker(filePath) {
749
+ try {
750
+ const fm = parseSkillFrontmatter(filePath);
751
+ return fm?.type === 'flow' && typeof fm.agents_workflow === 'string' ? fm.agents_workflow : null;
752
+ }
753
+ catch {
754
+ return null;
755
+ }
756
+ }
520
757
  /** Iterate all installed (agent, version) pairs that support workflows. */
521
758
  export function iterWorkflowsCapableVersions(filter) {
522
759
  const result = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.59",
3
+ "version": "1.20.60",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",