@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.
Files changed (59) hide show
  1. package/README.md +28 -1
  2. package/bin/cli.js +11 -1055
  3. package/bin/hooks/block-file-creation.js +271 -0
  4. package/bin/hooks/product-spec-watcher.js +151 -0
  5. package/lib/index.js +0 -11
  6. package/lib/init.js +2 -21
  7. package/lib/parallel-execution.js +235 -0
  8. package/lib/presets.js +26 -4
  9. package/package.json +4 -7
  10. package/template/.claude/agents/architect.md +2 -2
  11. package/template/.claude/agents/backend.md +17 -30
  12. package/template/.claude/agents/frontend.md +17 -39
  13. package/template/.claude/agents/orchestrator.md +63 -4
  14. package/template/.claude/agents/product-analyzer.md +1 -1
  15. package/template/.claude/agents/tester.md +16 -29
  16. package/template/.claude/commands/agentful-generate.md +221 -14
  17. package/template/.claude/commands/agentful-init.md +621 -0
  18. package/template/.claude/commands/agentful-product.md +1 -1
  19. package/template/.claude/commands/agentful-start.md +99 -1
  20. package/template/.claude/product/EXAMPLES.md +2 -2
  21. package/template/.claude/product/index.md +1 -1
  22. package/template/.claude/settings.json +22 -0
  23. package/template/.claude/skills/research/SKILL.md +432 -0
  24. package/template/CLAUDE.md +5 -6
  25. package/template/bin/hooks/architect-drift-detector.js +242 -0
  26. package/template/bin/hooks/product-spec-watcher.js +151 -0
  27. package/version.json +1 -1
  28. package/bin/hooks/post-agent.js +0 -101
  29. package/bin/hooks/post-feature.js +0 -227
  30. package/bin/hooks/pre-agent.js +0 -118
  31. package/bin/hooks/pre-feature.js +0 -138
  32. package/lib/VALIDATION_README.md +0 -455
  33. package/lib/ci/claude-action-integration.js +0 -641
  34. package/lib/ci/index.js +0 -10
  35. package/lib/core/analyzer.js +0 -497
  36. package/lib/core/cli.js +0 -141
  37. package/lib/core/detectors/conventions.js +0 -342
  38. package/lib/core/detectors/framework.js +0 -276
  39. package/lib/core/detectors/index.js +0 -15
  40. package/lib/core/detectors/language.js +0 -199
  41. package/lib/core/detectors/patterns.js +0 -356
  42. package/lib/core/generator.js +0 -626
  43. package/lib/core/index.js +0 -9
  44. package/lib/core/output-parser.js +0 -458
  45. package/lib/core/storage.js +0 -515
  46. package/lib/core/templates.js +0 -556
  47. package/lib/pipeline/cli.js +0 -423
  48. package/lib/pipeline/engine.js +0 -928
  49. package/lib/pipeline/executor.js +0 -440
  50. package/lib/pipeline/index.js +0 -33
  51. package/lib/pipeline/integrations.js +0 -559
  52. package/lib/pipeline/schemas.js +0 -288
  53. package/lib/remote/client.js +0 -361
  54. package/lib/server/auth.js +0 -270
  55. package/lib/server/client-example.js +0 -190
  56. package/lib/server/executor.js +0 -477
  57. package/lib/server/index.js +0 -494
  58. package/lib/update-helpers.js +0 -505
  59. 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 - Thin wrapper for template initialization and status
4
+ * agentful CLI - Template initialization and status
5
5
  *
6
- * Smart analysis and generation happens in Claude Code using:
7
- * - /agentful-generate command (analyzes codebase & generates agents)
8
- * - /agentful-start command (begins structured development)
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':