@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/README.md +110 -0
- package/bin/index.js +2768 -983
- package/bin/mcp.js +2071 -475
- package/bin/migrate.js +82 -24
- package/bin/server.js +1446 -654
- package/bin/worker.js +582 -274
- package/dist/admin-contract.js +8 -8
- package/dist/cli/commands/grants.d.ts +2 -0
- package/dist/index.js +168 -168
- package/dist/lib/execution-grant-client.d.ts +5 -0
- package/dist/lib/execution-grants.d.ts +49 -0
- package/dist/lib/execution-secrets.d.ts +40 -0
- package/dist/lib/selected-manifest.d.ts +6 -0
- package/dist/lib/selected-run.d.ts +28 -0
- package/dist/sdk/index.d.ts +4 -0
- package/dist/sdk/index.js +41008 -26743
- package/dist/server/execution-grant-store.d.ts +29 -0
- package/dist/server/execution-grants-api.d.ts +4 -0
- package/dist/server/sqlite-store.d.ts +2 -0
- package/dist/server/store.d.ts +3 -0
- package/dist/server/types.d.ts +2 -0
- package/migrations/postgres/0009_execution_grants.sql +15 -0
- package/migrations/sqlite/0009_execution_grants.sql +15 -0
- package/package.json +1 -1
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.
|
|
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
|
|
504
|
+
const next2 = {
|
|
450
505
|
id,
|
|
451
506
|
workspaceId: p.orgId,
|
|
452
|
-
revision:
|
|
507
|
+
revision: randomUUID2(),
|
|
453
508
|
selections: structuredClone(selected),
|
|
454
509
|
updatedAt: new Date().toISOString()
|
|
455
510
|
};
|
|
456
|
-
this.profiles.set(key,
|
|
457
|
-
return structuredClone(
|
|
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
|
|
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),
|
|
473
|
-
return structuredClone(
|
|
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 =
|
|
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
|
|
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
|
|
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)}_${
|
|
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
|
-
|
|
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
|
|
1529
|
+
const next2 = { ...current, ...patch };
|
|
1472
1530
|
return this.db.transaction(() => {
|
|
1473
|
-
const revisionId = revisionIdOfRecord(
|
|
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
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
JSON.stringify(
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
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
|
|
1563
|
+
for (const tag of next2.tags) {
|
|
1506
1564
|
if (!tag.trim())
|
|
1507
1565
|
continue;
|
|
1508
1566
|
insertTag.run(principal.orgId, slug, tag);
|