@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.
Files changed (85) hide show
  1. package/README.md +141 -10
  2. package/dist/chunks/{apply-ynfn1av7.js → apply-c3sd4agz.js} +5 -3
  3. package/dist/chunks/{apply-sfzja1f3.js → apply-pnn7a3y7.js} +6 -3
  4. package/dist/chunks/database-wstmr56z.js +23 -0
  5. package/dist/chunks/{index-pkscjnj5.js → index-3djthcm6.js} +0 -1
  6. package/dist/chunks/index-5d7zdftc.js +186 -0
  7. package/dist/chunks/{index-1hpwskwd.js → index-6jncnnw7.js} +318 -329
  8. package/dist/chunks/index-8rb7ng2y.js +148 -0
  9. package/dist/chunks/{index-v7dkb384.js → index-aj66f6xr.js} +243 -100
  10. package/dist/chunks/index-fpeyzn3n.js +186 -0
  11. package/dist/chunks/{index-9p9gn7gh.js → index-jg8ptcwh.js} +5 -7
  12. package/dist/chunks/index-mvckkf15.js +363 -0
  13. package/dist/chunks/index-n3fjspr0.js +119 -0
  14. package/dist/chunks/{index-85n5wnd8.js → index-pdpg8a44.js} +5 -7
  15. package/dist/chunks/{index-e9epbsvs.js → index-r7nfef2g.js} +15 -4
  16. package/dist/chunks/{index-axhwg61p.js → index-ypcef3a2.js} +15 -4
  17. package/dist/chunks/legacy-store-migration-z2m5yzyk.js +322 -0
  18. package/dist/chunks/{local-a9nen65z.js → local-0a9gja58.js} +142 -2
  19. package/dist/chunks/{local-0d8qrc92.js → local-969ye538.js} +153 -188
  20. package/dist/chunks/s3-backup-hxe3zpfz.js +240 -0
  21. package/dist/chunks/s3-config-hhght91k.js +14 -0
  22. package/dist/chunks/s3-object-store-n0s82swt.js +14 -0
  23. package/dist/chunks/{sync-vy1yjvs6.js → sync-q5hvwq6e.js} +4 -4
  24. package/dist/chunks/{sync-m46fc257.js → sync-xtkbm5br.js} +5 -4
  25. package/dist/chunks/{template-02wjvv05.js → template-fsfxpe4p.js} +1 -1
  26. package/dist/chunks/{template-ke972qqe.js → template-wgnzdn7h.js} +1 -1
  27. package/dist/cli/index.js +1236 -256
  28. package/dist/data/config-store.d.ts +10 -1
  29. package/dist/data/config-store.d.ts.map +1 -1
  30. package/dist/db/configs.d.ts +4 -1
  31. package/dist/db/configs.d.ts.map +1 -1
  32. package/dist/db/local.d.ts +1 -1
  33. package/dist/db/local.d.ts.map +1 -1
  34. package/dist/index.js +1086 -215
  35. package/dist/lib/apply.d.ts +7 -0
  36. package/dist/lib/apply.d.ts.map +1 -1
  37. package/dist/lib/compact-output.d.ts +3 -18
  38. package/dist/lib/compact-output.d.ts.map +1 -1
  39. package/dist/lib/export.d.ts +22 -3
  40. package/dist/lib/export.d.ts.map +1 -1
  41. package/dist/lib/import.d.ts +18 -0
  42. package/dist/lib/import.d.ts.map +1 -1
  43. package/dist/lib/legacy-store-migration.d.ts +39 -0
  44. package/dist/lib/legacy-store-migration.d.ts.map +1 -0
  45. package/dist/lib/managed-skill-runtimes.d.ts +1 -0
  46. package/dist/lib/managed-skill-runtimes.d.ts.map +1 -1
  47. package/dist/lib/sync-dir.d.ts.map +1 -1
  48. package/dist/mcp/index.js +41 -30
  49. package/dist/mcp/server.d.ts.map +1 -1
  50. package/dist/sdk/index.d.ts +2 -0
  51. package/dist/sdk/index.d.ts.map +1 -1
  52. package/dist/sdk/index.js +229 -10
  53. package/dist/sdk/resolve.d.ts +1 -1
  54. package/dist/sdk/resolve.d.ts.map +1 -1
  55. package/dist/sdk/v1-client.d.ts +58 -0
  56. package/dist/sdk/v1-client.d.ts.map +1 -0
  57. package/dist/sdk/v1.generated.d.ts +242 -21
  58. package/dist/sdk/v1.generated.d.ts.map +1 -1
  59. package/dist/server/cloud.d.ts.map +1 -1
  60. package/dist/server/index.d.ts +13 -5
  61. package/dist/server/index.d.ts.map +1 -1
  62. package/dist/server/index.js +1261 -427
  63. package/dist/server/openapi.d.ts +1379 -93
  64. package/dist/server/openapi.d.ts.map +1 -1
  65. package/dist/server/v1.d.ts +25 -1
  66. package/dist/server/v1.d.ts.map +1 -1
  67. package/dist/storage/cloud-store.d.ts +45 -2
  68. package/dist/storage/cloud-store.d.ts.map +1 -1
  69. package/dist/storage/index.d.ts +7 -0
  70. package/dist/storage/index.d.ts.map +1 -0
  71. package/dist/storage/index.js +744 -0
  72. package/dist/storage/s3-backup.d.ts +76 -0
  73. package/dist/storage/s3-backup.d.ts.map +1 -0
  74. package/dist/storage/s3-config.d.ts +46 -0
  75. package/dist/storage/s3-config.d.ts.map +1 -0
  76. package/dist/storage/s3-object-store.d.ts +70 -0
  77. package/dist/storage/s3-object-store.d.ts.map +1 -0
  78. package/dist/types/index.d.ts +98 -3
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/hasna.contract.json +110 -0
  81. package/migrations/0001_instructions.sql +63 -0
  82. package/migrations/0002_api_keys.sql +20 -0
  83. package/migrations/0003_idempotency_receipts.sql +24 -0
  84. package/package.json +24 -13
  85. 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-v7dkb384.js";
17
+ } from "./index-aj66f6xr.js";
18
18
  import"./index-35z65jsa.js";
19
19
  import {
20
20
  ConfigNotFoundError,
21
21
  ProfileNotFoundError
22
- } from "./index-pkscjnj5.js";
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-1hpwskwd.js";
10
+ } from "./index-6jncnnw7.js";
18
11
  import {
19
12
  ConfigNotFoundError,
20
13
  ProfileNotFoundError
21
- } from "./index-pkscjnj5.js";
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,