@supaku/agentfactory-cli 0.3.0 → 0.4.1

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 (34) hide show
  1. package/dist/src/analyze-logs.d.ts +2 -2
  2. package/dist/src/analyze-logs.js +23 -194
  3. package/dist/src/cleanup.d.ts +2 -6
  4. package/dist/src/cleanup.d.ts.map +1 -1
  5. package/dist/src/cleanup.js +24 -225
  6. package/dist/src/lib/analyze-logs-runner.d.ts +47 -0
  7. package/dist/src/lib/analyze-logs-runner.d.ts.map +1 -0
  8. package/dist/src/lib/analyze-logs-runner.js +216 -0
  9. package/dist/src/lib/cleanup-runner.d.ts +28 -0
  10. package/dist/src/lib/cleanup-runner.d.ts.map +1 -0
  11. package/dist/src/lib/cleanup-runner.js +224 -0
  12. package/dist/src/lib/orchestrator-runner.d.ts +45 -0
  13. package/dist/src/lib/orchestrator-runner.d.ts.map +1 -0
  14. package/dist/src/lib/orchestrator-runner.js +144 -0
  15. package/dist/src/lib/queue-admin-runner.d.ts +30 -0
  16. package/dist/src/lib/queue-admin-runner.d.ts.map +1 -0
  17. package/dist/src/lib/queue-admin-runner.js +378 -0
  18. package/dist/src/lib/worker-fleet-runner.d.ts +28 -0
  19. package/dist/src/lib/worker-fleet-runner.d.ts.map +1 -0
  20. package/dist/src/lib/worker-fleet-runner.js +224 -0
  21. package/dist/src/lib/worker-runner.d.ts +31 -0
  22. package/dist/src/lib/worker-runner.d.ts.map +1 -0
  23. package/dist/src/lib/worker-runner.js +735 -0
  24. package/dist/src/orchestrator.d.ts +1 -1
  25. package/dist/src/orchestrator.js +42 -106
  26. package/dist/src/queue-admin.d.ts +3 -2
  27. package/dist/src/queue-admin.d.ts.map +1 -1
  28. package/dist/src/queue-admin.js +38 -360
  29. package/dist/src/worker-fleet.d.ts +1 -1
  30. package/dist/src/worker-fleet.js +23 -162
  31. package/dist/src/worker.d.ts +1 -0
  32. package/dist/src/worker.d.ts.map +1 -1
  33. package/dist/src/worker.js +33 -702
  34. package/package.json +28 -4
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Log Analyzer Runner -- Programmatic API for the log analyzer CLI.
3
+ *
4
+ * Extracts the core logic from the analyze-logs bin script so that it can be
5
+ * invoked programmatically (e.g. from a Next.js route handler or test) without
6
+ * process.exit / dotenv / argv coupling.
7
+ */
8
+ import { execSync } from 'child_process';
9
+ import { createLogAnalyzer } from '@supaku/agentfactory';
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+ const DEFAULT_POLL_INTERVAL = 5000;
14
+ /** Detect the git repository root. Falls back to cwd. */
15
+ export function getGitRoot() {
16
+ try {
17
+ return execSync('git rev-parse --show-toplevel', {
18
+ encoding: 'utf-8',
19
+ stdio: ['pipe', 'pipe', 'pipe'],
20
+ }).trim();
21
+ }
22
+ catch {
23
+ return process.cwd();
24
+ }
25
+ }
26
+ function formatTime() {
27
+ return new Date().toLocaleTimeString();
28
+ }
29
+ function emptyStats() {
30
+ return {
31
+ sessionsAnalyzed: 0,
32
+ totalErrors: 0,
33
+ totalPatterns: 0,
34
+ issuesCreated: 0,
35
+ issuesUpdated: 0,
36
+ };
37
+ }
38
+ async function analyzeAndPrintSession(analyzer, sessionId, options, stats) {
39
+ console.log(`\nAnalyzing session: ${sessionId}`);
40
+ console.log('-'.repeat(50));
41
+ const result = analyzer.analyzeSession(sessionId);
42
+ if (!result) {
43
+ console.log(' [SKIP] Session not found or incomplete');
44
+ return false;
45
+ }
46
+ console.log(` Issue: ${result.metadata.issueIdentifier}`);
47
+ console.log(` Work Type: ${result.metadata.workType}`);
48
+ console.log(` Status: ${result.metadata.status}`);
49
+ console.log(` Events: ${result.eventsAnalyzed}`);
50
+ console.log(` Errors: ${result.errorsFound}`);
51
+ console.log(` Patterns: ${result.patterns.length}`);
52
+ stats.sessionsAnalyzed++;
53
+ stats.totalErrors += result.errorsFound;
54
+ stats.totalPatterns += result.patterns.length;
55
+ if (options.verbose && result.patterns.length > 0) {
56
+ console.log('\n Detected Patterns:');
57
+ for (const pattern of result.patterns) {
58
+ console.log(` - [${pattern.severity}] ${pattern.title}`);
59
+ console.log(` Type: ${pattern.type}, Occurrences: ${pattern.occurrences}`);
60
+ if (pattern.tool) {
61
+ console.log(` Tool: ${pattern.tool}`);
62
+ }
63
+ }
64
+ }
65
+ if (result.suggestedIssues.length > 0) {
66
+ console.log(`\n Suggested Issues: ${result.suggestedIssues.length}`);
67
+ if (options.verbose) {
68
+ for (const issue of result.suggestedIssues) {
69
+ console.log(` - ${issue.title}`);
70
+ console.log(` Signature: ${issue.signature}`);
71
+ console.log(` Labels: ${issue.labels.join(', ')}`);
72
+ }
73
+ }
74
+ try {
75
+ const issueResults = await analyzer.createIssues(result.suggestedIssues, sessionId, options.dryRun);
76
+ for (const issueResult of issueResults) {
77
+ if (issueResult.created) {
78
+ console.log(` [${options.dryRun ? 'WOULD CREATE' : 'CREATED'}] ${issueResult.identifier}`);
79
+ stats.issuesCreated++;
80
+ }
81
+ else {
82
+ console.log(` [${options.dryRun ? 'WOULD UPDATE' : 'UPDATED'}] ${issueResult.identifier}`);
83
+ stats.issuesUpdated++;
84
+ }
85
+ }
86
+ }
87
+ catch (error) {
88
+ console.log(` [ERROR] Failed to create issues: ${error instanceof Error ? error.message : String(error)}`);
89
+ }
90
+ }
91
+ if (!options.dryRun) {
92
+ analyzer.markProcessed(sessionId, result);
93
+ console.log(' [PROCESSED]');
94
+ }
95
+ return true;
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // Summary printer
99
+ // ---------------------------------------------------------------------------
100
+ export function printSummary(stats, dryRun) {
101
+ console.log('\n' + '='.repeat(50));
102
+ console.log('=== Summary ===\n');
103
+ console.log(` Sessions analyzed: ${stats.sessionsAnalyzed}`);
104
+ console.log(` Total errors found: ${stats.totalErrors}`);
105
+ console.log(` Total patterns detected: ${stats.totalPatterns}`);
106
+ console.log(` Issues created: ${stats.issuesCreated}${dryRun ? ' (dry run)' : ''}`);
107
+ console.log(` Issues updated: ${stats.issuesUpdated}${dryRun ? ' (dry run)' : ''}`);
108
+ console.log('');
109
+ }
110
+ // ---------------------------------------------------------------------------
111
+ // Follow mode (watch)
112
+ // ---------------------------------------------------------------------------
113
+ async function runFollowMode(analyzer, options, interval, signal) {
114
+ const stats = emptyStats();
115
+ const processedInSession = new Set();
116
+ console.log(`[${formatTime()}] Watching for new sessions (poll interval: ${interval}ms)`);
117
+ console.log(`[${formatTime()}] Press Ctrl+C to stop\n`);
118
+ const isAborted = () => signal?.aborted ?? false;
119
+ // Initial check for existing unprocessed sessions
120
+ const initialSessions = analyzer.getUnprocessedSessions();
121
+ if (initialSessions.length > 0) {
122
+ console.log(`[${formatTime()}] Found ${initialSessions.length} existing unprocessed session(s)`);
123
+ for (const sid of initialSessions) {
124
+ if (isAborted())
125
+ break;
126
+ await analyzeAndPrintSession(analyzer, sid, options, stats);
127
+ processedInSession.add(sid);
128
+ }
129
+ }
130
+ // Poll loop
131
+ while (!isAborted()) {
132
+ await new Promise((resolve) => {
133
+ const timer = setTimeout(resolve, interval);
134
+ // If an abort signal fires while waiting, resolve immediately
135
+ if (signal) {
136
+ const onAbort = () => {
137
+ clearTimeout(timer);
138
+ resolve();
139
+ };
140
+ signal.addEventListener('abort', onAbort, { once: true });
141
+ }
142
+ });
143
+ if (isAborted())
144
+ break;
145
+ const sessions = analyzer.getUnprocessedSessions();
146
+ const newSessions = sessions.filter((s) => !processedInSession.has(s));
147
+ if (newSessions.length > 0) {
148
+ console.log(`[${formatTime()}] Found ${newSessions.length} new session(s) ready for analysis`);
149
+ for (const sid of newSessions) {
150
+ if (isAborted())
151
+ break;
152
+ const analyzed = await analyzeAndPrintSession(analyzer, sid, options, stats);
153
+ if (analyzed) {
154
+ processedInSession.add(sid);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ console.log(`\n[${formatTime()}] Stopping...`);
160
+ return stats;
161
+ }
162
+ // ---------------------------------------------------------------------------
163
+ // Public entry point
164
+ // ---------------------------------------------------------------------------
165
+ /**
166
+ * Run the log analyzer programmatically.
167
+ *
168
+ * For one-shot mode (default) the returned promise resolves with the analysis
169
+ * result once all sessions have been processed.
170
+ *
171
+ * For follow mode (`config.follow = true`) the analyzer keeps running until
172
+ * the optional `signal` is aborted. When the signal fires the current poll
173
+ * cycle finishes and the accumulated stats are returned.
174
+ */
175
+ export async function runLogAnalyzer(config = {}, signal) {
176
+ const gitRoot = config.gitRoot ?? getGitRoot();
177
+ const logsDir = config.logsDir ?? process.env.AGENT_LOGS_DIR ?? `${gitRoot}/.agent-logs`;
178
+ const dryRun = config.dryRun ?? false;
179
+ const verbose = config.verbose ?? false;
180
+ const interval = config.interval ?? DEFAULT_POLL_INTERVAL;
181
+ console.log('\n=== AgentFactory Log Analyzer ===\n');
182
+ if (dryRun) {
183
+ console.log('[DRY RUN MODE - No issues will be created]\n');
184
+ }
185
+ const analyzer = createLogAnalyzer({ logsDir });
186
+ // Cleanup mode
187
+ if (config.cleanup) {
188
+ console.log('Cleaning up old logs...\n');
189
+ const deleted = analyzer.cleanupOldLogs();
190
+ console.log(`Deleted ${deleted} old log entries.\n`);
191
+ return emptyStats();
192
+ }
193
+ // Follow (watch) mode
194
+ if (config.follow) {
195
+ return runFollowMode(analyzer, { dryRun, verbose }, interval, signal);
196
+ }
197
+ // Standard one-shot mode
198
+ const stats = emptyStats();
199
+ let sessionsToAnalyze;
200
+ if (config.sessionId) {
201
+ sessionsToAnalyze = [config.sessionId];
202
+ console.log(`Analyzing session: ${config.sessionId}\n`);
203
+ }
204
+ else {
205
+ sessionsToAnalyze = analyzer.getUnprocessedSessions();
206
+ console.log(`Found ${sessionsToAnalyze.length} unprocessed session(s)\n`);
207
+ }
208
+ if (sessionsToAnalyze.length === 0) {
209
+ console.log('No sessions to analyze.\n');
210
+ return stats;
211
+ }
212
+ for (const sid of sessionsToAnalyze) {
213
+ await analyzeAndPrintSession(analyzer, sid, { dryRun, verbose }, stats);
214
+ }
215
+ return stats;
216
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Cleanup Runner -- Programmatic API for the worktree cleanup CLI.
3
+ *
4
+ * Exports `runCleanup()` so worktree cleanup can be invoked from code
5
+ * without going through process.argv / process.env / process.exit.
6
+ */
7
+ export interface CleanupRunnerConfig {
8
+ /** Show what would be cleaned up without removing (default: false) */
9
+ dryRun?: boolean;
10
+ /** Force removal even if worktree appears active (default: false) */
11
+ force?: boolean;
12
+ /** Custom worktrees directory (default: {gitRoot}/.worktrees) */
13
+ worktreePath?: string;
14
+ /** Git root for default worktree path (default: auto-detect) */
15
+ gitRoot?: string;
16
+ }
17
+ export interface CleanupResult {
18
+ scanned: number;
19
+ orphaned: number;
20
+ cleaned: number;
21
+ errors: Array<{
22
+ path: string;
23
+ error: string;
24
+ }>;
25
+ }
26
+ export declare function getGitRoot(): string;
27
+ export declare function runCleanup(config?: CleanupRunnerConfig): CleanupResult;
28
+ //# sourceMappingURL=cleanup-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/cleanup-runner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/C;AAuBD,wBAAgB,UAAU,IAAI,MAAM,CASnC;AAuND,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,aAAa,CAUtE"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Cleanup Runner -- Programmatic API for the worktree cleanup CLI.
3
+ *
4
+ * Exports `runCleanup()` so worktree cleanup can be invoked from code
5
+ * without going through process.argv / process.env / process.exit.
6
+ */
7
+ import { execSync } from 'child_process';
8
+ import { existsSync, readdirSync, statSync } from 'fs';
9
+ import { resolve, basename } from 'path';
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+ export function getGitRoot() {
14
+ try {
15
+ return execSync('git rev-parse --show-toplevel', {
16
+ encoding: 'utf-8',
17
+ stdio: ['pipe', 'pipe', 'pipe'],
18
+ }).trim();
19
+ }
20
+ catch {
21
+ return process.cwd();
22
+ }
23
+ }
24
+ /**
25
+ * Get list of git worktrees from 'git worktree list'
26
+ */
27
+ function getGitWorktrees() {
28
+ const worktrees = new Map();
29
+ try {
30
+ const output = execSync('git worktree list --porcelain', {
31
+ encoding: 'utf-8',
32
+ stdio: ['pipe', 'pipe', 'pipe'],
33
+ });
34
+ let currentPath = '';
35
+ for (const line of output.split('\n')) {
36
+ if (line.startsWith('worktree ')) {
37
+ currentPath = line.substring(9);
38
+ }
39
+ else if (line.startsWith('branch ')) {
40
+ const branch = line.substring(7).replace('refs/heads/', '');
41
+ worktrees.set(currentPath, branch);
42
+ }
43
+ }
44
+ }
45
+ catch (error) {
46
+ console.error('Failed to list git worktrees:', error);
47
+ }
48
+ return worktrees;
49
+ }
50
+ /**
51
+ * Check if a git branch exists
52
+ */
53
+ function branchExists(branchName) {
54
+ try {
55
+ execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, {
56
+ stdio: ['pipe', 'pipe', 'pipe'],
57
+ });
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Scan the worktrees directory and identify orphaned worktrees
66
+ */
67
+ function scanWorktrees(options) {
68
+ const worktreesDir = resolve(options.worktreePath);
69
+ const result = [];
70
+ if (!existsSync(worktreesDir)) {
71
+ console.log(`Worktrees directory not found: ${worktreesDir}`);
72
+ return result;
73
+ }
74
+ const gitWorktrees = getGitWorktrees();
75
+ const entries = readdirSync(worktreesDir);
76
+ for (const entry of entries) {
77
+ const entryPath = resolve(worktreesDir, entry);
78
+ try {
79
+ if (!statSync(entryPath).isDirectory()) {
80
+ continue;
81
+ }
82
+ }
83
+ catch {
84
+ continue;
85
+ }
86
+ const info = {
87
+ path: entryPath,
88
+ branch: entry,
89
+ isOrphaned: false,
90
+ };
91
+ const isKnownWorktree = gitWorktrees.has(entryPath);
92
+ const branchName = isKnownWorktree ? gitWorktrees.get(entryPath) : entry;
93
+ if (options.force) {
94
+ info.isOrphaned = true;
95
+ info.reason = 'force cleanup requested';
96
+ }
97
+ else if (!isKnownWorktree) {
98
+ info.isOrphaned = true;
99
+ info.reason = 'not registered with git worktree';
100
+ }
101
+ else if (!branchExists(branchName)) {
102
+ info.isOrphaned = true;
103
+ info.reason = `branch '${branchName}' no longer exists`;
104
+ }
105
+ result.push(info);
106
+ }
107
+ return result;
108
+ }
109
+ /**
110
+ * Remove a single worktree
111
+ */
112
+ function removeWorktree(worktreePath) {
113
+ try {
114
+ execSync(`git worktree remove "${worktreePath}" --force`, {
115
+ encoding: 'utf-8',
116
+ stdio: ['pipe', 'pipe', 'pipe'],
117
+ });
118
+ return { success: true };
119
+ }
120
+ catch {
121
+ try {
122
+ execSync(`rm -rf "${worktreePath}"`, {
123
+ encoding: 'utf-8',
124
+ stdio: ['pipe', 'pipe', 'pipe'],
125
+ });
126
+ execSync('git worktree prune', {
127
+ encoding: 'utf-8',
128
+ stdio: ['pipe', 'pipe', 'pipe'],
129
+ });
130
+ return { success: true };
131
+ }
132
+ catch (rmError) {
133
+ return {
134
+ success: false,
135
+ error: rmError instanceof Error ? rmError.message : String(rmError),
136
+ };
137
+ }
138
+ }
139
+ }
140
+ /**
141
+ * Core cleanup logic
142
+ */
143
+ function cleanup(options) {
144
+ const result = {
145
+ scanned: 0,
146
+ orphaned: 0,
147
+ cleaned: 0,
148
+ errors: [],
149
+ };
150
+ console.log('Scanning worktrees...\n');
151
+ try {
152
+ execSync('git worktree prune', {
153
+ encoding: 'utf-8',
154
+ stdio: ['pipe', 'pipe', 'pipe'],
155
+ });
156
+ }
157
+ catch {
158
+ console.log('Note: Could not prune git worktree metadata');
159
+ }
160
+ const worktrees = scanWorktrees(options);
161
+ result.scanned = worktrees.length;
162
+ if (worktrees.length === 0) {
163
+ console.log('No worktrees found.\n');
164
+ return result;
165
+ }
166
+ console.log(`Found ${worktrees.length} worktree(s) in ${options.worktreePath}/\n`);
167
+ for (const wt of worktrees) {
168
+ const status = wt.isOrphaned ? ' orphaned' : ' active';
169
+ const reason = wt.reason ? ` (${wt.reason})` : '';
170
+ console.log(` ${status}: ${basename(wt.path)}${reason}`);
171
+ }
172
+ console.log('');
173
+ const orphaned = worktrees.filter((wt) => wt.isOrphaned);
174
+ result.orphaned = orphaned.length;
175
+ if (orphaned.length === 0) {
176
+ console.log('No orphaned worktrees to clean up.\n');
177
+ return result;
178
+ }
179
+ if (options.dryRun) {
180
+ console.log(`[DRY RUN] Would clean up ${orphaned.length} orphaned worktree(s):\n`);
181
+ for (const wt of orphaned) {
182
+ console.log(` Would remove: ${wt.path}`);
183
+ }
184
+ console.log('');
185
+ return result;
186
+ }
187
+ console.log(`Cleaning up ${orphaned.length} orphaned worktree(s)...\n`);
188
+ for (const wt of orphaned) {
189
+ process.stdout.write(` Removing ${basename(wt.path)}... `);
190
+ const removal = removeWorktree(wt.path);
191
+ if (removal.success) {
192
+ console.log('done');
193
+ result.cleaned++;
194
+ }
195
+ else {
196
+ console.log(`FAILED: ${removal.error}`);
197
+ result.errors.push({ path: wt.path, error: removal.error || 'Unknown error' });
198
+ }
199
+ }
200
+ console.log('');
201
+ try {
202
+ execSync('git worktree prune', {
203
+ encoding: 'utf-8',
204
+ stdio: ['pipe', 'pipe', 'pipe'],
205
+ });
206
+ console.log('Pruned git worktree metadata.\n');
207
+ }
208
+ catch {
209
+ // Ignore prune errors
210
+ }
211
+ return result;
212
+ }
213
+ // ---------------------------------------------------------------------------
214
+ // Runner
215
+ // ---------------------------------------------------------------------------
216
+ export function runCleanup(config) {
217
+ const gitRoot = config?.gitRoot ?? getGitRoot();
218
+ const options = {
219
+ dryRun: config?.dryRun ?? false,
220
+ force: config?.force ?? false,
221
+ worktreePath: config?.worktreePath ?? resolve(gitRoot, '.worktrees'),
222
+ };
223
+ return cleanup(options);
224
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Orchestrator Runner -- Programmatic API for the orchestrator CLI.
3
+ *
4
+ * Exports `runOrchestrator()` so the orchestrator can be invoked from code
5
+ * (e.g. Next.js route handlers, tests, or custom scripts) without going
6
+ * through process.argv / process.env / process.exit.
7
+ */
8
+ import { type AgentProcess, type OrchestratorIssue } from '@supaku/agentfactory';
9
+ export interface OrchestratorRunnerConfig {
10
+ /** Linear API key for authentication */
11
+ linearApiKey: string;
12
+ /** Filter issues by project name */
13
+ project?: string;
14
+ /** Maximum concurrent agents (default: 3) */
15
+ max?: number;
16
+ /** Process a single issue by ID */
17
+ single?: string;
18
+ /** Wait for agents to complete (default: true) */
19
+ wait?: boolean;
20
+ /** Show what would be done without executing (default: false) */
21
+ dryRun?: boolean;
22
+ /** Git repository root (default: auto-detect) */
23
+ gitRoot?: string;
24
+ /** Callbacks for agent lifecycle events */
25
+ callbacks?: OrchestratorCallbacks;
26
+ }
27
+ export interface OrchestratorCallbacks {
28
+ onIssueSelected?: (issue: OrchestratorIssue) => void;
29
+ onAgentStart?: (agent: AgentProcess) => void;
30
+ onAgentComplete?: (agent: AgentProcess) => void;
31
+ onAgentError?: (agent: AgentProcess, error: Error) => void;
32
+ onAgentIncomplete?: (agent: AgentProcess) => void;
33
+ }
34
+ export interface OrchestratorRunnerResult {
35
+ agentsSpawned: number;
36
+ errors: Array<{
37
+ issueId: string;
38
+ error: Error;
39
+ }>;
40
+ completed: AgentProcess[];
41
+ }
42
+ export declare function getGitRoot(): string;
43
+ export declare function formatDuration(ms: number): string;
44
+ export declare function runOrchestrator(config: OrchestratorRunnerConfig): Promise<OrchestratorRunnerResult>;
45
+ //# sourceMappingURL=orchestrator-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/orchestrator-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAA;AAM7B,MAAM,WAAW,wBAAwB;IACvC,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,qBAAqB,CAAA;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACpD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IAC5C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IAC/C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAC1D,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;CAClD;AAED,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;IAChD,SAAS,EAAE,YAAY,EAAE,CAAA;CAC1B;AAMD,wBAAgB,UAAU,IAAI,MAAM,CASnC;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAYjD;AA0CD,wBAAsB,eAAe,CACnC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CAqFnC"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Orchestrator Runner -- Programmatic API for the orchestrator CLI.
3
+ *
4
+ * Exports `runOrchestrator()` so the orchestrator can be invoked from code
5
+ * (e.g. Next.js route handlers, tests, or custom scripts) without going
6
+ * through process.argv / process.env / process.exit.
7
+ */
8
+ import path from 'path';
9
+ import { execSync } from 'child_process';
10
+ import { createOrchestrator, } from '@supaku/agentfactory';
11
+ // ---------------------------------------------------------------------------
12
+ // Helpers
13
+ // ---------------------------------------------------------------------------
14
+ export function getGitRoot() {
15
+ try {
16
+ return execSync('git rev-parse --show-toplevel', {
17
+ encoding: 'utf-8',
18
+ stdio: ['pipe', 'pipe', 'pipe'],
19
+ }).trim();
20
+ }
21
+ catch {
22
+ return process.cwd();
23
+ }
24
+ }
25
+ export function formatDuration(ms) {
26
+ const seconds = Math.floor(ms / 1000);
27
+ const minutes = Math.floor(seconds / 60);
28
+ const hours = Math.floor(minutes / 60);
29
+ if (hours > 0) {
30
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
31
+ }
32
+ if (minutes > 0) {
33
+ return `${minutes}m ${seconds % 60}s`;
34
+ }
35
+ return `${seconds}s`;
36
+ }
37
+ // ---------------------------------------------------------------------------
38
+ // Default callbacks (console.log-based, matching the original CLI output)
39
+ // ---------------------------------------------------------------------------
40
+ function defaultCallbacks() {
41
+ return {
42
+ onIssueSelected: (issue) => {
43
+ console.log(`Selected: ${issue.identifier} - ${issue.title}`);
44
+ console.log(` URL: ${issue.url}`);
45
+ console.log(` Labels: ${issue.labels.join(', ') || 'none'}`);
46
+ },
47
+ onAgentStart: (agent) => {
48
+ console.log(`Agent started: ${agent.identifier} (PID: ${agent.pid})`);
49
+ console.log(` Worktree: ${agent.worktreePath}`);
50
+ },
51
+ onAgentComplete: (agent) => {
52
+ const duration = agent.completedAt
53
+ ? formatDuration(agent.completedAt.getTime() - agent.startedAt.getTime())
54
+ : 'unknown';
55
+ console.log(`Agent completed: ${agent.identifier} (${duration})`);
56
+ },
57
+ onAgentError: (_agent, error) => {
58
+ console.error(`Agent failed: ${_agent.identifier}`);
59
+ console.error(` Error: ${error.message}`);
60
+ },
61
+ onAgentIncomplete: (agent) => {
62
+ const duration = agent.completedAt
63
+ ? formatDuration(agent.completedAt.getTime() - agent.startedAt.getTime())
64
+ : 'unknown';
65
+ console.warn(`Agent incomplete: ${agent.identifier} (${duration})`);
66
+ console.warn(` Reason: ${agent.incompleteReason ?? 'unknown'}`);
67
+ console.warn(` Worktree preserved: ${agent.worktreePath}`);
68
+ },
69
+ };
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // Runner
73
+ // ---------------------------------------------------------------------------
74
+ export async function runOrchestrator(config) {
75
+ const maxConcurrent = config.max ?? 3;
76
+ const wait = config.wait ?? true;
77
+ const dryRun = config.dryRun ?? false;
78
+ const gitRoot = config.gitRoot ?? getGitRoot();
79
+ const cb = config.callbacks ?? defaultCallbacks();
80
+ const orchestrator = createOrchestrator({
81
+ project: config.project,
82
+ maxConcurrent,
83
+ worktreePath: path.resolve(gitRoot, '.worktrees'),
84
+ linearApiKey: config.linearApiKey,
85
+ }, {
86
+ onIssueSelected: cb.onIssueSelected,
87
+ onAgentStart: cb.onAgentStart,
88
+ onAgentComplete: cb.onAgentComplete,
89
+ onAgentError: cb.onAgentError,
90
+ onAgentIncomplete: cb.onAgentIncomplete,
91
+ });
92
+ const result = {
93
+ agentsSpawned: 0,
94
+ errors: [],
95
+ completed: [],
96
+ };
97
+ // --single mode ----------------------------------------------------------
98
+ if (config.single) {
99
+ if (dryRun) {
100
+ return result;
101
+ }
102
+ await orchestrator.spawnAgentForIssue(config.single);
103
+ result.agentsSpawned = 1;
104
+ if (wait) {
105
+ // Wire up SIGINT so callers running from a terminal can stop agents
106
+ const sigintHandler = () => {
107
+ orchestrator.stopAll();
108
+ };
109
+ process.on('SIGINT', sigintHandler);
110
+ try {
111
+ const completed = await orchestrator.waitForAll();
112
+ result.completed = completed;
113
+ }
114
+ finally {
115
+ process.removeListener('SIGINT', sigintHandler);
116
+ }
117
+ }
118
+ return result;
119
+ }
120
+ // --dry-run mode ---------------------------------------------------------
121
+ if (dryRun) {
122
+ await orchestrator.getBacklogIssues();
123
+ // Nothing to spawn in dry-run; caller can inspect issues via callbacks
124
+ return result;
125
+ }
126
+ // Normal run -------------------------------------------------------------
127
+ const runResult = await orchestrator.run();
128
+ result.agentsSpawned = runResult.agents.length;
129
+ result.errors = runResult.errors;
130
+ if (wait && runResult.agents.length > 0) {
131
+ const sigintHandler = () => {
132
+ orchestrator.stopAll();
133
+ };
134
+ process.on('SIGINT', sigintHandler);
135
+ try {
136
+ const completed = await orchestrator.waitForAll();
137
+ result.completed = completed;
138
+ }
139
+ finally {
140
+ process.removeListener('SIGINT', sigintHandler);
141
+ }
142
+ }
143
+ return result;
144
+ }