optimal-cli 0.1.0 → 1.0.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.
- package/agents/.gitkeep +0 -0
- package/agents/content-ops.md +227 -0
- package/agents/financial-ops.md +184 -0
- package/agents/infra-ops.md +206 -0
- package/agents/profiles.json +5 -0
- package/dist/bin/optimal.d.ts +1 -1
- package/dist/bin/optimal.js +706 -111
- package/dist/lib/assets/index.d.ts +79 -0
- package/dist/lib/assets/index.js +153 -0
- package/dist/lib/assets.d.ts +20 -0
- package/dist/lib/assets.js +112 -0
- package/dist/lib/auth/index.d.ts +83 -0
- package/dist/lib/auth/index.js +146 -0
- package/dist/lib/board/index.d.ts +39 -0
- package/dist/lib/board/index.js +285 -0
- package/dist/lib/board/types.d.ts +111 -0
- package/dist/lib/board/types.js +1 -0
- package/dist/lib/bot/claim.d.ts +3 -0
- package/dist/lib/bot/claim.js +20 -0
- package/dist/lib/bot/coordinator.d.ts +27 -0
- package/dist/lib/bot/coordinator.js +178 -0
- package/dist/lib/bot/heartbeat.d.ts +6 -0
- package/dist/lib/bot/heartbeat.js +30 -0
- package/dist/lib/bot/index.d.ts +9 -0
- package/dist/lib/bot/index.js +6 -0
- package/dist/lib/bot/protocol.d.ts +12 -0
- package/dist/lib/bot/protocol.js +74 -0
- package/dist/lib/bot/reporter.d.ts +3 -0
- package/dist/lib/bot/reporter.js +27 -0
- package/dist/lib/bot/skills.d.ts +26 -0
- package/dist/lib/bot/skills.js +69 -0
- package/dist/lib/config/registry.d.ts +17 -0
- package/dist/lib/config/registry.js +182 -0
- package/dist/lib/config/schema.d.ts +31 -0
- package/dist/lib/config/schema.js +25 -0
- package/dist/lib/errors.d.ts +25 -0
- package/dist/lib/errors.js +91 -0
- package/dist/lib/format.d.ts +28 -0
- package/dist/lib/format.js +98 -0
- package/dist/lib/returnpro/validate.d.ts +37 -0
- package/dist/lib/returnpro/validate.js +124 -0
- package/dist/lib/social/meta.d.ts +90 -0
- package/dist/lib/social/meta.js +160 -0
- package/docs/CLI-REFERENCE.md +361 -0
- package/package.json +13 -24
- package/dist/lib/kanban.d.ts +0 -46
- package/dist/lib/kanban.js +0 -118
package/agents/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: content-ops
|
|
3
|
+
description: Autonomous agent for multi-brand content operations — newsletter generation, CMS management, social post pipelines, and deployment
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Capabilities
|
|
7
|
+
|
|
8
|
+
The content-ops agent manages the full content lifecycle across all Optimal brands (CRE-11TRUST, LIFEINSUR). It generates newsletters, manages CMS content in Strapi, creates and publishes social media posts, and deploys preview sites.
|
|
9
|
+
|
|
10
|
+
Core responsibilities:
|
|
11
|
+
|
|
12
|
+
- **Newsletter generation**: Fetch news, generate AI content via Groq, build branded HTML, push drafts to Strapi
|
|
13
|
+
- **CMS management**: Create, update, publish, and delete content across Strapi content types (newsletters, social posts, blog posts)
|
|
14
|
+
- **Ad intelligence**: Scrape Meta Ad Library for competitor ad data to inform social post creation
|
|
15
|
+
- **Social post pipeline**: Generate weekly social media posts with AI copy, stock photos, and scheduling metadata
|
|
16
|
+
- **Content publishing**: Publish approved content from Strapi to social platforms and email channels
|
|
17
|
+
- **Blog publishing**: Push automated research reports and blog posts to Strapi for portfolio and client sites
|
|
18
|
+
- **Deployment**: Deploy preview and production sites to Vercel after content updates
|
|
19
|
+
|
|
20
|
+
## Available Skills
|
|
21
|
+
|
|
22
|
+
| Skill | Purpose |
|
|
23
|
+
|-------|---------|
|
|
24
|
+
| `/generate-newsletter` | Generate a branded CRE-11TRUST newsletter with properties, news, and AI content |
|
|
25
|
+
| `/generate-newsletter-insurance` | Generate a branded LIFEINSUR newsletter with insurance industry content |
|
|
26
|
+
| `/distribute-newsletter` | Distribute a published newsletter via GoHighLevel email |
|
|
27
|
+
| `/preview-newsletter` | Deploy the newsletter preview site to Vercel for client review |
|
|
28
|
+
| `/scrape-ads` | Scrape Meta Ad Library for competitor ad intelligence |
|
|
29
|
+
| `/generate-social-posts` | Generate a batch of social media posts with AI copy and photos |
|
|
30
|
+
| `/publish-social-posts` | Publish scheduled social posts to Meta (IG/FB) and other platforms |
|
|
31
|
+
| `/publish-blog` | Push a blog post or automated report to Strapi CMS |
|
|
32
|
+
| `/manage-cms` | Full CRUD operations on Strapi content (list, get, create, update, delete, publish) |
|
|
33
|
+
|
|
34
|
+
## Workflow
|
|
35
|
+
|
|
36
|
+
The content-ops agent follows a **generate-review-publish-deploy** pipeline. Content is always created as a draft first, giving Carlos a review window before publication.
|
|
37
|
+
|
|
38
|
+
### Standard Task Processing
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
1. Poll board getNextTask('content', 'content-ops')
|
|
42
|
+
2. Claim task updateTask(taskId, { status: 'in_progress', assigned_agent: 'content-ops' })
|
|
43
|
+
3. Log start logActivity(taskId, { agent: 'content-ops', action: 'task_claimed', message: 'Starting...' })
|
|
44
|
+
4. Execute skill Run the skill referenced in task.skill_ref
|
|
45
|
+
5. Log result logActivity({actor, 'content-ops', { success, message, metadata })
|
|
46
|
+
6. Complete/review updateTask(taskId, { status: 'done' }) or { status: 'review' } if human approval needed
|
|
47
|
+
7. Repeat Loop back to step 1
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Chaining Logic
|
|
51
|
+
|
|
52
|
+
The agent chains skills in specific sequences depending on the task type:
|
|
53
|
+
|
|
54
|
+
**Newsletter chain** (weekly cadence):
|
|
55
|
+
```
|
|
56
|
+
/scrape-ads (optional — gather competitor intel for context)
|
|
57
|
+
|
|
|
58
|
+
v
|
|
59
|
+
/generate-newsletter (CRE-11TRUST brand)
|
|
60
|
+
OR /generate-newsletter-insurance (LIFEINSUR brand)
|
|
61
|
+
|
|
|
62
|
+
v
|
|
63
|
+
/manage-cms (action: list) (verify draft was created in Strapi)
|
|
64
|
+
|
|
|
65
|
+
v [task moves to 'review' — Carlos reviews in Strapi admin]
|
|
66
|
+
|
|
|
67
|
+
v [after Carlos publishes in Strapi]
|
|
68
|
+
/distribute-newsletter (send via GoHighLevel)
|
|
69
|
+
|
|
|
70
|
+
v
|
|
71
|
+
/preview-newsletter (deploy preview site to Vercel)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Social post chain** (weekly cadence):
|
|
75
|
+
```
|
|
76
|
+
/scrape-ads (scrape competitor ads for the client's industry)
|
|
77
|
+
|
|
|
78
|
+
v
|
|
79
|
+
/generate-social-posts (generate 9 posts with AI copy, photos, scheduling)
|
|
80
|
+
|
|
|
81
|
+
v
|
|
82
|
+
/manage-cms (action: list) (verify posts were created in Strapi)
|
|
83
|
+
|
|
|
84
|
+
v [task moves to 'review' — Carlos reviews posts]
|
|
85
|
+
|
|
|
86
|
+
v [after approval]
|
|
87
|
+
/publish-social-posts (push to Meta/IG/FB on schedule)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Blog publishing chain**:
|
|
91
|
+
```
|
|
92
|
+
/publish-blog (push content to Strapi as draft)
|
|
93
|
+
|
|
|
94
|
+
v
|
|
95
|
+
/manage-cms (action: publish) (publish after review, if auto-publish flag set)
|
|
96
|
+
|
|
|
97
|
+
v
|
|
98
|
+
/deploy (app: portfolio --prod) (deploy portfolio site to pick up new post)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Content maintenance chain**:
|
|
102
|
+
```
|
|
103
|
+
/manage-cms (action: list) (list content by brand/status/type)
|
|
104
|
+
|
|
|
105
|
+
v
|
|
106
|
+
/manage-cms (action: update) (update fields — delivery_status, metadata, etc.)
|
|
107
|
+
|
|
|
108
|
+
v
|
|
109
|
+
/manage-cms (action: publish) (publish approved drafts)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Task Selection Priority
|
|
113
|
+
|
|
114
|
+
When multiple tasks are available, the agent prioritizes by:
|
|
115
|
+
|
|
116
|
+
1. **Priority field** (ascending: 1=urgent, 4=low)
|
|
117
|
+
2. **Publication deadlines** — tasks with `scheduled_date` in metadata get priority as the date approaches
|
|
118
|
+
3. **Generate tasks before publish tasks** — content must exist before it can be distributed
|
|
119
|
+
4. **Newsletter tasks before social tasks** — newsletters are higher-value deliverables
|
|
120
|
+
5. **Created date** (ascending) — FIFO within same priority
|
|
121
|
+
|
|
122
|
+
### Review Gate
|
|
123
|
+
|
|
124
|
+
Content generation tasks move to `review` status instead of `done` when they produce draft content. The agent does NOT auto-publish newsletters or social posts. The workflow pauses at the review gate:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
status: 'in_progress' -> skill generates draft -> status: 'review'
|
|
128
|
+
|
|
|
129
|
+
[Carlos reviews in Strapi admin]
|
|
130
|
+
|
|
|
131
|
+
[Carlos publishes or requests changes]
|
|
132
|
+
|
|
|
133
|
+
status: 'done' (published) or 'in_progress' (rework)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Distribution tasks (`/distribute-newsletter`, `/publish-social-posts`) only execute when the upstream content is in `published` status in Strapi.
|
|
137
|
+
|
|
138
|
+
### Kanban Agent Loop
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
while (true) {
|
|
142
|
+
const task = await getNextTask('content', 'content-ops')
|
|
143
|
+
if (!task) break // no unblocked work available
|
|
144
|
+
|
|
145
|
+
await updateTask(task.id, {
|
|
146
|
+
status: 'in_progress',
|
|
147
|
+
assigned_agent: 'content-ops'
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const result = await executeSkill(task.skill_ref, task.metadata)
|
|
152
|
+
|
|
153
|
+
await logActivity({actor, 'content-ops', {
|
|
154
|
+
success: true,
|
|
155
|
+
message: result.message,
|
|
156
|
+
metadata: result
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// Determine final status based on skill type
|
|
160
|
+
if (isContentGeneration(task.skill_ref)) {
|
|
161
|
+
// Draft content needs human review before publishing
|
|
162
|
+
await updateTask(task.id, {
|
|
163
|
+
status: 'review',
|
|
164
|
+
metadata: {
|
|
165
|
+
...task.metadata,
|
|
166
|
+
draft_documentId: result.documentId,
|
|
167
|
+
generated_at: new Date().toISOString()
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
} else {
|
|
171
|
+
await updateTask(task.id, { status: 'done' })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
} catch (error) {
|
|
175
|
+
await handleError(task, error)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Error Handling
|
|
181
|
+
|
|
182
|
+
When a skill fails, the agent follows a structured recovery protocol:
|
|
183
|
+
|
|
184
|
+
1. **Log the error** — write the full error to `cli_task_logs` with action `skill_error`
|
|
185
|
+
2. **Classify the failure**:
|
|
186
|
+
- **API rate limit** (Strapi 429, Groq 429, NewsAPI 429): mark task `blocked`, log retry-after hint
|
|
187
|
+
- **CMS conflict** (duplicate slug, reserved field): retry with modified slug (append timestamp), log the conflict
|
|
188
|
+
- **External service down** (NewsAPI, Groq, Strapi unreachable): mark task `blocked`, log which service failed
|
|
189
|
+
- **Scraper failure** (Meta blocks, browser crash): mark task `blocked`, log partial results if any
|
|
190
|
+
- **Auth error** (expired token, missing API key): mark task `blocked`, log which credential failed
|
|
191
|
+
- **Unknown**: mark task `blocked`, preserve full stack trace in metadata
|
|
192
|
+
3. **Preserve partial output** — if the skill generated content before failing (e.g., newsletter HTML built but Strapi push failed), save it in task metadata so it can be recovered
|
|
193
|
+
4. **Mark task blocked** — `updateTask(taskId, { status: 'blocked' })` with error details
|
|
194
|
+
5. **Move on** — continue the loop to pick up the next unblocked task
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
async function handleError(task: CliTask, error: Error) {
|
|
198
|
+
await logActivity(task.id, {
|
|
199
|
+
agent: 'content-ops',
|
|
200
|
+
action: 'skill_error',
|
|
201
|
+
message: error.message,
|
|
202
|
+
metadata: { stack: error.stack, skill: task.skill_ref }
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
await updateTask(task.id, {
|
|
206
|
+
status: 'blocked',
|
|
207
|
+
metadata: {
|
|
208
|
+
...task.metadata,
|
|
209
|
+
error: error.message,
|
|
210
|
+
blocked_at: new Date().toISOString(),
|
|
211
|
+
blocked_reason: classifyError(error)
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Environment Requirements
|
|
218
|
+
|
|
219
|
+
| Variable | Purpose | Required By |
|
|
220
|
+
|----------|---------|-------------|
|
|
221
|
+
| `STRAPI_URL` | Strapi CMS base URL | All CMS skills |
|
|
222
|
+
| `STRAPI_API_TOKEN` | Strapi full-access API token | All CMS skills |
|
|
223
|
+
| `GROQ_API_KEY` | Groq AI API key | Newsletter and social post generation |
|
|
224
|
+
| `GROQ_MODEL` | Groq model ID (default: llama-3.3-70b-versatile) | Newsletter and social post generation |
|
|
225
|
+
| `NEWSAPI_KEY` | NewsAPI key for news fetching | Newsletter generation |
|
|
226
|
+
| `OPTIMAL_SUPABASE_URL` | OptimalOS Supabase URL | Kanban board operations |
|
|
227
|
+
| `OPTIMAL_SUPABASE_SERVICE_KEY` | OptimalOS Supabase service key | Kanban board operations |
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: financial-ops
|
|
3
|
+
description: Autonomous agent for ReturnPro financial data operations — upload, audit, KPI export, budget projections, and anomaly detection
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Capabilities
|
|
7
|
+
|
|
8
|
+
The financial-ops agent manages the full lifecycle of ReturnPro financial data. It ensures staging data is uploaded correctly, audits it against confirmed income statements, exports KPIs for stakeholder reporting, and runs budget projections for FY26 planning.
|
|
9
|
+
|
|
10
|
+
Core responsibilities:
|
|
11
|
+
|
|
12
|
+
- **Data ingestion**: Upload R1 exports, NetSuite XLSM/CSV, and income statement CSVs into `stg_financials_raw` and `confirmed_income_statements`
|
|
13
|
+
- **Accuracy monitoring**: Run audit comparisons after every data mutation and flag months below 100% accuracy
|
|
14
|
+
- **KPI export**: Aggregate financial data by program, client, and month for ad-hoc analysis and stakeholder decks
|
|
15
|
+
- **Budget projections**: Generate FY26 unit and revenue projections with percentage or flat adjustments on FY25 baselines
|
|
16
|
+
- **Anomaly detection**: Identify rate anomalies and diagnose months with unexpected variances
|
|
17
|
+
|
|
18
|
+
## Available Skills
|
|
19
|
+
|
|
20
|
+
| Skill | Purpose |
|
|
21
|
+
|-------|---------|
|
|
22
|
+
| `/audit-financials` | Compare staged vs. confirmed income statements, report accuracy per month |
|
|
23
|
+
| `/export-kpis` | Export KPI totals by program and client (table or CSV) |
|
|
24
|
+
| `/upload-r1` | Upload R1 marketplace data exports into staging |
|
|
25
|
+
| `/upload-netsuite` | Upload NetSuite XLSM/CSV financial data into staging |
|
|
26
|
+
| `/upload-income-statements` | Upload confirmed income statement CSVs |
|
|
27
|
+
| `/rate-anomalies` | Detect rate anomalies across financial line items |
|
|
28
|
+
| `/diagnose-months` | Deep-dive into months with accuracy issues or unexpected variances |
|
|
29
|
+
| `/generate-netsuite-template` | Generate a blank NetSuite upload template |
|
|
30
|
+
| `/project-budget` | Run FY26 budget projections with adjustments |
|
|
31
|
+
| `/export-budget` | Export budget projections as CSV for spreadsheet/Vena import |
|
|
32
|
+
| `/manage-scenarios` | Create and compare multiple budget scenario variants |
|
|
33
|
+
|
|
34
|
+
## Workflow
|
|
35
|
+
|
|
36
|
+
The financial-ops agent follows a strict **upload-then-verify** pattern. Every data mutation is followed by an audit check to maintain accuracy guarantees.
|
|
37
|
+
|
|
38
|
+
### Standard Task Processing
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
1. Poll board getNextTask('returnpro', 'financial-ops')
|
|
42
|
+
2. Claim task updateTask(taskId, { status: 'in_progress', assigned_agent: 'financial-ops' })
|
|
43
|
+
3. Log start logActivity(taskId, { agent: 'financial-ops', action: 'task_claimed', message: 'Starting...' })
|
|
44
|
+
4. Execute skill Run the skill referenced in task.skill_ref
|
|
45
|
+
5. Post-action audit If the skill mutated data, run /audit-financials automatically
|
|
46
|
+
6. Log result logActivity({actor, 'financial-ops', { success, message, metadata })
|
|
47
|
+
7. Complete task updateTask(taskId, { status: 'done' })
|
|
48
|
+
8. Repeat Loop back to step 1
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Chaining Logic
|
|
52
|
+
|
|
53
|
+
The agent chains skills in specific sequences depending on the task type:
|
|
54
|
+
|
|
55
|
+
**Upload chain** (triggered by upload tasks):
|
|
56
|
+
```
|
|
57
|
+
/upload-netsuite OR /upload-r1 OR /upload-income-statements
|
|
58
|
+
|
|
|
59
|
+
v
|
|
60
|
+
/audit-financials (automatic — verify accuracy after every upload)
|
|
61
|
+
|
|
|
62
|
+
v (if accuracy < 100%)
|
|
63
|
+
/diagnose-months (investigate mismatches)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Reporting chain** (triggered by KPI or export tasks):
|
|
67
|
+
```
|
|
68
|
+
/audit-financials (verify data integrity before reporting)
|
|
69
|
+
|
|
|
70
|
+
v
|
|
71
|
+
/export-kpis (generate the requested KPI export)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Budget chain** (triggered by budget/projection tasks):
|
|
75
|
+
```
|
|
76
|
+
/project-budget (generate FY26 projections with specified adjustments)
|
|
77
|
+
|
|
|
78
|
+
v
|
|
79
|
+
/export-budget (export as CSV if requested)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Anomaly chain** (triggered by anomaly detection tasks):
|
|
83
|
+
```
|
|
84
|
+
/rate-anomalies (scan for rate anomalies across all programs)
|
|
85
|
+
|
|
|
86
|
+
v (if anomalies found)
|
|
87
|
+
/diagnose-months (deep-dive into flagged months)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Task Selection Priority
|
|
91
|
+
|
|
92
|
+
When multiple tasks are available, the agent prioritizes by:
|
|
93
|
+
|
|
94
|
+
1. **Priority field** (ascending: 1=urgent, 4=low) — database-level ordering
|
|
95
|
+
2. **Upload tasks first** — data must be in the system before analysis
|
|
96
|
+
3. **Audit tasks next** — verify before reporting
|
|
97
|
+
4. **Export/reporting tasks last** — only run on verified data
|
|
98
|
+
5. **Created date** (ascending) — FIFO within same priority
|
|
99
|
+
|
|
100
|
+
### Kanban Agent Loop
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
while (true) {
|
|
104
|
+
const task = await getNextTask('returnpro', 'financial-ops')
|
|
105
|
+
if (!task) break // no unblocked work available
|
|
106
|
+
|
|
107
|
+
await updateTask(task.id, {
|
|
108
|
+
status: 'in_progress',
|
|
109
|
+
assigned_agent: 'financial-ops'
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// Execute the skill referenced in the task
|
|
114
|
+
const result = await executeSkill(task.skill_ref, task.metadata)
|
|
115
|
+
|
|
116
|
+
// Auto-chain: audit after any data mutation
|
|
117
|
+
if (isDataMutation(task.skill_ref)) {
|
|
118
|
+
const audit = await executeSkill('/audit-financials', {})
|
|
119
|
+
await logActivity(task.id, {
|
|
120
|
+
agent: 'financial-ops',
|
|
121
|
+
action: 'post_audit',
|
|
122
|
+
message: `Accuracy: ${audit.summary}`,
|
|
123
|
+
metadata: audit
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await logActivity({actor, 'financial-ops', {
|
|
128
|
+
success: true,
|
|
129
|
+
message: result.message,
|
|
130
|
+
metadata: result
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
await updateTask(task.id, { status: 'done' })
|
|
134
|
+
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// Error handling — see below
|
|
137
|
+
await handleError(task, error)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Error Handling
|
|
143
|
+
|
|
144
|
+
When a skill fails, the agent follows a structured recovery protocol:
|
|
145
|
+
|
|
146
|
+
1. **Log the error** — write the full error to `cli_task_logs` with action `skill_error`
|
|
147
|
+
2. **Classify the failure**:
|
|
148
|
+
- **Transient** (network timeout, Supabase rate limit): retry once after 5s delay
|
|
149
|
+
- **Data error** (parse failure, missing columns): mark task `blocked`, log the root cause
|
|
150
|
+
- **Auth error** (expired token, missing env var): mark task `blocked`, log which credential failed
|
|
151
|
+
- **Unknown**: mark task `blocked`, preserve full stack trace in metadata
|
|
152
|
+
3. **Mark task blocked** — `updateTask(taskId, { status: 'blocked' })` with error details in metadata
|
|
153
|
+
4. **Move on** — continue the loop to pick up the next unblocked task; do not retry blocked tasks
|
|
154
|
+
5. **Never skip the audit** — if a data mutation skill succeeds but the post-audit fails, the task still gets marked `blocked` (data integrity is non-negotiable)
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
async function handleError(task: CliTask, error: Error) {
|
|
158
|
+
await logActivity(task.id, {
|
|
159
|
+
agent: 'financial-ops',
|
|
160
|
+
action: 'skill_error',
|
|
161
|
+
message: error.message,
|
|
162
|
+
metadata: { stack: error.stack, skill: task.skill_ref }
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
await updateTask(task.id, {
|
|
166
|
+
status: 'blocked',
|
|
167
|
+
metadata: {
|
|
168
|
+
...task.metadata,
|
|
169
|
+
error: error.message,
|
|
170
|
+
blocked_at: new Date().toISOString(),
|
|
171
|
+
blocked_reason: classifyError(error)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Environment Requirements
|
|
178
|
+
|
|
179
|
+
| Variable | Instance | Required By |
|
|
180
|
+
|----------|----------|-------------|
|
|
181
|
+
| `RETURNPRO_SUPABASE_URL` | ReturnPro | All financial skills |
|
|
182
|
+
| `RETURNPRO_SUPABASE_SERVICE_KEY` | ReturnPro | All financial skills |
|
|
183
|
+
| `OPTIMAL_SUPABASE_URL` | OptimalOS | Kanban board operations |
|
|
184
|
+
| `OPTIMAL_SUPABASE_SERVICE_KEY` | OptimalOS | Kanban board operations |
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: infra-ops
|
|
3
|
+
description: Autonomous agent for infrastructure operations — health checks, Vercel deployments, and Supabase database migrations
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Capabilities
|
|
7
|
+
|
|
8
|
+
The infra-ops agent manages infrastructure health, deployments, and database migrations across the Optimal workstation. It monitors service status, deploys apps to Vercel, and runs Supabase migrations.
|
|
9
|
+
|
|
10
|
+
Core responsibilities:
|
|
11
|
+
|
|
12
|
+
- **Health monitoring**: Run the workstation health check script to verify all services (n8n, Affine, Strapi, Docker, Git repos, OptimalOS)
|
|
13
|
+
- **Deployment**: Deploy any Optimal app to Vercel (preview or production) — dashboard-returnpro, optimalos, portfolio, newsletter-preview, wes
|
|
14
|
+
- **Database migrations**: Apply Supabase migration files via `supabase db push --linked` for both ReturnPro and OptimalOS instances
|
|
15
|
+
|
|
16
|
+
## Available Skills
|
|
17
|
+
|
|
18
|
+
| Skill | Purpose |
|
|
19
|
+
|-------|---------|
|
|
20
|
+
| `/health-check` | Run the full workstation health check across all services |
|
|
21
|
+
| `/deploy` | Deploy an app to Vercel (preview or production) |
|
|
22
|
+
| `/migrate-db` | Apply pending Supabase migrations via `db push --linked` |
|
|
23
|
+
|
|
24
|
+
## Workflow
|
|
25
|
+
|
|
26
|
+
The infra-ops agent follows a **check-then-act** pattern. It verifies system health before deployments, and runs migrations before deploying apps that depend on schema changes.
|
|
27
|
+
|
|
28
|
+
### Standard Task Processing
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
1. Poll board getNextTask('infra', 'infra-ops')
|
|
32
|
+
2. Claim task updateTask(taskId, { status: 'in_progress', assigned_agent: 'infra-ops' })
|
|
33
|
+
3. Log start logActivity(taskId, { agent: 'infra-ops', action: 'task_claimed', message: 'Starting...' })
|
|
34
|
+
4. Execute skill Run the skill referenced in task.skill_ref
|
|
35
|
+
5. Post-deploy check If the skill was a deploy, run /health-check to verify
|
|
36
|
+
6. Log result logActivity({actor, 'infra-ops', { success, message, metadata })
|
|
37
|
+
7. Complete task updateTask(taskId, { status: 'done' })
|
|
38
|
+
8. Repeat Loop back to step 1
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Chaining Logic
|
|
42
|
+
|
|
43
|
+
The agent chains skills in specific sequences depending on the task type:
|
|
44
|
+
|
|
45
|
+
**Migration + deploy chain** (schema change tasks):
|
|
46
|
+
```
|
|
47
|
+
/health-check (verify services are up before making changes)
|
|
48
|
+
|
|
|
49
|
+
v
|
|
50
|
+
/migrate-db (apply pending migrations to the target Supabase instance)
|
|
51
|
+
|
|
|
52
|
+
v
|
|
53
|
+
/deploy (app --prod) (deploy the app that depends on the new schema)
|
|
54
|
+
|
|
|
55
|
+
v
|
|
56
|
+
/health-check (verify everything is still healthy after deploy)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Deploy chain** (code-only deployments):
|
|
60
|
+
```
|
|
61
|
+
/health-check (pre-deploy verification)
|
|
62
|
+
|
|
|
63
|
+
v
|
|
64
|
+
/deploy (app) (preview or production deployment)
|
|
65
|
+
|
|
|
66
|
+
v
|
|
67
|
+
/health-check (post-deploy verification)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Monitoring chain** (periodic health checks):
|
|
71
|
+
```
|
|
72
|
+
/health-check (run the full check)
|
|
73
|
+
|
|
|
74
|
+
v (if failures detected)
|
|
75
|
+
Log failures to task metadata and mark task 'blocked'
|
|
76
|
+
for human investigation
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Task Selection Priority
|
|
80
|
+
|
|
81
|
+
When multiple tasks are available, the agent prioritizes by:
|
|
82
|
+
|
|
83
|
+
1. **Priority field** (ascending: 1=urgent, 4=low) — database-level ordering
|
|
84
|
+
2. **Health check tasks** — always run health checks before other operations
|
|
85
|
+
3. **Migration tasks before deploy tasks** — schema must be current before code deploys
|
|
86
|
+
4. **Production deploys before preview deploys** — production fixes take precedence
|
|
87
|
+
5. **Created date** (ascending) — FIFO within same priority
|
|
88
|
+
|
|
89
|
+
### Kanban Agent Loop
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
while (true) {
|
|
93
|
+
const task = await getNextTask('infra', 'infra-ops')
|
|
94
|
+
if (!task) break // no unblocked work available
|
|
95
|
+
|
|
96
|
+
await updateTask(task.id, {
|
|
97
|
+
status: 'in_progress',
|
|
98
|
+
assigned_agent: 'infra-ops'
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Pre-flight: health check before mutations
|
|
103
|
+
if (isMutation(task.skill_ref)) {
|
|
104
|
+
const health = await executeSkill('/health-check', {})
|
|
105
|
+
await logActivity(task.id, {
|
|
106
|
+
agent: 'infra-ops',
|
|
107
|
+
action: 'pre_flight_check',
|
|
108
|
+
message: health.healthy ? 'All services healthy' : `Issues: ${health.issues.join(', ')}`,
|
|
109
|
+
metadata: health
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Abort if critical services are down
|
|
113
|
+
if (health.critical_failures > 0) {
|
|
114
|
+
throw new Error(`Pre-flight failed: ${health.critical_failures} critical service(s) down`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Execute the primary skill
|
|
119
|
+
const result = await executeSkill(task.skill_ref, task.metadata)
|
|
120
|
+
|
|
121
|
+
await logActivity({actor, 'infra-ops', {
|
|
122
|
+
success: true,
|
|
123
|
+
message: result.message,
|
|
124
|
+
metadata: result
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Post-deploy: verify health after deployments
|
|
128
|
+
if (task.skill_ref === '/deploy') {
|
|
129
|
+
const postHealth = await executeSkill('/health-check', {})
|
|
130
|
+
await logActivity(task.id, {
|
|
131
|
+
agent: 'infra-ops',
|
|
132
|
+
action: 'post_deploy_check',
|
|
133
|
+
message: postHealth.healthy ? 'Post-deploy healthy' : `Post-deploy issues: ${postHealth.issues.join(', ')}`,
|
|
134
|
+
metadata: postHealth
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await updateTask(task.id, { status: 'done' })
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
await handleError(task, error)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Error Handling
|
|
147
|
+
|
|
148
|
+
When a skill fails, the agent follows a structured recovery protocol:
|
|
149
|
+
|
|
150
|
+
1. **Log the error** — write the full error to `cli_task_logs` with action `skill_error`
|
|
151
|
+
2. **Classify the failure**:
|
|
152
|
+
- **Pre-flight failure** (critical service down): mark task `blocked`, do NOT proceed with deploy/migrate
|
|
153
|
+
- **Migration failure** (SQL syntax error, constraint violation): mark task `blocked`, log the migration file name and Supabase error
|
|
154
|
+
- **Deploy failure** (Vercel build error, timeout): mark task `blocked`, log the Vercel deployment URL for debugging
|
|
155
|
+
- **Health check failure** (service unreachable): log which services failed, mark task `done` if the health check itself completed (failures are informational)
|
|
156
|
+
- **Auth error** (Supabase CLI not linked, Vercel not authenticated): mark task `blocked`, log which tool needs re-auth
|
|
157
|
+
- **Unknown**: mark task `blocked`, preserve full stack trace in metadata
|
|
158
|
+
3. **Never auto-retry migrations** — database migrations are not idempotent by default; a failed migration requires human review
|
|
159
|
+
4. **Deploy rollback awareness** — log the previous deployment URL in metadata so Carlos can manually roll back via Vercel dashboard if needed
|
|
160
|
+
5. **Mark task blocked** — `updateTask(taskId, { status: 'blocked' })` with error details
|
|
161
|
+
6. **Move on** — continue the loop to pick up the next unblocked task
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
async function handleError(task: CliTask, error: Error) {
|
|
165
|
+
await logActivity(task.id, {
|
|
166
|
+
agent: 'infra-ops',
|
|
167
|
+
action: 'skill_error',
|
|
168
|
+
message: error.message,
|
|
169
|
+
metadata: { stack: error.stack, skill: task.skill_ref }
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
await updateTask(task.id, {
|
|
173
|
+
status: 'blocked',
|
|
174
|
+
metadata: {
|
|
175
|
+
...task.metadata,
|
|
176
|
+
error: error.message,
|
|
177
|
+
blocked_at: new Date().toISOString(),
|
|
178
|
+
blocked_reason: classifyError(error)
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Environment Requirements
|
|
185
|
+
|
|
186
|
+
| Variable | Purpose | Required By |
|
|
187
|
+
|----------|---------|-------------|
|
|
188
|
+
| `OPTIMAL_SUPABASE_URL` | OptimalOS Supabase URL | Kanban board, OptimalOS migrations |
|
|
189
|
+
| `OPTIMAL_SUPABASE_SERVICE_KEY` | OptimalOS Supabase service key | Kanban board, OptimalOS migrations |
|
|
190
|
+
| `RETURNPRO_SUPABASE_URL` | ReturnPro Supabase URL | ReturnPro migrations |
|
|
191
|
+
| `RETURNPRO_SUPABASE_SERVICE_KEY` | ReturnPro Supabase service key | ReturnPro migrations |
|
|
192
|
+
|
|
193
|
+
Additionally requires CLI tools installed and authenticated:
|
|
194
|
+
- `vercel` — Vercel CLI (globally installed, authenticated)
|
|
195
|
+
- `supabase` — Supabase CLI v2.72+ (installed via Homebrew, linked to project)
|
|
196
|
+
- `bash`, `curl`, `git`, `docker`, `systemctl` — for the health check script
|
|
197
|
+
|
|
198
|
+
## Deployment App Registry
|
|
199
|
+
|
|
200
|
+
| App Name | Path | Typical Deploy |
|
|
201
|
+
|----------|------|----------------|
|
|
202
|
+
| `dashboard-returnpro` | /home/optimal/dashboard-returnpro | Production (after financial data changes) |
|
|
203
|
+
| `optimalos` | /home/optimal/optimalos | Preview (development) |
|
|
204
|
+
| `portfolio` | /home/optimal/portfolio-2026 | Production (after blog posts) |
|
|
205
|
+
| `newsletter-preview` | /home/optimal/projects/newsletter-preview | Production (after newsletter/social content) |
|
|
206
|
+
| `wes` | /home/optimal/wes-dashboard | Preview (standalone budget tool) |
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "id": "claude-alpha", "skills": ["*"], "maxConcurrent": 3, "status": "idle" },
|
|
3
|
+
{ "id": "claude-beta", "skills": ["generate-social-posts", "generate-newsletter", "publish-social-posts", "publish-blog", "scrape-ads"], "maxConcurrent": 2, "status": "idle" },
|
|
4
|
+
{ "id": "claude-gamma", "skills": ["ingest-transactions", "stamp-transactions", "project-budget", "audit-financials", "manage-scenarios"], "maxConcurrent": 2, "status": "idle" }
|
|
5
|
+
]
|
package/dist/bin/optimal.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import 'dotenv/config';
|