@mamdouh-aboammar/agentic-workflow 1.2.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 (118) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/.codex-plugin/plugin.json +13 -0
  3. package/.skills.json +19 -0
  4. package/AGENTS.md +1344 -0
  5. package/CLAUDE.md +178 -0
  6. package/GEMINI.md +102 -0
  7. package/LICENSE +21 -0
  8. package/README.md +350 -0
  9. package/SKILL.md +132 -0
  10. package/bin/agentic-hooks.sh +79 -0
  11. package/bin/cli.js +1060 -0
  12. package/core/__init__.py +52 -0
  13. package/core/ai_evaluator.py +117 -0
  14. package/core/autopilot_engine.py +368 -0
  15. package/core/clean_code_guard.py +188 -0
  16. package/core/engine_py/__init__.py +29 -0
  17. package/core/engine_py/agent_worker.py +136 -0
  18. package/core/engine_py/decider.py +150 -0
  19. package/core/engine_py/energy.py +45 -0
  20. package/core/engine_py/event_bus.py +63 -0
  21. package/core/engine_py/executor.py +186 -0
  22. package/core/engine_py/models.py +193 -0
  23. package/core/engine_py/queue.py +314 -0
  24. package/core/engine_py/runner.py +116 -0
  25. package/core/engine_py/system_workers.py +70 -0
  26. package/core/engine_py/toon_adapter.py +586 -0
  27. package/core/engine_py/verification_controller.py +208 -0
  28. package/core/engine_py/worker.py +167 -0
  29. package/core/engine_spec/event_schema.json +65 -0
  30. package/core/engine_spec/example_workflow.yaml +73 -0
  31. package/core/engine_spec/workflow_schema.json +127 -0
  32. package/core/hooks/__init__.py +29 -0
  33. package/core/hooks/adapters/__init__.py +25 -0
  34. package/core/hooks/adapters/claude_adapter.py +83 -0
  35. package/core/hooks/adapters/cli_agent_adapter.py +82 -0
  36. package/core/hooks/adapters/codex_adapter.py +78 -0
  37. package/core/hooks/adapters/cursor_adapter.py +73 -0
  38. package/core/hooks/adapters/gemini_adapter.py +93 -0
  39. package/core/hooks/adapters/homebrew_adapter.py +69 -0
  40. package/core/hooks/adapters/mcp_proxy.py +133 -0
  41. package/core/hooks/adapters/shell_adapter.py +65 -0
  42. package/core/hooks/dispatcher.py +118 -0
  43. package/core/hooks/policy_engine.py +375 -0
  44. package/core/hooks/session_end.py +141 -0
  45. package/core/hooks/types.py +147 -0
  46. package/core/integrations/__init__.py +28 -0
  47. package/core/integrations/installer.py +225 -0
  48. package/core/integrations/lifecycle_director.py +175 -0
  49. package/core/integrations/registry.py +105 -0
  50. package/core/multi_agent_system.py +164 -0
  51. package/core/skills_indexer.py +742 -0
  52. package/core/system/__init__.py +25 -0
  53. package/core/system/announcements.py +72 -0
  54. package/core/system/dependencies.py +69 -0
  55. package/core/system/doctor.py +171 -0
  56. package/core/system/health.py +144 -0
  57. package/core/system/installer.py +137 -0
  58. package/core/system/notifications.py +97 -0
  59. package/core/system/refresher.py +110 -0
  60. package/core/system/updater.py +167 -0
  61. package/core/system/version_tracker.py +65 -0
  62. package/docs/architecture_plan.md +7 -0
  63. package/docs/guides/failure-recovery.md +714 -0
  64. package/docs/implementation_summary.md +10 -0
  65. package/docs/protocols/autopilot-execution.md +148 -0
  66. package/docs/protocols/code-change-protocol.md +49 -0
  67. package/docs/protocols/context-preservation-detail.md +114 -0
  68. package/docs/protocols/quality-gates.md +110 -0
  69. package/docs/protocols/ulw-mode.md +60 -0
  70. package/docs/research_findings.md +10 -0
  71. package/docs/solutions/autonomous-autopilot-engine-architecture.md +38 -0
  72. package/install.sh +111 -0
  73. package/marketplace.json +37 -0
  74. package/package.json +81 -0
  75. package/skills/agentic-workflow/SKILL.md +132 -0
  76. package/skills/agentic-workflow/skill-spec.json +100 -0
  77. package/soul.md +445 -0
  78. package/src/engine_ts/decider.ts +186 -0
  79. package/src/engine_ts/event-bus.ts +57 -0
  80. package/src/engine_ts/executor.ts +262 -0
  81. package/src/engine_ts/index.ts +12 -0
  82. package/src/engine_ts/queue.ts +93 -0
  83. package/src/engine_ts/runner.ts +108 -0
  84. package/src/engine_ts/skills-indexer.ts +264 -0
  85. package/src/engine_ts/toon-adapter.ts +91 -0
  86. package/src/engine_ts/types.ts +134 -0
  87. package/src/engine_ts/verification-controller.ts +204 -0
  88. package/src/engine_ts/worker.ts +280 -0
  89. package/src/hooks/adapters/claude-adapter.ts +54 -0
  90. package/src/hooks/adapters/cli-agent-adapter.ts +46 -0
  91. package/src/hooks/adapters/codex-adapter.ts +69 -0
  92. package/src/hooks/adapters/cursor-adapter.ts +60 -0
  93. package/src/hooks/adapters/gemini-adapter.ts +71 -0
  94. package/src/hooks/adapters/homebrew-adapter.ts +36 -0
  95. package/src/hooks/adapters/mcp-proxy.ts +66 -0
  96. package/src/hooks/adapters/shell-adapter.ts +42 -0
  97. package/src/hooks/dispatcher.ts +113 -0
  98. package/src/hooks/index.ts +16 -0
  99. package/src/hooks/policy-engine.ts +376 -0
  100. package/src/hooks/session-end.ts +125 -0
  101. package/src/hooks/types.ts +61 -0
  102. package/src/index.d.ts +34 -0
  103. package/src/index.ts +23 -0
  104. package/src/integrations/index.ts +7 -0
  105. package/src/integrations/installer.ts +208 -0
  106. package/src/integrations/lifecycle-director.ts +139 -0
  107. package/src/integrations/registry.ts +82 -0
  108. package/src/system/announcements.ts +143 -0
  109. package/src/system/dependencies.ts +176 -0
  110. package/src/system/doctor.ts +374 -0
  111. package/src/system/health.ts +270 -0
  112. package/src/system/index.ts +14 -0
  113. package/src/system/installer.ts +262 -0
  114. package/src/system/notifications.ts +180 -0
  115. package/src/system/refresher.ts +207 -0
  116. package/src/system/types.ts +268 -0
  117. package/src/system/updater.ts +219 -0
  118. package/src/system/version-tracker.ts +137 -0
@@ -0,0 +1,270 @@
1
+ /**
2
+ * src/system/health.ts — Health Telemetry & Composite Scoring Engine
3
+ */
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import { execSync } from 'node:child_process';
9
+ import type { HealthReport, HealthScore, SystemVitals, WorkflowVitals, HealthGrade } from './types.ts';
10
+
11
+ export class HealthEngine {
12
+ private projectDir: string;
13
+
14
+ constructor(projectDir: string = '.') {
15
+ this.projectDir = path.resolve(projectDir);
16
+ }
17
+
18
+ private getDiskUsage(): { freeBytes?: number; totalBytes?: number } {
19
+ try {
20
+ const out = execSync("df -k . | tail -1", { cwd: this.projectDir, stdio: 'pipe', encoding: 'utf-8' }).trim();
21
+ const parts = out.split(/\s+/);
22
+ if (parts.length >= 4) {
23
+ const total = parseInt(parts[1], 10) * 1024;
24
+ const free = parseInt(parts[3], 10) * 1024;
25
+ return { totalBytes: total, freeBytes: free };
26
+ }
27
+ } catch {
28
+ // Ignore
29
+ }
30
+ return {};
31
+ }
32
+
33
+ public collectSystemVitals(): SystemVitals {
34
+ const totalMem = os.totalmem();
35
+ const freeMem = os.freemem();
36
+ const usedMem = totalMem - freeMem;
37
+ const usedPct = totalMem > 0 ? Math.round((usedMem / totalMem) * 100) : 0;
38
+ const rss = process.memoryUsage().rss;
39
+
40
+ let bunVersion: string | undefined;
41
+ try {
42
+ bunVersion = execSync('bun --version', { stdio: 'pipe', encoding: 'utf-8' }).trim();
43
+ } catch {
44
+ // Ignore
45
+ }
46
+
47
+ let pyVersion: string | undefined;
48
+ try {
49
+ pyVersion = execSync('python3 --version', { stdio: 'pipe', encoding: 'utf-8' }).trim().replace('Python ', '');
50
+ } catch {
51
+ // Ignore
52
+ }
53
+
54
+ return {
55
+ platform: os.platform(),
56
+ arch: os.arch(),
57
+ uptimeSeconds: Math.floor(os.uptime()),
58
+ memory: {
59
+ totalBytes: totalMem,
60
+ freeBytes: freeMem,
61
+ rssBytes: rss,
62
+ usedPercentage: usedPct
63
+ },
64
+ disk: this.getDiskUsage(),
65
+ nodeVersion: process.version,
66
+ bunVersion,
67
+ pythonVersion: pyVersion
68
+ };
69
+ }
70
+
71
+ public collectWorkflowVitals(): WorkflowVitals {
72
+ const sotPath = path.join(this.projectDir, 'state.yaml');
73
+ let hasActiveWorkflow = false;
74
+ let currentStep: string | undefined;
75
+ let retryCount = 0;
76
+
77
+ if (fs.existsSync(sotPath)) {
78
+ try {
79
+ const content = fs.readFileSync(sotPath, 'utf-8');
80
+ hasActiveWorkflow = content.includes('status: in_progress') || content.includes('status: planning');
81
+ const match = content.match(/current_step:\s*["']?([^"'\n]+)/);
82
+ if (match) currentStep = match[1];
83
+ const retryMatch = content.match(/retry_count:\s*(\d+)/);
84
+ if (retryMatch) retryCount = parseInt(retryMatch[1], 10);
85
+ } catch {
86
+ // Ignore
87
+ }
88
+ }
89
+
90
+ let circuitBreakerStatus: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
91
+ let failureStreak = 0;
92
+ const fableStatePath = path.join(this.projectDir, '.fable', 'state.json');
93
+ if (fs.existsSync(fableStatePath)) {
94
+ try {
95
+ const fsData = JSON.parse(fs.readFileSync(fableStatePath, 'utf-8'));
96
+ if (fsData.circuit_breaker_tripped) circuitBreakerStatus = 'OPEN';
97
+ if (typeof fsData.failure_streak === 'number') failureStreak = fsData.failure_streak;
98
+ } catch {
99
+ // Ignore
100
+ }
101
+ }
102
+
103
+ let traceSpansCount = 0;
104
+ const traceDir = path.join(this.projectDir, '.traces');
105
+ if (fs.existsSync(traceDir)) {
106
+ try {
107
+ const files = fs.readdirSync(traceDir).filter(f => f.endsWith('.jsonl'));
108
+ for (const file of files) {
109
+ const lines = fs.readFileSync(path.join(traceDir, file), 'utf-8').trim().split('\n');
110
+ traceSpansCount += lines.length;
111
+ }
112
+ } catch {
113
+ // Ignore
114
+ }
115
+ }
116
+
117
+ return {
118
+ hasActiveWorkflow,
119
+ currentStep,
120
+ circuitBreakerStatus,
121
+ failureStreak,
122
+ retryCount,
123
+ traceSpansCount
124
+ };
125
+ }
126
+
127
+ public evaluateHealthScore(vitals: SystemVitals, wf: WorkflowVitals): HealthScore {
128
+ const warnings: string[] = [];
129
+ let runtimeScore = 20;
130
+ let workflowScore = 25;
131
+ let securityScore = 20;
132
+ let dependenciesScore = 15;
133
+ let integrityScore = 20;
134
+
135
+ // Runtime deductions
136
+ if (!vitals.bunVersion) {
137
+ runtimeScore -= 5;
138
+ warnings.push('Bun runtime not detected (preferred engine).');
139
+ }
140
+ if (!vitals.pythonVersion) {
141
+ runtimeScore -= 5;
142
+ warnings.push('Python 3 runtime not detected (dual-parity hooks).');
143
+ }
144
+ if (vitals.memory.usedPercentage > 90) {
145
+ runtimeScore -= 5;
146
+ warnings.push(`High system memory utilization (${vitals.memory.usedPercentage}%).`);
147
+ }
148
+
149
+ // Workflow deductions
150
+ if (wf.circuitBreakerStatus === 'OPEN') {
151
+ workflowScore -= 15;
152
+ warnings.push('Circuit breaker is OPEN due to repeated step failures.');
153
+ } else if (wf.failureStreak > 0) {
154
+ workflowScore -= Math.min(10, wf.failureStreak * 3);
155
+ warnings.push(`Current failure streak: ${wf.failureStreak}.`);
156
+ }
157
+ if (wf.retryCount > 3) {
158
+ workflowScore -= 5;
159
+ warnings.push(`Elevated retry count in workflow: ${wf.retryCount}.`);
160
+ }
161
+
162
+ // Security score
163
+ const gitignore = path.join(this.projectDir, '.gitignore');
164
+ if (!fs.existsSync(gitignore) || !fs.readFileSync(gitignore, 'utf-8').includes('.env')) {
165
+ securityScore -= 5;
166
+ warnings.push('.env secret exclusion rule missing from .gitignore.');
167
+ }
168
+
169
+ // Dependencies score
170
+ const hasNodeModules = fs.existsSync(path.join(this.projectDir, 'node_modules'));
171
+ if (!hasNodeModules) {
172
+ dependenciesScore -= 5;
173
+ warnings.push('node_modules directory missing. Dependencies not installed.');
174
+ }
175
+
176
+ // Integrity score
177
+ const hasSkillsIndex = fs.existsSync(path.join(this.projectDir, 'skills-index.toon'));
178
+ if (!hasSkillsIndex) {
179
+ integrityScore -= 5;
180
+ warnings.push('High-density skills-index.toon missing.');
181
+ }
182
+
183
+ const total = Math.max(0, Math.min(100, runtimeScore + workflowScore + securityScore + dependenciesScore + integrityScore));
184
+ let grade: HealthGrade = 'F';
185
+ if (total >= 95) grade = 'A+';
186
+ else if (total >= 85) grade = 'A';
187
+ else if (total >= 70) grade = 'B';
188
+ else if (total >= 50) grade = 'C';
189
+
190
+ return {
191
+ score: total,
192
+ grade,
193
+ breakdown: {
194
+ runtime: Math.max(0, runtimeScore),
195
+ workflow: Math.max(0, workflowScore),
196
+ security: Math.max(0, securityScore),
197
+ dependencies: Math.max(0, dependenciesScore),
198
+ integrity: Math.max(0, integrityScore)
199
+ },
200
+ warnings
201
+ };
202
+ }
203
+
204
+ public getReport(): HealthReport {
205
+ const vitals = this.collectSystemVitals();
206
+ const wf = this.collectWorkflowVitals();
207
+ const score = this.evaluateHealthScore(vitals, wf);
208
+
209
+ return {
210
+ timestamp: new Date().toISOString(),
211
+ score,
212
+ vitals,
213
+ workflow: wf,
214
+ services: {
215
+ TS_ENGINE: vitals.bunVersion ? 'HEALTHY' : 'DEGRADED',
216
+ PY_ENGINE: vitals.pythonVersion ? 'HEALTHY' : 'DEGRADED',
217
+ UAHF_HOOKS: fs.existsSync(path.join(this.projectDir, '.claude', 'hooks', 'scripts')) ? 'HEALTHY' : 'DOWN',
218
+ SKILLS_MESH: fs.existsSync(path.join(this.projectDir, 'skills-index.toon')) ? 'HEALTHY' : 'DEGRADED',
219
+ CIRCUIT_BREAKER: wf.circuitBreakerStatus === 'CLOSED' ? 'HEALTHY' : 'DEGRADED'
220
+ }
221
+ };
222
+ }
223
+
224
+ public formatToon(report: HealthReport): string {
225
+ return `health_telemetry{timestamp:"${report.timestamp}",score:${report.score.score},grade:"${report.score.grade}"}:
226
+ runtime[1]{os,arch,uptime_s,mem_used_pct,bun_v,py_v}:
227
+ ${report.vitals.platform},${report.vitals.arch},${report.vitals.uptimeSeconds},${report.vitals.memory.usedPercentage}%,${report.vitals.bunVersion || 'none'},${report.vitals.pythonVersion || 'none'}
228
+ workflow[1]{active,step,circuit_breaker,streak,retries,spans}:
229
+ ${report.workflow.hasActiveWorkflow},${report.workflow.currentStep || 'idle'},${report.workflow.circuitBreakerStatus},${report.workflow.failureStreak},${report.workflow.retryCount},${report.workflow.traceSpansCount}
230
+ services[5]{service,status}:
231
+ TS_ENGINE,${report.services.TS_ENGINE}
232
+ PY_ENGINE,${report.services.PY_ENGINE}
233
+ UAHF_HOOKS,${report.services.UAHF_HOOKS}
234
+ SKILLS_MESH,${report.services.SKILLS_MESH}
235
+ CIRCUIT_BREAKER,${report.services.CIRCUIT_BREAKER}
236
+ breakdown{runtime:${report.score.breakdown.runtime}/20,workflow:${report.score.breakdown.workflow}/25,security:${report.score.breakdown.security}/20,deps:${report.score.breakdown.dependencies}/15,integrity:${report.score.breakdown.integrity}/20}
237
+ warnings[${report.score.warnings.length}]:
238
+ ${report.score.warnings.map(w => ` - "${w}"`).join('\n') || ' none'}`;
239
+ }
240
+
241
+ public formatDashboard(report: HealthReport): string {
242
+ const s = report.score;
243
+ const gradeColor = s.grade.startsWith('A') ? '\x1b[32m' : s.grade === 'B' ? '\x1b[34m' : '\x1b[33m';
244
+ const reset = '\x1b[0m';
245
+ const bold = '\x1b[1m';
246
+
247
+ return `
248
+ ${bold}══════════════════════════════════════════════════════════════════${reset}
249
+ ⚡ ${bold}AGENTICWORKFLOW LIVE HEALTH ENGINE DASHBOARD${reset} ⚡
250
+ ${bold}══════════════════════════════════════════════════════════════════${reset}
251
+ Composite Health Score: ${gradeColor}${bold}${s.score}/100 [Grade: ${s.grade}]${reset}
252
+ System Vitals: OS: ${report.vitals.platform} (${report.vitals.arch}) | Uptime: ${Math.floor(report.vitals.uptimeSeconds / 60)}m
253
+ Memory Utilization: ${report.vitals.memory.usedPercentage}% [RSS: ${Math.round(report.vitals.memory.rssBytes / 1024 / 1024)}MB]
254
+ Dual Engine Runtimes: Bun: ${report.vitals.bunVersion || 'N/A'} | Python: ${report.vitals.pythonVersion || 'N/A'}
255
+
256
+ Workflow Status: ${report.workflow.hasActiveWorkflow ? `ACTIVE (Step: ${report.workflow.currentStep})` : 'IDLE / READY'}
257
+ Circuit Breaker: ${report.workflow.circuitBreakerStatus === 'CLOSED' ? '\x1b[32mCLOSED [Healthy]\x1b[0m' : '\x1b[31mOPEN [Tripped]\x1b[0m'}
258
+ Telemetry Spans: ${report.workflow.traceSpansCount} spans recorded
259
+
260
+ Score Subsystem Breakdown:
261
+ • Runtime Parity: ${s.breakdown.runtime}/20
262
+ • Workflow Vitals: ${s.breakdown.workflow}/25
263
+ • Security & Secrets: ${s.breakdown.security}/20
264
+ • Lib Dependencies: ${s.breakdown.dependencies}/15
265
+ • State Integrity: ${s.breakdown.integrity}/20
266
+ ${s.warnings.length > 0 ? `\nActive Advisories:\n${s.warnings.map(w => ` ⚠️ ${w}`).join('\n')}` : '\n✅ All system invariants and health parameters optimal.'}
267
+ ${bold}══════════════════════════════════════════════════════════════════${reset}
268
+ `;
269
+ }
270
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * src/system/index.ts — Unified Export for AgenticWorkflow System Engines Subsystem
3
+ */
4
+
5
+ export * from './types.ts';
6
+ export * from './updater.ts';
7
+ export * from './installer.ts';
8
+ export * from './refresher.ts';
9
+ export * from './doctor.ts';
10
+ export * from './health.ts';
11
+ export * from './dependencies.ts';
12
+ export * from './notifications.ts';
13
+ export * from './announcements.ts';
14
+ export * from './version-tracker.ts';
@@ -0,0 +1,262 @@
1
+ /**
2
+ * src/system/installer.ts — Auto-Installer & Environment Bootstrapper Engine
3
+ */
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import { execSync } from 'node:child_process';
9
+ import type { InstallOptions, InstallReport, InstallTarget, HostPlatform } from './types.ts';
10
+
11
+ export class AutoInstaller {
12
+ private projectDir: string;
13
+ private homeDir: string;
14
+
15
+ constructor(projectDir: string = '.') {
16
+ this.projectDir = path.resolve(projectDir);
17
+ this.homeDir = os.homedir();
18
+ }
19
+
20
+ private checkBinary(cmd: string): { available: boolean; version?: string } {
21
+ try {
22
+ const out = execSync(`${cmd} --version`, { stdio: 'pipe', encoding: 'utf-8' }).trim();
23
+ return { available: true, version: out.split('\n')[0] };
24
+ } catch {
25
+ return { available: false };
26
+ }
27
+ }
28
+
29
+ public getHostTargets(): InstallTarget[] {
30
+ const targets: InstallTarget[] = [
31
+ {
32
+ name: 'Claude Code Skills',
33
+ platform: 'claude',
34
+ path: path.join(this.homeDir, '.claude', 'skills', 'agentic-workflow'),
35
+ installed: fs.existsSync(path.join(this.homeDir, '.claude', 'skills', 'agentic-workflow'))
36
+ },
37
+ {
38
+ name: 'Gemini CLI / Antigravity Skills',
39
+ platform: 'gemini',
40
+ path: path.join(this.homeDir, '.gemini', 'config', 'skills', 'agentic-workflow'),
41
+ installed: fs.existsSync(path.join(this.homeDir, '.gemini', 'config', 'skills', 'agentic-workflow'))
42
+ },
43
+ {
44
+ name: 'Cursor IDE Skills',
45
+ platform: 'cursor',
46
+ path: path.join(this.homeDir, '.cursor', 'skills', 'agentic-workflow'),
47
+ installed: fs.existsSync(path.join(this.homeDir, '.cursor', 'skills', 'agentic-workflow'))
48
+ },
49
+ {
50
+ name: 'Codex / OpenCode Skills',
51
+ platform: 'codex',
52
+ path: path.join(this.homeDir, '.codex', 'skills', 'agentic-workflow'),
53
+ installed: fs.existsSync(path.join(this.homeDir, '.codex', 'skills', 'agentic-workflow'))
54
+ },
55
+ {
56
+ name: 'Universal Agent Kernel',
57
+ platform: 'agents',
58
+ path: path.join(this.homeDir, '.agents', 'skills', 'agentic-workflow'),
59
+ installed: fs.existsSync(path.join(this.homeDir, '.agents', 'skills', 'agentic-workflow'))
60
+ }
61
+ ];
62
+ return targets;
63
+ }
64
+
65
+ public checkStatus(): InstallReport {
66
+ const targets = this.getHostTargets();
67
+ const installed = targets.filter(t => t.installed);
68
+ const skipped = targets.filter(t => !t.installed);
69
+
70
+ const binPaths = [
71
+ path.join(this.homeDir, '.local', 'bin', 'agentic-workflow'),
72
+ '/usr/local/bin/agentic-workflow'
73
+ ];
74
+ const linked = binPaths.filter(p => fs.existsSync(p));
75
+
76
+ const systemDeps = [
77
+ { name: 'bun', ...this.checkBinary('bun') },
78
+ { name: 'node', ...this.checkBinary('node') },
79
+ { name: 'python3', ...this.checkBinary('python3') },
80
+ { name: 'git', ...this.checkBinary('git') },
81
+ { name: 'brew', ...this.checkBinary('brew') }
82
+ ];
83
+
84
+ return {
85
+ success: true,
86
+ installedTargets: installed,
87
+ skippedTargets: skipped,
88
+ binLinked: linked,
89
+ shellConfigured: [],
90
+ systemDeps,
91
+ messages: [
92
+ `Installed in ${installed.length} agent host(s)`,
93
+ `CLI binaries linked in ${linked.length} path(s)`
94
+ ]
95
+ };
96
+ }
97
+
98
+ public install(options: InstallOptions = {}): InstallReport {
99
+ const messages: string[] = [];
100
+ const installedTargets: InstallTarget[] = [];
101
+ const skippedTargets: InstallTarget[] = [];
102
+ const binLinked: string[] = [];
103
+ const shellConfigured: string[] = [];
104
+
105
+ const hostTargets = this.getHostTargets();
106
+ const requestedPlatforms = options.targets || ['claude', 'gemini', 'cursor', 'codex', 'agents'];
107
+
108
+ // 1. Copy or Symlink into host skills directories
109
+ for (const target of hostTargets) {
110
+ if (!requestedPlatforms.includes(target.platform)) {
111
+ skippedTargets.push(target);
112
+ continue;
113
+ }
114
+
115
+ const parentDir = path.dirname(target.path);
116
+ try {
117
+ if (!fs.existsSync(parentDir)) {
118
+ fs.mkdirSync(parentDir, { recursive: true });
119
+ }
120
+
121
+ if (fs.existsSync(target.path)) {
122
+ fs.rmSync(target.path, { recursive: true, force: true });
123
+ }
124
+
125
+ // Copy files excluding node_modules / .git
126
+ fs.cpSync(this.projectDir, target.path, {
127
+ recursive: true,
128
+ filter: (src) => {
129
+ const rel = path.relative(this.projectDir, src);
130
+ if (rel.startsWith('node_modules') || rel.startsWith('.git') || rel.startsWith('__pycache__')) {
131
+ return false;
132
+ }
133
+ return true;
134
+ }
135
+ });
136
+
137
+ target.installed = true;
138
+ installedTargets.push(target);
139
+ messages.push(`✓ Installed skill to ${target.name} (${target.path})`);
140
+ } catch (err: any) {
141
+ messages.push(`! Failed installing to ${target.name}: ${err.message}`);
142
+ skippedTargets.push(target);
143
+ }
144
+ }
145
+
146
+ // 2. Global CLI Binary Symlink
147
+ const cliSource = path.join(this.projectDir, 'bin', 'cli.js');
148
+ if (fs.existsSync(cliSource)) {
149
+ try {
150
+ fs.chmodSync(cliSource, 0o755);
151
+ } catch {
152
+ // Ignore
153
+ }
154
+
155
+ const localBinDir = path.join(this.homeDir, '.local', 'bin');
156
+ if (!fs.existsSync(localBinDir)) {
157
+ fs.mkdirSync(localBinDir, { recursive: true });
158
+ }
159
+
160
+ const localBinTarget = path.join(localBinDir, 'agentic-workflow');
161
+ try {
162
+ if (fs.existsSync(localBinTarget)) {
163
+ fs.unlinkSync(localBinTarget);
164
+ }
165
+ fs.symlinkSync(cliSource, localBinTarget);
166
+ binLinked.push(localBinTarget);
167
+ messages.push(`✓ Linked executable to ${localBinTarget}`);
168
+ } catch (err: any) {
169
+ messages.push(`! Failed linking ${localBinTarget}: ${err.message}`);
170
+ }
171
+
172
+ // Try /usr/local/bin if writable
173
+ if (options.globalBin) {
174
+ const usrBinTarget = '/usr/local/bin/agentic-workflow';
175
+ try {
176
+ if (fs.existsSync(usrBinTarget)) {
177
+ fs.unlinkSync(usrBinTarget);
178
+ }
179
+ fs.symlinkSync(cliSource, usrBinTarget);
180
+ binLinked.push(usrBinTarget);
181
+ messages.push(`✓ Linked global executable to ${usrBinTarget}`);
182
+ } catch {
183
+ // May need sudo, skip gracefully
184
+ }
185
+ }
186
+ }
187
+
188
+ // 3. Update Shell RC if requested
189
+ if (options.updateShellRc !== false) {
190
+ const localBin = path.join(this.homeDir, '.local', 'bin');
191
+ const exportLine = `export PATH="$PATH:${localBin}"`;
192
+ const rcFiles = [
193
+ path.join(this.homeDir, '.zshrc'),
194
+ path.join(this.homeDir, '.bashrc'),
195
+ path.join(this.homeDir, '.profile')
196
+ ];
197
+
198
+ for (const rc of rcFiles) {
199
+ if (fs.existsSync(rc)) {
200
+ try {
201
+ const content = fs.readFileSync(rc, 'utf-8');
202
+ if (!content.includes(localBin)) {
203
+ fs.appendFileSync(rc, `\n# AgenticWorkflow CLI\n${exportLine}\n`);
204
+ shellConfigured.push(rc);
205
+ messages.push(`✓ Added PATH export to ${path.basename(rc)}`);
206
+ }
207
+ } catch {
208
+ // Ignore
209
+ }
210
+ }
211
+ }
212
+ }
213
+
214
+ // 4. System Dependencies Check
215
+ const systemDeps = [
216
+ { name: 'bun', ...this.checkBinary('bun') },
217
+ { name: 'node', ...this.checkBinary('node') },
218
+ { name: 'python3', ...this.checkBinary('python3') },
219
+ { name: 'git', ...this.checkBinary('git') },
220
+ { name: 'brew', ...this.checkBinary('brew') }
221
+ ];
222
+
223
+ return {
224
+ success: installedTargets.length > 0 || binLinked.length > 0,
225
+ installedTargets,
226
+ skippedTargets,
227
+ binLinked,
228
+ shellConfigured,
229
+ systemDeps,
230
+ messages
231
+ };
232
+ }
233
+
234
+ public generateShellCompletion(shell: 'zsh' | 'bash' | 'fish'): string {
235
+ const commands = [
236
+ 'engine', 'autopilot', 'run', 'skills', 'guard', 'eval', 'traces',
237
+ 'hooks', 'integrations', 'fable', 'toon', 'init', 'validate', 'status',
238
+ 'test', 'update', 'install', 'refresh', 'doctor', 'health', 'deps',
239
+ 'notify', 'announcements', 'version', 'help'
240
+ ].join(' ');
241
+
242
+ if (shell === 'zsh') {
243
+ return `#compdef agentic-workflow
244
+ _agentic_workflow() {
245
+ local -a commands
246
+ commands=(${commands})
247
+ _describe 'command' commands
248
+ }
249
+ compdef _agentic_workflow agentic-workflow
250
+ `;
251
+ } else if (shell === 'fish') {
252
+ return `complete -c agentic-workflow -f -a "${commands}"`;
253
+ } else {
254
+ return `_agentic_workflow() {
255
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
256
+ COMPREPLY=( $(compgen -W "${commands}" -- "$cur") )
257
+ }
258
+ complete -F _agentic_workflow agentic-workflow
259
+ `;
260
+ }
261
+ }
262
+ }