contribute-now 0.7.0-dev.3b89c66 → 0.7.0-dev.45c1376

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 (2) hide show
  1. package/dist/index.js +1649 -128
  2. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -1298,6 +1298,579 @@ var init_formatter = __esm(() => {
1298
1298
  init_message_formatter();
1299
1299
  });
1300
1300
 
1301
+ // node_modules/@wgtechlabs/secrets-engine/dist/index.js
1302
+ var exports_dist = {};
1303
+ __export(exports_dist, {
1304
+ SecurityError: () => SecurityError,
1305
+ SecretsEngineError: () => SecretsEngineError,
1306
+ SecretsEngine: () => SecretsEngine,
1307
+ KeyNotFoundError: () => KeyNotFoundError,
1308
+ IntegrityError: () => IntegrityError,
1309
+ InitializationError: () => InitializationError,
1310
+ DecryptionError: () => DecryptionError
1311
+ });
1312
+ import { readdir, rm, unlink } from "fs/promises";
1313
+ import { join as join3 } from "path";
1314
+ import {
1315
+ createCipheriv,
1316
+ createDecipheriv,
1317
+ createHash,
1318
+ createHmac,
1319
+ randomBytes,
1320
+ scryptSync
1321
+ } from "crypto";
1322
+ import { readFile as readFile2 } from "fs/promises";
1323
+ import { chmod, mkdir, readFile, stat, writeFile } from "fs/promises";
1324
+ import { homedir, hostname, networkInterfaces, userInfo } from "os";
1325
+ import { join as join4 } from "path";
1326
+ import { Database } from "bun:sqlite";
1327
+ import { readFile as readFile3 } from "fs/promises";
1328
+ import { join as join22 } from "path";
1329
+ function deriveMasterKey(machineId, keyfile, salt) {
1330
+ const password = Buffer.concat([Buffer.from(machineId, "utf-8"), keyfile]);
1331
+ return scryptSync(password, salt, CONSTANTS.KEY_LENGTH, {
1332
+ N: CONSTANTS.SCRYPT_N,
1333
+ r: CONSTANTS.SCRYPT_R,
1334
+ p: CONSTANTS.SCRYPT_P,
1335
+ maxmem: 256 * CONSTANTS.SCRYPT_N * CONSTANTS.SCRYPT_R
1336
+ });
1337
+ }
1338
+ function encrypt(masterKey, plaintext) {
1339
+ const iv = randomBytes(CONSTANTS.IV_LENGTH);
1340
+ const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
1341
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
1342
+ const authTag = cipher.getAuthTag();
1343
+ return {
1344
+ iv,
1345
+ ciphertext: Buffer.concat([encrypted, authTag])
1346
+ };
1347
+ }
1348
+ function decrypt(masterKey, iv, ciphertext, keyHash) {
1349
+ if (ciphertext.length < CONSTANTS.AUTH_TAG_LENGTH) {
1350
+ throw new DecryptionError("Ciphertext too short to contain auth tag", keyHash);
1351
+ }
1352
+ const authTag = ciphertext.subarray(ciphertext.length - CONSTANTS.AUTH_TAG_LENGTH);
1353
+ const encrypted = ciphertext.subarray(0, ciphertext.length - CONSTANTS.AUTH_TAG_LENGTH);
1354
+ try {
1355
+ const decipher = createDecipheriv("aes-256-gcm", masterKey, iv);
1356
+ decipher.setAuthTag(authTag);
1357
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf-8");
1358
+ } catch (error) {
1359
+ throw new DecryptionError(error instanceof Error ? error.message : "Unknown decryption error", keyHash);
1360
+ }
1361
+ }
1362
+ function hmac(masterKey, data) {
1363
+ return createHmac("sha256", masterKey).update(data).digest("hex");
1364
+ }
1365
+ function sha256(data) {
1366
+ return createHash("sha256").update(data).digest();
1367
+ }
1368
+ function generateSalt() {
1369
+ return randomBytes(CONSTANTS.SALT_LENGTH);
1370
+ }
1371
+ function matchGlob(pattern, key) {
1372
+ const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^.]*");
1373
+ const regex2 = new RegExp(`^${regexStr}$`);
1374
+ return regex2.test(key);
1375
+ }
1376
+ function filterKeys(keys, pattern) {
1377
+ return keys.filter((key) => matchGlob(pattern, key));
1378
+ }
1379
+ function resolveStoragePath(options) {
1380
+ if (options?.path) {
1381
+ return options.path;
1382
+ }
1383
+ if (options?.location === "xdg") {
1384
+ return resolveXdgPath();
1385
+ }
1386
+ if (process.platform !== "win32" && process.env.XDG_CONFIG_HOME) {
1387
+ return join4(process.env.XDG_CONFIG_HOME, "secrets-engine");
1388
+ }
1389
+ return join4(homedir(), CONSTANTS.DIR_NAME);
1390
+ }
1391
+ function resolveXdgPath() {
1392
+ if (process.platform === "win32") {
1393
+ const appData = process.env.APPDATA;
1394
+ if (!appData) {
1395
+ throw new InitializationError("APPDATA environment variable is not set");
1396
+ }
1397
+ return join4(appData, "secrets-engine");
1398
+ }
1399
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join4(homedir(), ".config");
1400
+ return join4(xdgConfig, "secrets-engine");
1401
+ }
1402
+ async function ensureDirectory(dirPath) {
1403
+ try {
1404
+ await mkdir(dirPath, { recursive: true, mode: EXPECTED_MODES.directory });
1405
+ } catch (error) {
1406
+ throw new InitializationError(`Cannot create storage directory at "${dirPath}"`, error);
1407
+ }
1408
+ if (process.platform !== "win32") {
1409
+ await verifyPermission(dirPath, EXPECTED_MODES.directory, "directory");
1410
+ }
1411
+ }
1412
+ async function verifyPermission(filePath, expectedMode, label) {
1413
+ if (process.platform === "win32") {
1414
+ return;
1415
+ }
1416
+ const fileStat = await stat(filePath);
1417
+ const actualMode = fileStat.mode & 511;
1418
+ if (actualMode !== expectedMode) {
1419
+ throw new SecurityError(`Insecure ${label} permissions`, formatOctal(expectedMode), formatOctal(actualMode), filePath);
1420
+ }
1421
+ }
1422
+ function formatOctal(mode) {
1423
+ return `0o${mode.toString(8).padStart(3, "0")}`;
1424
+ }
1425
+ function getMachineIdentity() {
1426
+ const host = hostname();
1427
+ const mac = getPrimaryMac();
1428
+ const user = userInfo().username;
1429
+ return `${host}:${mac}:${user}`;
1430
+ }
1431
+ function getPrimaryMac() {
1432
+ const interfaces = networkInterfaces();
1433
+ for (const entries of Object.values(interfaces)) {
1434
+ if (!entries)
1435
+ continue;
1436
+ for (const entry of entries) {
1437
+ if (!entry.internal && entry.mac && entry.mac !== "00:00:00:00:00:00") {
1438
+ return entry.mac;
1439
+ }
1440
+ }
1441
+ }
1442
+ return "no-mac-available";
1443
+ }
1444
+ async function ensureKeyfile(dirPath) {
1445
+ const keyfilePath = join4(dirPath, CONSTANTS.KEYFILE_NAME);
1446
+ try {
1447
+ if (process.platform !== "win32") {
1448
+ await verifyPermission(keyfilePath, EXPECTED_MODES.keyfile, "keyfile");
1449
+ }
1450
+ return await readFile(keyfilePath);
1451
+ } catch (error) {
1452
+ if (isFileNotFoundError(error)) {
1453
+ return await createKeyfile(keyfilePath);
1454
+ }
1455
+ throw error;
1456
+ }
1457
+ }
1458
+ async function createKeyfile(keyfilePath) {
1459
+ const { randomBytes: randomBytes2 } = await import("crypto");
1460
+ const keyfileData = randomBytes2(CONSTANTS.KEYFILE_LENGTH);
1461
+ await writeFile(keyfilePath, keyfileData, { mode: EXPECTED_MODES.keyfile });
1462
+ if (process.platform !== "win32") {
1463
+ await chmod(keyfilePath, EXPECTED_MODES.keyfile);
1464
+ }
1465
+ return keyfileData;
1466
+ }
1467
+ async function readMetaFile(dirPath) {
1468
+ const metaPath = join4(dirPath, CONSTANTS.META_NAME);
1469
+ try {
1470
+ return await readFile(metaPath, "utf-8");
1471
+ } catch (error) {
1472
+ if (isFileNotFoundError(error)) {
1473
+ return null;
1474
+ }
1475
+ throw error;
1476
+ }
1477
+ }
1478
+ async function writeMetaFile(dirPath, content) {
1479
+ const metaPath = join4(dirPath, CONSTANTS.META_NAME);
1480
+ await writeFile(metaPath, content, { mode: EXPECTED_MODES.meta });
1481
+ if (process.platform !== "win32") {
1482
+ await chmod(metaPath, EXPECTED_MODES.meta);
1483
+ }
1484
+ }
1485
+ function isFileNotFoundError(error) {
1486
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
1487
+ }
1488
+ async function computeIntegrityHmac(masterKey, dbFilePath, checkpointFn) {
1489
+ if (checkpointFn) {
1490
+ try {
1491
+ checkpointFn();
1492
+ } catch (err) {
1493
+ const originalMessage = err instanceof Error ? err.message : String(err);
1494
+ throw new IntegrityError(`Integrity checkpoint failed: ${originalMessage}`);
1495
+ }
1496
+ }
1497
+ const dbBytes = Buffer.from(await readFile2(dbFilePath));
1498
+ const dbHash = sha256(dbBytes);
1499
+ return hmac(masterKey, dbHash);
1500
+ }
1501
+ async function verifyIntegrity(masterKey, dbFilePath, dirPath, checkpointFn) {
1502
+ const metaRaw = await readMetaFile(dirPath);
1503
+ if (!metaRaw) {
1504
+ throw new IntegrityError("Metadata file (meta.json) is missing");
1505
+ }
1506
+ let meta;
1507
+ try {
1508
+ meta = JSON.parse(metaRaw);
1509
+ } catch {
1510
+ throw new IntegrityError("Metadata file (meta.json) is corrupted");
1511
+ }
1512
+ if (meta.version !== CONSTANTS.STORE_VERSION) {
1513
+ throw new IntegrityError(`Unsupported store version: expected "${CONSTANTS.STORE_VERSION}", got "${meta.version}"`);
1514
+ }
1515
+ const computedHmac = await computeIntegrityHmac(masterKey, dbFilePath, checkpointFn);
1516
+ if (computedHmac !== meta.integrity) {
1517
+ throw new IntegrityError;
1518
+ }
1519
+ return meta;
1520
+ }
1521
+ async function updateIntegrity(masterKey, dbFilePath, dirPath, salt, checkpointFn) {
1522
+ const integrity = await computeIntegrityHmac(masterKey, dbFilePath, checkpointFn);
1523
+ const meta = {
1524
+ version: CONSTANTS.STORE_VERSION,
1525
+ salt,
1526
+ integrity
1527
+ };
1528
+ await writeMetaFile(dirPath, JSON.stringify(meta, null, 2));
1529
+ }
1530
+
1531
+ class SecretStore {
1532
+ db;
1533
+ constructor(db) {
1534
+ this.db = db;
1535
+ }
1536
+ static open(dirPath) {
1537
+ const dbPath = join22(dirPath, CONSTANTS.DB_NAME);
1538
+ try {
1539
+ const db = new Database(dbPath, { create: true });
1540
+ db.exec("PRAGMA journal_mode = WAL;");
1541
+ db.exec("PRAGMA foreign_keys = ON;");
1542
+ db.exec("PRAGMA busy_timeout = 5000;");
1543
+ db.exec(CREATE_SECRETS_TABLE);
1544
+ db.exec(CREATE_META_TABLE);
1545
+ return new SecretStore(db);
1546
+ } catch (error) {
1547
+ throw new InitializationError(`Cannot open database at "${dbPath}"`, error);
1548
+ }
1549
+ }
1550
+ upsert(entry) {
1551
+ const now = Math.floor(Date.now() / 1000);
1552
+ const stmt = this.db.prepare(`
1553
+ INSERT INTO secrets (key_hash, key_enc, iv, cipher, created, updated)
1554
+ VALUES ($key_hash, $key_enc, $iv, $cipher, $created, $updated)
1555
+ ON CONFLICT(key_hash) DO UPDATE SET
1556
+ key_enc = excluded.key_enc,
1557
+ iv = excluded.iv,
1558
+ cipher = excluded.cipher,
1559
+ updated = excluded.updated
1560
+ `);
1561
+ stmt.run({
1562
+ $key_hash: entry.key_hash,
1563
+ $key_enc: entry.key_enc,
1564
+ $iv: entry.iv,
1565
+ $cipher: entry.cipher,
1566
+ $created: now,
1567
+ $updated: now
1568
+ });
1569
+ }
1570
+ findByHash(keyHash) {
1571
+ const stmt = this.db.prepare("SELECT key_hash, key_enc, iv, cipher, created, updated FROM secrets WHERE key_hash = ?");
1572
+ const row = stmt.get(keyHash);
1573
+ return row ?? null;
1574
+ }
1575
+ findAll() {
1576
+ const stmt = this.db.prepare("SELECT key_hash, key_enc, iv, cipher, created, updated FROM secrets");
1577
+ return stmt.all();
1578
+ }
1579
+ deleteByHash(keyHash) {
1580
+ const stmt = this.db.prepare("DELETE FROM secrets WHERE key_hash = ?");
1581
+ const result = stmt.run(keyHash);
1582
+ return result.changes > 0;
1583
+ }
1584
+ deleteAll() {
1585
+ this.db.exec("DELETE FROM secrets");
1586
+ }
1587
+ count() {
1588
+ const stmt = this.db.prepare("SELECT COUNT(*) as count FROM secrets");
1589
+ const row = stmt.get();
1590
+ return row.count;
1591
+ }
1592
+ async readRawBytes() {
1593
+ return Buffer.from(await readFile3(this.db.filename));
1594
+ }
1595
+ get filePath() {
1596
+ return this.db.filename;
1597
+ }
1598
+ checkpoint() {
1599
+ this.db.exec("PRAGMA wal_checkpoint(TRUNCATE);");
1600
+ }
1601
+ close() {
1602
+ this.db.close();
1603
+ }
1604
+ }
1605
+
1606
+ class SecretsEngine {
1607
+ masterKey;
1608
+ store;
1609
+ dirPath;
1610
+ salt;
1611
+ keyIndex = new Map;
1612
+ closed = false;
1613
+ constructor(masterKey, store, dirPath, salt) {
1614
+ this.masterKey = masterKey;
1615
+ this.store = store;
1616
+ this.dirPath = dirPath;
1617
+ this.salt = salt;
1618
+ }
1619
+ static async open(options) {
1620
+ const dirPath = resolveStoragePath(options);
1621
+ await ensureDirectory(dirPath);
1622
+ const keyfile = await ensureKeyfile(dirPath);
1623
+ const { salt, isNewStore } = await resolveSalt(dirPath);
1624
+ const machineId = getMachineIdentity();
1625
+ const masterKey = deriveMasterKey(machineId, keyfile, Buffer.from(salt, "hex"));
1626
+ const store = SecretStore.open(dirPath);
1627
+ try {
1628
+ if (!isNewStore) {
1629
+ await verifyIntegrity(masterKey, store.filePath, dirPath, () => store.checkpoint());
1630
+ }
1631
+ const engine = new SecretsEngine(masterKey, store, dirPath, salt);
1632
+ engine.buildKeyIndex();
1633
+ if (isNewStore) {
1634
+ await updateIntegrity(masterKey, store.filePath, dirPath, salt, () => store.checkpoint());
1635
+ }
1636
+ return engine;
1637
+ } catch (error) {
1638
+ try {
1639
+ store.close();
1640
+ } catch {}
1641
+ throw error;
1642
+ }
1643
+ }
1644
+ async get(key) {
1645
+ this.ensureOpen();
1646
+ const keyHash = this.hashKey(key);
1647
+ const entry = this.store.findByHash(keyHash);
1648
+ if (!entry) {
1649
+ return null;
1650
+ }
1651
+ return decrypt(this.masterKey, Buffer.from(entry.iv), Buffer.from(entry.cipher), keyHash);
1652
+ }
1653
+ async getOrThrow(key) {
1654
+ const value = await this.get(key);
1655
+ if (value === null) {
1656
+ throw new KeyNotFoundError(key);
1657
+ }
1658
+ return value;
1659
+ }
1660
+ async set(key, value) {
1661
+ this.ensureOpen();
1662
+ const keyHash = this.hashKey(key);
1663
+ const encryptedKey = encrypt(this.masterKey, key);
1664
+ const encryptedValue = encrypt(this.masterKey, value);
1665
+ const keyEncPacked = Buffer.concat([encryptedKey.iv, encryptedKey.ciphertext]);
1666
+ this.store.upsert({
1667
+ key_hash: keyHash,
1668
+ key_enc: keyEncPacked,
1669
+ iv: encryptedValue.iv,
1670
+ cipher: encryptedValue.ciphertext
1671
+ });
1672
+ this.keyIndex.set(keyHash, key);
1673
+ await updateIntegrity(this.masterKey, this.store.filePath, this.dirPath, this.salt, () => this.store.checkpoint());
1674
+ }
1675
+ async has(key) {
1676
+ this.ensureOpen();
1677
+ return this.keyIndex.has(this.hashKey(key));
1678
+ }
1679
+ async delete(key) {
1680
+ this.ensureOpen();
1681
+ const keyHash = this.hashKey(key);
1682
+ const deleted = this.store.deleteByHash(keyHash);
1683
+ if (deleted) {
1684
+ this.keyIndex.delete(keyHash);
1685
+ await updateIntegrity(this.masterKey, this.store.filePath, this.dirPath, this.salt, () => this.store.checkpoint());
1686
+ }
1687
+ return deleted;
1688
+ }
1689
+ async keys(pattern) {
1690
+ this.ensureOpen();
1691
+ const allKeys = Array.from(this.keyIndex.values());
1692
+ if (!pattern) {
1693
+ return allKeys.sort();
1694
+ }
1695
+ return filterKeys(allKeys, pattern).sort();
1696
+ }
1697
+ async destroy() {
1698
+ this.ensureOpen();
1699
+ this.store.checkpoint();
1700
+ this.store.close();
1701
+ this.keyIndex.clear();
1702
+ this.closed = true;
1703
+ await new Promise((resolve3) => setTimeout(resolve3, 150));
1704
+ await removeDirectoryContents(this.dirPath);
1705
+ }
1706
+ async close() {
1707
+ if (!this.closed) {
1708
+ try {
1709
+ this.store.checkpoint();
1710
+ await updateIntegrity(this.masterKey, this.store.filePath, this.dirPath, this.salt);
1711
+ } finally {
1712
+ this.store.close();
1713
+ this.keyIndex.clear();
1714
+ this.closed = true;
1715
+ }
1716
+ }
1717
+ }
1718
+ get size() {
1719
+ this.ensureOpen();
1720
+ return this.keyIndex.size;
1721
+ }
1722
+ get storagePath() {
1723
+ return this.dirPath;
1724
+ }
1725
+ buildKeyIndex() {
1726
+ const entries = this.store.findAll();
1727
+ for (const entry of entries) {
1728
+ try {
1729
+ const keyEncBuf = Buffer.from(entry.key_enc);
1730
+ const keyIv = keyEncBuf.subarray(0, 12);
1731
+ const keyCipher = keyEncBuf.subarray(12);
1732
+ const keyName = decrypt(this.masterKey, keyIv, keyCipher, entry.key_hash);
1733
+ this.keyIndex.set(entry.key_hash, keyName);
1734
+ } catch (error) {
1735
+ if (error instanceof DecryptionError) {
1736
+ console.warn(`[secrets-engine] Skipping corrupted entry: ${entry.key_hash.slice(0, 16)}…`);
1737
+ continue;
1738
+ }
1739
+ throw error;
1740
+ }
1741
+ }
1742
+ }
1743
+ hashKey(key) {
1744
+ return hmac(this.masterKey, key);
1745
+ }
1746
+ ensureOpen() {
1747
+ if (this.closed) {
1748
+ throw new Error("SecretsEngine instance is closed");
1749
+ }
1750
+ }
1751
+ }
1752
+ async function resolveSalt(dirPath) {
1753
+ const metaRaw = await readMetaFile(dirPath);
1754
+ if (metaRaw) {
1755
+ try {
1756
+ const meta = JSON.parse(metaRaw);
1757
+ if (meta.salt) {
1758
+ return { salt: meta.salt, isNewStore: false };
1759
+ }
1760
+ } catch {}
1761
+ }
1762
+ const salt = generateSalt().toString("hex");
1763
+ return { salt, isNewStore: true };
1764
+ }
1765
+ async function removeDirectoryContents(dirPath) {
1766
+ const maxRetries = 5;
1767
+ const retryDelay = 200;
1768
+ for (let attempt = 0;attempt < maxRetries; attempt++) {
1769
+ try {
1770
+ const entries = await readdir(dirPath, { withFileTypes: true });
1771
+ for (const entry of entries) {
1772
+ const fullPath = join3(dirPath, entry.name);
1773
+ if (entry.isDirectory()) {
1774
+ await rm(fullPath, { recursive: true, force: true });
1775
+ } else {
1776
+ await unlink(fullPath);
1777
+ }
1778
+ }
1779
+ await rm(dirPath, { force: true, recursive: true });
1780
+ return;
1781
+ } catch (error) {
1782
+ const isRetryable = error instanceof Error && "code" in error && (error.code === "EBUSY" || error.code === "EPERM");
1783
+ if (!isRetryable || attempt === maxRetries - 1) {
1784
+ throw error;
1785
+ }
1786
+ await new Promise((resolve3) => setTimeout(resolve3, retryDelay * (attempt + 1)));
1787
+ }
1788
+ }
1789
+ }
1790
+ var SecretsEngineError, SecurityError, IntegrityError, KeyNotFoundError, DecryptionError, InitializationError, CONSTANTS, EXPECTED_MODES, CREATE_SECRETS_TABLE = `
1791
+ CREATE TABLE IF NOT EXISTS secrets (
1792
+ key_hash TEXT PRIMARY KEY,
1793
+ key_enc BLOB NOT NULL,
1794
+ iv BLOB NOT NULL,
1795
+ cipher BLOB NOT NULL,
1796
+ created INTEGER NOT NULL,
1797
+ updated INTEGER NOT NULL
1798
+ ) STRICT;
1799
+ `, CREATE_META_TABLE = `
1800
+ CREATE TABLE IF NOT EXISTS meta (
1801
+ key TEXT PRIMARY KEY,
1802
+ value TEXT NOT NULL
1803
+ ) STRICT;
1804
+ `;
1805
+ var init_dist = __esm(() => {
1806
+ SecretsEngineError = class SecretsEngineError extends Error {
1807
+ constructor(message, options) {
1808
+ super(message, options);
1809
+ this.name = this.constructor.name;
1810
+ }
1811
+ };
1812
+ SecurityError = class SecurityError extends SecretsEngineError {
1813
+ expectedPermission;
1814
+ actualPermission;
1815
+ path;
1816
+ code = "SECURITY_ERROR";
1817
+ constructor(message, expectedPermission, actualPermission, path2) {
1818
+ super(`${message} — expected ${expectedPermission}, got ${actualPermission} on "${path2}"`);
1819
+ this.expectedPermission = expectedPermission;
1820
+ this.actualPermission = actualPermission;
1821
+ this.path = path2;
1822
+ }
1823
+ };
1824
+ IntegrityError = class IntegrityError extends SecretsEngineError {
1825
+ code = "INTEGRITY_ERROR";
1826
+ constructor(message = "Database integrity check failed — possible tampering detected") {
1827
+ super(message);
1828
+ }
1829
+ };
1830
+ KeyNotFoundError = class KeyNotFoundError extends SecretsEngineError {
1831
+ code = "KEY_NOT_FOUND";
1832
+ constructor(key) {
1833
+ super(`Key not found: "${key}"`);
1834
+ }
1835
+ };
1836
+ DecryptionError = class DecryptionError extends SecretsEngineError {
1837
+ keyHash;
1838
+ code = "DECRYPTION_ERROR";
1839
+ constructor(message, keyHash) {
1840
+ const detail = keyHash ? ` (entry: ${keyHash.slice(0, 16)}…)` : "";
1841
+ super(`Decryption failed${detail}: ${message}`);
1842
+ this.keyHash = keyHash;
1843
+ }
1844
+ };
1845
+ InitializationError = class InitializationError extends SecretsEngineError {
1846
+ code = "INITIALIZATION_ERROR";
1847
+ constructor(message, cause) {
1848
+ super(`Initialization failed: ${message}`, { cause });
1849
+ }
1850
+ };
1851
+ CONSTANTS = {
1852
+ IV_LENGTH: 12,
1853
+ AUTH_TAG_LENGTH: 16,
1854
+ KEY_LENGTH: 32,
1855
+ SCRYPT_N: 131072,
1856
+ SCRYPT_R: 8,
1857
+ SCRYPT_P: 1,
1858
+ KEYFILE_LENGTH: 32,
1859
+ SALT_LENGTH: 32,
1860
+ STORE_VERSION: "1",
1861
+ DIR_NAME: ".secrets-engine",
1862
+ KEYFILE_NAME: ".keyfile",
1863
+ DB_NAME: "store.db",
1864
+ META_NAME: "meta.json"
1865
+ };
1866
+ EXPECTED_MODES = {
1867
+ directory: 448,
1868
+ database: 384,
1869
+ keyfile: 256,
1870
+ meta: 384
1871
+ };
1872
+ });
1873
+
1301
1874
  // node_modules/sisteransi/src/index.js
1302
1875
  var require_src = __commonJS((exports, module) => {
1303
1876
  var ESC = "\x1B";
@@ -4857,7 +5430,7 @@ var init_session = __esm(() => {
4857
5430
  import { spawn } from "node:child_process";
4858
5431
  import { existsSync as existsSync4 } from "node:fs";
4859
5432
  import { Socket } from "node:net";
4860
- import { dirname as dirname4, join as join4 } from "node:path";
5433
+ import { dirname as dirname5, join as join8 } from "node:path";
4861
5434
  import { fileURLToPath } from "node:url";
4862
5435
  function isZodSchema(value) {
4863
5436
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
@@ -4879,7 +5452,7 @@ function getNodeExecPath() {
4879
5452
  function getBundledCliPath() {
4880
5453
  const sdkUrl = import.meta.resolve("@github/copilot/sdk");
4881
5454
  const sdkPath = fileURLToPath(sdkUrl);
4882
- return join4(dirname4(dirname4(sdkPath)), "index.js");
5455
+ return join8(dirname5(dirname5(sdkPath)), "index.js");
4883
5456
  }
4884
5457
 
4885
5458
  class CopilotClient {
@@ -5631,14 +6204,14 @@ var approveAll = () => ({ kind: "approved" });
5631
6204
  var init_types2 = () => {};
5632
6205
 
5633
6206
  // node_modules/@github/copilot-sdk/dist/index.js
5634
- var exports_dist2 = {};
5635
- __export(exports_dist2, {
6207
+ var exports_dist3 = {};
6208
+ __export(exports_dist3, {
5636
6209
  defineTool: () => defineTool,
5637
6210
  approveAll: () => approveAll,
5638
6211
  CopilotSession: () => CopilotSession,
5639
6212
  CopilotClient: () => CopilotClient
5640
6213
  });
5641
- var init_dist = __esm(() => {
6214
+ var init_dist2 = __esm(() => {
5642
6215
  init_client();
5643
6216
  init_session();
5644
6217
  init_types2();
@@ -8826,21 +9399,991 @@ var LogEngine = {
8826
9399
  var import_picocolors = __toESM(require_picocolors(), 1);
8827
9400
 
8828
9401
  // src/utils/state.ts
8829
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
8830
- import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
9402
+ import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
9403
+ import { dirname as dirname4, join as join7, resolve as resolve3 } from "node:path";
9404
+
9405
+ // node_modules/@wgtechlabs/config-engine/dist/chunk-gs6j6hrj.js
9406
+ import { createRequire as createRequire2 } from "node:module";
9407
+ var __require2 = /* @__PURE__ */ createRequire2(import.meta.url);
9408
+ function createZodValidator(schema) {
9409
+ return {
9410
+ validate(data) {
9411
+ const result = schema.safeParse(data);
9412
+ if (result.success) {
9413
+ return { success: true, data: result.data };
9414
+ }
9415
+ return {
9416
+ success: false,
9417
+ errors: formatZodErrors(result.error)
9418
+ };
9419
+ }
9420
+ };
9421
+ }
9422
+ function formatZodErrors(error) {
9423
+ return error.issues.map((issue) => {
9424
+ const path2 = issue.path.length > 0 ? `\`${issue.path.join(".")}\` ` : "";
9425
+ return `${path2}${issue.message}`;
9426
+ });
9427
+ }
9428
+ function resolveValidator(options) {
9429
+ if (options.validator)
9430
+ return options.validator;
9431
+ if (options.schema)
9432
+ return createZodValidator(options.schema);
9433
+ return;
9434
+ }
9435
+
9436
+ // node_modules/@wgtechlabs/config-engine/dist/index.js
9437
+ import { mkdirSync as mkdirSync3 } from "node:fs";
9438
+ import { dirname as dirname3 } from "node:path";
9439
+ import { watch } from "node:fs";
9440
+ import { homedir as homedir2, platform as platform2 } from "node:os";
9441
+ import { join as join6 } from "node:path";
9442
+
9443
+ class ConfigCache {
9444
+ #store;
9445
+ #strategy;
9446
+ #data = new Map;
9447
+ #dirty = new Set;
9448
+ #deleted = new Set;
9449
+ #flushScheduled = false;
9450
+ #flushPromise = null;
9451
+ constructor(store, strategy = "batched") {
9452
+ this.#store = store;
9453
+ this.#strategy = strategy;
9454
+ }
9455
+ load(initial) {
9456
+ this.#data.clear();
9457
+ this.#dirty.clear();
9458
+ this.#deleted.clear();
9459
+ const stored = initial ?? this.#store.getAll();
9460
+ for (const [key, value] of Object.entries(stored)) {
9461
+ this.#data.set(key, value);
9462
+ }
9463
+ }
9464
+ get(key) {
9465
+ return this.#data.get(key);
9466
+ }
9467
+ has(key) {
9468
+ return this.#data.has(key);
9469
+ }
9470
+ get size() {
9471
+ return this.#data.size;
9472
+ }
9473
+ getAll() {
9474
+ const obj = Object.create(null);
9475
+ for (const [key, value] of this.#data) {
9476
+ obj[key] = value;
9477
+ }
9478
+ return obj;
9479
+ }
9480
+ entries() {
9481
+ return this.#data.entries();
9482
+ }
9483
+ set(key, value) {
9484
+ this.#data.set(key, value);
9485
+ this.#dirty.add(key);
9486
+ this.#deleted.delete(key);
9487
+ this.#scheduleFlush();
9488
+ }
9489
+ setMany(entries) {
9490
+ for (const [key, value] of entries) {
9491
+ this.#data.set(key, value);
9492
+ this.#dirty.add(key);
9493
+ this.#deleted.delete(key);
9494
+ }
9495
+ this.#scheduleFlush();
9496
+ }
9497
+ delete(key) {
9498
+ const existed = this.#data.delete(key);
9499
+ if (existed) {
9500
+ this.#dirty.delete(key);
9501
+ this.#deleted.add(key);
9502
+ this.#scheduleFlush();
9503
+ }
9504
+ return existed;
9505
+ }
9506
+ clear() {
9507
+ for (const key of this.#data.keys()) {
9508
+ this.#deleted.add(key);
9509
+ }
9510
+ this.#data.clear();
9511
+ this.#dirty.clear();
9512
+ this.#scheduleFlush();
9513
+ }
9514
+ replaceAll(data) {
9515
+ for (const key of this.#data.keys()) {
9516
+ if (!(key in data)) {
9517
+ this.#deleted.add(key);
9518
+ }
9519
+ }
9520
+ this.#data.clear();
9521
+ this.#dirty.clear();
9522
+ for (const [key, value] of Object.entries(data)) {
9523
+ this.#data.set(key, value);
9524
+ this.#dirty.add(key);
9525
+ this.#deleted.delete(key);
9526
+ }
9527
+ this.#scheduleFlush();
9528
+ }
9529
+ #scheduleFlush() {
9530
+ if (this.#strategy === "manual")
9531
+ return;
9532
+ if (this.#strategy === "immediate") {
9533
+ this.flushSync();
9534
+ return;
9535
+ }
9536
+ if (!this.#flushScheduled) {
9537
+ this.#flushScheduled = true;
9538
+ this.#flushPromise = Promise.resolve().then(() => {
9539
+ this.flushSync();
9540
+ this.#flushScheduled = false;
9541
+ this.#flushPromise = null;
9542
+ });
9543
+ }
9544
+ }
9545
+ flushSync() {
9546
+ if (this.#dirty.size === 0 && this.#deleted.size === 0)
9547
+ return;
9548
+ this.#store.transaction(() => {
9549
+ if (this.#dirty.size > 0) {
9550
+ const entries = [];
9551
+ for (const key of this.#dirty) {
9552
+ entries.push([key, this.#data.get(key)]);
9553
+ }
9554
+ this.#store.setMany(entries);
9555
+ }
9556
+ for (const key of this.#deleted) {
9557
+ this.#store.deleteOne(key);
9558
+ }
9559
+ this.#store.touchLastWrite();
9560
+ });
9561
+ this.#dirty.clear();
9562
+ this.#deleted.clear();
9563
+ }
9564
+ async flush() {
9565
+ if (this.#flushPromise) {
9566
+ await this.#flushPromise;
9567
+ } else if (this.#dirty.size > 0 || this.#deleted.size > 0) {
9568
+ this.flushSync();
9569
+ }
9570
+ }
9571
+ reload() {
9572
+ this.#dirty.clear();
9573
+ this.#deleted.clear();
9574
+ this.load();
9575
+ }
9576
+ }
9577
+ function getByPath(obj, path2) {
9578
+ if (typeof obj !== "object" || obj === null)
9579
+ return;
9580
+ const keys = path2.split(".");
9581
+ let current = obj;
9582
+ for (const key of keys) {
9583
+ if (typeof current !== "object" || current === null)
9584
+ return;
9585
+ current = current[key];
9586
+ }
9587
+ return current;
9588
+ }
9589
+ function setByPath(obj, path2, value) {
9590
+ const keys = path2.split(".");
9591
+ if (keys.length === 0)
9592
+ return obj;
9593
+ const result = { ...obj };
9594
+ let current = result;
9595
+ for (let i2 = 0;i2 < keys.length - 1; i2++) {
9596
+ const key = keys[i2];
9597
+ if (typeof current[key] !== "object" || current[key] === null) {
9598
+ current[key] = {};
9599
+ } else {
9600
+ current[key] = { ...current[key] };
9601
+ }
9602
+ current = current[key];
9603
+ }
9604
+ const lastKey = keys[keys.length - 1];
9605
+ current[lastKey] = value;
9606
+ return result;
9607
+ }
9608
+ function hasByPath(obj, path2) {
9609
+ if (typeof obj !== "object" || obj === null)
9610
+ return false;
9611
+ const keys = path2.split(".");
9612
+ let current = obj;
9613
+ for (let i2 = 0;i2 < keys.length; i2++) {
9614
+ if (typeof current !== "object" || current === null)
9615
+ return false;
9616
+ const key = keys[i2];
9617
+ if (!Object.prototype.hasOwnProperty.call(current, key))
9618
+ return false;
9619
+ current = current[key];
9620
+ }
9621
+ return true;
9622
+ }
9623
+ function deleteByPath(obj, path2) {
9624
+ const keys = path2.split(".");
9625
+ if (keys.length === 0)
9626
+ return obj;
9627
+ const result = { ...obj };
9628
+ let current = result;
9629
+ for (let i2 = 0;i2 < keys.length - 1; i2++) {
9630
+ const key = keys[i2];
9631
+ if (typeof current[key] !== "object" || current[key] === null) {
9632
+ return obj;
9633
+ }
9634
+ current[key] = { ...current[key] };
9635
+ current = current[key];
9636
+ }
9637
+ const lastKey = keys[keys.length - 1];
9638
+ delete current[lastKey];
9639
+ return result;
9640
+ }
9641
+
9642
+ class SecretsEngineEncryptor {
9643
+ #keyName;
9644
+ #derivedKey = null;
9645
+ #secrets = null;
9646
+ constructor(keyName) {
9647
+ this.#keyName = keyName;
9648
+ }
9649
+ async init() {
9650
+ let SecretsEngine2;
9651
+ try {
9652
+ const mod = await Promise.resolve().then(() => (init_dist(), exports_dist));
9653
+ SecretsEngine2 = mod.SecretsEngine ?? mod.default?.SecretsEngine ?? mod;
9654
+ } catch {
9655
+ throw new Error('Encryption requires "@wgtechlabs/secrets-engine" as a peer dependency. Install it with: bun add @wgtechlabs/secrets-engine');
9656
+ }
9657
+ this.#secrets = await SecretsEngine2.open();
9658
+ const hasKey = await this.#secrets.has(this.#keyName);
9659
+ if (!hasKey) {
9660
+ const keyBytes = crypto.getRandomValues(new Uint8Array(32));
9661
+ const keyHex2 = Array.from(keyBytes).map((b2) => b2.toString(16).padStart(2, "0")).join("");
9662
+ await this.#secrets.set(this.#keyName, keyHex2);
9663
+ }
9664
+ const keyHex = await this.#secrets.get(this.#keyName);
9665
+ const keyBuffer = hexToUint8Array(keyHex);
9666
+ this.#derivedKey = await crypto.subtle.importKey("raw", keyBuffer.buffer, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
9667
+ }
9668
+ async encrypt(plaintext) {
9669
+ if (!this.#derivedKey)
9670
+ throw new Error("Encryptor not initialized. Call init() first.");
9671
+ const iv = crypto.getRandomValues(new Uint8Array(12));
9672
+ const encoded = new TextEncoder().encode(plaintext);
9673
+ const cipherBuffer = await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv.buffer }, this.#derivedKey, encoded);
9674
+ const ivB64 = uint8ArrayToBase64(iv);
9675
+ const cipherB64 = uint8ArrayToBase64(new Uint8Array(cipherBuffer));
9676
+ return `${ivB64}:${cipherB64}`;
9677
+ }
9678
+ async decrypt(ciphertext) {
9679
+ if (!this.#derivedKey)
9680
+ throw new Error("Encryptor not initialized. Call init() first.");
9681
+ const [ivB64, cipherB64] = ciphertext.split(":");
9682
+ if (!ivB64 || !cipherB64)
9683
+ throw new Error("Invalid encrypted data format.");
9684
+ const iv = base64ToUint8Array(ivB64);
9685
+ const cipherBuffer = base64ToUint8Array(cipherB64);
9686
+ const plainBuffer = await crypto.subtle.decrypt({ name: "AES-GCM", iv: iv.buffer }, this.#derivedKey, cipherBuffer.buffer);
9687
+ return new TextDecoder().decode(plainBuffer);
9688
+ }
9689
+ async close() {
9690
+ if (this.#secrets) {
9691
+ this.#secrets.close();
9692
+ this.#secrets = null;
9693
+ }
9694
+ }
9695
+ }
9696
+ function hexToUint8Array(hex) {
9697
+ const bytes = new Uint8Array(hex.length / 2);
9698
+ for (let i2 = 0;i2 < hex.length; i2 += 2) {
9699
+ bytes[i2 / 2] = Number.parseInt(hex.substring(i2, i2 + 2), 16);
9700
+ }
9701
+ return bytes;
9702
+ }
9703
+ function uint8ArrayToBase64(bytes) {
9704
+ if (typeof Buffer !== "undefined") {
9705
+ return Buffer.from(bytes).toString("base64");
9706
+ }
9707
+ let binary = "";
9708
+ for (const byte of bytes) {
9709
+ binary += String.fromCharCode(byte);
9710
+ }
9711
+ return btoa(binary);
9712
+ }
9713
+ function base64ToUint8Array(b64) {
9714
+ if (typeof Buffer !== "undefined") {
9715
+ return new Uint8Array(Buffer.from(b64, "base64"));
9716
+ }
9717
+ const binary = atob(b64);
9718
+ const bytes = new Uint8Array(binary.length);
9719
+ for (let i2 = 0;i2 < binary.length; i2++) {
9720
+ bytes[i2] = binary.charCodeAt(i2);
9721
+ }
9722
+ return bytes;
9723
+ }
9724
+ async function resolveEncryptor(encryptionKey) {
9725
+ if (!encryptionKey)
9726
+ return;
9727
+ if (typeof encryptionKey === "string") {
9728
+ const enc = new SecretsEngineEncryptor(encryptionKey);
9729
+ await enc.init();
9730
+ return enc;
9731
+ }
9732
+ return encryptionKey;
9733
+ }
9734
+ var META_KEY = "schema_version";
9735
+ var INITIAL_VERSION = "0.0.0";
9736
+ async function runMigrations(options) {
9737
+ const { store, migrations, projectVersion, beforeEachMigration, afterEachMigration } = options;
9738
+ if (migrations.length === 0)
9739
+ return;
9740
+ const currentVersion = store.getMeta(META_KEY) ?? INITIAL_VERSION;
9741
+ const versions = migrations.map((m2) => m2.version);
9742
+ const finalVersion = projectVersion;
9743
+ const pending = migrations.filter((m2) => compareVersions(m2.version, currentVersion) > 0);
9744
+ if (pending.length === 0) {
9745
+ store.setMeta(META_KEY, projectVersion);
9746
+ return;
9747
+ }
9748
+ pending.sort((a2, b2) => compareVersions(a2.version, b2.version));
9749
+ const ctx = createMigrationContext(store);
9750
+ const snapshot = store.getAll();
9751
+ const snapshotVersion = currentVersion;
9752
+ try {
9753
+ for (const migration of pending) {
9754
+ const hookCtx = {
9755
+ fromVersion: currentVersion,
9756
+ toVersion: migration.version,
9757
+ finalVersion,
9758
+ versions
9759
+ };
9760
+ if (beforeEachMigration) {
9761
+ await beforeEachMigration(hookCtx);
9762
+ }
9763
+ await migration.up(ctx);
9764
+ if (afterEachMigration) {
9765
+ await afterEachMigration(hookCtx);
9766
+ }
9767
+ }
9768
+ store.setMeta(META_KEY, projectVersion);
9769
+ store.touchLastWrite();
9770
+ } catch (error) {
9771
+ store.transaction(() => {
9772
+ store.deleteAll();
9773
+ const entries = Object.entries(snapshot);
9774
+ if (entries.length > 0) {
9775
+ store.setMany(entries);
9776
+ }
9777
+ store.setMeta(META_KEY, snapshotVersion);
9778
+ });
9779
+ throw new Error(`Migration to version "${pending.find(() => true)?.version}" failed. All changes have been rolled back. Original error: ${error instanceof Error ? error.message : String(error)}`);
9780
+ }
9781
+ }
9782
+ function createMigrationContext(store) {
9783
+ return {
9784
+ get(key) {
9785
+ return store.getOne(key);
9786
+ },
9787
+ set(key, value) {
9788
+ store.setOne(key, value);
9789
+ },
9790
+ has(key) {
9791
+ return store.has(key);
9792
+ },
9793
+ delete(key) {
9794
+ return store.deleteOne(key);
9795
+ }
9796
+ };
9797
+ }
9798
+ function compareVersions(a2, b2) {
9799
+ const partsA = a2.split(".").map(Number);
9800
+ const partsB = b2.split(".").map(Number);
9801
+ for (let i2 = 0;i2 < 3; i2++) {
9802
+ const va = partsA[i2] ?? 0;
9803
+ const vb = partsB[i2] ?? 0;
9804
+ if (va !== vb)
9805
+ return va - vb;
9806
+ }
9807
+ return 0;
9808
+ }
9809
+ function resolveConfigDir(projectName) {
9810
+ const home = homedir2();
9811
+ const os2 = platform2();
9812
+ switch (os2) {
9813
+ case "darwin":
9814
+ return join6(home, "Library", "Preferences", projectName);
9815
+ case "win32":
9816
+ return join6(process.env.APPDATA ?? join6(home, "AppData", "Roaming"), projectName);
9817
+ default:
9818
+ return join6(process.env.XDG_CONFIG_HOME ?? join6(home, ".config"), projectName);
9819
+ }
9820
+ }
9821
+ function resolveConfigPath(options) {
9822
+ const dir = options.cwd ?? resolveConfigDir(options.projectName);
9823
+ const name = options.configName ?? "config";
9824
+ return join6(dir, `${name}.db`);
9825
+ }
9826
+ function isBun() {
9827
+ return typeof globalThis.Bun !== "undefined";
9828
+ }
9829
+ function openDatabase(filepath) {
9830
+ if (isBun()) {
9831
+ return openBunDatabase(filepath);
9832
+ }
9833
+ return openNodeDatabase(filepath);
9834
+ }
9835
+ function openBunDatabase(filepath) {
9836
+ const { Database: Database2 } = __require2("bun:sqlite");
9837
+ const db = new Database2(filepath);
9838
+ db.exec("PRAGMA journal_mode = WAL;");
9839
+ db.exec("PRAGMA busy_timeout = 5000;");
9840
+ return {
9841
+ prepare(sql) {
9842
+ const stmt = db.prepare(sql);
9843
+ return {
9844
+ run(...params) {
9845
+ stmt.run(...params);
9846
+ },
9847
+ get(...params) {
9848
+ return stmt.get(...params);
9849
+ },
9850
+ all(...params) {
9851
+ return stmt.all(...params);
9852
+ }
9853
+ };
9854
+ },
9855
+ exec(sql) {
9856
+ db.exec(sql);
9857
+ },
9858
+ close() {
9859
+ db.close();
9860
+ },
9861
+ transaction(fn) {
9862
+ return db.transaction(fn);
9863
+ }
9864
+ };
9865
+ }
9866
+ function openNodeDatabase(filepath) {
9867
+ let BetterSqlite3;
9868
+ try {
9869
+ BetterSqlite3 = __require2("better-sqlite3");
9870
+ } catch {
9871
+ throw new Error('config-engine requires "better-sqlite3" as a peer dependency when running on Node.js. Install it with: npm install better-sqlite3');
9872
+ }
9873
+ const db = new BetterSqlite3(filepath);
9874
+ db.pragma("journal_mode = WAL");
9875
+ db.pragma("busy_timeout = 5000");
9876
+ return {
9877
+ prepare(sql) {
9878
+ const stmt = db.prepare(sql);
9879
+ return {
9880
+ run(...params) {
9881
+ stmt.run(...params);
9882
+ },
9883
+ get(...params) {
9884
+ return stmt.get(...params);
9885
+ },
9886
+ all(...params) {
9887
+ return stmt.all(...params);
9888
+ }
9889
+ };
9890
+ },
9891
+ exec(sql) {
9892
+ db.exec(sql);
9893
+ },
9894
+ close() {
9895
+ db.close();
9896
+ },
9897
+ transaction(fn) {
9898
+ return db.transaction(fn);
9899
+ }
9900
+ };
9901
+ }
9902
+
9903
+ class ConfigStore {
9904
+ #db;
9905
+ constructor(db) {
9906
+ this.#db = db;
9907
+ this.#initialize();
9908
+ }
9909
+ #initialize() {
9910
+ this.#db.exec(`
9911
+ CREATE TABLE IF NOT EXISTS config (
9912
+ key TEXT PRIMARY KEY,
9913
+ value TEXT NOT NULL
9914
+ );
9915
+ `);
9916
+ this.#db.exec(`
9917
+ CREATE TABLE IF NOT EXISTS _meta (
9918
+ key TEXT PRIMARY KEY,
9919
+ value TEXT NOT NULL
9920
+ );
9921
+ `);
9922
+ }
9923
+ getAll() {
9924
+ const rows = this.#db.prepare("SELECT key, value FROM config").all();
9925
+ const result = Object.create(null);
9926
+ for (const row of rows) {
9927
+ result[row.key] = JSON.parse(row.value);
9928
+ }
9929
+ return result;
9930
+ }
9931
+ getOne(key) {
9932
+ const row = this.#db.prepare("SELECT value FROM config WHERE key = ?").get(key);
9933
+ return row ? JSON.parse(row.value) : undefined;
9934
+ }
9935
+ setOne(key, value) {
9936
+ this.#db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
9937
+ }
9938
+ setMany(entries) {
9939
+ const stmt = this.#db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)");
9940
+ const runTransaction = this.#db.transaction(() => {
9941
+ for (const [key, value] of entries) {
9942
+ stmt.run(key, JSON.stringify(value));
9943
+ }
9944
+ });
9945
+ runTransaction();
9946
+ }
9947
+ deleteOne(key) {
9948
+ const before = this.count();
9949
+ this.#db.prepare("DELETE FROM config WHERE key = ?").run(key);
9950
+ return this.count() < before;
9951
+ }
9952
+ deleteAll() {
9953
+ this.#db.exec("DELETE FROM config;");
9954
+ }
9955
+ has(key) {
9956
+ const row = this.#db.prepare("SELECT 1 FROM config WHERE key = ? LIMIT 1").get(key);
9957
+ return row !== undefined && row !== null;
9958
+ }
9959
+ count() {
9960
+ const row = this.#db.prepare("SELECT COUNT(*) as cnt FROM config").get();
9961
+ return row.cnt;
9962
+ }
9963
+ getMeta(key) {
9964
+ const row = this.#db.prepare("SELECT value FROM _meta WHERE key = ?").get(key);
9965
+ return row?.value;
9966
+ }
9967
+ setMeta(key, value) {
9968
+ this.#db.prepare("INSERT OR REPLACE INTO _meta (key, value) VALUES (?, ?)").run(key, value);
9969
+ }
9970
+ hasMeta(key) {
9971
+ const row = this.#db.prepare("SELECT 1 FROM _meta WHERE key = ? LIMIT 1").get(key);
9972
+ return row !== undefined && row !== null;
9973
+ }
9974
+ transaction(fn) {
9975
+ const wrapped = this.#db.transaction(fn);
9976
+ return wrapped();
9977
+ }
9978
+ touchLastWrite() {
9979
+ this.setMeta("last_write", Date.now().toString());
9980
+ }
9981
+ getLastWrite() {
9982
+ const val = this.getMeta("last_write");
9983
+ return val ? Number(val) : 0;
9984
+ }
9985
+ close() {
9986
+ this.#db.close();
9987
+ }
9988
+ }
9989
+
9990
+ class ConfigEngineError extends Error {
9991
+ name = "ConfigEngineError";
9992
+ }
9993
+
9994
+ class ValidationError extends ConfigEngineError {
9995
+ name = "ValidationError";
9996
+ errors;
9997
+ constructor(errors) {
9998
+ super(`Config validation failed:
9999
+ - ${errors.join(`
10000
+ - `)}`);
10001
+ this.errors = errors;
10002
+ }
10003
+ }
10004
+
10005
+ class ConfigEngine {
10006
+ #store;
10007
+ #cache;
10008
+ #validator;
10009
+ #encryptor;
10010
+ #defaults;
10011
+ #dotNotation;
10012
+ #filePath;
10013
+ #listeners = new Map;
10014
+ #anyListeners = new Set;
10015
+ #watcher = null;
10016
+ #lastKnownWrite = 0;
10017
+ #closed = false;
10018
+ constructor(store, cache, options) {
10019
+ this.#store = store;
10020
+ this.#cache = cache;
10021
+ this.#validator = options.validator;
10022
+ this.#encryptor = options.encryptor;
10023
+ this.#defaults = options.defaults;
10024
+ this.#dotNotation = options.dotNotation;
10025
+ this.#filePath = options.filePath;
10026
+ this.#lastKnownWrite = store.getLastWrite();
10027
+ if (options.watch) {
10028
+ this.#startWatching();
10029
+ }
10030
+ }
10031
+ static async open(options) {
10032
+ const {
10033
+ projectName,
10034
+ projectVersion,
10035
+ cwd,
10036
+ configName,
10037
+ defaults = {},
10038
+ encryptionKey,
10039
+ migrations,
10040
+ beforeEachMigration,
10041
+ afterEachMigration,
10042
+ flushStrategy = "batched",
10043
+ accessPropertiesByDotNotation = true,
10044
+ watch: enableWatch = false,
10045
+ clearInvalidConfig = false
10046
+ } = options;
10047
+ const filePath = resolveConfigPath({ projectName, cwd, configName });
10048
+ mkdirSync3(dirname3(filePath), { recursive: true });
10049
+ const db = openDatabase(filePath);
10050
+ const store = new ConfigStore(db);
10051
+ const encryptor = await resolveEncryptor(encryptionKey);
10052
+ const validator = resolveValidator(options);
10053
+ let storedData;
10054
+ try {
10055
+ storedData = store.getAll();
10056
+ if (encryptor) {
10057
+ storedData = await decryptStore(storedData, encryptor);
10058
+ }
10059
+ } catch (error) {
10060
+ if (clearInvalidConfig) {
10061
+ store.deleteAll();
10062
+ storedData = {};
10063
+ } else {
10064
+ store.close();
10065
+ throw new ConfigEngineError(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
10066
+ }
10067
+ }
10068
+ const merged = { ...defaults, ...storedData };
10069
+ if (validator) {
10070
+ const result = validator.validate(merged);
10071
+ if (!result.success) {
10072
+ if (clearInvalidConfig) {
10073
+ store.deleteAll();
10074
+ const freshData = { ...defaults };
10075
+ const freshResult = validator.validate(freshData);
10076
+ if (!freshResult.success) {
10077
+ store.close();
10078
+ throw new ValidationError(freshResult.errors);
10079
+ }
10080
+ const entries = Object.entries(freshData);
10081
+ if (encryptor) {
10082
+ const encrypted = await encryptStore(freshData, encryptor);
10083
+ store.setMany(Object.entries(encrypted));
10084
+ } else {
10085
+ store.setMany(entries);
10086
+ }
10087
+ store.touchLastWrite();
10088
+ const cache2 = new ConfigCache(store, flushStrategy);
10089
+ cache2.load(freshData);
10090
+ return new ConfigEngine(store, cache2, {
10091
+ validator,
10092
+ encryptor,
10093
+ defaults,
10094
+ dotNotation: accessPropertiesByDotNotation,
10095
+ filePath,
10096
+ watch: enableWatch
10097
+ });
10098
+ }
10099
+ store.close();
10100
+ throw new ValidationError(result.errors);
10101
+ }
10102
+ }
10103
+ const hasNewDefaults = Object.keys(defaults).some((key) => !(key in storedData));
10104
+ if (hasNewDefaults) {
10105
+ if (encryptor) {
10106
+ const encrypted = await encryptStore(merged, encryptor);
10107
+ store.setMany(Object.entries(encrypted));
10108
+ } else {
10109
+ store.setMany(Object.entries(merged));
10110
+ }
10111
+ store.touchLastWrite();
10112
+ }
10113
+ if (migrations && migrations.length > 0) {
10114
+ if (!projectVersion) {
10115
+ store.close();
10116
+ throw new ConfigEngineError('"projectVersion" is required when "migrations" is provided.');
10117
+ }
10118
+ await runMigrations({
10119
+ store,
10120
+ migrations,
10121
+ projectVersion,
10122
+ beforeEachMigration,
10123
+ afterEachMigration
10124
+ });
10125
+ }
10126
+ const cache = new ConfigCache(store, flushStrategy);
10127
+ let finalData;
10128
+ if (encryptor) {
10129
+ finalData = await decryptStore(store.getAll(), encryptor);
10130
+ } else {
10131
+ finalData = store.getAll();
10132
+ }
10133
+ const cacheData = { ...defaults, ...finalData };
10134
+ cache.load(cacheData);
10135
+ return new ConfigEngine(store, cache, {
10136
+ validator,
10137
+ encryptor,
10138
+ defaults,
10139
+ dotNotation: accessPropertiesByDotNotation,
10140
+ filePath,
10141
+ watch: enableWatch
10142
+ });
10143
+ }
10144
+ get(key, defaultValue) {
10145
+ this.#ensureOpen();
10146
+ if (this.#dotNotation && key.includes(".")) {
10147
+ const full = this.#cache.getAll();
10148
+ const value2 = getByPath(full, key);
10149
+ return value2 !== undefined ? value2 : defaultValue;
10150
+ }
10151
+ const value = this.#cache.get(key);
10152
+ return value !== undefined ? value : defaultValue;
10153
+ }
10154
+ has(key) {
10155
+ this.#ensureOpen();
10156
+ if (this.#dotNotation && key.includes(".")) {
10157
+ return hasByPath(this.#cache.getAll(), key);
10158
+ }
10159
+ return this.#cache.has(key);
10160
+ }
10161
+ get store() {
10162
+ this.#ensureOpen();
10163
+ return this.#cache.getAll();
10164
+ }
10165
+ set store(value) {
10166
+ this.#ensureOpen();
10167
+ this.#validateFull(value);
10168
+ const oldStore = this.#cache.getAll();
10169
+ this.#cache.replaceAll(value);
10170
+ this.#notifyAnyChange(value, oldStore);
10171
+ }
10172
+ get size() {
10173
+ this.#ensureOpen();
10174
+ return this.#cache.size;
10175
+ }
10176
+ get path() {
10177
+ return this.#filePath;
10178
+ }
10179
+ set(keyOrObject, value) {
10180
+ this.#ensureOpen();
10181
+ if (typeof keyOrObject === "string") {
10182
+ this.#setKey(keyOrObject, value);
10183
+ } else {
10184
+ const entries = Object.entries(keyOrObject);
10185
+ const oldStore = this.#cache.getAll();
10186
+ for (const [key, val] of entries) {
10187
+ if (this.#dotNotation && key.includes(".")) {
10188
+ const full = this.#cache.getAll();
10189
+ const updated = setByPath(full, key, val);
10190
+ this.#validateFull(updated);
10191
+ this.#cache.replaceAll(updated);
10192
+ } else {
10193
+ this.#cache.set(key, val);
10194
+ }
10195
+ }
10196
+ this.#validateFull(this.#cache.getAll());
10197
+ this.#notifyAnyChange(this.#cache.getAll(), oldStore);
10198
+ }
10199
+ }
10200
+ #setKey(key, value) {
10201
+ const oldStore = this.#cache.getAll();
10202
+ const oldValue = this.get(key);
10203
+ if (this.#dotNotation && key.includes(".")) {
10204
+ const full = this.#cache.getAll();
10205
+ const updated = setByPath(full, key, value);
10206
+ this.#validateFull(updated);
10207
+ this.#cache.replaceAll(updated);
10208
+ } else {
10209
+ const testStore = { ...this.#cache.getAll(), [key]: value };
10210
+ this.#validateFull(testStore);
10211
+ this.#cache.set(key, value);
10212
+ }
10213
+ this.#notifyKeyChange(key, value, oldValue);
10214
+ this.#notifyAnyChange(this.#cache.getAll(), oldStore);
10215
+ }
10216
+ delete(key) {
10217
+ this.#ensureOpen();
10218
+ const oldStore = this.#cache.getAll();
10219
+ const oldValue = this.get(key);
10220
+ if (this.#dotNotation && key.includes(".")) {
10221
+ const full = this.#cache.getAll();
10222
+ const updated = deleteByPath(full, key);
10223
+ this.#cache.replaceAll(updated);
10224
+ } else {
10225
+ this.#cache.delete(key);
10226
+ }
10227
+ this.#notifyKeyChange(key, undefined, oldValue);
10228
+ this.#notifyAnyChange(this.#cache.getAll(), oldStore);
10229
+ }
10230
+ clear() {
10231
+ this.#ensureOpen();
10232
+ const oldStore = this.#cache.getAll();
10233
+ this.#cache.clear();
10234
+ if (this.#defaults && Object.keys(this.#defaults).length > 0) {
10235
+ this.#cache.setMany(Object.entries(this.#defaults));
10236
+ }
10237
+ this.#notifyAnyChange(this.#cache.getAll(), oldStore);
10238
+ }
10239
+ reset(...keys) {
10240
+ this.#ensureOpen();
10241
+ const oldStore = this.#cache.getAll();
10242
+ for (const key of keys) {
10243
+ if (key in this.#defaults) {
10244
+ this.#cache.set(key, this.#defaults[key]);
10245
+ } else {
10246
+ this.#cache.delete(key);
10247
+ }
10248
+ }
10249
+ this.#notifyAnyChange(this.#cache.getAll(), oldStore);
10250
+ }
10251
+ onDidChange(key, callback) {
10252
+ if (!this.#listeners.has(key)) {
10253
+ this.#listeners.set(key, new Set);
10254
+ }
10255
+ const set = this.#listeners.get(key);
10256
+ set.add(callback);
10257
+ return () => {
10258
+ set.delete(callback);
10259
+ if (set.size === 0)
10260
+ this.#listeners.delete(key);
10261
+ };
10262
+ }
10263
+ onDidAnyChange(callback) {
10264
+ this.#anyListeners.add(callback);
10265
+ return () => {
10266
+ this.#anyListeners.delete(callback);
10267
+ };
10268
+ }
10269
+ async flush() {
10270
+ this.#ensureOpen();
10271
+ await this.#cache.flush();
10272
+ }
10273
+ flushSync() {
10274
+ this.#ensureOpen();
10275
+ this.#cache.flushSync();
10276
+ }
10277
+ close() {
10278
+ if (this.#closed)
10279
+ return;
10280
+ this.#stopWatching();
10281
+ this.#cache.flushSync();
10282
+ this.#store.close();
10283
+ this.#closed = true;
10284
+ }
10285
+ *[Symbol.iterator]() {
10286
+ this.#ensureOpen();
10287
+ yield* this.#cache.entries();
10288
+ }
10289
+ #ensureOpen() {
10290
+ if (this.#closed) {
10291
+ throw new ConfigEngineError("This ConfigEngine instance has been closed. Create a new one with ConfigEngine.open().");
10292
+ }
10293
+ }
10294
+ #validateFull(data) {
10295
+ if (!this.#validator)
10296
+ return;
10297
+ const result = this.#validator.validate(data);
10298
+ if (!result.success) {
10299
+ throw new ValidationError(result.errors);
10300
+ }
10301
+ }
10302
+ #notifyKeyChange(key, newValue, oldValue) {
10303
+ if (newValue === oldValue)
10304
+ return;
10305
+ if (typeof newValue === "object" && typeof oldValue === "object" && JSON.stringify(newValue) === JSON.stringify(oldValue)) {
10306
+ return;
10307
+ }
10308
+ const listeners = this.#listeners.get(key);
10309
+ if (listeners) {
10310
+ for (const cb of listeners) {
10311
+ cb(newValue, oldValue);
10312
+ }
10313
+ }
10314
+ }
10315
+ #notifyAnyChange(newStore, oldStore) {
10316
+ if (JSON.stringify(newStore) === JSON.stringify(oldStore))
10317
+ return;
10318
+ for (const cb of this.#anyListeners) {
10319
+ cb(newStore, oldStore);
10320
+ }
10321
+ }
10322
+ #startWatching() {
10323
+ try {
10324
+ this.#watcher = watch(dirname3(this.#filePath), (_event, filename) => {
10325
+ if (!filename)
10326
+ return;
10327
+ const expectedName = this.#filePath.split(/[\\/]/).pop();
10328
+ if (filename !== expectedName)
10329
+ return;
10330
+ const currentWrite = this.#store.getLastWrite();
10331
+ if (currentWrite > this.#lastKnownWrite) {
10332
+ this.#lastKnownWrite = currentWrite;
10333
+ const oldStore = this.#cache.getAll();
10334
+ this.#cache.reload();
10335
+ const newStore = this.#cache.getAll();
10336
+ this.#notifyAnyChange(newStore, oldStore);
10337
+ }
10338
+ });
10339
+ if (this.#watcher && "unref" in this.#watcher) {
10340
+ this.#watcher.unref();
10341
+ }
10342
+ } catch {}
10343
+ }
10344
+ #stopWatching() {
10345
+ if (this.#watcher) {
10346
+ this.#watcher.close();
10347
+ this.#watcher = null;
10348
+ }
10349
+ }
10350
+ }
10351
+ async function encryptStore(data, encryptor) {
10352
+ const result = {};
10353
+ for (const [key, value] of Object.entries(data)) {
10354
+ result[key] = await encryptor.encrypt(JSON.stringify(value));
10355
+ }
10356
+ return result;
10357
+ }
10358
+ async function decryptStore(data, encryptor) {
10359
+ const result = {};
10360
+ for (const [key, value] of Object.entries(data)) {
10361
+ if (typeof value === "string") {
10362
+ try {
10363
+ const decrypted = await encryptor.decrypt(value);
10364
+ result[key] = JSON.parse(decrypted);
10365
+ } catch {
10366
+ result[key] = value;
10367
+ }
10368
+ } else {
10369
+ result[key] = value;
10370
+ }
10371
+ }
10372
+ return result;
10373
+ }
10374
+
10375
+ // src/utils/state.ts
8831
10376
  var LOCAL_STATE_DIRNAME = "contribute-now";
8832
10377
  var LOCAL_STATE_CONFIG_NAME = "state";
8833
10378
  var LOCAL_STATE_LOCATION_LABEL = `.git/${LOCAL_STATE_DIRNAME}/${LOCAL_STATE_CONFIG_NAME}.db`;
8834
- var DEFAULT_LOCAL_STATE = {
8835
- guideRotation: {}
8836
- };
10379
+ var stateStoreCache = new Map;
8837
10380
  function findRepoRoot2(cwd = process.cwd()) {
8838
10381
  let current = resolve3(cwd);
8839
10382
  while (true) {
8840
- if (existsSync3(join3(current, ".git"))) {
10383
+ if (existsSync3(join7(current, ".git"))) {
8841
10384
  return current;
8842
10385
  }
8843
- const parent = dirname3(current);
10386
+ const parent = dirname4(current);
8844
10387
  if (parent === current) {
8845
10388
  return null;
8846
10389
  }
@@ -8852,13 +10395,13 @@ function resolveGitDir2(cwd = process.cwd()) {
8852
10395
  if (!repoRoot) {
8853
10396
  return null;
8854
10397
  }
8855
- const dotGitPath = join3(repoRoot, ".git");
10398
+ const dotGitPath = join7(repoRoot, ".git");
8856
10399
  try {
8857
- const stat = statSync3(dotGitPath);
8858
- if (stat.isDirectory()) {
10400
+ const stat2 = statSync3(dotGitPath);
10401
+ if (stat2.isDirectory()) {
8859
10402
  return dotGitPath;
8860
10403
  }
8861
- if (!stat.isFile()) {
10404
+ if (!stat2.isFile()) {
8862
10405
  return null;
8863
10406
  }
8864
10407
  const content = readFileSync3(dotGitPath, "utf-8").trim();
@@ -8876,41 +10419,40 @@ function getLocalStateDir(cwd = process.cwd()) {
8876
10419
  if (!gitDir) {
8877
10420
  return null;
8878
10421
  }
8879
- return join3(gitDir, LOCAL_STATE_DIRNAME);
10422
+ return join7(gitDir, LOCAL_STATE_DIRNAME);
8880
10423
  }
8881
- function readLocalState(cwd = process.cwd()) {
8882
- const statePath = getLocalStatePath(cwd);
8883
- if (!statePath || !existsSync3(statePath)) {
8884
- return DEFAULT_LOCAL_STATE;
8885
- }
8886
- try {
8887
- const raw = JSON.parse(readFileSync3(statePath, "utf-8"));
8888
- const guideRotation = raw.guideRotation && typeof raw.guideRotation === "object" ? raw.guideRotation : {};
8889
- return {
8890
- guideRotation: Object.fromEntries(Object.entries(guideRotation).filter((entry) => {
8891
- const [key, value] = entry;
8892
- return typeof key === "string" && typeof value === "number" && Number.isFinite(value);
8893
- }))
8894
- };
8895
- } catch {
8896
- return DEFAULT_LOCAL_STATE;
10424
+ async function openLocalStateStore(cwd = process.cwd()) {
10425
+ const stateDir = getLocalStateDir(cwd);
10426
+ if (!stateDir) {
10427
+ return null;
8897
10428
  }
10429
+ return ConfigEngine.open({
10430
+ projectName: "contribute-now",
10431
+ cwd: stateDir,
10432
+ configName: LOCAL_STATE_CONFIG_NAME,
10433
+ defaults: {
10434
+ guideRotation: {}
10435
+ },
10436
+ flushStrategy: "immediate"
10437
+ });
8898
10438
  }
8899
- function writeLocalState(state, cwd = process.cwd()) {
10439
+ async function getLocalStateStore(cwd = process.cwd()) {
8900
10440
  const statePath = getLocalStatePath(cwd);
8901
- if (!statePath) {
8902
- return;
10441
+ const cacheKey = statePath ?? resolve3(cwd);
10442
+ const existing = stateStoreCache.get(cacheKey);
10443
+ if (existing) {
10444
+ return existing;
8903
10445
  }
8904
- mkdirSync3(dirname3(statePath), { recursive: true });
8905
- writeFileSync3(statePath, `${JSON.stringify(state, null, 2)}
8906
- `, "utf-8");
10446
+ const storePromise = openLocalStateStore(cwd).catch(() => null);
10447
+ stateStoreCache.set(cacheKey, storePromise);
10448
+ return storePromise;
8907
10449
  }
8908
10450
  function getLocalStatePath(cwd = process.cwd()) {
8909
10451
  const stateDir = getLocalStateDir(cwd);
8910
10452
  if (!stateDir) {
8911
10453
  return null;
8912
10454
  }
8913
- return join3(stateDir, `${LOCAL_STATE_CONFIG_NAME}.db`);
10455
+ return join7(stateDir, `${LOCAL_STATE_CONFIG_NAME}.db`);
8914
10456
  }
8915
10457
  function getLocalStateLocationLabel(cwd = process.cwd()) {
8916
10458
  return getLocalStatePath(cwd) ? LOCAL_STATE_LOCATION_LABEL : null;
@@ -8920,23 +10462,25 @@ function hasLocalStateStore(cwd = process.cwd()) {
8920
10462
  return !!statePath && existsSync3(statePath);
8921
10463
  }
8922
10464
  async function getGuideRotationIndex(commandKey, cwd = process.cwd()) {
8923
- const rotationIndex = readLocalState(cwd).guideRotation[commandKey];
10465
+ const store = await getLocalStateStore(cwd);
10466
+ if (!store) {
10467
+ return 0;
10468
+ }
10469
+ const rotationIndex = store.get(`guideRotation.${commandKey}`, 0);
8924
10470
  return typeof rotationIndex === "number" ? rotationIndex : 0;
8925
10471
  }
8926
10472
  async function advanceGuideRotation(commandKey, rotatableCount, cwd = process.cwd()) {
8927
10473
  if (rotatableCount <= 0) {
8928
10474
  return;
8929
10475
  }
8930
- const state = readLocalState(cwd);
8931
- const rotationIndex = state.guideRotation[commandKey];
10476
+ const store = await getLocalStateStore(cwd);
10477
+ if (!store) {
10478
+ return;
10479
+ }
10480
+ const rotationIndex = store.get(`guideRotation.${commandKey}`, 0);
8932
10481
  const currentIndex = typeof rotationIndex === "number" ? rotationIndex : 0;
8933
10482
  const nextIndex = (currentIndex + 1) % rotatableCount;
8934
- writeLocalState({
8935
- guideRotation: {
8936
- ...state.guideRotation,
8937
- [commandKey]: nextIndex
8938
- }
8939
- }, cwd);
10483
+ store.set(`guideRotation.${commandKey}`, nextIndex);
8940
10484
  }
8941
10485
 
8942
10486
  // src/utils/tips.ts
@@ -10612,79 +12156,54 @@ async function multiSelectPrompt(message, choices) {
10612
12156
  }
10613
12157
 
10614
12158
  // src/utils/copilot.ts
10615
- init_dist();
12159
+ init_dist2();
10616
12160
 
10617
12161
  // src/utils/secrets.ts
10618
- import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
10619
- import { homedir } from "node:os";
10620
- import { join as join5, resolve as resolve4 } from "node:path";
12162
+ init_dist();
12163
+ import { existsSync as existsSync5 } from "node:fs";
12164
+ import { homedir as homedir3 } from "node:os";
12165
+ import { resolve as resolve4 } from "node:path";
10621
12166
  var CONTRIBUTE_NOW_SECRETS_DIRNAME = ".contribute-now";
10622
12167
  var CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME = "secrets";
10623
12168
  var OLLAMA_CLOUD_API_KEY = "ollama.cloud.apiKey";
10624
- function getSecretsStorePath(baseDir = homedir()) {
12169
+ var secretsStoreCache = new Map;
12170
+ function getSecretsStorePath(baseDir = homedir3()) {
10625
12171
  return resolve4(baseDir, CONTRIBUTE_NOW_SECRETS_DIRNAME, CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME);
10626
12172
  }
10627
- function getSecretsFilePath(baseDir = homedir()) {
10628
- return join5(getSecretsStorePath(baseDir), "store.json");
10629
- }
10630
- function readSecretsStore(baseDir = homedir()) {
10631
- const filePath = getSecretsFilePath(baseDir);
10632
- if (!existsSync5(filePath)) {
10633
- return null;
12173
+ async function getSecretsStore(baseDir = homedir3(), createIfMissing = true) {
12174
+ const storePath = getSecretsStorePath(baseDir);
12175
+ const existing = secretsStoreCache.get(storePath);
12176
+ if (existing) {
12177
+ return existing;
10634
12178
  }
10635
- try {
10636
- const parsed = JSON.parse(readFileSync4(filePath, "utf-8"));
10637
- return typeof parsed === "object" && parsed !== null ? parsed : null;
10638
- } catch {
12179
+ if (!createIfMissing && !existsSync5(storePath)) {
10639
12180
  return null;
10640
12181
  }
12182
+ const storePromise = SecretsEngine.open({ path: storePath });
12183
+ secretsStoreCache.set(storePath, storePromise);
12184
+ return storePromise;
10641
12185
  }
10642
- function writeSecretsStore(store, baseDir = homedir()) {
10643
- const storePath = getSecretsStorePath(baseDir);
10644
- const filePath = getSecretsFilePath(baseDir);
10645
- mkdirSync4(storePath, { recursive: true, mode: 448 });
10646
- writeFileSync4(filePath, `${JSON.stringify(store, null, 2)}
10647
- `, {
10648
- encoding: "utf-8",
10649
- mode: 384
10650
- });
10651
- try {
10652
- chmodSync(storePath, 448);
10653
- chmodSync(filePath, 384);
10654
- } catch {}
10655
- }
10656
- function hasSecretsStore(baseDir = homedir()) {
10657
- return existsSync5(getSecretsFilePath(baseDir));
12186
+ function hasSecretsStore(baseDir = homedir3()) {
12187
+ return existsSync5(getSecretsStorePath(baseDir));
10658
12188
  }
10659
- async function hasOllamaCloudApiKey(baseDir = homedir()) {
10660
- return typeof readSecretsStore(baseDir)?.[OLLAMA_CLOUD_API_KEY] === "string";
12189
+ async function hasOllamaCloudApiKey(baseDir = homedir3()) {
12190
+ const store = await getSecretsStore(baseDir, false);
12191
+ return store ? store.has(OLLAMA_CLOUD_API_KEY) : false;
10661
12192
  }
10662
- async function getOllamaCloudApiKey(baseDir = homedir()) {
10663
- return readSecretsStore(baseDir)?.[OLLAMA_CLOUD_API_KEY] ?? null;
12193
+ async function getOllamaCloudApiKey(baseDir = homedir3()) {
12194
+ const store = await getSecretsStore(baseDir, false);
12195
+ return store ? store.get(OLLAMA_CLOUD_API_KEY) : null;
10664
12196
  }
10665
- async function setOllamaCloudApiKey(value, baseDir = homedir()) {
10666
- const existingStore = readSecretsStore(baseDir) ?? {};
10667
- writeSecretsStore({
10668
- ...existingStore,
10669
- [OLLAMA_CLOUD_API_KEY]: value
10670
- }, baseDir);
10671
- }
10672
- async function deleteOllamaCloudApiKey(baseDir = homedir()) {
10673
- const existingStore = readSecretsStore(baseDir);
10674
- if (!existingStore || !(OLLAMA_CLOUD_API_KEY in existingStore)) {
10675
- return false;
12197
+ async function setOllamaCloudApiKey(value, baseDir = homedir3()) {
12198
+ const store = await getSecretsStore(baseDir);
12199
+ if (!store) {
12200
+ throw new Error("Secrets store could not be opened");
10676
12201
  }
10677
- const nextStore = { ...existingStore };
10678
- delete nextStore[OLLAMA_CLOUD_API_KEY];
10679
- if (Object.keys(nextStore).length === 0) {
10680
- try {
10681
- rmSync(getSecretsFilePath(baseDir), { force: true });
10682
- rmSync(getSecretsStorePath(baseDir), { recursive: true, force: true });
10683
- } catch {}
10684
- return true;
10685
- }
10686
- writeSecretsStore(nextStore, baseDir);
10687
- return true;
12202
+ await store.set(OLLAMA_CLOUD_API_KEY, value);
12203
+ }
12204
+ async function deleteOllamaCloudApiKey(baseDir = homedir3()) {
12205
+ const store = await getSecretsStore(baseDir, false);
12206
+ return store ? store.delete(OLLAMA_CLOUD_API_KEY) : false;
10688
12207
  }
10689
12208
 
10690
12209
  // src/utils/copilot.ts
@@ -12670,13 +14189,13 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
12670
14189
  const modelLookupApiKey = ollamaApiKeyAction === "set" ? ollamaApiKey ?? null : ollamaApiKeyAction === "keep" ? await getOllamaCloudApiKey() : null;
12671
14190
  aiModel = await promptForOllamaCloudModelSelection(modelLookupApiKey, current.aiProvider === "ollama-cloud" ? current.aiModel ?? DEFAULT_OLLAMA_CLOUD_MODEL : DEFAULT_OLLAMA_CLOUD_MODEL);
12672
14191
  } else if (hasExistingOllamaApiKey) {
12673
- const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from the local secrets store?");
14192
+ const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from secrets-engine?");
12674
14193
  if (shouldDeleteStoredKey) {
12675
14194
  ollamaApiKeyAction = "delete";
12676
14195
  }
12677
14196
  }
12678
14197
  } else if (hasExistingOllamaApiKey) {
12679
- const shouldDeleteStoredKey = await confirmPrompt("AI is disabled. Delete the stored Ollama Cloud API key from the local secrets store?");
14198
+ const shouldDeleteStoredKey = await confirmPrompt("AI is disabled. Delete the stored Ollama Cloud API key from secrets-engine?");
12680
14199
  if (shouldDeleteStoredKey) {
12681
14200
  ollamaApiKeyAction = "delete";
12682
14201
  }
@@ -12703,7 +14222,7 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
12703
14222
  async function applyOllamaApiKeyEdit(result) {
12704
14223
  if (result.ollamaApiKeyAction === "set" && result.ollamaApiKey) {
12705
14224
  await setOllamaCloudApiKey(result.ollamaApiKey);
12706
- success("Stored Ollama Cloud API key in the local secrets store.");
14225
+ success("Stored Ollama Cloud API key in secrets-engine.");
12707
14226
  info(`Secrets path: ${import_picocolors10.default.bold(getSecretsStorePath())}`);
12708
14227
  return;
12709
14228
  }
@@ -12826,7 +14345,7 @@ var import_picocolors11 = __toESM(require_picocolors(), 1);
12826
14345
  // package.json
12827
14346
  var package_default = {
12828
14347
  name: "contribute-now",
12829
- version: "0.7.0-dev.3b89c66",
14348
+ version: "0.7.0-dev.45c1376",
12830
14349
  description: "Developer CLI that automates git workflows — branching, syncing, committing, and PRs — with multi-workflow and commit convention support.",
12831
14350
  type: "module",
12832
14351
  bin: {
@@ -12874,7 +14393,9 @@ var package_default = {
12874
14393
  dependencies: {
12875
14394
  "@clack/prompts": "^1.0.1",
12876
14395
  "@github/copilot-sdk": "^0.1.25",
14396
+ "@wgtechlabs/config-engine": "^0.1.0",
12877
14397
  "@wgtechlabs/log-engine": "^2.3.1",
14398
+ "@wgtechlabs/secrets-engine": "^2.0.0",
12878
14399
  citty: "^0.1.6",
12879
14400
  figlet: "^1.10.0",
12880
14401
  picocolors: "^1.1.1"
@@ -12991,7 +14512,7 @@ async function depsSection() {
12991
14512
  });
12992
14513
  }
12993
14514
  try {
12994
- await Promise.resolve().then(() => (init_dist(), exports_dist2));
14515
+ await Promise.resolve().then(() => (init_dist2(), exports_dist3));
12995
14516
  checks.push({ label: "Copilot SDK importable", ok: true });
12996
14517
  } catch {
12997
14518
  checks.push({
@@ -13079,7 +14600,7 @@ async function configSection() {
13079
14600
  label: hasApiKey ? "Ollama Cloud API key present" : "Ollama Cloud API key missing",
13080
14601
  ok: true,
13081
14602
  warning: !hasApiKey,
13082
- detail: hasSecretsStore() ? "stored in the local secrets store" : "run `contrib setup` to save it"
14603
+ detail: hasSecretsStore() ? "stored in secrets-engine" : "run `contrib setup` to save it"
13083
14604
  });
13084
14605
  }
13085
14606
  }
@@ -13273,15 +14794,15 @@ var doctor_default = defineCommand({
13273
14794
  });
13274
14795
 
13275
14796
  // src/commands/hook.ts
13276
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
13277
- import { join as join6 } from "node:path";
14797
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
14798
+ import { join as join9 } from "node:path";
13278
14799
  var import_picocolors12 = __toESM(require_picocolors(), 1);
13279
14800
  var HOOK_MARKER = "# managed by contribute-now";
13280
14801
  function getHooksDir(cwd = process.cwd()) {
13281
- return join6(cwd, ".git", "hooks");
14802
+ return join9(cwd, ".git", "hooks");
13282
14803
  }
13283
14804
  function getHookPath(cwd = process.cwd()) {
13284
- return join6(getHooksDir(cwd), "commit-msg");
14805
+ return join9(getHooksDir(cwd), "commit-msg");
13285
14806
  }
13286
14807
  function generateHookScript() {
13287
14808
  return `#!/bin/sh
@@ -13357,7 +14878,7 @@ async function installHook() {
13357
14878
  const hookPath = getHookPath();
13358
14879
  const hooksDir = getHooksDir();
13359
14880
  if (existsSync6(hookPath)) {
13360
- const existing = readFileSync5(hookPath, "utf-8");
14881
+ const existing = readFileSync4(hookPath, "utf-8");
13361
14882
  if (!existing.includes(HOOK_MARKER)) {
13362
14883
  error("A commit-msg hook already exists and was not installed by contribute-now.");
13363
14884
  warn(`Path: ${hookPath}`);
@@ -13367,9 +14888,9 @@ async function installHook() {
13367
14888
  info("Updating existing contribute-now hook...");
13368
14889
  }
13369
14890
  if (!existsSync6(hooksDir)) {
13370
- mkdirSync5(hooksDir, { recursive: true });
14891
+ mkdirSync4(hooksDir, { recursive: true });
13371
14892
  }
13372
- writeFileSync5(hookPath, generateHookScript(), { mode: 493 });
14893
+ writeFileSync3(hookPath, generateHookScript(), { mode: 493 });
13373
14894
  success(`commit-msg hook installed.`);
13374
14895
  info(`Convention: ${import_picocolors12.default.bold(CONVENTION_LABELS[config.commitConvention])}`, "");
13375
14896
  info(`Path: ${import_picocolors12.default.dim(hookPath)}`, "");
@@ -13382,12 +14903,12 @@ async function uninstallHook() {
13382
14903
  info("No commit-msg hook found. Nothing to uninstall.");
13383
14904
  return;
13384
14905
  }
13385
- const content = readFileSync5(hookPath, "utf-8");
14906
+ const content = readFileSync4(hookPath, "utf-8");
13386
14907
  if (!content.includes(HOOK_MARKER)) {
13387
14908
  error("The commit-msg hook was not installed by contribute-now. Leaving it untouched.");
13388
14909
  process.exit(1);
13389
14910
  }
13390
- rmSync2(hookPath);
14911
+ rmSync(hookPath);
13391
14912
  success("commit-msg hook removed.");
13392
14913
  }
13393
14914
 
@@ -14068,7 +15589,7 @@ var setup_default = defineCommand({
14068
15589
  if (enableAI) {
14069
15590
  const providerChoice = await selectPrompt("Which AI provider should this clone use?", [
14070
15591
  "GitHub Copilot — use your existing GitHub/Copilot auth",
14071
- "Ollama Cloud — use an API key stored in the local secrets store"
15592
+ "Ollama Cloud — use an API key stored with secrets-engine"
14072
15593
  ]);
14073
15594
  aiProvider = providerChoice.startsWith("Ollama Cloud") ? "ollama-cloud" : "copilot";
14074
15595
  if (aiProvider === "ollama-cloud") {
@@ -14080,7 +15601,7 @@ var setup_default = defineCommand({
14080
15601
  aiModel = await promptForOllamaCloudModel(apiKey);
14081
15602
  try {
14082
15603
  await setOllamaCloudApiKey(apiKey);
14083
- success("Stored Ollama Cloud API key in the local secrets store.");
15604
+ success("Stored Ollama Cloud API key in secrets-engine.");
14084
15605
  info(`Secrets path: ${import_picocolors15.default.bold(getSecretsStorePath())}`);
14085
15606
  } catch (err) {
14086
15607
  const message = err instanceof Error ? err.message : String(err);
@@ -15359,7 +16880,7 @@ var sync_default = defineCommand({
15359
16880
  });
15360
16881
 
15361
16882
  // src/commands/update.ts
15362
- import { readFileSync as readFileSync6 } from "node:fs";
16883
+ import { readFileSync as readFileSync5 } from "node:fs";
15363
16884
  var import_picocolors21 = __toESM(require_picocolors(), 1);
15364
16885
  function hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, hasUncommittedChanges2) {
15365
16886
  return hasUncommittedChanges2 || uniqueCommitsAheadOfBase > 0;
@@ -15563,7 +17084,7 @@ var update_default = defineCommand({
15563
17084
  let conflictDiff = "";
15564
17085
  for (const file of conflictFiles.slice(0, 3)) {
15565
17086
  try {
15566
- const content = readFileSync6(file, "utf-8");
17087
+ const content = readFileSync5(file, "utf-8");
15567
17088
  if (content.includes("<<<<<<<")) {
15568
17089
  conflictDiff += `
15569
17090
  --- ${file} ---
@@ -15604,7 +17125,7 @@ ${import_picocolors21.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance
15604
17125
  });
15605
17126
 
15606
17127
  // src/commands/validate.ts
15607
- import { readFileSync as readFileSync7 } from "node:fs";
17128
+ import { readFileSync as readFileSync6 } from "node:fs";
15608
17129
  var import_picocolors22 = __toESM(require_picocolors(), 1);
15609
17130
  var validate_default = defineCommand({
15610
17131
  meta: {
@@ -15634,7 +17155,7 @@ var validate_default = defineCommand({
15634
17155
  info('Commit convention is set to "none". All messages are accepted.');
15635
17156
  process.exit(0);
15636
17157
  }
15637
- const message = args.file ? readFileSync7(args.file, "utf-8").split(/\r?\n/, 1)[0] ?? "" : args.message;
17158
+ const message = args.file ? readFileSync6(args.file, "utf-8").split(/\r?\n/, 1)[0] ?? "" : args.message;
15638
17159
  if (!message) {
15639
17160
  error("No commit message provided. Pass a message or use --file <path>.");
15640
17161
  process.exit(1);