@hasna/configs 0.2.36 → 0.2.37
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 +2 -2
- package/dist/cli/index.js +188 -75
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +256 -131
- package/dist/mcp/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/status.d.ts +56 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.test.d.ts +2 -0
- package/dist/status.test.d.ts.map +1 -0
- package/package.json +1 -1
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 `
|
|
62
|
-
- `macos-arm64` for `
|
|
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
|
|
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
|
|
13604
|
-
selectors: { os: ["linux"], arch: ["arm64"], hostnames: ["
|
|
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
|
|
13615
|
-
selectors: { os: ["macos"], arch: ["arm64"], hostnames: ["
|
|
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,9 +13645,144 @@ function ensurePlatformProfiles(db) {
|
|
|
13646
13645
|
return ensured;
|
|
13647
13646
|
}
|
|
13648
13647
|
|
|
13649
|
-
// src/
|
|
13648
|
+
// src/status.ts
|
|
13649
|
+
init_database();
|
|
13650
|
+
init_configs();
|
|
13651
|
+
import { existsSync as existsSync12, readFileSync as readFileSync6 } from "fs";
|
|
13650
13652
|
import { createRequire as createRequire2 } from "module";
|
|
13651
|
-
|
|
13653
|
+
|
|
13654
|
+
// src/db/machines.ts
|
|
13655
|
+
init_database();
|
|
13656
|
+
function listMachines(db) {
|
|
13657
|
+
const d = db || getDatabase();
|
|
13658
|
+
return d.query("SELECT * FROM machines ORDER BY last_applied_at DESC NULLS LAST").all();
|
|
13659
|
+
}
|
|
13660
|
+
|
|
13661
|
+
// src/status.ts
|
|
13662
|
+
init_apply();
|
|
13663
|
+
init_redact();
|
|
13664
|
+
var require2 = createRequire2(import.meta.url);
|
|
13665
|
+
var pkg = require2("../package.json");
|
|
13666
|
+
function activeDatabaseEnv() {
|
|
13667
|
+
if (process.env["HASNA_CONFIGS_DB_PATH"])
|
|
13668
|
+
return "HASNA_CONFIGS_DB_PATH";
|
|
13669
|
+
if (process.env["CONFIGS_DB_PATH"])
|
|
13670
|
+
return "CONFIGS_DB_PATH";
|
|
13671
|
+
return null;
|
|
13672
|
+
}
|
|
13673
|
+
function configuredDatabaseKind() {
|
|
13674
|
+
const value = process.env["HASNA_CONFIGS_DB_PATH"] ?? process.env["CONFIGS_DB_PATH"] ?? "";
|
|
13675
|
+
return value === ":memory:" || value.startsWith("file::memory:") ? "memory" : "file";
|
|
13676
|
+
}
|
|
13677
|
+
function countBy(items, getValue) {
|
|
13678
|
+
const counts = {};
|
|
13679
|
+
for (const item of items) {
|
|
13680
|
+
const value = getValue(item);
|
|
13681
|
+
if (!value)
|
|
13682
|
+
continue;
|
|
13683
|
+
counts[value] = (counts[value] ?? 0) + 1;
|
|
13684
|
+
}
|
|
13685
|
+
return counts;
|
|
13686
|
+
}
|
|
13687
|
+
function tableCount(db, table) {
|
|
13688
|
+
try {
|
|
13689
|
+
const row = db.query(`SELECT COUNT(*) AS count FROM ${table}`).get();
|
|
13690
|
+
return Number(row?.count ?? 0);
|
|
13691
|
+
} catch {
|
|
13692
|
+
return 0;
|
|
13693
|
+
}
|
|
13694
|
+
}
|
|
13695
|
+
function getConfigsStatus(db = getDatabase()) {
|
|
13696
|
+
let databaseReachable = true;
|
|
13697
|
+
let configs = [];
|
|
13698
|
+
let categoryStats = { total: 0 };
|
|
13699
|
+
try {
|
|
13700
|
+
configs = listConfigs(undefined, db);
|
|
13701
|
+
categoryStats = getConfigStats(db);
|
|
13702
|
+
} catch {
|
|
13703
|
+
databaseReachable = false;
|
|
13704
|
+
}
|
|
13705
|
+
const fileConfigs = configs.filter((config) => config.kind === "file");
|
|
13706
|
+
let driftedTargets = 0;
|
|
13707
|
+
let missingTargets = 0;
|
|
13708
|
+
let unredactedSecretFindings = 0;
|
|
13709
|
+
let knownTargets = 0;
|
|
13710
|
+
for (const config of fileConfigs) {
|
|
13711
|
+
unredactedSecretFindings += scanSecrets(config.content, config.format).length;
|
|
13712
|
+
if (!config.target_path)
|
|
13713
|
+
continue;
|
|
13714
|
+
knownTargets += 1;
|
|
13715
|
+
const targetPath = expandPath(config.target_path);
|
|
13716
|
+
if (!existsSync12(targetPath)) {
|
|
13717
|
+
missingTargets += 1;
|
|
13718
|
+
continue;
|
|
13719
|
+
}
|
|
13720
|
+
const disk = readFileSync6(targetPath, "utf-8");
|
|
13721
|
+
const { content: redactedDisk } = redactContent(disk, config.format);
|
|
13722
|
+
if (redactedDisk !== config.content) {
|
|
13723
|
+
driftedTargets += 1;
|
|
13724
|
+
}
|
|
13725
|
+
}
|
|
13726
|
+
const profiles = databaseReachable ? listProfiles(db).length : 0;
|
|
13727
|
+
const machines = databaseReachable ? listMachines(db).length : 0;
|
|
13728
|
+
const profileLinks = databaseReachable ? tableCount(db, "profile_configs") : 0;
|
|
13729
|
+
const snapshots = databaseReachable ? tableCount(db, "config_snapshots") : 0;
|
|
13730
|
+
const byCategory = Object.fromEntries(Object.entries(categoryStats).filter(([key]) => key !== "total"));
|
|
13731
|
+
const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 ? "ok" : "warn";
|
|
13732
|
+
return {
|
|
13733
|
+
service: "configs",
|
|
13734
|
+
schemaVersion: "1.0",
|
|
13735
|
+
package: {
|
|
13736
|
+
name: pkg.name,
|
|
13737
|
+
version: pkg.version
|
|
13738
|
+
},
|
|
13739
|
+
env: {
|
|
13740
|
+
database: {
|
|
13741
|
+
primary: "HASNA_CONFIGS_DB_PATH",
|
|
13742
|
+
fallback: "CONFIGS_DB_PATH",
|
|
13743
|
+
active: activeDatabaseEnv(),
|
|
13744
|
+
kind: configuredDatabaseKind()
|
|
13745
|
+
}
|
|
13746
|
+
},
|
|
13747
|
+
counts: {
|
|
13748
|
+
configs: {
|
|
13749
|
+
total: configs.length,
|
|
13750
|
+
file: fileConfigs.length,
|
|
13751
|
+
reference: configs.filter((config) => config.kind === "reference").length,
|
|
13752
|
+
templates: configs.filter((config) => config.is_template).length
|
|
13753
|
+
},
|
|
13754
|
+
byCategory,
|
|
13755
|
+
byAgent: countBy(configs, (config) => config.agent),
|
|
13756
|
+
byFormat: countBy(configs, (config) => config.format),
|
|
13757
|
+
profiles,
|
|
13758
|
+
profileLinks,
|
|
13759
|
+
machines,
|
|
13760
|
+
snapshots,
|
|
13761
|
+
knownTargets
|
|
13762
|
+
},
|
|
13763
|
+
health: {
|
|
13764
|
+
status,
|
|
13765
|
+
databaseReachable,
|
|
13766
|
+
driftedTargets,
|
|
13767
|
+
missingTargets,
|
|
13768
|
+
unredactedSecretFindings,
|
|
13769
|
+
hasDrift: driftedTargets > 0,
|
|
13770
|
+
hasMissingTargets: missingTargets > 0,
|
|
13771
|
+
hasUnredactedSecrets: unredactedSecretFindings > 0
|
|
13772
|
+
},
|
|
13773
|
+
safety: {
|
|
13774
|
+
includesConfigValues: false,
|
|
13775
|
+
includesPrivatePaths: false,
|
|
13776
|
+
includesHostnames: false,
|
|
13777
|
+
includesSecretValues: false,
|
|
13778
|
+
statusOutputIsMetadataOnly: true
|
|
13779
|
+
}
|
|
13780
|
+
};
|
|
13781
|
+
}
|
|
13782
|
+
|
|
13783
|
+
// src/cli/index.tsx
|
|
13784
|
+
import { createRequire as createRequire3 } from "module";
|
|
13785
|
+
var pkg2 = createRequire3(import.meta.url)("../../package.json");
|
|
13652
13786
|
function fmtConfig(c, format) {
|
|
13653
13787
|
if (format === "json")
|
|
13654
13788
|
return JSON.stringify(c, null, 2);
|
|
@@ -13759,11 +13893,11 @@ program.command("show <id>").description("Show a config's content and metadata")
|
|
|
13759
13893
|
});
|
|
13760
13894
|
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
13895
|
const abs = resolve4(filePath);
|
|
13762
|
-
if (!
|
|
13896
|
+
if (!existsSync13(abs)) {
|
|
13763
13897
|
console.error(chalk.red(`File not found: ${abs}`));
|
|
13764
13898
|
process.exit(1);
|
|
13765
13899
|
}
|
|
13766
|
-
const rawContent =
|
|
13900
|
+
const rawContent = readFileSync7(abs, "utf-8");
|
|
13767
13901
|
const fmt = detectFormat(abs);
|
|
13768
13902
|
const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
|
|
13769
13903
|
const targetPath = abs.startsWith(homedir11()) ? abs.replace(homedir11(), "~") : abs;
|
|
@@ -13850,7 +13984,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
13850
13984
|
if (!entry.isDirectory())
|
|
13851
13985
|
continue;
|
|
13852
13986
|
const projDir = join13(absDir, entry.name);
|
|
13853
|
-
const hasClaude =
|
|
13987
|
+
const hasClaude = existsSync13(join13(projDir, "CLAUDE.md")) || existsSync13(join13(projDir, ".mcp.json")) || existsSync13(join13(projDir, ".claude"));
|
|
13854
13988
|
if (!hasClaude)
|
|
13855
13989
|
continue;
|
|
13856
13990
|
const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun });
|
|
@@ -13900,7 +14034,7 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
|
|
|
13900
14034
|
program.command("whoami").description("Show setup summary").action(async () => {
|
|
13901
14035
|
const dbPath = process.env["CONFIGS_DB_PATH"] || join13(homedir11(), ".hasna", "configs", "configs.db");
|
|
13902
14036
|
const stats = getConfigStats();
|
|
13903
|
-
console.log(chalk.bold("@hasna/configs") + chalk.dim(" v" +
|
|
14037
|
+
console.log(chalk.bold("@hasna/configs") + chalk.dim(" v" + pkg2.version));
|
|
13904
14038
|
console.log(chalk.cyan("DB:") + " " + dbPath);
|
|
13905
14039
|
console.log(chalk.cyan("Total configs:") + " " + (stats["total"] || 0));
|
|
13906
14040
|
console.log();
|
|
@@ -14234,7 +14368,7 @@ command = "${mcpBinary}"
|
|
|
14234
14368
|
args = []
|
|
14235
14369
|
`;
|
|
14236
14370
|
if (ex(configPath)) {
|
|
14237
|
-
const content =
|
|
14371
|
+
const content = readFileSync7(configPath, "utf-8");
|
|
14238
14372
|
if (content.includes("[mcp_servers.configs]")) {
|
|
14239
14373
|
console.log(chalk.dim("= Already installed in Codex"));
|
|
14240
14374
|
continue;
|
|
@@ -14273,7 +14407,7 @@ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP serv
|
|
|
14273
14407
|
});
|
|
14274
14408
|
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
14409
|
const dbPath = join13(homedir11(), ".hasna", "configs", "configs.db");
|
|
14276
|
-
if (opts.force &&
|
|
14410
|
+
if (opts.force && existsSync13(dbPath)) {
|
|
14277
14411
|
const { rmSync: rmSync3 } = await import("fs");
|
|
14278
14412
|
rmSync3(dbPath);
|
|
14279
14413
|
console.log(chalk.dim("Deleted existing DB."));
|
|
@@ -14325,41 +14459,20 @@ DB stats:`));
|
|
|
14325
14459
|
console.log(chalk.dim(`
|
|
14326
14460
|
DB: ${dbPath}`));
|
|
14327
14461
|
});
|
|
14328
|
-
program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").action(async () => {
|
|
14329
|
-
const
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
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
|
|
14336
|
-
`);
|
|
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;
|
|
14462
|
+
program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").option("--json", "output metadata-only JSON").action(async (opts) => {
|
|
14463
|
+
const status = getConfigsStatus();
|
|
14464
|
+
if (opts.json) {
|
|
14465
|
+
console.log(JSON.stringify(status, null, 2));
|
|
14466
|
+
return;
|
|
14358
14467
|
}
|
|
14359
|
-
console.log(chalk.
|
|
14360
|
-
console.log(chalk.cyan("
|
|
14361
|
-
console.log(chalk.cyan("
|
|
14362
|
-
|
|
14468
|
+
console.log(chalk.bold("@hasna/configs") + chalk.dim(` v${pkg2.version}`));
|
|
14469
|
+
console.log(chalk.cyan("Database:") + ` ${status.env.database.kind} (${status.env.database.active ?? "default"})`);
|
|
14470
|
+
console.log(chalk.cyan("Total:") + ` ${status.counts.configs.total} configs
|
|
14471
|
+
`);
|
|
14472
|
+
console.log(chalk.cyan("Drifted:") + ` ${status.health.driftedTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.driftedTargets))} (stored differs from disk)`);
|
|
14473
|
+
console.log(chalk.cyan("Missing:") + ` ${status.health.missingTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.missingTargets))} (file not on disk)`);
|
|
14474
|
+
console.log(chalk.cyan("Secrets:") + ` ${status.health.unredactedSecretFindings === 0 ? chalk.green("0 \u2713") : chalk.red(String(status.health.unredactedSecretFindings) + " \u26A0")} unredacted`);
|
|
14475
|
+
console.log(chalk.cyan("Templates:") + ` ${status.counts.configs.templates} (with {{VAR}} placeholders)`);
|
|
14363
14476
|
});
|
|
14364
14477
|
program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
|
|
14365
14478
|
const { mkdirSync: mk } = await import("fs");
|
|
@@ -14393,9 +14506,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
|
|
|
14393
14506
|
console.log(chalk.cyan("Known files on disk:"));
|
|
14394
14507
|
for (const k of KNOWN_CONFIGS) {
|
|
14395
14508
|
if (k.rulesDir) {
|
|
14396
|
-
|
|
14509
|
+
existsSync13(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail(`${k.rulesDir}/ not found`);
|
|
14397
14510
|
} else {
|
|
14398
|
-
|
|
14511
|
+
existsSync13(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail(`${k.path} not found`);
|
|
14399
14512
|
}
|
|
14400
14513
|
}
|
|
14401
14514
|
const allConfigs = listConfigs();
|
|
@@ -14517,7 +14630,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
14517
14630
|
for (const k of KNOWN_CONFIGS) {
|
|
14518
14631
|
if (k.rulesDir) {
|
|
14519
14632
|
const absDir = expandPath2(k.rulesDir);
|
|
14520
|
-
if (!
|
|
14633
|
+
if (!existsSync13(absDir))
|
|
14521
14634
|
continue;
|
|
14522
14635
|
const { readdirSync: readdirSync5 } = await import("fs");
|
|
14523
14636
|
for (const f of readdirSync5(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
@@ -14526,7 +14639,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
14526
14639
|
}
|
|
14527
14640
|
} else {
|
|
14528
14641
|
const abs = expandPath2(k.path);
|
|
14529
|
-
if (
|
|
14642
|
+
if (existsSync13(abs))
|
|
14530
14643
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
14531
14644
|
}
|
|
14532
14645
|
}
|
|
@@ -14534,7 +14647,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
14534
14647
|
const tick = async () => {
|
|
14535
14648
|
let changed = 0;
|
|
14536
14649
|
for (const [abs, oldMtime] of mtimes) {
|
|
14537
|
-
if (!
|
|
14650
|
+
if (!existsSync13(abs))
|
|
14538
14651
|
continue;
|
|
14539
14652
|
const newMtime = st(abs).mtimeMs;
|
|
14540
14653
|
if (newMtime !== oldMtime) {
|
|
@@ -14546,7 +14659,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
14546
14659
|
for (const k of KNOWN_CONFIGS) {
|
|
14547
14660
|
if (k.rulesDir) {
|
|
14548
14661
|
const absDir = expandPath2(k.rulesDir);
|
|
14549
|
-
if (!
|
|
14662
|
+
if (!existsSync13(absDir))
|
|
14550
14663
|
continue;
|
|
14551
14664
|
for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
14552
14665
|
const abs = join13(absDir, f);
|
|
@@ -14557,7 +14670,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
14557
14670
|
}
|
|
14558
14671
|
} else {
|
|
14559
14672
|
const abs = expandPath2(k.path);
|
|
14560
|
-
if (
|
|
14673
|
+
if (existsSync13(abs) && !mtimes.has(abs)) {
|
|
14561
14674
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
14562
14675
|
changed++;
|
|
14563
14676
|
}
|
|
@@ -14584,11 +14697,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
|
|
|
14584
14697
|
if (!c.target_path)
|
|
14585
14698
|
continue;
|
|
14586
14699
|
const abs = expandPath(c.target_path);
|
|
14587
|
-
if (!
|
|
14700
|
+
if (!existsSync13(abs)) {
|
|
14588
14701
|
missing++;
|
|
14589
14702
|
continue;
|
|
14590
14703
|
}
|
|
14591
|
-
const disk =
|
|
14704
|
+
const disk = readFileSync7(abs, "utf-8");
|
|
14592
14705
|
const { content: redactedDisk } = redactContent(disk, c.format);
|
|
14593
14706
|
if (redactedDisk !== c.content)
|
|
14594
14707
|
drifted++;
|
|
@@ -14626,7 +14739,7 @@ program.command("clean").description("Remove configs from DB whose target files
|
|
|
14626
14739
|
if (!c.target_path)
|
|
14627
14740
|
continue;
|
|
14628
14741
|
const abs = expandPath(c.target_path);
|
|
14629
|
-
if (!
|
|
14742
|
+
if (!existsSync13(abs)) {
|
|
14630
14743
|
if (opts.dryRun) {
|
|
14631
14744
|
console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${c.target_path})`)}`);
|
|
14632
14745
|
} else {
|
|
@@ -14660,39 +14773,39 @@ program.command("bootstrap").description("Install the full @hasna ecosystem: CLI
|
|
|
14660
14773
|
console.log(chalk.bold("@hasna/configs bootstrap") + chalk.dim(` \u2014 installing ${packages.length} ecosystem packages
|
|
14661
14774
|
`));
|
|
14662
14775
|
console.log(chalk.cyan("Installing CLI tools:"));
|
|
14663
|
-
for (const
|
|
14776
|
+
for (const pkg3 of packages) {
|
|
14664
14777
|
if (opts.dryRun) {
|
|
14665
|
-
console.log(chalk.dim(` would install: ${
|
|
14778
|
+
console.log(chalk.dim(` would install: ${pkg3.name}`));
|
|
14666
14779
|
continue;
|
|
14667
14780
|
}
|
|
14668
14781
|
try {
|
|
14669
|
-
const proc = Bun.spawn(["bun", "install", "-g",
|
|
14782
|
+
const proc = Bun.spawn(["bun", "install", "-g", pkg3.name], { stdout: "pipe", stderr: "pipe" });
|
|
14670
14783
|
const code = await proc.exited;
|
|
14671
14784
|
if (code === 0)
|
|
14672
|
-
console.log(chalk.green(" \u2713 ") +
|
|
14785
|
+
console.log(chalk.green(" \u2713 ") + pkg3.name);
|
|
14673
14786
|
else
|
|
14674
|
-
console.log(chalk.yellow(" \u26A0 ") +
|
|
14787
|
+
console.log(chalk.yellow(" \u26A0 ") + pkg3.name + chalk.dim(" (may already be installed)"));
|
|
14675
14788
|
} catch {
|
|
14676
|
-
console.log(chalk.yellow(" \u26A0 ") +
|
|
14789
|
+
console.log(chalk.yellow(" \u26A0 ") + pkg3.name + chalk.dim(" (skipped)"));
|
|
14677
14790
|
}
|
|
14678
14791
|
}
|
|
14679
14792
|
if (!opts.skipMcp) {
|
|
14680
14793
|
console.log(chalk.cyan(`
|
|
14681
14794
|
Registering MCP servers in Claude Code:`));
|
|
14682
|
-
for (const
|
|
14795
|
+
for (const pkg3 of packages) {
|
|
14683
14796
|
if (opts.dryRun) {
|
|
14684
|
-
console.log(chalk.dim(` would register: ${
|
|
14797
|
+
console.log(chalk.dim(` would register: ${pkg3.mcp}`));
|
|
14685
14798
|
continue;
|
|
14686
14799
|
}
|
|
14687
14800
|
try {
|
|
14688
|
-
const proc = Bun.spawn(["claude", "mcp", "add", "--transport", "stdio", "--scope", "user",
|
|
14801
|
+
const proc = Bun.spawn(["claude", "mcp", "add", "--transport", "stdio", "--scope", "user", pkg3.bin, "--", pkg3.mcp], { stdout: "pipe", stderr: "pipe" });
|
|
14689
14802
|
const code = await proc.exited;
|
|
14690
14803
|
if (code === 0)
|
|
14691
|
-
console.log(chalk.green(" \u2713 ") +
|
|
14804
|
+
console.log(chalk.green(" \u2713 ") + pkg3.bin);
|
|
14692
14805
|
else
|
|
14693
|
-
console.log(chalk.dim(" = ") +
|
|
14806
|
+
console.log(chalk.dim(" = ") + pkg3.bin + chalk.dim(" (already registered)"));
|
|
14694
14807
|
} catch {
|
|
14695
|
-
console.log(chalk.yellow(" \u26A0 ") +
|
|
14808
|
+
console.log(chalk.yellow(" \u26A0 ") + pkg3.bin + chalk.dim(" (skipped)"));
|
|
14696
14809
|
}
|
|
14697
14810
|
}
|
|
14698
14811
|
}
|
|
@@ -14720,10 +14833,10 @@ program.command("update").description("Check for updates and install latest vers
|
|
|
14720
14833
|
const proc = Bun.spawn(["npm", "view", "@hasna/configs", "version"], { stdout: "pipe", stderr: "pipe" });
|
|
14721
14834
|
const latest = (await new Response(proc.stdout).text()).trim();
|
|
14722
14835
|
await proc.exited;
|
|
14723
|
-
if (latest ===
|
|
14724
|
-
console.log(chalk.green("\u2713") + ` Already on latest version (${
|
|
14836
|
+
if (latest === pkg2.version) {
|
|
14837
|
+
console.log(chalk.green("\u2713") + ` Already on latest version (${pkg2.version})`);
|
|
14725
14838
|
} else {
|
|
14726
|
-
console.log(`Current: ${chalk.dim(
|
|
14839
|
+
console.log(`Current: ${chalk.dim(pkg2.version)} \u2192 Latest: ${chalk.green(latest)}`);
|
|
14727
14840
|
if (!opts.check) {
|
|
14728
14841
|
console.log(chalk.dim("Installing..."));
|
|
14729
14842
|
const install = Bun.spawn(["bun", "install", "-g", `@hasna/configs@${latest}`], { stdout: "inherit", stderr: "inherit" });
|
|
@@ -14737,9 +14850,9 @@ program.command("update").description("Check for updates and install latest vers
|
|
|
14737
14850
|
});
|
|
14738
14851
|
program.command("feedback <message>").description("Send feedback about this service").option("-e, --email <email>", "Contact email").option("-c, --category <cat>", "Category: bug, feature, general", "general").action(async (message, opts) => {
|
|
14739
14852
|
const db = getDatabase();
|
|
14740
|
-
db.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [message, opts.email || null, opts.category || "general",
|
|
14853
|
+
db.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [message, opts.email || null, opts.category || "general", pkg2.version]);
|
|
14741
14854
|
console.log(chalk.green("\u2713") + " Feedback saved. Thank you!");
|
|
14742
14855
|
});
|
|
14743
|
-
program.version(
|
|
14856
|
+
program.version(pkg2.version).name("configs");
|
|
14744
14857
|
registerEventsCommands(program, { source: "configs" });
|
|
14745
14858
|
program.parse(process.argv);
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,10 @@ 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/
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
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
|
+
import { createRequire as createRequire2 } from "module";
|
|
10242
|
+
|
|
10297
10243
|
// src/lib/apply.ts
|
|
10298
10244
|
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10299
10245
|
import { dirname as dirname2, resolve } from "path";
|
|
@@ -10344,59 +10290,6 @@ async function applyConfigs(configs, opts = {}) {
|
|
|
10344
10290
|
}
|
|
10345
10291
|
return results;
|
|
10346
10292
|
}
|
|
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
10293
|
|
|
10401
10294
|
// src/lib/redact.ts
|
|
10402
10295
|
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 +10471,239 @@ function hasSecrets(content, format) {
|
|
|
10578
10471
|
return scanSecrets(content, format).length > 0;
|
|
10579
10472
|
}
|
|
10580
10473
|
|
|
10474
|
+
// src/status.ts
|
|
10475
|
+
var require2 = createRequire2(import.meta.url);
|
|
10476
|
+
var pkg = require2("../package.json");
|
|
10477
|
+
function activeDatabaseEnv() {
|
|
10478
|
+
if (process.env["HASNA_CONFIGS_DB_PATH"])
|
|
10479
|
+
return "HASNA_CONFIGS_DB_PATH";
|
|
10480
|
+
if (process.env["CONFIGS_DB_PATH"])
|
|
10481
|
+
return "CONFIGS_DB_PATH";
|
|
10482
|
+
return null;
|
|
10483
|
+
}
|
|
10484
|
+
function configuredDatabaseKind() {
|
|
10485
|
+
const value = process.env["HASNA_CONFIGS_DB_PATH"] ?? process.env["CONFIGS_DB_PATH"] ?? "";
|
|
10486
|
+
return value === ":memory:" || value.startsWith("file::memory:") ? "memory" : "file";
|
|
10487
|
+
}
|
|
10488
|
+
function countBy(items, getValue) {
|
|
10489
|
+
const counts = {};
|
|
10490
|
+
for (const item of items) {
|
|
10491
|
+
const value = getValue(item);
|
|
10492
|
+
if (!value)
|
|
10493
|
+
continue;
|
|
10494
|
+
counts[value] = (counts[value] ?? 0) + 1;
|
|
10495
|
+
}
|
|
10496
|
+
return counts;
|
|
10497
|
+
}
|
|
10498
|
+
function tableCount(db, table) {
|
|
10499
|
+
try {
|
|
10500
|
+
const row = db.query(`SELECT COUNT(*) AS count FROM ${table}`).get();
|
|
10501
|
+
return Number(row?.count ?? 0);
|
|
10502
|
+
} catch {
|
|
10503
|
+
return 0;
|
|
10504
|
+
}
|
|
10505
|
+
}
|
|
10506
|
+
function getConfigsStatus(db = getDatabase()) {
|
|
10507
|
+
let databaseReachable = true;
|
|
10508
|
+
let configs = [];
|
|
10509
|
+
let categoryStats = { total: 0 };
|
|
10510
|
+
try {
|
|
10511
|
+
configs = listConfigs(undefined, db);
|
|
10512
|
+
categoryStats = getConfigStats(db);
|
|
10513
|
+
} catch {
|
|
10514
|
+
databaseReachable = false;
|
|
10515
|
+
}
|
|
10516
|
+
const fileConfigs = configs.filter((config) => config.kind === "file");
|
|
10517
|
+
let driftedTargets = 0;
|
|
10518
|
+
let missingTargets = 0;
|
|
10519
|
+
let unredactedSecretFindings = 0;
|
|
10520
|
+
let knownTargets = 0;
|
|
10521
|
+
for (const config of fileConfigs) {
|
|
10522
|
+
unredactedSecretFindings += scanSecrets(config.content, config.format).length;
|
|
10523
|
+
if (!config.target_path)
|
|
10524
|
+
continue;
|
|
10525
|
+
knownTargets += 1;
|
|
10526
|
+
const targetPath = expandPath(config.target_path);
|
|
10527
|
+
if (!existsSync9(targetPath)) {
|
|
10528
|
+
missingTargets += 1;
|
|
10529
|
+
continue;
|
|
10530
|
+
}
|
|
10531
|
+
const disk = readFileSync3(targetPath, "utf-8");
|
|
10532
|
+
const { content: redactedDisk } = redactContent(disk, config.format);
|
|
10533
|
+
if (redactedDisk !== config.content) {
|
|
10534
|
+
driftedTargets += 1;
|
|
10535
|
+
}
|
|
10536
|
+
}
|
|
10537
|
+
const profiles = databaseReachable ? listProfiles(db).length : 0;
|
|
10538
|
+
const machines = databaseReachable ? listMachines(db).length : 0;
|
|
10539
|
+
const profileLinks = databaseReachable ? tableCount(db, "profile_configs") : 0;
|
|
10540
|
+
const snapshots = databaseReachable ? tableCount(db, "config_snapshots") : 0;
|
|
10541
|
+
const byCategory = Object.fromEntries(Object.entries(categoryStats).filter(([key]) => key !== "total"));
|
|
10542
|
+
const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 ? "ok" : "warn";
|
|
10543
|
+
return {
|
|
10544
|
+
service: "configs",
|
|
10545
|
+
schemaVersion: "1.0",
|
|
10546
|
+
package: {
|
|
10547
|
+
name: pkg.name,
|
|
10548
|
+
version: pkg.version
|
|
10549
|
+
},
|
|
10550
|
+
env: {
|
|
10551
|
+
database: {
|
|
10552
|
+
primary: "HASNA_CONFIGS_DB_PATH",
|
|
10553
|
+
fallback: "CONFIGS_DB_PATH",
|
|
10554
|
+
active: activeDatabaseEnv(),
|
|
10555
|
+
kind: configuredDatabaseKind()
|
|
10556
|
+
}
|
|
10557
|
+
},
|
|
10558
|
+
counts: {
|
|
10559
|
+
configs: {
|
|
10560
|
+
total: configs.length,
|
|
10561
|
+
file: fileConfigs.length,
|
|
10562
|
+
reference: configs.filter((config) => config.kind === "reference").length,
|
|
10563
|
+
templates: configs.filter((config) => config.is_template).length
|
|
10564
|
+
},
|
|
10565
|
+
byCategory,
|
|
10566
|
+
byAgent: countBy(configs, (config) => config.agent),
|
|
10567
|
+
byFormat: countBy(configs, (config) => config.format),
|
|
10568
|
+
profiles,
|
|
10569
|
+
profileLinks,
|
|
10570
|
+
machines,
|
|
10571
|
+
snapshots,
|
|
10572
|
+
knownTargets
|
|
10573
|
+
},
|
|
10574
|
+
health: {
|
|
10575
|
+
status,
|
|
10576
|
+
databaseReachable,
|
|
10577
|
+
driftedTargets,
|
|
10578
|
+
missingTargets,
|
|
10579
|
+
unredactedSecretFindings,
|
|
10580
|
+
hasDrift: driftedTargets > 0,
|
|
10581
|
+
hasMissingTargets: missingTargets > 0,
|
|
10582
|
+
hasUnredactedSecrets: unredactedSecretFindings > 0
|
|
10583
|
+
},
|
|
10584
|
+
safety: {
|
|
10585
|
+
includesConfigValues: false,
|
|
10586
|
+
includesPrivatePaths: false,
|
|
10587
|
+
includesHostnames: false,
|
|
10588
|
+
includesSecretValues: false,
|
|
10589
|
+
statusOutputIsMetadataOnly: true
|
|
10590
|
+
}
|
|
10591
|
+
};
|
|
10592
|
+
}
|
|
10593
|
+
// src/db/pg-migrations.ts
|
|
10594
|
+
var PG_MIGRATIONS = [
|
|
10595
|
+
`CREATE TABLE IF NOT EXISTS configs (
|
|
10596
|
+
id TEXT PRIMARY KEY,
|
|
10597
|
+
name TEXT NOT NULL,
|
|
10598
|
+
slug TEXT NOT NULL UNIQUE,
|
|
10599
|
+
kind TEXT NOT NULL DEFAULT 'file',
|
|
10600
|
+
category TEXT NOT NULL,
|
|
10601
|
+
agent TEXT NOT NULL DEFAULT 'global',
|
|
10602
|
+
target_path TEXT,
|
|
10603
|
+
format TEXT NOT NULL DEFAULT 'text',
|
|
10604
|
+
content TEXT NOT NULL DEFAULT '',
|
|
10605
|
+
description TEXT,
|
|
10606
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
10607
|
+
is_template BOOLEAN NOT NULL DEFAULT FALSE,
|
|
10608
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
10609
|
+
created_at TEXT NOT NULL,
|
|
10610
|
+
updated_at TEXT NOT NULL,
|
|
10611
|
+
synced_at TEXT
|
|
10612
|
+
)`,
|
|
10613
|
+
`CREATE TABLE IF NOT EXISTS config_snapshots (
|
|
10614
|
+
id TEXT PRIMARY KEY,
|
|
10615
|
+
config_id TEXT NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
|
|
10616
|
+
content TEXT NOT NULL,
|
|
10617
|
+
version INTEGER NOT NULL,
|
|
10618
|
+
created_at TEXT NOT NULL
|
|
10619
|
+
)`,
|
|
10620
|
+
`CREATE TABLE IF NOT EXISTS profiles (
|
|
10621
|
+
id TEXT PRIMARY KEY,
|
|
10622
|
+
name TEXT NOT NULL,
|
|
10623
|
+
slug TEXT NOT NULL UNIQUE,
|
|
10624
|
+
description TEXT,
|
|
10625
|
+
created_at TEXT NOT NULL,
|
|
10626
|
+
updated_at TEXT NOT NULL
|
|
10627
|
+
)`,
|
|
10628
|
+
`CREATE TABLE IF NOT EXISTS profile_configs (
|
|
10629
|
+
profile_id TEXT NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
|
|
10630
|
+
config_id TEXT NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
|
|
10631
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
10632
|
+
PRIMARY KEY (profile_id, config_id)
|
|
10633
|
+
)`,
|
|
10634
|
+
`CREATE TABLE IF NOT EXISTS machines (
|
|
10635
|
+
id TEXT PRIMARY KEY,
|
|
10636
|
+
hostname TEXT NOT NULL UNIQUE,
|
|
10637
|
+
os TEXT,
|
|
10638
|
+
last_applied_at TEXT,
|
|
10639
|
+
created_at TEXT NOT NULL
|
|
10640
|
+
)`,
|
|
10641
|
+
`CREATE TABLE IF NOT EXISTS feedback (
|
|
10642
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
10643
|
+
message TEXT NOT NULL,
|
|
10644
|
+
email TEXT,
|
|
10645
|
+
category TEXT DEFAULT 'general',
|
|
10646
|
+
version TEXT,
|
|
10647
|
+
machine_id TEXT,
|
|
10648
|
+
created_at TEXT NOT NULL DEFAULT NOW()::text
|
|
10649
|
+
)`
|
|
10650
|
+
];
|
|
10651
|
+
// src/lib/platform-profiles.ts
|
|
10652
|
+
var PLATFORM_PROFILE_PRESETS = [
|
|
10653
|
+
{
|
|
10654
|
+
name: "linux-arm64",
|
|
10655
|
+
description: "Default Linux arm64 profile for linux-node-a/linux-node-b-style machines",
|
|
10656
|
+
selectors: { os: ["linux"], arch: ["arm64"], hostnames: ["linux-node-a", "linux-node-b"] },
|
|
10657
|
+
variables: {
|
|
10658
|
+
WORKSPACE_ROOT: "{{HOME_DIR}}/workspace",
|
|
10659
|
+
BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
|
|
10660
|
+
BUN_PATH: "{{BUN_BIN_DIR}}/bun",
|
|
10661
|
+
PATH_PREFIX: "{{BUN_BIN_DIR}}"
|
|
10662
|
+
}
|
|
10663
|
+
},
|
|
10664
|
+
{
|
|
10665
|
+
name: "macos-arm64",
|
|
10666
|
+
description: "Default macOS arm64 profile for macos-node-a/macos-node-b-style machines",
|
|
10667
|
+
selectors: { os: ["macos"], arch: ["arm64"], hostnames: ["macos-node-a", "macos-node-b"] },
|
|
10668
|
+
variables: {
|
|
10669
|
+
WORKSPACE_ROOT: "{{HOME_DIR}}/Workspace",
|
|
10670
|
+
BUN_BIN_DIR: "{{HOME_DIR}}/.bun/bin",
|
|
10671
|
+
BUN_PATH: "/opt/homebrew/bin/bun",
|
|
10672
|
+
PATH_PREFIX: "/opt/homebrew/bin:{{BUN_BIN_DIR}}"
|
|
10673
|
+
}
|
|
10674
|
+
}
|
|
10675
|
+
];
|
|
10676
|
+
function ensurePlatformProfiles(db) {
|
|
10677
|
+
const configs = listConfigs(undefined, db);
|
|
10678
|
+
const ensured = [];
|
|
10679
|
+
for (const preset of PLATFORM_PROFILE_PRESETS) {
|
|
10680
|
+
let profile;
|
|
10681
|
+
try {
|
|
10682
|
+
profile = getProfile(preset.name, db);
|
|
10683
|
+
if (!profileHasSelectors(profile) || Object.keys(profile.variables).length === 0) {
|
|
10684
|
+
profile = updateProfile(profile.id, {
|
|
10685
|
+
description: profile.description ?? preset.description,
|
|
10686
|
+
selectors: profileHasSelectors(profile) ? profile.selectors : preset.selectors,
|
|
10687
|
+
variables: Object.keys(profile.variables).length > 0 ? profile.variables : preset.variables
|
|
10688
|
+
}, db);
|
|
10689
|
+
}
|
|
10690
|
+
} catch {
|
|
10691
|
+
profile = createProfile(preset, db);
|
|
10692
|
+
}
|
|
10693
|
+
for (const config of configs) {
|
|
10694
|
+
addConfigToProfile(profile.id, config.id, db);
|
|
10695
|
+
}
|
|
10696
|
+
ensured.push(profile);
|
|
10697
|
+
}
|
|
10698
|
+
return ensured;
|
|
10699
|
+
}
|
|
10700
|
+
// src/lib/sync.ts
|
|
10701
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
|
|
10702
|
+
import { extname, join as join10 } from "path";
|
|
10703
|
+
import { homedir as homedir10 } from "os";
|
|
10704
|
+
|
|
10581
10705
|
// src/lib/sync-dir.ts
|
|
10582
|
-
import { existsSync as
|
|
10706
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "fs";
|
|
10583
10707
|
import { join as join9, relative as relative2 } from "path";
|
|
10584
10708
|
import { homedir as homedir9 } from "os";
|
|
10585
10709
|
var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
|
|
@@ -10589,7 +10713,7 @@ function shouldSkip(p) {
|
|
|
10589
10713
|
async function syncFromDir(dir, opts = {}) {
|
|
10590
10714
|
const d = opts.db || getDatabase();
|
|
10591
10715
|
const absDir = expandPath(dir);
|
|
10592
|
-
if (!
|
|
10716
|
+
if (!existsSync10(absDir))
|
|
10593
10717
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
10594
10718
|
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync2(absDir).map((f) => join9(absDir, f)).filter((f) => statSync(f).isFile());
|
|
10595
10719
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
@@ -10601,7 +10725,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
10601
10725
|
continue;
|
|
10602
10726
|
}
|
|
10603
10727
|
try {
|
|
10604
|
-
const content =
|
|
10728
|
+
const content = readFileSync4(file, "utf-8");
|
|
10605
10729
|
if (content.length > 500000) {
|
|
10606
10730
|
result.skipped.push(file + " (too large)");
|
|
10607
10731
|
continue;
|
|
@@ -10696,10 +10820,10 @@ async function syncProject(opts) {
|
|
|
10696
10820
|
const machine = detectMachineContext();
|
|
10697
10821
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
10698
10822
|
const abs = join10(absDir, pf.file);
|
|
10699
|
-
if (!
|
|
10823
|
+
if (!existsSync11(abs))
|
|
10700
10824
|
continue;
|
|
10701
10825
|
try {
|
|
10702
|
-
const rawContent =
|
|
10826
|
+
const rawContent = readFileSync5(abs, "utf-8");
|
|
10703
10827
|
if (rawContent.length > 500000) {
|
|
10704
10828
|
result.skipped.push(pf.file);
|
|
10705
10829
|
continue;
|
|
@@ -10728,11 +10852,11 @@ async function syncProject(opts) {
|
|
|
10728
10852
|
}
|
|
10729
10853
|
}
|
|
10730
10854
|
const rulesDir = join10(absDir, ".claude", "rules");
|
|
10731
|
-
if (
|
|
10855
|
+
if (existsSync11(rulesDir)) {
|
|
10732
10856
|
const mdFiles = readdirSync4(rulesDir).filter((f) => f.endsWith(".md"));
|
|
10733
10857
|
for (const f of mdFiles) {
|
|
10734
10858
|
const abs = join10(rulesDir, f);
|
|
10735
|
-
const raw =
|
|
10859
|
+
const raw = readFileSync5(abs, "utf-8");
|
|
10736
10860
|
const redacted = redactContent(raw, "markdown");
|
|
10737
10861
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
10738
10862
|
const content = machineAware.content;
|
|
@@ -10770,7 +10894,7 @@ async function syncKnown(opts = {}) {
|
|
|
10770
10894
|
for (const known of targets) {
|
|
10771
10895
|
if (known.rulesDir) {
|
|
10772
10896
|
const absDir = expandPath(known.rulesDir);
|
|
10773
|
-
if (!
|
|
10897
|
+
if (!existsSync11(absDir)) {
|
|
10774
10898
|
result.skipped.push(known.rulesDir);
|
|
10775
10899
|
continue;
|
|
10776
10900
|
}
|
|
@@ -10778,7 +10902,7 @@ async function syncKnown(opts = {}) {
|
|
|
10778
10902
|
for (const f of mdFiles) {
|
|
10779
10903
|
const abs2 = join10(absDir, f);
|
|
10780
10904
|
const targetPath = abs2.replace(home, "~");
|
|
10781
|
-
const raw =
|
|
10905
|
+
const raw = readFileSync5(abs2, "utf-8");
|
|
10782
10906
|
const redacted = redactContent(raw, "markdown");
|
|
10783
10907
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
10784
10908
|
const content = machineAware.content;
|
|
@@ -10801,12 +10925,12 @@ async function syncKnown(opts = {}) {
|
|
|
10801
10925
|
continue;
|
|
10802
10926
|
}
|
|
10803
10927
|
const abs = expandPath(known.path);
|
|
10804
|
-
if (!
|
|
10928
|
+
if (!existsSync11(abs)) {
|
|
10805
10929
|
result.skipped.push(known.path);
|
|
10806
10930
|
continue;
|
|
10807
10931
|
}
|
|
10808
10932
|
try {
|
|
10809
|
-
const rawContent =
|
|
10933
|
+
const rawContent = readFileSync5(abs, "utf-8");
|
|
10810
10934
|
if (rawContent.length > 500000) {
|
|
10811
10935
|
result.skipped.push(known.path + " (too large)");
|
|
10812
10936
|
continue;
|
|
@@ -10866,9 +10990,9 @@ function diffConfig(config) {
|
|
|
10866
10990
|
if (!config.target_path)
|
|
10867
10991
|
return "(reference \u2014 no target path)";
|
|
10868
10992
|
const path = expandPath(config.target_path);
|
|
10869
|
-
if (!
|
|
10993
|
+
if (!existsSync11(path))
|
|
10870
10994
|
return `(file not found on disk: ${path})`;
|
|
10871
|
-
const diskContent =
|
|
10995
|
+
const diskContent = readFileSync5(path, "utf-8");
|
|
10872
10996
|
if (diskContent === config.content)
|
|
10873
10997
|
return "(no diff \u2014 identical)";
|
|
10874
10998
|
const stored = config.content.split(`
|
|
@@ -10942,7 +11066,7 @@ function detectFormat(filePath) {
|
|
|
10942
11066
|
return "text";
|
|
10943
11067
|
}
|
|
10944
11068
|
// src/lib/export.ts
|
|
10945
|
-
import { existsSync as
|
|
11069
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
10946
11070
|
import { join as join11, resolve as resolve2 } from "path";
|
|
10947
11071
|
import { tmpdir } from "os";
|
|
10948
11072
|
async function exportConfigs(outputPath, opts = {}) {
|
|
@@ -10974,13 +11098,13 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
10974
11098
|
}
|
|
10975
11099
|
return { path: absOutput, count: configs.length };
|
|
10976
11100
|
} finally {
|
|
10977
|
-
if (
|
|
11101
|
+
if (existsSync12(tmpDir)) {
|
|
10978
11102
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
10979
11103
|
}
|
|
10980
11104
|
}
|
|
10981
11105
|
}
|
|
10982
11106
|
// src/lib/import.ts
|
|
10983
|
-
import { existsSync as
|
|
11107
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync6, rmSync as rmSync2 } from "fs";
|
|
10984
11108
|
import { join as join12, resolve as resolve3 } from "path";
|
|
10985
11109
|
import { tmpdir as tmpdir2 } from "os";
|
|
10986
11110
|
async function importConfigs(bundlePath, opts = {}) {
|
|
@@ -11001,14 +11125,14 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
11001
11125
|
throw new Error(`tar extraction failed: ${stderr}`);
|
|
11002
11126
|
}
|
|
11003
11127
|
const manifestPath = join12(tmpDir, "manifest.json");
|
|
11004
|
-
if (!
|
|
11128
|
+
if (!existsSync13(manifestPath))
|
|
11005
11129
|
throw new Error("Invalid bundle: missing manifest.json");
|
|
11006
|
-
const manifest = JSON.parse(
|
|
11130
|
+
const manifest = JSON.parse(readFileSync6(manifestPath, "utf-8"));
|
|
11007
11131
|
for (const meta of manifest.configs) {
|
|
11008
11132
|
try {
|
|
11009
11133
|
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
11010
11134
|
const contentFile = join12(tmpDir, "contents", `${meta.slug}.${ext}`);
|
|
11011
|
-
const content =
|
|
11135
|
+
const content = existsSync13(contentFile) ? readFileSync6(contentFile, "utf-8") : "";
|
|
11012
11136
|
let existing = null;
|
|
11013
11137
|
try {
|
|
11014
11138
|
existing = getConfig(meta.slug, d);
|
|
@@ -11041,7 +11165,7 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
11041
11165
|
}
|
|
11042
11166
|
return result;
|
|
11043
11167
|
} finally {
|
|
11044
|
-
if (
|
|
11168
|
+
if (existsSync13(tmpDir)) {
|
|
11045
11169
|
rmSync2(tmpDir, { recursive: true, force: true });
|
|
11046
11170
|
}
|
|
11047
11171
|
}
|
|
@@ -11086,6 +11210,7 @@ export {
|
|
|
11086
11210
|
getProfileConfigs,
|
|
11087
11211
|
getProfile,
|
|
11088
11212
|
getDatabase,
|
|
11213
|
+
getConfigsStatus,
|
|
11089
11214
|
getConfigStats,
|
|
11090
11215
|
getConfigById,
|
|
11091
11216
|
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.
|
|
11244
|
+
version: "0.2.37",
|
|
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",
|
package/dist/server/index.js
CHANGED
|
@@ -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.
|
|
17832
|
+
version: "0.2.37",
|
|
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",
|
package/dist/status.d.ts
ADDED
|
@@ -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":"AAEA,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 @@
|
|
|
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.
|
|
3
|
+
"version": "0.2.37",
|
|
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",
|