@hasna/configs 0.2.36 → 0.2.38

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 CHANGED
@@ -58,8 +58,8 @@ Data is stored in `~/.hasna/configs/`.
58
58
 
59
59
  `configs init` now seeds two platform profiles:
60
60
 
61
- - `linux-arm64` for `spark01` / `spark02`
62
- - `macos-arm64` for `apple01` / `apple03`
61
+ - `linux-arm64` for `linux-node-a` / `linux-node-b`
62
+ - `macos-arm64` for `macos-node-a` / `macos-node-b`
63
63
 
64
64
  These profiles resolve machine variables like `{{WORKSPACE_ROOT}}`, `{{BUN_BIN_DIR}}`, `{{BUN_PATH}}`, and `{{PATH_PREFIX}}`, so synced configs can be portable across Linux and macOS arm64 machines.
65
65
 
package/dist/cli/index.js CHANGED
@@ -12694,7 +12694,6 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
12694
12694
  function getEventsDataDir(override) {
12695
12695
  return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
12696
12696
  }
12697
-
12698
12697
  class JsonEventsStore {
12699
12698
  dataDir;
12700
12699
  channelsPath;
@@ -13342,7 +13341,7 @@ var {
13342
13341
  // src/cli/index.tsx
13343
13342
  init_configs();
13344
13343
  import chalk from "chalk";
13345
- import { existsSync as existsSync12, readFileSync as readFileSync6 } from "fs";
13344
+ import { existsSync as existsSync13, readFileSync as readFileSync7 } from "fs";
13346
13345
  import { homedir as homedir11 } from "os";
13347
13346
  import { join as join13, resolve as resolve4 } from "path";
13348
13347
 
@@ -13600,8 +13599,8 @@ init_configs();
13600
13599
  var PLATFORM_PROFILE_PRESETS = [
13601
13600
  {
13602
13601
  name: "linux-arm64",
13603
- description: "Default Linux arm64 profile for spark01/spark02-style machines",
13604
- selectors: { os: ["linux"], arch: ["arm64"], hostnames: ["spark01", "spark02"] },
13602
+ description: "Default Linux arm64 profile for linux-node-a/linux-node-b-style machines",
13603
+ selectors: { os: ["linux"], arch: ["arm64"], hostnames: ["linux-node-a", "linux-node-b"] },
13605
13604
  variables: {
13606
13605
  WORKSPACE_ROOT: "{{HOME_DIR}}/workspace",
13607
13606
  BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
@@ -13611,8 +13610,8 @@ var PLATFORM_PROFILE_PRESETS = [
13611
13610
  },
13612
13611
  {
13613
13612
  name: "macos-arm64",
13614
- description: "Default macOS arm64 profile for apple01/apple03-style machines",
13615
- selectors: { os: ["macos"], arch: ["arm64"], hostnames: ["apple01", "apple03"] },
13613
+ description: "Default macOS arm64 profile for macos-node-a/macos-node-b-style machines",
13614
+ selectors: { os: ["macos"], arch: ["arm64"], hostnames: ["macos-node-a", "macos-node-b"] },
13616
13615
  variables: {
13617
13616
  WORKSPACE_ROOT: "{{HOME_DIR}}/Workspace",
13618
13617
  BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
@@ -13646,6 +13645,140 @@ function ensurePlatformProfiles(db) {
13646
13645
  return ensured;
13647
13646
  }
13648
13647
 
13648
+ // src/status.ts
13649
+ init_database();
13650
+ init_configs();
13651
+ import { existsSync as existsSync12, readFileSync as readFileSync6 } from "fs";
13652
+
13653
+ // src/db/machines.ts
13654
+ init_database();
13655
+ function listMachines(db) {
13656
+ const d = db || getDatabase();
13657
+ return d.query("SELECT * FROM machines ORDER BY last_applied_at DESC NULLS LAST").all();
13658
+ }
13659
+
13660
+ // src/status.ts
13661
+ init_apply();
13662
+ init_redact();
13663
+ var PACKAGE_NAME = "@hasna/configs";
13664
+ var PACKAGE_VERSION = "0.2.38";
13665
+ function activeDatabaseEnv() {
13666
+ if (process.env["HASNA_CONFIGS_DB_PATH"])
13667
+ return "HASNA_CONFIGS_DB_PATH";
13668
+ if (process.env["CONFIGS_DB_PATH"])
13669
+ return "CONFIGS_DB_PATH";
13670
+ return null;
13671
+ }
13672
+ function configuredDatabaseKind() {
13673
+ const value = process.env["HASNA_CONFIGS_DB_PATH"] ?? process.env["CONFIGS_DB_PATH"] ?? "";
13674
+ return value === ":memory:" || value.startsWith("file::memory:") ? "memory" : "file";
13675
+ }
13676
+ function countBy(items, getValue) {
13677
+ const counts = {};
13678
+ for (const item of items) {
13679
+ const value = getValue(item);
13680
+ if (!value)
13681
+ continue;
13682
+ counts[value] = (counts[value] ?? 0) + 1;
13683
+ }
13684
+ return counts;
13685
+ }
13686
+ function tableCount(db, table) {
13687
+ try {
13688
+ const row = db.query(`SELECT COUNT(*) AS count FROM ${table}`).get();
13689
+ return Number(row?.count ?? 0);
13690
+ } catch {
13691
+ return 0;
13692
+ }
13693
+ }
13694
+ function getConfigsStatus(db = getDatabase()) {
13695
+ let databaseReachable = true;
13696
+ let configs = [];
13697
+ let categoryStats = { total: 0 };
13698
+ try {
13699
+ configs = listConfigs(undefined, db);
13700
+ categoryStats = getConfigStats(db);
13701
+ } catch {
13702
+ databaseReachable = false;
13703
+ }
13704
+ const fileConfigs = configs.filter((config) => config.kind === "file");
13705
+ let driftedTargets = 0;
13706
+ let missingTargets = 0;
13707
+ let unredactedSecretFindings = 0;
13708
+ let knownTargets = 0;
13709
+ for (const config of fileConfigs) {
13710
+ unredactedSecretFindings += scanSecrets(config.content, config.format).length;
13711
+ if (!config.target_path)
13712
+ continue;
13713
+ knownTargets += 1;
13714
+ const targetPath = expandPath(config.target_path);
13715
+ if (!existsSync12(targetPath)) {
13716
+ missingTargets += 1;
13717
+ continue;
13718
+ }
13719
+ const disk = readFileSync6(targetPath, "utf-8");
13720
+ const { content: redactedDisk } = redactContent(disk, config.format);
13721
+ if (redactedDisk !== config.content) {
13722
+ driftedTargets += 1;
13723
+ }
13724
+ }
13725
+ const profiles = databaseReachable ? listProfiles(db).length : 0;
13726
+ const machines = databaseReachable ? listMachines(db).length : 0;
13727
+ const profileLinks = databaseReachable ? tableCount(db, "profile_configs") : 0;
13728
+ const snapshots = databaseReachable ? tableCount(db, "config_snapshots") : 0;
13729
+ const byCategory = Object.fromEntries(Object.entries(categoryStats).filter(([key]) => key !== "total"));
13730
+ const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 ? "ok" : "warn";
13731
+ return {
13732
+ service: "configs",
13733
+ schemaVersion: "1.0",
13734
+ package: {
13735
+ name: PACKAGE_NAME,
13736
+ version: PACKAGE_VERSION
13737
+ },
13738
+ env: {
13739
+ database: {
13740
+ primary: "HASNA_CONFIGS_DB_PATH",
13741
+ fallback: "CONFIGS_DB_PATH",
13742
+ active: activeDatabaseEnv(),
13743
+ kind: configuredDatabaseKind()
13744
+ }
13745
+ },
13746
+ counts: {
13747
+ configs: {
13748
+ total: configs.length,
13749
+ file: fileConfigs.length,
13750
+ reference: configs.filter((config) => config.kind === "reference").length,
13751
+ templates: configs.filter((config) => config.is_template).length
13752
+ },
13753
+ byCategory,
13754
+ byAgent: countBy(configs, (config) => config.agent),
13755
+ byFormat: countBy(configs, (config) => config.format),
13756
+ profiles,
13757
+ profileLinks,
13758
+ machines,
13759
+ snapshots,
13760
+ knownTargets
13761
+ },
13762
+ health: {
13763
+ status,
13764
+ databaseReachable,
13765
+ driftedTargets,
13766
+ missingTargets,
13767
+ unredactedSecretFindings,
13768
+ hasDrift: driftedTargets > 0,
13769
+ hasMissingTargets: missingTargets > 0,
13770
+ hasUnredactedSecrets: unredactedSecretFindings > 0
13771
+ },
13772
+ safety: {
13773
+ includesConfigValues: false,
13774
+ includesPrivatePaths: false,
13775
+ includesHostnames: false,
13776
+ includesSecretValues: false,
13777
+ statusOutputIsMetadataOnly: true
13778
+ }
13779
+ };
13780
+ }
13781
+
13649
13782
  // src/cli/index.tsx
13650
13783
  import { createRequire as createRequire2 } from "module";
13651
13784
  var pkg = createRequire2(import.meta.url)("../../package.json");
@@ -13759,11 +13892,11 @@ program.command("show <id>").description("Show a config's content and metadata")
13759
13892
  });
13760
13893
  program.command("add <path>").description("Ingest a file into the config DB").option("-n, --name <name>", "config name (defaults to filename)").option("-c, --category <cat>", "category override").option("-a, --agent <agent>", "agent override").option("-k, --kind <kind>", "kind: file|reference", "file").option("--template", "mark as template (has {{VAR}} placeholders)").action(async (filePath, opts) => {
13761
13894
  const abs = resolve4(filePath);
13762
- if (!existsSync12(abs)) {
13895
+ if (!existsSync13(abs)) {
13763
13896
  console.error(chalk.red(`File not found: ${abs}`));
13764
13897
  process.exit(1);
13765
13898
  }
13766
- const rawContent = readFileSync6(abs, "utf-8");
13899
+ const rawContent = readFileSync7(abs, "utf-8");
13767
13900
  const fmt = detectFormat(abs);
13768
13901
  const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
13769
13902
  const targetPath = abs.startsWith(homedir11()) ? abs.replace(homedir11(), "~") : abs;
@@ -13850,7 +13983,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
13850
13983
  if (!entry.isDirectory())
13851
13984
  continue;
13852
13985
  const projDir = join13(absDir, entry.name);
13853
- const hasClaude = existsSync12(join13(projDir, "CLAUDE.md")) || existsSync12(join13(projDir, ".mcp.json")) || existsSync12(join13(projDir, ".claude"));
13986
+ const hasClaude = existsSync13(join13(projDir, "CLAUDE.md")) || existsSync13(join13(projDir, ".mcp.json")) || existsSync13(join13(projDir, ".claude"));
13854
13987
  if (!hasClaude)
13855
13988
  continue;
13856
13989
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun });
@@ -14234,7 +14367,7 @@ command = "${mcpBinary}"
14234
14367
  args = []
14235
14368
  `;
14236
14369
  if (ex(configPath)) {
14237
- const content = readFileSync6(configPath, "utf-8");
14370
+ const content = readFileSync7(configPath, "utf-8");
14238
14371
  if (content.includes("[mcp_servers.configs]")) {
14239
14372
  console.log(chalk.dim("= Already installed in Codex"));
14240
14373
  continue;
@@ -14273,7 +14406,7 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
14273
14406
  });
14274
14407
  program.command("init").description("First-time setup: sync all known configs, create default profile").option("--force", "delete existing DB and start fresh").action(async (opts) => {
14275
14408
  const dbPath = join13(homedir11(), ".hasna", "configs", "configs.db");
14276
- if (opts.force && existsSync12(dbPath)) {
14409
+ if (opts.force && existsSync13(dbPath)) {
14277
14410
  const { rmSync: rmSync3 } = await import("fs");
14278
14411
  rmSync3(dbPath);
14279
14412
  console.log(chalk.dim("Deleted existing DB."));
@@ -14325,41 +14458,20 @@ DB stats:`));
14325
14458
  console.log(chalk.dim(`
14326
14459
  DB: ${dbPath}`));
14327
14460
  });
14328
- program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").action(async () => {
14329
- const dbPath = join13(homedir11(), ".hasna", "configs", "configs.db");
14330
- const stats = getConfigStats();
14331
- const { statSync: st } = await import("fs");
14332
- const dbSize = existsSync12(dbPath) ? st(dbPath).size : 0;
14461
+ program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").option("--json", "output metadata-only JSON").action(async (opts) => {
14462
+ const status = getConfigsStatus();
14463
+ if (opts.json) {
14464
+ console.log(JSON.stringify(status, null, 2));
14465
+ return;
14466
+ }
14333
14467
  console.log(chalk.bold("@hasna/configs") + chalk.dim(` v${pkg.version}`));
14334
- console.log(chalk.cyan("DB:") + ` ${dbPath} (${(dbSize / 1024).toFixed(1)}KB)`);
14335
- console.log(chalk.cyan("Total:") + ` ${stats["total"] || 0} configs
14468
+ console.log(chalk.cyan("Database:") + ` ${status.env.database.kind} (${status.env.database.active ?? "default"})`);
14469
+ console.log(chalk.cyan("Total:") + ` ${status.counts.configs.total} configs
14336
14470
  `);
14337
- const allKnown = listConfigs({ kind: "file" });
14338
- let drifted = 0;
14339
- let missing = 0;
14340
- let secrets = 0;
14341
- let templates = 0;
14342
- for (const c of allKnown) {
14343
- if (!c.target_path)
14344
- continue;
14345
- const path = expandPath(c.target_path);
14346
- if (!existsSync12(path)) {
14347
- missing++;
14348
- continue;
14349
- }
14350
- const disk = readFileSync6(path, "utf-8");
14351
- const { content: redactedDisk } = redactContent(disk, c.format);
14352
- if (redactedDisk !== c.content)
14353
- drifted++;
14354
- if (c.is_template)
14355
- templates++;
14356
- const found = scanSecrets(c.content, c.format);
14357
- secrets += found.length;
14358
- }
14359
- console.log(chalk.cyan("Drifted:") + ` ${drifted === 0 ? chalk.green("0") : chalk.yellow(String(drifted))} (stored \u2260 disk)`);
14360
- console.log(chalk.cyan("Missing:") + ` ${missing === 0 ? chalk.green("0") : chalk.yellow(String(missing))} (file not on disk)`);
14361
- console.log(chalk.cyan("Secrets:") + ` ${secrets === 0 ? chalk.green("0 \u2713") : chalk.red(String(secrets) + " \u26A0")} unredacted`);
14362
- console.log(chalk.cyan("Templates:") + ` ${templates} (with {{VAR}} placeholders)`);
14471
+ console.log(chalk.cyan("Drifted:") + ` ${status.health.driftedTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.driftedTargets))} (stored differs from disk)`);
14472
+ console.log(chalk.cyan("Missing:") + ` ${status.health.missingTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.missingTargets))} (file not on disk)`);
14473
+ console.log(chalk.cyan("Secrets:") + ` ${status.health.unredactedSecretFindings === 0 ? chalk.green("0 \u2713") : chalk.red(String(status.health.unredactedSecretFindings) + " \u26A0")} unredacted`);
14474
+ console.log(chalk.cyan("Templates:") + ` ${status.counts.configs.templates} (with {{VAR}} placeholders)`);
14363
14475
  });
14364
14476
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
14365
14477
  const { mkdirSync: mk } = await import("fs");
@@ -14393,9 +14505,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
14393
14505
  console.log(chalk.cyan("Known files on disk:"));
14394
14506
  for (const k of KNOWN_CONFIGS) {
14395
14507
  if (k.rulesDir) {
14396
- existsSync12(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail(`${k.rulesDir}/ not found`);
14508
+ existsSync13(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail(`${k.rulesDir}/ not found`);
14397
14509
  } else {
14398
- existsSync12(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail(`${k.path} not found`);
14510
+ existsSync13(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail(`${k.path} not found`);
14399
14511
  }
14400
14512
  }
14401
14513
  const allConfigs = listConfigs();
@@ -14517,7 +14629,7 @@ program.command("watch").description("Watch known config files for changes and a
14517
14629
  for (const k of KNOWN_CONFIGS) {
14518
14630
  if (k.rulesDir) {
14519
14631
  const absDir = expandPath2(k.rulesDir);
14520
- if (!existsSync12(absDir))
14632
+ if (!existsSync13(absDir))
14521
14633
  continue;
14522
14634
  const { readdirSync: readdirSync5 } = await import("fs");
14523
14635
  for (const f of readdirSync5(absDir).filter((f2) => f2.endsWith(".md"))) {
@@ -14526,7 +14638,7 @@ program.command("watch").description("Watch known config files for changes and a
14526
14638
  }
14527
14639
  } else {
14528
14640
  const abs = expandPath2(k.path);
14529
- if (existsSync12(abs))
14641
+ if (existsSync13(abs))
14530
14642
  mtimes.set(abs, st(abs).mtimeMs);
14531
14643
  }
14532
14644
  }
@@ -14534,7 +14646,7 @@ program.command("watch").description("Watch known config files for changes and a
14534
14646
  const tick = async () => {
14535
14647
  let changed = 0;
14536
14648
  for (const [abs, oldMtime] of mtimes) {
14537
- if (!existsSync12(abs))
14649
+ if (!existsSync13(abs))
14538
14650
  continue;
14539
14651
  const newMtime = st(abs).mtimeMs;
14540
14652
  if (newMtime !== oldMtime) {
@@ -14546,7 +14658,7 @@ program.command("watch").description("Watch known config files for changes and a
14546
14658
  for (const k of KNOWN_CONFIGS) {
14547
14659
  if (k.rulesDir) {
14548
14660
  const absDir = expandPath2(k.rulesDir);
14549
- if (!existsSync12(absDir))
14661
+ if (!existsSync13(absDir))
14550
14662
  continue;
14551
14663
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
14552
14664
  const abs = join13(absDir, f);
@@ -14557,7 +14669,7 @@ program.command("watch").description("Watch known config files for changes and a
14557
14669
  }
14558
14670
  } else {
14559
14671
  const abs = expandPath2(k.path);
14560
- if (existsSync12(abs) && !mtimes.has(abs)) {
14672
+ if (existsSync13(abs) && !mtimes.has(abs)) {
14561
14673
  mtimes.set(abs, st(abs).mtimeMs);
14562
14674
  changed++;
14563
14675
  }
@@ -14584,11 +14696,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
14584
14696
  if (!c.target_path)
14585
14697
  continue;
14586
14698
  const abs = expandPath(c.target_path);
14587
- if (!existsSync12(abs)) {
14699
+ if (!existsSync13(abs)) {
14588
14700
  missing++;
14589
14701
  continue;
14590
14702
  }
14591
- const disk = readFileSync6(abs, "utf-8");
14703
+ const disk = readFileSync7(abs, "utf-8");
14592
14704
  const { content: redactedDisk } = redactContent(disk, c.format);
14593
14705
  if (redactedDisk !== c.content)
14594
14706
  drifted++;
@@ -14626,7 +14738,7 @@ program.command("clean").description("Remove configs from DB whose target files
14626
14738
  if (!c.target_path)
14627
14739
  continue;
14628
14740
  const abs = expandPath(c.target_path);
14629
- if (!existsSync12(abs)) {
14741
+ if (!existsSync13(abs)) {
14630
14742
  if (opts.dryRun) {
14631
14743
  console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${c.target_path})`)}`);
14632
14744
  } else {
package/dist/index.d.ts CHANGED
@@ -4,6 +4,8 @@ export { createSnapshot, listSnapshots, getSnapshot, getSnapshotByVersion, prune
4
4
  export { createProfile, getProfile, listProfiles, updateProfile, deleteProfile, addConfigToProfile, removeConfigFromProfile, getProfileConfigs, profileHasSelectors, profileMatchesMachine, resolveProfileForMachine } from "./db/profiles.js";
5
5
  export { registerMachine, updateMachineApplied, listMachines, currentHostname, currentOs, currentArch } from "./db/machines.js";
6
6
  export { getDatabase, resetDatabase, uuid, now, slugify } from "./db/database.js";
7
+ export { getConfigsStatus } from "./status.js";
8
+ export type { ConfigsStatusContract } from "./status.js";
7
9
  export { PG_MIGRATIONS } from "./db/pg-migrations.js";
8
10
  export { applyConfig, applyConfigs, expandPath } from "./lib/apply.js";
9
11
  export type { ApplyOptions } from "./lib/apply.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAC;AAGjC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGlI,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGrH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAG/O,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhI,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGlF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACvE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AACrL,YAAY,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAG9F,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC/J,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1G,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGnE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACvG,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACzE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAC;AAGjC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGlI,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGrH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAG/O,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGhI,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGlF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACvE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AACrL,YAAY,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAG9F,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC/J,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1G,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGnE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACvG,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACzE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -10236,64 +10236,9 @@ function listMachines(db) {
10236
10236
  const d = db || getDatabase();
10237
10237
  return d.query("SELECT * FROM machines ORDER BY last_applied_at DESC NULLS LAST").all();
10238
10238
  }
10239
- // src/db/pg-migrations.ts
10240
- var PG_MIGRATIONS = [
10241
- `CREATE TABLE IF NOT EXISTS configs (
10242
- id TEXT PRIMARY KEY,
10243
- name TEXT NOT NULL,
10244
- slug TEXT NOT NULL UNIQUE,
10245
- kind TEXT NOT NULL DEFAULT 'file',
10246
- category TEXT NOT NULL,
10247
- agent TEXT NOT NULL DEFAULT 'global',
10248
- target_path TEXT,
10249
- format TEXT NOT NULL DEFAULT 'text',
10250
- content TEXT NOT NULL DEFAULT '',
10251
- description TEXT,
10252
- tags TEXT NOT NULL DEFAULT '[]',
10253
- is_template BOOLEAN NOT NULL DEFAULT FALSE,
10254
- version INTEGER NOT NULL DEFAULT 1,
10255
- created_at TEXT NOT NULL,
10256
- updated_at TEXT NOT NULL,
10257
- synced_at TEXT
10258
- )`,
10259
- `CREATE TABLE IF NOT EXISTS config_snapshots (
10260
- id TEXT PRIMARY KEY,
10261
- config_id TEXT NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
10262
- content TEXT NOT NULL,
10263
- version INTEGER NOT NULL,
10264
- created_at TEXT NOT NULL
10265
- )`,
10266
- `CREATE TABLE IF NOT EXISTS profiles (
10267
- id TEXT PRIMARY KEY,
10268
- name TEXT NOT NULL,
10269
- slug TEXT NOT NULL UNIQUE,
10270
- description TEXT,
10271
- created_at TEXT NOT NULL,
10272
- updated_at TEXT NOT NULL
10273
- )`,
10274
- `CREATE TABLE IF NOT EXISTS profile_configs (
10275
- profile_id TEXT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
10276
- config_id TEXT NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
10277
- sort_order INTEGER NOT NULL DEFAULT 0,
10278
- PRIMARY KEY (profile_id, config_id)
10279
- )`,
10280
- `CREATE TABLE IF NOT EXISTS machines (
10281
- id TEXT PRIMARY KEY,
10282
- hostname TEXT NOT NULL UNIQUE,
10283
- os TEXT,
10284
- last_applied_at TEXT,
10285
- created_at TEXT NOT NULL
10286
- )`,
10287
- `CREATE TABLE IF NOT EXISTS feedback (
10288
- id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
10289
- message TEXT NOT NULL,
10290
- email TEXT,
10291
- category TEXT DEFAULT 'general',
10292
- version TEXT,
10293
- machine_id TEXT,
10294
- created_at TEXT NOT NULL DEFAULT NOW()::text
10295
- )`
10296
- ];
10239
+ // src/status.ts
10240
+ import { existsSync as existsSync9, readFileSync as readFileSync3 } from "fs";
10241
+
10297
10242
  // src/lib/apply.ts
10298
10243
  import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
10299
10244
  import { dirname as dirname2, resolve } from "path";
@@ -10344,59 +10289,6 @@ async function applyConfigs(configs, opts = {}) {
10344
10289
  }
10345
10290
  return results;
10346
10291
  }
10347
- // src/lib/platform-profiles.ts
10348
- var PLATFORM_PROFILE_PRESETS = [
10349
- {
10350
- name: "linux-arm64",
10351
- description: "Default Linux arm64 profile for spark01/spark02-style machines",
10352
- selectors: { os: ["linux"], arch: ["arm64"], hostnames: ["spark01", "spark02"] },
10353
- variables: {
10354
- WORKSPACE_ROOT: "{{HOME_DIR}}/workspace",
10355
- BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
10356
- BUN_PATH: "{{BUN_BIN_DIR}}/bun",
10357
- PATH_PREFIX: "{{BUN_BIN_DIR}}"
10358
- }
10359
- },
10360
- {
10361
- name: "macos-arm64",
10362
- description: "Default macOS arm64 profile for apple01/apple03-style machines",
10363
- selectors: { os: ["macos"], arch: ["arm64"], hostnames: ["apple01", "apple03"] },
10364
- variables: {
10365
- WORKSPACE_ROOT: "{{HOME_DIR}}/Workspace",
10366
- BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
10367
- BUN_PATH: "/opt/homebrew/bin/bun",
10368
- PATH_PREFIX: "/opt/homebrew/bin:{{BUN_BIN_DIR}}"
10369
- }
10370
- }
10371
- ];
10372
- function ensurePlatformProfiles(db) {
10373
- const configs = listConfigs(undefined, db);
10374
- const ensured = [];
10375
- for (const preset of PLATFORM_PROFILE_PRESETS) {
10376
- let profile;
10377
- try {
10378
- profile = getProfile(preset.name, db);
10379
- if (!profileHasSelectors(profile) || Object.keys(profile.variables).length === 0) {
10380
- profile = updateProfile(profile.id, {
10381
- description: profile.description ?? preset.description,
10382
- selectors: profileHasSelectors(profile) ? profile.selectors : preset.selectors,
10383
- variables: Object.keys(profile.variables).length > 0 ? profile.variables : preset.variables
10384
- }, db);
10385
- }
10386
- } catch {
10387
- profile = createProfile(preset, db);
10388
- }
10389
- for (const config of configs) {
10390
- addConfigToProfile(profile.id, config.id, db);
10391
- }
10392
- ensured.push(profile);
10393
- }
10394
- return ensured;
10395
- }
10396
- // src/lib/sync.ts
10397
- import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync4 } from "fs";
10398
- import { extname, join as join10 } from "path";
10399
- import { homedir as homedir10 } from "os";
10400
10292
 
10401
10293
  // src/lib/redact.ts
10402
10294
  var SECRET_KEY_PATTERN = /^(.*_?API_?KEY|.*_?TOKEN|.*_?SECRET|.*_?PASSWORD|.*_?PASSWD|.*_?CREDENTIAL|.*_?AUTH(?:_TOKEN|_KEY|ORIZATION)?|.*_?PRIVATE_?KEY|.*_?ACCESS_?KEY|.*_?CLIENT_?SECRET|.*_?SIGNING_?KEY|.*_?ENCRYPTION_?KEY|.*_AUTH_TOKEN)$/i;
@@ -10578,8 +10470,239 @@ function hasSecrets(content, format) {
10578
10470
  return scanSecrets(content, format).length > 0;
10579
10471
  }
10580
10472
 
10473
+ // src/status.ts
10474
+ var PACKAGE_NAME = "@hasna/configs";
10475
+ var PACKAGE_VERSION = "0.2.38";
10476
+ function activeDatabaseEnv() {
10477
+ if (process.env["HASNA_CONFIGS_DB_PATH"])
10478
+ return "HASNA_CONFIGS_DB_PATH";
10479
+ if (process.env["CONFIGS_DB_PATH"])
10480
+ return "CONFIGS_DB_PATH";
10481
+ return null;
10482
+ }
10483
+ function configuredDatabaseKind() {
10484
+ const value = process.env["HASNA_CONFIGS_DB_PATH"] ?? process.env["CONFIGS_DB_PATH"] ?? "";
10485
+ return value === ":memory:" || value.startsWith("file::memory:") ? "memory" : "file";
10486
+ }
10487
+ function countBy(items, getValue) {
10488
+ const counts = {};
10489
+ for (const item of items) {
10490
+ const value = getValue(item);
10491
+ if (!value)
10492
+ continue;
10493
+ counts[value] = (counts[value] ?? 0) + 1;
10494
+ }
10495
+ return counts;
10496
+ }
10497
+ function tableCount(db, table) {
10498
+ try {
10499
+ const row = db.query(`SELECT COUNT(*) AS count FROM ${table}`).get();
10500
+ return Number(row?.count ?? 0);
10501
+ } catch {
10502
+ return 0;
10503
+ }
10504
+ }
10505
+ function getConfigsStatus(db = getDatabase()) {
10506
+ let databaseReachable = true;
10507
+ let configs = [];
10508
+ let categoryStats = { total: 0 };
10509
+ try {
10510
+ configs = listConfigs(undefined, db);
10511
+ categoryStats = getConfigStats(db);
10512
+ } catch {
10513
+ databaseReachable = false;
10514
+ }
10515
+ const fileConfigs = configs.filter((config) => config.kind === "file");
10516
+ let driftedTargets = 0;
10517
+ let missingTargets = 0;
10518
+ let unredactedSecretFindings = 0;
10519
+ let knownTargets = 0;
10520
+ for (const config of fileConfigs) {
10521
+ unredactedSecretFindings += scanSecrets(config.content, config.format).length;
10522
+ if (!config.target_path)
10523
+ continue;
10524
+ knownTargets += 1;
10525
+ const targetPath = expandPath(config.target_path);
10526
+ if (!existsSync9(targetPath)) {
10527
+ missingTargets += 1;
10528
+ continue;
10529
+ }
10530
+ const disk = readFileSync3(targetPath, "utf-8");
10531
+ const { content: redactedDisk } = redactContent(disk, config.format);
10532
+ if (redactedDisk !== config.content) {
10533
+ driftedTargets += 1;
10534
+ }
10535
+ }
10536
+ const profiles = databaseReachable ? listProfiles(db).length : 0;
10537
+ const machines = databaseReachable ? listMachines(db).length : 0;
10538
+ const profileLinks = databaseReachable ? tableCount(db, "profile_configs") : 0;
10539
+ const snapshots = databaseReachable ? tableCount(db, "config_snapshots") : 0;
10540
+ const byCategory = Object.fromEntries(Object.entries(categoryStats).filter(([key]) => key !== "total"));
10541
+ const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 ? "ok" : "warn";
10542
+ return {
10543
+ service: "configs",
10544
+ schemaVersion: "1.0",
10545
+ package: {
10546
+ name: PACKAGE_NAME,
10547
+ version: PACKAGE_VERSION
10548
+ },
10549
+ env: {
10550
+ database: {
10551
+ primary: "HASNA_CONFIGS_DB_PATH",
10552
+ fallback: "CONFIGS_DB_PATH",
10553
+ active: activeDatabaseEnv(),
10554
+ kind: configuredDatabaseKind()
10555
+ }
10556
+ },
10557
+ counts: {
10558
+ configs: {
10559
+ total: configs.length,
10560
+ file: fileConfigs.length,
10561
+ reference: configs.filter((config) => config.kind === "reference").length,
10562
+ templates: configs.filter((config) => config.is_template).length
10563
+ },
10564
+ byCategory,
10565
+ byAgent: countBy(configs, (config) => config.agent),
10566
+ byFormat: countBy(configs, (config) => config.format),
10567
+ profiles,
10568
+ profileLinks,
10569
+ machines,
10570
+ snapshots,
10571
+ knownTargets
10572
+ },
10573
+ health: {
10574
+ status,
10575
+ databaseReachable,
10576
+ driftedTargets,
10577
+ missingTargets,
10578
+ unredactedSecretFindings,
10579
+ hasDrift: driftedTargets > 0,
10580
+ hasMissingTargets: missingTargets > 0,
10581
+ hasUnredactedSecrets: unredactedSecretFindings > 0
10582
+ },
10583
+ safety: {
10584
+ includesConfigValues: false,
10585
+ includesPrivatePaths: false,
10586
+ includesHostnames: false,
10587
+ includesSecretValues: false,
10588
+ statusOutputIsMetadataOnly: true
10589
+ }
10590
+ };
10591
+ }
10592
+ // src/db/pg-migrations.ts
10593
+ var PG_MIGRATIONS = [
10594
+ `CREATE TABLE IF NOT EXISTS configs (
10595
+ id TEXT PRIMARY KEY,
10596
+ name TEXT NOT NULL,
10597
+ slug TEXT NOT NULL UNIQUE,
10598
+ kind TEXT NOT NULL DEFAULT 'file',
10599
+ category TEXT NOT NULL,
10600
+ agent TEXT NOT NULL DEFAULT 'global',
10601
+ target_path TEXT,
10602
+ format TEXT NOT NULL DEFAULT 'text',
10603
+ content TEXT NOT NULL DEFAULT '',
10604
+ description TEXT,
10605
+ tags TEXT NOT NULL DEFAULT '[]',
10606
+ is_template BOOLEAN NOT NULL DEFAULT FALSE,
10607
+ version INTEGER NOT NULL DEFAULT 1,
10608
+ created_at TEXT NOT NULL,
10609
+ updated_at TEXT NOT NULL,
10610
+ synced_at TEXT
10611
+ )`,
10612
+ `CREATE TABLE IF NOT EXISTS config_snapshots (
10613
+ id TEXT PRIMARY KEY,
10614
+ config_id TEXT NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
10615
+ content TEXT NOT NULL,
10616
+ version INTEGER NOT NULL,
10617
+ created_at TEXT NOT NULL
10618
+ )`,
10619
+ `CREATE TABLE IF NOT EXISTS profiles (
10620
+ id TEXT PRIMARY KEY,
10621
+ name TEXT NOT NULL,
10622
+ slug TEXT NOT NULL UNIQUE,
10623
+ description TEXT,
10624
+ created_at TEXT NOT NULL,
10625
+ updated_at TEXT NOT NULL
10626
+ )`,
10627
+ `CREATE TABLE IF NOT EXISTS profile_configs (
10628
+ profile_id TEXT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
10629
+ config_id TEXT NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
10630
+ sort_order INTEGER NOT NULL DEFAULT 0,
10631
+ PRIMARY KEY (profile_id, config_id)
10632
+ )`,
10633
+ `CREATE TABLE IF NOT EXISTS machines (
10634
+ id TEXT PRIMARY KEY,
10635
+ hostname TEXT NOT NULL UNIQUE,
10636
+ os TEXT,
10637
+ last_applied_at TEXT,
10638
+ created_at TEXT NOT NULL
10639
+ )`,
10640
+ `CREATE TABLE IF NOT EXISTS feedback (
10641
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
10642
+ message TEXT NOT NULL,
10643
+ email TEXT,
10644
+ category TEXT DEFAULT 'general',
10645
+ version TEXT,
10646
+ machine_id TEXT,
10647
+ created_at TEXT NOT NULL DEFAULT NOW()::text
10648
+ )`
10649
+ ];
10650
+ // src/lib/platform-profiles.ts
10651
+ var PLATFORM_PROFILE_PRESETS = [
10652
+ {
10653
+ name: "linux-arm64",
10654
+ description: "Default Linux arm64 profile for linux-node-a/linux-node-b-style machines",
10655
+ selectors: { os: ["linux"], arch: ["arm64"], hostnames: ["linux-node-a", "linux-node-b"] },
10656
+ variables: {
10657
+ WORKSPACE_ROOT: "{{HOME_DIR}}/workspace",
10658
+ BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
10659
+ BUN_PATH: "{{BUN_BIN_DIR}}/bun",
10660
+ PATH_PREFIX: "{{BUN_BIN_DIR}}"
10661
+ }
10662
+ },
10663
+ {
10664
+ name: "macos-arm64",
10665
+ description: "Default macOS arm64 profile for macos-node-a/macos-node-b-style machines",
10666
+ selectors: { os: ["macos"], arch: ["arm64"], hostnames: ["macos-node-a", "macos-node-b"] },
10667
+ variables: {
10668
+ WORKSPACE_ROOT: "{{HOME_DIR}}/Workspace",
10669
+ BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
10670
+ BUN_PATH: "/opt/homebrew/bin/bun",
10671
+ PATH_PREFIX: "/opt/homebrew/bin:{{BUN_BIN_DIR}}"
10672
+ }
10673
+ }
10674
+ ];
10675
+ function ensurePlatformProfiles(db) {
10676
+ const configs = listConfigs(undefined, db);
10677
+ const ensured = [];
10678
+ for (const preset of PLATFORM_PROFILE_PRESETS) {
10679
+ let profile;
10680
+ try {
10681
+ profile = getProfile(preset.name, db);
10682
+ if (!profileHasSelectors(profile) || Object.keys(profile.variables).length === 0) {
10683
+ profile = updateProfile(profile.id, {
10684
+ description: profile.description ?? preset.description,
10685
+ selectors: profileHasSelectors(profile) ? profile.selectors : preset.selectors,
10686
+ variables: Object.keys(profile.variables).length > 0 ? profile.variables : preset.variables
10687
+ }, db);
10688
+ }
10689
+ } catch {
10690
+ profile = createProfile(preset, db);
10691
+ }
10692
+ for (const config of configs) {
10693
+ addConfigToProfile(profile.id, config.id, db);
10694
+ }
10695
+ ensured.push(profile);
10696
+ }
10697
+ return ensured;
10698
+ }
10699
+ // src/lib/sync.ts
10700
+ import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
10701
+ import { extname, join as join10 } from "path";
10702
+ import { homedir as homedir10 } from "os";
10703
+
10581
10704
  // src/lib/sync-dir.ts
10582
- import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync } from "fs";
10705
+ import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "fs";
10583
10706
  import { join as join9, relative as relative2 } from "path";
10584
10707
  import { homedir as homedir9 } from "os";
10585
10708
  var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
@@ -10589,7 +10712,7 @@ function shouldSkip(p) {
10589
10712
  async function syncFromDir(dir, opts = {}) {
10590
10713
  const d = opts.db || getDatabase();
10591
10714
  const absDir = expandPath(dir);
10592
- if (!existsSync9(absDir))
10715
+ if (!existsSync10(absDir))
10593
10716
  return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
10594
10717
  const files = opts.recursive !== false ? walkDir(absDir) : readdirSync2(absDir).map((f) => join9(absDir, f)).filter((f) => statSync(f).isFile());
10595
10718
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
@@ -10601,7 +10724,7 @@ async function syncFromDir(dir, opts = {}) {
10601
10724
  continue;
10602
10725
  }
10603
10726
  try {
10604
- const content = readFileSync3(file, "utf-8");
10727
+ const content = readFileSync4(file, "utf-8");
10605
10728
  if (content.length > 500000) {
10606
10729
  result.skipped.push(file + " (too large)");
10607
10730
  continue;
@@ -10696,10 +10819,10 @@ async function syncProject(opts) {
10696
10819
  const machine = detectMachineContext();
10697
10820
  for (const pf of PROJECT_CONFIG_FILES) {
10698
10821
  const abs = join10(absDir, pf.file);
10699
- if (!existsSync10(abs))
10822
+ if (!existsSync11(abs))
10700
10823
  continue;
10701
10824
  try {
10702
- const rawContent = readFileSync4(abs, "utf-8");
10825
+ const rawContent = readFileSync5(abs, "utf-8");
10703
10826
  if (rawContent.length > 500000) {
10704
10827
  result.skipped.push(pf.file);
10705
10828
  continue;
@@ -10728,11 +10851,11 @@ async function syncProject(opts) {
10728
10851
  }
10729
10852
  }
10730
10853
  const rulesDir = join10(absDir, ".claude", "rules");
10731
- if (existsSync10(rulesDir)) {
10854
+ if (existsSync11(rulesDir)) {
10732
10855
  const mdFiles = readdirSync4(rulesDir).filter((f) => f.endsWith(".md"));
10733
10856
  for (const f of mdFiles) {
10734
10857
  const abs = join10(rulesDir, f);
10735
- const raw = readFileSync4(abs, "utf-8");
10858
+ const raw = readFileSync5(abs, "utf-8");
10736
10859
  const redacted = redactContent(raw, "markdown");
10737
10860
  const machineAware = templateizeMachineContent(redacted.content, machine);
10738
10861
  const content = machineAware.content;
@@ -10770,7 +10893,7 @@ async function syncKnown(opts = {}) {
10770
10893
  for (const known of targets) {
10771
10894
  if (known.rulesDir) {
10772
10895
  const absDir = expandPath(known.rulesDir);
10773
- if (!existsSync10(absDir)) {
10896
+ if (!existsSync11(absDir)) {
10774
10897
  result.skipped.push(known.rulesDir);
10775
10898
  continue;
10776
10899
  }
@@ -10778,7 +10901,7 @@ async function syncKnown(opts = {}) {
10778
10901
  for (const f of mdFiles) {
10779
10902
  const abs2 = join10(absDir, f);
10780
10903
  const targetPath = abs2.replace(home, "~");
10781
- const raw = readFileSync4(abs2, "utf-8");
10904
+ const raw = readFileSync5(abs2, "utf-8");
10782
10905
  const redacted = redactContent(raw, "markdown");
10783
10906
  const machineAware = templateizeMachineContent(redacted.content, machine);
10784
10907
  const content = machineAware.content;
@@ -10801,12 +10924,12 @@ async function syncKnown(opts = {}) {
10801
10924
  continue;
10802
10925
  }
10803
10926
  const abs = expandPath(known.path);
10804
- if (!existsSync10(abs)) {
10927
+ if (!existsSync11(abs)) {
10805
10928
  result.skipped.push(known.path);
10806
10929
  continue;
10807
10930
  }
10808
10931
  try {
10809
- const rawContent = readFileSync4(abs, "utf-8");
10932
+ const rawContent = readFileSync5(abs, "utf-8");
10810
10933
  if (rawContent.length > 500000) {
10811
10934
  result.skipped.push(known.path + " (too large)");
10812
10935
  continue;
@@ -10866,9 +10989,9 @@ function diffConfig(config) {
10866
10989
  if (!config.target_path)
10867
10990
  return "(reference \u2014 no target path)";
10868
10991
  const path = expandPath(config.target_path);
10869
- if (!existsSync10(path))
10992
+ if (!existsSync11(path))
10870
10993
  return `(file not found on disk: ${path})`;
10871
- const diskContent = readFileSync4(path, "utf-8");
10994
+ const diskContent = readFileSync5(path, "utf-8");
10872
10995
  if (diskContent === config.content)
10873
10996
  return "(no diff \u2014 identical)";
10874
10997
  const stored = config.content.split(`
@@ -10942,7 +11065,7 @@ function detectFormat(filePath) {
10942
11065
  return "text";
10943
11066
  }
10944
11067
  // src/lib/export.ts
10945
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, rmSync, writeFileSync as writeFileSync3 } from "fs";
11068
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, rmSync, writeFileSync as writeFileSync3 } from "fs";
10946
11069
  import { join as join11, resolve as resolve2 } from "path";
10947
11070
  import { tmpdir } from "os";
10948
11071
  async function exportConfigs(outputPath, opts = {}) {
@@ -10974,13 +11097,13 @@ async function exportConfigs(outputPath, opts = {}) {
10974
11097
  }
10975
11098
  return { path: absOutput, count: configs.length };
10976
11099
  } finally {
10977
- if (existsSync11(tmpDir)) {
11100
+ if (existsSync12(tmpDir)) {
10978
11101
  rmSync(tmpDir, { recursive: true, force: true });
10979
11102
  }
10980
11103
  }
10981
11104
  }
10982
11105
  // src/lib/import.ts
10983
- import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync2 } from "fs";
11106
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync6, rmSync as rmSync2 } from "fs";
10984
11107
  import { join as join12, resolve as resolve3 } from "path";
10985
11108
  import { tmpdir as tmpdir2 } from "os";
10986
11109
  async function importConfigs(bundlePath, opts = {}) {
@@ -11001,14 +11124,14 @@ async function importConfigs(bundlePath, opts = {}) {
11001
11124
  throw new Error(`tar extraction failed: ${stderr}`);
11002
11125
  }
11003
11126
  const manifestPath = join12(tmpDir, "manifest.json");
11004
- if (!existsSync12(manifestPath))
11127
+ if (!existsSync13(manifestPath))
11005
11128
  throw new Error("Invalid bundle: missing manifest.json");
11006
- const manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
11129
+ const manifest = JSON.parse(readFileSync6(manifestPath, "utf-8"));
11007
11130
  for (const meta of manifest.configs) {
11008
11131
  try {
11009
11132
  const ext = meta.format === "text" ? "txt" : meta.format;
11010
11133
  const contentFile = join12(tmpDir, "contents", `${meta.slug}.${ext}`);
11011
- const content = existsSync12(contentFile) ? readFileSync5(contentFile, "utf-8") : "";
11134
+ const content = existsSync13(contentFile) ? readFileSync6(contentFile, "utf-8") : "";
11012
11135
  let existing = null;
11013
11136
  try {
11014
11137
  existing = getConfig(meta.slug, d);
@@ -11041,7 +11164,7 @@ async function importConfigs(bundlePath, opts = {}) {
11041
11164
  }
11042
11165
  return result;
11043
11166
  } finally {
11044
- if (existsSync12(tmpDir)) {
11167
+ if (existsSync13(tmpDir)) {
11045
11168
  rmSync2(tmpDir, { recursive: true, force: true });
11046
11169
  }
11047
11170
  }
@@ -11086,6 +11209,7 @@ export {
11086
11209
  getProfileConfigs,
11087
11210
  getProfile,
11088
11211
  getDatabase,
11212
+ getConfigsStatus,
11089
11213
  getConfigStats,
11090
11214
  getConfigById,
11091
11215
  getConfig,
package/dist/mcp/index.js CHANGED
@@ -11241,7 +11241,7 @@ var init_sync_dir = __esm(() => {
11241
11241
  var require_package = __commonJS((exports, module) => {
11242
11242
  module.exports = {
11243
11243
  name: "@hasna/configs",
11244
- version: "0.2.36",
11244
+ version: "0.2.38",
11245
11245
  description: "AI coding agent configuration manager \u2014 store, version, apply, and share all your AI coding configs. CLI + MCP + REST API + Dashboard.",
11246
11246
  type: "module",
11247
11247
  main: "dist/index.js",
@@ -17829,7 +17829,7 @@ var require_dist2 = __commonJS((exports, module) => {
17829
17829
  var require_package = __commonJS((exports, module) => {
17830
17830
  module.exports = {
17831
17831
  name: "@hasna/configs",
17832
- version: "0.2.36",
17832
+ version: "0.2.38",
17833
17833
  description: "AI coding agent configuration manager \u2014 store, version, apply, and share all your AI coding configs. CLI + MCP + REST API + Dashboard.",
17834
17834
  type: "module",
17835
17835
  main: "dist/index.js",
@@ -0,0 +1,56 @@
1
+ import type { Database } from "bun:sqlite";
2
+ type ActiveDbEnv = "HASNA_CONFIGS_DB_PATH" | "CONFIGS_DB_PATH" | null;
3
+ type DatabaseKind = "memory" | "file";
4
+ type ContractStatus = "ok" | "warn";
5
+ export interface ConfigsStatusContract {
6
+ service: "configs";
7
+ schemaVersion: "1.0";
8
+ package: {
9
+ name: string;
10
+ version: string;
11
+ };
12
+ env: {
13
+ database: {
14
+ primary: "HASNA_CONFIGS_DB_PATH";
15
+ fallback: "CONFIGS_DB_PATH";
16
+ active: ActiveDbEnv;
17
+ kind: DatabaseKind;
18
+ };
19
+ };
20
+ counts: {
21
+ configs: {
22
+ total: number;
23
+ file: number;
24
+ reference: number;
25
+ templates: number;
26
+ };
27
+ byCategory: Record<string, number>;
28
+ byAgent: Record<string, number>;
29
+ byFormat: Record<string, number>;
30
+ profiles: number;
31
+ profileLinks: number;
32
+ machines: number;
33
+ snapshots: number;
34
+ knownTargets: number;
35
+ };
36
+ health: {
37
+ status: ContractStatus;
38
+ databaseReachable: boolean;
39
+ driftedTargets: number;
40
+ missingTargets: number;
41
+ unredactedSecretFindings: number;
42
+ hasDrift: boolean;
43
+ hasMissingTargets: boolean;
44
+ hasUnredactedSecrets: boolean;
45
+ };
46
+ safety: {
47
+ includesConfigValues: false;
48
+ includesPrivatePaths: false;
49
+ includesHostnames: false;
50
+ includesSecretValues: false;
51
+ statusOutputIsMetadataOnly: true;
52
+ };
53
+ }
54
+ export declare function getConfigsStatus(db?: Database): ConfigsStatusContract;
55
+ export {};
56
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAW3C,KAAK,WAAW,GAAG,uBAAuB,GAAG,iBAAiB,GAAG,IAAI,CAAC;AACtE,KAAK,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;AACtC,KAAK,cAAc,GAAG,IAAI,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,SAAS,CAAC;IACnB,aAAa,EAAE,KAAK,CAAC;IACrB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,GAAG,EAAE;QACH,QAAQ,EAAE;YACR,OAAO,EAAE,uBAAuB,CAAC;YACjC,QAAQ,EAAE,iBAAiB,CAAC;YAC5B,MAAM,EAAE,WAAW,CAAC;YACpB,IAAI,EAAE,YAAY,CAAC;SACpB,CAAC;KACH,CAAC;IACF,MAAM,EAAE;QACN,OAAO,EAAE;YACP,KAAK,EAAE,MAAM,CAAC;YACd,IAAI,EAAE,MAAM,CAAC;YACb,SAAS,EAAE,MAAM,CAAC;YAClB,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC;QACF,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,MAAM,EAAE;QACN,MAAM,EAAE,cAAc,CAAC;QACvB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,wBAAwB,EAAE,MAAM,CAAC;QACjC,QAAQ,EAAE,OAAO,CAAC;QAClB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,oBAAoB,EAAE,OAAO,CAAC;KAC/B,CAAC;IACF,MAAM,EAAE;QACN,oBAAoB,EAAE,KAAK,CAAC;QAC5B,oBAAoB,EAAE,KAAK,CAAC;QAC5B,iBAAiB,EAAE,KAAK,CAAC;QACzB,oBAAoB,EAAE,KAAK,CAAC;QAC5B,0BAA0B,EAAE,IAAI,CAAC;KAClC,CAAC;CACH;AAgCD,wBAAgB,gBAAgB,CAAC,EAAE,GAAE,QAAwB,GAAG,qBAAqB,CAmGpF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=status.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.test.d.ts","sourceRoot":"","sources":["../src/status.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/configs",
3
- "version": "0.2.36",
3
+ "version": "0.2.38",
4
4
  "description": "AI coding agent configuration manager — store, version, apply, and share all your AI coding configs. CLI + MCP + REST API + Dashboard.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",