@hasna/skills 0.8.1 → 0.8.3

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/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.8.1",
10
+ version: "0.8.3",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
@@ -401,11 +401,66 @@ function defaultStartDirs() {
401
401
  return dirs;
402
402
  }
403
403
 
404
+ // src/server/execution-grant-store.ts
405
+ import { randomUUID } from "crypto";
406
+ function next(p, id, grants, expected) {
407
+ return {
408
+ schema: "hasna.skills-execution-grants.v1",
409
+ profileId: id,
410
+ workspaceId: p.orgId,
411
+ revision: randomUUID(),
412
+ previousRevision: expected,
413
+ updatedAt: new Date().toISOString(),
414
+ actorId: p.userId,
415
+ grants: structuredClone(grants)
416
+ };
417
+ }
418
+ function document(row) {
419
+ return row ? typeof row.document_json === "string" ? JSON.parse(row.document_json) : structuredClone(row.document_json) : null;
420
+ }
421
+
422
+ class MemoryExecutionGrantStore {
423
+ current = new Map;
424
+ history = new Map;
425
+ async get(p, id, revision) {
426
+ return structuredClone((revision ? this.history.get(JSON.stringify([p.orgId, id, revision])) : this.current.get(JSON.stringify([p.orgId, id]))) ?? null);
427
+ }
428
+ async save(p, id, grants, expected) {
429
+ const key = JSON.stringify([p.orgId, id]);
430
+ if ((this.current.get(key)?.revision ?? null) !== expected)
431
+ return null;
432
+ const value = next(p, id, grants, expected);
433
+ this.current.set(key, value);
434
+ this.history.set(JSON.stringify([p.orgId, id, value.revision]), value);
435
+ return structuredClone(value);
436
+ }
437
+ }
438
+
439
+ class SqliteExecutionGrantStore {
440
+ db;
441
+ constructor(db) {
442
+ this.db = db;
443
+ }
444
+ async get(p, id, revision) {
445
+ return document(revision ? this.db.query("SELECT document_json FROM skills_execution_grant_revisions WHERE org_id=? AND profile_id=? AND revision=?").get(p.orgId, id, revision) : this.db.query("SELECT document_json FROM skills_execution_grants WHERE org_id=? AND profile_id=?").get(p.orgId, id));
446
+ }
447
+ async save(p, id, grants, expected) {
448
+ return this.db.transaction(() => {
449
+ const value = next(p, id, grants, expected), json = JSON.stringify(value);
450
+ const row = expected === null ? this.db.query("INSERT INTO skills_execution_grants(org_id,profile_id,revision,document_json) VALUES(?,?,?,?) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING document_json").get(p.orgId, id, value.revision, json) : this.db.query("UPDATE skills_execution_grants SET revision=?,document_json=? WHERE org_id=? AND profile_id=? AND revision=? RETURNING document_json").get(value.revision, json, p.orgId, id, expected);
451
+ if (!row)
452
+ return null;
453
+ this.db.query("INSERT INTO skills_execution_grant_revisions(org_id,profile_id,revision,document_json) VALUES(?,?,?,?)").run(p.orgId, id, value.revision, json);
454
+ return document(row);
455
+ })();
456
+ }
457
+ }
458
+
404
459
  // src/server/sqlite-store.ts
405
460
  import { Database } from "bun:sqlite";
406
461
 
407
462
  // src/server/selection-store.ts
408
- import { randomUUID } from "crypto";
463
+ import { randomUUID as randomUUID2 } from "crypto";
409
464
  function selections(value) {
410
465
  return typeof value === "string" ? JSON.parse(value) : structuredClone(value);
411
466
  }
@@ -446,15 +501,15 @@ class MemorySkillSelectionStore {
446
501
  const key = this.key(p.orgId, id), previous = this.profiles.get(key);
447
502
  if ((previous?.revision ?? null) !== expected)
448
503
  return null;
449
- const next = {
504
+ const next2 = {
450
505
  id,
451
506
  workspaceId: p.orgId,
452
- revision: randomUUID(),
507
+ revision: randomUUID2(),
453
508
  selections: structuredClone(selected),
454
509
  updatedAt: new Date().toISOString()
455
510
  };
456
- this.profiles.set(key, next);
457
- return structuredClone(next);
511
+ this.profiles.set(key, next2);
512
+ return structuredClone(next2);
458
513
  }
459
514
  async getStationState(p, id) {
460
515
  return structuredClone(this.states.get(this.key(p.orgId, p.userId, id)) ?? null);
@@ -462,15 +517,15 @@ class MemorySkillSelectionStore {
462
517
  async saveStationState(p, id, input) {
463
518
  if (this.profiles.get(this.key(p.orgId, input.profileId))?.revision !== input.profileRevision)
464
519
  return null;
465
- const next = {
520
+ const next2 = {
466
521
  ...structuredClone(input),
467
522
  stationId: id,
468
523
  workspaceId: p.orgId,
469
524
  actorId: p.userId,
470
525
  appliedAt: new Date().toISOString()
471
526
  };
472
- this.states.set(this.key(p.orgId, p.userId, id), next);
473
- return structuredClone(next);
527
+ this.states.set(this.key(p.orgId, p.userId, id), next2);
528
+ return structuredClone(next2);
474
529
  }
475
530
  }
476
531
 
@@ -484,7 +539,7 @@ class SqliteSkillSelectionStore {
484
539
  return row ? profile(row) : null;
485
540
  }
486
541
  async saveProfile(p, id, selected, expected) {
487
- const revision = randomUUID(), timestamp = new Date().toISOString();
542
+ const revision = randomUUID2(), timestamp = new Date().toISOString();
488
543
  const row = expected === null ? this.db.query("INSERT INTO skills_profiles(org_id,profile_id,revision,selections_json,updated_at) VALUES(?,?,?,?,?) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING *").get(p.orgId, id, revision, JSON.stringify(selected), timestamp) : this.db.query("UPDATE skills_profiles SET revision=?,selections_json=?,updated_at=? WHERE org_id=? AND profile_id=? AND revision=? RETURNING *").get(revision, JSON.stringify(selected), timestamp, p.orgId, id, expected);
489
544
  return row ? profile(row) : null;
490
545
  }
@@ -501,7 +556,7 @@ class SqliteSkillSelectionStore {
501
556
  }
502
557
 
503
558
  // src/server/sqlite-store.ts
504
- import { randomUUID as randomUUID3 } from "crypto";
559
+ import { randomUUID as randomUUID4 } from "crypto";
505
560
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync } from "fs";
506
561
  import { dirname as dirname2, join as join4 } from "path";
507
562
 
@@ -524,7 +579,7 @@ function publicPrincipal(partial = {}) {
524
579
  }
525
580
 
526
581
  // src/server/rows.ts
527
- import { randomUUID as randomUUID2 } from "crypto";
582
+ import { randomUUID as randomUUID3 } from "crypto";
528
583
 
529
584
  // src/lib/skill-entry-path.ts
530
585
  class SkillEntryPaths {
@@ -834,7 +889,7 @@ function normalizeLimit(limit) {
834
889
  return Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : 0;
835
890
  }
836
891
  function runId() {
837
- return `run_${Date.now().toString(36)}_${randomUUID2().slice(0, 8)}`;
892
+ return `run_${Date.now().toString(36)}_${randomUUID3().slice(0, 8)}`;
838
893
  }
839
894
  function rowToRun(row) {
840
895
  return {
@@ -1057,6 +1112,9 @@ class SqliteSkillsStore {
1057
1112
  get selectionStore() {
1058
1113
  return new SqliteSkillSelectionStore(this.db);
1059
1114
  }
1115
+ get executionGrantStore() {
1116
+ return new SqliteExecutionGrantStore(this.db);
1117
+ }
1060
1118
  backend;
1061
1119
  db;
1062
1120
  closed = false;
@@ -1158,7 +1216,7 @@ class SqliteSkillsStore {
1158
1216
  JSON.stringify(input.input),
1159
1217
  JSON.stringify(input.args),
1160
1218
  input.idempotencyKey ?? null,
1161
- randomUUID3(),
1219
+ randomUUID4(),
1162
1220
  nowIso()
1163
1221
  ]);
1164
1222
  return rowToRun(row);
@@ -1468,21 +1526,21 @@ class SqliteSkillsStore {
1468
1526
  if (expectedRevisionId !== current.revisionId) {
1469
1527
  throw new SkillRevisionConflictError(slug, expectedRevisionId, current.revisionId);
1470
1528
  }
1471
- const next = { ...current, ...patch };
1529
+ const next2 = { ...current, ...patch };
1472
1530
  return this.db.transaction(() => {
1473
- const revisionId = revisionIdOfRecord(next);
1531
+ const revisionId = revisionIdOfRecord(next2);
1474
1532
  const row = this.get(`UPDATE skills_registry
1475
1533
  SET display_name = ?, description = ?, category = ?, tags_json = ?, kind = ?, version = ?, skill_md = ?,
1476
1534
  revision_id = ?, revision_number = revision_number + 1, updated_at = ?
1477
1535
  WHERE org_id = ? AND slug = ? AND tombstoned_at IS NULL AND revision_id = ?
1478
1536
  RETURNING *`, [
1479
- next.displayName,
1480
- next.description,
1481
- next.category,
1482
- JSON.stringify(next.tags),
1483
- next.kind,
1484
- next.version ?? null,
1485
- next.skillMd ?? null,
1537
+ next2.displayName,
1538
+ next2.description,
1539
+ next2.category,
1540
+ JSON.stringify(next2.tags),
1541
+ next2.kind,
1542
+ next2.version ?? null,
1543
+ next2.skillMd ?? null,
1486
1544
  revisionId,
1487
1545
  nowIso(),
1488
1546
  principal.orgId,
@@ -1502,7 +1560,7 @@ class SqliteSkillsStore {
1502
1560
  }
1503
1561
  this.db.run("DELETE FROM skills_tags WHERE org_id = ? AND slug = ?", [principal.orgId, slug]);
1504
1562
  const insertTag = this.db.prepare("INSERT OR IGNORE INTO skills_tags (org_id, slug, tag) VALUES (?, ?, ?)");
1505
- for (const tag of next.tags) {
1563
+ for (const tag of next2.tags) {
1506
1564
  if (!tag.trim())
1507
1565
  continue;
1508
1566
  insertTag.run(principal.orgId, slug, tag);