@renseiai/agentfactory-cli 0.8.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/dist/src/agent.d.ts +20 -0
  4. package/dist/src/agent.d.ts.map +1 -0
  5. package/dist/src/agent.js +109 -0
  6. package/dist/src/analyze-logs.d.ts +26 -0
  7. package/dist/src/analyze-logs.d.ts.map +1 -0
  8. package/dist/src/analyze-logs.js +152 -0
  9. package/dist/src/cleanup.d.ts +17 -0
  10. package/dist/src/cleanup.d.ts.map +1 -0
  11. package/dist/src/cleanup.js +111 -0
  12. package/dist/src/governor.d.ts +26 -0
  13. package/dist/src/governor.d.ts.map +1 -0
  14. package/dist/src/governor.js +305 -0
  15. package/dist/src/index.d.ts +10 -0
  16. package/dist/src/index.d.ts.map +1 -0
  17. package/dist/src/index.js +76 -0
  18. package/dist/src/lib/agent-runner.d.ts +28 -0
  19. package/dist/src/lib/agent-runner.d.ts.map +1 -0
  20. package/dist/src/lib/agent-runner.js +272 -0
  21. package/dist/src/lib/analyze-logs-runner.d.ts +47 -0
  22. package/dist/src/lib/analyze-logs-runner.d.ts.map +1 -0
  23. package/dist/src/lib/analyze-logs-runner.js +216 -0
  24. package/dist/src/lib/auto-updater.d.ts +40 -0
  25. package/dist/src/lib/auto-updater.d.ts.map +1 -0
  26. package/dist/src/lib/auto-updater.js +109 -0
  27. package/dist/src/lib/cleanup-runner.d.ts +29 -0
  28. package/dist/src/lib/cleanup-runner.d.ts.map +1 -0
  29. package/dist/src/lib/cleanup-runner.js +295 -0
  30. package/dist/src/lib/governor-dependencies.d.ts +23 -0
  31. package/dist/src/lib/governor-dependencies.d.ts.map +1 -0
  32. package/dist/src/lib/governor-dependencies.js +361 -0
  33. package/dist/src/lib/governor-logger.d.ts +30 -0
  34. package/dist/src/lib/governor-logger.d.ts.map +1 -0
  35. package/dist/src/lib/governor-logger.js +210 -0
  36. package/dist/src/lib/governor-runner.d.ts +103 -0
  37. package/dist/src/lib/governor-runner.d.ts.map +1 -0
  38. package/dist/src/lib/governor-runner.js +210 -0
  39. package/dist/src/lib/linear-runner.d.ts +8 -0
  40. package/dist/src/lib/linear-runner.d.ts.map +1 -0
  41. package/dist/src/lib/linear-runner.js +7 -0
  42. package/dist/src/lib/orchestrator-runner.d.ts +51 -0
  43. package/dist/src/lib/orchestrator-runner.d.ts.map +1 -0
  44. package/dist/src/lib/orchestrator-runner.js +151 -0
  45. package/dist/src/lib/queue-admin-runner.d.ts +30 -0
  46. package/dist/src/lib/queue-admin-runner.d.ts.map +1 -0
  47. package/dist/src/lib/queue-admin-runner.js +378 -0
  48. package/dist/src/lib/sync-routes-runner.d.ts +28 -0
  49. package/dist/src/lib/sync-routes-runner.d.ts.map +1 -0
  50. package/dist/src/lib/sync-routes-runner.js +110 -0
  51. package/dist/src/lib/version.d.ts +35 -0
  52. package/dist/src/lib/version.d.ts.map +1 -0
  53. package/dist/src/lib/version.js +168 -0
  54. package/dist/src/lib/worker-fleet-runner.d.ts +32 -0
  55. package/dist/src/lib/worker-fleet-runner.d.ts.map +1 -0
  56. package/dist/src/lib/worker-fleet-runner.js +256 -0
  57. package/dist/src/lib/worker-runner.d.ts +33 -0
  58. package/dist/src/lib/worker-runner.d.ts.map +1 -0
  59. package/dist/src/lib/worker-runner.js +781 -0
  60. package/dist/src/linear.d.ts +37 -0
  61. package/dist/src/linear.d.ts.map +1 -0
  62. package/dist/src/linear.js +118 -0
  63. package/dist/src/orchestrator.d.ts +21 -0
  64. package/dist/src/orchestrator.d.ts.map +1 -0
  65. package/dist/src/orchestrator.js +190 -0
  66. package/dist/src/queue-admin.d.ts +25 -0
  67. package/dist/src/queue-admin.d.ts.map +1 -0
  68. package/dist/src/queue-admin.js +96 -0
  69. package/dist/src/sync-routes.d.ts +17 -0
  70. package/dist/src/sync-routes.d.ts.map +1 -0
  71. package/dist/src/sync-routes.js +100 -0
  72. package/dist/src/worker-fleet.d.ts +25 -0
  73. package/dist/src/worker-fleet.d.ts.map +1 -0
  74. package/dist/src/worker-fleet.js +140 -0
  75. package/dist/src/worker.d.ts +26 -0
  76. package/dist/src/worker.d.ts.map +1 -0
  77. package/dist/src/worker.js +135 -0
  78. package/package.json +175 -0
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentFactory Governor CLI
4
+ *
5
+ * Thin wrapper around the governor runner.
6
+ *
7
+ * Usage:
8
+ * af-governor [options]
9
+ *
10
+ * Options:
11
+ * --project <name> Project to scan (can be repeated)
12
+ * --scan-interval <ms> Scan interval in milliseconds (default: 60000)
13
+ * --max-dispatches <n> Maximum concurrent dispatches per scan (default: 3)
14
+ * --no-auto-research Disable auto-research from Icebox
15
+ * --no-auto-backlog-creation Disable auto-backlog-creation from Icebox
16
+ * --no-auto-development Disable auto-development from Backlog
17
+ * --no-auto-qa Disable auto-QA from Finished
18
+ * --no-auto-acceptance Disable auto-acceptance from Delivered
19
+ * --once Run a single scan pass and exit
20
+ *
21
+ * Environment:
22
+ * LINEAR_API_KEY Required API key for Linear authentication
23
+ * GOVERNOR_PROJECTS Comma-separated project names (fallback for --project)
24
+ */
25
+ import path from 'path';
26
+ import { config } from 'dotenv';
27
+ // Load environment variables from .env.local
28
+ config({ path: path.resolve(process.cwd(), '.env.local') });
29
+ import { parseGovernorArgs, runGovernor, } from './lib/governor-runner.js';
30
+ import { createRealDependencies } from './lib/governor-dependencies.js';
31
+ import { printStartupBanner, printScanSummary, printCircuitBreakerWarning, } from './lib/governor-logger.js';
32
+ import { getVersion, checkForUpdate, printUpdateNotification } from './lib/version.js';
33
+ import { maybeAutoUpdate } from './lib/auto-updater.js';
34
+ import { createLinearAgentClient } from '@renseiai/agentfactory-linear';
35
+ import { createLogger, initTouchpointStorage } from '@renseiai/agentfactory';
36
+ import { RedisOverrideStorage, listStoredWorkspaces, getAccessToken, createRedisTokenBucket, createRedisCircuitBreaker, } from '@renseiai/agentfactory-server';
37
+ // ---------------------------------------------------------------------------
38
+ // Logger
39
+ // ---------------------------------------------------------------------------
40
+ const log = createLogger({ workerShortId: 'governor' });
41
+ // ---------------------------------------------------------------------------
42
+ // Stub dependencies
43
+ // ---------------------------------------------------------------------------
44
+ /**
45
+ * Create placeholder dependencies for the Governor.
46
+ *
47
+ * In a production deployment, these would be backed by the Linear SDK
48
+ * and Redis (via packages/server). For now we provide stubs that log
49
+ * calls and return safe defaults. The WorkSchedulingFrontend (SUP-709
50
+ * Wave 3) will provide the real implementations.
51
+ */
52
+ function createStubDependencies() {
53
+ return {
54
+ listIssues: async (_project) => {
55
+ log.warn('listIssues stub called — no issues returned', { project: _project });
56
+ return [];
57
+ },
58
+ hasActiveSession: async (_issueId) => false,
59
+ isWithinCooldown: async (_issueId) => false,
60
+ isParentIssue: async (_issueId) => false,
61
+ isHeld: async (_issueId) => false,
62
+ getOverridePriority: async (_issueId) => null,
63
+ getWorkflowStrategy: async (_issueId) => undefined,
64
+ isResearchCompleted: async (_issueId) => false,
65
+ isBacklogCreationCompleted: async (_issueId) => false,
66
+ getCompletedSessionCount: async (_issueId) => 0,
67
+ dispatchWork: async (_issue, _action) => {
68
+ log.warn('dispatchWork stub called', { issueId: _issue.id, action: _action });
69
+ },
70
+ };
71
+ }
72
+ // ---------------------------------------------------------------------------
73
+ // Default prompt generator (used when no external generator is provided)
74
+ // ---------------------------------------------------------------------------
75
+ function defaultGeneratePrompt(identifier, workType) {
76
+ const prompts = {
77
+ research: `Research and analyze ${identifier}.`,
78
+ 'backlog-creation': `Create backlog issues for ${identifier}.`,
79
+ development: `Start work on ${identifier}.`,
80
+ qa: `QA ${identifier}.`,
81
+ acceptance: `Process acceptance for ${identifier}.`,
82
+ refinement: `Refine ${identifier} based on feedback.`,
83
+ coordination: `Coordinate sub-issue execution for ${identifier}.`,
84
+ };
85
+ return prompts[workType] || `Process ${workType} for ${identifier}.`;
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Main
89
+ // ---------------------------------------------------------------------------
90
+ async function main() {
91
+ const args = parseGovernorArgs();
92
+ // Fall back to GOVERNOR_PROJECTS env var (comma-separated) when no --project flags
93
+ if (args.projects.length === 0 && process.env.GOVERNOR_PROJECTS) {
94
+ args.projects = process.env.GOVERNOR_PROJECTS.split(',').map(s => s.trim()).filter(Boolean);
95
+ }
96
+ if (args.projects.length === 0) {
97
+ console.error('Error: at least one --project is required (or set GOVERNOR_PROJECTS env var)');
98
+ process.exit(1);
99
+ }
100
+ const version = getVersion();
101
+ // -----------------------------------------------------------------------
102
+ // Choose real or stub dependencies based on environment
103
+ // -----------------------------------------------------------------------
104
+ let dependencies;
105
+ let linearClient;
106
+ const linearApiKey = process.env.LINEAR_API_KEY;
107
+ const redisUrl = process.env.REDIS_URL;
108
+ // Track latest quota from API responses
109
+ let latestQuota;
110
+ let redisConnected = false;
111
+ let oauthResolved = false;
112
+ if (linearApiKey) {
113
+ let organizationId;
114
+ // Shared rate limiter and circuit breaker strategies (Redis-backed when available)
115
+ let rateLimiterStrategy;
116
+ let circuitBreakerStrategy;
117
+ // OAuth client cache — re-created when the underlying token changes
118
+ let cachedOAuthToken;
119
+ let cachedOAuthClient;
120
+ // Initialize touchpoint storage (for isHeld / getOverridePriority) when Redis is available
121
+ if (redisUrl) {
122
+ redisConnected = true;
123
+ initTouchpointStorage(new RedisOverrideStorage());
124
+ // Resolve workspace and create shared strategies
125
+ try {
126
+ const workspaces = await listStoredWorkspaces();
127
+ if (workspaces.length > 0) {
128
+ organizationId = workspaces[0]; // Use first workspace (single-tenant)
129
+ // Create shared Redis rate limiter and circuit breaker for this workspace
130
+ rateLimiterStrategy = createRedisTokenBucket(organizationId);
131
+ circuitBreakerStrategy = createRedisCircuitBreaker(organizationId);
132
+ // Eagerly resolve token at startup to populate banner status
133
+ const accessToken = await getAccessToken(organizationId);
134
+ if (accessToken) {
135
+ cachedOAuthToken = accessToken;
136
+ cachedOAuthClient = createLinearAgentClient({
137
+ apiKey: accessToken,
138
+ rateLimiterStrategy,
139
+ circuitBreakerStrategy,
140
+ });
141
+ oauthResolved = true;
142
+ }
143
+ }
144
+ }
145
+ catch (err) {
146
+ log.warn('Failed to resolve OAuth token', {
147
+ error: err instanceof Error ? err.message : String(err),
148
+ });
149
+ }
150
+ }
151
+ /**
152
+ * Lazy OAuth client resolver.
153
+ *
154
+ * Called on each dispatchWork() to ensure the OAuth token is fresh.
155
+ * Re-reads the token from Redis (which handles refresh internally),
156
+ * and only creates a new LinearAgentClient when the token has changed.
157
+ * Rate limiter and circuit breaker strategies are reused across clients.
158
+ */
159
+ const resolveOAuthClient = organizationId
160
+ ? async () => {
161
+ try {
162
+ const accessToken = await getAccessToken(organizationId);
163
+ if (!accessToken)
164
+ return cachedOAuthClient;
165
+ // Token unchanged — reuse existing client
166
+ if (accessToken === cachedOAuthToken && cachedOAuthClient) {
167
+ return cachedOAuthClient;
168
+ }
169
+ // Token was refreshed — create new client with same strategies
170
+ log.info('OAuth token refreshed, creating new client');
171
+ cachedOAuthToken = accessToken;
172
+ cachedOAuthClient = createLinearAgentClient({
173
+ apiKey: accessToken,
174
+ rateLimiterStrategy,
175
+ circuitBreakerStrategy,
176
+ });
177
+ oauthResolved = true;
178
+ return cachedOAuthClient;
179
+ }
180
+ catch (err) {
181
+ log.warn('Failed to resolve OAuth client', {
182
+ error: err instanceof Error ? err.message : String(err),
183
+ });
184
+ return cachedOAuthClient;
185
+ }
186
+ }
187
+ : undefined;
188
+ linearClient = createLinearAgentClient({
189
+ apiKey: linearApiKey,
190
+ rateLimiterStrategy,
191
+ circuitBreakerStrategy,
192
+ onApiResponse: (quota) => {
193
+ latestQuota = quota;
194
+ },
195
+ });
196
+ dependencies = createRealDependencies({
197
+ linearClient,
198
+ resolveOAuthClient,
199
+ organizationId,
200
+ generatePrompt: defaultGeneratePrompt,
201
+ });
202
+ }
203
+ else {
204
+ log.warn('LINEAR_API_KEY not set — using stub dependencies (no real work will be dispatched)');
205
+ dependencies = createStubDependencies();
206
+ }
207
+ // -----------------------------------------------------------------------
208
+ // Print startup banner
209
+ // -----------------------------------------------------------------------
210
+ printStartupBanner({
211
+ version,
212
+ projects: args.projects,
213
+ scanIntervalMs: args.scanIntervalMs,
214
+ maxConcurrentDispatches: args.maxConcurrentDispatches,
215
+ mode: args.mode,
216
+ once: args.once,
217
+ features: {
218
+ autoResearch: args.enableAutoResearch,
219
+ autoBacklogCreation: args.enableAutoBacklogCreation,
220
+ autoDevelopment: args.enableAutoDevelopment,
221
+ autoQA: args.enableAutoQA,
222
+ autoAcceptance: args.enableAutoAcceptance,
223
+ },
224
+ redisConnected,
225
+ oauthResolved,
226
+ });
227
+ // -----------------------------------------------------------------------
228
+ // Update check (non-blocking — runs in background)
229
+ // -----------------------------------------------------------------------
230
+ const updateCheck = await checkForUpdate();
231
+ printUpdateNotification(updateCheck);
232
+ // -----------------------------------------------------------------------
233
+ // Configure and run
234
+ // -----------------------------------------------------------------------
235
+ const runnerConfig = {
236
+ projects: args.projects,
237
+ scanIntervalMs: args.scanIntervalMs,
238
+ maxConcurrentDispatches: args.maxConcurrentDispatches,
239
+ enableAutoResearch: args.enableAutoResearch,
240
+ enableAutoBacklogCreation: args.enableAutoBacklogCreation,
241
+ enableAutoDevelopment: args.enableAutoDevelopment,
242
+ enableAutoQA: args.enableAutoQA,
243
+ enableAutoAcceptance: args.enableAutoAcceptance,
244
+ once: args.once,
245
+ mode: args.mode,
246
+ dependencies,
247
+ callbacks: {
248
+ onScanComplete: async (results) => {
249
+ const apiCalls = linearClient?.apiCallCount;
250
+ printScanSummary(results, 0, latestQuota, apiCalls);
251
+ // Check circuit breaker status if available
252
+ // (CircuitBreaker instances expose a .state getter)
253
+ if (linearClient) {
254
+ const breaker = linearClient.circuitBreaker;
255
+ if (breaker?.state && breaker.state !== 'closed') {
256
+ printCircuitBreakerWarning(breaker.state);
257
+ }
258
+ }
259
+ // Reset for next scan
260
+ linearClient?.resetApiCallCount();
261
+ latestQuota = undefined;
262
+ // Auto-update at end of scan when no active dispatches
263
+ await maybeAutoUpdate(updateCheck, {
264
+ cliFlag: args.autoUpdate,
265
+ });
266
+ },
267
+ onError: (error) => {
268
+ log.error('Governor error', { error: error.message });
269
+ },
270
+ },
271
+ };
272
+ try {
273
+ const { governor, scanResults } = await runGovernor(runnerConfig);
274
+ if (args.once && scanResults) {
275
+ // Print summary and exit
276
+ const apiCalls = linearClient?.apiCallCount;
277
+ printScanSummary(scanResults, 0, latestQuota, apiCalls);
278
+ let totalDispatched = 0;
279
+ let totalErrors = 0;
280
+ for (const result of scanResults) {
281
+ totalDispatched += result.actionsDispatched;
282
+ totalErrors += result.errors.length;
283
+ }
284
+ log.info(`Scan complete: ${totalDispatched} dispatched, ${totalErrors} errors`);
285
+ return;
286
+ }
287
+ // Continuous mode — handle graceful shutdown
288
+ log.info('Governor running. Press Ctrl+C to stop.');
289
+ const shutdown = () => {
290
+ log.info('Shutting down governor...');
291
+ governor.stop();
292
+ process.exit(0);
293
+ };
294
+ process.on('SIGINT', shutdown);
295
+ process.on('SIGTERM', shutdown);
296
+ }
297
+ catch (error) {
298
+ log.error('Governor failed', { error: error instanceof Error ? error.message : String(error) });
299
+ process.exit(1);
300
+ }
301
+ }
302
+ main().catch((error) => {
303
+ log.error('Fatal error', { error: error instanceof Error ? error.message : String(error) });
304
+ process.exit(1);
305
+ });
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentFactory CLI
4
+ *
5
+ * Entry point for the agentfactory command.
6
+ * Dispatches to sub-commands based on first argument.
7
+ */
8
+ declare const command: string;
9
+ declare function printHelp(): void;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,QAAA,MAAM,OAAO,QAAkB,CAAA;AAE/B,iBAAS,SAAS,IAAI,IAAI,CAwBzB"}
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * AgentFactory CLI
5
+ *
6
+ * Entry point for the agentfactory command.
7
+ * Dispatches to sub-commands based on first argument.
8
+ */
9
+ const command = process.argv[2];
10
+ function printHelp() {
11
+ console.log(`
12
+ AgentFactory CLI — Multi-agent fleet management for coding agents
13
+
14
+ Usage:
15
+ agentfactory <command> [options]
16
+
17
+ Commands:
18
+ orchestrator Spawn concurrent agents on backlog issues
19
+ governor Automated workflow scan loop with configurable triggers
20
+ worker Start a remote worker that polls for queued work
21
+ worker-fleet Spawn and manage multiple worker processes
22
+ agent Manage running agent sessions (stop, chat, status, reconnect)
23
+ cleanup Clean up orphaned git worktrees
24
+ queue-admin Manage Redis work queue and sessions
25
+ analyze-logs Analyze agent session logs for errors
26
+ linear Linear issue tracker operations
27
+ sync-routes Generate missing route and page files from manifest
28
+ help Show this help message
29
+
30
+ Run 'agentfactory <command> --help' for command-specific options.
31
+
32
+ Learn more: https://github.com/renseiai/agentfactory
33
+ `);
34
+ }
35
+ switch (command) {
36
+ case 'orchestrator':
37
+ import('./orchestrator');
38
+ break;
39
+ case 'governor':
40
+ import('./governor');
41
+ break;
42
+ case 'worker':
43
+ import('./worker');
44
+ break;
45
+ case 'worker-fleet':
46
+ import('./worker-fleet');
47
+ break;
48
+ case 'agent':
49
+ import('./agent');
50
+ break;
51
+ case 'cleanup':
52
+ import('./cleanup');
53
+ break;
54
+ case 'queue-admin':
55
+ import('./queue-admin');
56
+ break;
57
+ case 'analyze-logs':
58
+ import('./analyze-logs');
59
+ break;
60
+ case 'linear':
61
+ import('./linear');
62
+ break;
63
+ case 'sync-routes':
64
+ import('./sync-routes');
65
+ break;
66
+ case 'help':
67
+ case '--help':
68
+ case '-h':
69
+ case undefined:
70
+ printHelp();
71
+ break;
72
+ default:
73
+ console.error(`Unknown command: ${command}`);
74
+ printHelp();
75
+ process.exit(1);
76
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Agent Runner -- Programmatic API for the af-agent CLI.
3
+ *
4
+ * Provides stop, chat, status, and reconnect commands for managing running
5
+ * agent sessions. Works by updating Redis state directly — workers poll for
6
+ * status changes and pending prompts every 5 seconds.
7
+ */
8
+ export type AgentCommand = 'stop' | 'chat' | 'status' | 'reconnect' | 'list';
9
+ export interface AgentRunnerConfig {
10
+ /** Command to execute */
11
+ command: AgentCommand;
12
+ /** Issue identifier (e.g., SUP-674) or partial session ID — not required for 'list' */
13
+ issueId?: string;
14
+ /** Message text for 'chat' command */
15
+ message?: string;
16
+ /** Show all sessions including completed/failed (for 'list' command) */
17
+ all?: boolean;
18
+ }
19
+ export declare const C: {
20
+ readonly reset: "\u001B[0m";
21
+ readonly red: "\u001B[31m";
22
+ readonly green: "\u001B[32m";
23
+ readonly yellow: "\u001B[33m";
24
+ readonly cyan: "\u001B[36m";
25
+ readonly gray: "\u001B[90m";
26
+ };
27
+ export declare function runAgent(config: AgentRunnerConfig): Promise<void>;
28
+ //# sourceMappingURL=agent-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/agent-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiBH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAA;AAE5E,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,OAAO,EAAE,YAAY,CAAA;IACrB,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAMD,eAAO,MAAM,CAAC;;;;;;;CAOJ,CAAA;AAuRV,wBAAsB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBvE"}