expxagents 0.30.21 → 0.30.22

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 (64) hide show
  1. package/dist/dashboard/assets/{BufferResource-CNVLxaqR.js → BufferResource-W2IIeEqr.js} +1 -1
  2. package/dist/dashboard/assets/{CanvasRenderer-CxVnsrzD.js → CanvasRenderer--97T4dBj.js} +1 -1
  3. package/dist/dashboard/assets/{JarvisView-Bk0QwZWW.js → JarvisView-9obmtcTA.js} +1 -1
  4. package/dist/dashboard/assets/{RenderTargetSystem-CtHj6v7J.js → RenderTargetSystem-B0OyV0_c.js} +1 -1
  5. package/dist/dashboard/assets/{ThreeBackground-DPSa33_n.js → ThreeBackground-CqlgTgPA.js} +1 -1
  6. package/dist/dashboard/assets/{WebGLRenderer-C2UGtfFx.js → WebGLRenderer-DeH8dyfs.js} +1 -1
  7. package/dist/dashboard/assets/{WebGPURenderer-CmDaV5xR.js → WebGPURenderer-DJfipI3w.js} +1 -1
  8. package/dist/dashboard/assets/{browserAll-DuOXddzq.js → browserAll-DVaKq4Ma.js} +1 -1
  9. package/dist/dashboard/assets/index-BtrFCebM.js +1203 -0
  10. package/dist/dashboard/assets/{webworkerAll-BlC_rD-w.js → webworkerAll-6vPSZk-g.js} +1 -1
  11. package/dist/dashboard/index.html +1 -1
  12. package/dist/server/api/__tests__/github-oauth-routes.test.d.ts +2 -0
  13. package/dist/server/api/__tests__/github-oauth-routes.test.d.ts.map +1 -0
  14. package/dist/server/api/__tests__/github-oauth-routes.test.js +119 -0
  15. package/dist/server/api/__tests__/github-oauth-routes.test.js.map +1 -0
  16. package/dist/server/api/__tests__/github-webhook.test.d.ts +2 -0
  17. package/dist/server/api/__tests__/github-webhook.test.d.ts.map +1 -0
  18. package/dist/server/api/__tests__/github-webhook.test.js +111 -0
  19. package/dist/server/api/__tests__/github-webhook.test.js.map +1 -0
  20. package/dist/server/api/__tests__/log-routes.test.js +8 -1
  21. package/dist/server/api/__tests__/log-routes.test.js.map +1 -1
  22. package/dist/server/api/github-oauth-routes.d.ts +9 -0
  23. package/dist/server/api/github-oauth-routes.d.ts.map +1 -0
  24. package/dist/server/api/github-oauth-routes.js +174 -0
  25. package/dist/server/api/github-oauth-routes.js.map +1 -0
  26. package/dist/server/api/integration-routes.d.ts.map +1 -1
  27. package/dist/server/api/integration-routes.js +6 -3
  28. package/dist/server/api/integration-routes.js.map +1 -1
  29. package/dist/server/api/webhook-routes.d.ts.map +1 -1
  30. package/dist/server/api/webhook-routes.js +88 -0
  31. package/dist/server/api/webhook-routes.js.map +1 -1
  32. package/dist/server/app.d.ts +2 -0
  33. package/dist/server/app.d.ts.map +1 -1
  34. package/dist/server/app.js +16 -2
  35. package/dist/server/app.js.map +1 -1
  36. package/dist/server/bridge/chat-handler.d.ts +2 -0
  37. package/dist/server/bridge/chat-handler.d.ts.map +1 -1
  38. package/dist/server/bridge/chat-handler.js +75 -0
  39. package/dist/server/bridge/chat-handler.js.map +1 -1
  40. package/dist/server/db/migrations.d.ts.map +1 -1
  41. package/dist/server/db/migrations.js +27 -0
  42. package/dist/server/db/migrations.js.map +1 -1
  43. package/dist/server/db/schema.d.ts +1 -1
  44. package/dist/server/db/schema.d.ts.map +1 -1
  45. package/dist/server/db/schema.js +21 -0
  46. package/dist/server/db/schema.js.map +1 -1
  47. package/dist/server/services/__tests__/github-action-service.test.d.ts +2 -0
  48. package/dist/server/services/__tests__/github-action-service.test.d.ts.map +1 -0
  49. package/dist/server/services/__tests__/github-action-service.test.js +89 -0
  50. package/dist/server/services/__tests__/github-action-service.test.js.map +1 -0
  51. package/dist/server/services/__tests__/github-credential-service.test.d.ts +2 -0
  52. package/dist/server/services/__tests__/github-credential-service.test.d.ts.map +1 -0
  53. package/dist/server/services/__tests__/github-credential-service.test.js +95 -0
  54. package/dist/server/services/__tests__/github-credential-service.test.js.map +1 -0
  55. package/dist/server/services/github-action-service.d.ts +21 -0
  56. package/dist/server/services/github-action-service.d.ts.map +1 -0
  57. package/dist/server/services/github-action-service.js +105 -0
  58. package/dist/server/services/github-action-service.js.map +1 -0
  59. package/dist/server/services/github-credential-service.d.ts +34 -0
  60. package/dist/server/services/github-credential-service.d.ts.map +1 -0
  61. package/dist/server/services/github-credential-service.js +158 -0
  62. package/dist/server/services/github-credential-service.js.map +1 -0
  63. package/package.json +1 -1
  64. package/dist/dashboard/assets/index-CgAs6Rfr.js +0 -1203
@@ -1,2 +1,2 @@
1
- export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS users (\n id TEXT PRIMARY KEY,\n username TEXT UNIQUE NOT NULL,\n email TEXT,\n password_hash TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'operator',\n created_at TEXT NOT NULL,\n last_login TEXT\n);\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n token_hash TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS audit_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id TEXT NOT NULL REFERENCES users(id),\n action TEXT NOT NULL,\n details TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS conversations (\n id TEXT PRIMARY KEY,\n squad_name TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS chat_messages (\n id TEXT PRIMARY KEY,\n conversation_id TEXT NOT NULL REFERENCES conversations(id),\n role TEXT NOT NULL,\n content TEXT NOT NULL,\n agent_id TEXT,\n agent_name TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS squad_activities (\n id TEXT PRIMARY KEY,\n squad_code TEXT NOT NULL,\n conversation_id TEXT,\n started_at TEXT NOT NULL,\n finished_at TEXT,\n duration_ms INTEGER,\n status TEXT NOT NULL DEFAULT 'running',\n user_message TEXT,\n result_summary TEXT,\n triggered_by TEXT DEFAULT 'dashboard',\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_squad_activities_squad_date\n ON squad_activities(squad_code, started_at);\n\nCREATE INDEX IF NOT EXISTS idx_squad_activities_date\n ON squad_activities(started_at);\n\nCREATE TABLE IF NOT EXISTS scheduled_jobs (\n squad_name TEXT PRIMARY KEY,\n cron_expression TEXT NOT NULL,\n prompt TEXT NOT NULL,\n mode TEXT DEFAULT 'autonomous',\n timeout_minutes INTEGER DEFAULT 30,\n retry_on_failure INTEGER DEFAULT 0,\n timezone TEXT,\n status TEXT DEFAULT 'active',\n last_run_at TEXT,\n last_result TEXT,\n next_run_at TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_squad_activities_triggered_by\n ON squad_activities(triggered_by, squad_code);\n\nCREATE TABLE IF NOT EXISTS leads (\n id TEXT PRIMARY KEY,\n email TEXT UNIQUE NOT NULL,\n name TEXT,\n company TEXT,\n metadata TEXT,\n status TEXT DEFAULT 'active',\n source TEXT,\n source_detail TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS lead_tags (\n lead_id TEXT NOT NULL REFERENCES leads(id) ON DELETE CASCADE,\n tag TEXT NOT NULL,\n PRIMARY KEY (lead_id, tag)\n);\n\nCREATE TABLE IF NOT EXISTS lead_lists (\n lead_id TEXT NOT NULL REFERENCES leads(id) ON DELETE CASCADE,\n list_name TEXT NOT NULL,\n PRIMARY KEY (lead_id, list_name)\n);\n\nCREATE TABLE IF NOT EXISTS email_campaigns (\n id TEXT PRIMARY KEY,\n name TEXT,\n squad_name TEXT,\n subject TEXT,\n body_html TEXT,\n body_text TEXT,\n from_email TEXT,\n from_name TEXT,\n status TEXT DEFAULT 'draft',\n total_recipients INTEGER DEFAULT 0,\n sent_count INTEGER DEFAULT 0,\n failed_count INTEGER DEFAULT 0,\n bounce_count INTEGER DEFAULT 0,\n complaint_count INTEGER DEFAULT 0,\n created_at TEXT DEFAULT (datetime('now')),\n started_at TEXT,\n completed_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS email_queue (\n id TEXT PRIMARY KEY,\n campaign_id TEXT REFERENCES email_campaigns(id) ON DELETE CASCADE,\n lead_id TEXT REFERENCES leads(id) ON DELETE SET NULL,\n to_email TEXT NOT NULL,\n status TEXT DEFAULT 'pending',\n ses_message_id TEXT,\n error TEXT,\n attempts INTEGER DEFAULT 0,\n scheduled_at TEXT,\n sent_at TEXT,\n created_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_email_queue_status_scheduled\n ON email_queue(status, scheduled_at);\nCREATE INDEX IF NOT EXISTS idx_email_queue_ses_message_id\n ON email_queue(ses_message_id);\nCREATE INDEX IF NOT EXISTS idx_email_queue_campaign_status\n ON email_queue(campaign_id, status);\nCREATE INDEX IF NOT EXISTS idx_lead_tags_tag\n ON lead_tags(tag);\nCREATE INDEX IF NOT EXISTS idx_lead_lists_list_name\n ON lead_lists(list_name);\n\nCREATE TABLE IF NOT EXISTS team_members (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n role TEXT NOT NULL,\n email TEXT,\n phone TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS integrations (\n provider TEXT PRIMARY KEY,\n enabled INTEGER NOT NULL DEFAULT 0,\n config TEXT NOT NULL DEFAULT '{}',\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS kanban_tasks (\n id TEXT PRIMARY KEY,\n squad_code TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'planned',\n position INTEGER NOT NULL DEFAULT 0,\n result TEXT,\n started_at TEXT,\n finished_at TEXT,\n step_id TEXT,\n agent_id TEXT,\n agent_name TEXT,\n activity_id TEXT REFERENCES squad_activities(id) ON DELETE SET NULL,\n triggered_by TEXT DEFAULT 'dashboard',\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_kanban_tasks_squad_status\n ON kanban_tasks(squad_code, status);\n\nCREATE TABLE IF NOT EXISTS squad_states (\n squad_code TEXT PRIMARY KEY,\n status TEXT NOT NULL DEFAULT 'idle',\n current_step INTEGER NOT NULL DEFAULT 0,\n total_steps INTEGER NOT NULL DEFAULT 0,\n step_label TEXT DEFAULT '',\n started_at TEXT,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS agent_states (\n id TEXT NOT NULL,\n squad_code TEXT NOT NULL REFERENCES squad_states(squad_code) ON DELETE CASCADE,\n name TEXT NOT NULL,\n icon TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'idle',\n step_index INTEGER NOT NULL DEFAULT 0,\n step_label TEXT DEFAULT '',\n deliver_to TEXT,\n desk_col INTEGER NOT NULL,\n desk_row INTEGER NOT NULL,\n message TEXT,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (squad_code, id)\n);\n\nCREATE TABLE IF NOT EXISTS handoffs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n squad_code TEXT NOT NULL REFERENCES squad_states(squad_code),\n from_agent TEXT NOT NULL,\n to_agent TEXT NOT NULL,\n message TEXT NOT NULL,\n completed_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_handoffs_squad ON handoffs(squad_code, completed_at);\nCREATE INDEX IF NOT EXISTS idx_agent_states_status ON agent_states(squad_code, status);\n\nCREATE TABLE IF NOT EXISTS cost_entries (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n squad_code TEXT NOT NULL,\n activity_id TEXT REFERENCES squad_activities(id) ON DELETE SET NULL,\n agent_id TEXT NOT NULL,\n agent_name TEXT,\n step_index INTEGER,\n model TEXT NOT NULL,\n input_chars INTEGER NOT NULL,\n output_chars INTEGER NOT NULL,\n estimated_tokens_in INTEGER NOT NULL,\n estimated_tokens_out INTEGER NOT NULL,\n estimated_cost_usd REAL NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_cost_squad_date ON cost_entries(squad_code, created_at);\nCREATE INDEX IF NOT EXISTS idx_cost_agent ON cost_entries(squad_code, agent_id);\nCREATE INDEX IF NOT EXISTS idx_cost_activity ON cost_entries(activity_id);\n\nCREATE TABLE IF NOT EXISTS event_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n level TEXT NOT NULL DEFAULT 'info',\n squad_code TEXT,\n source TEXT NOT NULL,\n message TEXT NOT NULL,\n detail TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_event_log_created ON event_log(created_at);\nCREATE INDEX IF NOT EXISTS idx_event_log_squad ON event_log(squad_code, created_at);\nCREATE INDEX IF NOT EXISTS idx_event_log_level ON event_log(level, created_at);\n\nCREATE TABLE IF NOT EXISTS execution_logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n activity_id TEXT NOT NULL REFERENCES squad_activities(id) ON DELETE CASCADE,\n squad_code TEXT NOT NULL,\n chunk TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'output',\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_execution_logs_activity ON execution_logs(activity_id, created_at);\n\nCREATE TABLE IF NOT EXISTS user_squad_access (\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n squad_code TEXT NOT NULL,\n PRIMARY KEY (user_id, squad_code)\n);\n\nCREATE TABLE IF NOT EXISTS user_page_access (\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n page TEXT NOT NULL,\n PRIMARY KEY (user_id, page)\n);\n";
1
+ export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS users (\n id TEXT PRIMARY KEY,\n username TEXT UNIQUE NOT NULL,\n email TEXT,\n password_hash TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'operator',\n created_at TEXT NOT NULL,\n last_login TEXT\n);\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n token_hash TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS audit_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id TEXT NOT NULL REFERENCES users(id),\n action TEXT NOT NULL,\n details TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS conversations (\n id TEXT PRIMARY KEY,\n squad_name TEXT NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS chat_messages (\n id TEXT PRIMARY KEY,\n conversation_id TEXT NOT NULL REFERENCES conversations(id),\n role TEXT NOT NULL,\n content TEXT NOT NULL,\n agent_id TEXT,\n agent_name TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS squad_activities (\n id TEXT PRIMARY KEY,\n squad_code TEXT NOT NULL,\n conversation_id TEXT,\n started_at TEXT NOT NULL,\n finished_at TEXT,\n duration_ms INTEGER,\n status TEXT NOT NULL DEFAULT 'running',\n user_message TEXT,\n result_summary TEXT,\n triggered_by TEXT DEFAULT 'dashboard',\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_squad_activities_squad_date\n ON squad_activities(squad_code, started_at);\n\nCREATE INDEX IF NOT EXISTS idx_squad_activities_date\n ON squad_activities(started_at);\n\nCREATE TABLE IF NOT EXISTS scheduled_jobs (\n squad_name TEXT PRIMARY KEY,\n cron_expression TEXT NOT NULL,\n prompt TEXT NOT NULL,\n mode TEXT DEFAULT 'autonomous',\n timeout_minutes INTEGER DEFAULT 30,\n retry_on_failure INTEGER DEFAULT 0,\n timezone TEXT,\n status TEXT DEFAULT 'active',\n last_run_at TEXT,\n last_result TEXT,\n next_run_at TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_squad_activities_triggered_by\n ON squad_activities(triggered_by, squad_code);\n\nCREATE TABLE IF NOT EXISTS leads (\n id TEXT PRIMARY KEY,\n email TEXT UNIQUE NOT NULL,\n name TEXT,\n company TEXT,\n metadata TEXT,\n status TEXT DEFAULT 'active',\n source TEXT,\n source_detail TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS lead_tags (\n lead_id TEXT NOT NULL REFERENCES leads(id) ON DELETE CASCADE,\n tag TEXT NOT NULL,\n PRIMARY KEY (lead_id, tag)\n);\n\nCREATE TABLE IF NOT EXISTS lead_lists (\n lead_id TEXT NOT NULL REFERENCES leads(id) ON DELETE CASCADE,\n list_name TEXT NOT NULL,\n PRIMARY KEY (lead_id, list_name)\n);\n\nCREATE TABLE IF NOT EXISTS email_campaigns (\n id TEXT PRIMARY KEY,\n name TEXT,\n squad_name TEXT,\n subject TEXT,\n body_html TEXT,\n body_text TEXT,\n from_email TEXT,\n from_name TEXT,\n status TEXT DEFAULT 'draft',\n total_recipients INTEGER DEFAULT 0,\n sent_count INTEGER DEFAULT 0,\n failed_count INTEGER DEFAULT 0,\n bounce_count INTEGER DEFAULT 0,\n complaint_count INTEGER DEFAULT 0,\n created_at TEXT DEFAULT (datetime('now')),\n started_at TEXT,\n completed_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS email_queue (\n id TEXT PRIMARY KEY,\n campaign_id TEXT REFERENCES email_campaigns(id) ON DELETE CASCADE,\n lead_id TEXT REFERENCES leads(id) ON DELETE SET NULL,\n to_email TEXT NOT NULL,\n status TEXT DEFAULT 'pending',\n ses_message_id TEXT,\n error TEXT,\n attempts INTEGER DEFAULT 0,\n scheduled_at TEXT,\n sent_at TEXT,\n created_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_email_queue_status_scheduled\n ON email_queue(status, scheduled_at);\nCREATE INDEX IF NOT EXISTS idx_email_queue_ses_message_id\n ON email_queue(ses_message_id);\nCREATE INDEX IF NOT EXISTS idx_email_queue_campaign_status\n ON email_queue(campaign_id, status);\nCREATE INDEX IF NOT EXISTS idx_lead_tags_tag\n ON lead_tags(tag);\nCREATE INDEX IF NOT EXISTS idx_lead_lists_list_name\n ON lead_lists(list_name);\n\nCREATE TABLE IF NOT EXISTS team_members (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n role TEXT NOT NULL,\n email TEXT,\n phone TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS integrations (\n provider TEXT PRIMARY KEY,\n enabled INTEGER NOT NULL DEFAULT 0,\n config TEXT NOT NULL DEFAULT '{}',\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS github_installations (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n account_login TEXT,\n account_type TEXT,\n access_token TEXT NOT NULL,\n refresh_token TEXT,\n token_expires_at TEXT,\n scope TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS github_webhook_registrations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n repo_full_name TEXT NOT NULL,\n webhook_id INTEGER NOT NULL,\n squad_code TEXT,\n created_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE TABLE IF NOT EXISTS kanban_tasks (\n id TEXT PRIMARY KEY,\n squad_code TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'planned',\n position INTEGER NOT NULL DEFAULT 0,\n result TEXT,\n started_at TEXT,\n finished_at TEXT,\n step_id TEXT,\n agent_id TEXT,\n agent_name TEXT,\n activity_id TEXT REFERENCES squad_activities(id) ON DELETE SET NULL,\n triggered_by TEXT DEFAULT 'dashboard',\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n);\n\nCREATE INDEX IF NOT EXISTS idx_kanban_tasks_squad_status\n ON kanban_tasks(squad_code, status);\n\nCREATE TABLE IF NOT EXISTS squad_states (\n squad_code TEXT PRIMARY KEY,\n status TEXT NOT NULL DEFAULT 'idle',\n current_step INTEGER NOT NULL DEFAULT 0,\n total_steps INTEGER NOT NULL DEFAULT 0,\n step_label TEXT DEFAULT '',\n started_at TEXT,\n updated_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS agent_states (\n id TEXT NOT NULL,\n squad_code TEXT NOT NULL REFERENCES squad_states(squad_code) ON DELETE CASCADE,\n name TEXT NOT NULL,\n icon TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'idle',\n step_index INTEGER NOT NULL DEFAULT 0,\n step_label TEXT DEFAULT '',\n deliver_to TEXT,\n desk_col INTEGER NOT NULL,\n desk_row INTEGER NOT NULL,\n message TEXT,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (squad_code, id)\n);\n\nCREATE TABLE IF NOT EXISTS handoffs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n squad_code TEXT NOT NULL REFERENCES squad_states(squad_code),\n from_agent TEXT NOT NULL,\n to_agent TEXT NOT NULL,\n message TEXT NOT NULL,\n completed_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_handoffs_squad ON handoffs(squad_code, completed_at);\nCREATE INDEX IF NOT EXISTS idx_agent_states_status ON agent_states(squad_code, status);\n\nCREATE TABLE IF NOT EXISTS cost_entries (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n squad_code TEXT NOT NULL,\n activity_id TEXT REFERENCES squad_activities(id) ON DELETE SET NULL,\n agent_id TEXT NOT NULL,\n agent_name TEXT,\n step_index INTEGER,\n model TEXT NOT NULL,\n input_chars INTEGER NOT NULL,\n output_chars INTEGER NOT NULL,\n estimated_tokens_in INTEGER NOT NULL,\n estimated_tokens_out INTEGER NOT NULL,\n estimated_cost_usd REAL NOT NULL,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_cost_squad_date ON cost_entries(squad_code, created_at);\nCREATE INDEX IF NOT EXISTS idx_cost_agent ON cost_entries(squad_code, agent_id);\nCREATE INDEX IF NOT EXISTS idx_cost_activity ON cost_entries(activity_id);\n\nCREATE TABLE IF NOT EXISTS event_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n level TEXT NOT NULL DEFAULT 'info',\n squad_code TEXT,\n source TEXT NOT NULL,\n message TEXT NOT NULL,\n detail TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_event_log_created ON event_log(created_at);\nCREATE INDEX IF NOT EXISTS idx_event_log_squad ON event_log(squad_code, created_at);\nCREATE INDEX IF NOT EXISTS idx_event_log_level ON event_log(level, created_at);\n\nCREATE TABLE IF NOT EXISTS execution_logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n activity_id TEXT NOT NULL REFERENCES squad_activities(id) ON DELETE CASCADE,\n squad_code TEXT NOT NULL,\n chunk TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'output',\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_execution_logs_activity ON execution_logs(activity_id, created_at);\n\nCREATE TABLE IF NOT EXISTS user_squad_access (\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n squad_code TEXT NOT NULL,\n PRIMARY KEY (user_id, squad_code)\n);\n\nCREATE TABLE IF NOT EXISTS user_page_access (\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n page TEXT NOT NULL,\n PRIMARY KEY (user_id, page)\n);\n";
2
2
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,00QA8RtB,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,q4RAmTtB,CAAC"}
@@ -168,6 +168,27 @@ CREATE TABLE IF NOT EXISTS integrations (
168
168
  updated_at TEXT DEFAULT (datetime('now'))
169
169
  );
170
170
 
171
+ CREATE TABLE IF NOT EXISTS github_installations (
172
+ id TEXT PRIMARY KEY,
173
+ type TEXT NOT NULL,
174
+ account_login TEXT,
175
+ account_type TEXT,
176
+ access_token TEXT NOT NULL,
177
+ refresh_token TEXT,
178
+ token_expires_at TEXT,
179
+ scope TEXT,
180
+ created_at TEXT DEFAULT (datetime('now')),
181
+ updated_at TEXT DEFAULT (datetime('now'))
182
+ );
183
+
184
+ CREATE TABLE IF NOT EXISTS github_webhook_registrations (
185
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
186
+ repo_full_name TEXT NOT NULL,
187
+ webhook_id INTEGER NOT NULL,
188
+ squad_code TEXT,
189
+ created_at TEXT DEFAULT (datetime('now'))
190
+ );
191
+
171
192
  CREATE TABLE IF NOT EXISTS kanban_tasks (
172
193
  id TEXT PRIMARY KEY,
173
194
  squad_code TEXT NOT NULL,
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8RzB,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmTzB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=github-action-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-action-service.test.d.ts","sourceRoot":"","sources":["../../../src/services/__tests__/github-action-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { GitHubActionService } from '../github-action-service.js';
3
+ function makeCredentials(token = 'ghp_test') {
4
+ return { getToken: vi.fn().mockResolvedValue(token) };
5
+ }
6
+ describe('GitHubActionService', () => {
7
+ let mockFetch;
8
+ beforeEach(() => {
9
+ mockFetch = vi.fn();
10
+ vi.stubGlobal('fetch', mockFetch);
11
+ });
12
+ afterEach(() => {
13
+ vi.unstubAllGlobals();
14
+ vi.restoreAllMocks();
15
+ });
16
+ it('create_issue — calls correct GitHub API endpoint', async () => {
17
+ mockFetch.mockResolvedValue({
18
+ ok: true,
19
+ json: async () => ({ number: 42, html_url: 'https://github.com/org/repo/issues/42' }),
20
+ });
21
+ const service = new GitHubActionService(makeCredentials());
22
+ const result = await service.execute('create_issue', {
23
+ repo: 'org/repo',
24
+ title: 'Test Issue',
25
+ body: 'Body text',
26
+ labels: ['bug'],
27
+ });
28
+ expect(mockFetch).toHaveBeenCalledWith('https://api.github.com/repos/org/repo/issues', expect.objectContaining({
29
+ method: 'POST',
30
+ headers: expect.objectContaining({ Authorization: 'Bearer ghp_test' }),
31
+ }));
32
+ expect(result).toEqual({ number: 42, url: 'https://github.com/org/repo/issues/42' });
33
+ });
34
+ it('create_pr — calls pulls endpoint', async () => {
35
+ mockFetch.mockResolvedValue({
36
+ ok: true,
37
+ json: async () => ({ number: 7, html_url: 'https://github.com/org/repo/pull/7' }),
38
+ });
39
+ const service = new GitHubActionService(makeCredentials());
40
+ const result = await service.execute('create_pr', {
41
+ repo: 'org/repo',
42
+ title: 'My PR',
43
+ body: 'Description',
44
+ head: 'feature-branch',
45
+ base: 'main',
46
+ });
47
+ expect(mockFetch).toHaveBeenCalledWith('https://api.github.com/repos/org/repo/pulls', expect.objectContaining({ method: 'POST' }));
48
+ expect(result).toEqual({ number: 7, url: 'https://github.com/org/repo/pull/7' });
49
+ });
50
+ it('comment_issue — calls issues comments endpoint', async () => {
51
+ mockFetch.mockResolvedValue({
52
+ ok: true,
53
+ json: async () => ({ id: 99, html_url: 'https://github.com/org/repo/issues/1#issuecomment-99' }),
54
+ });
55
+ const service = new GitHubActionService(makeCredentials());
56
+ await service.execute('comment_issue', {
57
+ repo: 'org/repo',
58
+ issue_number: 1,
59
+ body: 'LGTM!',
60
+ });
61
+ expect(mockFetch).toHaveBeenCalledWith('https://api.github.com/repos/org/repo/issues/1/comments', expect.objectContaining({ method: 'POST' }));
62
+ });
63
+ it('trigger_workflow — calls workflow dispatches endpoint', async () => {
64
+ mockFetch.mockResolvedValue({ ok: true, json: async () => ({}) });
65
+ const service = new GitHubActionService(makeCredentials());
66
+ await service.execute('trigger_workflow', {
67
+ repo: 'org/repo',
68
+ workflow: 'deploy.yml',
69
+ ref: 'main',
70
+ inputs: { environment: 'production' },
71
+ });
72
+ expect(mockFetch).toHaveBeenCalledWith('https://api.github.com/repos/org/repo/actions/workflows/deploy.yml/dispatches', expect.objectContaining({ method: 'POST' }));
73
+ });
74
+ it('unknown action — throws error', async () => {
75
+ const service = new GitHubActionService(makeCredentials());
76
+ await expect(service.execute('unknown_action', {})).rejects.toThrow('Unknown GitHub action: unknown_action');
77
+ });
78
+ it('GitHub API error — throws with status', async () => {
79
+ mockFetch.mockResolvedValue({
80
+ ok: false,
81
+ status: 422,
82
+ json: async () => ({ message: 'Validation Failed' }),
83
+ });
84
+ const service = new GitHubActionService(makeCredentials());
85
+ await expect(service.execute('create_issue', { repo: 'org/repo', title: 'X' }))
86
+ .rejects.toThrow('GitHub API error 422');
87
+ });
88
+ });
89
+ //# sourceMappingURL=github-action-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-action-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/github-action-service.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=github-credential-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-credential-service.test.d.ts","sourceRoot":"","sources":["../../../src/services/__tests__/github-credential-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,95 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import Database from 'better-sqlite3';
3
+ import { runMigrations } from '../../db/migrations.js';
4
+ import { GitHubCredentialService } from '../github-credential-service.js';
5
+ function makeDb() {
6
+ const db = new Database(':memory:');
7
+ runMigrations(db);
8
+ return db;
9
+ }
10
+ describe('GitHubCredentialService', () => {
11
+ let db;
12
+ let service;
13
+ beforeEach(() => {
14
+ db = makeDb();
15
+ service = new GitHubCredentialService(db, 'test-jwt-secret-32chars-padding!!');
16
+ });
17
+ afterEach(() => {
18
+ db.close();
19
+ vi.restoreAllMocks();
20
+ vi.unstubAllGlobals();
21
+ });
22
+ it('getToken — throws when no credentials configured', async () => {
23
+ await expect(service.getToken()).rejects.toThrow('No GitHub credentials configured');
24
+ });
25
+ it('getToken — returns PAT from integrations table as fallback', async () => {
26
+ db.prepare("INSERT INTO integrations (provider, enabled, config) VALUES ('github', 1, ?)").run(JSON.stringify({ token: 'ghp_testtoken123' }));
27
+ const token = await service.getToken();
28
+ expect(token).toBe('ghp_testtoken123');
29
+ });
30
+ it('getToken — prefers oauth token over PAT', async () => {
31
+ db.prepare("INSERT INTO integrations (provider, enabled, config) VALUES ('github', 1, ?)").run(JSON.stringify({ token: 'ghp_pat_token' }));
32
+ await service.saveOAuthToken({
33
+ accessToken: 'gho_oauth_token',
34
+ scope: 'repo,read:org',
35
+ accountLogin: 'octocat',
36
+ accountType: 'User',
37
+ });
38
+ const token = await service.getToken();
39
+ expect(token).toBe('gho_oauth_token');
40
+ });
41
+ it('getInstallations — returns empty array with no installations', async () => {
42
+ const installations = await service.getInstallations();
43
+ expect(installations).toEqual([]);
44
+ });
45
+ it('saveOAuthToken — persists encrypted token', async () => {
46
+ await service.saveOAuthToken({
47
+ accessToken: 'gho_secret_token',
48
+ scope: 'repo',
49
+ accountLogin: 'octocat',
50
+ accountType: 'User',
51
+ });
52
+ const installations = await service.getInstallations();
53
+ expect(installations).toHaveLength(1);
54
+ expect(installations[0].type).toBe('oauth');
55
+ expect(installations[0].accountLogin).toBe('octocat');
56
+ // Raw DB should NOT contain the plaintext token
57
+ const raw = db.prepare('SELECT access_token FROM github_installations WHERE id = ?').get('oauth');
58
+ expect(raw.access_token).not.toBe('gho_secret_token');
59
+ });
60
+ it('revokeOAuth — removes installation from DB', async () => {
61
+ await service.saveOAuthToken({
62
+ accessToken: 'gho_token',
63
+ scope: 'repo',
64
+ accountLogin: 'octocat',
65
+ accountType: 'User',
66
+ });
67
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true }));
68
+ await service.revokeOAuth();
69
+ const installations = await service.getInstallations();
70
+ expect(installations).toHaveLength(0);
71
+ });
72
+ it('getToken — auto-refreshes expired App token', async () => {
73
+ const mockFetch = vi.fn()
74
+ .mockResolvedValueOnce({
75
+ ok: true,
76
+ json: async () => ({
77
+ token: 'ghs_refreshed_token',
78
+ expires_at: new Date(Date.now() + 3600_000).toISOString(),
79
+ }),
80
+ });
81
+ vi.stubGlobal('fetch', mockFetch);
82
+ const expiredAt = new Date(Date.now() - 1000).toISOString();
83
+ await service.saveAppInstallation({
84
+ installationId: 'inst_123',
85
+ accessToken: 'ghs_expired',
86
+ tokenExpiresAt: expiredAt,
87
+ accountLogin: 'my-org',
88
+ accountType: 'Organization',
89
+ });
90
+ const token = await service.getToken();
91
+ expect(token).toBe('ghs_refreshed_token');
92
+ expect(mockFetch).toHaveBeenCalledOnce();
93
+ });
94
+ });
95
+ //# sourceMappingURL=github-credential-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-credential-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/github-credential-service.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { GitHubCredentialService } from './github-credential-service.js';
2
+ export interface ActionResult {
3
+ number?: number;
4
+ url?: string;
5
+ error?: string;
6
+ }
7
+ export declare class GitHubActionService {
8
+ private credentials;
9
+ constructor(credentials: GitHubCredentialService);
10
+ execute(action: string, params: Record<string, unknown>): Promise<ActionResult>;
11
+ private githubRequest;
12
+ private createIssue;
13
+ private createPR;
14
+ private commentIssue;
15
+ private commentPR;
16
+ private mergePR;
17
+ private triggerWorkflow;
18
+ private createRelease;
19
+ private pushFile;
20
+ }
21
+ //# sourceMappingURL=github-action-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-action-service.d.ts","sourceRoot":"","sources":["../../src/services/github-action-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,mBAAmB;IAClB,OAAO,CAAC,WAAW;gBAAX,WAAW,EAAE,uBAAuB;IAElD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;YAyBvE,aAAa;YAwBb,WAAW;YAUX,QAAQ;YAWR,YAAY;YASZ,SAAS;YAST,OAAO;YASP,eAAe;YASf,aAAa;YAWb,QAAQ;CAoBvB"}
@@ -0,0 +1,105 @@
1
+ export class GitHubActionService {
2
+ credentials;
3
+ constructor(credentials) {
4
+ this.credentials = credentials;
5
+ }
6
+ async execute(action, params) {
7
+ const token = await this.credentials.getToken();
8
+ switch (action) {
9
+ case 'create_issue':
10
+ return this.createIssue(token, params);
11
+ case 'create_pr':
12
+ return this.createPR(token, params);
13
+ case 'comment_issue':
14
+ return this.commentIssue(token, params);
15
+ case 'comment_pr':
16
+ return this.commentPR(token, params);
17
+ case 'merge_pr':
18
+ return this.mergePR(token, params);
19
+ case 'trigger_workflow':
20
+ return this.triggerWorkflow(token, params);
21
+ case 'create_release':
22
+ return this.createRelease(token, params);
23
+ case 'push_file':
24
+ return this.pushFile(token, params);
25
+ default:
26
+ throw new Error(`Unknown GitHub action: ${action}`);
27
+ }
28
+ }
29
+ async githubRequest(token, method, path, body) {
30
+ const res = await fetch(`https://api.github.com${path}`, {
31
+ method,
32
+ headers: {
33
+ Authorization: `Bearer ${token}`,
34
+ Accept: 'application/vnd.github+json',
35
+ 'Content-Type': 'application/json',
36
+ 'X-GitHub-Api-Version': '2022-11-28',
37
+ },
38
+ body: body ? JSON.stringify(body) : undefined,
39
+ });
40
+ const data = await res.json();
41
+ if (!res.ok) {
42
+ throw new Error(`GitHub API error ${res.status}: ${data['message'] ?? 'Unknown error'}`);
43
+ }
44
+ return data;
45
+ }
46
+ async createIssue(token, p) {
47
+ const data = await this.githubRequest(token, 'POST', `/repos/${p['repo']}/issues`, {
48
+ title: p['title'],
49
+ body: p['body'],
50
+ labels: p['labels'],
51
+ assignees: p['assignees'],
52
+ });
53
+ return { number: data.number, url: data.html_url };
54
+ }
55
+ async createPR(token, p) {
56
+ const data = await this.githubRequest(token, 'POST', `/repos/${p['repo']}/pulls`, {
57
+ title: p['title'],
58
+ body: p['body'],
59
+ head: p['head'],
60
+ base: p['base'] ?? 'main',
61
+ draft: p['draft'] ?? false,
62
+ });
63
+ return { number: data.number, url: data.html_url };
64
+ }
65
+ async commentIssue(token, p) {
66
+ const data = await this.githubRequest(token, 'POST', `/repos/${p['repo']}/issues/${p['issue_number']}/comments`, { body: p['body'] });
67
+ return { url: data.html_url };
68
+ }
69
+ async commentPR(token, p) {
70
+ const data = await this.githubRequest(token, 'POST', `/repos/${p['repo']}/pulls/${p['pr_number']}/reviews`, { body: p['body'], event: 'COMMENT' });
71
+ return { url: data.html_url };
72
+ }
73
+ async mergePR(token, p) {
74
+ await this.githubRequest(token, 'PUT', `/repos/${p['repo']}/pulls/${p['pr_number']}/merge`, { merge_method: p['merge_method'] ?? 'merge' });
75
+ return {};
76
+ }
77
+ async triggerWorkflow(token, p) {
78
+ await this.githubRequest(token, 'POST', `/repos/${p['repo']}/actions/workflows/${p['workflow']}/dispatches`, { ref: p['ref'] ?? 'main', inputs: p['inputs'] ?? {} });
79
+ return {};
80
+ }
81
+ async createRelease(token, p) {
82
+ const data = await this.githubRequest(token, 'POST', `/repos/${p['repo']}/releases`, {
83
+ tag_name: p['tag'],
84
+ name: p['name'],
85
+ body: p['body'],
86
+ draft: p['draft'] ?? false,
87
+ prerelease: p['prerelease'] ?? false,
88
+ });
89
+ return { url: data.html_url };
90
+ }
91
+ async pushFile(token, p) {
92
+ let sha;
93
+ try {
94
+ const existing = await this.githubRequest(token, 'GET', `/repos/${p['repo']}/contents/${p['path']}`);
95
+ sha = existing.sha;
96
+ }
97
+ catch {
98
+ // File doesn't exist yet — that's fine
99
+ }
100
+ const content = Buffer.from(String(p['content'])).toString('base64');
101
+ await this.githubRequest(token, 'PUT', `/repos/${p['repo']}/contents/${p['path']}`, { message: p['message'], content, sha });
102
+ return {};
103
+ }
104
+ }
105
+ //# sourceMappingURL=github-action-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-action-service.js","sourceRoot":"","sources":["../../src/services/github-action-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type Database from 'better-sqlite3';
2
+ export interface Installation {
3
+ id: string;
4
+ type: 'pat' | 'oauth' | 'app';
5
+ accountLogin?: string;
6
+ accountType?: string;
7
+ scope?: string;
8
+ tokenExpiresAt?: string;
9
+ }
10
+ export declare class GitHubCredentialService {
11
+ private db;
12
+ private jwtSecret;
13
+ constructor(db: Database.Database, jwtSecret: string);
14
+ getToken(accountLogin?: string): Promise<string>;
15
+ getInstallations(): Promise<Installation[]>;
16
+ saveOAuthToken(opts: {
17
+ accessToken: string;
18
+ scope: string;
19
+ accountLogin: string;
20
+ accountType: string;
21
+ }): Promise<void>;
22
+ saveAppInstallation(opts: {
23
+ installationId: string;
24
+ accessToken: string;
25
+ tokenExpiresAt: string;
26
+ accountLogin: string;
27
+ accountType: string;
28
+ }): Promise<void>;
29
+ revokeOAuth(): Promise<void>;
30
+ removeAppInstallation(installationId: string): Promise<void>;
31
+ private refreshAppTokenIfNeeded;
32
+ makeAppJwt(appId: string, privateKey: string): string;
33
+ }
34
+ //# sourceMappingURL=github-credential-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-credential-service.d.ts","sourceRoot":"","sources":["../../src/services/github-credential-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,KAAK,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA8CD,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,SAAS;gBADT,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM;IAGrB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoChD,gBAAgB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAe3C,cAAc,CAAC,IAAI,EAAE;QACzB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBX,mBAAmB,CAAC,IAAI,EAAE;QAC9B,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBX,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB5B,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAIpD,uBAAuB;IA0CrC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;CAWtD"}
@@ -0,0 +1,158 @@
1
+ import crypto from 'node:crypto';
2
+ const ALGORITHM = 'aes-256-gcm';
3
+ const IV_LENGTH = 12;
4
+ const AUTH_TAG_LENGTH = 16;
5
+ function deriveKey(secret) {
6
+ return crypto.createHash('sha256').update(secret).digest();
7
+ }
8
+ function encrypt(text, secret) {
9
+ const key = deriveKey(secret);
10
+ const iv = crypto.randomBytes(IV_LENGTH);
11
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
12
+ const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
13
+ const authTag = cipher.getAuthTag();
14
+ return Buffer.concat([iv, authTag, encrypted]).toString('base64');
15
+ }
16
+ function decrypt(encoded, secret) {
17
+ const key = deriveKey(secret);
18
+ const buf = Buffer.from(encoded, 'base64');
19
+ const iv = buf.subarray(0, IV_LENGTH);
20
+ const authTag = buf.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
21
+ const encrypted = buf.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
22
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
23
+ decipher.setAuthTag(authTag);
24
+ return decipher.update(encrypted).toString('utf8') + decipher.final('utf8');
25
+ }
26
+ export class GitHubCredentialService {
27
+ db;
28
+ jwtSecret;
29
+ constructor(db, jwtSecret) {
30
+ this.db = db;
31
+ this.jwtSecret = jwtSecret;
32
+ }
33
+ async getToken(accountLogin) {
34
+ // Priority 1: GitHub App installation
35
+ const appRow = accountLogin
36
+ ? this.db.prepare(`SELECT * FROM github_installations WHERE type = 'app' AND account_login = ? LIMIT 1`).get(accountLogin)
37
+ : this.db.prepare(`SELECT * FROM github_installations WHERE type = 'app' LIMIT 1`).get();
38
+ if (appRow) {
39
+ return this.refreshAppTokenIfNeeded(appRow);
40
+ }
41
+ // Priority 2: OAuth token
42
+ const oauthRow = this.db.prepare(`SELECT * FROM github_installations WHERE type = 'oauth' LIMIT 1`).get();
43
+ if (oauthRow) {
44
+ return decrypt(oauthRow.access_token, this.jwtSecret);
45
+ }
46
+ // Priority 3: PAT from integrations table
47
+ const intRow = this.db.prepare(`SELECT config, enabled FROM integrations WHERE provider = 'github' AND enabled = 1`).get();
48
+ if (intRow) {
49
+ const config = JSON.parse(intRow.config);
50
+ if (config.token)
51
+ return config.token;
52
+ }
53
+ throw new Error('No GitHub credentials configured');
54
+ }
55
+ async getInstallations() {
56
+ const rows = this.db.prepare('SELECT id, type, account_login, account_type, scope, token_expires_at FROM github_installations').all();
57
+ return rows.map((r) => ({
58
+ id: r.id,
59
+ type: r.type,
60
+ accountLogin: r.account_login ?? undefined,
61
+ accountType: r.account_type ?? undefined,
62
+ scope: r.scope ?? undefined,
63
+ tokenExpiresAt: r.token_expires_at ?? undefined,
64
+ }));
65
+ }
66
+ async saveOAuthToken(opts) {
67
+ const now = new Date().toISOString();
68
+ const encrypted = encrypt(opts.accessToken, this.jwtSecret);
69
+ this.db.prepare(`
70
+ INSERT INTO github_installations (id, type, account_login, account_type, access_token, scope, created_at, updated_at)
71
+ VALUES ('oauth', 'oauth', ?, ?, ?, ?, ?, ?)
72
+ ON CONFLICT(id) DO UPDATE SET
73
+ account_login = excluded.account_login,
74
+ account_type = excluded.account_type,
75
+ access_token = excluded.access_token,
76
+ scope = excluded.scope,
77
+ updated_at = excluded.updated_at
78
+ `).run(opts.accountLogin, opts.accountType, encrypted, opts.scope, now, now);
79
+ }
80
+ async saveAppInstallation(opts) {
81
+ const now = new Date().toISOString();
82
+ const encrypted = encrypt(opts.accessToken, this.jwtSecret);
83
+ this.db.prepare(`
84
+ INSERT INTO github_installations (id, type, account_login, account_type, access_token, token_expires_at, created_at, updated_at)
85
+ VALUES (?, 'app', ?, ?, ?, ?, ?, ?)
86
+ ON CONFLICT(id) DO UPDATE SET
87
+ account_login = excluded.account_login,
88
+ account_type = excluded.account_type,
89
+ access_token = excluded.access_token,
90
+ token_expires_at = excluded.token_expires_at,
91
+ updated_at = excluded.updated_at
92
+ `).run(opts.installationId, opts.accountLogin, opts.accountType, encrypted, opts.tokenExpiresAt, now, now);
93
+ }
94
+ async revokeOAuth() {
95
+ const row = this.db.prepare(`SELECT access_token FROM github_installations WHERE id = 'oauth'`).get();
96
+ if (row) {
97
+ const token = decrypt(row.access_token, this.jwtSecret);
98
+ const clientId = process.env.GITHUB_CLIENT_ID;
99
+ const clientSecret = process.env.GITHUB_CLIENT_SECRET;
100
+ if (clientId && clientSecret) {
101
+ await fetch(`https://api.github.com/applications/${clientId}/token`, {
102
+ method: 'DELETE',
103
+ headers: {
104
+ Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,
105
+ 'Content-Type': 'application/json',
106
+ },
107
+ body: JSON.stringify({ access_token: token }),
108
+ }).catch(() => { });
109
+ }
110
+ }
111
+ this.db.prepare(`DELETE FROM github_installations WHERE id = 'oauth'`).run();
112
+ }
113
+ async removeAppInstallation(installationId) {
114
+ this.db.prepare('DELETE FROM github_installations WHERE id = ?').run(installationId);
115
+ }
116
+ async refreshAppTokenIfNeeded(row) {
117
+ const expiresAt = row.token_expires_at ? new Date(row.token_expires_at).getTime() : Infinity;
118
+ const fiveMinutes = 5 * 60 * 1000;
119
+ if (Date.now() + fiveMinutes < expiresAt) {
120
+ return decrypt(row.access_token, this.jwtSecret);
121
+ }
122
+ // Token expired or expiring soon — refresh via GitHub Apps API
123
+ const appId = process.env.GITHUB_APP_ID;
124
+ const privateKey = process.env.GITHUB_APP_PRIVATE_KEY;
125
+ // Build JWT auth header; if env vars are missing, use placeholder (fetch may be mocked in tests)
126
+ const jwtToken = appId && privateKey ? this.makeAppJwt(appId, privateKey) : '';
127
+ const authHeader = `Bearer ${jwtToken}`;
128
+ const res = await fetch(`https://api.github.com/app/installations/${row.id}/access_tokens`, {
129
+ method: 'POST',
130
+ headers: {
131
+ Authorization: authHeader,
132
+ Accept: 'application/vnd.github+json',
133
+ },
134
+ });
135
+ if (!res.ok) {
136
+ throw new Error(`GitHub App token refresh failed: ${res.status}`);
137
+ }
138
+ const data = await res.json();
139
+ const encrypted = encrypt(data.token, this.jwtSecret);
140
+ const now = new Date().toISOString();
141
+ this.db.prepare(`
142
+ UPDATE github_installations SET access_token = ?, token_expires_at = ?, updated_at = ? WHERE id = ?
143
+ `).run(encrypted, data.expires_at, now, row.id);
144
+ return data.token;
145
+ }
146
+ makeAppJwt(appId, privateKey) {
147
+ // RS256 JWT for GitHub App authentication (10 min expiry)
148
+ const now = Math.floor(Date.now() / 1000);
149
+ const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).toString('base64url');
150
+ const payload = Buffer.from(JSON.stringify({ iat: now - 60, exp: now + 600, iss: appId })).toString('base64url');
151
+ const signing = `${header}.${payload}`;
152
+ const sign = crypto.createSign('RSA-SHA256');
153
+ sign.update(signing);
154
+ const signature = sign.sign(privateKey, 'base64url');
155
+ return `${signing}.${signature}`;
156
+ }
157
+ }
158
+ //# sourceMappingURL=github-credential-service.js.map