circuschief 0.7.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 (185) hide show
  1. package/package.json +1 -1
  2. package/packages/server/src/agents/adapters/CodexAdapter.js +5 -4
  3. package/packages/server/src/api/canvas.js +22 -57
  4. package/packages/server/src/api/commandButtons.js +16 -15
  5. package/packages/server/src/api/index.js +2 -0
  6. package/packages/server/src/api/kanban.js +4 -2
  7. package/packages/server/src/api/projects-commandButtons.js +6 -6
  8. package/packages/server/src/api/projects-helpers.js +20 -3
  9. package/packages/server/src/api/projects-session-create.js +109 -0
  10. package/packages/server/src/api/projects-session-defaults.js +51 -0
  11. package/packages/server/src/api/projects-session-helpers.js +57 -5
  12. package/packages/server/src/api/projects-templates.js +38 -0
  13. package/packages/server/src/api/projects.js +28 -170
  14. package/packages/server/src/api/providers.js +11 -1
  15. package/packages/server/src/api/sessions-commands.js +46 -25
  16. package/packages/server/src/api/sessions-lifecycle.js +10 -10
  17. package/packages/server/src/api/sessions-patch.js +45 -1
  18. package/packages/server/src/api/sessions.js +6 -5
  19. package/packages/server/src/database.js +0 -2
  20. package/packages/server/src/db/DatabaseManager.js +5 -1
  21. package/packages/server/src/db/ProjectDefaultsRepository.js +3 -3
  22. package/packages/server/src/db/ProviderRepository.js +87 -32
  23. package/packages/server/src/db/SessionRepository.js +2 -1
  24. package/packages/server/src/db/SessionTemplateRepository.js +23 -2
  25. package/packages/server/src/db/index.js +0 -3
  26. package/packages/server/src/db/migrations/index.js +60 -7
  27. package/packages/server/src/db/migrations/kanbanMigrations.js +1 -1
  28. package/packages/server/src/db/migrations/miscMigrations.js +59 -184
  29. package/packages/server/src/db/migrations/projectsMigrations.js +31 -0
  30. package/packages/server/src/db/migrations/providerCommitAttributionMigrations.js +30 -0
  31. package/packages/server/src/db/migrations/providerMigrations.js +165 -0
  32. package/packages/server/src/db/migrations/sessionTableRecreate.js +136 -0
  33. package/packages/server/src/db/migrations/sessionsMigrations.js +18 -5
  34. package/packages/server/src/db/seedBaselineData.js +137 -0
  35. package/packages/server/src/db/session-helpers.js +32 -4
  36. package/packages/server/src/middleware/sessionLookup.js +81 -8
  37. package/packages/server/src/schema.sql +153 -132
  38. package/packages/server/src/scripts/backupDatabase.js +21 -0
  39. package/packages/server/src/scripts/dbUtils.js +81 -0
  40. package/packages/server/src/scripts/inspectDatabaseSchema.js +81 -0
  41. package/packages/server/src/scripts/validateDatabaseBaseline.js +212 -0
  42. package/packages/server/src/services/codexSpawnHelper.js +9 -0
  43. package/packages/server/src/services/commandButtonPrompts.js +14 -12
  44. package/packages/server/src/services/commandRunner.js +7 -1
  45. package/packages/server/src/services/e2eSpawnCapture.js +147 -0
  46. package/packages/server/src/services/gitCommitAttribution.js +150 -0
  47. package/packages/server/src/services/gitDiff.js +132 -0
  48. package/packages/server/src/services/gitRepoUrl.js +174 -0
  49. package/packages/server/src/services/gitService.js +48 -311
  50. package/packages/server/src/services/gitSessionSetup.js +11 -1
  51. package/packages/server/src/services/gitWorktree.js +127 -0
  52. package/packages/server/src/services/kanbanTriggers.js +6 -3
  53. package/packages/server/src/services/nodeSpawnHelper.js +9 -0
  54. package/packages/server/src/services/prUrlService.js +3 -3
  55. package/packages/server/src/services/queryParamBuilder.js +90 -0
  56. package/packages/server/src/services/sessionDuplicator.js +1 -5
  57. package/packages/server/src/services/sessionExecution.js +56 -108
  58. package/packages/server/src/services/sessionPrompts.js +12 -47
  59. package/packages/server/src/services/sessionProvider.js +10 -0
  60. package/packages/server/src/services/summaryService.js +5 -3
  61. package/packages/server/src/services/summaryStaleCheck.js +23 -4
  62. package/packages/server/src/services/templateTriggerService.js +3 -1
  63. package/packages/shared/src/constants.js +3 -0
  64. package/packages/shared/src/contracts/commandButtons.js +16 -2
  65. package/packages/shared/src/contracts/projects.js +2 -2
  66. package/packages/shared/src/contracts/providers.js +60 -0
  67. package/packages/shared/src/contracts/sessions.js +29 -2
  68. package/packages/shared/src/contracts/templates.js +12 -2
  69. package/packages/shared/src/types.js +1 -9
  70. package/packages/shared/src/utils.js +2 -2
  71. package/packages/web/dist/assets/{ActiveSessionsView-UJsCILDL.js → ActiveSessionsView-Cxh8mHmB.js} +1 -1
  72. package/packages/web/dist/assets/{AgentLogsView-BGFPLjLa.js → AgentLogsView-xdfI2bR6.js} +2 -2
  73. package/packages/web/dist/assets/ApiClient-DfbJwzpz.js +1 -0
  74. package/packages/web/dist/assets/ArchiveConfirmModal-DXZYdzHR.js +1 -0
  75. package/packages/web/dist/assets/ArchiveConfirmModal-DeoCVGXt.css +1 -0
  76. package/packages/web/dist/assets/CommandButtonDetailView-D8xfqLAp.js +1 -0
  77. package/packages/web/dist/assets/CommandButtonDetailView-D9zjx9ME.css +1 -0
  78. package/packages/web/dist/assets/EffortLevelSelector-D2Hdzc_8.js +1 -0
  79. package/packages/web/dist/assets/{GeneralSettingsView-DsHChEhv.js → GeneralSettingsView-sPXkLlLy.js} +1 -1
  80. package/packages/web/dist/assets/InputWithButton-B-o0DgMH.js +1 -0
  81. package/packages/web/dist/assets/{InputWithButton-cYdrEmTs.css → InputWithButton-D9HMvfR5.css} +1 -1
  82. package/packages/web/dist/assets/{InterpolationHelp-CIkOSkWX.js → InterpolationHelp-Dxn1li4l.js} +1 -1
  83. package/packages/web/dist/assets/MarkdownEditor-D4Kbb-9l.js +2 -0
  84. package/packages/web/dist/assets/ModelSelector-72C7MUH4.js +1 -0
  85. package/packages/web/dist/assets/{ModelSelector-D8hbTRIt.css → ModelSelector-BNYKujL-.css} +1 -1
  86. package/packages/web/dist/assets/NewSessionView-BR_COfgW.js +3 -0
  87. package/packages/web/dist/assets/NewSessionView-DBl7T2Xp.css +1 -0
  88. package/packages/web/dist/assets/ProjectEditView-DbqTbA0q.css +1 -0
  89. package/packages/web/dist/assets/ProjectEditView-WImU7sNd.js +1 -0
  90. package/packages/web/dist/assets/{ProjectListView-B9FuWESY.js → ProjectListView-CYmmAcBD.js} +1 -1
  91. package/packages/web/dist/assets/{ProjectNewView-D62jYlBL.js → ProjectNewView-DEhqw3Jv.js} +1 -1
  92. package/packages/web/dist/assets/ProvidersView-XZh3jkmH.js +1 -0
  93. package/packages/web/dist/assets/ProvidersView-bZemq_Rv.css +1 -0
  94. package/packages/web/dist/assets/QuickResponsesPanel-BqmnTd-D.js +1 -0
  95. package/packages/web/dist/assets/QuickResponsesPanel-dk-Rj8xx.css +1 -0
  96. package/packages/web/dist/assets/ResizableTextarea-BQNw5e0C.css +1 -0
  97. package/packages/web/dist/assets/ResizableTextarea-DpWdIAP6.js +1 -0
  98. package/packages/web/dist/assets/SessionCard-Bw77-KwD.js +1 -0
  99. package/packages/web/dist/assets/SessionDetailView-B59TEkr-.js +36 -0
  100. package/packages/web/dist/assets/SessionDetailView-CKVBnR4T.css +1 -0
  101. package/packages/web/dist/assets/{SessionFormOptions-DYUISplS.js → SessionFormOptions-hqijxc0S.js} +1 -1
  102. package/packages/web/dist/assets/SessionListView-3-xx6EVs.css +1 -0
  103. package/packages/web/dist/assets/SessionListView-DYXHM9I-.js +1 -0
  104. package/packages/web/dist/assets/{SessionLogStream-DpUE6Xsh.js → SessionLogStream-5NfVr9pF.js} +6 -6
  105. package/packages/web/dist/assets/{SettingsView-BC055tIA.js → SettingsView-DI8ncOAV.js} +1 -1
  106. package/packages/web/dist/assets/{SlashCommandWizard-DmTyNG9O.js → SlashCommandWizard-BQ_rMzn-.js} +1 -1
  107. package/packages/web/dist/assets/{SlashCommandWizard-Dn7sNaBd.css → SlashCommandWizard-DJzw3LP5.css} +1 -1
  108. package/packages/web/dist/assets/{SummarySettingsView-BgnRCwlq.js → SummarySettingsView-C2Qs35mm.js} +1 -1
  109. package/packages/web/dist/assets/TemplateDetailView-B5NI2oTR.css +1 -0
  110. package/packages/web/dist/assets/TemplateDetailView-zVkIvgtu.js +1 -0
  111. package/packages/web/dist/assets/{commandButtons-D4RPpLiu.js → commandButtons-CoU3G4zK.js} +1 -1
  112. package/packages/web/dist/assets/index-9yF1uCCA.js +1 -0
  113. package/packages/web/dist/assets/index-BKstCaYU.js +1 -0
  114. package/packages/web/dist/assets/index-BhbH7eOk.js +1 -0
  115. package/packages/web/dist/assets/{index-BGwH4Cfn.js → index-BjuRttEY.js} +3 -3
  116. package/packages/web/dist/assets/index-Bo7PdwM5.js +1 -0
  117. package/packages/web/dist/assets/index-C2QFVD7d.js +83 -0
  118. package/packages/web/dist/assets/index-C7Ww2auW.js +1 -0
  119. package/packages/web/dist/assets/index-CAGdsDh7.js +1 -0
  120. package/packages/web/dist/assets/index-CLRsVASf.js +3 -0
  121. package/packages/web/dist/assets/{index-Bn5xdGFM.js → index-CP-SxOlV.js} +1 -1
  122. package/packages/web/dist/assets/index-CslU0psO.js +1 -0
  123. package/packages/web/dist/assets/index-DI4NxaWD.js +1 -0
  124. package/packages/web/dist/assets/index-DOzONENy.js +1 -0
  125. package/packages/web/dist/assets/index-DUa7adFh.js +1 -0
  126. package/packages/web/dist/assets/index-DZBpETI5.js +1 -0
  127. package/packages/web/dist/assets/index-DsjWqc6R.js +7 -0
  128. package/packages/web/dist/assets/index-c99Bo3JV.js +1 -0
  129. package/packages/web/dist/assets/index-mT1JpxDc.js +1 -0
  130. package/packages/web/dist/assets/index-rkQx2tso.js +1 -0
  131. package/packages/web/dist/assets/{index-Cs2nxhrT.css → index-uySCcnA_.css} +1 -1
  132. package/packages/web/dist/assets/projectDefaults-B8esIcYq.js +1 -0
  133. package/packages/web/dist/assets/{projects-BUiOGmmb.js → projects-C-8PSxKi.js} +1 -1
  134. package/packages/web/dist/assets/{providers-Bh1ZiiJi.js → providers-oXifvvqN.js} +1 -1
  135. package/packages/web/dist/assets/sessions-Nq5VafSf.js +1 -0
  136. package/packages/web/dist/assets/{settings-Z4AVVmkJ.js → settings-DtpuiyT6.js} +1 -1
  137. package/packages/web/dist/index.html +2 -2
  138. package/packages/server/src/api/sessions-notes.js +0 -51
  139. package/packages/server/src/db/SessionNoteRepository.js +0 -60
  140. package/packages/web/dist/assets/ApiClient-B4YTtyY4.js +0 -1
  141. package/packages/web/dist/assets/ArchiveConfirmModal-BQ-4gI0R.css +0 -1
  142. package/packages/web/dist/assets/ArchiveConfirmModal-OFaj_uX5.js +0 -1
  143. package/packages/web/dist/assets/CommandButtonDetailView-D8S258uP.js +0 -1
  144. package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +0 -1
  145. package/packages/web/dist/assets/EffortLevelSelector-C2378L8e.js +0 -1
  146. package/packages/web/dist/assets/InputWithButton-Ci15ox0a.js +0 -1
  147. package/packages/web/dist/assets/MarkdownEditor-5-bexzUT.js +0 -2
  148. package/packages/web/dist/assets/ModelSelector-BMpR0DPr.js +0 -1
  149. package/packages/web/dist/assets/NewSessionView-BCqtIgWH.js +0 -3
  150. package/packages/web/dist/assets/NewSessionView-CUUdHkfv.css +0 -1
  151. package/packages/web/dist/assets/ProjectEditView-D9sK0fdH.css +0 -1
  152. package/packages/web/dist/assets/ProjectEditView-RFaxHhAX.js +0 -1
  153. package/packages/web/dist/assets/ProvidersView-DDKMIQWZ.js +0 -1
  154. package/packages/web/dist/assets/ProvidersView-DE82G_5W.css +0 -1
  155. package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +0 -1
  156. package/packages/web/dist/assets/QuickResponseSettings-CDm5vwP7.js +0 -1
  157. package/packages/web/dist/assets/QuickResponsesPanel-BlFDvnZ2.css +0 -1
  158. package/packages/web/dist/assets/QuickResponsesPanel-DZ_Lre_l.js +0 -1
  159. package/packages/web/dist/assets/ResizableTextarea-DiIOEGjN.js +0 -1
  160. package/packages/web/dist/assets/ResizableTextarea-DsU3TVwF.css +0 -1
  161. package/packages/web/dist/assets/SessionCard-DmjnVYWn.js +0 -1
  162. package/packages/web/dist/assets/SessionDetailView-CL7nmfiB.js +0 -36
  163. package/packages/web/dist/assets/SessionDetailView-CupIkI7u.css +0 -1
  164. package/packages/web/dist/assets/SessionListView-BcxGz4aC.js +0 -1
  165. package/packages/web/dist/assets/SessionListView-fHlQyecX.css +0 -1
  166. package/packages/web/dist/assets/TemplateDetailView-BlhOmLUX.js +0 -1
  167. package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +0 -1
  168. package/packages/web/dist/assets/index-4rhEeO0B.js +0 -1
  169. package/packages/web/dist/assets/index-9vb2KaAd.js +0 -1
  170. package/packages/web/dist/assets/index-B0CvZXuN.js +0 -7
  171. package/packages/web/dist/assets/index-B6G18FqB.js +0 -82
  172. package/packages/web/dist/assets/index-BUhvkAdF.js +0 -1
  173. package/packages/web/dist/assets/index-BcnkUk2o.js +0 -1
  174. package/packages/web/dist/assets/index-CNwkdB0T.js +0 -1
  175. package/packages/web/dist/assets/index-CfL84oGW.js +0 -1
  176. package/packages/web/dist/assets/index-CkmxO8Mm.js +0 -1
  177. package/packages/web/dist/assets/index-Cpy4-yv3.js +0 -1
  178. package/packages/web/dist/assets/index-CrAQJmoZ.js +0 -1
  179. package/packages/web/dist/assets/index-D6Ky9vJe.js +0 -3
  180. package/packages/web/dist/assets/index-DfrE0gAC.js +0 -1
  181. package/packages/web/dist/assets/index-KwEyz0F3.js +0 -1
  182. package/packages/web/dist/assets/index-OfCywayk.js +0 -1
  183. package/packages/web/dist/assets/index-PDesaJc6.js +0 -1
  184. package/packages/web/dist/assets/index-uB6nhSvz.js +0 -1
  185. package/packages/web/dist/assets/sessions-DH1R-NhV.js +0 -1
@@ -1,4 +1,3 @@
1
- -- Projects table
2
1
  CREATE TABLE IF NOT EXISTS projects (
3
2
  id TEXT PRIMARY KEY,
4
3
  name TEXT NOT NULL,
@@ -6,14 +5,14 @@ CREATE TABLE IF NOT EXISTS projects (
6
5
  system_prompt TEXT,
7
6
  on_session_created TEXT,
8
7
  on_session_deleted TEXT,
9
- pr_poll_interval INTEGER NOT NULL DEFAULT 60000, -- PR status poll interval in ms (default 1 minute)
10
- repo_url TEXT, -- GitHub repository URL for PR validation
8
+ pr_poll_interval INTEGER NOT NULL DEFAULT 60000,
9
+ repo_url TEXT,
10
+ worktree_path TEXT,
11
+ kanban_enabled INTEGER NOT NULL DEFAULT 1,
11
12
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
12
13
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
13
14
  );
14
15
 
15
- -- Session templates (reusable prompts that can chain sessions)
16
- -- Must be created before sessions due to foreign key reference
17
16
  CREATE TABLE IF NOT EXISTS session_templates (
18
17
  id TEXT PRIMARY KEY,
19
18
  project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
@@ -26,26 +25,57 @@ CREATE TABLE IF NOT EXISTS session_templates (
26
25
  model TEXT,
27
26
  mode TEXT DEFAULT 'yolo' CHECK(mode IN ('plan', 'standard', 'yolo')),
28
27
  effort_level TEXT CHECK(effort_level IN ('low', 'medium', 'high', 'max', 'auto')),
28
+ target_lane_id TEXT REFERENCES kanban_lanes(id) ON DELETE SET NULL,
29
+ show_in_quick_responses INTEGER NOT NULL DEFAULT 0,
30
+ quick_response_auto_submit INTEGER NOT NULL DEFAULT 0,
31
+ quick_response_sort_order INTEGER NOT NULL DEFAULT 0,
32
+ legacy_quick_response_id TEXT UNIQUE,
29
33
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
30
34
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
31
35
  );
32
36
 
33
- -- Sessions table
37
+ CREATE TABLE IF NOT EXISTS providers (
38
+ id TEXT PRIMARY KEY,
39
+ name TEXT NOT NULL,
40
+ base_url TEXT,
41
+ auth_token TEXT,
42
+ api_timeout_ms INTEGER,
43
+ additional_env_vars TEXT,
44
+ commit_attribution_override TEXT,
45
+ is_built_in INTEGER NOT NULL DEFAULT 0,
46
+ kind TEXT NOT NULL DEFAULT 'anthropic' CHECK(kind IN ('anthropic','openai')),
47
+ created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
48
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
49
+ );
50
+
51
+ CREATE TABLE IF NOT EXISTS provider_models (
52
+ id TEXT PRIMARY KEY,
53
+ provider_id TEXT NOT NULL REFERENCES providers(id) ON DELETE CASCADE,
54
+ model_id TEXT NOT NULL,
55
+ display_name TEXT NOT NULL,
56
+ description TEXT,
57
+ tier TEXT CHECK(tier IN ('opus', 'sonnet', 'haiku', 'custom')),
58
+ created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
59
+ );
60
+
34
61
  CREATE TABLE IF NOT EXISTS sessions (
35
62
  id TEXT PRIMARY KEY,
36
63
  project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
37
64
  name TEXT NOT NULL,
38
65
  status TEXT NOT NULL DEFAULT 'starting' CHECK (status IN ('starting', 'running', 'waiting', 'stopped', 'completed', 'error', 'scheduled')),
39
- mode TEXT NOT NULL DEFAULT 'standard' CHECK (mode IN ('plan', 'standard', 'yolo')),
40
- thinking_enabled INTEGER NOT NULL DEFAULT 0,
66
+ mode TEXT NOT NULL DEFAULT 'yolo' CHECK (mode IN ('plan', 'standard', 'yolo')),
67
+ thinking_enabled INTEGER NOT NULL DEFAULT 1,
41
68
  archived INTEGER NOT NULL DEFAULT 0,
42
69
  git_branch TEXT,
43
70
  git_worktree TEXT,
44
71
  pr_url TEXT,
72
+ pr_url_auto_link_disabled INTEGER NOT NULL DEFAULT 0,
45
73
  error TEXT,
46
74
  effort_level TEXT CHECK(effort_level IN ('low', 'medium', 'high', 'max', 'auto')),
47
75
  cost_usd REAL DEFAULT 0,
48
76
  claude_session_id TEXT,
77
+ model TEXT,
78
+ provider_id TEXT REFERENCES providers(id),
49
79
  next_template_id TEXT REFERENCES session_templates(id) ON DELETE SET NULL,
50
80
  parent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
51
81
  input_tokens INTEGER DEFAULT 0,
@@ -55,20 +85,36 @@ CREATE TABLE IF NOT EXISTS sessions (
55
85
  cache_creation_input_tokens INTEGER DEFAULT 0,
56
86
  web_search_requests INTEGER DEFAULT 0,
57
87
  context_window INTEGER DEFAULT 200000,
88
+ starred INTEGER NOT NULL DEFAULT 0,
89
+ manually_named INTEGER NOT NULL DEFAULT 0,
90
+ scheduled_at INTEGER DEFAULT NULL,
91
+ reschedule_delay_minutes INTEGER DEFAULT 60,
92
+ auto_reschedule_enabled INTEGER DEFAULT 0,
93
+ reschedule_on_token_limit INTEGER DEFAULT 1,
94
+ reschedule_on_service_error INTEGER DEFAULT 1,
95
+ max_reschedule_count INTEGER DEFAULT NULL,
96
+ max_total_tokens INTEGER DEFAULT NULL,
97
+ reschedule_count INTEGER DEFAULT 0,
98
+ reschedule_at_token_count INTEGER DEFAULT NULL,
99
+ pending_prompt TEXT,
100
+ slash_commands TEXT,
101
+ pending_model TEXT,
102
+ auto_send_pending_prompt INTEGER DEFAULT 0,
103
+ agent_type TEXT DEFAULT 'claude-code',
104
+ target_lane_id TEXT REFERENCES kanban_lanes(id) ON DELETE SET NULL,
105
+ lane_trigger_depth INTEGER NOT NULL DEFAULT 0,
58
106
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
59
107
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
60
108
  );
61
109
 
62
- -- Conversations (multiple conversation threads per session)
63
110
  CREATE TABLE IF NOT EXISTS conversations (
64
111
  id TEXT PRIMARY KEY,
65
112
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
66
- name TEXT, -- Auto from first message or user-defined
67
- summary TEXT, -- Per-conversation summary
68
- summary_generated_at INTEGER, -- When summary was last generated
69
- is_active INTEGER NOT NULL DEFAULT 0, -- Currently selected conversation (1 = active)
70
- claude_session_id TEXT, -- Claude SDK session ID for this conversation's context
71
- -- Token usage fields (per-conversation tracking)
113
+ name TEXT,
114
+ summary TEXT,
115
+ summary_generated_at INTEGER,
116
+ is_active INTEGER NOT NULL DEFAULT 0,
117
+ claude_session_id TEXT,
72
118
  input_tokens INTEGER DEFAULT 0,
73
119
  output_tokens INTEGER DEFAULT 0,
74
120
  thinking_tokens INTEGER DEFAULT 0,
@@ -76,48 +122,39 @@ CREATE TABLE IF NOT EXISTS conversations (
76
122
  cache_creation_input_tokens INTEGER DEFAULT 0,
77
123
  web_search_requests INTEGER DEFAULT 0,
78
124
  context_window INTEGER DEFAULT 200000,
125
+ model TEXT,
126
+ parent_conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,
127
+ branch_from_message_id TEXT REFERENCES conversation_messages(id) ON DELETE SET NULL,
79
128
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
80
129
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
81
130
  );
82
131
 
83
- -- Conversation messages
84
132
  CREATE TABLE IF NOT EXISTS conversation_messages (
85
133
  id TEXT PRIMARY KEY,
86
134
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
87
135
  conversation_id TEXT REFERENCES conversations(id) ON DELETE CASCADE,
88
136
  role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
89
137
  content TEXT NOT NULL,
90
- tool_use TEXT, -- JSON array of tool uses
91
- model TEXT, -- Model that generated this message (for assistant messages)
138
+ tool_use TEXT,
139
+ model TEXT,
92
140
  timestamp INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
93
141
  );
94
142
 
95
- -- Canvas items
96
143
  CREATE TABLE IF NOT EXISTS canvas_items (
97
144
  id TEXT PRIMARY KEY,
98
145
  session_id TEXT REFERENCES sessions(id) ON DELETE CASCADE,
99
146
  type TEXT NOT NULL CHECK (type IN ('image', 'markdown', 'text', 'json', 'pdf', 'code')),
100
- content TEXT, -- For markdown/text
101
- data TEXT, -- For json (stored as JSON string) or image (base64)
102
- mime_type TEXT, -- For images
147
+ content TEXT,
148
+ data TEXT,
149
+ mime_type TEXT,
103
150
  filename TEXT,
104
151
  width INTEGER,
105
152
  height INTEGER,
106
- deleted_at INTEGER, -- null = active, timestamp = soft deleted
107
- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
108
- updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
109
- );
110
-
111
- -- Session notes
112
- CREATE TABLE IF NOT EXISTS session_notes (
113
- id TEXT PRIMARY KEY,
114
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
115
- content TEXT NOT NULL,
153
+ deleted_at INTEGER,
116
154
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
117
- updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
155
+ updated_at INTEGER
118
156
  );
119
157
 
120
- -- Global tool templates
121
158
  CREATE TABLE IF NOT EXISTS global_tool_templates (
122
159
  id TEXT PRIMARY KEY,
123
160
  name TEXT NOT NULL,
@@ -127,7 +164,6 @@ CREATE TABLE IF NOT EXISTS global_tool_templates (
127
164
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
128
165
  );
129
166
 
130
- -- Project tool templates
131
167
  CREATE TABLE IF NOT EXISTS project_tool_templates (
132
168
  id TEXT PRIMARY KEY,
133
169
  project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
@@ -138,50 +174,46 @@ CREATE TABLE IF NOT EXISTS project_tool_templates (
138
174
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
139
175
  );
140
176
 
141
- -- Session todos (Claude's task list, scoped to conversations)
142
177
  CREATE TABLE IF NOT EXISTS session_todos (
143
178
  id TEXT PRIMARY KEY,
144
179
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
145
180
  conversation_id TEXT REFERENCES conversations(id) ON DELETE CASCADE,
146
181
  content TEXT NOT NULL,
147
- status TEXT NOT NULL DEFAULT 'pending'
148
- CHECK (status IN ('pending', 'in_progress', 'completed')),
182
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'in_progress', 'completed')),
149
183
  position INTEGER NOT NULL,
150
184
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
151
185
  );
152
186
 
153
- -- Work logs (thinking, command outputs, tool executions)
154
187
  CREATE TABLE IF NOT EXISTS work_logs (
155
188
  id TEXT PRIMARY KEY,
156
189
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
157
190
  message_id TEXT REFERENCES conversation_messages(id) ON DELETE CASCADE,
158
191
  type TEXT NOT NULL CHECK (type IN ('thinking', 'tool_input', 'tool_output')),
159
- tool_name TEXT, -- Tool name for tool_input/tool_output types
160
- content TEXT NOT NULL, -- The actual log content
192
+ tool_name TEXT,
193
+ content TEXT NOT NULL,
161
194
  timestamp INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
162
195
  );
163
196
 
164
- -- Session summaries (AI-generated)
165
197
  CREATE TABLE IF NOT EXISTS session_summaries (
166
198
  id TEXT PRIMARY KEY,
167
199
  session_id TEXT NOT NULL UNIQUE REFERENCES sessions(id) ON DELETE CASCADE,
168
- short_summary TEXT NOT NULL, -- 1-2 sentence preview for list view (~100 chars)
169
- full_summary TEXT NOT NULL, -- Detailed summary with key points (~500 chars)
170
- key_actions TEXT, -- JSON array of main actions taken
171
- files_modified TEXT, -- JSON array of files touched
172
- outcome TEXT, -- 'completed' | 'partial' | 'failed' | 'ongoing'
173
- message_count INTEGER, -- Track what was summarized (staleness detection)
174
- pr_merged INTEGER, -- 0/1 boolean - whether PR is merged
175
- pr_state TEXT, -- 'open' | 'closed' | 'merged' | 'draft'
176
- has_merge_conflicts INTEGER, -- 0/1 boolean - whether PR has merge conflicts
177
- ci_status TEXT, -- 'success' | 'failure' | 'pending' | null
178
- ci_failures TEXT, -- JSON array of failed check names
200
+ short_summary TEXT NOT NULL,
201
+ full_summary TEXT NOT NULL,
202
+ key_actions TEXT,
203
+ files_modified TEXT,
204
+ outcome TEXT,
205
+ message_count INTEGER,
206
+ pr_merged INTEGER,
207
+ pr_state TEXT,
208
+ has_merge_conflicts INTEGER,
209
+ ci_status TEXT,
210
+ ci_failures TEXT,
211
+ last_summarized_message_id TEXT,
179
212
  generated_at INTEGER NOT NULL,
180
213
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
181
214
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
182
215
  );
183
216
 
184
- -- Message attachments (files attached to user messages)
185
217
  CREATE TABLE IF NOT EXISTS message_attachments (
186
218
  id TEXT PRIMARY KEY,
187
219
  message_id TEXT REFERENCES conversation_messages(id) ON DELETE CASCADE,
@@ -190,12 +222,11 @@ CREATE TABLE IF NOT EXISTS message_attachments (
190
222
  mime_type TEXT NOT NULL,
191
223
  size_bytes INTEGER NOT NULL,
192
224
  storage_type TEXT NOT NULL DEFAULT 'base64' CHECK (storage_type IN ('base64', 'file_path', 'project_file')),
193
- content TEXT, -- base64 data or file reference depending on storage_type
194
- file_path TEXT, -- absolute path to file on disk (for Claude's Read tool)
225
+ content TEXT,
226
+ file_path TEXT,
195
227
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
196
228
  );
197
229
 
198
- -- Command buttons (configurable buttons per project)
199
230
  CREATE TABLE IF NOT EXISTS command_buttons (
200
231
  id TEXT PRIMARY KEY,
201
232
  project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
@@ -207,7 +238,6 @@ CREATE TABLE IF NOT EXISTS command_buttons (
207
238
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
208
239
  );
209
240
 
210
- -- Command runs (execution history for command buttons)
211
241
  CREATE TABLE IF NOT EXISTS command_runs (
212
242
  id TEXT PRIMARY KEY,
213
243
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
@@ -219,7 +249,6 @@ CREATE TABLE IF NOT EXISTS command_runs (
219
249
  completed_at INTEGER
220
250
  );
221
251
 
222
- -- Quick responses (reusable messages for conversation)
223
252
  CREATE TABLE IF NOT EXISTS quick_responses (
224
253
  id TEXT PRIMARY KEY,
225
254
  project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
@@ -232,102 +261,51 @@ CREATE TABLE IF NOT EXISTS quick_responses (
232
261
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
233
262
  );
234
263
 
235
- -- Providers (custom API endpoints for Claude; replaces the old model_providers table)
236
- CREATE TABLE IF NOT EXISTS providers (
264
+ CREATE TABLE IF NOT EXISTS project_session_defaults (
237
265
  id TEXT PRIMARY KEY,
238
- name TEXT NOT NULL,
239
- base_url TEXT,
240
- auth_token TEXT,
241
- api_timeout_ms INTEGER,
242
- additional_env_vars TEXT,
243
- is_built_in INTEGER NOT NULL DEFAULT 0,
244
- kind TEXT NOT NULL DEFAULT 'anthropic' CHECK(kind IN ('anthropic','openai')),
245
- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
246
- updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
266
+ project_id TEXT NOT NULL UNIQUE,
267
+ mode TEXT CHECK(mode IN ('plan', 'standard', 'yolo')),
268
+ thinking_enabled INTEGER,
269
+ start_immediately INTEGER,
270
+ git_mode TEXT CHECK(git_mode IN ('branch', 'worktree', 'current')),
271
+ git_branch TEXT,
272
+ model TEXT,
273
+ provider_id TEXT REFERENCES providers(id),
274
+ effort_level TEXT CHECK(effort_level IN ('low', 'medium', 'high', 'max', 'auto')),
275
+ created_at INTEGER NOT NULL,
276
+ updated_at INTEGER NOT NULL,
277
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
247
278
  );
248
279
 
249
- -- Provider models (custom models available per provider)
250
- CREATE TABLE IF NOT EXISTS provider_models (
251
- id TEXT PRIMARY KEY,
252
- provider_id TEXT NOT NULL REFERENCES providers(id) ON DELETE CASCADE,
253
- model_id TEXT NOT NULL,
254
- display_name TEXT NOT NULL,
255
- description TEXT,
256
- tier TEXT CHECK(tier IN ('opus', 'sonnet', 'haiku', 'custom')),
257
- created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
280
+ CREATE TABLE IF NOT EXISTS app_settings (
281
+ key TEXT PRIMARY KEY,
282
+ value TEXT NOT NULL,
283
+ updated_at INTEGER NOT NULL
258
284
  );
259
285
 
260
- -- Indexes
261
- CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
262
- CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
263
- CREATE INDEX IF NOT EXISTS idx_sessions_archived ON sessions(archived);
264
- CREATE INDEX IF NOT EXISTS idx_conversations_session ON conversations(session_id);
265
- CREATE INDEX IF NOT EXISTS idx_messages_session ON conversation_messages(session_id);
266
- -- Note: idx_messages_conversation is created in migrations to handle existing databases
267
- -- that may not have the conversation_id column yet
268
- CREATE INDEX IF NOT EXISTS idx_canvas_session ON canvas_items(session_id);
269
- -- Note: idx_canvas_deleted is created in migrations to handle existing databases
270
- -- that may not have the deleted_at column yet
271
- CREATE INDEX IF NOT EXISTS idx_notes_session ON session_notes(session_id);
272
- CREATE INDEX IF NOT EXISTS idx_project_tools ON project_tool_templates(project_id);
273
- CREATE INDEX IF NOT EXISTS idx_session_templates_project ON session_templates(project_id);
274
- -- Note: idx_sessions_next_template and idx_sessions_parent are created in migrations
275
- -- to handle existing databases that may not have these columns yet
276
- CREATE INDEX IF NOT EXISTS idx_todos_session ON session_todos(session_id);
277
- -- Note: idx_todos_conversation is created in migrations to handle existing databases
278
- -- that may not have the conversation_id column yet
279
- CREATE INDEX IF NOT EXISTS idx_work_logs_session ON work_logs(session_id);
280
- CREATE INDEX IF NOT EXISTS idx_work_logs_message ON work_logs(message_id);
281
- CREATE INDEX IF NOT EXISTS idx_summaries_session ON session_summaries(session_id);
282
- CREATE INDEX IF NOT EXISTS idx_attachments_message ON message_attachments(message_id);
283
- CREATE INDEX IF NOT EXISTS idx_attachments_session ON message_attachments(session_id);
284
- CREATE INDEX IF NOT EXISTS idx_command_buttons_project ON command_buttons(project_id);
285
- CREATE INDEX IF NOT EXISTS idx_command_runs_session ON command_runs(session_id);
286
- CREATE INDEX IF NOT EXISTS idx_command_runs_button ON command_runs(button_id);
287
- CREATE INDEX IF NOT EXISTS idx_command_runs_status ON command_runs(status);
288
- CREATE INDEX IF NOT EXISTS idx_quick_responses_project ON quick_responses(project_id);
289
- CREATE INDEX IF NOT EXISTS idx_quick_responses_sort ON quick_responses(project_id, sort_order);
290
- CREATE INDEX IF NOT EXISTS idx_provider_models_provider ON provider_models(provider_id);
291
-
292
- -- Agent call logs (metrics and analytics for agent interactions)
293
286
  CREATE TABLE IF NOT EXISTS agent_call_logs (
294
287
  id TEXT PRIMARY KEY,
295
288
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
296
289
  conversation_id TEXT,
297
290
  agent_type TEXT NOT NULL,
298
291
  model TEXT,
299
-
300
- -- Request info
301
- call_type TEXT NOT NULL, -- 'runSession' | 'continueSession' | 'continueSessionWithExistingMessage'
292
+ call_type TEXT NOT NULL,
302
293
  prompt_length INTEGER,
303
-
304
- -- Response token info (updated during streaming and finalized at result)
305
294
  input_tokens INTEGER DEFAULT 0,
306
295
  output_tokens INTEGER DEFAULT 0,
307
296
  thinking_tokens INTEGER DEFAULT 0,
308
297
  cache_read_tokens INTEGER DEFAULT 0,
309
298
  cache_write_tokens INTEGER DEFAULT 0,
310
299
  total_tokens INTEGER DEFAULT 0,
311
-
312
- -- Timing
313
300
  started_at INTEGER NOT NULL,
314
301
  completed_at INTEGER,
315
302
  duration_ms INTEGER,
316
-
317
- -- Status
318
303
  status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'streaming', 'completed', 'error')),
319
304
  error_message TEXT,
320
-
321
- -- Metadata (JSON blob for extensibility)
322
305
  metadata TEXT,
323
-
324
306
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
325
307
  );
326
308
 
327
- CREATE INDEX IF NOT EXISTS idx_agent_call_logs_session ON agent_call_logs(session_id);
328
- CREATE INDEX IF NOT EXISTS idx_agent_call_logs_started ON agent_call_logs(started_at);
329
-
330
- -- Kanban boards (one per project)
331
309
  CREATE TABLE IF NOT EXISTS kanban_boards (
332
310
  id TEXT PRIMARY KEY,
333
311
  project_id TEXT NOT NULL UNIQUE REFERENCES projects(id) ON DELETE CASCADE,
@@ -335,7 +313,6 @@ CREATE TABLE IF NOT EXISTS kanban_boards (
335
313
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
336
314
  );
337
315
 
338
- -- Kanban lanes (columns on the board)
339
316
  CREATE TABLE IF NOT EXISTS kanban_lanes (
340
317
  id TEXT PRIMARY KEY,
341
318
  board_id TEXT NOT NULL REFERENCES kanban_boards(id) ON DELETE CASCADE,
@@ -343,11 +320,21 @@ CREATE TABLE IF NOT EXISTS kanban_lanes (
343
320
  sort_order INTEGER NOT NULL DEFAULT 0,
344
321
  on_enter_template_id TEXT REFERENCES session_templates(id) ON DELETE SET NULL,
345
322
  on_enter_prompt TEXT,
323
+ on_enter_mode TEXT,
324
+ on_enter_model TEXT,
325
+ on_enter_effort_level TEXT,
326
+ on_enter_thinking_enabled INTEGER,
327
+ on_enter_auto_reschedule_enabled INTEGER DEFAULT 0,
328
+ on_enter_reschedule_delay_minutes INTEGER DEFAULT 60,
329
+ on_enter_reschedule_on_token_limit INTEGER DEFAULT 1,
330
+ on_enter_reschedule_on_service_error INTEGER DEFAULT 1,
331
+ on_enter_max_reschedule_count INTEGER,
332
+ on_enter_max_total_tokens INTEGER,
333
+ on_enter_reschedule_at_token_count INTEGER,
346
334
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
347
335
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
348
336
  );
349
337
 
350
- -- Kanban cards (entries on the board, separate from sessions for future flexibility)
351
338
  CREATE TABLE IF NOT EXISTS kanban_cards (
352
339
  id TEXT PRIMARY KEY,
353
340
  lane_id TEXT NOT NULL REFERENCES kanban_lanes(id) ON DELETE CASCADE,
@@ -356,7 +343,6 @@ CREATE TABLE IF NOT EXISTS kanban_cards (
356
343
  updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
357
344
  );
358
345
 
359
- -- Kanban card sessions (links cards to sessions, currently 1:1 but allows future N:1)
360
346
  CREATE TABLE IF NOT EXISTS kanban_card_sessions (
361
347
  id TEXT PRIMARY KEY,
362
348
  card_id TEXT NOT NULL REFERENCES kanban_cards(id) ON DELETE CASCADE,
@@ -364,7 +350,42 @@ CREATE TABLE IF NOT EXISTS kanban_card_sessions (
364
350
  created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
365
351
  );
366
352
 
367
- -- Kanban indexes
353
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
354
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
355
+ CREATE INDEX IF NOT EXISTS idx_sessions_archived ON sessions(archived);
356
+ CREATE INDEX IF NOT EXISTS idx_sessions_starred ON sessions(archived, starred);
357
+ CREATE INDEX IF NOT EXISTS idx_sessions_next_template ON sessions(next_template_id);
358
+ CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id);
359
+ CREATE INDEX IF NOT EXISTS idx_sessions_scheduled ON sessions(scheduled_at) WHERE scheduled_at IS NOT NULL;
360
+ CREATE INDEX IF NOT EXISTS idx_conversations_session ON conversations(session_id);
361
+ CREATE INDEX IF NOT EXISTS idx_conversations_parent ON conversations(parent_conversation_id);
362
+ CREATE INDEX IF NOT EXISTS idx_messages_session ON conversation_messages(session_id);
363
+ CREATE INDEX IF NOT EXISTS idx_messages_conversation ON conversation_messages(conversation_id);
364
+ CREATE INDEX IF NOT EXISTS idx_canvas_session ON canvas_items(session_id);
365
+ CREATE INDEX IF NOT EXISTS idx_canvas_deleted ON canvas_items(deleted_at);
366
+ CREATE INDEX IF NOT EXISTS idx_project_tools ON project_tool_templates(project_id);
367
+ CREATE INDEX IF NOT EXISTS idx_session_templates_project ON session_templates(project_id);
368
+ CREATE INDEX IF NOT EXISTS idx_todos_session ON session_todos(session_id);
369
+ CREATE INDEX IF NOT EXISTS idx_todos_conversation ON session_todos(conversation_id);
370
+ CREATE INDEX IF NOT EXISTS idx_work_logs_session ON work_logs(session_id);
371
+ CREATE INDEX IF NOT EXISTS idx_work_logs_message ON work_logs(message_id);
372
+ CREATE INDEX IF NOT EXISTS idx_summaries_session ON session_summaries(session_id);
373
+ CREATE INDEX IF NOT EXISTS idx_attachments_message ON message_attachments(message_id);
374
+ CREATE INDEX IF NOT EXISTS idx_attachments_session ON message_attachments(session_id);
375
+ CREATE INDEX IF NOT EXISTS idx_command_buttons_project ON command_buttons(project_id);
376
+ CREATE INDEX IF NOT EXISTS idx_command_runs_session ON command_runs(session_id);
377
+ CREATE INDEX IF NOT EXISTS idx_command_runs_button ON command_runs(button_id);
378
+ CREATE INDEX IF NOT EXISTS idx_command_runs_status ON command_runs(status);
379
+ CREATE INDEX IF NOT EXISTS idx_quick_responses_project ON quick_responses(project_id);
380
+ CREATE INDEX IF NOT EXISTS idx_quick_responses_sort ON quick_responses(project_id, sort_order);
381
+ CREATE INDEX IF NOT EXISTS idx_provider_models_provider ON provider_models(provider_id);
382
+ CREATE INDEX IF NOT EXISTS idx_project_defaults_projectId ON project_session_defaults(project_id);
383
+ CREATE INDEX IF NOT EXISTS idx_agent_call_logs_session ON agent_call_logs(session_id);
384
+ CREATE INDEX IF NOT EXISTS idx_agent_call_logs_started ON agent_call_logs(started_at);
385
+ CREATE INDEX IF NOT EXISTS idx_agent_call_logs_agent_type ON agent_call_logs(agent_type);
386
+ CREATE INDEX IF NOT EXISTS idx_agent_call_logs_call_type ON agent_call_logs(call_type);
387
+ CREATE INDEX IF NOT EXISTS idx_agent_call_logs_status ON agent_call_logs(status);
388
+ CREATE INDEX IF NOT EXISTS idx_agent_call_logs_model ON agent_call_logs(model);
368
389
  CREATE INDEX IF NOT EXISTS idx_kanban_boards_project ON kanban_boards(project_id);
369
390
  CREATE INDEX IF NOT EXISTS idx_kanban_lanes_board ON kanban_lanes(board_id, sort_order);
370
391
  CREATE INDEX IF NOT EXISTS idx_kanban_cards_lane ON kanban_cards(lane_id, sort_order);
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { copyDatabaseBackups } from './dbUtils.js';
3
+
4
+ export function main() {
5
+ const result = copyDatabaseBackups();
6
+
7
+ if (result.copied.length === 0) {
8
+ console.log(`No database found at ${result.dbPath}; nothing to back up.`);
9
+ return 0;
10
+ }
11
+
12
+ console.log(`Database backup directory: ${result.backupDir}`);
13
+ for (const file of result.copied) {
14
+ console.log(`${file.source} -> ${file.target}`);
15
+ }
16
+ return 0;
17
+ }
18
+
19
+ if (import.meta.url === `file://${process.argv[1]}`) {
20
+ process.exit(main());
21
+ }
@@ -0,0 +1,81 @@
1
+ import { existsSync, mkdirSync, copyFileSync, mkdtempSync, rmSync } from 'node:fs';
2
+ import { basename, join } from 'node:path';
3
+ import { homedir, tmpdir } from 'node:os';
4
+ import { getDefaultDbPath } from '../config.js';
5
+ import { DatabaseManager } from '../db/DatabaseManager.js';
6
+
7
+ export function getActiveDbPath() {
8
+ return process.env.DB_PATH || getDefaultDbPath();
9
+ }
10
+
11
+ export function getSqliteSidecarPaths(dbPath) {
12
+ return [`${dbPath}-wal`, `${dbPath}-shm`];
13
+ }
14
+
15
+ export function getBackupDir() {
16
+ return join(homedir(), '.circuschief', 'backups');
17
+ }
18
+
19
+ export function copyDatabaseBackups(dbPath = getActiveDbPath(), timestamp = new Date()) {
20
+ if (!existsSync(dbPath)) {
21
+ return { dbPath, backupDir: getBackupDir(), copied: [], missing: [dbPath] };
22
+ }
23
+
24
+ const backupDir = getBackupDir();
25
+ mkdirSync(backupDir, { recursive: true });
26
+
27
+ const stamp = timestamp.toISOString().replace(/[:.]/g, '-');
28
+ const candidates = [dbPath, ...getSqliteSidecarPaths(dbPath)];
29
+ const copied = [];
30
+ const missing = [];
31
+
32
+ for (const source of candidates) {
33
+ if (!existsSync(source)) {
34
+ missing.push(source);
35
+ continue;
36
+ }
37
+ const target = join(backupDir, `${basename(source)}.${stamp}.bak`);
38
+ copyFileSync(source, target);
39
+ copied.push({ source, target });
40
+ }
41
+
42
+ return { dbPath, backupDir, copied, missing };
43
+ }
44
+
45
+ export function createFreshBaselineDb() {
46
+ const tempDir = mkdtempSync(join(tmpdir(), 'circuschief-baseline-'));
47
+ const dbPath = join(tempDir, 'baseline.db');
48
+ const manager = new DatabaseManager();
49
+ const db = manager.init(dbPath);
50
+
51
+ return {
52
+ db,
53
+ dbPath,
54
+ close() {
55
+ manager.close();
56
+ rmSync(tempDir, { recursive: true, force: true });
57
+ },
58
+ };
59
+ }
60
+
61
+ export function getSchemaObjects(db) {
62
+ return db.prepare(`
63
+ SELECT type, name, tbl_name, sql
64
+ FROM sqlite_master
65
+ WHERE type IN ('table', 'index', 'trigger', 'view')
66
+ AND name NOT LIKE 'sqlite_%'
67
+ ORDER BY type, name
68
+ `).all();
69
+ }
70
+
71
+ export function getTableColumns(db, tableName) {
72
+ return db.prepare(`PRAGMA table_info(${tableName})`).all();
73
+ }
74
+
75
+ export function getIndexColumns(db, indexName) {
76
+ return db.prepare(`PRAGMA index_info(${indexName})`).all().map((row) => row.name);
77
+ }
78
+
79
+ export function normalizeSql(sql) {
80
+ return (sql || '').replace(/\s+/g, ' ').trim();
81
+ }
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import Database from 'better-sqlite3';
3
+ import { existsSync } from 'node:fs';
4
+ import {
5
+ createFreshBaselineDb,
6
+ getActiveDbPath,
7
+ getSchemaObjects,
8
+ } from './dbUtils.js';
9
+
10
+ const BASELINE_TABLES = [
11
+ 'sessions',
12
+ 'projects',
13
+ 'project_session_defaults',
14
+ 'session_templates',
15
+ 'canvas_items',
16
+ 'providers',
17
+ 'provider_models',
18
+ 'kanban_lanes',
19
+ 'agent_call_logs',
20
+ ];
21
+
22
+ function printDatabase(db, label) {
23
+ console.log(`# ${label}`);
24
+
25
+ for (const row of getSchemaObjects(db)) {
26
+ console.log(`\n-- ${row.type} ${row.name} (${row.tbl_name})`);
27
+ console.log(row.sql || '');
28
+ }
29
+
30
+ for (const table of BASELINE_TABLES) {
31
+ const exists = db.prepare(
32
+ "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?"
33
+ ).get(table);
34
+ if (!exists) continue;
35
+
36
+ console.log(`\n-- PRAGMA table_info(${table})`);
37
+ console.table(db.prepare(`PRAGMA table_info(${table})`).all());
38
+ console.log(`-- PRAGMA foreign_key_list(${table})`);
39
+ console.table(db.prepare(`PRAGMA foreign_key_list(${table})`).all());
40
+ console.log(`-- PRAGMA index_list(${table})`);
41
+ const indexes = db.prepare(`PRAGMA index_list(${table})`).all();
42
+ console.table(indexes);
43
+
44
+ for (const index of indexes) {
45
+ console.log(`-- PRAGMA index_info(${index.name})`);
46
+ console.table(db.prepare(`PRAGMA index_info(${index.name})`).all());
47
+ console.log(`-- PRAGMA index_xinfo(${index.name})`);
48
+ console.table(db.prepare(`PRAGMA index_xinfo(${index.name})`).all());
49
+ }
50
+ }
51
+ }
52
+
53
+ export function main(argv = process.argv.slice(2)) {
54
+ if (argv.includes('--fresh')) {
55
+ const fresh = createFreshBaselineDb();
56
+ try {
57
+ printDatabase(fresh.db, `fresh baseline ${fresh.dbPath}`);
58
+ } finally {
59
+ fresh.close();
60
+ }
61
+ return 0;
62
+ }
63
+
64
+ const dbPath = getActiveDbPath();
65
+ if (!existsSync(dbPath)) {
66
+ console.log(`No database found at ${dbPath}; use --fresh to inspect a fresh baseline.`);
67
+ return 0;
68
+ }
69
+
70
+ const db = new Database(dbPath, { readonly: true, fileMustExist: true });
71
+ try {
72
+ printDatabase(db, `active database ${dbPath}`);
73
+ } finally {
74
+ db.close();
75
+ }
76
+ return 0;
77
+ }
78
+
79
+ if (import.meta.url === `file://${process.argv[1]}`) {
80
+ process.exit(main());
81
+ }