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
@@ -1,309 +0,0 @@
1
- import { getSupabase } from '../supabase.js'
2
- import type {
3
- Project, Task, Label, Comment, Milestone, ActivityEntry,
4
- CreateProjectInput, CreateTaskInput, CreateCommentInput, CreateMilestoneInput,
5
- UpdateTaskInput, TaskStatus,
6
- } from './types.js'
7
-
8
- export * from './types.js'
9
-
10
- const sb = () => getSupabase('optimal')
11
-
12
- // --- Helpers ---
13
-
14
- export function formatBoardTable(tasks: Task[]): string {
15
- if (tasks.length === 0) return 'No tasks found.'
16
- const lines = [
17
- '| Status | P | Title | Agent | Skill | Effort |',
18
- '|-------------|---|--------------------------------|---------|-----------------|--------|',
19
- ]
20
- const order: TaskStatus[] = ['in_progress', 'claimed', 'blocked', 'ready', 'review', 'backlog', 'done']
21
- const sorted = [...tasks].sort((a, b) => {
22
- const ai = order.indexOf(a.status)
23
- const bi = order.indexOf(b.status)
24
- if (ai !== bi) return ai - bi
25
- return a.priority - b.priority
26
- })
27
- for (const t of sorted) {
28
- const title = t.title.length > 30 ? t.title.slice(0, 27) + '...' : t.title.padEnd(30)
29
- const agent = (t.claimed_by ?? t.assigned_to ?? '—').padEnd(7)
30
- const skill = (t.skill_required ?? '—').padEnd(15)
31
- const effort = (t.estimated_effort ?? '—').padEnd(6)
32
- lines.push(`| ${t.status.padEnd(11)} | ${t.priority} | ${title} | ${agent} | ${skill} | ${effort} |`)
33
- }
34
- lines.push(`\nTotal: ${tasks.length} tasks`)
35
- return lines.join('\n')
36
- }
37
-
38
- export function getNextClaimable(readyTasks: Task[], allTasks: Task[]): Task | null {
39
- for (const task of readyTasks) {
40
- if (!task.blocked_by || task.blocked_by.length === 0) return task
41
- const allDone = task.blocked_by.every(depId => {
42
- const dep = allTasks.find(t => t.id === depId)
43
- return dep && (dep.status === 'done')
44
- })
45
- if (allDone) return task
46
- }
47
- return null
48
- }
49
-
50
- // --- Projects ---
51
-
52
- export async function createProject(input: CreateProjectInput): Promise<Project> {
53
- const { data, error } = await sb()
54
- .from('projects')
55
- .insert({
56
- slug: input.slug,
57
- name: input.name,
58
- description: input.description ?? null,
59
- owner: input.owner ?? null,
60
- priority: input.priority ?? 3,
61
- })
62
- .select()
63
- .single()
64
- if (error) throw new Error(`Failed to create project: ${error.message}`)
65
- return data as Project
66
- }
67
-
68
- export async function getProjectBySlug(slug: string): Promise<Project> {
69
- const { data, error } = await sb()
70
- .from('projects')
71
- .select('*')
72
- .eq('slug', slug)
73
- .single()
74
- if (error) throw new Error(`Project not found: ${slug} — ${error.message}`)
75
- return data as Project
76
- }
77
-
78
- export async function listProjects(): Promise<Project[]> {
79
- const { data, error } = await sb()
80
- .from('projects')
81
- .select('*')
82
- .neq('status', 'archived')
83
- .order('priority', { ascending: true })
84
- if (error) throw new Error(`Failed to list projects: ${error.message}`)
85
- return (data ?? []) as Project[]
86
- }
87
-
88
- export async function updateProject(slug: string, updates: Partial<Pick<Project, 'status' | 'owner' | 'priority' | 'description'>>): Promise<Project> {
89
- const { data, error } = await sb()
90
- .from('projects')
91
- .update(updates)
92
- .eq('slug', slug)
93
- .select()
94
- .single()
95
- if (error) throw new Error(`Failed to update project: ${error.message}`)
96
- return data as Project
97
- }
98
-
99
- // --- Milestones ---
100
-
101
- export async function createMilestone(input: CreateMilestoneInput): Promise<Milestone> {
102
- const { data, error } = await sb()
103
- .from('milestones')
104
- .insert({
105
- project_id: input.project_id,
106
- name: input.name,
107
- description: input.description ?? null,
108
- due_date: input.due_date ?? null,
109
- })
110
- .select()
111
- .single()
112
- if (error) throw new Error(`Failed to create milestone: ${error.message}`)
113
- return data as Milestone
114
- }
115
-
116
- export async function listMilestones(projectId?: string): Promise<Milestone[]> {
117
- let query = sb().from('milestones').select('*').order('due_date', { ascending: true })
118
- if (projectId) query = query.eq('project_id', projectId)
119
- const { data, error } = await query
120
- if (error) throw new Error(`Failed to list milestones: ${error.message}`)
121
- return (data ?? []) as Milestone[]
122
- }
123
-
124
- // --- Labels ---
125
-
126
- export async function createLabel(name: string, color?: string): Promise<Label> {
127
- const { data, error } = await sb()
128
- .from('labels')
129
- .insert({ name, color: color ?? null })
130
- .select()
131
- .single()
132
- if (error) throw new Error(`Failed to create label: ${error.message}`)
133
- return data as Label
134
- }
135
-
136
- export async function listLabels(): Promise<Label[]> {
137
- const { data, error } = await sb().from('labels').select('*').order('name')
138
- if (error) throw new Error(`Failed to list labels: ${error.message}`)
139
- return (data ?? []) as Label[]
140
- }
141
-
142
- export async function getLabelByName(name: string): Promise<Label | null> {
143
- const { data } = await sb().from('labels').select('*').eq('name', name).single()
144
- return (data as Label) ?? null
145
- }
146
-
147
- // --- Tasks ---
148
-
149
- export async function createTask(input: CreateTaskInput): Promise<Task> {
150
- const { labels: labelNames, ...rest } = input
151
- const { data, error } = await sb()
152
- .from('tasks')
153
- .insert({
154
- ...rest,
155
- milestone_id: rest.milestone_id ?? null,
156
- description: rest.description ?? null,
157
- priority: rest.priority ?? 3,
158
- skill_required: rest.skill_required ?? null,
159
- source_repo: rest.source_repo ?? null,
160
- target_module: rest.target_module ?? null,
161
- estimated_effort: rest.estimated_effort ?? null,
162
- blocked_by: rest.blocked_by ?? [],
163
- })
164
- .select()
165
- .single()
166
- if (error) throw new Error(`Failed to create task: ${error.message}`)
167
- const task = data as Task
168
-
169
- if (labelNames && labelNames.length > 0) {
170
- for (const name of labelNames) {
171
- const label = await getLabelByName(name)
172
- if (label) {
173
- await sb().from('task_labels').insert({ task_id: task.id, label_id: label.id })
174
- }
175
- }
176
- }
177
-
178
- await logActivity({ task_id: task.id, project_id: task.project_id, actor: 'system', action: 'created', new_value: { title: task.title } })
179
- return task
180
- }
181
-
182
- export async function updateTask(taskId: string, updates: UpdateTaskInput, actor?: string): Promise<Task> {
183
- const old = await getTask(taskId)
184
- const { data, error } = await sb()
185
- .from('tasks')
186
- .update(updates)
187
- .eq('id', taskId)
188
- .select()
189
- .single()
190
- if (error) throw new Error(`Failed to update task ${taskId}: ${error.message}`)
191
- const task = data as Task
192
-
193
- if (actor) {
194
- await logActivity({
195
- task_id: taskId,
196
- project_id: task.project_id,
197
- actor,
198
- action: updates.status ? 'status_changed' : 'updated',
199
- old_value: { status: old.status, assigned_to: old.assigned_to },
200
- new_value: updates as Record<string, unknown>,
201
- })
202
- }
203
- return task
204
- }
205
-
206
- export async function getTask(taskId: string): Promise<Task> {
207
- const { data, error } = await sb()
208
- .from('tasks')
209
- .select('*')
210
- .eq('id', taskId)
211
- .single()
212
- if (error) throw new Error(`Task not found: ${taskId}`)
213
- return data as Task
214
- }
215
-
216
- export async function listTasks(opts?: {
217
- project_id?: string
218
- status?: TaskStatus
219
- claimed_by?: string
220
- assigned_to?: string
221
- }): Promise<Task[]> {
222
- let query = sb().from('tasks').select('*')
223
- if (opts?.project_id) query = query.eq('project_id', opts.project_id)
224
- if (opts?.status) query = query.eq('status', opts.status)
225
- if (opts?.claimed_by) query = query.eq('claimed_by', opts.claimed_by)
226
- if (opts?.assigned_to) query = query.eq('assigned_to', opts.assigned_to)
227
- query = query.order('priority', { ascending: true }).order('sort_order', { ascending: true })
228
- const { data, error } = await query
229
- if (error) throw new Error(`Failed to list tasks: ${error.message}`)
230
- return (data ?? []) as Task[]
231
- }
232
-
233
- export async function claimTask(taskId: string, agent: string): Promise<Task> {
234
- const task = await updateTask(taskId, {
235
- status: 'claimed',
236
- claimed_by: agent,
237
- claimed_at: new Date().toISOString(),
238
- }, agent)
239
-
240
- await addComment({ task_id: taskId, author: agent, body: `Claimed by ${agent}`, comment_type: 'claim' })
241
- return task
242
- }
243
-
244
- export async function completeTask(taskId: string, actor: string): Promise<Task> {
245
- return updateTask(taskId, {
246
- status: 'done',
247
- completed_at: new Date().toISOString(),
248
- }, actor)
249
- }
250
-
251
- // --- Comments ---
252
-
253
- export async function addComment(input: CreateCommentInput): Promise<Comment> {
254
- const { data, error } = await sb()
255
- .from('comments')
256
- .insert({
257
- task_id: input.task_id,
258
- author: input.author,
259
- body: input.body,
260
- comment_type: input.comment_type ?? 'comment',
261
- })
262
- .select()
263
- .single()
264
- if (error) throw new Error(`Failed to add comment: ${error.message}`)
265
- return data as Comment
266
- }
267
-
268
- export async function listComments(taskId: string): Promise<Comment[]> {
269
- const { data, error } = await sb()
270
- .from('comments')
271
- .select('*')
272
- .eq('task_id', taskId)
273
- .order('created_at', { ascending: true })
274
- if (error) throw new Error(`Failed to list comments: ${error.message}`)
275
- return (data ?? []) as Comment[]
276
- }
277
-
278
- // --- Activity Log ---
279
-
280
- export async function logActivity(entry: {
281
- task_id?: string
282
- project_id?: string
283
- actor: string
284
- action: string
285
- old_value?: Record<string, unknown>
286
- new_value?: Record<string, unknown>
287
- }): Promise<void> {
288
- const { error } = await sb()
289
- .from('activity_log')
290
- .insert({
291
- task_id: entry.task_id ?? null,
292
- project_id: entry.project_id ?? null,
293
- actor: entry.actor,
294
- action: entry.action,
295
- old_value: entry.old_value ?? null,
296
- new_value: entry.new_value ?? null,
297
- })
298
- if (error) throw new Error(`Failed to log activity: ${error.message}`)
299
- }
300
-
301
- export async function listActivity(opts?: { task_id?: string; actor?: string; limit?: number }): Promise<ActivityEntry[]> {
302
- let query = sb().from('activity_log').select('*')
303
- if (opts?.task_id) query = query.eq('task_id', opts.task_id)
304
- if (opts?.actor) query = query.eq('actor', opts.actor)
305
- query = query.order('created_at', { ascending: false }).limit(opts?.limit ?? 50)
306
- const { data, error } = await query
307
- if (error) throw new Error(`Failed to list activity: ${error.message}`)
308
- return (data ?? []) as ActivityEntry[]
309
- }
@@ -1,124 +0,0 @@
1
- export interface Project {
2
- id: string
3
- slug: string
4
- name: string
5
- description: string | null
6
- status: 'active' | 'paused' | 'completed' | 'archived'
7
- owner: string | null
8
- priority: 1 | 2 | 3 | 4
9
- created_at: string
10
- updated_at: string
11
- }
12
-
13
- export interface Milestone {
14
- id: string
15
- project_id: string
16
- name: string
17
- description: string | null
18
- due_date: string | null
19
- status: 'open' | 'completed' | 'missed'
20
- created_at: string
21
- updated_at: string
22
- }
23
-
24
- export interface Label {
25
- id: string
26
- name: string
27
- color: string | null
28
- created_at: string
29
- }
30
-
31
- export type TaskStatus = 'backlog' | 'ready' | 'claimed' | 'in_progress' | 'review' | 'done' | 'blocked'
32
- export type Priority = 1 | 2 | 3 | 4
33
- export type Effort = 'xs' | 's' | 'm' | 'l' | 'xl'
34
-
35
- export interface Task {
36
- id: string
37
- project_id: string
38
- milestone_id: string | null
39
- title: string
40
- description: string | null
41
- status: TaskStatus
42
- priority: Priority
43
- assigned_to: string | null
44
- claimed_by: string | null
45
- claimed_at: string | null
46
- skill_required: string | null
47
- source_repo: string | null
48
- target_module: string | null
49
- estimated_effort: Effort | null
50
- blocked_by: string[]
51
- sort_order: number
52
- created_at: string
53
- updated_at: string
54
- completed_at: string | null
55
- }
56
-
57
- export interface Comment {
58
- id: string
59
- task_id: string
60
- author: string
61
- body: string
62
- comment_type: 'comment' | 'status_change' | 'claim' | 'review'
63
- created_at: string
64
- }
65
-
66
- export interface ActivityEntry {
67
- id: string
68
- task_id: string | null
69
- project_id: string | null
70
- actor: string
71
- action: string
72
- old_value: Record<string, unknown> | null
73
- new_value: Record<string, unknown> | null
74
- created_at: string
75
- }
76
-
77
- // --- Input types ---
78
-
79
- export interface CreateProjectInput {
80
- slug: string
81
- name: string
82
- description?: string
83
- owner?: string
84
- priority?: Priority
85
- }
86
-
87
- export interface CreateMilestoneInput {
88
- project_id: string
89
- name: string
90
- description?: string
91
- due_date?: string
92
- }
93
-
94
- export interface CreateTaskInput {
95
- project_id: string
96
- title: string
97
- description?: string
98
- priority?: Priority
99
- milestone_id?: string
100
- skill_required?: string
101
- source_repo?: string
102
- target_module?: string
103
- estimated_effort?: Effort
104
- blocked_by?: string[]
105
- labels?: string[]
106
- }
107
-
108
- export interface UpdateTaskInput {
109
- status?: TaskStatus
110
- priority?: Priority
111
- assigned_to?: string | null
112
- claimed_by?: string | null
113
- claimed_at?: string | null
114
- milestone_id?: string | null
115
- description?: string
116
- completed_at?: string | null
117
- }
118
-
119
- export interface CreateCommentInput {
120
- task_id: string
121
- author: string
122
- body: string
123
- comment_type?: 'comment' | 'status_change' | 'claim' | 'review'
124
- }
package/lib/bot/claim.ts DELETED
@@ -1,43 +0,0 @@
1
- import {
2
- listTasks,
3
- claimTask,
4
- updateTask,
5
- getNextClaimable,
6
- type Task,
7
- } from '../board/index.js'
8
-
9
- export async function claimNextTask(
10
- agentId: string,
11
- skills?: string[],
12
- ): Promise<Task | null> {
13
- const readyTasks = await listTasks({ status: 'ready' })
14
- const allTasks = await listTasks()
15
-
16
- let candidates = readyTasks
17
- if (skills && skills.length > 0) {
18
- candidates = readyTasks.filter(
19
- (t) => !t.skill_required || skills.includes(t.skill_required),
20
- )
21
- }
22
-
23
- const next = getNextClaimable(candidates, allTasks)
24
- if (!next) return null
25
-
26
- return claimTask(next.id, agentId)
27
- }
28
-
29
- export async function releaseTask(
30
- taskId: string,
31
- agentId: string,
32
- reason?: string,
33
- ): Promise<Task> {
34
- return updateTask(
35
- taskId,
36
- {
37
- status: 'ready',
38
- claimed_by: null,
39
- claimed_at: null,
40
- },
41
- agentId,
42
- )
43
- }