heyio 0.42.0 → 1.0.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 (100) hide show
  1. package/README.md +40 -52
  2. package/dist/api/auth.js +35 -38
  3. package/dist/api/server.js +157 -1139
  4. package/dist/config.js +49 -32
  5. package/dist/copilot/agents.js +72 -1055
  6. package/dist/copilot/client.js +6 -17
  7. package/dist/copilot/io-scheduler.js +55 -139
  8. package/dist/copilot/model-router.js +100 -72
  9. package/dist/copilot/orchestrator.js +91 -515
  10. package/dist/copilot/scheduler.js +67 -189
  11. package/dist/copilot/skills.js +41 -366
  12. package/dist/copilot/system-message.js +40 -200
  13. package/dist/copilot/tools.js +191 -2042
  14. package/dist/daemon.js +54 -201
  15. package/dist/index.js +15 -133
  16. package/dist/mcp/config.js +23 -31
  17. package/dist/mcp/index.js +2 -3
  18. package/dist/mcp/registry.js +33 -88
  19. package/dist/notify.js +18 -100
  20. package/dist/paths.js +13 -24
  21. package/dist/setup.js +35 -0
  22. package/dist/store/db.js +111 -297
  23. package/dist/store/feed.js +29 -97
  24. package/dist/store/instances.js +56 -121
  25. package/dist/store/schedules.js +21 -73
  26. package/dist/store/squads.js +35 -186
  27. package/dist/store/tasks.js +25 -168
  28. package/dist/telegram/bot.js +20 -312
  29. package/dist/telegram/handlers.js +39 -3
  30. package/dist/watchdog.js +31 -45
  31. package/dist/wiki/fs.js +38 -155
  32. package/dist/wiki/search.js +31 -44
  33. package/package.json +5 -8
  34. package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
  35. package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
  36. package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
  37. package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
  38. package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
  39. package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
  40. package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
  41. package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
  42. package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
  43. package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
  44. package/web-dist/assets/api-WGvTsXaE.js +1 -0
  45. package/web-dist/assets/index-D7M5O-_l.css +1 -0
  46. package/web-dist/assets/index-DZOS9syn.js +95 -0
  47. package/web-dist/assets/plus-BOvyX1BC.js +6 -0
  48. package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
  49. package/web-dist/favicon.svg +4 -1
  50. package/web-dist/index.html +7 -10
  51. package/dist/api/logout.test.js +0 -129
  52. package/dist/api/mcp.test.js +0 -285
  53. package/dist/api/wiki.test.js +0 -283
  54. package/dist/auth/session-logic.js +0 -79
  55. package/dist/auth/session-logic.test.js +0 -201
  56. package/dist/copilot/auto-complete-instance.test.js +0 -104
  57. package/dist/copilot/cron.js +0 -136
  58. package/dist/copilot/event-summary.js +0 -286
  59. package/dist/copilot/instance-deactivate.test.js +0 -119
  60. package/dist/copilot/model-router.test.js +0 -71
  61. package/dist/copilot/review-backfill.js +0 -57
  62. package/dist/copilot/session-timeout.js +0 -112
  63. package/dist/copilot/session-timeout.test.js +0 -372
  64. package/dist/copilot/skills.test.js +0 -55
  65. package/dist/copilot/universes.js +0 -469
  66. package/dist/instance-watchdog.js +0 -104
  67. package/dist/instance-watchdog.test.js +0 -183
  68. package/dist/mcp/client.js +0 -109
  69. package/dist/mcp/client.test.js +0 -99
  70. package/dist/mcp/config.test.js +0 -49
  71. package/dist/mcp/registry.test.js +0 -79
  72. package/dist/notify.test.js +0 -232
  73. package/dist/store/feed.test.js +0 -279
  74. package/dist/store/instances.test.js +0 -310
  75. package/dist/store/io-schedules.js +0 -63
  76. package/dist/store/notifications.js +0 -79
  77. package/dist/store/notifications.test.js +0 -197
  78. package/dist/store/schedule-runs.js +0 -46
  79. package/dist/store/squads.test.js +0 -405
  80. package/dist/store/tasks.test.js +0 -150
  81. package/dist/store/worktrees.js +0 -83
  82. package/dist/tui/index.js +0 -286
  83. package/dist/update.js +0 -81
  84. package/dist/watchdog.test.js +0 -83
  85. package/dist/wiki/wiki-squad.test.js +0 -54
  86. package/web-dist/assets/AgentActivityView-CedxxE6K.js +0 -1
  87. package/web-dist/assets/ChatView-DMkYQo_V.js +0 -4
  88. package/web-dist/assets/FeedView-BH4q-31V.js +0 -1
  89. package/web-dist/assets/InboxView-BVwVP4EW.js +0 -1
  90. package/web-dist/assets/LoginView-DRPDhnwu.js +0 -1
  91. package/web-dist/assets/McpView-D8yWz-lq.js +0 -1
  92. package/web-dist/assets/SchedulesView-BzzyncGF.js +0 -1
  93. package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-oW3ySu7Y.js +0 -1
  94. package/web-dist/assets/SkillsView-oxpYuhx7.js +0 -1
  95. package/web-dist/assets/SquadsView-CaKUIKlq.js +0 -1
  96. package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-8U15Qp_Q.js +0 -1
  97. package/web-dist/assets/WikiView-C5jXUlfW.js +0 -1
  98. package/web-dist/assets/index-BrWzNw-N.css +0 -10
  99. package/web-dist/assets/index-f67odrrt.js +0 -81
  100. package/web-dist/icons.svg +0 -24
@@ -1,112 +1,44 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { getDb } from "./db.js";
2
- export function createFeedEntry(input) {
3
+ export function postFeedItem(source, title, content) {
3
4
  const db = getDb();
4
- const info = db
5
- .prepare(`INSERT INTO unified_feed (type, title, body, source_type, source_ref, squad_slug, instance_id, task_id)
6
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
7
- .run(input.type, input.title, input.body, input.source_type ?? null, input.source_ref ?? null, input.squad_slug ?? null, input.instance_id ?? null, input.task_id ?? null);
8
- return db
9
- .prepare("SELECT * FROM unified_feed WHERE id = ?")
10
- .get(info.lastInsertRowid);
5
+ const id = randomUUID();
6
+ db.prepare("INSERT INTO feed_items (id, source, title, content) VALUES (?, ?, ?, ?)").run(id, source, title, content);
7
+ return db.prepare("SELECT * FROM feed_items WHERE id = ?").get(id);
11
8
  }
12
- export function listFeedEntries(opts) {
13
- const conditions = [];
9
+ export function getFeedItems(opts) {
10
+ const db = getDb();
11
+ let sql = "SELECT * FROM feed_items WHERE 1=1";
14
12
  const params = [];
15
- if (opts?.type) {
16
- conditions.push("type = ?");
17
- params.push(opts.type);
18
- }
19
13
  if (opts?.unreadOnly) {
20
- conditions.push("read_at IS NULL");
14
+ sql += " AND read = 0";
21
15
  }
22
- if (opts?.search) {
23
- conditions.push("(title LIKE ? OR body LIKE ?)");
24
- const term = `%${opts.search}%`;
25
- params.push(term, term);
16
+ if (opts?.source) {
17
+ sql += " AND source = ?";
18
+ params.push(opts.source);
26
19
  }
27
- if (opts?.squad) {
28
- conditions.push("title LIKE ?");
29
- params.push(`[${opts.squad}] %`);
20
+ sql += " ORDER BY created_at DESC";
21
+ if (opts?.limit) {
22
+ sql += " LIMIT ?";
23
+ params.push(opts.limit);
30
24
  }
31
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
32
- const limit = opts?.limit ?? 50;
33
- params.push(limit);
34
- return getDb()
35
- .prepare(`SELECT * FROM unified_feed ${where} ORDER BY created_at DESC, id DESC LIMIT ?`)
36
- .all(...params);
37
- }
38
- export function listFeedSquads() {
39
- const rows = getDb()
40
- .prepare(`SELECT DISTINCT substr(title, 2, instr(title, ']') - 2) AS squad
41
- FROM unified_feed
42
- WHERE title LIKE '[%]%'
43
- ORDER BY squad`)
44
- .all();
45
- return rows.map((r) => r.squad);
46
- }
47
- export function countUnreadFeedEntries(type) {
48
- if (type) {
49
- const row = getDb()
50
- .prepare("SELECT COUNT(*) AS n FROM unified_feed WHERE read_at IS NULL AND type = ?")
51
- .get(type);
52
- return row.n;
25
+ if (opts?.offset) {
26
+ sql += " OFFSET ?";
27
+ params.push(opts.offset);
53
28
  }
54
- const row = getDb()
55
- .prepare("SELECT COUNT(*) AS n FROM unified_feed WHERE read_at IS NULL")
56
- .get();
57
- return row.n;
29
+ return db.prepare(sql).all(...params);
58
30
  }
59
- export function markFeedEntryRead(id) {
31
+ export function markFeedItemRead(id) {
60
32
  const db = getDb();
61
- const info = db
62
- .prepare("UPDATE unified_feed SET read_at = CURRENT_TIMESTAMP WHERE id = ? AND read_at IS NULL")
63
- .run(id);
64
- if (info.changes > 0)
65
- return true;
66
- // Idempotent: return true if row exists (already read), false if missing
67
- const exists = db.prepare("SELECT id FROM unified_feed WHERE id = ?").get(id);
68
- return exists !== undefined;
69
- }
70
- export function markAllFeedEntriesRead(type) {
71
- if (type) {
72
- const info = getDb()
73
- .prepare("UPDATE unified_feed SET read_at = CURRENT_TIMESTAMP WHERE read_at IS NULL AND type = ?")
74
- .run(type);
75
- return info.changes;
76
- }
77
- const info = getDb()
78
- .prepare("UPDATE unified_feed SET read_at = CURRENT_TIMESTAMP WHERE read_at IS NULL")
79
- .run();
80
- return info.changes;
81
- }
82
- export function deleteFeedEntry(id) {
83
- const info = getDb()
84
- .prepare("DELETE FROM unified_feed WHERE id = ?")
85
- .run(id);
86
- return info.changes > 0;
87
- }
88
- export function pruneOldFeedEntries(olderThanDays) {
89
- const info = getDb()
90
- .prepare(`DELETE FROM unified_feed WHERE created_at < datetime('now', '-' || ? || ' days')`)
91
- .run(olderThanDays);
92
- return info.changes;
33
+ db.prepare("UPDATE feed_items SET read = 1 WHERE id = ?").run(id);
93
34
  }
94
- export function markFeedEntriesRead(ids) {
95
- if (ids.length === 0)
96
- return 0;
97
- const placeholders = ids.map(() => "?").join(", ");
98
- const info = getDb()
99
- .prepare(`UPDATE unified_feed SET read_at = CURRENT_TIMESTAMP WHERE id IN (${placeholders}) AND read_at IS NULL`)
100
- .run(...ids);
101
- return info.changes;
35
+ export function deleteFeedItem(id) {
36
+ const db = getDb();
37
+ db.prepare("DELETE FROM feed_items WHERE id = ?").run(id);
102
38
  }
103
- export function deleteFeedEntries(ids) {
104
- if (ids.length === 0)
105
- return 0;
106
- const placeholders = ids.map(() => "?").join(", ");
107
- const info = getDb()
108
- .prepare(`DELETE FROM unified_feed WHERE id IN (${placeholders})`)
109
- .run(...ids);
110
- return info.changes;
39
+ export function getUnreadCount() {
40
+ const db = getDb();
41
+ const row = db.prepare("SELECT COUNT(*) as count FROM feed_items WHERE read = 0").get();
42
+ return row.count;
111
43
  }
112
44
  //# sourceMappingURL=feed.js.map
@@ -1,139 +1,74 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { execSync } from "node:child_process";
1
3
  import { getDb } from "./db.js";
2
- import { getDecisions } from "./squads.js";
3
- import { worktreeExists } from "./worktrees.js";
4
- import { readSquadWikiPages } from "../wiki/fs.js";
5
- export function ensureInstanceTables() {
4
+ const MAX_INSTANCES_PER_SQUAD = 3;
5
+ export async function createInstance(squadId, branch) {
6
6
  const db = getDb();
7
- db.exec(`
8
- CREATE TABLE IF NOT EXISTS squad_instances (
9
- id TEXT PRIMARY KEY,
10
- master_squad_slug TEXT NOT NULL,
11
- issue_ref TEXT,
12
- worktree_path TEXT NOT NULL,
13
- branch_name TEXT NOT NULL,
14
- status TEXT NOT NULL DEFAULT 'pending',
15
- context_snapshot TEXT,
16
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
17
- completed_at DATETIME
18
- );
19
-
20
- CREATE TABLE IF NOT EXISTS instance_decisions (
21
- id INTEGER PRIMARY KEY AUTOINCREMENT,
22
- instance_id TEXT NOT NULL,
23
- decision TEXT NOT NULL,
24
- context TEXT,
25
- merged_to_master INTEGER DEFAULT 0,
26
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
27
- );
28
- `);
29
- }
30
- export const MAX_CONCURRENT_INSTANCES = 3;
31
- export function createInstance(input) {
32
- const db = getDb();
33
- const activeCount = db
34
- .prepare("SELECT COUNT(*) as cnt FROM squad_instances WHERE master_squad_slug = ? AND status NOT IN ('done', 'failed')")
35
- .get(input.masterSquadSlug).cnt;
36
- if (activeCount >= MAX_CONCURRENT_INSTANCES) {
37
- throw new Error(`Max concurrent instances (${MAX_CONCURRENT_INSTANCES}) reached for squad "${input.masterSquadSlug}"`);
7
+ // Check max instances
8
+ const active = db
9
+ .prepare("SELECT COUNT(*) as count FROM instances WHERE squad_id = ? AND status = 'active'")
10
+ .get(squadId);
11
+ if (active.count >= MAX_INSTANCES_PER_SQUAD) {
12
+ throw new Error(`Squad already has ${MAX_INSTANCES_PER_SQUAD} active instances. Destroy one first.`);
38
13
  }
39
- db.prepare(`INSERT INTO squad_instances (id, master_squad_slug, issue_ref, worktree_path, branch_name, status, context_snapshot)
40
- VALUES (?, ?, ?, ?, ?, 'pending', ?)`).run(input.id, input.masterSquadSlug, input.issueRef ?? null, input.worktreePath, input.branchName, input.contextSnapshot ?? null);
41
- return getInstance(input.id);
42
- }
43
- export function getInstance(id) {
44
- const db = getDb();
45
- return db.prepare("SELECT * FROM squad_instances WHERE id = ?").get(id);
46
- }
47
- export function listInstances(masterSquadSlug, opts) {
48
- const db = getDb();
49
- const includeCompleted = opts?.includeCompleted ?? false;
50
- if (includeCompleted) {
51
- return db
52
- .prepare("SELECT id, issue_ref, status, branch_name, created_at, completed_at FROM squad_instances WHERE master_squad_slug = ? ORDER BY created_at DESC")
53
- .all(masterSquadSlug);
14
+ // Get squad repo to determine worktree location
15
+ const squad = db.prepare("SELECT * FROM squads WHERE id = ?").get(squadId);
16
+ if (!squad?.repo_url) {
17
+ throw new Error("Squad has no repo_url configured.");
54
18
  }
55
- return db
56
- .prepare("SELECT id, issue_ref, status, branch_name, created_at, completed_at FROM squad_instances WHERE master_squad_slug = ? AND status NOT IN ('done', 'failed') ORDER BY created_at DESC")
57
- .all(masterSquadSlug);
58
- }
59
- export function updateInstanceStatus(id, status) {
60
- const db = getDb();
61
- if (status === "done" || status === "failed") {
62
- db.prepare("UPDATE squad_instances SET status = ?, completed_at = CURRENT_TIMESTAMP WHERE id = ?").run(status, id);
19
+ const id = randomUUID();
20
+ const worktreePath = `/tmp/io-worktrees/${squadId}/${branch}`;
21
+ // Create git worktree
22
+ try {
23
+ execSync(`git worktree add ${worktreePath} -b ${branch}`, {
24
+ cwd: squad.repo_url.startsWith("/") ? squad.repo_url : process.cwd(),
25
+ stdio: "pipe",
26
+ });
63
27
  }
64
- else {
65
- db.prepare("UPDATE squad_instances SET status = ? WHERE id = ?").run(status, id);
28
+ catch (err) {
29
+ // Branch may already exist
30
+ execSync(`git worktree add ${worktreePath} ${branch}`, {
31
+ cwd: squad.repo_url.startsWith("/") ? squad.repo_url : process.cwd(),
32
+ stdio: "pipe",
33
+ });
66
34
  }
35
+ db.prepare(`INSERT INTO instances (id, squad_id, branch, worktree_path, status)
36
+ VALUES (?, ?, ?, ?, 'active')`).run(id, squadId, branch, worktreePath);
37
+ return db.prepare("SELECT * FROM instances WHERE id = ?").get(id);
67
38
  }
68
- export function logInstanceDecision(instanceId, decision, context) {
39
+ export async function destroyInstance(instanceId) {
69
40
  const db = getDb();
70
- db.prepare("INSERT INTO instance_decisions (instance_id, decision, context) VALUES (?, ?, ?)").run(instanceId, decision, context ?? null);
41
+ const instance = db
42
+ .prepare("SELECT * FROM instances WHERE id = ?")
43
+ .get(instanceId);
44
+ if (!instance)
45
+ throw new Error(`Instance ${instanceId} not found.`);
46
+ // Remove worktree
47
+ try {
48
+ execSync(`git worktree remove ${instance.worktree_path} --force`, {
49
+ stdio: "pipe",
50
+ });
51
+ }
52
+ catch {
53
+ // Already removed or doesn't exist
54
+ }
55
+ db.prepare("UPDATE instances SET status = 'destroyed' WHERE id = ?").run(instanceId);
71
56
  }
72
- export function getInstanceDecisions(instanceId) {
57
+ export function getInstancesForSquad(squadId) {
73
58
  const db = getDb();
74
59
  return db
75
- .prepare("SELECT decision, context, created_at, merged_to_master FROM instance_decisions WHERE instance_id = ? ORDER BY created_at ASC")
76
- .all(instanceId);
77
- }
78
- /**
79
- * Merge instance decisions back to master squad. Returns count merged.
80
- */
81
- export function mergeInstanceDecisions(instanceId, masterSquadSlug) {
82
- const db = getDb();
83
- const decisions = db
84
- .prepare("SELECT id, decision, context FROM instance_decisions WHERE instance_id = ? AND merged_to_master = 0")
85
- .all(instanceId);
86
- if (decisions.length === 0)
87
- return 0;
88
- const insertStmt = db.prepare("INSERT INTO squad_decisions (squad_slug, decision, context) VALUES (?, ?, ?)");
89
- const markStmt = db.prepare("UPDATE instance_decisions SET merged_to_master = 1 WHERE id = ?");
90
- const mergeAll = db.transaction(() => {
91
- for (const d of decisions) {
92
- const ctx = d.context
93
- ? `${d.context} [from instance: ${instanceId}]`
94
- : `[from instance: ${instanceId}]`;
95
- insertStmt.run(masterSquadSlug, d.decision, ctx);
96
- markStmt.run(d.id);
97
- }
98
- });
99
- mergeAll();
100
- return decisions.length;
60
+ .prepare("SELECT * FROM instances WHERE squad_id = ? AND status = 'active' ORDER BY created_at")
61
+ .all(squadId);
101
62
  }
102
- export function deleteInstance(id) {
63
+ export function touchInstanceActivity(instanceId) {
103
64
  const db = getDb();
104
- db.prepare("DELETE FROM instance_decisions WHERE instance_id = ?").run(id);
105
- db.prepare("DELETE FROM squad_instances WHERE id = ?").run(id);
65
+ db.prepare("UPDATE instances SET last_activity = datetime('now') WHERE id = ?").run(instanceId);
106
66
  }
107
- /**
108
- * Build a JSON snapshot of the master squad's recent decisions for context inheritance.
109
- */
110
- export function buildContextSnapshot(masterSquadSlug, limit = 30) {
111
- const decisions = getDecisions(masterSquadSlug, limit);
112
- const wikiPages = readSquadWikiPages(masterSquadSlug);
113
- const snapshot = {
114
- decisions: decisions.map((d) => ({ decision: d.decision, context: d.context, created_at: d.created_at })),
115
- };
116
- if (wikiPages.length > 0) {
117
- snapshot.wiki = wikiPages.map(p => ({ path: p.path, content: p.content }));
118
- }
119
- return JSON.stringify(snapshot);
120
- }
121
- /**
122
- * Reconcile instances on startup: detect orphaned worktrees and mark stale active instances.
123
- * Returns the number of instances cleaned up.
124
- */
125
- export function reconcileInstances() {
67
+ export function getStaleInstances(minutesThreshold = 30) {
126
68
  const db = getDb();
127
- const activeInstances = db
128
- .prepare("SELECT id, worktree_path FROM squad_instances WHERE status IN ('active', 'pending', 'merging')")
69
+ return db
70
+ .prepare(`SELECT * FROM instances WHERE status = 'active'
71
+ AND datetime(last_activity, '+${minutesThreshold} minutes') < datetime('now')`)
129
72
  .all();
130
- let cleaned = 0;
131
- for (const inst of activeInstances) {
132
- if (!worktreeExists(inst.worktree_path)) {
133
- updateInstanceStatus(inst.id, "failed");
134
- cleaned++;
135
- }
136
- }
137
- return cleaned;
138
73
  }
139
74
  //# sourceMappingURL=instances.js.map
@@ -1,83 +1,31 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { getDb } from "./db.js";
2
- function rowToSchedule(row) {
3
- let agenda = [];
4
- try {
5
- agenda = JSON.parse(row.agenda);
6
- if (!Array.isArray(agenda))
7
- agenda = [];
8
- }
9
- catch {
10
- agenda = [];
11
- }
12
- return { ...row, agenda };
13
- }
14
3
  export function createSchedule(input) {
15
4
  const db = getDb();
16
- const info = db
17
- .prepare(`INSERT INTO squad_schedules
18
- (squad_slug, name, cron_expr, agenda, notes, enabled, next_run_at)
19
- VALUES (?, ?, ?, ?, ?, 1, ?)`)
20
- .run(input.squadSlug, input.name, input.cronExpr, JSON.stringify(input.agenda), input.notes ?? null, input.nextRunAt);
21
- const id = Number(info.lastInsertRowid);
22
- return getSchedule(id);
5
+ const id = randomUUID();
6
+ db.prepare(`INSERT INTO schedules (id, type, squad_id, cron, agenda, prompt)
7
+ VALUES (?, ?, ?, ?, ?, ?)`).run(id, input.type, input.squad_id ?? null, input.cron, input.agenda ?? "", input.prompt ?? "");
8
+ return db.prepare("SELECT * FROM schedules WHERE id = ?").get(id);
23
9
  }
24
- export function getSchedule(id) {
25
- const row = getDb()
26
- .prepare("SELECT * FROM squad_schedules WHERE id = ?")
27
- .get(id);
28
- return row ? rowToSchedule(row) : undefined;
10
+ export function listSchedules(type) {
11
+ const db = getDb();
12
+ if (type) {
13
+ return db
14
+ .prepare("SELECT * FROM schedules WHERE type = ? ORDER BY created_at")
15
+ .all(type);
16
+ }
17
+ return db.prepare("SELECT * FROM schedules ORDER BY created_at").all();
29
18
  }
30
- export function listSchedules(squadSlug) {
31
- const rows = squadSlug
32
- ? getDb()
33
- .prepare("SELECT * FROM squad_schedules WHERE squad_slug = ? ORDER BY id ASC")
34
- .all(squadSlug)
35
- : getDb()
36
- .prepare("SELECT * FROM squad_schedules ORDER BY squad_slug, id ASC")
37
- .all();
38
- return rows.map(rowToSchedule);
19
+ export function updateScheduleLastRun(id) {
20
+ const db = getDb();
21
+ db.prepare("UPDATE schedules SET last_run = datetime('now') WHERE id = ?").run(id);
39
22
  }
40
- export function listDueSchedules(now) {
41
- const iso = now.toISOString();
42
- const rows = getDb()
43
- .prepare(`SELECT * FROM squad_schedules
44
- WHERE enabled = 1
45
- AND next_run_at IS NOT NULL
46
- AND next_run_at <= ?
47
- ORDER BY next_run_at ASC`)
48
- .all(iso);
49
- return rows.map(rowToSchedule);
23
+ export function toggleSchedule(id, enabled) {
24
+ const db = getDb();
25
+ db.prepare("UPDATE schedules SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, id);
50
26
  }
51
27
  export function deleteSchedule(id) {
52
- const info = getDb()
53
- .prepare("DELETE FROM squad_schedules WHERE id = ?")
54
- .run(id);
55
- return info.changes > 0;
56
- }
57
- export function setScheduleEnabled(id, enabled) {
58
- const info = getDb()
59
- .prepare("UPDATE squad_schedules SET enabled = ? WHERE id = ?")
60
- .run(enabled ? 1 : 0, id);
61
- return info.changes > 0;
62
- }
63
- export function recordScheduleRun(id, ranAt, nextRunAt) {
64
- getDb()
65
- .prepare("UPDATE squad_schedules SET last_run_at = ?, next_run_at = ? WHERE id = ?")
66
- .run(ranAt.toISOString(), nextRunAt, id);
67
- }
68
- export function updateNextRun(id, nextRunAt) {
69
- getDb()
70
- .prepare("UPDATE squad_schedules SET next_run_at = ? WHERE id = ?")
71
- .run(nextRunAt, id);
72
- }
73
- /**
74
- * Overwrite both last_run_at and next_run_at directly. Unlike
75
- * recordScheduleRun this accepts NULL for last_run_at, which is needed when
76
- * restoring a schedule's "never run" state after a manual run_now.
77
- */
78
- export function setScheduleTimestamps(id, lastRunAt, nextRunAt) {
79
- getDb()
80
- .prepare("UPDATE squad_schedules SET last_run_at = ?, next_run_at = ? WHERE id = ?")
81
- .run(lastRunAt, nextRunAt, id);
28
+ const db = getDb();
29
+ db.prepare("DELETE FROM schedules WHERE id = ?").run(id);
82
30
  }
83
31
  //# sourceMappingURL=schedules.js.map