optimal-cli 1.0.0 → 1.1.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 (85) hide show
  1. package/.claude-plugin/marketplace.json +18 -0
  2. package/.claude-plugin/plugin.json +10 -0
  3. package/.env.example +17 -0
  4. package/CLAUDE.md +67 -0
  5. package/COMMANDS.md +264 -0
  6. package/PUBLISH.md +70 -0
  7. package/agents/content-ops.md +2 -2
  8. package/agents/financial-ops.md +2 -2
  9. package/agents/infra-ops.md +2 -2
  10. package/apps/.gitkeep +0 -0
  11. package/bin/optimal.ts +278 -591
  12. package/docs/MIGRATION_NEEDED.md +37 -0
  13. package/docs/plans/.gitkeep +0 -0
  14. package/docs/plans/optimal-cli-config-registry-v1.md +71 -0
  15. package/hooks/.gitkeep +0 -0
  16. package/lib/config/registry.ts +5 -4
  17. package/lib/kanban-obsidian.ts +232 -0
  18. package/lib/kanban-sync.ts +258 -0
  19. package/lib/kanban.ts +239 -0
  20. package/lib/obsidian-tasks.ts +231 -0
  21. package/package.json +5 -19
  22. package/pnpm-workspace.yaml +3 -0
  23. package/scripts/check-table.ts +24 -0
  24. package/scripts/create-tables.ts +94 -0
  25. package/scripts/migrate-kanban.sh +28 -0
  26. package/scripts/migrate-v2.ts +78 -0
  27. package/scripts/migrate.ts +79 -0
  28. package/scripts/run-migration.ts +59 -0
  29. package/scripts/seed-board.ts +203 -0
  30. package/scripts/test-kanban.ts +21 -0
  31. package/skills/audit-financials/SKILL.md +33 -0
  32. package/skills/board-create/SKILL.md +28 -0
  33. package/skills/board-update/SKILL.md +27 -0
  34. package/skills/board-view/SKILL.md +27 -0
  35. package/skills/delete-batch/SKILL.md +77 -0
  36. package/skills/deploy/SKILL.md +40 -0
  37. package/skills/diagnose-months/SKILL.md +68 -0
  38. package/skills/distribute-newsletter/SKILL.md +58 -0
  39. package/skills/export-budget/SKILL.md +44 -0
  40. package/skills/export-kpis/SKILL.md +52 -0
  41. package/skills/generate-netsuite-template/SKILL.md +51 -0
  42. package/skills/generate-newsletter/SKILL.md +53 -0
  43. package/skills/generate-newsletter-insurance/SKILL.md +59 -0
  44. package/skills/generate-social-posts/SKILL.md +67 -0
  45. package/skills/health-check/SKILL.md +42 -0
  46. package/skills/ingest-transactions/SKILL.md +51 -0
  47. package/skills/manage-cms/SKILL.md +50 -0
  48. package/skills/manage-scenarios/SKILL.md +83 -0
  49. package/skills/migrate-db/SKILL.md +79 -0
  50. package/skills/preview-newsletter/SKILL.md +50 -0
  51. package/skills/project-budget/SKILL.md +60 -0
  52. package/skills/publish-blog/SKILL.md +70 -0
  53. package/skills/publish-social-posts/SKILL.md +70 -0
  54. package/skills/rate-anomalies/SKILL.md +62 -0
  55. package/skills/scrape-ads/SKILL.md +49 -0
  56. package/skills/stamp-transactions/SKILL.md +62 -0
  57. package/skills/upload-income-statements/SKILL.md +54 -0
  58. package/skills/upload-netsuite/SKILL.md +56 -0
  59. package/skills/upload-r1/SKILL.md +45 -0
  60. package/supabase/.temp/cli-latest +1 -0
  61. package/supabase/migrations/.gitkeep +0 -0
  62. package/supabase/migrations/20250305000001_create_agent_configs.sql +36 -0
  63. package/supabase/migrations/20260305111300_create_cli_config_registry.sql +22 -0
  64. package/supabase/migrations/20260306195000_create_kanban_tables.sql +97 -0
  65. package/tests/config-command-smoke.test.ts +395 -0
  66. package/tests/config-registry.test.ts +173 -0
  67. package/tsconfig.json +19 -0
  68. package/agents/profiles.json +0 -5
  69. package/docs/CLI-REFERENCE.md +0 -361
  70. package/lib/assets/index.ts +0 -225
  71. package/lib/assets.ts +0 -124
  72. package/lib/auth/index.ts +0 -189
  73. package/lib/board/index.ts +0 -309
  74. package/lib/board/types.ts +0 -124
  75. package/lib/bot/claim.ts +0 -43
  76. package/lib/bot/coordinator.ts +0 -254
  77. package/lib/bot/heartbeat.ts +0 -37
  78. package/lib/bot/index.ts +0 -9
  79. package/lib/bot/protocol.ts +0 -99
  80. package/lib/bot/reporter.ts +0 -42
  81. package/lib/bot/skills.ts +0 -81
  82. package/lib/errors.ts +0 -129
  83. package/lib/format.ts +0 -120
  84. package/lib/returnpro/validate.ts +0 -154
  85. package/lib/social/meta.ts +0 -228
@@ -0,0 +1,78 @@
1
+ import { createClient } from '@supabase/supabase-js'
2
+
3
+ const url = 'https://hbfalrpswysryltysonm.supabase.co'
4
+ const key = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhiZmFscnBzd3lzcnlsdHlzb25tIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MjIzMTEyMiwiZXhwIjoyMDU3ODA3MTIyfQ.oyzf_We-WCOsJ8xYs2_Q9wi8QSBr_1Ym_F_75o67kR0'
5
+
6
+ const supabase = createClient(url, key)
7
+
8
+ async function runMigration() {
9
+ console.log('🔧 Running migration: agent_configs table')
10
+ console.log(` URL: ${url}`)
11
+ console.log('')
12
+
13
+ // Test connection first
14
+ const { data: testData, error: testError } = await supabase
15
+ .from('agent_configs')
16
+ .select('count')
17
+ .limit(1)
18
+
19
+ if (testError?.code === '42P01') {
20
+ console.log(' Table does not exist. Creating...')
21
+ } else if (testError) {
22
+ console.log(' Error checking table:', testError.message)
23
+ } else {
24
+ console.log(' ✓ Table already exists')
25
+ return
26
+ }
27
+
28
+ // Create table using raw SQL
29
+ const sql = `
30
+ CREATE TABLE IF NOT EXISTS agent_configs (
31
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
32
+ agent_name TEXT NOT NULL UNIQUE,
33
+ config_json JSONB NOT NULL DEFAULT '{}',
34
+ version TEXT NOT NULL DEFAULT '0.0.0',
35
+ created_at TIMESTAMPTZ DEFAULT NOW(),
36
+ updated_at TIMESTAMPTZ DEFAULT NOW()
37
+ );
38
+ `
39
+
40
+ // Try using the SQL API directly
41
+ const response = await fetch(`${url}/rest/v1/`, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ 'Authorization': `Bearer ${key}`,
46
+ 'apikey': key,
47
+ 'Prefer': 'tx=rollback'
48
+ },
49
+ body: JSON.stringify({ query: sql })
50
+ })
51
+
52
+ console.log(' Response status:', response.status)
53
+
54
+ if (response.status === 404 || response.status === 400) {
55
+ console.log('')
56
+ console.log('⚠️ Cannot run migration via REST API.')
57
+ console.log('')
58
+ console.log('Please run this SQL in Supabase SQL Editor:')
59
+ console.log('')
60
+ console.log('━'.repeat(60))
61
+ console.log(sql)
62
+ console.log('━'.repeat(60))
63
+ console.log('')
64
+ console.log('Then run:')
65
+ console.log(' CREATE INDEX idx_agent_configs_name ON agent_configs(agent_name);')
66
+ console.log(' ALTER TABLE agent_configs ENABLE ROW LEVEL SECURITY;')
67
+ console.log(' CREATE POLICY "Service role full access" ON agent_configs FOR ALL TO service_role USING (true) WITH CHECK (true);')
68
+ process.exit(1)
69
+ }
70
+
71
+ const text = await response.text()
72
+ console.log(' Response:', text.slice(0, 200))
73
+ }
74
+
75
+ runMigration().catch(err => {
76
+ console.error('❌ Migration failed:', err)
77
+ process.exit(1)
78
+ })
@@ -0,0 +1,79 @@
1
+ import { createClient } from '@supabase/supabase-js'
2
+
3
+ const url = 'https://hbfalrpswysryltysonm.supabase.co'
4
+ const key = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhiZmFscnBzd3lzcnlsdHlzb25tIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MjIzMTEyMiwiZXhwIjoyMDU3ODA3MTIyfQ.oyzf_We-WCOsJ8xYs2_Q9wi8QSBr_1Ym_F_75o67kR0'
5
+
6
+ const supabase = createClient(url, key)
7
+
8
+ async function createTable() {
9
+ console.log('🔧 Creating agent_configs table...')
10
+
11
+ // Create table using raw SQL via RPC or direct query
12
+ const createTableSQL = `
13
+ CREATE TABLE IF NOT EXISTS agent_configs (
14
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
15
+ agent_name TEXT NOT NULL UNIQUE,
16
+ config_json JSONB NOT NULL DEFAULT '{}',
17
+ version TEXT NOT NULL DEFAULT '0.0.0',
18
+ created_at TIMESTAMPTZ DEFAULT NOW(),
19
+ updated_at TIMESTAMPTZ DEFAULT NOW()
20
+ );
21
+ `
22
+
23
+ // Try to create the table by inserting and letting it fail if exists
24
+ const { error: insertError } = await supabase
25
+ .from('agent_configs')
26
+ .insert({
27
+ agent_name: '__test__',
28
+ config_json: {},
29
+ version: '0.0.0'
30
+ })
31
+
32
+ if (insertError?.code === '42P01') {
33
+ console.log(' Table does not exist. Creating via SQL...')
34
+
35
+ // Use the SQL API
36
+ const response = await fetch(`${url}/rest/v1/rpc/exec_sql`, {
37
+ method: 'POST',
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ 'Authorization': `Bearer ${key}`,
41
+ 'apikey': key
42
+ },
43
+ body: JSON.stringify({ sql: createTableSQL })
44
+ })
45
+
46
+ if (!response.ok) {
47
+ const text = await response.text()
48
+ console.log(' RPC failed, trying alternative...')
49
+ console.log(' Error:', text.slice(0, 200))
50
+
51
+ // Alternative: create via pg_graphql or direct postgrest
52
+ console.log(' Please run this SQL in Supabase SQL Editor:')
53
+ console.log('')
54
+ console.log(createTableSQL)
55
+ console.log('')
56
+ console.log(' Then run:')
57
+ console.log(' CREATE INDEX idx_agent_configs_name ON agent_configs(agent_name);')
58
+ console.log(' ALTER TABLE agent_configs ENABLE ROW LEVEL SECURITY;')
59
+ process.exit(1)
60
+ }
61
+
62
+ console.log(' ✓ Table created')
63
+ } else if (insertError?.code === '23505') {
64
+ console.log(' ✓ Table already exists (unique constraint violation on test insert)')
65
+ // Clean up test row
66
+ await supabase.from('agent_configs').delete().eq('agent_name', '__test__')
67
+ } else if (insertError) {
68
+ console.log(' Error:', insertError.message)
69
+ } else {
70
+ console.log(' ✓ Table exists and is writable')
71
+ // Clean up test row
72
+ await supabase.from('agent_configs').delete().eq('agent_name', '__test__')
73
+ }
74
+
75
+ console.log('')
76
+ console.log('✅ Migration complete!')
77
+ }
78
+
79
+ createTable().catch(console.error)
@@ -0,0 +1,59 @@
1
+ import { createClient } from '@supabase/supabase-js'
2
+
3
+ const url = process.env.OPTIMAL_SUPABASE_URL!
4
+ const key = process.env.OPTIMAL_SUPABASE_SERVICE_KEY!
5
+
6
+ const supabase = createClient(url, key)
7
+
8
+ // Create the table via SQL query
9
+ const sql = `
10
+ create table if not exists public.cli_config_registry (
11
+ id uuid primary key default gen_random_uuid(),
12
+ owner text not null,
13
+ profile text not null default 'default',
14
+ config_version text not null,
15
+ payload jsonb not null,
16
+ payload_hash text not null,
17
+ source text not null default 'optimal-cli',
18
+ updated_by text,
19
+ updated_at timestamptz not null default now(),
20
+ created_at timestamptz not null default now(),
21
+ unique (owner, profile)
22
+ );
23
+
24
+ create index if not exists idx_cli_config_registry_owner_profile
25
+ on public.cli_config_registry (owner, profile);
26
+
27
+ create index if not exists idx_cli_config_registry_updated_at
28
+ on public.cli_config_registry (updated_at desc);
29
+ `
30
+
31
+ const { data, error } = await supabase.rpc('pg_execute', { sql_text: sql })
32
+
33
+ if (error) {
34
+ console.error('Migration failed:', error)
35
+ // Try alternative approach
36
+ console.log('Attempting alternative table creation...')
37
+
38
+ // Just verify table exists by querying
39
+ const { data: test, error: testErr } = await supabase
40
+ .from('cli_config_registry')
41
+ .select('count')
42
+ .limit(1)
43
+
44
+ if (testErr && testErr.code !== '42P01') {
45
+ console.error('Table verification failed:', testErr)
46
+ process.exit(1)
47
+ }
48
+
49
+ if (testErr?.code === '42P01') {
50
+ console.error('Table cli_config_registry does not exist and could not be created.')
51
+ console.error('Please run this SQL manually in Supabase SQL Editor:')
52
+ console.log(sql)
53
+ process.exit(1)
54
+ }
55
+
56
+ console.log('Table exists!')
57
+ } else {
58
+ console.log('Migration applied successfully')
59
+ }
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Seed the kanban board with the full implementation backlog.
4
+ * Idempotent — checks existing tasks by title before creating.
5
+ *
6
+ * Usage: npx tsx scripts/seed-board.ts
7
+ */
8
+ import 'dotenv/config'
9
+ import { createTask, getBoard } from '../lib/kanban.js'
10
+
11
+ const PROJECT_SLUG = 'optimal-cli-refactor'
12
+
13
+ interface SeedTask {
14
+ title: string
15
+ skill_ref?: string
16
+ priority: 1 | 2 | 3 | 4
17
+ labels: string[]
18
+ description?: string
19
+ }
20
+
21
+ const tasks: SeedTask[] = [
22
+ // --- Phase 2 follow-ups (ready — no blockers) ---
23
+ {
24
+ title: 'Implement upload-r1 lib function',
25
+ skill_ref: '/upload-r1',
26
+ priority: 2,
27
+ labels: ['returnpro', 'phase-2'],
28
+ description: 'Extract R1 marketplace data upload from dashboard-returnpro into lib/returnpro/upload-r1.ts',
29
+ },
30
+ {
31
+ title: 'Implement upload-netsuite lib function',
32
+ skill_ref: '/upload-netsuite',
33
+ priority: 2,
34
+ labels: ['returnpro', 'phase-2'],
35
+ description: 'Extract NetSuite XLSM upload pipeline into lib/returnpro/upload-netsuite.ts',
36
+ },
37
+ {
38
+ title: 'Implement upload-income-statements lib function',
39
+ skill_ref: '/upload-income-statements',
40
+ priority: 2,
41
+ labels: ['returnpro', 'phase-2'],
42
+ description: 'Extract confirmed income statement CSV upload into lib/returnpro/upload-income-statements.ts',
43
+ },
44
+ {
45
+ title: 'Implement rate-anomalies lib function',
46
+ skill_ref: '/rate-anomalies',
47
+ priority: 3,
48
+ labels: ['returnpro', 'phase-2'],
49
+ description: 'Extract rate anomaly detection logic from dashboard-returnpro into lib/returnpro/rate-anomalies.ts',
50
+ },
51
+ {
52
+ title: 'Implement diagnose-months lib function',
53
+ skill_ref: '/diagnose-months',
54
+ priority: 3,
55
+ labels: ['returnpro', 'phase-2'],
56
+ description: 'Extract month-level diagnostic comparison into lib/returnpro/diagnose-months.ts',
57
+ },
58
+ {
59
+ title: 'Implement generate-netsuite-template lib function',
60
+ skill_ref: '/generate-netsuite-template',
61
+ priority: 3,
62
+ labels: ['returnpro', 'phase-2'],
63
+ description: 'Generate blank NetSuite XLSM templates for data entry into lib/returnpro/generate-netsuite-template.ts',
64
+ },
65
+
66
+ // --- Content follow-ups (ready) ---
67
+ {
68
+ title: 'Implement generate-newsletter-insurance',
69
+ skill_ref: '/generate-newsletter-insurance',
70
+ priority: 2,
71
+ labels: ['content', 'phase-3'],
72
+ description: 'Insurance-specific newsletter generation (LIFEINSUR brand) with Groq AI content',
73
+ },
74
+ {
75
+ title: 'Implement distribute-newsletter (n8n webhook)',
76
+ skill_ref: '/distribute-newsletter',
77
+ priority: 2,
78
+ labels: ['content', 'phase-3'],
79
+ description: 'Trigger n8n webhook to distribute published newsletter via GoHighLevel email',
80
+ },
81
+ {
82
+ title: 'Implement generate-social-posts pipeline',
83
+ skill_ref: '/generate-social-posts',
84
+ priority: 2,
85
+ labels: ['content', 'phase-3'],
86
+ description: 'Full pipeline: scrape competitors, analyze patterns, generate 9 social posts, push to Strapi',
87
+ },
88
+ {
89
+ title: 'Implement publish-social-posts',
90
+ skill_ref: '/publish-social-posts',
91
+ priority: 3,
92
+ labels: ['content', 'phase-3'],
93
+ description: 'Publish scheduled social posts to Meta (IG/FB) via Marketing API',
94
+ },
95
+ {
96
+ title: 'Implement publish-blog',
97
+ skill_ref: '/publish-blog',
98
+ priority: 3,
99
+ labels: ['content', 'phase-3'],
100
+ description: 'Publish blog post to Strapi CMS and deploy preview site via Vercel',
101
+ },
102
+
103
+ // --- Infrastructure follow-ups (ready) ---
104
+ {
105
+ title: 'Implement migrate-db skill',
106
+ skill_ref: '/migrate-db',
107
+ priority: 3,
108
+ labels: ['infra', 'phase-3'],
109
+ description: 'Run Supabase migrations via CLI (supabase db push --linked) with pre-flight checks',
110
+ },
111
+ {
112
+ title: 'Implement manage-scenarios for budget',
113
+ skill_ref: '/manage-scenarios',
114
+ priority: 3,
115
+ labels: ['budget', 'phase-3'],
116
+ description: 'CRUD for budget projection scenarios (save/load/compare named adjustment sets)',
117
+ },
118
+ {
119
+ title: 'Implement delete-batch for transactions',
120
+ skill_ref: '/delete-batch',
121
+ priority: 3,
122
+ labels: ['transactions', 'phase-3'],
123
+ description: 'Bulk delete transactions by date range or import batch ID with confirmation safeguard',
124
+ },
125
+
126
+ // --- Frontend migration (backlog — future phase) ---
127
+ {
128
+ title: 'Migrate dashboard-returnpro to apps/ as read-only',
129
+ priority: 4,
130
+ labels: ['frontend', 'phase-4'],
131
+ description: 'Move dashboard-returnpro Next.js app into apps/dashboard-returnpro as a read-only frontend consuming CLI skills',
132
+ },
133
+ {
134
+ title: 'Migrate optimalos to apps/ as read-only',
135
+ priority: 4,
136
+ labels: ['frontend', 'phase-4'],
137
+ description: 'Move optimalos Next.js app into apps/optimalos as a read-only frontend consuming CLI skills',
138
+ },
139
+ {
140
+ title: 'Migrate portfolio-2026 to apps/ as read-only',
141
+ priority: 4,
142
+ labels: ['frontend', 'phase-4'],
143
+ description: 'Move portfolio-2026 Next.js app into apps/portfolio-2026 as a read-only frontend consuming CLI skills',
144
+ },
145
+ {
146
+ title: 'Migrate wes-dashboard to apps/ as read-only',
147
+ priority: 4,
148
+ labels: ['frontend', 'phase-4'],
149
+ description: 'Move wes-dashboard Next.js app into apps/wes-dashboard as a read-only frontend consuming CLI skills',
150
+ },
151
+ {
152
+ title: 'Migrate newsletter-preview to apps/ as read-only',
153
+ priority: 4,
154
+ labels: ['frontend', 'phase-4'],
155
+ description: 'Move newsletter-preview Next.js app into apps/newsletter-preview as a read-only frontend consuming CLI skills',
156
+ },
157
+ ]
158
+
159
+ async function main() {
160
+ console.log(`Seeding kanban board for project: ${PROJECT_SLUG}`)
161
+ console.log(`Tasks to seed: ${tasks.length}\n`)
162
+
163
+ // Fetch existing tasks for idempotency check
164
+ const existing = await getBoard(PROJECT_SLUG)
165
+ const existingTitles = new Set(existing.map(t => t.title))
166
+
167
+ console.log(`Existing tasks on board: ${existing.length}`)
168
+
169
+ let created = 0
170
+ let skipped = 0
171
+
172
+ for (const task of tasks) {
173
+ if (existingTitles.has(task.title)) {
174
+ console.log(` SKIP: "${task.title}" (already exists)`)
175
+ skipped++
176
+ continue
177
+ }
178
+
179
+ try {
180
+ const result = await createTask({
181
+ project_slug: PROJECT_SLUG,
182
+ title: task.title,
183
+ description: task.description,
184
+ priority: task.priority,
185
+ skill_ref: task.skill_ref,
186
+ labels: task.labels,
187
+ })
188
+ console.log(` CREATE: "${result.title}" [P${result.priority}] (${result.id})`)
189
+ created++
190
+ } catch (err) {
191
+ const msg = err instanceof Error ? err.message : String(err)
192
+ console.error(` ERROR: "${task.title}" — ${msg}`)
193
+ }
194
+ }
195
+
196
+ console.log(`\nDone. Created: ${created}, Skipped: ${skipped}`)
197
+ console.log(`Total tasks on board: ${existing.length + created}`)
198
+ }
199
+
200
+ main().catch((err) => {
201
+ console.error('Seed failed:', err)
202
+ process.exit(1)
203
+ })
@@ -0,0 +1,21 @@
1
+ import { getBoardByStatus, listProjects } from '../lib/kanban.js'
2
+
3
+ async function main() {
4
+ console.log('Testing kanban lib...')
5
+
6
+ const projects = await listProjects()
7
+ console.log('Projects:', projects.map(p => p.slug).join(', '))
8
+
9
+ const board = await getBoardByStatus('optimal-cli')
10
+ console.log('Board:')
11
+ for (const [status, tasks] of Object.entries(board)) {
12
+ if (tasks.length > 0) {
13
+ console.log(` ${status}: ${tasks.length}`)
14
+ for (const t of tasks) {
15
+ console.log(` - ${t.title} (claimed: ${t.claimed_by || 'none'})`)
16
+ }
17
+ }
18
+ }
19
+ }
20
+
21
+ main().catch(console.error)
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: audit-financials
3
+ description: Compare staged financials against confirmed income statements and report accuracy per month
4
+ ---
5
+
6
+ ## Purpose
7
+ Verifies data accuracy between `stg_financials_raw` (staged from NetSuite XLSM/CSV) and `confirmed_income_statements` (from NetSuite income statement CSVs). This is the most critical financial health check — every data session should start by running this.
8
+
9
+ ## Inputs
10
+ - **months** (optional): Comma-separated YYYY-MM strings to filter (e.g., `2026-01,2025-12`). Omit for all months.
11
+ - **tolerance** (optional): Dollar tolerance for match detection. Default `1.00`.
12
+
13
+ ## Steps
14
+ 1. Call `lib/returnpro/audit.ts::runAuditComparison(months?, tolerance?)` to fetch and compare data
15
+ 2. Paginate all `stg_financials_raw` rows (amount is TEXT — parseFloat)
16
+ 3. Paginate all `confirmed_income_statements` rows
17
+ 4. Aggregate staging by `account_code|YYYY-MM` key
18
+ 5. Compare each overlapping account: exact match, sign-flip match, or mismatch (within tolerance)
19
+ 6. Compute accuracy = (exactMatch + signFlipMatch) / overlap * 100
20
+
21
+ ## Output
22
+ Per-month table:
23
+
24
+ | Month | Confirmed | Staged | Match | Mismatch | Accuracy |
25
+ |-------|-----------|--------|-------|----------|----------|
26
+ | 2026-01 | 189 | 91 | 83 | 8 | 91.2% |
27
+
28
+ Plus total staging rows and confirmed rows counts.
29
+
30
+ Flag any month below 100% accuracy — investigate mismatches and identify root causes.
31
+
32
+ ## Environment
33
+ Requires: `RETURNPRO_SUPABASE_URL`, `RETURNPRO_SUPABASE_SERVICE_KEY`
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: board-create
3
+ description: Create a new task on the kanban board
4
+ ---
5
+
6
+ ## Purpose
7
+ Adds a new task to the project board. Agents use this to break work into trackable subtasks.
8
+
9
+ ## Inputs
10
+ - **project** (optional): Project slug. Default: `optimal-cli-refactor`
11
+ - **title** (required): Task title
12
+ - **description** (optional): Detailed task description
13
+ - **priority** (optional): 1=urgent, 2=high, 3=normal, 4=low. Default: 3
14
+ - **skill** (optional): Skill that should handle this task (e.g. `/audit-financials`)
15
+ - **labels** (optional): Comma-separated labels
16
+ - **blocked_by** (optional): Comma-separated task IDs that must complete first
17
+
18
+ ## Steps
19
+ 1. Call `lib/kanban.ts::createTask(input)` with provided params
20
+ 2. Return the created task ID and title
21
+
22
+ ## Output
23
+ ```
24
+ Created task: {id} — "{title}" (priority {priority}, status backlog)
25
+ ```
26
+
27
+ ## Environment
28
+ Requires: `OPTIMAL_SUPABASE_URL`, `OPTIMAL_SUPABASE_SERVICE_KEY`
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: board-update
3
+ description: Update a task's status, assignment, or metadata on the kanban board
4
+ ---
5
+
6
+ ## Purpose
7
+ Moves tasks through the kanban lifecycle. Agents call this to claim work, mark completion, or flag blockers.
8
+
9
+ ## Inputs
10
+ - **task_id** (required): UUID of the task to update
11
+ - **status** (optional): New status (backlog, ready, in_progress, blocked, review, done, canceled)
12
+ - **agent** (optional): Agent name to assign (e.g. `claude-code`, `carlos`)
13
+ - **priority** (optional): New priority (1-4)
14
+ - **message** (optional): Log message describing the update
15
+
16
+ ## Steps
17
+ 1. Call `lib/kanban.ts::updateTask(taskId, updates)`
18
+ 2. Call `lib/kanban.ts::logActivity(taskId, { agent, action: 'status_change', message })`
19
+ 3. Return updated task summary
20
+
21
+ ## Output
22
+ ```
23
+ Updated task {id}: status → {status}, agent → {agent}
24
+ ```
25
+
26
+ ## Environment
27
+ Requires: `OPTIMAL_SUPABASE_URL`, `OPTIMAL_SUPABASE_SERVICE_KEY`
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: board-view
3
+ description: Display the current kanban board as a markdown table
4
+ ---
5
+
6
+ ## Purpose
7
+ Shows all tasks for a project grouped by status column. Use this to check what work is queued, in progress, or completed.
8
+
9
+ ## Inputs
10
+ - **project** (optional): Project slug. Default: `optimal-cli-refactor`
11
+ - **status** (optional): Filter to a single status column (backlog, ready, in_progress, blocked, review, done)
12
+
13
+ ## Steps
14
+ 1. Call `lib/kanban.ts::getBoard(projectSlug)` to fetch all tasks
15
+ 2. Group tasks by status
16
+ 3. Format as a markdown table with columns: Status | Priority | Title | Agent | Skill
17
+
18
+ ## Output
19
+ Markdown table grouped by status:
20
+
21
+ | Status | P | Title | Agent | Skill |
22
+ |--------|---|-------|-------|-------|
23
+ | in_progress | 1 | Upload R1 data | claude-code | /upload-r1 |
24
+ | backlog | 2 | Extract KPI skills | — | — |
25
+
26
+ ## Environment
27
+ Requires: `OPTIMAL_SUPABASE_URL`, `OPTIMAL_SUPABASE_SERVICE_KEY`
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: delete-batch
3
+ description: Bulk delete transactions by filter from the OptimalOS transactions table
4
+ ---
5
+
6
+ ## Purpose
7
+ Performs bulk deletion of transactions from OptimalOS's `transactions` table based on filter criteria. Used to clean up bad imports, remove duplicate batches, or clear test data. Requires explicit confirmation due to the destructive nature of the operation. Always creates an audit log entry before deleting.
8
+
9
+ ## Inputs
10
+ - **batch-id** (optional): Delete all transactions belonging to a specific `upload_batches.id`. This is the safest and most common deletion method.
11
+ - **user-id** (optional): Filter by owner UUID. Required if not using `--batch-id`.
12
+ - **date-range** (optional): Delete transactions within a date range as `YYYY-MM-DD:YYYY-MM-DD` (inclusive).
13
+ - **bank** (optional): Filter by bank/source format (`chase_checking`, `chase_credit`, `discover`, `generic`).
14
+ - **confirm** (optional): Skip the interactive confirmation prompt. Default: false (requires confirmation).
15
+ - **dry-run** (optional): Show what would be deleted without actually deleting. Default: false.
16
+
17
+ ## Steps
18
+ 1. Call `lib/transactions/ingest.ts::deleteBatch(options)` to orchestrate the deletion
19
+ 2. **Build filter** — construct WHERE clause from provided filters (batch-id, user-id, date-range, bank)
20
+ 3. **Count affected rows** — `SELECT COUNT(*) FROM transactions WHERE <filters>` to show impact
21
+ 4. **Dry-run check** — if `--dry-run`, display count and sample rows, then exit
22
+ 5. **Confirm** — unless `--confirm` is set, display count and ask for explicit confirmation
23
+ 6. **Audit log** — insert a record into `cli_task_logs` with deletion details (filter, count, timestamp)
24
+ 7. **Delete** — `DELETE FROM transactions WHERE <filters>` in batches of 100
25
+ 8. **Clean up batches** — if `--batch-id` used, update `upload_batches.status` to `deleted`
26
+ 9. Log execution via `lib/kanban.ts::logSkillExecution()`
27
+
28
+ ## Output
29
+ ```
30
+ Filter: batch_id = 42
31
+ Affected rows: 231 transactions
32
+ Date range: 2025-11-01 to 2025-11-30
33
+ Bank: chase_credit
34
+
35
+ Deleted: 231 transactions
36
+ Batch 42 marked as deleted.
37
+ Audit log entry: cli_task_logs.id = 789
38
+ ```
39
+
40
+ Dry-run mode:
41
+ ```
42
+ [DRY RUN] Would delete 231 transactions matching:
43
+ batch_id = 42, bank = chase_credit
44
+ Sample: 2025-11-01 "AMAZON.COM" -$47.99, 2025-11-02 "WHOLE FOODS" -$82.31, ...
45
+ ```
46
+
47
+ ## CLI Usage
48
+ ```bash
49
+ # Delete by batch ID (safest)
50
+ optimal delete-batch --batch-id 42
51
+
52
+ # Delete by user and date range
53
+ optimal delete-batch --user-id <uuid> --date-range 2025-11-01:2025-11-30
54
+
55
+ # Dry run to preview
56
+ optimal delete-batch --batch-id 42 --dry-run
57
+
58
+ # Skip confirmation prompt
59
+ optimal delete-batch --batch-id 42 --confirm
60
+ ```
61
+
62
+ ## Environment
63
+ Requires: `OPTIMAL_SUPABASE_URL`, `OPTIMAL_SUPABASE_SERVICE_KEY`
64
+
65
+ ## Tables Touched
66
+ - `transactions` — DELETE matching rows
67
+ - `upload_batches` — update status to `deleted` (if batch-id used)
68
+ - `cli_task_logs` — audit trail entry
69
+
70
+ ## Gotchas
71
+ - **Destructive operation**: Always runs a count + dry-run preview before actual deletion unless `--confirm` is explicitly passed.
72
+ - **No undo**: Deleted transactions cannot be recovered. Re-import the original CSV if needed.
73
+ - **Batch deletion is preferred**: Using `--batch-id` is the safest method because it deletes exactly what was imported in one operation, with clean provenance tracking.
74
+ - **Categories are preserved**: Deleting transactions does not cascade to `categories`.
75
+
76
+ ## Status
77
+ Implementation status: Not yet implemented. Spec only. Lib function to be added to `lib/transactions/ingest.ts` alongside existing ingestion logic.
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: deploy
3
+ description: Deploy an app to Vercel (preview or production)
4
+ ---
5
+
6
+ ## Purpose
7
+ Deploy one of the Optimal project apps to Vercel. Supports both preview deployments (default) and production deployments (with `--prod`).
8
+
9
+ ## Inputs
10
+ - **app** (required): App name to deploy. One of: `dashboard-returnpro`, `optimalos`, `portfolio`, `newsletter-preview`, `wes`.
11
+ - **prod** (optional): Pass `--prod` flag to deploy to production instead of preview.
12
+
13
+ ## Steps
14
+ 1. Resolve the app name to its absolute filesystem path via `lib/infra/deploy.ts::getAppPath()`
15
+ 2. Call `vercel --cwd <path>` (or `vercel --prod --cwd <path>` for production)
16
+ 3. Wait up to 2 minutes for the deployment to complete
17
+ 4. Return the deployment URL
18
+
19
+ ## Output
20
+ The Vercel deployment URL (e.g., `https://portfolio-2026-abc123.vercel.app` for preview, or the production URL for `--prod`).
21
+
22
+ ## Available Apps
23
+
24
+ | Name | Path |
25
+ |------|------|
26
+ | dashboard-returnpro | /home/optimal/dashboard-returnpro |
27
+ | optimalos | /home/optimal/optimalos |
28
+ | portfolio | /home/optimal/portfolio-2026 |
29
+ | newsletter-preview | /home/optimal/projects/newsletter-preview |
30
+ | wes | /home/optimal/wes-dashboard |
31
+
32
+ ## Usage
33
+ ```bash
34
+ optimal deploy portfolio # preview deployment
35
+ optimal deploy portfolio --prod # production deployment
36
+ optimal deploy dashboard-returnpro --prod
37
+ ```
38
+
39
+ ## Environment
40
+ Requires: `vercel` CLI installed globally and authenticated.