@hasna/todos 0.4.1 → 0.5.1
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/dist/cli/index.js +173 -2
- package/dist/index.d.ts +19 -0
- package/dist/index.js +68 -0
- package/dist/mcp/index.js +13 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2319,6 +2319,19 @@ var init_database = __esm(() => {
|
|
|
2319
2319
|
ALTER TABLE tasks ADD COLUMN plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL;
|
|
2320
2320
|
CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id);
|
|
2321
2321
|
INSERT OR IGNORE INTO _migrations (id) VALUES (4);
|
|
2322
|
+
`,
|
|
2323
|
+
`
|
|
2324
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
2325
|
+
id TEXT PRIMARY KEY,
|
|
2326
|
+
name TEXT NOT NULL,
|
|
2327
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
2328
|
+
key_prefix TEXT NOT NULL,
|
|
2329
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2330
|
+
last_used_at TEXT,
|
|
2331
|
+
expires_at TEXT
|
|
2332
|
+
);
|
|
2333
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
|
|
2334
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (5);
|
|
2322
2335
|
`
|
|
2323
2336
|
];
|
|
2324
2337
|
});
|
|
@@ -8015,6 +8028,60 @@ ${text}` }] };
|
|
|
8015
8028
|
});
|
|
8016
8029
|
});
|
|
8017
8030
|
|
|
8031
|
+
// src/db/api-keys.ts
|
|
8032
|
+
function generateApiKey() {
|
|
8033
|
+
const bytes = new Uint8Array(32);
|
|
8034
|
+
crypto.getRandomValues(bytes);
|
|
8035
|
+
return "td_" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8036
|
+
}
|
|
8037
|
+
async function hashKey(key) {
|
|
8038
|
+
const encoder = new TextEncoder;
|
|
8039
|
+
const data = encoder.encode(key);
|
|
8040
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
8041
|
+
return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8042
|
+
}
|
|
8043
|
+
async function createApiKey(input, db) {
|
|
8044
|
+
const d = db || getDatabase();
|
|
8045
|
+
const id = uuid();
|
|
8046
|
+
const timestamp = now();
|
|
8047
|
+
const key = generateApiKey();
|
|
8048
|
+
const keyHash = await hashKey(key);
|
|
8049
|
+
const keyPrefix = key.slice(0, 10) + "...";
|
|
8050
|
+
d.run(`INSERT INTO api_keys (id, name, key_hash, key_prefix, created_at, expires_at)
|
|
8051
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, keyHash, keyPrefix, timestamp, input.expires_at || null]);
|
|
8052
|
+
const row = d.query("SELECT id, name, key_prefix, created_at, last_used_at, expires_at FROM api_keys WHERE id = ?").get(id);
|
|
8053
|
+
return { ...row, key };
|
|
8054
|
+
}
|
|
8055
|
+
function listApiKeys(db) {
|
|
8056
|
+
const d = db || getDatabase();
|
|
8057
|
+
return d.query("SELECT id, name, key_prefix, created_at, last_used_at, expires_at FROM api_keys ORDER BY created_at DESC").all();
|
|
8058
|
+
}
|
|
8059
|
+
function deleteApiKey(id, db) {
|
|
8060
|
+
const d = db || getDatabase();
|
|
8061
|
+
const result = d.run("DELETE FROM api_keys WHERE id = ?", [id]);
|
|
8062
|
+
return result.changes > 0;
|
|
8063
|
+
}
|
|
8064
|
+
async function validateApiKey(key, db) {
|
|
8065
|
+
const d = db || getDatabase();
|
|
8066
|
+
const keyHash = await hashKey(key);
|
|
8067
|
+
const row = d.query("SELECT id, name, key_prefix, created_at, last_used_at, expires_at FROM api_keys WHERE key_hash = ?").get(keyHash);
|
|
8068
|
+
if (!row)
|
|
8069
|
+
return null;
|
|
8070
|
+
if (row.expires_at && new Date(row.expires_at) < new Date) {
|
|
8071
|
+
return null;
|
|
8072
|
+
}
|
|
8073
|
+
d.run("UPDATE api_keys SET last_used_at = ? WHERE id = ?", [now(), row.id]);
|
|
8074
|
+
return row;
|
|
8075
|
+
}
|
|
8076
|
+
function hasAnyApiKeys(db) {
|
|
8077
|
+
const d = db || getDatabase();
|
|
8078
|
+
const row = d.query("SELECT COUNT(*) as count FROM api_keys").get();
|
|
8079
|
+
return (row?.count ?? 0) > 0;
|
|
8080
|
+
}
|
|
8081
|
+
var init_api_keys = __esm(() => {
|
|
8082
|
+
init_database();
|
|
8083
|
+
});
|
|
8084
|
+
|
|
8018
8085
|
// src/server/serve.ts
|
|
8019
8086
|
var exports_serve = {};
|
|
8020
8087
|
__export(exports_serve, {
|
|
@@ -8092,18 +8159,35 @@ function createFetchHandler(getPort, dashboardDir, dashboardExists) {
|
|
|
8092
8159
|
}
|
|
8093
8160
|
const method = req.method;
|
|
8094
8161
|
const port = getPort();
|
|
8162
|
+
if (path.startsWith("/api/") && !path.startsWith("/api/system/") && !path.startsWith("/api/keys")) {
|
|
8163
|
+
const hasKeys = hasAnyApiKeys();
|
|
8164
|
+
if (hasKeys) {
|
|
8165
|
+
const authHeader = req.headers.get("authorization");
|
|
8166
|
+
const apiKey = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
8167
|
+
if (!apiKey) {
|
|
8168
|
+
return json({ error: "API key required. Pass via Authorization: Bearer <key>" }, 401, port);
|
|
8169
|
+
}
|
|
8170
|
+
const valid = await validateApiKey(apiKey);
|
|
8171
|
+
if (!valid) {
|
|
8172
|
+
return json({ error: "Invalid or expired API key" }, 403, port);
|
|
8173
|
+
}
|
|
8174
|
+
}
|
|
8175
|
+
}
|
|
8095
8176
|
if (path === "/api/tasks" && method === "GET") {
|
|
8096
8177
|
try {
|
|
8097
8178
|
const filter = {};
|
|
8098
8179
|
const status = url.searchParams.get("status");
|
|
8099
8180
|
const priority = url.searchParams.get("priority");
|
|
8100
8181
|
const projectId = url.searchParams.get("project_id");
|
|
8182
|
+
const planId = url.searchParams.get("plan_id");
|
|
8101
8183
|
if (status)
|
|
8102
8184
|
filter.status = status;
|
|
8103
8185
|
if (priority)
|
|
8104
8186
|
filter.priority = priority;
|
|
8105
8187
|
if (projectId)
|
|
8106
8188
|
filter.project_id = projectId;
|
|
8189
|
+
if (planId)
|
|
8190
|
+
filter.plan_id = planId;
|
|
8107
8191
|
const tasks = listTasks(filter);
|
|
8108
8192
|
const projectCache = new Map;
|
|
8109
8193
|
const planCache = new Map;
|
|
@@ -8466,6 +8550,39 @@ function createFetchHandler(getPort, dashboardDir, dashboardExists) {
|
|
|
8466
8550
|
return json({ error: e instanceof Error ? e.message : "Failed to delete plan" }, 500, port);
|
|
8467
8551
|
}
|
|
8468
8552
|
}
|
|
8553
|
+
const projectGetMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
8554
|
+
if (projectGetMatch && method === "GET") {
|
|
8555
|
+
try {
|
|
8556
|
+
const id = projectGetMatch[1];
|
|
8557
|
+
const project = getProject(id);
|
|
8558
|
+
if (!project)
|
|
8559
|
+
return json({ error: "Project not found" }, 404, port);
|
|
8560
|
+
return json(project, 200, port);
|
|
8561
|
+
} catch (e) {
|
|
8562
|
+
return json({ error: e instanceof Error ? e.message : "Failed to get project" }, 500, port);
|
|
8563
|
+
}
|
|
8564
|
+
}
|
|
8565
|
+
const projectPatchMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
8566
|
+
if (projectPatchMatch && method === "PATCH") {
|
|
8567
|
+
try {
|
|
8568
|
+
const id = projectPatchMatch[1];
|
|
8569
|
+
const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
|
|
8570
|
+
if (contentLength > MAX_BODY_SIZE)
|
|
8571
|
+
return json({ error: "Request body too large" }, 413, port);
|
|
8572
|
+
const body = await parseJsonBody(req);
|
|
8573
|
+
if (!body)
|
|
8574
|
+
return json({ error: "Invalid JSON" }, 400, port);
|
|
8575
|
+
const project = updateProject(id, {
|
|
8576
|
+
name: typeof body.name === "string" ? body.name : undefined,
|
|
8577
|
+
description: typeof body.description === "string" ? body.description : body.description === null ? "" : undefined,
|
|
8578
|
+
task_list_id: typeof body.task_list_id === "string" ? body.task_list_id : body.task_list_id === null ? "" : undefined
|
|
8579
|
+
});
|
|
8580
|
+
return json(project, 200, port);
|
|
8581
|
+
} catch (e) {
|
|
8582
|
+
const status = e instanceof Error && e.name === "ProjectNotFoundError" ? 404 : 500;
|
|
8583
|
+
return json({ error: e instanceof Error ? e.message : "Failed to update project" }, status, port);
|
|
8584
|
+
}
|
|
8585
|
+
}
|
|
8469
8586
|
const projectDeleteMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
8470
8587
|
if (projectDeleteMatch && method === "DELETE") {
|
|
8471
8588
|
try {
|
|
@@ -8478,12 +8595,61 @@ function createFetchHandler(getPort, dashboardDir, dashboardExists) {
|
|
|
8478
8595
|
return json({ error: e instanceof Error ? e.message : "Failed to delete project" }, 500, port);
|
|
8479
8596
|
}
|
|
8480
8597
|
}
|
|
8598
|
+
if (path === "/api/keys" && method === "GET") {
|
|
8599
|
+
try {
|
|
8600
|
+
const keys = listApiKeys();
|
|
8601
|
+
return json(keys, 200, port);
|
|
8602
|
+
} catch (e) {
|
|
8603
|
+
return json({ error: e instanceof Error ? e.message : "Failed to list API keys" }, 500, port);
|
|
8604
|
+
}
|
|
8605
|
+
}
|
|
8606
|
+
if (path === "/api/keys" && method === "POST") {
|
|
8607
|
+
try {
|
|
8608
|
+
const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
|
|
8609
|
+
if (contentLength > MAX_BODY_SIZE)
|
|
8610
|
+
return json({ error: "Request body too large" }, 413, port);
|
|
8611
|
+
const body = await parseJsonBody(req);
|
|
8612
|
+
if (!body)
|
|
8613
|
+
return json({ error: "Invalid JSON" }, 400, port);
|
|
8614
|
+
if (!body.name || typeof body.name !== "string") {
|
|
8615
|
+
return json({ error: "Missing required field: name" }, 400, port);
|
|
8616
|
+
}
|
|
8617
|
+
const parsed = createApiKeySchema.safeParse(body);
|
|
8618
|
+
if (!parsed.success) {
|
|
8619
|
+
return json({ error: "Invalid request body" }, 400, port);
|
|
8620
|
+
}
|
|
8621
|
+
const apiKey = await createApiKey(parsed.data);
|
|
8622
|
+
return json(apiKey, 201, port);
|
|
8623
|
+
} catch (e) {
|
|
8624
|
+
return json({ error: e instanceof Error ? e.message : "Failed to create API key" }, 500, port);
|
|
8625
|
+
}
|
|
8626
|
+
}
|
|
8627
|
+
const keyDeleteMatch = path.match(/^\/api\/keys\/([^/]+)$/);
|
|
8628
|
+
if (keyDeleteMatch && method === "DELETE") {
|
|
8629
|
+
try {
|
|
8630
|
+
const id = keyDeleteMatch[1];
|
|
8631
|
+
const deleted = deleteApiKey(id);
|
|
8632
|
+
if (!deleted)
|
|
8633
|
+
return json({ error: "API key not found" }, 404, port);
|
|
8634
|
+
return json({ deleted: true }, 200, port);
|
|
8635
|
+
} catch (e) {
|
|
8636
|
+
return json({ error: e instanceof Error ? e.message : "Failed to delete API key" }, 500, port);
|
|
8637
|
+
}
|
|
8638
|
+
}
|
|
8639
|
+
if (path === "/api/keys/status" && method === "GET") {
|
|
8640
|
+
try {
|
|
8641
|
+
const enabled = hasAnyApiKeys();
|
|
8642
|
+
return json({ auth_enabled: enabled }, 200, port);
|
|
8643
|
+
} catch (e) {
|
|
8644
|
+
return json({ error: e instanceof Error ? e.message : "Failed to check auth status" }, 500, port);
|
|
8645
|
+
}
|
|
8646
|
+
}
|
|
8481
8647
|
if (method === "OPTIONS") {
|
|
8482
8648
|
return new Response(null, {
|
|
8483
8649
|
headers: {
|
|
8484
8650
|
"Access-Control-Allow-Origin": `http://localhost:${port}`,
|
|
8485
8651
|
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
8486
|
-
"Access-Control-Allow-Headers": "Content-Type"
|
|
8652
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
8487
8653
|
}
|
|
8488
8654
|
});
|
|
8489
8655
|
}
|
|
@@ -8559,7 +8725,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
8559
8725
|
}
|
|
8560
8726
|
return server2;
|
|
8561
8727
|
}
|
|
8562
|
-
var MIME_TYPES, SECURITY_HEADERS, MAX_BODY_SIZE, createTaskSchema, updateTaskSchema, createProjectSchema, createCommentSchema, createPlanSchema, updatePlanSchema, agentSchema;
|
|
8728
|
+
var MIME_TYPES, SECURITY_HEADERS, MAX_BODY_SIZE, createTaskSchema, updateTaskSchema, createProjectSchema, createCommentSchema, createPlanSchema, updatePlanSchema, createApiKeySchema, agentSchema;
|
|
8563
8729
|
var init_serve = __esm(() => {
|
|
8564
8730
|
init_zod();
|
|
8565
8731
|
init_tasks();
|
|
@@ -8567,6 +8733,7 @@ var init_serve = __esm(() => {
|
|
|
8567
8733
|
init_plans();
|
|
8568
8734
|
init_database();
|
|
8569
8735
|
init_comments();
|
|
8736
|
+
init_api_keys();
|
|
8570
8737
|
init_search();
|
|
8571
8738
|
MIME_TYPES = {
|
|
8572
8739
|
".html": "text/html; charset=utf-8",
|
|
@@ -8630,6 +8797,10 @@ var init_serve = __esm(() => {
|
|
|
8630
8797
|
description: exports_external.string().optional(),
|
|
8631
8798
|
status: exports_external.enum(["active", "completed", "archived"]).optional()
|
|
8632
8799
|
});
|
|
8800
|
+
createApiKeySchema = exports_external.object({
|
|
8801
|
+
name: exports_external.string(),
|
|
8802
|
+
expires_at: exports_external.string().optional()
|
|
8803
|
+
});
|
|
8633
8804
|
agentSchema = exports_external.object({
|
|
8634
8805
|
agent_id: exports_external.string().optional()
|
|
8635
8806
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -178,6 +178,25 @@ export interface CreateSessionInput {
|
|
|
178
178
|
metadata?: Record<string, unknown>;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
// API Key
|
|
182
|
+
export interface ApiKey {
|
|
183
|
+
id: string;
|
|
184
|
+
name: string;
|
|
185
|
+
key_prefix: string;
|
|
186
|
+
created_at: string;
|
|
187
|
+
last_used_at: string | null;
|
|
188
|
+
expires_at: string | null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface ApiKeyWithSecret extends ApiKey {
|
|
192
|
+
key: string; // full key, only returned on creation
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface CreateApiKeyInput {
|
|
196
|
+
name: string;
|
|
197
|
+
expires_at?: string;
|
|
198
|
+
}
|
|
199
|
+
|
|
181
200
|
// DB row types (raw from SQLite - JSON fields are strings)
|
|
182
201
|
export interface TaskRow {
|
|
183
202
|
id: string;
|
package/dist/index.js
CHANGED
|
@@ -164,6 +164,19 @@ var MIGRATIONS = [
|
|
|
164
164
|
ALTER TABLE tasks ADD COLUMN plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL;
|
|
165
165
|
CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id);
|
|
166
166
|
INSERT OR IGNORE INTO _migrations (id) VALUES (4);
|
|
167
|
+
`,
|
|
168
|
+
`
|
|
169
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
170
|
+
id TEXT PRIMARY KEY,
|
|
171
|
+
name TEXT NOT NULL,
|
|
172
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
173
|
+
key_prefix TEXT NOT NULL,
|
|
174
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
175
|
+
last_used_at TEXT,
|
|
176
|
+
expires_at TEXT
|
|
177
|
+
);
|
|
178
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
|
|
179
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (5);
|
|
167
180
|
`
|
|
168
181
|
];
|
|
169
182
|
var _db = null;
|
|
@@ -858,6 +871,56 @@ function deleteSession(id, db) {
|
|
|
858
871
|
const result = d.run("DELETE FROM sessions WHERE id = ?", [id]);
|
|
859
872
|
return result.changes > 0;
|
|
860
873
|
}
|
|
874
|
+
// src/db/api-keys.ts
|
|
875
|
+
function generateApiKey() {
|
|
876
|
+
const bytes = new Uint8Array(32);
|
|
877
|
+
crypto.getRandomValues(bytes);
|
|
878
|
+
return "td_" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
879
|
+
}
|
|
880
|
+
async function hashKey(key) {
|
|
881
|
+
const encoder = new TextEncoder;
|
|
882
|
+
const data = encoder.encode(key);
|
|
883
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
884
|
+
return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
885
|
+
}
|
|
886
|
+
async function createApiKey(input, db) {
|
|
887
|
+
const d = db || getDatabase();
|
|
888
|
+
const id = uuid();
|
|
889
|
+
const timestamp = now();
|
|
890
|
+
const key = generateApiKey();
|
|
891
|
+
const keyHash = await hashKey(key);
|
|
892
|
+
const keyPrefix = key.slice(0, 10) + "...";
|
|
893
|
+
d.run(`INSERT INTO api_keys (id, name, key_hash, key_prefix, created_at, expires_at)
|
|
894
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [id, input.name, keyHash, keyPrefix, timestamp, input.expires_at || null]);
|
|
895
|
+
const row = d.query("SELECT id, name, key_prefix, created_at, last_used_at, expires_at FROM api_keys WHERE id = ?").get(id);
|
|
896
|
+
return { ...row, key };
|
|
897
|
+
}
|
|
898
|
+
function listApiKeys(db) {
|
|
899
|
+
const d = db || getDatabase();
|
|
900
|
+
return d.query("SELECT id, name, key_prefix, created_at, last_used_at, expires_at FROM api_keys ORDER BY created_at DESC").all();
|
|
901
|
+
}
|
|
902
|
+
function deleteApiKey(id, db) {
|
|
903
|
+
const d = db || getDatabase();
|
|
904
|
+
const result = d.run("DELETE FROM api_keys WHERE id = ?", [id]);
|
|
905
|
+
return result.changes > 0;
|
|
906
|
+
}
|
|
907
|
+
async function validateApiKey(key, db) {
|
|
908
|
+
const d = db || getDatabase();
|
|
909
|
+
const keyHash = await hashKey(key);
|
|
910
|
+
const row = d.query("SELECT id, name, key_prefix, created_at, last_used_at, expires_at FROM api_keys WHERE key_hash = ?").get(keyHash);
|
|
911
|
+
if (!row)
|
|
912
|
+
return null;
|
|
913
|
+
if (row.expires_at && new Date(row.expires_at) < new Date) {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
d.run("UPDATE api_keys SET last_used_at = ? WHERE id = ?", [now(), row.id]);
|
|
917
|
+
return row;
|
|
918
|
+
}
|
|
919
|
+
function hasAnyApiKeys(db) {
|
|
920
|
+
const d = db || getDatabase();
|
|
921
|
+
const row = d.query("SELECT COUNT(*) as count FROM api_keys").get();
|
|
922
|
+
return (row?.count ?? 0) > 0;
|
|
923
|
+
}
|
|
861
924
|
// src/lib/search.ts
|
|
862
925
|
function rowToTask2(row) {
|
|
863
926
|
return {
|
|
@@ -1468,6 +1531,7 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
1468
1531
|
return { pushed, pulled, errors };
|
|
1469
1532
|
}
|
|
1470
1533
|
export {
|
|
1534
|
+
validateApiKey,
|
|
1471
1535
|
updateTask,
|
|
1472
1536
|
updateSessionActivity,
|
|
1473
1537
|
updateProject,
|
|
@@ -1488,6 +1552,8 @@ export {
|
|
|
1488
1552
|
listProjects,
|
|
1489
1553
|
listPlans,
|
|
1490
1554
|
listComments,
|
|
1555
|
+
listApiKeys,
|
|
1556
|
+
hasAnyApiKeys,
|
|
1491
1557
|
getTaskWithRelations,
|
|
1492
1558
|
getTaskDependents,
|
|
1493
1559
|
getTaskDependencies,
|
|
@@ -1504,11 +1570,13 @@ export {
|
|
|
1504
1570
|
deleteProject,
|
|
1505
1571
|
deletePlan,
|
|
1506
1572
|
deleteComment,
|
|
1573
|
+
deleteApiKey,
|
|
1507
1574
|
defaultSyncAgents,
|
|
1508
1575
|
createTask,
|
|
1509
1576
|
createSession,
|
|
1510
1577
|
createProject,
|
|
1511
1578
|
createPlan,
|
|
1579
|
+
createApiKey,
|
|
1512
1580
|
completeTask,
|
|
1513
1581
|
closeDatabase,
|
|
1514
1582
|
addDependency,
|
package/dist/mcp/index.js
CHANGED
|
@@ -4206,6 +4206,19 @@ var MIGRATIONS = [
|
|
|
4206
4206
|
ALTER TABLE tasks ADD COLUMN plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL;
|
|
4207
4207
|
CREATE INDEX IF NOT EXISTS idx_tasks_plan ON tasks(plan_id);
|
|
4208
4208
|
INSERT OR IGNORE INTO _migrations (id) VALUES (4);
|
|
4209
|
+
`,
|
|
4210
|
+
`
|
|
4211
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
4212
|
+
id TEXT PRIMARY KEY,
|
|
4213
|
+
name TEXT NOT NULL,
|
|
4214
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
4215
|
+
key_prefix TEXT NOT NULL,
|
|
4216
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
4217
|
+
last_used_at TEXT,
|
|
4218
|
+
expires_at TEXT
|
|
4219
|
+
);
|
|
4220
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
|
|
4221
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (5);
|
|
4209
4222
|
`
|
|
4210
4223
|
];
|
|
4211
4224
|
var _db = null;
|