@hasna/instructions 0.6.1 → 0.7.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/README.md +141 -10
- package/dist/chunks/{apply-ynfn1av7.js → apply-c3sd4agz.js} +5 -3
- package/dist/chunks/{apply-sfzja1f3.js → apply-pnn7a3y7.js} +6 -3
- package/dist/chunks/database-wstmr56z.js +23 -0
- package/dist/chunks/{index-pkscjnj5.js → index-3djthcm6.js} +0 -1
- package/dist/chunks/index-5d7zdftc.js +186 -0
- package/dist/chunks/{index-1hpwskwd.js → index-6jncnnw7.js} +318 -329
- package/dist/chunks/index-8rb7ng2y.js +148 -0
- package/dist/chunks/{index-v7dkb384.js → index-aj66f6xr.js} +243 -100
- package/dist/chunks/index-fpeyzn3n.js +186 -0
- package/dist/chunks/{index-9p9gn7gh.js → index-jg8ptcwh.js} +5 -7
- package/dist/chunks/index-mvckkf15.js +363 -0
- package/dist/chunks/index-n3fjspr0.js +119 -0
- package/dist/chunks/{index-85n5wnd8.js → index-pdpg8a44.js} +5 -7
- package/dist/chunks/{index-e9epbsvs.js → index-r7nfef2g.js} +15 -4
- package/dist/chunks/{index-axhwg61p.js → index-ypcef3a2.js} +15 -4
- package/dist/chunks/legacy-store-migration-z2m5yzyk.js +322 -0
- package/dist/chunks/{local-a9nen65z.js → local-0a9gja58.js} +142 -2
- package/dist/chunks/{local-0d8qrc92.js → local-969ye538.js} +153 -188
- package/dist/chunks/s3-backup-hxe3zpfz.js +240 -0
- package/dist/chunks/s3-config-hhght91k.js +14 -0
- package/dist/chunks/s3-object-store-n0s82swt.js +14 -0
- package/dist/chunks/{sync-vy1yjvs6.js → sync-q5hvwq6e.js} +4 -4
- package/dist/chunks/{sync-m46fc257.js → sync-xtkbm5br.js} +5 -4
- package/dist/chunks/{template-02wjvv05.js → template-fsfxpe4p.js} +1 -1
- package/dist/chunks/{template-ke972qqe.js → template-wgnzdn7h.js} +1 -1
- package/dist/cli/index.js +1236 -256
- package/dist/data/config-store.d.ts +10 -1
- package/dist/data/config-store.d.ts.map +1 -1
- package/dist/db/configs.d.ts +4 -1
- package/dist/db/configs.d.ts.map +1 -1
- package/dist/db/local.d.ts +1 -1
- package/dist/db/local.d.ts.map +1 -1
- package/dist/index.js +1086 -215
- package/dist/lib/apply.d.ts +7 -0
- package/dist/lib/apply.d.ts.map +1 -1
- package/dist/lib/compact-output.d.ts +3 -18
- package/dist/lib/compact-output.d.ts.map +1 -1
- package/dist/lib/export.d.ts +22 -3
- package/dist/lib/export.d.ts.map +1 -1
- package/dist/lib/import.d.ts +18 -0
- package/dist/lib/import.d.ts.map +1 -1
- package/dist/lib/legacy-store-migration.d.ts +39 -0
- package/dist/lib/legacy-store-migration.d.ts.map +1 -0
- package/dist/lib/managed-skill-runtimes.d.ts +1 -0
- package/dist/lib/managed-skill-runtimes.d.ts.map +1 -1
- package/dist/lib/sync-dir.d.ts.map +1 -1
- package/dist/mcp/index.js +41 -30
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +229 -10
- package/dist/sdk/resolve.d.ts +1 -1
- package/dist/sdk/resolve.d.ts.map +1 -1
- package/dist/sdk/v1-client.d.ts +58 -0
- package/dist/sdk/v1-client.d.ts.map +1 -0
- package/dist/sdk/v1.generated.d.ts +242 -21
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.d.ts +13 -5
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1261 -427
- package/dist/server/openapi.d.ts +1379 -93
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts +25 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/cloud-store.d.ts +45 -2
- package/dist/storage/cloud-store.d.ts.map +1 -1
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +744 -0
- package/dist/storage/s3-backup.d.ts +76 -0
- package/dist/storage/s3-backup.d.ts.map +1 -0
- package/dist/storage/s3-config.d.ts +46 -0
- package/dist/storage/s3-config.d.ts.map +1 -0
- package/dist/storage/s3-object-store.d.ts +70 -0
- package/dist/storage/s3-object-store.d.ts.map +1 -0
- package/dist/types/index.d.ts +98 -3
- package/dist/types/index.d.ts.map +1 -1
- package/hasna.contract.json +110 -0
- package/migrations/0001_instructions.sql +63 -0
- package/migrations/0002_api_keys.sql +20 -0
- package/migrations/0003_idempotency_receipts.sql +24 -0
- package/package.json +24 -13
- package/assets/skills/inbox/SKILL.md +0 -86
|
@@ -14,12 +14,12 @@ import {
|
|
|
14
14
|
normalizeOsFamily,
|
|
15
15
|
normalizeProfileAssetBinding,
|
|
16
16
|
normalizeProfileConfigBinding
|
|
17
|
-
} from "./index-
|
|
17
|
+
} from "./index-aj66f6xr.js";
|
|
18
18
|
import"./index-35z65jsa.js";
|
|
19
19
|
import {
|
|
20
20
|
ConfigNotFoundError,
|
|
21
21
|
ProfileNotFoundError
|
|
22
|
-
} from "./index-
|
|
22
|
+
} from "./index-3djthcm6.js";
|
|
23
23
|
import"./index-nnwp92tk.js";
|
|
24
24
|
|
|
25
25
|
// src/db/database.ts
|
|
@@ -339,6 +339,143 @@ function listConfigs(filter, db) {
|
|
|
339
339
|
const rows = d.query(`SELECT * FROM configs ${where} ORDER BY category, name`).all(...params);
|
|
340
340
|
return rows.map(rowToConfig);
|
|
341
341
|
}
|
|
342
|
+
function listConfigsPage(filter = {}, options = {}, db) {
|
|
343
|
+
const d = db || getDatabase();
|
|
344
|
+
const conditions = [];
|
|
345
|
+
const params = [];
|
|
346
|
+
if (filter.category) {
|
|
347
|
+
conditions.push("category = ?");
|
|
348
|
+
params.push(filter.category);
|
|
349
|
+
}
|
|
350
|
+
if (filter.agent) {
|
|
351
|
+
conditions.push("agent = ?");
|
|
352
|
+
params.push(filter.agent);
|
|
353
|
+
}
|
|
354
|
+
if (filter.kind) {
|
|
355
|
+
conditions.push("kind = ?");
|
|
356
|
+
params.push(filter.kind);
|
|
357
|
+
}
|
|
358
|
+
if (filter.is_template !== undefined) {
|
|
359
|
+
conditions.push("is_template = ?");
|
|
360
|
+
params.push(filter.is_template ? 1 : 0);
|
|
361
|
+
}
|
|
362
|
+
if (filter.search) {
|
|
363
|
+
conditions.push("(name LIKE ? OR description LIKE ? OR content LIKE ?)");
|
|
364
|
+
const q = `%${filter.search}%`;
|
|
365
|
+
params.push(q, q, q);
|
|
366
|
+
}
|
|
367
|
+
if (filter.tags?.length) {
|
|
368
|
+
conditions.push(`(${filter.tags.map(() => "tags LIKE ?").join(" AND ")})`);
|
|
369
|
+
for (const tag of filter.tags)
|
|
370
|
+
params.push(`%"${tag}"%`);
|
|
371
|
+
}
|
|
372
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
373
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
374
|
+
const total = d.query(`SELECT COUNT(*) AS total FROM configs ${where}`).get(...params)?.total ?? 0;
|
|
375
|
+
const pageParams = [...params, normalized.limit, normalized.cursor];
|
|
376
|
+
const rows = d.query(`SELECT * FROM configs ${where} ORDER BY id LIMIT ? OFFSET ?`).all(...pageParams);
|
|
377
|
+
return boundedReadPage(rows.map(rowToConfig), total, normalized);
|
|
378
|
+
}
|
|
379
|
+
function listConfigIdentitiesPage(filter = {}, options = {}, db) {
|
|
380
|
+
const d = db || getDatabase();
|
|
381
|
+
const conditions = [];
|
|
382
|
+
const params = [];
|
|
383
|
+
if (filter.category) {
|
|
384
|
+
conditions.push("category = ?");
|
|
385
|
+
params.push(filter.category);
|
|
386
|
+
}
|
|
387
|
+
if (filter.agent) {
|
|
388
|
+
conditions.push("agent = ?");
|
|
389
|
+
params.push(filter.agent);
|
|
390
|
+
}
|
|
391
|
+
if (filter.kind) {
|
|
392
|
+
conditions.push("kind = ?");
|
|
393
|
+
params.push(filter.kind);
|
|
394
|
+
}
|
|
395
|
+
if (filter.is_template !== undefined) {
|
|
396
|
+
conditions.push("is_template = ?");
|
|
397
|
+
params.push(filter.is_template ? 1 : 0);
|
|
398
|
+
}
|
|
399
|
+
if (filter.search) {
|
|
400
|
+
conditions.push("(name LIKE ? OR description LIKE ? OR content LIKE ?)");
|
|
401
|
+
const q = `%${filter.search}%`;
|
|
402
|
+
params.push(q, q, q);
|
|
403
|
+
}
|
|
404
|
+
if (filter.tags?.length) {
|
|
405
|
+
conditions.push(`(${filter.tags.map(() => "tags LIKE ?").join(" AND ")})`);
|
|
406
|
+
for (const tag of filter.tags)
|
|
407
|
+
params.push(`%"${tag}"%`);
|
|
408
|
+
}
|
|
409
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
410
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
411
|
+
const total = d.query(`SELECT COUNT(*) AS total FROM configs ${where}`).get(...params)?.total ?? 0;
|
|
412
|
+
const pageParams = [...params, normalized.limit, normalized.cursor];
|
|
413
|
+
const rows = d.query(`SELECT id, name, slug, kind, category, agent, format, is_template, version, created_at, updated_at, synced_at
|
|
414
|
+
FROM configs ${where} ORDER BY id LIMIT ? OFFSET ?`).all(...pageParams);
|
|
415
|
+
return boundedReadPage(rows.map((row) => ({
|
|
416
|
+
...row,
|
|
417
|
+
kind: row.kind,
|
|
418
|
+
category: row.category,
|
|
419
|
+
agent: row.agent,
|
|
420
|
+
format: row.format,
|
|
421
|
+
is_template: Boolean(row.is_template)
|
|
422
|
+
})), total, normalized);
|
|
423
|
+
}
|
|
424
|
+
function listConfigSummariesPage(filter = {}, options = {}, db) {
|
|
425
|
+
const d = db || getDatabase();
|
|
426
|
+
const conditions = [];
|
|
427
|
+
const params = [];
|
|
428
|
+
if (filter.category) {
|
|
429
|
+
conditions.push("category = ?");
|
|
430
|
+
params.push(filter.category);
|
|
431
|
+
}
|
|
432
|
+
if (filter.agent) {
|
|
433
|
+
conditions.push("agent = ?");
|
|
434
|
+
params.push(filter.agent);
|
|
435
|
+
}
|
|
436
|
+
if (filter.kind) {
|
|
437
|
+
conditions.push("kind = ?");
|
|
438
|
+
params.push(filter.kind);
|
|
439
|
+
}
|
|
440
|
+
if (filter.is_template !== undefined) {
|
|
441
|
+
conditions.push("is_template = ?");
|
|
442
|
+
params.push(filter.is_template ? 1 : 0);
|
|
443
|
+
}
|
|
444
|
+
if (filter.search) {
|
|
445
|
+
conditions.push("(name LIKE ? OR description LIKE ? OR content LIKE ?)");
|
|
446
|
+
const q = `%${filter.search}%`;
|
|
447
|
+
params.push(q, q, q);
|
|
448
|
+
}
|
|
449
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
450
|
+
conditions.push(`(${filter.tags.map(() => "tags LIKE ?").join(" AND ")})`);
|
|
451
|
+
for (const tag of filter.tags)
|
|
452
|
+
params.push(`%"${tag}"%`);
|
|
453
|
+
}
|
|
454
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
455
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
456
|
+
const total = d.query(`SELECT COUNT(*) AS total FROM configs ${where}`).get(...params)?.total ?? 0;
|
|
457
|
+
const pageParams = [...params, normalized.limit, normalized.cursor];
|
|
458
|
+
const rows = d.query(`SELECT id, name, slug, kind, category, agent, target_path, format,
|
|
459
|
+
CASE WHEN json_valid(outputs) AND json_type(outputs) = 'array' THEN json_array_length(outputs) ELSE 0 END AS output_count, description, tags,
|
|
460
|
+
is_template, version, updated_at
|
|
461
|
+
FROM configs ${where} ORDER BY category, name, id LIMIT ? OFFSET ?`).all(...pageParams);
|
|
462
|
+
return boundedReadPage(rows.map((row) => ({
|
|
463
|
+
id: row.id,
|
|
464
|
+
slug: row.slug,
|
|
465
|
+
name: row.name,
|
|
466
|
+
category: row.category,
|
|
467
|
+
agent: row.agent,
|
|
468
|
+
kind: row.kind,
|
|
469
|
+
format: row.format,
|
|
470
|
+
target_path: row.target_path,
|
|
471
|
+
output_count: Number(row.output_count ?? 0),
|
|
472
|
+
version: row.version,
|
|
473
|
+
is_template: Boolean(row.is_template),
|
|
474
|
+
updated_at: row.updated_at,
|
|
475
|
+
description: row.description,
|
|
476
|
+
tags: JSON.parse(row.tags || "[]")
|
|
477
|
+
})), total, normalized);
|
|
478
|
+
}
|
|
342
479
|
function updateConfig(idOrSlug, input, db) {
|
|
343
480
|
const d = db || getDatabase();
|
|
344
481
|
const existing = getConfig(idOrSlug, d);
|
|
@@ -673,7 +810,10 @@ export {
|
|
|
673
810
|
listSnapshots,
|
|
674
811
|
listProfilesPage,
|
|
675
812
|
listMachines,
|
|
813
|
+
listConfigsPage,
|
|
676
814
|
listConfigs,
|
|
815
|
+
listConfigSummariesPage,
|
|
816
|
+
listConfigIdentitiesPage,
|
|
677
817
|
insertFeedback,
|
|
678
818
|
getSnapshotByVersion,
|
|
679
819
|
getSnapshot,
|
|
@@ -1,205 +1,30 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import {
|
|
3
|
-
now,
|
|
4
|
-
slugify,
|
|
5
|
-
uuid
|
|
6
|
-
} from "./index-ma2xmp6c.js";
|
|
7
2
|
import {
|
|
8
3
|
boundedReadPage,
|
|
9
4
|
detectMachineContext,
|
|
10
|
-
getRawStoreRoot,
|
|
11
|
-
hasInstructionsEnvAuthorityIntent,
|
|
12
5
|
legacyProfileConfigBinding,
|
|
13
6
|
normalizeBoundedReadOptions,
|
|
14
7
|
normalizeOsFamily,
|
|
15
8
|
normalizeProfileAssetBinding,
|
|
16
9
|
normalizeProfileConfigBinding
|
|
17
|
-
} from "./index-
|
|
10
|
+
} from "./index-6jncnnw7.js";
|
|
18
11
|
import {
|
|
19
12
|
ConfigNotFoundError,
|
|
20
13
|
ProfileNotFoundError
|
|
21
|
-
} from "./index-
|
|
14
|
+
} from "./index-n3fjspr0.js";
|
|
15
|
+
import {
|
|
16
|
+
getDatabase,
|
|
17
|
+
insertFeedback,
|
|
18
|
+
resetLocalDatabase
|
|
19
|
+
} from "./index-5d7zdftc.js";
|
|
20
|
+
import {
|
|
21
|
+
now,
|
|
22
|
+
slugify,
|
|
23
|
+
uuid
|
|
24
|
+
} from "./index-ma2xmp6c.js";
|
|
25
|
+
import"./index-fpeyzn3n.js";
|
|
22
26
|
import"./index-bsan4c16.js";
|
|
23
27
|
|
|
24
|
-
// src/db/database.ts
|
|
25
|
-
import { Database } from "bun:sqlite";
|
|
26
|
-
import { existsSync, mkdirSync, rmSync } from "fs";
|
|
27
|
-
import { join } from "path";
|
|
28
|
-
function getDbPath() {
|
|
29
|
-
if (process.env["HASNA_INSTRUCTIONS_DB_PATH"]) {
|
|
30
|
-
return process.env["HASNA_INSTRUCTIONS_DB_PATH"];
|
|
31
|
-
}
|
|
32
|
-
const dir = getRawStoreRoot();
|
|
33
|
-
mkdirSync(dir, { recursive: true });
|
|
34
|
-
return join(dir, "instructions.db");
|
|
35
|
-
}
|
|
36
|
-
var MIGRATIONS = [
|
|
37
|
-
`
|
|
38
|
-
CREATE TABLE IF NOT EXISTS configs (
|
|
39
|
-
id TEXT PRIMARY KEY,
|
|
40
|
-
name TEXT NOT NULL,
|
|
41
|
-
slug TEXT NOT NULL UNIQUE,
|
|
42
|
-
kind TEXT NOT NULL DEFAULT 'file',
|
|
43
|
-
category TEXT NOT NULL,
|
|
44
|
-
agent TEXT NOT NULL DEFAULT 'global',
|
|
45
|
-
target_path TEXT,
|
|
46
|
-
format TEXT NOT NULL DEFAULT 'text',
|
|
47
|
-
content TEXT NOT NULL DEFAULT '',
|
|
48
|
-
description TEXT,
|
|
49
|
-
tags TEXT NOT NULL DEFAULT '[]',
|
|
50
|
-
is_template INTEGER NOT NULL DEFAULT 0,
|
|
51
|
-
version INTEGER NOT NULL DEFAULT 1,
|
|
52
|
-
created_at TEXT NOT NULL,
|
|
53
|
-
updated_at TEXT NOT NULL,
|
|
54
|
-
synced_at TEXT
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
CREATE TABLE IF NOT EXISTS config_snapshots (
|
|
58
|
-
id TEXT PRIMARY KEY,
|
|
59
|
-
config_id TEXT NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
|
|
60
|
-
content TEXT NOT NULL,
|
|
61
|
-
version INTEGER NOT NULL,
|
|
62
|
-
created_at TEXT NOT NULL
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
CREATE TABLE IF NOT EXISTS profiles (
|
|
66
|
-
id TEXT PRIMARY KEY,
|
|
67
|
-
name TEXT NOT NULL,
|
|
68
|
-
slug TEXT NOT NULL UNIQUE,
|
|
69
|
-
description TEXT,
|
|
70
|
-
created_at TEXT NOT NULL,
|
|
71
|
-
updated_at TEXT NOT NULL
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
CREATE TABLE IF NOT EXISTS profile_configs (
|
|
75
|
-
profile_id TEXT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
|
|
76
|
-
config_id TEXT NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
|
|
77
|
-
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
78
|
-
PRIMARY KEY (profile_id, config_id)
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
CREATE TABLE IF NOT EXISTS machines (
|
|
82
|
-
id TEXT PRIMARY KEY,
|
|
83
|
-
hostname TEXT NOT NULL UNIQUE,
|
|
84
|
-
os TEXT,
|
|
85
|
-
last_applied_at TEXT,
|
|
86
|
-
created_at TEXT NOT NULL
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
CREATE TABLE IF NOT EXISTS schema_version (
|
|
90
|
-
version INTEGER PRIMARY KEY
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
INSERT OR IGNORE INTO schema_version (version) VALUES (1);
|
|
94
|
-
`,
|
|
95
|
-
`
|
|
96
|
-
ALTER TABLE profiles ADD COLUMN selectors TEXT NOT NULL DEFAULT '{}';
|
|
97
|
-
ALTER TABLE profiles ADD COLUMN variables TEXT NOT NULL DEFAULT '{}';
|
|
98
|
-
ALTER TABLE machines ADD COLUMN arch TEXT;
|
|
99
|
-
`,
|
|
100
|
-
`
|
|
101
|
-
ALTER TABLE configs ADD COLUMN outputs TEXT NOT NULL DEFAULT '[]';
|
|
102
|
-
`,
|
|
103
|
-
`
|
|
104
|
-
ALTER TABLE profile_configs ADD COLUMN binding TEXT NOT NULL DEFAULT '{"schema":"hasna.instructions.profile-config-binding/v1","activation":{"mode":"always"},"required":true,"fallback":"fail"}';
|
|
105
|
-
`,
|
|
106
|
-
`
|
|
107
|
-
CREATE TABLE IF NOT EXISTS profile_assets (
|
|
108
|
-
profile_id TEXT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
|
|
109
|
-
source_config_id TEXT NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
|
|
110
|
-
asset_key TEXT NOT NULL,
|
|
111
|
-
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
112
|
-
binding TEXT NOT NULL,
|
|
113
|
-
PRIMARY KEY (profile_id, asset_key)
|
|
114
|
-
);
|
|
115
|
-
CREATE INDEX IF NOT EXISTS profile_assets_source_config_idx ON profile_assets (source_config_id);
|
|
116
|
-
`
|
|
117
|
-
];
|
|
118
|
-
var _db = null;
|
|
119
|
-
function getDatabase(path) {
|
|
120
|
-
if (_db)
|
|
121
|
-
return _db;
|
|
122
|
-
if (!path && hasInstructionsEnvAuthorityIntent(process.env)) {
|
|
123
|
-
throw new Error("instructions: refusing to open the on-box SQLite store \u2014 the environment configures a hosted " + "Instructions authority or credential (HASNA_INSTRUCTIONS_*), and reading or writing the local store " + "here would silently drift from the shared dataset. Pass an explicit database path (or an injected " + "Database) to work against the on-box store deliberately.");
|
|
124
|
-
}
|
|
125
|
-
const dbPath = path || getDbPath();
|
|
126
|
-
const db = new Database(dbPath);
|
|
127
|
-
db.run("PRAGMA journal_mode = WAL");
|
|
128
|
-
db.run("PRAGMA foreign_keys = ON");
|
|
129
|
-
applyMigrations(db);
|
|
130
|
-
ensureFeedbackTable(db);
|
|
131
|
-
_db = db;
|
|
132
|
-
return db;
|
|
133
|
-
}
|
|
134
|
-
function resetDatabase() {
|
|
135
|
-
if (_db) {
|
|
136
|
-
try {
|
|
137
|
-
_db.close();
|
|
138
|
-
} catch {}
|
|
139
|
-
}
|
|
140
|
-
_db = null;
|
|
141
|
-
}
|
|
142
|
-
function resetLocalDatabase() {
|
|
143
|
-
resetDatabase();
|
|
144
|
-
const dbPath = getDbPath();
|
|
145
|
-
if (dbPath === ":memory:")
|
|
146
|
-
return;
|
|
147
|
-
for (const p of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
148
|
-
if (existsSync(p))
|
|
149
|
-
rmSync(p);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
function applyMigrations(db) {
|
|
153
|
-
let currentVersion = 0;
|
|
154
|
-
try {
|
|
155
|
-
const row = db.query("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get();
|
|
156
|
-
currentVersion = row?.version ?? 0;
|
|
157
|
-
} catch {
|
|
158
|
-
currentVersion = 0;
|
|
159
|
-
}
|
|
160
|
-
const applyOne = db.transaction((index) => {
|
|
161
|
-
if (!migrationEffectAlreadyPresent(db, index))
|
|
162
|
-
db.exec(MIGRATIONS[index]);
|
|
163
|
-
db.run(`INSERT OR REPLACE INTO schema_version (version) VALUES (${index + 1})`);
|
|
164
|
-
});
|
|
165
|
-
for (let i = currentVersion;i < MIGRATIONS.length; i++)
|
|
166
|
-
applyOne(i);
|
|
167
|
-
}
|
|
168
|
-
function migrationEffectAlreadyPresent(db, index) {
|
|
169
|
-
if (index !== 3)
|
|
170
|
-
return false;
|
|
171
|
-
return db.query("PRAGMA table_info(profile_configs)").all().some((column) => column.name === "binding");
|
|
172
|
-
}
|
|
173
|
-
function ensureFeedbackTable(db) {
|
|
174
|
-
db.exec(`
|
|
175
|
-
CREATE TABLE IF NOT EXISTS feedback (
|
|
176
|
-
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
177
|
-
message TEXT NOT NULL,
|
|
178
|
-
email TEXT,
|
|
179
|
-
category TEXT DEFAULT 'general',
|
|
180
|
-
version TEXT,
|
|
181
|
-
machine_id TEXT,
|
|
182
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
183
|
-
)
|
|
184
|
-
`);
|
|
185
|
-
const existing = new Set(db.query("PRAGMA table_info(feedback)").all().map((r) => r.name));
|
|
186
|
-
const required = [
|
|
187
|
-
["email", "TEXT"],
|
|
188
|
-
["category", "TEXT DEFAULT 'general'"],
|
|
189
|
-
["version", "TEXT"],
|
|
190
|
-
["machine_id", "TEXT"],
|
|
191
|
-
["created_at", "TEXT"]
|
|
192
|
-
];
|
|
193
|
-
for (const [name, def] of required) {
|
|
194
|
-
if (!existing.has(name))
|
|
195
|
-
db.exec(`ALTER TABLE feedback ADD COLUMN ${name} ${def}`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
function insertFeedback(input, db) {
|
|
199
|
-
const d = db || getDatabase();
|
|
200
|
-
d.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [input.message, input.email ?? null, input.category ?? "general", input.version ?? null]);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
28
|
// src/db/snapshots.ts
|
|
204
29
|
function createSnapshot(configId, content, version, db) {
|
|
205
30
|
const d = db || getDatabase();
|
|
@@ -338,6 +163,143 @@ function listConfigs(filter, db) {
|
|
|
338
163
|
const rows = d.query(`SELECT * FROM configs ${where} ORDER BY category, name`).all(...params);
|
|
339
164
|
return rows.map(rowToConfig);
|
|
340
165
|
}
|
|
166
|
+
function listConfigsPage(filter = {}, options = {}, db) {
|
|
167
|
+
const d = db || getDatabase();
|
|
168
|
+
const conditions = [];
|
|
169
|
+
const params = [];
|
|
170
|
+
if (filter.category) {
|
|
171
|
+
conditions.push("category = ?");
|
|
172
|
+
params.push(filter.category);
|
|
173
|
+
}
|
|
174
|
+
if (filter.agent) {
|
|
175
|
+
conditions.push("agent = ?");
|
|
176
|
+
params.push(filter.agent);
|
|
177
|
+
}
|
|
178
|
+
if (filter.kind) {
|
|
179
|
+
conditions.push("kind = ?");
|
|
180
|
+
params.push(filter.kind);
|
|
181
|
+
}
|
|
182
|
+
if (filter.is_template !== undefined) {
|
|
183
|
+
conditions.push("is_template = ?");
|
|
184
|
+
params.push(filter.is_template ? 1 : 0);
|
|
185
|
+
}
|
|
186
|
+
if (filter.search) {
|
|
187
|
+
conditions.push("(name LIKE ? OR description LIKE ? OR content LIKE ?)");
|
|
188
|
+
const q = `%${filter.search}%`;
|
|
189
|
+
params.push(q, q, q);
|
|
190
|
+
}
|
|
191
|
+
if (filter.tags?.length) {
|
|
192
|
+
conditions.push(`(${filter.tags.map(() => "tags LIKE ?").join(" AND ")})`);
|
|
193
|
+
for (const tag of filter.tags)
|
|
194
|
+
params.push(`%"${tag}"%`);
|
|
195
|
+
}
|
|
196
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
197
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
198
|
+
const total = d.query(`SELECT COUNT(*) AS total FROM configs ${where}`).get(...params)?.total ?? 0;
|
|
199
|
+
const pageParams = [...params, normalized.limit, normalized.cursor];
|
|
200
|
+
const rows = d.query(`SELECT * FROM configs ${where} ORDER BY id LIMIT ? OFFSET ?`).all(...pageParams);
|
|
201
|
+
return boundedReadPage(rows.map(rowToConfig), total, normalized);
|
|
202
|
+
}
|
|
203
|
+
function listConfigIdentitiesPage(filter = {}, options = {}, db) {
|
|
204
|
+
const d = db || getDatabase();
|
|
205
|
+
const conditions = [];
|
|
206
|
+
const params = [];
|
|
207
|
+
if (filter.category) {
|
|
208
|
+
conditions.push("category = ?");
|
|
209
|
+
params.push(filter.category);
|
|
210
|
+
}
|
|
211
|
+
if (filter.agent) {
|
|
212
|
+
conditions.push("agent = ?");
|
|
213
|
+
params.push(filter.agent);
|
|
214
|
+
}
|
|
215
|
+
if (filter.kind) {
|
|
216
|
+
conditions.push("kind = ?");
|
|
217
|
+
params.push(filter.kind);
|
|
218
|
+
}
|
|
219
|
+
if (filter.is_template !== undefined) {
|
|
220
|
+
conditions.push("is_template = ?");
|
|
221
|
+
params.push(filter.is_template ? 1 : 0);
|
|
222
|
+
}
|
|
223
|
+
if (filter.search) {
|
|
224
|
+
conditions.push("(name LIKE ? OR description LIKE ? OR content LIKE ?)");
|
|
225
|
+
const q = `%${filter.search}%`;
|
|
226
|
+
params.push(q, q, q);
|
|
227
|
+
}
|
|
228
|
+
if (filter.tags?.length) {
|
|
229
|
+
conditions.push(`(${filter.tags.map(() => "tags LIKE ?").join(" AND ")})`);
|
|
230
|
+
for (const tag of filter.tags)
|
|
231
|
+
params.push(`%"${tag}"%`);
|
|
232
|
+
}
|
|
233
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
234
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
235
|
+
const total = d.query(`SELECT COUNT(*) AS total FROM configs ${where}`).get(...params)?.total ?? 0;
|
|
236
|
+
const pageParams = [...params, normalized.limit, normalized.cursor];
|
|
237
|
+
const rows = d.query(`SELECT id, name, slug, kind, category, agent, format, is_template, version, created_at, updated_at, synced_at
|
|
238
|
+
FROM configs ${where} ORDER BY id LIMIT ? OFFSET ?`).all(...pageParams);
|
|
239
|
+
return boundedReadPage(rows.map((row) => ({
|
|
240
|
+
...row,
|
|
241
|
+
kind: row.kind,
|
|
242
|
+
category: row.category,
|
|
243
|
+
agent: row.agent,
|
|
244
|
+
format: row.format,
|
|
245
|
+
is_template: Boolean(row.is_template)
|
|
246
|
+
})), total, normalized);
|
|
247
|
+
}
|
|
248
|
+
function listConfigSummariesPage(filter = {}, options = {}, db) {
|
|
249
|
+
const d = db || getDatabase();
|
|
250
|
+
const conditions = [];
|
|
251
|
+
const params = [];
|
|
252
|
+
if (filter.category) {
|
|
253
|
+
conditions.push("category = ?");
|
|
254
|
+
params.push(filter.category);
|
|
255
|
+
}
|
|
256
|
+
if (filter.agent) {
|
|
257
|
+
conditions.push("agent = ?");
|
|
258
|
+
params.push(filter.agent);
|
|
259
|
+
}
|
|
260
|
+
if (filter.kind) {
|
|
261
|
+
conditions.push("kind = ?");
|
|
262
|
+
params.push(filter.kind);
|
|
263
|
+
}
|
|
264
|
+
if (filter.is_template !== undefined) {
|
|
265
|
+
conditions.push("is_template = ?");
|
|
266
|
+
params.push(filter.is_template ? 1 : 0);
|
|
267
|
+
}
|
|
268
|
+
if (filter.search) {
|
|
269
|
+
conditions.push("(name LIKE ? OR description LIKE ? OR content LIKE ?)");
|
|
270
|
+
const q = `%${filter.search}%`;
|
|
271
|
+
params.push(q, q, q);
|
|
272
|
+
}
|
|
273
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
274
|
+
conditions.push(`(${filter.tags.map(() => "tags LIKE ?").join(" AND ")})`);
|
|
275
|
+
for (const tag of filter.tags)
|
|
276
|
+
params.push(`%"${tag}"%`);
|
|
277
|
+
}
|
|
278
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
279
|
+
const normalized = normalizeBoundedReadOptions(options);
|
|
280
|
+
const total = d.query(`SELECT COUNT(*) AS total FROM configs ${where}`).get(...params)?.total ?? 0;
|
|
281
|
+
const pageParams = [...params, normalized.limit, normalized.cursor];
|
|
282
|
+
const rows = d.query(`SELECT id, name, slug, kind, category, agent, target_path, format,
|
|
283
|
+
CASE WHEN json_valid(outputs) AND json_type(outputs) = 'array' THEN json_array_length(outputs) ELSE 0 END AS output_count, description, tags,
|
|
284
|
+
is_template, version, updated_at
|
|
285
|
+
FROM configs ${where} ORDER BY category, name, id LIMIT ? OFFSET ?`).all(...pageParams);
|
|
286
|
+
return boundedReadPage(rows.map((row) => ({
|
|
287
|
+
id: row.id,
|
|
288
|
+
slug: row.slug,
|
|
289
|
+
name: row.name,
|
|
290
|
+
category: row.category,
|
|
291
|
+
agent: row.agent,
|
|
292
|
+
kind: row.kind,
|
|
293
|
+
format: row.format,
|
|
294
|
+
target_path: row.target_path,
|
|
295
|
+
output_count: Number(row.output_count ?? 0),
|
|
296
|
+
version: row.version,
|
|
297
|
+
is_template: Boolean(row.is_template),
|
|
298
|
+
updated_at: row.updated_at,
|
|
299
|
+
description: row.description,
|
|
300
|
+
tags: JSON.parse(row.tags || "[]")
|
|
301
|
+
})), total, normalized);
|
|
302
|
+
}
|
|
341
303
|
function updateConfig(idOrSlug, input, db) {
|
|
342
304
|
const d = db || getDatabase();
|
|
343
305
|
const existing = getConfig(idOrSlug, d);
|
|
@@ -672,7 +634,10 @@ export {
|
|
|
672
634
|
listSnapshots,
|
|
673
635
|
listProfilesPage,
|
|
674
636
|
listMachines,
|
|
637
|
+
listConfigsPage,
|
|
675
638
|
listConfigs,
|
|
639
|
+
listConfigSummariesPage,
|
|
640
|
+
listConfigIdentitiesPage,
|
|
676
641
|
insertFeedback,
|
|
677
642
|
getSnapshotByVersion,
|
|
678
643
|
getSnapshot,
|