@phnx-labs/agents-cli 1.20.59 → 1.20.61

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.
@@ -1148,6 +1148,72 @@ export function setGlobalDefault(agent, version) {
1148
1148
  }
1149
1149
  writeMeta(meta);
1150
1150
  }
1151
+ /**
1152
+ * Grok's official installer writes into ~/.grok/downloads, which (because we
1153
+ * symlink ~/.grok to the active version home) resolves to the PREVIOUS default
1154
+ * home during `agents add grok@<new>`. Move the freshly-downloaded binary and
1155
+ * its generic platform copy into the target version's isolated home so
1156
+ * `listInstalledVersions` and the shim resolve the right binary.
1157
+ */
1158
+ function relocateGrokBinaryToVersionHome(installedVersion) {
1159
+ const hostGrokLink = path.join(getHomeDir(), agentConfigDirName('grok'));
1160
+ let sourceDownloads;
1161
+ try {
1162
+ sourceDownloads = path.join(fs.readlinkSync(hostGrokLink), 'downloads');
1163
+ }
1164
+ catch {
1165
+ sourceDownloads = path.join(hostGrokLink, 'downloads');
1166
+ }
1167
+ const targetDownloads = path.join(getVersionHomePath('grok', installedVersion), agentConfigDirName('grok'), 'downloads');
1168
+ if (!fs.existsSync(sourceDownloads))
1169
+ return;
1170
+ if (path.resolve(sourceDownloads) === path.resolve(targetDownloads))
1171
+ return;
1172
+ const entries = fs.readdirSync(sourceDownloads).filter((e) => e.startsWith('grok-'));
1173
+ if (entries.length === 0)
1174
+ return;
1175
+ fs.mkdirSync(targetDownloads, { recursive: true });
1176
+ const escapedVersion = installedVersion.replace(/\./g, '\\.');
1177
+ const versionedPattern = new RegExp(`^grok-${escapedVersion}-`);
1178
+ const movedPaths = [];
1179
+ // Move the versioned binary first.
1180
+ for (const entry of entries) {
1181
+ if (!versionedPattern.test(entry))
1182
+ continue;
1183
+ const src = path.join(sourceDownloads, entry);
1184
+ const dst = path.join(targetDownloads, entry);
1185
+ if (fs.existsSync(dst))
1186
+ continue;
1187
+ try {
1188
+ fs.renameSync(src, dst);
1189
+ movedPaths.push(dst);
1190
+ }
1191
+ catch {
1192
+ /* ignore per-file failures */
1193
+ }
1194
+ }
1195
+ if (movedPaths.length === 0)
1196
+ return;
1197
+ // The installer also creates a generic platform binary (e.g. grok-macos-aarch64)
1198
+ // that is a copy of the versioned binary. Move it too if its size matches.
1199
+ const movedSize = fs.statSync(movedPaths[0]).size;
1200
+ for (const entry of entries) {
1201
+ if (versionedPattern.test(entry))
1202
+ continue; // already handled
1203
+ const src = path.join(sourceDownloads, entry);
1204
+ const dst = path.join(targetDownloads, entry);
1205
+ if (fs.existsSync(dst))
1206
+ continue;
1207
+ try {
1208
+ if (fs.statSync(src).size === movedSize) {
1209
+ fs.renameSync(src, dst);
1210
+ }
1211
+ }
1212
+ catch {
1213
+ /* ignore per-file failures */
1214
+ }
1215
+ }
1216
+ }
1151
1217
  /**
1152
1218
  * Install a specific version of an agent.
1153
1219
  */
@@ -1204,6 +1270,12 @@ export async function installVersion(agent, version, onProgress, opts) {
1204
1270
  const versionDir = getVersionDir(agent, installedVersion);
1205
1271
  fs.mkdirSync(versionDir, { recursive: true });
1206
1272
  fs.mkdirSync(path.join(versionDir, 'home'), { recursive: true });
1273
+ // Grok's installer drops the binary into ~/.grok/downloads, which currently
1274
+ // resolves to the PREVIOUS default home. Move it into the target version home
1275
+ // so version isolation is correct.
1276
+ if (agent === 'grok') {
1277
+ relocateGrokBinaryToVersionHome(installedVersion);
1278
+ }
1207
1279
  // Symlink the installed binary into the version's node_modules/.bin so
1208
1280
  // listInstalledVersions (which checks getBinaryPath) sees this version as
1209
1281
  // installed. Without this, `agents add antigravity@latest` succeeds
@@ -2462,9 +2534,14 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
2462
2534
  // reads only user + system layers (project excluded for the same defense
2463
2535
  // as commands/skills/hooks).
2464
2536
  const subagentsWriter = getWriter('subagents', agent);
2465
- const subagentsToSync = selection
2537
+ const subagentsGate = supports(agent, 'subagents', version);
2538
+ const subagentsRequested = selection
2466
2539
  ? resolveSelection(selection.subagents, available.subagents)
2467
2540
  : (subagentsWriter ? available.subagents : []);
2541
+ const subagentsToSync = subagentsGate.ok ? subagentsRequested : [];
2542
+ if (subagentsRequested.length > 0 && !subagentsGate.ok) {
2543
+ console.warn(explainSkip(agent, 'subagents', subagentsGate, version) + ' -- skipped');
2544
+ }
2468
2545
  if (subagentsToSync.length > 0 && subagentsWriter) {
2469
2546
  const r = subagentsWriter.write({ version, versionHome, selection: subagentsToSync, cwd });
2470
2547
  result.subagents.push(...r.synced);
@@ -2499,9 +2576,14 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
2499
2576
  }
2500
2577
  // Sync workflows — dispatch through WRITERS.workflows.
2501
2578
  const workflowsWriter = getWriter('workflows', agent);
2502
- const workflowsToSync = selection
2579
+ const workflowsGate = supports(agent, 'workflows', version);
2580
+ const workflowsRequested = selection
2503
2581
  ? resolveSelection(selection.workflows, available.workflows)
2504
2582
  : (workflowsWriter ? available.workflows : []);
2583
+ const workflowsToSync = workflowsGate.ok ? workflowsRequested : [];
2584
+ if (workflowsRequested.length > 0 && !workflowsGate.ok) {
2585
+ console.warn(explainSkip(agent, 'workflows', workflowsGate, version) + ' -- skipped');
2586
+ }
2505
2587
  if (workflowsToSync.length > 0 && workflowsWriter) {
2506
2588
  const r = workflowsWriter.write({ version, versionHome, selection: workflowsToSync, cwd });
2507
2589
  result.workflows.push(...r.synced);
@@ -263,6 +263,17 @@ 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;
268
+ /**
269
+ * Convert a canonical agents-cli workflow bundle into an Antigravity workflow
270
+ * markdown file. Antigravity discovers workflows as flat `<name>.md` files under
271
+ * `~/.gemini/config/global_workflows/` (scanned by `agy` at startup) and exposes
272
+ * each as a `/<name>` slash command. Frontmatter carries the required `description`
273
+ * plus the shared `agents_workflow` ownership marker so agents-cli never clobbers a
274
+ * user-authored workflow of the same name.
275
+ */
276
+ export declare function transformWorkflowForAntigravity(workflowPath: string, name: string): string;
266
277
  /**
267
278
  * Resolve an `agents run <workflow>` reference.
268
279
  *
@@ -291,10 +302,10 @@ export declare function removeWorkflow(name: string): {
291
302
  success: boolean;
292
303
  error?: string;
293
304
  };
294
- /** List workflow names synced into a specific agent version home (at {versionHome}/workflows/). */
295
- export declare function listWorkflowsForAgent(_agent: AgentId, versionHome: string): string[];
305
+ /** List workflow names synced into a specific agent version home. */
306
+ export declare function listWorkflowsForAgent(agent: AgentId, versionHome: string): string[];
296
307
  /** 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): {
308
+ export declare function syncWorkflowToVersion(workflowPath: string, name: string, agent: AgentId, versionHome: string): {
298
309
  success: boolean;
299
310
  error?: string;
300
311
  };
@@ -6,6 +6,7 @@
6
6
  * are composed at runtime by `agents run <workflow>`.
7
7
  */
8
8
  import * as fs from 'fs';
9
+ import * as os from 'os';
9
10
  import * as path from 'path';
10
11
  import * as yaml from 'yaml';
11
12
  import { capableAgents } from './capabilities.js';
@@ -56,6 +57,19 @@ export function parseWorkflowFrontmatter(workflowDir) {
56
57
  return null;
57
58
  }
58
59
  }
60
+ function readWorkflowBody(workflowDir) {
61
+ const workflowMdPath = path.join(workflowDir, 'WORKFLOW.md');
62
+ if (!fs.existsSync(workflowMdPath))
63
+ return '';
64
+ const content = fs.readFileSync(workflowMdPath, 'utf-8');
65
+ const lines = content.split('\n');
66
+ if (lines[0] !== '---')
67
+ return content.trim();
68
+ const endIndex = lines.slice(1).findIndex(l => l === '---');
69
+ if (endIndex < 0)
70
+ return content.trim();
71
+ return lines.slice(endIndex + 2).join('\n').trim();
72
+ }
59
73
  /**
60
74
  * Defensively coerce a frontmatter `loop:` value into a LoopConfigRaw.
61
75
  *
@@ -315,6 +329,67 @@ export function countWorkflowSubagents(workflowDir) {
315
329
  return 0;
316
330
  }
317
331
  }
332
+ function getWorkflowBody(workflowDir) {
333
+ const workflowMdPath = path.join(workflowDir, 'WORKFLOW.md');
334
+ if (!fs.existsSync(workflowMdPath))
335
+ return '';
336
+ const content = fs.readFileSync(workflowMdPath, 'utf-8');
337
+ const lines = content.split('\n');
338
+ if (lines[0] === '---') {
339
+ const endIndex = lines.slice(1).findIndex(l => l === '---');
340
+ if (endIndex >= 0)
341
+ return lines.slice(endIndex + 2).join('\n').trim();
342
+ }
343
+ return content.trim();
344
+ }
345
+ function indentD2BlockString(content) {
346
+ return content
347
+ .split('\n')
348
+ .map(line => ` ${line}`)
349
+ .join('\n');
350
+ }
351
+ function containsFlowDiagram(content) {
352
+ return /```(?:mermaid|d2)\b/i.test(content);
353
+ }
354
+ const KIMI_WORKFLOW_MARKER = 'agents_workflow';
355
+ /** Convert a canonical agents-cli workflow bundle into a Kimi flow skill. */
356
+ export function transformWorkflowForKimi(workflowPath, name) {
357
+ const fm = parseWorkflowFrontmatter(workflowPath);
358
+ if (!fm)
359
+ throw new Error(`Invalid WORKFLOW.md in ${workflowPath}`);
360
+ const body = getWorkflowBody(workflowPath);
361
+ const frontmatter = yaml.stringify({
362
+ name,
363
+ description: fm.description,
364
+ type: 'flow',
365
+ [KIMI_WORKFLOW_MARKER]: name,
366
+ }).trim();
367
+ if (containsFlowDiagram(body)) {
368
+ return `---\n${frontmatter}\n---\n\n${body.trim()}\n`;
369
+ }
370
+ const instructions = (body || fm.description).trim();
371
+ return `---\n${frontmatter}\n---\n\n\`\`\`d2\nBEGIN -> step -> END\nstep: |md\n${indentD2BlockString(instructions)}\n|\n\`\`\`\n`;
372
+ }
373
+ /**
374
+ * Convert a canonical agents-cli workflow bundle into an Antigravity workflow
375
+ * markdown file. Antigravity discovers workflows as flat `<name>.md` files under
376
+ * `~/.gemini/config/global_workflows/` (scanned by `agy` at startup) and exposes
377
+ * each as a `/<name>` slash command. Frontmatter carries the required `description`
378
+ * plus the shared `agents_workflow` ownership marker so agents-cli never clobbers a
379
+ * user-authored workflow of the same name.
380
+ */
381
+ export function transformWorkflowForAntigravity(workflowPath, name) {
382
+ const fm = parseWorkflowFrontmatter(workflowPath);
383
+ if (!fm)
384
+ throw new Error(`Invalid WORKFLOW.md in ${workflowPath}`);
385
+ const body = getWorkflowBody(workflowPath) || fm.description;
386
+ const frontmatter = yaml.stringify({
387
+ description: fm.description,
388
+ name: fm.name || name,
389
+ [KIMI_WORKFLOW_MARKER]: name,
390
+ }).trim();
391
+ return `---\n${frontmatter}\n---\n\n${body.trim()}\n`;
392
+ }
318
393
  function expandWorkflowPath(ref) {
319
394
  if (ref === '~')
320
395
  return process.env.HOME ?? ref;
@@ -473,9 +548,65 @@ export function removeWorkflow(name) {
473
548
  return { success: false, error: err.message };
474
549
  }
475
550
  }
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');
551
+ /**
552
+ * Antigravity user workflows are NOT version-isolated. `agy` scans a single,
553
+ * shared, HOME-global directory at startup — `~/.gemini/config/global_workflows/`
554
+ * — and that dir is a real directory in the user's home, never symlinked into a
555
+ * per-version home (only `~/.gemini/antigravity-cli` is version-scoped). Writing
556
+ * into a version home therefore lands somewhere agy never reads. So every
557
+ * antigravity version resolves to the same real shared dir; `versionHome` is
558
+ * intentionally ignored. (Verified via strace of `agy`: it opens
559
+ * `$HOME/.gemini/config/global_workflows/<name>.md` and never the version home.)
560
+ */
561
+ function antigravityWorkflowsDir() {
562
+ return path.join(process.env.HOME ?? os.homedir(), '.gemini', 'config', 'global_workflows');
563
+ }
564
+ function workflowTargetRoot(agent, versionHome) {
565
+ if (agent === 'kimi')
566
+ return path.join(versionHome, '.kimi-code', 'skills');
567
+ if (agent === 'antigravity')
568
+ return antigravityWorkflowsDir();
569
+ return path.join(versionHome, 'workflows');
570
+ }
571
+ /** List workflow names synced into a specific agent version home. */
572
+ export function listWorkflowsForAgent(agent, versionHome) {
573
+ if (agent === 'kimi') {
574
+ const skillsDir = workflowTargetRoot(agent, versionHome);
575
+ if (!fs.existsSync(skillsDir))
576
+ return [];
577
+ return fs.readdirSync(skillsDir, { withFileTypes: true })
578
+ .filter(d => d.isDirectory() && fs.existsSync(path.join(skillsDir, d.name, 'SKILL.md')))
579
+ .filter(d => kimiWorkflowMarker(path.join(skillsDir, d.name, 'SKILL.md')) === d.name)
580
+ .map(d => d.name);
581
+ }
582
+ if (agent === 'antigravity') {
583
+ const dir = workflowTargetRoot(agent, versionHome);
584
+ if (!fs.existsSync(dir))
585
+ return [];
586
+ try {
587
+ return fs.readdirSync(dir, { withFileTypes: true })
588
+ .filter(d => d.isFile() && d.name.endsWith('.md') && !d.name.startsWith('.'))
589
+ .map(d => d.name.slice(0, -'.md'.length))
590
+ .filter(base => antigravityWorkflowMarker(path.join(dir, `${base}.md`)) === base);
591
+ }
592
+ catch {
593
+ return [];
594
+ }
595
+ }
596
+ if (agent === 'goose') {
597
+ const recipesDir = path.join(versionHome, '.config', 'goose', 'recipes');
598
+ if (!fs.existsSync(recipesDir))
599
+ return [];
600
+ try {
601
+ return fs.readdirSync(recipesDir, { withFileTypes: true })
602
+ .filter(d => d.isFile() && d.name.endsWith('.yaml') && !d.name.startsWith('.'))
603
+ .map(d => d.name.slice(0, -'.yaml'.length));
604
+ }
605
+ catch {
606
+ return [];
607
+ }
608
+ }
609
+ const workflowsDir = workflowTargetRoot(agent, versionHome);
479
610
  if (!fs.existsSync(workflowsDir))
480
611
  return [];
481
612
  try {
@@ -487,11 +618,130 @@ export function listWorkflowsForAgent(_agent, versionHome) {
487
618
  return [];
488
619
  }
489
620
  }
621
+ function parseSubrecipeFrontmatter(filePath) {
622
+ const content = fs.readFileSync(filePath, 'utf-8');
623
+ const lines = content.split('\n');
624
+ if (lines[0] !== '---')
625
+ return { body: content.trim() };
626
+ const endIndex = lines.slice(1).findIndex(l => l === '---');
627
+ if (endIndex < 0)
628
+ return { body: content.trim() };
629
+ const frontmatter = lines.slice(1, endIndex + 1).join('\n');
630
+ let parsed = {};
631
+ try {
632
+ const value = yaml.parse(frontmatter);
633
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
634
+ parsed = value;
635
+ }
636
+ }
637
+ catch { /* ignore malformed subagent frontmatter */ }
638
+ return {
639
+ name: typeof parsed.name === 'string' ? parsed.name : undefined,
640
+ description: typeof parsed.description === 'string' ? parsed.description : undefined,
641
+ body: lines.slice(endIndex + 2).join('\n').trim(),
642
+ };
643
+ }
644
+ function selectedWorkflowSubagents(workflowPath, allowedAgents) {
645
+ const subagentsDir = path.join(workflowPath, 'subagents');
646
+ if (!fs.existsSync(subagentsDir))
647
+ return [];
648
+ const allowed = allowedAgents ? new Set(allowedAgents) : null;
649
+ return fs.readdirSync(subagentsDir, { withFileTypes: true })
650
+ .filter(e => e.isFile() && e.name.endsWith('.md') && !e.name.startsWith('.'))
651
+ .map(e => e.name.slice(0, -'.md'.length))
652
+ .filter(name => !allowed || allowed.has(name))
653
+ .sort();
654
+ }
655
+ function writeGooseSubrecipe(workflowPath, subrecipeName, destDir) {
656
+ const sourcePath = path.join(workflowPath, 'subagents', `${subrecipeName}.md`);
657
+ const parsed = parseSubrecipeFrontmatter(sourcePath);
658
+ const body = parsed.body || parsed.description || subrecipeName;
659
+ const recipe = {
660
+ version: '1.0.0',
661
+ title: parsed.name || subrecipeName,
662
+ description: parsed.description || `Subrecipe for ${subrecipeName}`,
663
+ instructions: body,
664
+ prompt: body,
665
+ };
666
+ fs.mkdirSync(destDir, { recursive: true });
667
+ fs.writeFileSync(path.join(destDir, `${subrecipeName}.yaml`), yaml.stringify(recipe), 'utf-8');
668
+ }
669
+ function syncWorkflowToGooseRecipe(workflowPath, name, versionHome) {
670
+ const frontmatter = parseWorkflowFrontmatter(workflowPath);
671
+ if (!frontmatter) {
672
+ return { success: false, error: `Workflow '${name}' has invalid WORKFLOW.md frontmatter` };
673
+ }
674
+ const recipesDir = path.join(versionHome, '.config', 'goose', 'recipes');
675
+ const recipePath = path.join(recipesDir, `${name}.yaml`);
676
+ const subrecipesDir = path.join(recipesDir, `${name}.subrecipes`);
677
+ const body = readWorkflowBody(workflowPath) || frontmatter.description || name;
678
+ const subagents = selectedWorkflowSubagents(workflowPath, frontmatter.allowedAgents);
679
+ const recipe = {
680
+ version: '1.0.0',
681
+ title: frontmatter.name || name,
682
+ description: frontmatter.description || name,
683
+ instructions: body,
684
+ prompt: body,
685
+ };
686
+ if (frontmatter.model) {
687
+ recipe.settings = { goose_model: frontmatter.model };
688
+ }
689
+ if (subagents.length > 0) {
690
+ recipe.sub_recipes = subagents.map(subagentName => ({
691
+ name: subagentName,
692
+ path: `./${name}.subrecipes/${subagentName}.yaml`,
693
+ description: `Workflow subrecipe ${subagentName}`,
694
+ }));
695
+ }
696
+ try {
697
+ fs.mkdirSync(recipesDir, { recursive: true });
698
+ if (fs.existsSync(subrecipesDir)) {
699
+ fs.rmSync(subrecipesDir, { recursive: true, force: true });
700
+ }
701
+ for (const subagentName of subagents) {
702
+ writeGooseSubrecipe(workflowPath, subagentName, subrecipesDir);
703
+ }
704
+ fs.writeFileSync(recipePath, yaml.stringify(recipe), 'utf-8');
705
+ return { success: true };
706
+ }
707
+ catch (err) {
708
+ return { success: false, error: err.message };
709
+ }
710
+ }
490
711
  /** 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);
712
+ export function syncWorkflowToVersion(workflowPath, name, agent, versionHome) {
713
+ if (agent === 'goose') {
714
+ return syncWorkflowToGooseRecipe(workflowPath, name, versionHome);
715
+ }
493
716
  try {
494
- fs.mkdirSync(path.join(versionHome, 'workflows'), { recursive: true });
717
+ if (agent === 'kimi') {
718
+ const targetDir = path.join(workflowTargetRoot(agent, versionHome), name);
719
+ const targetFile = path.join(targetDir, 'SKILL.md');
720
+ if (fs.existsSync(targetFile)) {
721
+ const marker = kimiWorkflowMarker(targetFile);
722
+ if (marker !== name) {
723
+ return { success: false, error: `Kimi skill '${name}' already exists and is not managed by agents-cli` };
724
+ }
725
+ }
726
+ fs.mkdirSync(targetDir, { recursive: true });
727
+ fs.writeFileSync(targetFile, transformWorkflowForKimi(workflowPath, name), 'utf-8');
728
+ return { success: true };
729
+ }
730
+ if (agent === 'antigravity') {
731
+ const targetDir = workflowTargetRoot(agent, versionHome);
732
+ const targetFile = path.join(targetDir, `${name}.md`);
733
+ if (fs.existsSync(targetFile)) {
734
+ const marker = antigravityWorkflowMarker(targetFile);
735
+ if (marker !== name) {
736
+ return { success: false, error: `Antigravity workflow '${name}' already exists and is not managed by agents-cli` };
737
+ }
738
+ }
739
+ fs.mkdirSync(targetDir, { recursive: true });
740
+ fs.writeFileSync(targetFile, transformWorkflowForAntigravity(workflowPath, name), 'utf-8');
741
+ return { success: true };
742
+ }
743
+ const targetDir = path.join(workflowTargetRoot(agent, versionHome), name);
744
+ fs.mkdirSync(workflowTargetRoot(agent, versionHome), { recursive: true });
495
745
  if (fs.existsSync(targetDir)) {
496
746
  fs.rmSync(targetDir, { recursive: true, force: true });
497
747
  }
@@ -505,18 +755,87 @@ export function syncWorkflowToVersion(workflowPath, name, _agent, versionHome) {
505
755
  /** Remove a workflow from a specific agent version home. */
506
756
  export function removeWorkflowFromVersion(agent, version, name) {
507
757
  const versionHome = getVersionHomePath(agent, version);
508
- const targetDir = path.join(versionHome, 'workflows', name);
509
- if (!fs.existsSync(targetDir)) {
758
+ if (agent === 'antigravity') {
759
+ const targetFile = path.join(workflowTargetRoot(agent, versionHome), `${name}.md`);
760
+ if (!fs.existsSync(targetFile)) {
761
+ return { success: false, error: `Workflow '${name}' not synced to ${agent}@${version}` };
762
+ }
763
+ if (antigravityWorkflowMarker(targetFile) !== name) {
764
+ return { success: false, error: `Antigravity workflow '${name}' is not managed by agents-cli` };
765
+ }
766
+ try {
767
+ fs.rmSync(targetFile, { force: true });
768
+ return { success: true };
769
+ }
770
+ catch (err) {
771
+ return { success: false, error: err.message };
772
+ }
773
+ }
774
+ if (agent === 'goose') {
775
+ const recipePath = path.join(versionHome, '.config', 'goose', 'recipes', `${name}.yaml`);
776
+ const subrecipesDir = path.join(versionHome, '.config', 'goose', 'recipes', `${name}.subrecipes`);
777
+ if (!fs.existsSync(recipePath) && !fs.existsSync(subrecipesDir)) {
778
+ return { success: false, error: `Workflow '${name}' not synced to ${agent}@${version}` };
779
+ }
780
+ try {
781
+ if (fs.existsSync(recipePath))
782
+ fs.rmSync(recipePath, { force: true });
783
+ if (fs.existsSync(subrecipesDir))
784
+ fs.rmSync(subrecipesDir, { recursive: true, force: true });
785
+ return { success: true };
786
+ }
787
+ catch (err) {
788
+ return { success: false, error: err.message };
789
+ }
790
+ }
791
+ const targetPath = path.join(workflowTargetRoot(agent, versionHome), name);
792
+ if (!fs.existsSync(targetPath)) {
510
793
  return { success: false, error: `Workflow '${name}' not synced to ${agent}@${version}` };
511
794
  }
512
795
  try {
513
- fs.rmSync(targetDir, { recursive: true, force: true });
796
+ if (agent === 'kimi') {
797
+ const targetFile = path.join(targetPath, 'SKILL.md');
798
+ if (kimiWorkflowMarker(targetFile) !== name) {
799
+ return { success: false, error: `Kimi skill '${name}' is not managed by agents-cli` };
800
+ }
801
+ }
802
+ fs.rmSync(targetPath, { recursive: true, force: true });
514
803
  return { success: true };
515
804
  }
516
805
  catch (err) {
517
806
  return { success: false, error: err.message };
518
807
  }
519
808
  }
809
+ function parseSkillFrontmatter(filePath) {
810
+ const content = fs.readFileSync(filePath, 'utf-8');
811
+ const lines = content.split('\n');
812
+ if (lines[0] !== '---')
813
+ return null;
814
+ const endIndex = lines.slice(1).findIndex(l => l === '---');
815
+ if (endIndex < 0)
816
+ return null;
817
+ const frontmatter = lines.slice(1, endIndex + 1).join('\n');
818
+ const parsed = yaml.parse(frontmatter);
819
+ return parsed && typeof parsed === 'object' ? parsed : null;
820
+ }
821
+ function kimiWorkflowMarker(filePath) {
822
+ try {
823
+ const fm = parseSkillFrontmatter(filePath);
824
+ return fm?.type === 'flow' && typeof fm.agents_workflow === 'string' ? fm.agents_workflow : null;
825
+ }
826
+ catch {
827
+ return null;
828
+ }
829
+ }
830
+ function antigravityWorkflowMarker(filePath) {
831
+ try {
832
+ const fm = parseSkillFrontmatter(filePath);
833
+ return typeof fm?.agents_workflow === 'string' ? fm.agents_workflow : null;
834
+ }
835
+ catch {
836
+ return null;
837
+ }
838
+ }
520
839
  /** Iterate all installed (agent, version) pairs that support workflows. */
521
840
  export function iterWorkflowsCapableVersions(filter) {
522
841
  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.61",
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",