@loicngr/kobo 1.4.4 → 1.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/README.md +2 -2
- package/dist/mcp-server/kobo-tasks-handlers.js +12 -1
- package/dist/mcp-server/kobo-tasks-server.js +14 -2
- package/dist/server/db/index.js +5 -0
- package/dist/server/db/migrations.js +9 -0
- package/dist/server/db/schema.js +2 -0
- package/dist/server/index.js +17 -18
- package/dist/server/routes/dev-server.js +1 -0
- package/dist/server/routes/git.js +1 -0
- package/dist/server/routes/images.js +3 -0
- package/dist/server/routes/notion.js +1 -0
- package/dist/server/routes/settings.js +1 -0
- package/dist/server/routes/workspaces.js +128 -31
- package/dist/server/services/agent-manager.js +32 -8
- package/dist/server/services/dev-server-service.js +7 -2
- package/dist/server/services/image-service.js +2 -1
- package/dist/server/services/notion-service.js +3 -16
- package/dist/server/services/pr-watcher-service.js +2 -0
- package/dist/server/services/settings-service.js +17 -6
- package/dist/server/services/setup-script-service.js +1 -0
- package/dist/server/services/websocket-service.js +8 -9
- package/dist/server/services/workspace-service.js +33 -2
- package/dist/server/services/worktree-service.js +4 -2
- package/dist/server/utils/git-ops.js +19 -5
- package/dist/server/utils/process-tracker.js +7 -0
- package/package.json +1 -1
- package/src/client/dist/spa/assets/ActivityFeed-CLqD89Cm.css +1 -0
- package/src/client/dist/spa/assets/ActivityFeed-DTH_Ij7C.js +68 -0
- package/src/client/dist/spa/assets/CreatePage-BB7McDfT.js +2 -0
- package/src/client/dist/spa/assets/CreatePage-DAZADSsw.css +1 -0
- package/src/client/dist/spa/assets/DiffViewer-DpdWjInq.css +1 -0
- package/src/client/dist/spa/assets/DiffViewer-DsVIBuje.js +2 -0
- package/src/client/dist/spa/assets/MainLayout-BVmmyrJW.js +2 -0
- package/src/client/dist/spa/assets/MainLayout-POre8X3k.css +1 -0
- package/src/client/dist/spa/assets/QBadge-C_R3Tjb9.js +1 -0
- package/src/client/dist/spa/assets/QExpansionItem-BPtEjGYj.js +1 -0
- package/src/client/dist/spa/assets/QPage-BX_DOfKi.js +1 -0
- package/src/client/dist/spa/assets/QSeparator-y-UWrZSp.js +1 -0
- package/src/client/dist/spa/assets/QSpinnerDots-vpiOHlmN.js +1 -0
- package/src/client/dist/spa/assets/QTooltip-D5Om2o3Y.js +1 -0
- package/src/client/dist/spa/assets/SettingsPage-BKDbZp9_.js +1 -0
- package/src/client/dist/spa/assets/SettingsPage-Dv9gCOw8.css +1 -0
- package/src/client/dist/spa/assets/WorkspacePage-Cxt0YZv0.css +1 -0
- package/src/client/dist/spa/assets/WorkspacePage-g-Y3BuoI.js +2 -0
- package/src/client/dist/spa/assets/_plugin-vue_export-helper-BZV6EEeb.js +1 -0
- package/src/client/dist/spa/assets/{cssMode-Dsa4Lydc.js → cssMode-CbCp8SFU.js} +1 -1
- package/src/client/dist/spa/assets/{editor.api-qmVdKoc0.js → editor.api-Bm9nrcuM.js} +1 -1
- package/src/client/dist/spa/assets/{editor.main-D2fZAQLs.js → editor.main-DCE1BHWQ.js} +3 -3
- package/src/client/dist/spa/assets/{freemarker2-e-FYsZTq.js → freemarker2-BEroNSFG.js} +1 -1
- package/src/client/dist/spa/assets/{handlebars-CAwfoT2m.js → handlebars-C8QC92C9.js} +1 -1
- package/src/client/dist/spa/assets/{html-BTRUpMfA.js → html-Dh4rYZTt.js} +1 -1
- package/src/client/dist/spa/assets/{htmlMode-mzgQeoHf.js → htmlMode-BUP5qnTw.js} +1 -1
- package/src/client/dist/spa/assets/i18n-CvzmE5dV.js +1 -0
- package/src/client/dist/spa/assets/index-BRIQl1ry.js +5 -0
- package/src/client/dist/spa/assets/{javascript-DDeQuxhB.js → javascript-C3VBJatM.js} +1 -1
- package/src/client/dist/spa/assets/{jsonMode-Ch5vu2Iw.js → jsonMode-fNrYYkV5.js} +1 -1
- package/src/client/dist/spa/assets/{liquid-qVj3a9kh.js → liquid-BnBhHusK.js} +1 -1
- package/src/client/dist/spa/assets/{mdx-Cb_a7RXe.js → mdx-CTu1vGiw.js} +1 -1
- package/src/client/dist/spa/assets/{monaco.contribution-Rz70-mfd.js → monaco.contribution-Byxe-pOH.js} +2 -2
- package/src/client/dist/spa/assets/nodes-THUz-Chh.js +1 -0
- package/src/client/dist/spa/assets/{python-Cz0peIkX.js → python-CBrfgeGm.js} +1 -1
- package/src/client/dist/spa/assets/{razor-CU8Xe-4p.js → razor-CaMsFgrW.js} +1 -1
- package/src/client/dist/spa/assets/settings-CYWSNYAA.js +1 -0
- package/src/client/dist/spa/assets/{tsMode-CP1svEaN.js → tsMode-CrwxLvLR.js} +1 -1
- package/src/client/dist/spa/assets/{typescript-aPJGGIsI.js → typescript-BOH3igZy.js} +1 -1
- package/src/client/dist/spa/assets/use-checkbox-ts4I7GAt.js +1 -0
- package/src/client/dist/spa/assets/use-quasar-CIVlxSZ-.js +1 -0
- package/src/client/dist/spa/assets/{xml-CZM1zQhV.js → xml-BtA9_-M9.js} +1 -1
- package/src/client/dist/spa/assets/{yaml-B8qPirw0.js → yaml-vYOBELrg.js} +1 -1
- package/src/client/dist/spa/index.html +3 -3
- package/src/mcp-server/kobo-tasks-handlers.ts +21 -5
- package/src/mcp-server/kobo-tasks-server.ts +14 -2
- package/src/client/dist/spa/assets/ActivityFeed-Bx7maW4r.css +0 -1
- package/src/client/dist/spa/assets/ActivityFeed-DoQIjq5C.js +0 -60
- package/src/client/dist/spa/assets/CreatePage-6J0aDFtf.js +0 -2
- package/src/client/dist/spa/assets/CreatePage-DOr3puTt.css +0 -1
- package/src/client/dist/spa/assets/DiffViewer-7dck6mJc.css +0 -1
- package/src/client/dist/spa/assets/DiffViewer-dvGJaxu-.js +0 -2
- package/src/client/dist/spa/assets/MainLayout-DGzPKBi9.js +0 -2
- package/src/client/dist/spa/assets/MainLayout-gjAr74zR.css +0 -1
- package/src/client/dist/spa/assets/QBadge-Y5QfSDtm.js +0 -1
- package/src/client/dist/spa/assets/QBtn-D_bkYnrl.js +0 -1
- package/src/client/dist/spa/assets/QExpansionItem-D5gm2xc8.js +0 -1
- package/src/client/dist/spa/assets/QPage-Bg3Rohl6.js +0 -1
- package/src/client/dist/spa/assets/QSeparator-MRAsTeNf.js +0 -1
- package/src/client/dist/spa/assets/QSpinnerDots-Bu0GloxK.js +0 -1
- package/src/client/dist/spa/assets/QTooltip-Cuj49WIu.js +0 -1
- package/src/client/dist/spa/assets/SettingsPage-BNA4jJYf.css +0 -1
- package/src/client/dist/spa/assets/SettingsPage-BOkWnRl2.js +0 -1
- package/src/client/dist/spa/assets/WorkspacePage-9gRnhdjv.js +0 -2
- package/src/client/dist/spa/assets/WorkspacePage-CFC48jKO.css +0 -1
- package/src/client/dist/spa/assets/_plugin-vue_export-helper-Bo392ayB.js +0 -1
- package/src/client/dist/spa/assets/i18n-B1eQvEGk.js +0 -1
- package/src/client/dist/spa/assets/index-C0u2YcnZ.js +0 -5
- package/src/client/dist/spa/assets/nodes-Bo-5xQjA.js +0 -1
- package/src/client/dist/spa/assets/position-engine-CDz__T_5.js +0 -1
- package/src/client/dist/spa/assets/use-checkbox-B8W131xl.js +0 -1
- package/src/client/dist/spa/assets/use-quasar-sc8fDqi0.js +0 -1
- /package/src/client/dist/spa/assets/{formatters-CXx5Gzsp.js → formatters-B3FG1fMI.js} +0 -0
package/AGENTS.md
CHANGED
|
@@ -200,7 +200,7 @@ The frontend uses `vue-i18n` v10 with 5 supported locales: English (`en`), Frenc
|
|
|
200
200
|
|
|
201
201
|
### Commit rules (mirrors `DEFAULT_GIT_CONVENTIONS` in `src/server/services/settings-service.ts`)
|
|
202
202
|
|
|
203
|
-
These rules are the source of truth and are also written to `.ai
|
|
203
|
+
These rules are the source of truth and are also written to `.ai/.git-conventions.md` inside every workspace that the agent creates. Follow them when committing on this repository too.
|
|
204
204
|
|
|
205
205
|
**Commits**
|
|
206
206
|
- Use Conventional Commits: `type(scope): subject`
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Think of it as an apprentice's hall: you hand out missions, each apprentice sets
|
|
|
16
16
|
- **Task & acceptance criteria tracking** — the agent reports progress through a dedicated MCP server (`kobo-tasks`) that reads and updates tasks directly from the SQLite database
|
|
17
17
|
- **Notion integration** — pull workspace missions straight from Notion pages, extract markdown, and use it as the source of truth for acceptance criteria
|
|
18
18
|
- **Per-workspace dev servers** — start/stop Docker or Node dev servers scoped to each branch, with log streaming
|
|
19
|
-
- **Conventional-commit enforcement** — project-level git conventions are written to `.ai
|
|
19
|
+
- **Conventional-commit enforcement** — project-level git conventions are written to `.ai/.git-conventions.md` inside every workspace so Claude follows them during commits
|
|
20
20
|
- **Pull request automation** — one-click `push` and `open-pr` endpoints integrate with the GitHub CLI, using a configurable prompt template
|
|
21
21
|
- **Archive instead of delete** — soft-remove workspaces without losing the worktree, branches, or history; unarchive restores the exact pre-archive state
|
|
22
22
|
|
|
@@ -181,7 +181,7 @@ Kōbō reads settings from `~/.config/kobo/settings.json` (or falls back to defa
|
|
|
181
181
|
|
|
182
182
|
- `defaultModel` — Claude model to use (e.g. `claude-opus-4-6`)
|
|
183
183
|
- `prPromptTemplate` — template rendered when opening a PR via the `/open-pr` endpoint; supports `{{pr_number}}`, `{{pr_url}}`, `{{branch_name}}`, `{{diff_stats}}`, `{{commits}}`, etc.
|
|
184
|
-
- `gitConventions` — markdown-formatted git conventions written to `.ai
|
|
184
|
+
- `gitConventions` — markdown-formatted git conventions written to `.ai/.git-conventions.md` in every workspace so the agent follows them when committing
|
|
185
185
|
- `devServer` — per-project `startCommand` / `stopCommand` for launching workspace-scoped dev servers
|
|
186
186
|
|
|
187
187
|
## Contributing
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { nanoid } from 'nanoid';
|
|
4
|
+
/** Allowed task status values. */
|
|
4
5
|
export const VALID_TASK_STATUSES = ['pending', 'in_progress', 'done'];
|
|
5
6
|
function rowToDto(row) {
|
|
6
7
|
return {
|
|
@@ -10,12 +11,14 @@ function rowToDto(row) {
|
|
|
10
11
|
is_acceptance_criterion: row.is_acceptance_criterion === 1,
|
|
11
12
|
};
|
|
12
13
|
}
|
|
14
|
+
/** Return all tasks for a workspace, ordered by sort_order. */
|
|
13
15
|
export function listTasksHandler(db, workspaceId) {
|
|
14
16
|
const rows = db
|
|
15
17
|
.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE workspace_id = ? ORDER BY sort_order ASC')
|
|
16
18
|
.all(workspaceId);
|
|
17
19
|
return rows.map(rowToDto);
|
|
18
20
|
}
|
|
21
|
+
/** Set a task's status to "done" and return the updated task. */
|
|
19
22
|
export function markTaskDoneHandler(db, workspaceId, taskId) {
|
|
20
23
|
const now = new Date().toISOString();
|
|
21
24
|
const result = db
|
|
@@ -29,6 +32,7 @@ export function markTaskDoneHandler(db, workspaceId, taskId) {
|
|
|
29
32
|
.get(taskId);
|
|
30
33
|
return { success: true, task: rowToDto(row) };
|
|
31
34
|
}
|
|
35
|
+
/** Create a new task appended at the end of the workspace's task list. */
|
|
32
36
|
export function createTaskHandler(db, workspaceId, data) {
|
|
33
37
|
if (!data.title?.trim()) {
|
|
34
38
|
throw new Error('title is required');
|
|
@@ -50,6 +54,7 @@ export function createTaskHandler(db, workspaceId, data) {
|
|
|
50
54
|
const row = db.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE id = ?').get(id);
|
|
51
55
|
return rowToDto(row);
|
|
52
56
|
}
|
|
57
|
+
/** Update one or more fields of an existing task (title, status, or acceptance criterion flag). */
|
|
53
58
|
export function updateTaskHandler(db, workspaceId, taskId, data) {
|
|
54
59
|
// Verify task belongs to workspace
|
|
55
60
|
const existing = db.prepare('SELECT id FROM tasks WHERE id = ? AND workspace_id = ?').get(taskId, workspaceId);
|
|
@@ -87,6 +92,7 @@ export function updateTaskHandler(db, workspaceId, taskId, data) {
|
|
|
87
92
|
.get(taskId);
|
|
88
93
|
return rowToDto(row);
|
|
89
94
|
}
|
|
95
|
+
/** Permanently delete a task from a workspace. */
|
|
90
96
|
export function deleteTaskHandler(db, workspaceId, taskId) {
|
|
91
97
|
const result = db.prepare('DELETE FROM tasks WHERE id = ? AND workspace_id = ?').run(taskId, workspaceId);
|
|
92
98
|
if (result.changes === 0) {
|
|
@@ -94,6 +100,7 @@ export function deleteTaskHandler(db, workspaceId, taskId) {
|
|
|
94
100
|
}
|
|
95
101
|
return { success: true, task_id: taskId };
|
|
96
102
|
}
|
|
103
|
+
/** Read the dev-server status for a workspace directly from the database. */
|
|
97
104
|
export function getDevServerStatusHandler(db, workspaceId) {
|
|
98
105
|
const row = db.prepare('SELECT dev_server_status FROM workspaces WHERE id = ?').get(workspaceId);
|
|
99
106
|
if (!row) {
|
|
@@ -101,6 +108,7 @@ export function getDevServerStatusHandler(db, workspaceId) {
|
|
|
101
108
|
}
|
|
102
109
|
return { workspaceId, status: row.dev_server_status };
|
|
103
110
|
}
|
|
111
|
+
/** Read global and per-project settings from the JSON file on disk. */
|
|
104
112
|
export function getSettingsHandler(settingsPath, projectPath) {
|
|
105
113
|
// Shape is determined solely by whether projectPath was provided:
|
|
106
114
|
// - with projectPath → { global, project }
|
|
@@ -125,9 +133,10 @@ export function getSettingsHandler(settingsPath, projectPath) {
|
|
|
125
133
|
}
|
|
126
134
|
return { global, projects };
|
|
127
135
|
}
|
|
136
|
+
/** Fetch workspace metadata from the database, computing the worktree path from project_path and working_branch. */
|
|
128
137
|
export function getWorkspaceInfoHandler(db, workspaceId) {
|
|
129
138
|
const row = db
|
|
130
|
-
.prepare('SELECT id, name, project_path, source_branch, working_branch, status, notion_url, notion_page_id, model, dev_server_status, created_at, updated_at FROM workspaces WHERE id = ?')
|
|
139
|
+
.prepare('SELECT id, name, project_path, source_branch, working_branch, status, notion_url, notion_page_id, model, dev_server_status, has_unread, created_at, updated_at FROM workspaces WHERE id = ?')
|
|
131
140
|
.get(workspaceId);
|
|
132
141
|
if (!row) {
|
|
133
142
|
throw new Error(`Workspace '${workspaceId}' not found`);
|
|
@@ -144,10 +153,12 @@ export function getWorkspaceInfoHandler(db, workspaceId) {
|
|
|
144
153
|
notionUrl: row.notion_url,
|
|
145
154
|
notionPageId: row.notion_page_id,
|
|
146
155
|
devServerStatus: row.dev_server_status,
|
|
156
|
+
hasUnread: row.has_unread === 1,
|
|
147
157
|
createdAt: row.created_at,
|
|
148
158
|
updatedAt: row.updated_at,
|
|
149
159
|
};
|
|
150
160
|
}
|
|
161
|
+
/** List images registered in the worktree's `.ai/images/index.json`, resolving each entry to its file path. */
|
|
151
162
|
export function listWorkspaceImagesHandler(worktreePath) {
|
|
152
163
|
const imagesDir = path.join(worktreePath, '.ai', 'images');
|
|
153
164
|
const indexPath = path.join(imagesDir, 'index.json');
|
|
@@ -27,6 +27,7 @@ catch (err) {
|
|
|
27
27
|
console.error('[kobo-tasks-server] Failed to open database:', err);
|
|
28
28
|
process.exit(1);
|
|
29
29
|
}
|
|
30
|
+
/** Fire-and-forget POST to the backend so the UI reflects a task marked as done. */
|
|
30
31
|
async function notifyBackend(taskId) {
|
|
31
32
|
try {
|
|
32
33
|
const url = `${backendUrl}/api/workspaces/${workspaceId}/tasks/${taskId}/notify-done`;
|
|
@@ -39,6 +40,7 @@ async function notifyBackend(taskId) {
|
|
|
39
40
|
console.error('[kobo-tasks-server] notify-done failed:', err);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
43
|
+
/** Fire-and-forget POST to the backend so the UI refreshes the task list after a mutation. */
|
|
42
44
|
async function notifyTasksUpdated() {
|
|
43
45
|
try {
|
|
44
46
|
const url = `${backendUrl}/api/workspaces/${workspaceId}/tasks/notify-updated`;
|
|
@@ -48,6 +50,7 @@ async function notifyTasksUpdated() {
|
|
|
48
50
|
console.error('[kobo-tasks-server] notify-updated failed:', err);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
53
|
+
/** Generic HTTP request to the Kobo backend, returning parsed JSON or null. */
|
|
51
54
|
async function backendRequest(method, pathname, body) {
|
|
52
55
|
const url = `${backendUrl}${pathname}`;
|
|
53
56
|
const init = { method };
|
|
@@ -152,7 +155,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
152
155
|
},
|
|
153
156
|
{
|
|
154
157
|
name: 'get_dev_server_status',
|
|
155
|
-
description: '
|
|
158
|
+
description: 'Get the live dev server status for the current workspace. Returns status (running/stopped/starting/error/unknown), URL, HTTP port, instance name, project name, and running container names.',
|
|
156
159
|
inputSchema: {
|
|
157
160
|
type: 'object',
|
|
158
161
|
properties: {},
|
|
@@ -215,9 +218,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
215
218
|
},
|
|
216
219
|
],
|
|
217
220
|
}));
|
|
221
|
+
/** Wrap a successful result as an MCP tool response with JSON text content. */
|
|
218
222
|
function ok(data) {
|
|
219
223
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
220
224
|
}
|
|
225
|
+
/** Wrap an error message as an MCP tool error response. */
|
|
221
226
|
function fail(message) {
|
|
222
227
|
return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
|
|
223
228
|
}
|
|
@@ -271,7 +276,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
271
276
|
return ok(getSettingsHandler(settingsPath, a.project_path));
|
|
272
277
|
}
|
|
273
278
|
if (name === 'get_dev_server_status') {
|
|
274
|
-
|
|
279
|
+
try {
|
|
280
|
+
const result = await backendRequest('GET', `/api/dev-server/${workspaceId}/status`);
|
|
281
|
+
return ok(result);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Fallback to DB if the backend HTTP API is unreachable
|
|
285
|
+
return ok(getDevServerStatusHandler(db, workspaceId));
|
|
286
|
+
}
|
|
275
287
|
}
|
|
276
288
|
if (name === 'get_workspace_info') {
|
|
277
289
|
return ok(getWorkspaceInfoHandler(db, workspaceId));
|
package/dist/server/db/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import { ensureKoboHome, getDbPath } from '../utils/paths.js';
|
|
3
3
|
let instance = null;
|
|
4
|
+
/**
|
|
5
|
+
* Return the singleton SQLite database connection, creating it on first call.
|
|
6
|
+
* Configures WAL mode, busy timeout, and foreign keys.
|
|
7
|
+
*/
|
|
4
8
|
export function getDb(dbPath) {
|
|
5
9
|
if (instance)
|
|
6
10
|
return instance;
|
|
@@ -15,6 +19,7 @@ export function getDb(dbPath) {
|
|
|
15
19
|
instance.pragma('foreign_keys=ON');
|
|
16
20
|
return instance;
|
|
17
21
|
}
|
|
22
|
+
/** Close the singleton database connection and release resources. */
|
|
18
23
|
export function closeDb() {
|
|
19
24
|
if (instance) {
|
|
20
25
|
instance.close();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { initSchema } from './schema.js';
|
|
2
|
+
/** Ordered registry of all schema migrations. Append new entries at the end. */
|
|
2
3
|
export const migrations = [
|
|
3
4
|
{
|
|
4
5
|
version: 2,
|
|
@@ -18,9 +19,17 @@ export const migrations = [
|
|
|
18
19
|
`);
|
|
19
20
|
},
|
|
20
21
|
},
|
|
22
|
+
{
|
|
23
|
+
version: 4,
|
|
24
|
+
name: 'add-has-unread',
|
|
25
|
+
migrate: (db) => {
|
|
26
|
+
db.prepare('ALTER TABLE workspaces ADD COLUMN has_unread INTEGER NOT NULL DEFAULT 0').run();
|
|
27
|
+
},
|
|
28
|
+
},
|
|
21
29
|
];
|
|
22
30
|
/** Current schema version — always equals the highest migration version. */
|
|
23
31
|
export const SCHEMA_VERSION = migrations.length > 0 ? migrations[migrations.length - 1].version : 1;
|
|
32
|
+
/** Apply all pending migrations sequentially, or bootstrap a fresh database via initSchema. */
|
|
24
33
|
export function runMigrations(db) {
|
|
25
34
|
// Create the history table (replaces the old single-row schema_version table).
|
|
26
35
|
db.exec(`
|
package/dist/server/db/schema.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Create all tables and indexes for a fresh install. Not used for upgrades -- see migrations.ts. */
|
|
1
2
|
export function initSchema(db) {
|
|
2
3
|
db.exec(`
|
|
3
4
|
CREATE TABLE IF NOT EXISTS workspaces (
|
|
@@ -12,6 +13,7 @@ export function initSchema(db) {
|
|
|
12
13
|
model TEXT NOT NULL DEFAULT 'claude-opus-4-6',
|
|
13
14
|
permission_mode TEXT NOT NULL DEFAULT 'auto-accept',
|
|
14
15
|
dev_server_status TEXT NOT NULL DEFAULT 'stopped',
|
|
16
|
+
has_unread INTEGER NOT NULL DEFAULT 0,
|
|
15
17
|
archived_at TEXT,
|
|
16
18
|
created_at TEXT NOT NULL,
|
|
17
19
|
updated_at TEXT NOT NULL
|
package/dist/server/index.js
CHANGED
|
@@ -20,9 +20,8 @@ import { emit, handleConnection, setMessageHandler } from './services/websocket-
|
|
|
20
20
|
import { getLatestSession, getWorkspace, updateWorkspaceStatus } from './services/workspace-service.js';
|
|
21
21
|
import { getClientSpaPath, getKoboHome, getPackageVersion } from './utils/paths.js';
|
|
22
22
|
import { initProcessCleanup, killAll as killAllTrackedProcesses } from './utils/process-tracker.js';
|
|
23
|
-
//
|
|
24
|
-
// startup: the user may still want to configure settings or browse workspaces
|
|
25
|
-
// before installing Claude Code.
|
|
23
|
+
// Runtime prerequisite check — warn if the claude CLI is missing. Don't block
|
|
24
|
+
// startup: the user may still want to configure settings or browse workspaces.
|
|
26
25
|
{
|
|
27
26
|
const check = spawnSync('claude', ['--version'], { stdio: 'ignore' });
|
|
28
27
|
if (check.error && check.error.code === 'ENOENT') {
|
|
@@ -30,18 +29,18 @@ import { initProcessCleanup, killAll as killAllTrackedProcesses } from './utils/
|
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
console.log(`[kobo] Kōbō home: ${getKoboHome()}`);
|
|
33
|
-
//
|
|
32
|
+
// Initialize DB + run migrations
|
|
34
33
|
const db = getDb();
|
|
35
34
|
runMigrations(db);
|
|
36
|
-
//
|
|
35
|
+
// Initialize process cleanup, agent watchdog, and PR watcher
|
|
37
36
|
initProcessCleanup();
|
|
38
37
|
startWatchdog();
|
|
39
38
|
startPrWatcher();
|
|
40
|
-
//
|
|
39
|
+
// Create Hono app
|
|
41
40
|
const app = new Hono();
|
|
42
41
|
// Health check (root / is handled by the SPA catch-all below)
|
|
43
42
|
app.get('/api/health', (c) => c.json({ status: 'ok', version: getPackageVersion() }));
|
|
44
|
-
//
|
|
43
|
+
// Mount route sub-routers
|
|
45
44
|
app.route('/api/workspaces', workspacesRouter);
|
|
46
45
|
app.route('/api/workspaces', imagesRouter);
|
|
47
46
|
app.route('/api/notion', notionRouter);
|
|
@@ -51,7 +50,7 @@ app.route('/api/dev-server', devServerRouter);
|
|
|
51
50
|
// Skills endpoint
|
|
52
51
|
app.get('/api/skills', (c) => c.json(getAvailableSkills()));
|
|
53
52
|
const PORT = parseInt(process.env.SERVER_PORT || process.env.PORT || '3000', 10);
|
|
54
|
-
//
|
|
53
|
+
// Serve static files from the built SPA if present (production mode).
|
|
55
54
|
// The path is resolved relative to the package install directory, so this
|
|
56
55
|
// works both in dev (tsx running from src/) and when installed via npm / npx
|
|
57
56
|
// (node running from dist/).
|
|
@@ -91,7 +90,7 @@ if (clientDistPath) {
|
|
|
91
90
|
});
|
|
92
91
|
});
|
|
93
92
|
}
|
|
94
|
-
//
|
|
93
|
+
// Create HTTP server via @hono/node-server
|
|
95
94
|
const server = serve({
|
|
96
95
|
fetch: app.fetch,
|
|
97
96
|
port: PORT,
|
|
@@ -99,13 +98,13 @@ const server = serve({
|
|
|
99
98
|
setBackendPort(info.port);
|
|
100
99
|
console.log(`Server running at http://localhost:${info.port}`);
|
|
101
100
|
});
|
|
102
|
-
//
|
|
101
|
+
// Create WebSocketServer attached to the HTTP server
|
|
103
102
|
const wss = new WebSocketServer({ noServer: true });
|
|
104
|
-
//
|
|
103
|
+
// Wire WebSocket connections to websocket-service.handleConnection()
|
|
105
104
|
wss.on('connection', (ws) => {
|
|
106
105
|
handleConnection(ws);
|
|
107
106
|
});
|
|
108
|
-
//
|
|
107
|
+
// Wire websocket-service message handler to agent-manager
|
|
109
108
|
setMessageHandler((type, payload) => {
|
|
110
109
|
const p = payload;
|
|
111
110
|
if (type === 'chat:message' && p?.workspaceId && p?.content) {
|
|
@@ -182,21 +181,21 @@ server.on('upgrade', (request, socket, head) => {
|
|
|
182
181
|
socket.destroy();
|
|
183
182
|
}
|
|
184
183
|
});
|
|
185
|
-
//
|
|
184
|
+
// Graceful shutdown handler
|
|
186
185
|
let isShuttingDown = false;
|
|
187
186
|
function gracefulShutdown(signal) {
|
|
188
187
|
if (isShuttingDown)
|
|
189
188
|
return;
|
|
190
189
|
isShuttingDown = true;
|
|
191
190
|
console.log(`\n[kobo] Received ${signal}, shutting down gracefully…`);
|
|
192
|
-
//
|
|
191
|
+
// Stop accepting new connections
|
|
193
192
|
wss.close(() => {
|
|
194
193
|
console.log('[kobo] WebSocket server closed');
|
|
195
194
|
});
|
|
196
195
|
server.close(() => {
|
|
197
196
|
console.log('[kobo] HTTP server closed');
|
|
198
197
|
});
|
|
199
|
-
//
|
|
198
|
+
// Stop background services
|
|
200
199
|
try {
|
|
201
200
|
stopWatchdog();
|
|
202
201
|
}
|
|
@@ -209,7 +208,7 @@ function gracefulShutdown(signal) {
|
|
|
209
208
|
catch {
|
|
210
209
|
// Best-effort
|
|
211
210
|
}
|
|
212
|
-
//
|
|
211
|
+
// Kill all tracked child processes (agents, dev servers)
|
|
213
212
|
try {
|
|
214
213
|
killAllTrackedProcesses();
|
|
215
214
|
console.log('[kobo] Tracked processes killed');
|
|
@@ -217,7 +216,7 @@ function gracefulShutdown(signal) {
|
|
|
217
216
|
catch {
|
|
218
217
|
// Best-effort
|
|
219
218
|
}
|
|
220
|
-
//
|
|
219
|
+
// Close database
|
|
221
220
|
try {
|
|
222
221
|
closeDb();
|
|
223
222
|
console.log('[kobo] Database closed');
|
|
@@ -225,7 +224,7 @@ function gracefulShutdown(signal) {
|
|
|
225
224
|
catch {
|
|
226
225
|
// Best-effort
|
|
227
226
|
}
|
|
228
|
-
//
|
|
227
|
+
// Give a short grace period for in-flight requests, then exit
|
|
229
228
|
setTimeout(() => {
|
|
230
229
|
console.log('[kobo] Shutdown complete');
|
|
231
230
|
process.exit(0);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { getDevServerLogs, getStatus, startDevServer, stopDevServer } from '../services/dev-server-service.js';
|
|
3
3
|
import { getWorkspace } from '../services/workspace-service.js';
|
|
4
|
+
/** Hono sub-router for per-workspace dev server lifecycle (start, stop, status, logs). */
|
|
4
5
|
const app = new Hono();
|
|
5
6
|
// GET /api/dev-server/:workspaceId/status
|
|
6
7
|
app.get('/:workspaceId/status', (c) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { listBranches, listRemoteBranches } from '../utils/git-ops.js';
|
|
3
|
+
/** Hono sub-router for git-related endpoints (branch listing). */
|
|
3
4
|
const app = new Hono();
|
|
4
5
|
// GET /api/git/branches?path=<repoPath> — list branches for a repo
|
|
5
6
|
app.get('/branches', (c) => {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import * as imageService from '../services/image-service.js';
|
|
3
3
|
import * as workspaceService from '../services/workspace-service.js';
|
|
4
|
+
/** Maximum allowed upload size for a single image (10 MB). */
|
|
4
5
|
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
6
|
+
/** MIME types accepted for image uploads. */
|
|
5
7
|
const ALLOWED_MIME_TYPES = new Set(['image/png', 'image/jpeg', 'image/gif', 'image/webp']);
|
|
8
|
+
/** Hono sub-router for workspace image upload and deletion. */
|
|
6
9
|
const app = new Hono();
|
|
7
10
|
// POST /:id/images — upload an image
|
|
8
11
|
app.post('/:id/images', async (c) => {
|