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.
- package/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +10 -0
- package/.env.example +17 -0
- package/CLAUDE.md +67 -0
- package/COMMANDS.md +264 -0
- package/PUBLISH.md +70 -0
- package/agents/content-ops.md +2 -2
- package/agents/financial-ops.md +2 -2
- package/agents/infra-ops.md +2 -2
- package/apps/.gitkeep +0 -0
- package/bin/optimal.ts +278 -591
- package/docs/MIGRATION_NEEDED.md +37 -0
- package/docs/plans/.gitkeep +0 -0
- package/docs/plans/optimal-cli-config-registry-v1.md +71 -0
- package/hooks/.gitkeep +0 -0
- package/lib/config/registry.ts +5 -4
- package/lib/kanban-obsidian.ts +232 -0
- package/lib/kanban-sync.ts +258 -0
- package/lib/kanban.ts +239 -0
- package/lib/obsidian-tasks.ts +231 -0
- package/package.json +5 -19
- package/pnpm-workspace.yaml +3 -0
- package/scripts/check-table.ts +24 -0
- package/scripts/create-tables.ts +94 -0
- package/scripts/migrate-kanban.sh +28 -0
- package/scripts/migrate-v2.ts +78 -0
- package/scripts/migrate.ts +79 -0
- package/scripts/run-migration.ts +59 -0
- package/scripts/seed-board.ts +203 -0
- package/scripts/test-kanban.ts +21 -0
- package/skills/audit-financials/SKILL.md +33 -0
- package/skills/board-create/SKILL.md +28 -0
- package/skills/board-update/SKILL.md +27 -0
- package/skills/board-view/SKILL.md +27 -0
- package/skills/delete-batch/SKILL.md +77 -0
- package/skills/deploy/SKILL.md +40 -0
- package/skills/diagnose-months/SKILL.md +68 -0
- package/skills/distribute-newsletter/SKILL.md +58 -0
- package/skills/export-budget/SKILL.md +44 -0
- package/skills/export-kpis/SKILL.md +52 -0
- package/skills/generate-netsuite-template/SKILL.md +51 -0
- package/skills/generate-newsletter/SKILL.md +53 -0
- package/skills/generate-newsletter-insurance/SKILL.md +59 -0
- package/skills/generate-social-posts/SKILL.md +67 -0
- package/skills/health-check/SKILL.md +42 -0
- package/skills/ingest-transactions/SKILL.md +51 -0
- package/skills/manage-cms/SKILL.md +50 -0
- package/skills/manage-scenarios/SKILL.md +83 -0
- package/skills/migrate-db/SKILL.md +79 -0
- package/skills/preview-newsletter/SKILL.md +50 -0
- package/skills/project-budget/SKILL.md +60 -0
- package/skills/publish-blog/SKILL.md +70 -0
- package/skills/publish-social-posts/SKILL.md +70 -0
- package/skills/rate-anomalies/SKILL.md +62 -0
- package/skills/scrape-ads/SKILL.md +49 -0
- package/skills/stamp-transactions/SKILL.md +62 -0
- package/skills/upload-income-statements/SKILL.md +54 -0
- package/skills/upload-netsuite/SKILL.md +56 -0
- package/skills/upload-r1/SKILL.md +45 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/migrations/.gitkeep +0 -0
- package/supabase/migrations/20250305000001_create_agent_configs.sql +36 -0
- package/supabase/migrations/20260305111300_create_cli_config_registry.sql +22 -0
- package/supabase/migrations/20260306195000_create_kanban_tables.sql +97 -0
- package/tests/config-command-smoke.test.ts +395 -0
- package/tests/config-registry.test.ts +173 -0
- package/tsconfig.json +19 -0
- package/agents/profiles.json +0 -5
- package/docs/CLI-REFERENCE.md +0 -361
- package/lib/assets/index.ts +0 -225
- package/lib/assets.ts +0 -124
- package/lib/auth/index.ts +0 -189
- package/lib/board/index.ts +0 -309
- package/lib/board/types.ts +0 -124
- package/lib/bot/claim.ts +0 -43
- package/lib/bot/coordinator.ts +0 -254
- package/lib/bot/heartbeat.ts +0 -37
- package/lib/bot/index.ts +0 -9
- package/lib/bot/protocol.ts +0 -99
- package/lib/bot/reporter.ts +0 -42
- package/lib/bot/skills.ts +0 -81
- package/lib/errors.ts +0 -129
- package/lib/format.ts +0 -120
- package/lib/returnpro/validate.ts +0 -154
- 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.
|