@phnx-labs/agents-cli 1.20.58 → 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.
- package/CHANGELOG.md +23 -1
- package/README.md +15 -7
- package/dist/bin/agents +0 -0
- package/dist/commands/exec.js +39 -2
- package/dist/commands/output.d.ts +19 -0
- package/dist/commands/output.js +333 -0
- package/dist/commands/secrets.js +6 -6
- package/dist/index.js +2 -1
- package/dist/lib/agents.js +19 -13
- package/dist/lib/hosts/dispatch.d.ts +36 -0
- package/dist/lib/hosts/dispatch.js +40 -2
- package/dist/lib/hosts/passthrough.js +1 -0
- package/dist/lib/mcp.js +1 -1
- package/dist/lib/output/git-output.d.ts +74 -0
- package/dist/lib/output/git-output.js +213 -0
- package/dist/lib/permissions.d.ts +26 -5
- package/dist/lib/permissions.js +212 -37
- package/dist/lib/plugins.d.ts +8 -0
- package/dist/lib/plugins.js +108 -0
- package/dist/lib/project-root.js +2 -1
- package/dist/lib/resources/mcp.js +1 -1
- package/dist/lib/resources/permissions.d.ts +1 -1
- package/dist/lib/resources/permissions.js +8 -2
- package/dist/lib/resources/skills.js +6 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/routines.d.ts +16 -0
- package/dist/lib/routines.js +46 -1
- package/dist/lib/secrets/remote.d.ts +7 -2
- package/dist/lib/secrets/remote.js +11 -10
- package/dist/lib/session/db.d.ts +3 -0
- package/dist/lib/session/db.js +20 -4
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +40 -4
- package/dist/lib/session/types.d.ts +2 -0
- package/dist/lib/shims.js +13 -3
- package/dist/lib/skills.js +14 -1
- package/dist/lib/staleness/detectors/permissions.js +50 -3
- package/dist/lib/staleness/detectors/subagents.js +31 -12
- package/dist/lib/staleness/detectors/workflows.js +33 -0
- package/dist/lib/staleness/writers/commands.js +3 -3
- package/dist/lib/staleness/writers/subagents.js +13 -5
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/subagents.d.ts +11 -1
- package/dist/lib/subagents.js +117 -26
- package/dist/lib/versions.js +12 -2
- package/dist/lib/workflows.d.ts +5 -3
- package/dist/lib/workflows.js +246 -9
- package/package.json +1 -1
package/dist/lib/workflows.js
CHANGED
|
@@ -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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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,
|
|
492
|
-
|
|
662
|
+
export function syncWorkflowToVersion(workflowPath, name, agent, versionHome) {
|
|
663
|
+
if (agent === 'goose') {
|
|
664
|
+
return syncWorkflowToGooseRecipe(workflowPath, name, versionHome);
|
|
665
|
+
}
|
|
493
666
|
try {
|
|
494
|
-
|
|
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
|
-
|
|
509
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|