@itz4blitz/agentful 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/bin/cli.js +11 -1055
- package/bin/hooks/block-file-creation.js +271 -0
- package/bin/hooks/product-spec-watcher.js +151 -0
- package/lib/index.js +0 -11
- package/lib/init.js +2 -21
- package/lib/parallel-execution.js +235 -0
- package/lib/presets.js +26 -4
- package/package.json +4 -7
- package/template/.claude/agents/architect.md +2 -2
- package/template/.claude/agents/backend.md +17 -30
- package/template/.claude/agents/frontend.md +17 -39
- package/template/.claude/agents/orchestrator.md +63 -4
- package/template/.claude/agents/product-analyzer.md +1 -1
- package/template/.claude/agents/tester.md +16 -29
- package/template/.claude/commands/agentful-generate.md +221 -14
- package/template/.claude/commands/agentful-init.md +621 -0
- package/template/.claude/commands/agentful-product.md +1 -1
- package/template/.claude/commands/agentful-start.md +99 -1
- package/template/.claude/product/EXAMPLES.md +2 -2
- package/template/.claude/product/index.md +1 -1
- package/template/.claude/settings.json +22 -0
- package/template/.claude/skills/research/SKILL.md +432 -0
- package/template/CLAUDE.md +5 -6
- package/template/bin/hooks/architect-drift-detector.js +242 -0
- package/template/bin/hooks/product-spec-watcher.js +151 -0
- package/version.json +1 -1
- package/bin/hooks/post-agent.js +0 -101
- package/bin/hooks/post-feature.js +0 -227
- package/bin/hooks/pre-agent.js +0 -118
- package/bin/hooks/pre-feature.js +0 -138
- package/lib/VALIDATION_README.md +0 -455
- package/lib/ci/claude-action-integration.js +0 -641
- package/lib/ci/index.js +0 -10
- package/lib/core/analyzer.js +0 -497
- package/lib/core/cli.js +0 -141
- package/lib/core/detectors/conventions.js +0 -342
- package/lib/core/detectors/framework.js +0 -276
- package/lib/core/detectors/index.js +0 -15
- package/lib/core/detectors/language.js +0 -199
- package/lib/core/detectors/patterns.js +0 -356
- package/lib/core/generator.js +0 -626
- package/lib/core/index.js +0 -9
- package/lib/core/output-parser.js +0 -458
- package/lib/core/storage.js +0 -515
- package/lib/core/templates.js +0 -556
- package/lib/pipeline/cli.js +0 -423
- package/lib/pipeline/engine.js +0 -928
- package/lib/pipeline/executor.js +0 -440
- package/lib/pipeline/index.js +0 -33
- package/lib/pipeline/integrations.js +0 -559
- package/lib/pipeline/schemas.js +0 -288
- package/lib/remote/client.js +0 -361
- package/lib/server/auth.js +0 -270
- package/lib/server/client-example.js +0 -190
- package/lib/server/executor.js +0 -477
- package/lib/server/index.js +0 -494
- package/lib/update-helpers.js +0 -505
- package/lib/validation.js +0 -460
package/bin/cli.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* agentful CLI -
|
|
4
|
+
* agentful CLI - Template initialization and status
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
6
|
+
* Core functionality:
|
|
7
|
+
* - Install templates (agents, skills, commands, hooks)
|
|
8
|
+
* - Show installation status
|
|
9
|
+
* - Display available presets
|
|
10
|
+
*
|
|
11
|
+
* Development happens in Claude Code using slash commands:
|
|
12
|
+
* - /agentful-generate - Analyze codebase & generate agents
|
|
13
|
+
* - /agentful-start - Begin structured development
|
|
14
|
+
* - /agentful-status - Check progress
|
|
15
|
+
* - /agentful-validate - Run quality gates
|
|
9
16
|
*
|
|
10
17
|
* REQUIREMENTS:
|
|
11
18
|
* - Node.js 22.0.0 or higher (native fetch() support)
|
|
12
|
-
* - This script uses the native fetch() API (no external dependencies)
|
|
13
19
|
*/
|
|
14
20
|
|
|
15
21
|
import fs from 'fs';
|
|
@@ -23,32 +29,6 @@ import {
|
|
|
23
29
|
mergePresetWithFlags,
|
|
24
30
|
validateConfiguration
|
|
25
31
|
} from '../lib/presets.js';
|
|
26
|
-
import pipelineCLI from '../lib/pipeline/cli.js';
|
|
27
|
-
import {
|
|
28
|
-
GitHubActionsAdapter,
|
|
29
|
-
GitLabCIAdapter,
|
|
30
|
-
JenkinsAdapter
|
|
31
|
-
} from '../lib/pipeline/integrations.js';
|
|
32
|
-
import { PipelineEngine } from '../lib/pipeline/engine.js';
|
|
33
|
-
import { AgentExecutor } from '../lib/pipeline/executor.js';
|
|
34
|
-
import {
|
|
35
|
-
buildCIPrompt,
|
|
36
|
-
generateWorkflow,
|
|
37
|
-
writeWorkflowFile,
|
|
38
|
-
listAvailableAgents
|
|
39
|
-
} from '../lib/ci/index.js';
|
|
40
|
-
import { startServerFromCLI } from '../lib/server/index.js';
|
|
41
|
-
import {
|
|
42
|
-
addRemote,
|
|
43
|
-
removeRemote,
|
|
44
|
-
listRemotes,
|
|
45
|
-
executeRemoteAgent,
|
|
46
|
-
getRemoteExecutionStatus,
|
|
47
|
-
listRemoteExecutions,
|
|
48
|
-
listRemoteAgents,
|
|
49
|
-
checkRemoteHealth,
|
|
50
|
-
pollExecution
|
|
51
|
-
} from '../lib/remote/client.js';
|
|
52
32
|
|
|
53
33
|
const __filename = fileURLToPath(import.meta.url);
|
|
54
34
|
const __dirname = path.dirname(__filename);
|
|
@@ -96,12 +76,6 @@ function showHelp() {
|
|
|
96
76
|
console.log(` ${colors.green}init${colors.reset} Install agentful (all components by default)`);
|
|
97
77
|
console.log(` ${colors.green}status${colors.reset} Show agentful status and generated files`);
|
|
98
78
|
console.log(` ${colors.green}presets${colors.reset} Show installation options`);
|
|
99
|
-
console.log(` ${colors.green}deploy${colors.reset} Deploy pipeline to CI/CD platform`);
|
|
100
|
-
console.log(` ${colors.green}trigger${colors.reset} Execute an agent with a task`);
|
|
101
|
-
console.log(` ${colors.green}pipeline${colors.reset} Run and manage pipeline workflows`);
|
|
102
|
-
console.log(` ${colors.green}ci${colors.reset} Generate prompts for claude-code-action`);
|
|
103
|
-
console.log(` ${colors.green}serve${colors.reset} Start remote execution server`);
|
|
104
|
-
console.log(` ${colors.green}remote${colors.reset} Configure and execute agents on remote servers`);
|
|
105
79
|
console.log(` ${colors.green}help${colors.reset} Show this help message`);
|
|
106
80
|
console.log(` ${colors.green}--version${colors.reset} Show version`);
|
|
107
81
|
console.log('');
|
|
@@ -120,33 +94,6 @@ function showHelp() {
|
|
|
120
94
|
console.log(` ${colors.dim}# Minimal setup (for simple scripts/CLIs)${colors.reset}`);
|
|
121
95
|
console.log(` ${colors.bright}agentful init --preset=minimal${colors.reset}`);
|
|
122
96
|
console.log('');
|
|
123
|
-
console.log(` ${colors.dim}# Deploy pipeline to GitHub Actions${colors.reset}`);
|
|
124
|
-
console.log(` ${colors.bright}agentful deploy --to github-actions --pipeline pipeline.yml${colors.reset}`);
|
|
125
|
-
console.log('');
|
|
126
|
-
console.log(` ${colors.dim}# Quick agent execution${colors.reset}`);
|
|
127
|
-
console.log(` ${colors.bright}agentful trigger backend "Implement user authentication"${colors.reset}`);
|
|
128
|
-
console.log('');
|
|
129
|
-
console.log(` ${colors.dim}# Run a pipeline${colors.reset}`);
|
|
130
|
-
console.log(` ${colors.bright}agentful pipeline run --pipeline pipeline.yml${colors.reset}`);
|
|
131
|
-
console.log('');
|
|
132
|
-
console.log(` ${colors.dim}# Generate CI prompt for claude-code-action${colors.reset}`);
|
|
133
|
-
console.log(` ${colors.bright}agentful ci backend "Review API changes"${colors.reset}`);
|
|
134
|
-
console.log('');
|
|
135
|
-
console.log(` ${colors.dim}# Generate workflow file for GitHub Actions${colors.reset}`);
|
|
136
|
-
console.log(` ${colors.bright}agentful ci --generate-workflow --platform github${colors.reset}`);
|
|
137
|
-
console.log('');
|
|
138
|
-
console.log(` ${colors.dim}# Start remote execution server (Tailscale mode)${colors.reset}`);
|
|
139
|
-
console.log(` ${colors.bright}agentful serve${colors.reset}`);
|
|
140
|
-
console.log('');
|
|
141
|
-
console.log(` ${colors.dim}# Start server with HMAC authentication${colors.reset}`);
|
|
142
|
-
console.log(` ${colors.bright}agentful serve --auth=hmac --secret=your-secret-key --https --cert=cert.pem --key=key.pem${colors.reset}`);
|
|
143
|
-
console.log('');
|
|
144
|
-
console.log(` ${colors.dim}# Configure remote server${colors.reset}`);
|
|
145
|
-
console.log(` ${colors.bright}agentful remote add prod http://my-server:3737${colors.reset}`);
|
|
146
|
-
console.log('');
|
|
147
|
-
console.log(` ${colors.dim}# Execute agent on remote${colors.reset}`);
|
|
148
|
-
console.log(` ${colors.bright}agentful remote exec backend "Fix memory leak" --remote=prod${colors.reset}`);
|
|
149
|
-
console.log('');
|
|
150
97
|
console.log('AFTER INIT:');
|
|
151
98
|
console.log(` 1. ${colors.bright}Run claude${colors.reset} to start Claude Code`);
|
|
152
99
|
console.log(` 2. ${colors.bright}Type /agentful-generate${colors.reset} to analyze codebase & generate agents`);
|
|
@@ -519,973 +466,6 @@ function showStatus() {
|
|
|
519
466
|
console.log('');
|
|
520
467
|
}
|
|
521
468
|
|
|
522
|
-
/**
|
|
523
|
-
* Deploy pipeline to CI/CD platform
|
|
524
|
-
*/
|
|
525
|
-
async function deploy(args) {
|
|
526
|
-
const flags = parseFlags(args);
|
|
527
|
-
const platform = flags.to;
|
|
528
|
-
const pipelineFile = flags.pipeline || flags.p;
|
|
529
|
-
|
|
530
|
-
if (!pipelineFile) {
|
|
531
|
-
log(colors.red, 'Error: --pipeline (-p) is required');
|
|
532
|
-
console.log('');
|
|
533
|
-
log(colors.dim, 'Usage: agentful deploy --to <platform> --pipeline <path>');
|
|
534
|
-
log(colors.dim, 'Platforms: github-actions, gitlab, jenkins');
|
|
535
|
-
process.exit(1);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (!platform) {
|
|
539
|
-
log(colors.red, 'Error: --to is required');
|
|
540
|
-
console.log('');
|
|
541
|
-
log(colors.dim, 'Usage: agentful deploy --to <platform> --pipeline <path>');
|
|
542
|
-
log(colors.dim, 'Platforms: github-actions, gitlab, jenkins');
|
|
543
|
-
process.exit(1);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
try {
|
|
547
|
-
// Load pipeline definition
|
|
548
|
-
const pipelinePath = path.resolve(process.cwd(), pipelineFile);
|
|
549
|
-
const engine = new PipelineEngine();
|
|
550
|
-
const pipeline = await engine.loadPipeline(pipelinePath);
|
|
551
|
-
|
|
552
|
-
log(colors.dim, `Deploying pipeline: ${pipeline.name}`);
|
|
553
|
-
log(colors.dim, `Platform: ${platform}`);
|
|
554
|
-
console.log('');
|
|
555
|
-
|
|
556
|
-
let outputPath;
|
|
557
|
-
|
|
558
|
-
switch (platform.toLowerCase()) {
|
|
559
|
-
case 'github-actions':
|
|
560
|
-
case 'github':
|
|
561
|
-
outputPath = flags.output || '.github/workflows/agentful.yml';
|
|
562
|
-
await GitHubActionsAdapter.writeWorkflowFile(pipeline, outputPath);
|
|
563
|
-
log(colors.green, `Deployed to GitHub Actions: ${outputPath}`);
|
|
564
|
-
console.log('');
|
|
565
|
-
log(colors.dim, 'Next steps:');
|
|
566
|
-
log(colors.dim, ' 1. Commit and push the workflow file');
|
|
567
|
-
log(colors.dim, ' 2. Check Actions tab in your GitHub repository');
|
|
568
|
-
break;
|
|
569
|
-
|
|
570
|
-
case 'gitlab':
|
|
571
|
-
case 'gitlab-ci':
|
|
572
|
-
outputPath = flags.output || '.gitlab-ci.yml';
|
|
573
|
-
await GitLabCIAdapter.writeConfigFile(pipeline, outputPath);
|
|
574
|
-
log(colors.green, `Deployed to GitLab CI: ${outputPath}`);
|
|
575
|
-
console.log('');
|
|
576
|
-
log(colors.dim, 'Next steps:');
|
|
577
|
-
log(colors.dim, ' 1. Commit and push the config file');
|
|
578
|
-
log(colors.dim, ' 2. Check CI/CD > Pipelines in your GitLab repository');
|
|
579
|
-
break;
|
|
580
|
-
|
|
581
|
-
case 'jenkins':
|
|
582
|
-
outputPath = flags.output || 'Jenkinsfile';
|
|
583
|
-
await JenkinsAdapter.writeJenkinsfile(pipeline, outputPath);
|
|
584
|
-
log(colors.green, `Deployed to Jenkins: ${outputPath}`);
|
|
585
|
-
console.log('');
|
|
586
|
-
log(colors.dim, 'Next steps:');
|
|
587
|
-
log(colors.dim, ' 1. Commit and push the Jenkinsfile');
|
|
588
|
-
log(colors.dim, ' 2. Create/update Jenkins pipeline job to use this file');
|
|
589
|
-
break;
|
|
590
|
-
|
|
591
|
-
default:
|
|
592
|
-
log(colors.red, `Unknown platform: ${platform}`);
|
|
593
|
-
console.log('');
|
|
594
|
-
log(colors.dim, 'Supported platforms: github-actions, gitlab, jenkins');
|
|
595
|
-
process.exit(1);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
console.log('');
|
|
599
|
-
} catch (error) {
|
|
600
|
-
log(colors.red, `Deployment failed: ${error.message}`);
|
|
601
|
-
if (flags.verbose) {
|
|
602
|
-
console.error(error.stack);
|
|
603
|
-
}
|
|
604
|
-
process.exit(1);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* Trigger a quick agent execution
|
|
610
|
-
*/
|
|
611
|
-
async function trigger(args) {
|
|
612
|
-
const agentName = args[0];
|
|
613
|
-
const task = args.slice(1).join(' ');
|
|
614
|
-
|
|
615
|
-
if (!agentName) {
|
|
616
|
-
log(colors.red, 'Error: Agent name is required');
|
|
617
|
-
console.log('');
|
|
618
|
-
log(colors.dim, 'Usage: agentful trigger <agent-name> "<task>"');
|
|
619
|
-
log(colors.dim, 'Example: agentful trigger backend "Implement user authentication"');
|
|
620
|
-
process.exit(1);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
if (!task) {
|
|
624
|
-
log(colors.red, 'Error: Task description is required');
|
|
625
|
-
console.log('');
|
|
626
|
-
log(colors.dim, 'Usage: agentful trigger <agent-name> "<task>"');
|
|
627
|
-
log(colors.dim, 'Example: agentful trigger backend "Implement user authentication"');
|
|
628
|
-
process.exit(1);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
console.log('');
|
|
632
|
-
log(colors.cyan, `Triggering agent: ${agentName}`);
|
|
633
|
-
log(colors.dim, `Task: ${task}`);
|
|
634
|
-
console.log('');
|
|
635
|
-
|
|
636
|
-
try {
|
|
637
|
-
const executor = new AgentExecutor({
|
|
638
|
-
agentsDir: '.claude/agents',
|
|
639
|
-
streamLogs: true
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
const result = await executor.execute(
|
|
643
|
-
{
|
|
644
|
-
id: 'triggered-task',
|
|
645
|
-
agent: agentName,
|
|
646
|
-
task
|
|
647
|
-
},
|
|
648
|
-
{},
|
|
649
|
-
{
|
|
650
|
-
timeout: 1800000, // 30 minutes
|
|
651
|
-
onProgress: (progress) => {
|
|
652
|
-
log(colors.dim, `Progress: ${progress}%`);
|
|
653
|
-
},
|
|
654
|
-
onLog: (message) => {
|
|
655
|
-
console.log(message);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
);
|
|
659
|
-
|
|
660
|
-
console.log('');
|
|
661
|
-
if (result.success) {
|
|
662
|
-
log(colors.green, 'Agent execution completed successfully!');
|
|
663
|
-
if (result.output) {
|
|
664
|
-
console.log('');
|
|
665
|
-
log(colors.bright, 'Output:');
|
|
666
|
-
console.log(JSON.stringify(result.output, null, 2));
|
|
667
|
-
}
|
|
668
|
-
} else {
|
|
669
|
-
log(colors.red, 'Agent execution failed');
|
|
670
|
-
if (result.error) {
|
|
671
|
-
console.log('');
|
|
672
|
-
log(colors.red, `Error: ${result.error}`);
|
|
673
|
-
}
|
|
674
|
-
process.exit(1);
|
|
675
|
-
}
|
|
676
|
-
} catch (error) {
|
|
677
|
-
console.log('');
|
|
678
|
-
log(colors.red, `Execution failed: ${error.message}`);
|
|
679
|
-
console.error(error.stack);
|
|
680
|
-
process.exit(1);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Pipeline command dispatcher
|
|
686
|
-
*/
|
|
687
|
-
async function pipeline(args) {
|
|
688
|
-
const subcommand = args[0];
|
|
689
|
-
const subArgs = args.slice(1);
|
|
690
|
-
|
|
691
|
-
if (!subcommand) {
|
|
692
|
-
// Show pipeline help
|
|
693
|
-
pipelineCLI.commands.help();
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// Parse args for pipeline CLI
|
|
698
|
-
const parsedArgs = pipelineCLI.parseArgs(subArgs);
|
|
699
|
-
|
|
700
|
-
// Route to pipeline CLI command
|
|
701
|
-
if (pipelineCLI.commands[subcommand]) {
|
|
702
|
-
await pipelineCLI.commands[subcommand](parsedArgs);
|
|
703
|
-
} else {
|
|
704
|
-
log(colors.red, `Unknown pipeline command: ${subcommand}`);
|
|
705
|
-
console.log('');
|
|
706
|
-
pipelineCLI.commands.help();
|
|
707
|
-
process.exit(1);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Remote command - configure and execute agents on remote servers
|
|
713
|
-
* @param {string[]} args - Command arguments
|
|
714
|
-
*/
|
|
715
|
-
async function remote(args) {
|
|
716
|
-
const subcommand = args[0];
|
|
717
|
-
const flags = parseFlags(args);
|
|
718
|
-
|
|
719
|
-
switch (subcommand) {
|
|
720
|
-
case 'add': {
|
|
721
|
-
const name = args[1];
|
|
722
|
-
const url = args[2];
|
|
723
|
-
|
|
724
|
-
if (!name || !url) {
|
|
725
|
-
log(colors.red, 'Usage: agentful remote add <name> <url> [--auth=<mode>] [--secret=<key>]');
|
|
726
|
-
console.log('');
|
|
727
|
-
log(colors.dim, 'Examples:');
|
|
728
|
-
log(colors.dim, ' agentful remote add prod http://server:3737');
|
|
729
|
-
log(colors.dim, ' agentful remote add prod https://server:3737 --auth=hmac --secret=xxx');
|
|
730
|
-
process.exit(1);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
try {
|
|
734
|
-
addRemote(name, url, {
|
|
735
|
-
auth: flags.auth || 'tailscale',
|
|
736
|
-
secret: flags.secret,
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
log(colors.green, `✓ Remote "${name}" added`);
|
|
740
|
-
console.log('');
|
|
741
|
-
log(colors.dim, `URL: ${url}`);
|
|
742
|
-
log(colors.dim, `Auth: ${flags.auth || 'tailscale'}`);
|
|
743
|
-
console.log('');
|
|
744
|
-
log(colors.dim, 'Test connection:');
|
|
745
|
-
log(colors.dim, ` agentful remote health ${name}`);
|
|
746
|
-
} catch (error) {
|
|
747
|
-
log(colors.red, `Failed to add remote: ${error.message}`);
|
|
748
|
-
process.exit(1);
|
|
749
|
-
}
|
|
750
|
-
break;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
case 'remove':
|
|
754
|
-
case 'rm': {
|
|
755
|
-
const name = args[1];
|
|
756
|
-
|
|
757
|
-
if (!name) {
|
|
758
|
-
log(colors.red, 'Usage: agentful remote remove <name>');
|
|
759
|
-
process.exit(1);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
try {
|
|
763
|
-
removeRemote(name);
|
|
764
|
-
log(colors.green, `✓ Remote "${name}" removed`);
|
|
765
|
-
} catch (error) {
|
|
766
|
-
log(colors.red, `Failed to remove remote: ${error.message}`);
|
|
767
|
-
process.exit(1);
|
|
768
|
-
}
|
|
769
|
-
break;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
case 'list':
|
|
773
|
-
case 'ls': {
|
|
774
|
-
const remotes = listRemotes();
|
|
775
|
-
const names = Object.keys(remotes);
|
|
776
|
-
|
|
777
|
-
if (names.length === 0) {
|
|
778
|
-
log(colors.dim, 'No remotes configured');
|
|
779
|
-
console.log('');
|
|
780
|
-
log(colors.dim, 'Add a remote:');
|
|
781
|
-
log(colors.dim, ' agentful remote add prod http://server:3737');
|
|
782
|
-
process.exit(0);
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
console.log('');
|
|
786
|
-
log(colors.bright, 'Configured Remotes:');
|
|
787
|
-
console.log('');
|
|
788
|
-
|
|
789
|
-
for (const name of names) {
|
|
790
|
-
const config = remotes[name];
|
|
791
|
-
log(colors.cyan, `${name}`);
|
|
792
|
-
log(colors.dim, ` URL: ${config.url}`);
|
|
793
|
-
log(colors.dim, ` Auth: ${config.auth}`);
|
|
794
|
-
console.log('');
|
|
795
|
-
}
|
|
796
|
-
break;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
case 'exec':
|
|
800
|
-
case 'execute': {
|
|
801
|
-
const agent = args[1];
|
|
802
|
-
const task = args[2];
|
|
803
|
-
const remoteName = flags.remote || 'default';
|
|
804
|
-
|
|
805
|
-
if (!agent || !task) {
|
|
806
|
-
log(colors.red, 'Usage: agentful remote exec <agent> <task> [--remote=<name>] [--follow]');
|
|
807
|
-
console.log('');
|
|
808
|
-
log(colors.dim, 'Examples:');
|
|
809
|
-
log(colors.dim, ' agentful remote exec backend "Fix memory leak"');
|
|
810
|
-
log(colors.dim, ' agentful remote exec reviewer "Review PR #123" --remote=prod --follow');
|
|
811
|
-
process.exit(1);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
try {
|
|
815
|
-
log(colors.dim, `Triggering ${agent} on ${remoteName}...`);
|
|
816
|
-
|
|
817
|
-
const result = await executeRemoteAgent(remoteName, agent, task, {
|
|
818
|
-
timeout: flags.timeout ? parseInt(flags.timeout) : undefined,
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
console.log('');
|
|
822
|
-
log(colors.green, '✓ Execution started');
|
|
823
|
-
log(colors.dim, `ID: ${result.executionId}`);
|
|
824
|
-
console.log('');
|
|
825
|
-
|
|
826
|
-
if (flags.follow) {
|
|
827
|
-
log(colors.dim, 'Polling for completion...');
|
|
828
|
-
console.log('');
|
|
829
|
-
|
|
830
|
-
await pollExecution(remoteName, result.executionId, {
|
|
831
|
-
interval: flags.interval ? parseInt(flags.interval) : 5000,
|
|
832
|
-
onUpdate: (status) => {
|
|
833
|
-
log(colors.dim, `[${status.state}] ${status.agent}: ${Math.floor((Date.now() - status.startTime) / 1000)}s`);
|
|
834
|
-
},
|
|
835
|
-
});
|
|
836
|
-
|
|
837
|
-
const final = await getRemoteExecutionStatus(remoteName, result.executionId);
|
|
838
|
-
|
|
839
|
-
console.log('');
|
|
840
|
-
if (final.state === 'completed') {
|
|
841
|
-
log(colors.green, '✓ Execution completed');
|
|
842
|
-
console.log('');
|
|
843
|
-
log(colors.bright, 'Output:');
|
|
844
|
-
console.log(final.output);
|
|
845
|
-
} else {
|
|
846
|
-
log(colors.red, '✗ Execution failed');
|
|
847
|
-
console.log('');
|
|
848
|
-
log(colors.bright, 'Error:');
|
|
849
|
-
console.log(final.error || 'Unknown error');
|
|
850
|
-
process.exit(1);
|
|
851
|
-
}
|
|
852
|
-
} else {
|
|
853
|
-
log(colors.dim, 'Check status:');
|
|
854
|
-
log(colors.dim, ` agentful remote status ${result.executionId} --remote=${remoteName}`);
|
|
855
|
-
}
|
|
856
|
-
} catch (error) {
|
|
857
|
-
log(colors.red, `Execution failed: ${error.message}`);
|
|
858
|
-
process.exit(1);
|
|
859
|
-
}
|
|
860
|
-
break;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
case 'status': {
|
|
864
|
-
const executionId = args[1];
|
|
865
|
-
const remoteName = flags.remote || 'default';
|
|
866
|
-
|
|
867
|
-
if (!executionId) {
|
|
868
|
-
log(colors.red, 'Usage: agentful remote status <execution-id> [--remote=<name>]');
|
|
869
|
-
process.exit(1);
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
try {
|
|
873
|
-
const status = await getRemoteExecutionStatus(remoteName, executionId);
|
|
874
|
-
|
|
875
|
-
console.log('');
|
|
876
|
-
log(colors.bright, 'Execution Status:');
|
|
877
|
-
console.log('');
|
|
878
|
-
log(colors.dim, `ID: ${status.id}`);
|
|
879
|
-
log(colors.dim, `Agent: ${status.agent}`);
|
|
880
|
-
log(colors.dim, `Task: ${status.task}`);
|
|
881
|
-
log(colors.dim, `State: ${status.state}`);
|
|
882
|
-
log(colors.dim, `Duration: ${Math.floor(status.duration / 1000)}s`);
|
|
883
|
-
|
|
884
|
-
if (status.exitCode !== null) {
|
|
885
|
-
log(colors.dim, `Exit: ${status.exitCode}`);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
if (status.output) {
|
|
889
|
-
console.log('');
|
|
890
|
-
log(colors.bright, 'Output:');
|
|
891
|
-
console.log(status.output);
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
if (status.error) {
|
|
895
|
-
console.log('');
|
|
896
|
-
log(colors.red, 'Error:');
|
|
897
|
-
console.log(status.error);
|
|
898
|
-
}
|
|
899
|
-
} catch (error) {
|
|
900
|
-
log(colors.red, `Failed to get status: ${error.message}`);
|
|
901
|
-
process.exit(1);
|
|
902
|
-
}
|
|
903
|
-
break;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
case 'agents': {
|
|
907
|
-
const remoteName = flags.remote || 'default';
|
|
908
|
-
|
|
909
|
-
try {
|
|
910
|
-
const result = await listRemoteAgents(remoteName);
|
|
911
|
-
|
|
912
|
-
console.log('');
|
|
913
|
-
log(colors.bright, `Available Agents on ${remoteName}:`);
|
|
914
|
-
console.log('');
|
|
915
|
-
|
|
916
|
-
for (const agent of result.agents) {
|
|
917
|
-
log(colors.cyan, ` ${agent}`);
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
console.log('');
|
|
921
|
-
log(colors.dim, `Total: ${result.count} agents`);
|
|
922
|
-
} catch (error) {
|
|
923
|
-
log(colors.red, `Failed to list agents: ${error.message}`);
|
|
924
|
-
process.exit(1);
|
|
925
|
-
}
|
|
926
|
-
break;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
case 'executions': {
|
|
930
|
-
const remoteName = flags.remote || 'default';
|
|
931
|
-
|
|
932
|
-
try {
|
|
933
|
-
const result = await listRemoteExecutions(remoteName, {
|
|
934
|
-
agent: flags.agent,
|
|
935
|
-
state: flags.state,
|
|
936
|
-
limit: flags.limit ? parseInt(flags.limit) : undefined,
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
console.log('');
|
|
940
|
-
log(colors.bright, `Recent Executions on ${remoteName}:`);
|
|
941
|
-
console.log('');
|
|
942
|
-
|
|
943
|
-
if (result.executions.length === 0) {
|
|
944
|
-
log(colors.dim, 'No executions found');
|
|
945
|
-
process.exit(0);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
for (const exec of result.executions) {
|
|
949
|
-
const duration = Math.floor(exec.duration / 1000);
|
|
950
|
-
log(colors.cyan, `${exec.id.substring(0, 8)} ${exec.agent}`);
|
|
951
|
-
log(colors.dim, ` State: ${exec.state} (${duration}s)`);
|
|
952
|
-
log(colors.dim, ` Task: ${exec.task.substring(0, 60)}${exec.task.length > 60 ? '...' : ''}`);
|
|
953
|
-
console.log('');
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
log(colors.dim, `Total: ${result.count} executions`);
|
|
957
|
-
} catch (error) {
|
|
958
|
-
log(colors.red, `Failed to list executions: ${error.message}`);
|
|
959
|
-
process.exit(1);
|
|
960
|
-
}
|
|
961
|
-
break;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
case 'health': {
|
|
965
|
-
const remoteName = args[1] || flags.remote || 'default';
|
|
966
|
-
|
|
967
|
-
try {
|
|
968
|
-
const health = await checkRemoteHealth(remoteName);
|
|
969
|
-
|
|
970
|
-
console.log('');
|
|
971
|
-
log(colors.green, `✓ ${remoteName} is healthy`);
|
|
972
|
-
console.log('');
|
|
973
|
-
log(colors.dim, `Status: ${health.status}`);
|
|
974
|
-
log(colors.dim, `Uptime: ${Math.floor(health.uptime / 3600)}h`);
|
|
975
|
-
log(colors.dim, `Mode: ${health.mode}`);
|
|
976
|
-
} catch (error) {
|
|
977
|
-
log(colors.red, `Health check failed: ${error.message}`);
|
|
978
|
-
process.exit(1);
|
|
979
|
-
}
|
|
980
|
-
break;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
default:
|
|
984
|
-
log(colors.red, `Unknown subcommand: ${subcommand}`);
|
|
985
|
-
console.log('');
|
|
986
|
-
log(colors.dim, 'Available subcommands:');
|
|
987
|
-
log(colors.dim, ' add - Add a remote server');
|
|
988
|
-
log(colors.dim, ' remove - Remove a remote server');
|
|
989
|
-
log(colors.dim, ' list - List configured remotes');
|
|
990
|
-
log(colors.dim, ' exec - Execute an agent on a remote');
|
|
991
|
-
log(colors.dim, ' status - Check execution status');
|
|
992
|
-
log(colors.dim, ' agents - List available agents');
|
|
993
|
-
log(colors.dim, ' executions - List recent executions');
|
|
994
|
-
log(colors.dim, ' health - Check server health');
|
|
995
|
-
process.exit(1);
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* Get PID file path
|
|
1001
|
-
* @returns {string} Path to PID file
|
|
1002
|
-
*/
|
|
1003
|
-
function getPidFilePath() {
|
|
1004
|
-
return path.join(process.cwd(), '.agentful', 'server.pid');
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
/**
|
|
1008
|
-
* Start server in daemon mode
|
|
1009
|
-
* @param {string[]} args - Original args
|
|
1010
|
-
* @param {Object} config - Server configuration
|
|
1011
|
-
*/
|
|
1012
|
-
async function startDaemon(args, config) {
|
|
1013
|
-
const { spawn } = await import('child_process');
|
|
1014
|
-
|
|
1015
|
-
// Check if daemon is already running
|
|
1016
|
-
const pidFile = getPidFilePath();
|
|
1017
|
-
if (fs.existsSync(pidFile)) {
|
|
1018
|
-
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1019
|
-
|
|
1020
|
-
// Check if process is still running
|
|
1021
|
-
try {
|
|
1022
|
-
process.kill(pid, 0); // Signal 0 checks if process exists
|
|
1023
|
-
log(colors.yellow, 'Server is already running');
|
|
1024
|
-
log(colors.dim, `PID: ${pid}`);
|
|
1025
|
-
console.log('');
|
|
1026
|
-
log(colors.dim, 'To stop: agentful serve --stop');
|
|
1027
|
-
log(colors.dim, 'To check status: agentful serve --status');
|
|
1028
|
-
process.exit(1);
|
|
1029
|
-
} catch (error) {
|
|
1030
|
-
// Process doesn't exist, clean up stale PID file
|
|
1031
|
-
fs.unlinkSync(pidFile);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// Ensure .agentful directory exists
|
|
1036
|
-
const agentfulDir = path.join(process.cwd(), '.agentful');
|
|
1037
|
-
if (!fs.existsSync(agentfulDir)) {
|
|
1038
|
-
fs.mkdirSync(agentfulDir, { recursive: true });
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
// Prepare args for child process (remove --daemon flag)
|
|
1042
|
-
const childArgs = args.filter(arg => !arg.startsWith('--daemon') && arg !== '-d');
|
|
1043
|
-
|
|
1044
|
-
// Create log files for daemon output
|
|
1045
|
-
const logFile = path.join(agentfulDir, 'server.log');
|
|
1046
|
-
const errLogFile = path.join(agentfulDir, 'server.err.log');
|
|
1047
|
-
const out = fs.openSync(logFile, 'a');
|
|
1048
|
-
const err = fs.openSync(errLogFile, 'a');
|
|
1049
|
-
|
|
1050
|
-
// Spawn detached child process
|
|
1051
|
-
const child = spawn(
|
|
1052
|
-
process.argv[0], // node executable
|
|
1053
|
-
[process.argv[1], 'serve', ...childArgs], // script path and args
|
|
1054
|
-
{
|
|
1055
|
-
detached: true,
|
|
1056
|
-
stdio: ['ignore', out, err],
|
|
1057
|
-
cwd: process.cwd(),
|
|
1058
|
-
env: {
|
|
1059
|
-
...process.env,
|
|
1060
|
-
AGENTFUL_DAEMON: '1' // Flag to indicate we're running as daemon
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
);
|
|
1064
|
-
|
|
1065
|
-
// Unref immediately to allow parent to exit independently
|
|
1066
|
-
child.unref();
|
|
1067
|
-
|
|
1068
|
-
// Wait for server to start (check health endpoint)
|
|
1069
|
-
const maxAttempts = 10;
|
|
1070
|
-
let started = false;
|
|
1071
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
1072
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1073
|
-
try {
|
|
1074
|
-
const response = await fetch(`http://localhost:${config.port}/health`);
|
|
1075
|
-
if (response.ok) {
|
|
1076
|
-
started = true;
|
|
1077
|
-
break;
|
|
1078
|
-
}
|
|
1079
|
-
} catch {
|
|
1080
|
-
// Server not ready yet
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
if (!started) {
|
|
1085
|
-
log(colors.yellow, 'Warning: Server may not have started successfully');
|
|
1086
|
-
log(colors.dim, `Check logs: ${logFile}`);
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// Write PID file after confirming server started
|
|
1090
|
-
fs.writeFileSync(pidFile, child.pid.toString(), 'utf-8');
|
|
1091
|
-
|
|
1092
|
-
// Show success message
|
|
1093
|
-
log(colors.green, `Server started in background (PID: ${child.pid})`);
|
|
1094
|
-
console.log('');
|
|
1095
|
-
log(colors.dim, `PID file: ${pidFile}`);
|
|
1096
|
-
log(colors.dim, `Port: ${config.port}`);
|
|
1097
|
-
log(colors.dim, `Auth: ${config.auth}`);
|
|
1098
|
-
log(colors.dim, `Logs: ${logFile}`);
|
|
1099
|
-
console.log('');
|
|
1100
|
-
log(colors.dim, 'Commands:');
|
|
1101
|
-
log(colors.dim, ' agentful serve --stop Stop the daemon');
|
|
1102
|
-
log(colors.dim, ' agentful serve --status Check daemon status');
|
|
1103
|
-
console.log('');
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Stop daemon server
|
|
1108
|
-
*/
|
|
1109
|
-
async function stopDaemon() {
|
|
1110
|
-
const pidFile = getPidFilePath();
|
|
1111
|
-
|
|
1112
|
-
if (!fs.existsSync(pidFile)) {
|
|
1113
|
-
log(colors.yellow, 'No daemon server running');
|
|
1114
|
-
log(colors.dim, 'PID file not found');
|
|
1115
|
-
process.exit(1);
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1119
|
-
|
|
1120
|
-
// Try to kill the process
|
|
1121
|
-
try {
|
|
1122
|
-
process.kill(pid, 'SIGTERM');
|
|
1123
|
-
|
|
1124
|
-
// Wait a moment for graceful shutdown
|
|
1125
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1126
|
-
|
|
1127
|
-
// Check if process is still running
|
|
1128
|
-
try {
|
|
1129
|
-
process.kill(pid, 0);
|
|
1130
|
-
// Still running, force kill
|
|
1131
|
-
log(colors.yellow, 'Graceful shutdown failed, forcing...');
|
|
1132
|
-
process.kill(pid, 'SIGKILL');
|
|
1133
|
-
} catch {
|
|
1134
|
-
// Process stopped successfully
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
// Remove PID file
|
|
1138
|
-
fs.unlinkSync(pidFile);
|
|
1139
|
-
|
|
1140
|
-
log(colors.green, `Server stopped (PID: ${pid})`);
|
|
1141
|
-
} catch (error) {
|
|
1142
|
-
if (error.code === 'ESRCH') {
|
|
1143
|
-
// Process doesn't exist
|
|
1144
|
-
log(colors.yellow, 'Server process not found (stale PID file)');
|
|
1145
|
-
fs.unlinkSync(pidFile);
|
|
1146
|
-
} else if (error.code === 'EPERM') {
|
|
1147
|
-
log(colors.red, `Permission denied to kill process ${pid}`);
|
|
1148
|
-
log(colors.dim, 'Try: sudo agentful serve --stop');
|
|
1149
|
-
process.exit(1);
|
|
1150
|
-
} else {
|
|
1151
|
-
log(colors.red, `Failed to stop server: ${error.message}`);
|
|
1152
|
-
process.exit(1);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
/**
|
|
1158
|
-
* Check daemon server status
|
|
1159
|
-
*/
|
|
1160
|
-
async function checkDaemonStatus() {
|
|
1161
|
-
const pidFile = getPidFilePath();
|
|
1162
|
-
|
|
1163
|
-
if (!fs.existsSync(pidFile)) {
|
|
1164
|
-
log(colors.yellow, 'No daemon server running');
|
|
1165
|
-
log(colors.dim, 'PID file not found');
|
|
1166
|
-
console.log('');
|
|
1167
|
-
log(colors.dim, 'Start daemon: agentful serve --daemon');
|
|
1168
|
-
process.exit(1);
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
1172
|
-
|
|
1173
|
-
// Check if process is running
|
|
1174
|
-
try {
|
|
1175
|
-
process.kill(pid, 0); // Signal 0 just checks if process exists
|
|
1176
|
-
|
|
1177
|
-
log(colors.green, 'Server is running');
|
|
1178
|
-
console.log('');
|
|
1179
|
-
log(colors.dim, `PID: ${pid}`);
|
|
1180
|
-
log(colors.dim, `PID file: ${pidFile}`);
|
|
1181
|
-
|
|
1182
|
-
// Try to get more info from /proc (Linux/macOS)
|
|
1183
|
-
try {
|
|
1184
|
-
const { execSync } = await import('child_process');
|
|
1185
|
-
const psOutput = execSync(`ps -p ${pid} -o comm,etime,rss`, { encoding: 'utf-8' });
|
|
1186
|
-
const lines = psOutput.trim().split('\n');
|
|
1187
|
-
if (lines.length > 1) {
|
|
1188
|
-
const [cmd, etime, rss] = lines[1].trim().split(/\s+/);
|
|
1189
|
-
console.log('');
|
|
1190
|
-
log(colors.dim, `Uptime: ${etime}`);
|
|
1191
|
-
log(colors.dim, `Memory: ${Math.round(parseInt(rss) / 1024)} MB`);
|
|
1192
|
-
}
|
|
1193
|
-
} catch {
|
|
1194
|
-
// ps command failed, skip detailed info
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
console.log('');
|
|
1198
|
-
log(colors.dim, 'Commands:');
|
|
1199
|
-
log(colors.dim, ' agentful serve --stop Stop the daemon');
|
|
1200
|
-
} catch (error) {
|
|
1201
|
-
if (error.code === 'ESRCH') {
|
|
1202
|
-
log(colors.yellow, 'Server not running (stale PID file)');
|
|
1203
|
-
log(colors.dim, `PID file exists but process ${pid} not found`);
|
|
1204
|
-
console.log('');
|
|
1205
|
-
log(colors.dim, 'Clean up: rm .agentful/server.pid');
|
|
1206
|
-
process.exit(1);
|
|
1207
|
-
} else {
|
|
1208
|
-
log(colors.red, `Failed to check status: ${error.message}`);
|
|
1209
|
-
process.exit(1);
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
/**
|
|
1215
|
-
* Serve command - Start remote execution server
|
|
1216
|
-
*/
|
|
1217
|
-
async function serve(args) {
|
|
1218
|
-
const flags = parseFlags(args);
|
|
1219
|
-
|
|
1220
|
-
// Handle --stop subcommand
|
|
1221
|
-
if (flags.stop) {
|
|
1222
|
-
return await stopDaemon();
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// Handle --status subcommand
|
|
1226
|
-
if (flags.status) {
|
|
1227
|
-
return await checkDaemonStatus();
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
// Handle --help flag first
|
|
1231
|
-
if (flags.help || flags.h) {
|
|
1232
|
-
showBanner();
|
|
1233
|
-
log(colors.bright, 'Agentful Remote Execution Server');
|
|
1234
|
-
console.log('');
|
|
1235
|
-
log(colors.dim, 'Start a secure HTTP server for remote agent execution.');
|
|
1236
|
-
console.log('');
|
|
1237
|
-
log(colors.bright, 'USAGE:');
|
|
1238
|
-
console.log(` ${colors.green}agentful serve${colors.reset} ${colors.dim}[options]${colors.reset}`);
|
|
1239
|
-
console.log('');
|
|
1240
|
-
log(colors.bright, 'AUTHENTICATION MODES:');
|
|
1241
|
-
console.log(` ${colors.cyan}--auth=tailscale${colors.reset} ${colors.dim}(default) Tailscale network only${colors.reset}`);
|
|
1242
|
-
console.log(` ${colors.cyan}--auth=hmac${colors.reset} ${colors.dim}HMAC signature authentication (requires --secret)${colors.reset}`);
|
|
1243
|
-
console.log(` ${colors.cyan}--auth=none${colors.reset} ${colors.dim}No authentication (binds to all interfaces, use with SSH tunnel)${colors.reset}`);
|
|
1244
|
-
console.log('');
|
|
1245
|
-
log(colors.bright, 'OPTIONS:');
|
|
1246
|
-
console.log(` ${colors.yellow}--port=<number>${colors.reset} ${colors.dim}Server port (default: 3000)${colors.reset}`);
|
|
1247
|
-
console.log(` ${colors.yellow}--secret=<key>${colors.reset} ${colors.dim}HMAC secret key (required for --auth=hmac)${colors.reset}`);
|
|
1248
|
-
console.log(` ${colors.yellow}--https${colors.reset} ${colors.dim}Enable HTTPS (requires --cert and --key)${colors.reset}`);
|
|
1249
|
-
console.log(` ${colors.yellow}--cert=<path>${colors.reset} ${colors.dim}SSL certificate file path${colors.reset}`);
|
|
1250
|
-
console.log(` ${colors.yellow}--key=<path>${colors.reset} ${colors.dim}SSL private key file path${colors.reset}`);
|
|
1251
|
-
console.log(` ${colors.yellow}--daemon, -d${colors.reset} ${colors.dim}Run server in background (daemon mode)${colors.reset}`);
|
|
1252
|
-
console.log(` ${colors.yellow}--stop${colors.reset} ${colors.dim}Stop background server${colors.reset}`);
|
|
1253
|
-
console.log(` ${colors.yellow}--status${colors.reset} ${colors.dim}Check background server status${colors.reset}`);
|
|
1254
|
-
console.log(` ${colors.yellow}--help, -h${colors.reset} ${colors.dim}Show this help message${colors.reset}`);
|
|
1255
|
-
console.log('');
|
|
1256
|
-
log(colors.bright, 'EXAMPLES:');
|
|
1257
|
-
console.log('');
|
|
1258
|
-
log(colors.dim, ' # Start server with Tailscale auth (default)');
|
|
1259
|
-
console.log(` ${colors.green}agentful serve${colors.reset}`);
|
|
1260
|
-
console.log('');
|
|
1261
|
-
log(colors.dim, ' # Start server with HMAC authentication');
|
|
1262
|
-
console.log(` ${colors.green}agentful serve --auth=hmac --secret=your-secret-key${colors.reset}`);
|
|
1263
|
-
console.log('');
|
|
1264
|
-
log(colors.dim, ' # Start HTTPS server with HMAC auth');
|
|
1265
|
-
console.log(` ${colors.green}agentful serve --auth=hmac --secret=key --https --cert=cert.pem --key=key.pem${colors.reset}`);
|
|
1266
|
-
console.log('');
|
|
1267
|
-
log(colors.dim, ' # Start server without auth (public access, use SSH tunnel)');
|
|
1268
|
-
console.log(` ${colors.green}agentful serve --auth=none --port=3737${colors.reset}`);
|
|
1269
|
-
console.log('');
|
|
1270
|
-
log(colors.dim, ' # Start server in background (daemon mode)');
|
|
1271
|
-
console.log(` ${colors.green}agentful serve --daemon${colors.reset}`);
|
|
1272
|
-
console.log('');
|
|
1273
|
-
log(colors.dim, ' # Check daemon status');
|
|
1274
|
-
console.log(` ${colors.green}agentful serve --status${colors.reset}`);
|
|
1275
|
-
console.log('');
|
|
1276
|
-
log(colors.dim, ' # Stop daemon');
|
|
1277
|
-
console.log(` ${colors.green}agentful serve --stop${colors.reset}`);
|
|
1278
|
-
console.log('');
|
|
1279
|
-
log(colors.dim, ' # Generate HMAC secret');
|
|
1280
|
-
console.log(` ${colors.green}openssl rand -hex 32${colors.reset}`);
|
|
1281
|
-
console.log('');
|
|
1282
|
-
log(colors.dim, ' # Generate self-signed certificate');
|
|
1283
|
-
console.log(` ${colors.green}openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes${colors.reset}`);
|
|
1284
|
-
console.log('');
|
|
1285
|
-
log(colors.bright, 'SECURITY NOTES:');
|
|
1286
|
-
console.log(` ${colors.yellow}Tailscale mode:${colors.reset} Binds to 0.0.0.0, relies on Tailscale network isolation`);
|
|
1287
|
-
console.log(` ${colors.yellow}HMAC mode:${colors.reset} Binds to 0.0.0.0, uses cryptographic signatures (recommended for public networks)`);
|
|
1288
|
-
console.log(` ${colors.yellow}None mode:${colors.reset} Binds to 0.0.0.0, no authentication (use SSH tunnel: ssh -L 3000:localhost:3000 user@host)`);
|
|
1289
|
-
console.log('');
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// Parse configuration
|
|
1294
|
-
const config = {
|
|
1295
|
-
auth: flags.auth || 'tailscale',
|
|
1296
|
-
port: parseInt(flags.port || '3000', 10),
|
|
1297
|
-
secret: flags.secret,
|
|
1298
|
-
https: flags.https || false,
|
|
1299
|
-
cert: flags.cert,
|
|
1300
|
-
key: flags.key,
|
|
1301
|
-
projectRoot: process.cwd(),
|
|
1302
|
-
};
|
|
1303
|
-
|
|
1304
|
-
// Check if --daemon flag is set
|
|
1305
|
-
const isDaemon = flags.daemon || flags.d;
|
|
1306
|
-
|
|
1307
|
-
// Validate auth mode
|
|
1308
|
-
const validAuthModes = ['tailscale', 'hmac', 'none'];
|
|
1309
|
-
if (!validAuthModes.includes(config.auth)) {
|
|
1310
|
-
log(colors.red, `Invalid auth mode: ${config.auth}`);
|
|
1311
|
-
console.log('');
|
|
1312
|
-
log(colors.dim, 'Valid modes: tailscale, hmac, none');
|
|
1313
|
-
console.log('');
|
|
1314
|
-
log(colors.dim, 'Usage examples:');
|
|
1315
|
-
log(colors.dim, ' agentful serve # Tailscale mode (default)');
|
|
1316
|
-
log(colors.dim, ' agentful serve --auth=hmac --secret=key --https # HMAC with HTTPS');
|
|
1317
|
-
log(colors.dim, ' agentful serve --auth=none # Localhost only');
|
|
1318
|
-
process.exit(1);
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
// If daemon mode, fork the process
|
|
1322
|
-
if (isDaemon) {
|
|
1323
|
-
return await startDaemon(args, config);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
// Show configuration (skip banner if running as daemon child)
|
|
1327
|
-
if (!process.env.AGENTFUL_DAEMON) {
|
|
1328
|
-
showBanner();
|
|
1329
|
-
}
|
|
1330
|
-
log(colors.bright, 'Starting Agentful Server');
|
|
1331
|
-
console.log('');
|
|
1332
|
-
log(colors.dim, `Authentication: ${config.auth}`);
|
|
1333
|
-
log(colors.dim, `Port: ${config.port}`);
|
|
1334
|
-
log(colors.dim, `HTTPS: ${config.https ? 'enabled' : 'disabled'}`);
|
|
1335
|
-
|
|
1336
|
-
if (config.auth === 'none') {
|
|
1337
|
-
log(colors.yellow, 'Warning: Server running with no authentication (binds to all interfaces)');
|
|
1338
|
-
log(colors.dim, 'Recommended: Use SSH tunnel for remote access: ssh -L 3000:localhost:3000 user@host');
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
if (config.auth === 'hmac' && !config.secret) {
|
|
1342
|
-
console.log('');
|
|
1343
|
-
log(colors.red, 'Error: --secret is required for HMAC mode');
|
|
1344
|
-
console.log('');
|
|
1345
|
-
log(colors.dim, 'Generate a secret:');
|
|
1346
|
-
log(colors.dim, ' openssl rand -hex 32');
|
|
1347
|
-
process.exit(1);
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
if (config.https && (!config.cert || !config.key)) {
|
|
1351
|
-
console.log('');
|
|
1352
|
-
log(colors.red, 'Error: --cert and --key are required for HTTPS mode');
|
|
1353
|
-
console.log('');
|
|
1354
|
-
log(colors.dim, 'Generate self-signed certificate:');
|
|
1355
|
-
log(colors.dim, ' openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes');
|
|
1356
|
-
process.exit(1);
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
console.log('');
|
|
1360
|
-
|
|
1361
|
-
try {
|
|
1362
|
-
await startServerFromCLI(config);
|
|
1363
|
-
} catch (error) {
|
|
1364
|
-
console.log('');
|
|
1365
|
-
log(colors.red, `Failed to start server: ${error.message}`);
|
|
1366
|
-
if (flags.verbose) {
|
|
1367
|
-
console.error(error.stack);
|
|
1368
|
-
}
|
|
1369
|
-
process.exit(1);
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
/**
|
|
1374
|
-
* CI command - Generate prompts for claude-code-action
|
|
1375
|
-
*/
|
|
1376
|
-
async function ci(args) {
|
|
1377
|
-
const flags = parseFlags(args);
|
|
1378
|
-
|
|
1379
|
-
// Generate workflow file
|
|
1380
|
-
if (flags['generate-workflow']) {
|
|
1381
|
-
const platform = flags.platform || 'github';
|
|
1382
|
-
const agents = flags.agents ? parseArrayFlag(flags.agents) : ['backend', 'frontend', 'reviewer'];
|
|
1383
|
-
const triggers = flags.triggers ? parseArrayFlag(flags.triggers) : ['pull_request'];
|
|
1384
|
-
|
|
1385
|
-
try {
|
|
1386
|
-
log(colors.dim, `Generating ${platform} workflow...`);
|
|
1387
|
-
const workflow = await generateWorkflow({
|
|
1388
|
-
platform,
|
|
1389
|
-
agents,
|
|
1390
|
-
triggers,
|
|
1391
|
-
options: {
|
|
1392
|
-
nodeVersion: flags['node-version'] || '22.x',
|
|
1393
|
-
runsOn: flags['runs-on'] || 'ubuntu-latest',
|
|
1394
|
-
branches: flags.branches ? parseArrayFlag(flags.branches) : ['main', 'develop'],
|
|
1395
|
-
},
|
|
1396
|
-
});
|
|
1397
|
-
|
|
1398
|
-
const outputPath = await writeWorkflowFile(workflow, platform);
|
|
1399
|
-
|
|
1400
|
-
console.log('');
|
|
1401
|
-
log(colors.green, `Workflow generated: ${outputPath}`);
|
|
1402
|
-
console.log('');
|
|
1403
|
-
log(colors.dim, 'Next steps:');
|
|
1404
|
-
log(colors.dim, ' 1. Review the generated workflow file');
|
|
1405
|
-
log(colors.dim, ' 2. Add ANTHROPIC_API_KEY to your CI secrets');
|
|
1406
|
-
log(colors.dim, ' 3. Commit and push to trigger the workflow');
|
|
1407
|
-
console.log('');
|
|
1408
|
-
} catch (error) {
|
|
1409
|
-
log(colors.red, `Failed to generate workflow: ${error.message}`);
|
|
1410
|
-
if (flags.verbose) {
|
|
1411
|
-
console.error(error.stack);
|
|
1412
|
-
}
|
|
1413
|
-
process.exit(1);
|
|
1414
|
-
}
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
// List available agents
|
|
1419
|
-
if (flags.list || flags.l) {
|
|
1420
|
-
try {
|
|
1421
|
-
const agents = await listAvailableAgents();
|
|
1422
|
-
|
|
1423
|
-
console.log('');
|
|
1424
|
-
log(colors.bright, 'Available Agents:');
|
|
1425
|
-
console.log('');
|
|
1426
|
-
|
|
1427
|
-
if (agents.length === 0) {
|
|
1428
|
-
log(colors.yellow, 'No agents found. Run "agentful init" first.');
|
|
1429
|
-
} else {
|
|
1430
|
-
agents.forEach(agent => {
|
|
1431
|
-
log(colors.green, ` ${agent}`);
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
console.log('');
|
|
1436
|
-
} catch (error) {
|
|
1437
|
-
log(colors.red, `Failed to list agents: ${error.message}`);
|
|
1438
|
-
process.exit(1);
|
|
1439
|
-
}
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
// Generate prompt for agent
|
|
1444
|
-
const agentName = args.find(arg => !arg.startsWith('--'));
|
|
1445
|
-
const taskStartIndex = args.findIndex(arg => !arg.startsWith('--') && arg !== agentName);
|
|
1446
|
-
const task = taskStartIndex >= 0 ? args.slice(taskStartIndex).join(' ') : '';
|
|
1447
|
-
|
|
1448
|
-
if (!agentName) {
|
|
1449
|
-
log(colors.red, 'Error: Agent name is required');
|
|
1450
|
-
console.log('');
|
|
1451
|
-
log(colors.dim, 'Usage: agentful ci <agent-name> "<task>"');
|
|
1452
|
-
log(colors.dim, 'Example: agentful ci backend "Review API changes"');
|
|
1453
|
-
console.log('');
|
|
1454
|
-
log(colors.dim, 'Options:');
|
|
1455
|
-
log(colors.dim, ' --list List available agents');
|
|
1456
|
-
log(colors.dim, ' --generate-workflow Generate CI workflow file');
|
|
1457
|
-
log(colors.dim, ' --platform=<name> CI platform (github, gitlab, jenkins)');
|
|
1458
|
-
log(colors.dim, ' --agents=<list> Agents to include in workflow');
|
|
1459
|
-
log(colors.dim, ' --no-ci-context Exclude CI metadata from prompt');
|
|
1460
|
-
console.log('');
|
|
1461
|
-
process.exit(1);
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
if (!task) {
|
|
1465
|
-
log(colors.red, 'Error: Task description is required');
|
|
1466
|
-
console.log('');
|
|
1467
|
-
log(colors.dim, 'Usage: agentful ci <agent-name> "<task>"');
|
|
1468
|
-
log(colors.dim, 'Example: agentful ci backend "Review API changes"');
|
|
1469
|
-
process.exit(1);
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
try {
|
|
1473
|
-
const prompt = await buildCIPrompt(agentName, task, {
|
|
1474
|
-
includeCIContext: !flags['no-ci-context'],
|
|
1475
|
-
context: flags.context ? JSON.parse(flags.context) : {},
|
|
1476
|
-
});
|
|
1477
|
-
|
|
1478
|
-
// Output the prompt (can be piped to claude-code-action)
|
|
1479
|
-
console.log(prompt);
|
|
1480
|
-
} catch (error) {
|
|
1481
|
-
log(colors.red, `Failed to build prompt: ${error.message}`);
|
|
1482
|
-
if (flags.verbose) {
|
|
1483
|
-
console.error(error.stack);
|
|
1484
|
-
}
|
|
1485
|
-
process.exit(1);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
469
|
// Main CLI
|
|
1490
470
|
async function main() {
|
|
1491
471
|
// Check Node.js version for native fetch() support
|
|
@@ -1515,30 +495,6 @@ async function main() {
|
|
|
1515
495
|
showPresets();
|
|
1516
496
|
break;
|
|
1517
497
|
|
|
1518
|
-
case 'deploy':
|
|
1519
|
-
await deploy(args.slice(1));
|
|
1520
|
-
break;
|
|
1521
|
-
|
|
1522
|
-
case 'trigger':
|
|
1523
|
-
await trigger(args.slice(1));
|
|
1524
|
-
break;
|
|
1525
|
-
|
|
1526
|
-
case 'pipeline':
|
|
1527
|
-
await pipeline(args.slice(1));
|
|
1528
|
-
break;
|
|
1529
|
-
|
|
1530
|
-
case 'ci':
|
|
1531
|
-
await ci(args.slice(1));
|
|
1532
|
-
break;
|
|
1533
|
-
|
|
1534
|
-
case 'serve':
|
|
1535
|
-
await serve(args.slice(1));
|
|
1536
|
-
break;
|
|
1537
|
-
|
|
1538
|
-
case 'remote':
|
|
1539
|
-
await remote(args.slice(1));
|
|
1540
|
-
break;
|
|
1541
|
-
|
|
1542
498
|
case 'help':
|
|
1543
499
|
case '--help':
|
|
1544
500
|
case '-h':
|