@hasna/configs 0.1.5 → 0.2.1

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/dist/cli/index.js CHANGED
@@ -2127,6 +2127,9 @@ function getDatabase(path) {
2127
2127
  _db = db;
2128
2128
  return db;
2129
2129
  }
2130
+ function resetDatabase() {
2131
+ _db = null;
2132
+ }
2130
2133
  function applyMigrations(db) {
2131
2134
  let currentVersion = 0;
2132
2135
  try {
@@ -2391,6 +2394,12 @@ var init_snapshots = __esm(() => {
2391
2394
  });
2392
2395
 
2393
2396
  // src/lib/apply.ts
2397
+ var exports_apply = {};
2398
+ __export(exports_apply, {
2399
+ expandPath: () => expandPath,
2400
+ applyConfigs: () => applyConfigs,
2401
+ applyConfig: () => applyConfig
2402
+ });
2394
2403
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
2395
2404
  import { dirname as dirname2, resolve as resolve2 } from "path";
2396
2405
  import { homedir } from "os";
@@ -2715,17 +2724,81 @@ var exports_sync = {};
2715
2724
  __export(exports_sync, {
2716
2725
  syncToDisk: () => syncToDisk,
2717
2726
  syncToDir: () => syncToDir,
2727
+ syncProject: () => syncProject,
2718
2728
  syncKnown: () => syncKnown,
2719
2729
  syncFromDir: () => syncFromDir,
2720
2730
  diffConfig: () => diffConfig,
2721
2731
  detectFormat: () => detectFormat,
2722
2732
  detectCategory: () => detectCategory,
2723
2733
  detectAgent: () => detectAgent,
2734
+ PROJECT_CONFIG_FILES: () => PROJECT_CONFIG_FILES,
2724
2735
  KNOWN_CONFIGS: () => KNOWN_CONFIGS
2725
2736
  });
2726
2737
  import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
2727
2738
  import { extname, join as join3 } from "path";
2728
2739
  import { homedir as homedir3 } from "os";
2740
+ async function syncProject(opts) {
2741
+ const d = opts.db || getDatabase();
2742
+ const absDir = expandPath(opts.projectDir);
2743
+ const projectName = absDir.split("/").pop() || "project";
2744
+ const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
2745
+ const allConfigs = listConfigs(undefined, d);
2746
+ for (const pf of PROJECT_CONFIG_FILES) {
2747
+ const abs = join3(absDir, pf.file);
2748
+ if (!existsSync4(abs))
2749
+ continue;
2750
+ try {
2751
+ const rawContent = readFileSync3(abs, "utf-8");
2752
+ if (rawContent.length > 500000) {
2753
+ result.skipped.push(pf.file);
2754
+ continue;
2755
+ }
2756
+ const { content, isTemplate } = redactContent(rawContent, pf.format);
2757
+ const name = `${projectName}/${pf.file}`;
2758
+ const targetPath = abs.replace(homedir3(), "~");
2759
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
2760
+ const existing = allConfigs.find((c) => c.target_path === targetPath || c.slug === slug);
2761
+ if (!existing) {
2762
+ if (!opts.dryRun)
2763
+ createConfig({ name, category: pf.category, agent: pf.agent, format: pf.format, content, target_path: targetPath, is_template: isTemplate }, d);
2764
+ result.added++;
2765
+ } else if (existing.content !== content) {
2766
+ if (!opts.dryRun)
2767
+ updateConfig(existing.id, { content, is_template: isTemplate }, d);
2768
+ result.updated++;
2769
+ } else {
2770
+ result.unchanged++;
2771
+ }
2772
+ } catch {
2773
+ result.skipped.push(pf.file);
2774
+ }
2775
+ }
2776
+ const rulesDir = join3(absDir, ".claude", "rules");
2777
+ if (existsSync4(rulesDir)) {
2778
+ const mdFiles = readdirSync2(rulesDir).filter((f) => f.endsWith(".md"));
2779
+ for (const f of mdFiles) {
2780
+ const abs = join3(rulesDir, f);
2781
+ const raw = readFileSync3(abs, "utf-8");
2782
+ const { content, isTemplate } = redactContent(raw, "markdown");
2783
+ const name = `${projectName}/rules/${f}`;
2784
+ const targetPath = abs.replace(homedir3(), "~");
2785
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
2786
+ const existing = allConfigs.find((c) => c.target_path === targetPath || c.slug === slug);
2787
+ if (!existing) {
2788
+ if (!opts.dryRun)
2789
+ createConfig({ name, category: "rules", agent: "claude", format: "markdown", content, target_path: targetPath, is_template: isTemplate }, d);
2790
+ result.added++;
2791
+ } else if (existing.content !== content) {
2792
+ if (!opts.dryRun)
2793
+ updateConfig(existing.id, { content, is_template: isTemplate }, d);
2794
+ result.updated++;
2795
+ } else {
2796
+ result.unchanged++;
2797
+ }
2798
+ }
2799
+ }
2800
+ return result;
2801
+ }
2729
2802
  async function syncKnown(opts = {}) {
2730
2803
  const d = opts.db || getDatabase();
2731
2804
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
@@ -2904,7 +2977,7 @@ function detectFormat(filePath) {
2904
2977
  return "ini";
2905
2978
  return "text";
2906
2979
  }
2907
- var KNOWN_CONFIGS;
2980
+ var KNOWN_CONFIGS, PROJECT_CONFIG_FILES;
2908
2981
  var init_sync = __esm(() => {
2909
2982
  init_database();
2910
2983
  init_configs();
@@ -2931,6 +3004,15 @@ var init_sync = __esm(() => {
2931
3004
  { path: "~/.npmrc", name: "npmrc", category: "tools", agent: "npm", format: "ini" },
2932
3005
  { path: "~/.bunfig.toml", name: "bunfig", category: "tools", agent: "global", format: "toml" }
2933
3006
  ];
3007
+ PROJECT_CONFIG_FILES = [
3008
+ { file: "CLAUDE.md", category: "rules", agent: "claude", format: "markdown" },
3009
+ { file: ".claude/settings.json", category: "agent", agent: "claude", format: "json" },
3010
+ { file: ".claude/settings.local.json", category: "agent", agent: "claude", format: "json" },
3011
+ { file: ".mcp.json", category: "mcp", agent: "claude", format: "json" },
3012
+ { file: "AGENTS.md", category: "rules", agent: "codex", format: "markdown" },
3013
+ { file: ".codex/AGENTS.md", category: "rules", agent: "codex", format: "markdown" },
3014
+ { file: "GEMINI.md", category: "rules", agent: "gemini", format: "markdown" }
3015
+ ];
2934
3016
  });
2935
3017
 
2936
3018
  // node_modules/commander/esm.mjs
@@ -3022,6 +3104,7 @@ function getProfileConfigs(profileIdOrSlug, db) {
3022
3104
 
3023
3105
  // src/cli/index.tsx
3024
3106
  init_snapshots();
3107
+ init_database();
3025
3108
  init_apply();
3026
3109
  init_sync();
3027
3110
  init_redact();
@@ -3171,7 +3254,8 @@ function fmtConfig(c, format) {
3171
3254
  ].filter(Boolean).join(`
3172
3255
  `);
3173
3256
  }
3174
- program.command("list").alias("ls").description("List stored configs").option("-c, --category <cat>", "filter by category").option("-a, --agent <agent>", "filter by agent").option("-k, --kind <kind>", "filter by kind (file|reference)").option("-t, --tag <tag>", "filter by tag").option("-s, --search <query>", "search name/description/content").option("-f, --format <fmt>", "output format: table|json|compact", "table").action(async (opts) => {
3257
+ program.command("list").alias("ls").description("List stored configs").option("-c, --category <cat>", "filter by category").option("-a, --agent <agent>", "filter by agent").option("-k, --kind <kind>", "filter by kind (file|reference)").option("-t, --tag <tag>", "filter by tag").option("-s, --search <query>", "search name/description/content").option("-f, --format <fmt>", "output format: table|json|compact", "table").option("--brief", "shorthand for --format compact").action(async (opts) => {
3258
+ const fmt = opts.brief ? "compact" : opts.format;
3175
3259
  const configs = listConfigs({
3176
3260
  category: opts.category,
3177
3261
  agent: opts.agent,
@@ -3183,13 +3267,13 @@ program.command("list").alias("ls").description("List stored configs").option("-
3183
3267
  console.log(chalk.dim("No configs found."));
3184
3268
  return;
3185
3269
  }
3186
- if (opts.format === "json") {
3270
+ if (fmt === "json") {
3187
3271
  console.log(JSON.stringify(configs, null, 2));
3188
3272
  return;
3189
3273
  }
3190
3274
  for (const c of configs) {
3191
- console.log(fmtConfig(c, opts.format));
3192
- if (opts.format === "table")
3275
+ console.log(fmtConfig(c, fmt));
3276
+ if (fmt === "table")
3193
3277
  console.log();
3194
3278
  }
3195
3279
  console.log(chalk.dim(`${configs.length} config(s)`));
@@ -3256,17 +3340,33 @@ program.command("apply <id>").description("Apply a config to its target_path on
3256
3340
  process.exit(1);
3257
3341
  }
3258
3342
  });
3259
- program.command("diff <id>").description("Show diff between stored config and disk").action(async (id) => {
3343
+ program.command("diff [id]").description("Show diff between stored config and disk (omit id for --all)").option("--all", "diff every known config against disk").action(async (id, opts) => {
3260
3344
  try {
3261
- const config = getConfig(id);
3262
- const diff = diffConfig(config);
3263
- console.log(diff);
3345
+ if (id) {
3346
+ const config = getConfig(id);
3347
+ console.log(diffConfig(config));
3348
+ return;
3349
+ }
3350
+ const configs = listConfigs({ kind: "file" });
3351
+ let drifted = 0;
3352
+ for (const c of configs) {
3353
+ if (!c.target_path)
3354
+ continue;
3355
+ const diff = diffConfig(c);
3356
+ if (diff.includes("no diff") || diff.includes("not found"))
3357
+ continue;
3358
+ drifted++;
3359
+ console.log(chalk.bold(c.slug) + chalk.dim(` (${c.target_path})`));
3360
+ console.log(diff);
3361
+ console.log();
3362
+ }
3363
+ console.log(chalk.dim(`${drifted}/${configs.length} drifted`));
3264
3364
  } catch (e) {
3265
3365
  console.error(chalk.red(e instanceof Error ? e.message : String(e)));
3266
3366
  process.exit(1);
3267
3367
  }
3268
3368
  });
3269
- program.command("sync").description("Sync known AI coding configs from disk into DB (claude, codex, gemini, zsh, git, npm)").option("-a, --agent <agent>", "only sync configs for this agent (claude|codex|gemini|zsh|git|npm)").option("-c, --category <cat>", "only sync configs in this category").option("--to-disk", "apply DB configs back to disk instead").option("--dry-run", "preview without writing").option("--list", "show which files would be synced without doing anything").action(async (opts) => {
3369
+ program.command("sync").description("Sync known AI coding configs from disk into DB (claude, codex, gemini, zsh, git, npm)").option("-a, --agent <agent>", "only sync configs for this agent (claude|codex|gemini|zsh|git|npm)").option("-c, --category <cat>", "only sync configs in this category").option("-p, --project [dir]", "sync project-scoped configs (CLAUDE.md, .mcp.json, etc.) from a project dir").option("--to-disk", "apply DB configs back to disk instead").option("--dry-run", "preview without writing").option("--list", "show which files would be synced without doing anything").action(async (opts) => {
3270
3370
  if (opts.list) {
3271
3371
  const targets = KNOWN_CONFIGS.filter((k) => {
3272
3372
  if (opts.agent && k.agent !== opts.agent)
@@ -3281,6 +3381,12 @@ program.command("sync").description("Sync known AI coding configs from disk into
3281
3381
  }
3282
3382
  return;
3283
3383
  }
3384
+ if (opts.project) {
3385
+ const dir = typeof opts.project === "string" ? opts.project : process.cwd();
3386
+ const result = await syncProject({ projectDir: dir, dryRun: opts.dryRun });
3387
+ console.log(chalk.green("\u2713") + ` Project sync: +${result.added} updated:${result.updated} unchanged:${result.unchanged} skipped:${result.skipped.length}`);
3388
+ return;
3389
+ }
3284
3390
  if (opts.toDisk) {
3285
3391
  const result = await syncToDisk({ dryRun: opts.dryRun, agent: opts.agent, category: opts.category });
3286
3392
  console.log(chalk.green("\u2713") + ` Written to disk: updated:${result.updated} unchanged:${result.unchanged} skipped:${result.skipped.length}`);
@@ -3332,13 +3438,22 @@ program.command("whoami").description("Show setup summary").action(async () => {
3332
3438
  }
3333
3439
  });
3334
3440
  var profileCmd = program.command("profile").description("Manage config profiles (named bundles)");
3335
- profileCmd.command("list").description("List all profiles").action(async () => {
3441
+ profileCmd.command("list").description("List all profiles").option("--brief", "compact one-line output").option("-f, --format <fmt>", "table|json|compact", "table").action(async (opts) => {
3442
+ const fmt = opts.brief ? "compact" : opts.format;
3336
3443
  const profiles = listProfiles();
3337
3444
  if (profiles.length === 0) {
3338
3445
  console.log(chalk.dim("No profiles."));
3339
3446
  return;
3340
3447
  }
3448
+ if (fmt === "json") {
3449
+ console.log(JSON.stringify(profiles, null, 2));
3450
+ return;
3451
+ }
3341
3452
  for (const p of profiles) {
3453
+ if (fmt === "compact") {
3454
+ console.log(`${p.slug} ${getProfileConfigs(p.id).length} configs`);
3455
+ continue;
3456
+ }
3342
3457
  const configs = getProfileConfigs(p.id);
3343
3458
  console.log(`${chalk.bold(p.name)} ${chalk.dim(`(${p.slug})`)} \u2014 ${configs.length} config(s)`);
3344
3459
  if (p.description)
@@ -3518,5 +3633,305 @@ program.command("scan [id]").description("Scan configs for secrets. Defaults to
3518
3633
  Run with --fix to redact in-place.`));
3519
3634
  }
3520
3635
  });
3636
+ var mcpCmd = program.command("mcp").description("Install/remove MCP server for AI agents");
3637
+ mcpCmd.command("install").alias("add").description("Install configs MCP server into an agent").option("--claude", "install into Claude Code").option("--codex", "install into Codex").option("--gemini", "install into Gemini").option("--all", "install into all agents").action(async (opts) => {
3638
+ const targets = opts.all ? ["claude", "codex", "gemini"] : [
3639
+ ...opts.claude ? ["claude"] : [],
3640
+ ...opts.codex ? ["codex"] : [],
3641
+ ...opts.gemini ? ["gemini"] : []
3642
+ ];
3643
+ if (targets.length === 0) {
3644
+ console.log(chalk.dim("Specify --claude, --codex, --gemini, or --all"));
3645
+ return;
3646
+ }
3647
+ for (const target of targets) {
3648
+ try {
3649
+ if (target === "claude") {
3650
+ const proc = Bun.spawn(["claude", "mcp", "add", "--transport", "stdio", "--scope", "user", "configs", "--", "configs-mcp"], { stdout: "inherit", stderr: "inherit" });
3651
+ await proc.exited;
3652
+ console.log(chalk.green("\u2713") + " Installed into Claude Code");
3653
+ } else if (target === "codex") {
3654
+ const { appendFileSync, existsSync: ex } = await import("fs");
3655
+ const { join: j } = await import("path");
3656
+ const configPath = j(homedir4(), ".codex", "config.toml");
3657
+ const block = `
3658
+ [mcp_servers.configs]
3659
+ command = "configs-mcp"
3660
+ args = []
3661
+ `;
3662
+ if (ex(configPath)) {
3663
+ const content = readFileSync5(configPath, "utf-8");
3664
+ if (content.includes("[mcp_servers.configs]")) {
3665
+ console.log(chalk.dim("= Already installed in Codex"));
3666
+ continue;
3667
+ }
3668
+ }
3669
+ appendFileSync(configPath, block);
3670
+ console.log(chalk.green("\u2713") + " Installed into Codex");
3671
+ } else if (target === "gemini") {
3672
+ const { readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
3673
+ const { join: j } = await import("path");
3674
+ const configPath = j(homedir4(), ".gemini", "settings.json");
3675
+ let settings = {};
3676
+ if (ex(configPath)) {
3677
+ try {
3678
+ settings = JSON.parse(rf(configPath, "utf-8"));
3679
+ } catch {}
3680
+ }
3681
+ const mcpServers = settings["mcpServers"] ?? {};
3682
+ mcpServers["configs"] = { command: "configs-mcp", args: [] };
3683
+ settings["mcpServers"] = mcpServers;
3684
+ wf(configPath, JSON.stringify(settings, null, 2) + `
3685
+ `, "utf-8");
3686
+ console.log(chalk.green("\u2713") + " Installed into Gemini");
3687
+ }
3688
+ } catch (e) {
3689
+ console.error(chalk.red(`\u2717 Failed to install into ${target}: ${e instanceof Error ? e.message : String(e)}`));
3690
+ }
3691
+ }
3692
+ });
3693
+ mcpCmd.command("uninstall").alias("remove").description("Remove configs MCP server from agents").option("--claude", "remove from Claude Code").option("--all", "remove from all agents").action(async (opts) => {
3694
+ if (opts.claude || opts.all) {
3695
+ const proc = Bun.spawn(["claude", "mcp", "remove", "configs"], { stdout: "inherit", stderr: "inherit" });
3696
+ await proc.exited;
3697
+ console.log(chalk.green("\u2713") + " Removed from Claude Code");
3698
+ }
3699
+ });
3700
+ 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) => {
3701
+ const dbPath = join6(homedir4(), ".configs", "configs.db");
3702
+ if (opts.force && existsSync7(dbPath)) {
3703
+ const { rmSync: rmSync3 } = await import("fs");
3704
+ rmSync3(dbPath);
3705
+ console.log(chalk.dim("Deleted existing DB."));
3706
+ resetDatabase();
3707
+ }
3708
+ console.log(chalk.bold(`@hasna/configs \u2014 initializing
3709
+ `));
3710
+ const result = await syncKnown({});
3711
+ console.log(chalk.green("\u2713") + ` Synced: +${result.added} updated:${result.updated} unchanged:${result.unchanged}`);
3712
+ if (result.skipped.length > 0) {
3713
+ console.log(chalk.dim(" skipped: " + result.skipped.join(", ")));
3714
+ }
3715
+ const refs = [
3716
+ { slug: "workspace-structure", name: "Workspace Structure", category: "workspace", content: `# Workspace Structure
3717
+
3718
+ See ~/.claude/rules/workspace.md for full conventions.`, desc: "~/Workspace/ hierarchy and naming" },
3719
+ { slug: "secrets-schema", name: "Secrets Schema", category: "secrets_schema", content: `# .secrets Schema
3720
+
3721
+ Location: ~/.secrets (sourced by ~/.zshrc)
3722
+ Format: export KEY_NAME="value"
3723
+
3724
+ Keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, EXA_API_KEY, NPM_TOKEN, GITHUB_TOKEN`, desc: "Shape of ~/.secrets (no values)" }
3725
+ ];
3726
+ for (const ref of refs) {
3727
+ try {
3728
+ getConfig(ref.slug);
3729
+ } catch {
3730
+ createConfig({ name: ref.name, category: ref.category, agent: "global", format: "markdown", content: ref.content, kind: "reference", description: ref.desc });
3731
+ }
3732
+ }
3733
+ try {
3734
+ getProfile("my-setup");
3735
+ } catch {
3736
+ const p = createProfile({ name: "my-setup", description: "Default profile with all known configs" });
3737
+ const allConfigs = listConfigs();
3738
+ for (const c of allConfigs)
3739
+ addConfigToProfile(p.id, c.id);
3740
+ console.log(chalk.green("\u2713") + ` Created profile "my-setup" with ${allConfigs.length} configs`);
3741
+ }
3742
+ const stats = getConfigStats();
3743
+ console.log(chalk.bold(`
3744
+ DB stats:`));
3745
+ for (const [key, count] of Object.entries(stats)) {
3746
+ if (count > 0)
3747
+ console.log(` ${key.padEnd(18)} ${count}`);
3748
+ }
3749
+ console.log(chalk.dim(`
3750
+ DB: ${dbPath}`));
3751
+ });
3752
+ program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").action(async () => {
3753
+ const dbPath = join6(homedir4(), ".configs", "configs.db");
3754
+ const stats = getConfigStats();
3755
+ const { statSync: st } = await import("fs");
3756
+ const dbSize = existsSync7(dbPath) ? st(dbPath).size : 0;
3757
+ console.log(chalk.bold("@hasna/configs") + chalk.dim(` v${pkg.version}`));
3758
+ console.log(chalk.cyan("DB:") + ` ${dbPath} (${(dbSize / 1024).toFixed(1)}KB)`);
3759
+ console.log(chalk.cyan("Total:") + ` ${stats["total"] || 0} configs
3760
+ `);
3761
+ const allKnown = listConfigs({ kind: "file" });
3762
+ let drifted = 0;
3763
+ let missing = 0;
3764
+ let secrets = 0;
3765
+ let templates = 0;
3766
+ for (const c of allKnown) {
3767
+ if (!c.target_path)
3768
+ continue;
3769
+ const path = expandPath(c.target_path);
3770
+ if (!existsSync7(path)) {
3771
+ missing++;
3772
+ continue;
3773
+ }
3774
+ const disk = readFileSync5(path, "utf-8");
3775
+ const { content: redactedDisk } = redactContent(disk, c.format);
3776
+ if (redactedDisk !== c.content)
3777
+ drifted++;
3778
+ if (c.is_template)
3779
+ templates++;
3780
+ const found = scanSecrets(c.content, c.format);
3781
+ secrets += found.length;
3782
+ }
3783
+ console.log(chalk.cyan("Drifted:") + ` ${drifted === 0 ? chalk.green("0") : chalk.yellow(String(drifted))} (stored \u2260 disk)`);
3784
+ console.log(chalk.cyan("Missing:") + ` ${missing === 0 ? chalk.green("0") : chalk.yellow(String(missing))} (file not on disk)`);
3785
+ console.log(chalk.cyan("Secrets:") + ` ${secrets === 0 ? chalk.green("0 \u2713") : chalk.red(String(secrets) + " \u26A0")} unredacted`);
3786
+ console.log(chalk.cyan("Templates:") + ` ${templates} (with {{VAR}} placeholders)`);
3787
+ });
3788
+ program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
3789
+ const { mkdirSync: mk } = await import("fs");
3790
+ const backupDir = join6(homedir4(), ".configs", "backups");
3791
+ mk(backupDir, { recursive: true });
3792
+ const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
3793
+ const outPath = join6(backupDir, `configs-${ts}.tar.gz`);
3794
+ const result = await exportConfigs(outPath);
3795
+ const { statSync: st } = await import("fs");
3796
+ const size = st(outPath).size;
3797
+ console.log(chalk.green("\u2713") + ` Backup: ${result.count} configs \u2192 ${outPath} (${(size / 1024).toFixed(1)}KB)`);
3798
+ });
3799
+ program.command("restore <file>").description("Restore configs from a backup file").option("--overwrite", "overwrite existing configs (default: skip)").action(async (file, opts) => {
3800
+ const result = await importConfigs(file, { conflict: opts.overwrite ? "overwrite" : "skip" });
3801
+ console.log(chalk.green("\u2713") + ` Restored: +${result.created} updated:${result.updated} skipped:${result.skipped}`);
3802
+ if (result.errors.length > 0) {
3803
+ for (const e of result.errors)
3804
+ console.log(chalk.red(" " + e));
3805
+ }
3806
+ });
3807
+ program.command("doctor").description("Validate configs: syntax, permissions, missing files, secrets").action(async () => {
3808
+ let issues = 0;
3809
+ const pass = (msg) => console.log(chalk.green(" \u2713 ") + msg);
3810
+ const fail = (msg) => {
3811
+ issues++;
3812
+ console.log(chalk.red(" \u2717 ") + msg);
3813
+ };
3814
+ console.log(chalk.bold(`Config Doctor
3815
+ `));
3816
+ console.log(chalk.cyan("Known files on disk:"));
3817
+ for (const k of KNOWN_CONFIGS) {
3818
+ if (k.rulesDir) {
3819
+ existsSync7(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : fail(`${k.rulesDir}/ not found`);
3820
+ } else {
3821
+ existsSync7(expandPath(k.path)) ? pass(k.path) : fail(`${k.path} not found`);
3822
+ }
3823
+ }
3824
+ const allConfigs = listConfigs();
3825
+ console.log(chalk.cyan(`
3826
+ Stored configs (${allConfigs.length}):`));
3827
+ let validCount = 0;
3828
+ for (const c of allConfigs) {
3829
+ if (c.format === "json") {
3830
+ try {
3831
+ JSON.parse(c.content);
3832
+ validCount++;
3833
+ } catch {
3834
+ fail(`${c.slug}: invalid JSON`);
3835
+ }
3836
+ } else {
3837
+ validCount++;
3838
+ }
3839
+ }
3840
+ pass(`${validCount}/${allConfigs.length} valid syntax`);
3841
+ let secretCount = 0;
3842
+ for (const c of allConfigs) {
3843
+ const found = scanSecrets(c.content, c.format);
3844
+ secretCount += found.length;
3845
+ }
3846
+ secretCount === 0 ? pass("No unredacted secrets") : fail(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
3847
+ console.log(`
3848
+ ${issues === 0 ? chalk.green("\u2713 All checks passed") : chalk.yellow(`${issues} issue(s) found`)}`);
3849
+ });
3850
+ program.command("completions [shell]").description("Output shell completion script (zsh or bash)").action(async (shell) => {
3851
+ const sh = shell || "zsh";
3852
+ if (sh === "zsh") {
3853
+ console.log(`#compdef configs
3854
+ _configs() {
3855
+ local -a commands
3856
+ commands=(
3857
+ 'list:List stored configs'
3858
+ 'show:Show a config'
3859
+ 'add:Ingest a file into the DB'
3860
+ 'apply:Apply a config to disk'
3861
+ 'diff:Show diff stored vs disk'
3862
+ 'sync:Sync known configs from disk'
3863
+ 'export:Export as tar.gz'
3864
+ 'import:Import from tar.gz'
3865
+ 'whoami:Setup summary'
3866
+ 'status:Health check'
3867
+ 'init:First-time setup'
3868
+ 'scan:Scan for secrets'
3869
+ 'profile:Manage profiles'
3870
+ 'snapshot:Version history'
3871
+ 'template:Template operations'
3872
+ 'mcp:Install MCP server'
3873
+ 'backup:Export to timestamped backup'
3874
+ 'restore:Import from backup'
3875
+ 'doctor:Validate configs'
3876
+ 'completions:Output shell completions'
3877
+ )
3878
+ _describe 'command' commands
3879
+ }
3880
+ compdef _configs configs`);
3881
+ } else {
3882
+ console.log(`# bash completion for configs
3883
+ _configs_completions() {
3884
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
3885
+ local commands="list show add apply diff sync export import whoami status init scan profile snapshot template mcp backup restore doctor completions"
3886
+ COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
3887
+ }
3888
+ complete -F _configs_completions configs`);
3889
+ }
3890
+ });
3891
+ program.command("watch").description("Watch known config files for changes and auto-sync to DB").option("-i, --interval <ms>", "poll interval in milliseconds", "3000").action(async (opts) => {
3892
+ const interval = Number(opts.interval);
3893
+ const { statSync: st } = await import("fs");
3894
+ const { expandPath: expandPath2 } = await Promise.resolve().then(() => (init_apply(), exports_apply));
3895
+ console.log(chalk.bold("@hasna/configs watch") + chalk.dim(` \u2014 polling every ${interval}ms`));
3896
+ console.log(chalk.dim(`Watching known config files for changes\u2026
3897
+ `));
3898
+ const mtimes = new Map;
3899
+ for (const k of KNOWN_CONFIGS) {
3900
+ if (k.rulesDir) {
3901
+ const absDir = expandPath2(k.rulesDir);
3902
+ if (!existsSync7(absDir))
3903
+ continue;
3904
+ const { readdirSync: readdirSync3 } = await import("fs");
3905
+ for (const f of readdirSync3(absDir).filter((f2) => f2.endsWith(".md"))) {
3906
+ const abs = join6(absDir, f);
3907
+ mtimes.set(abs, st(abs).mtimeMs);
3908
+ }
3909
+ } else {
3910
+ const abs = expandPath2(k.path);
3911
+ if (existsSync7(abs))
3912
+ mtimes.set(abs, st(abs).mtimeMs);
3913
+ }
3914
+ }
3915
+ console.log(chalk.dim(`Tracking ${mtimes.size} files`));
3916
+ const tick = async () => {
3917
+ let changed = 0;
3918
+ for (const [abs, oldMtime] of mtimes) {
3919
+ if (!existsSync7(abs))
3920
+ continue;
3921
+ const newMtime = st(abs).mtimeMs;
3922
+ if (newMtime !== oldMtime) {
3923
+ changed++;
3924
+ mtimes.set(abs, newMtime);
3925
+ }
3926
+ }
3927
+ if (changed > 0) {
3928
+ const result = await syncKnown({});
3929
+ const ts = new Date().toLocaleTimeString();
3930
+ console.log(`${chalk.dim(ts)} ${chalk.green("\u2713")} ${changed} file(s) changed \u2192 synced +${result.added} updated:${result.updated}`);
3931
+ }
3932
+ };
3933
+ setInterval(tick, interval);
3934
+ await new Promise(() => {});
3935
+ });
3521
3936
  program.version(pkg.version).name("configs");
3522
3937
  program.parse(process.argv);
@@ -11,6 +11,18 @@ export interface KnownConfig {
11
11
  rulesDir?: string;
12
12
  }
13
13
  export declare const KNOWN_CONFIGS: KnownConfig[];
14
+ export declare const PROJECT_CONFIG_FILES: {
15
+ file: string;
16
+ category: ConfigCategory;
17
+ agent: ConfigAgent;
18
+ format: ConfigFormat;
19
+ }[];
20
+ export interface SyncProjectOptions {
21
+ db?: ReturnType<typeof getDatabase>;
22
+ dryRun?: boolean;
23
+ projectDir: string;
24
+ }
25
+ export declare function syncProject(opts: SyncProjectOptions): Promise<SyncResult>;
14
26
  export interface SyncKnownOptions {
15
27
  db?: ReturnType<typeof getDatabase>;
16
28
  dryRun?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/lib/sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACvG,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAShD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EAiCtC,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,CAAC,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4EhF;AAGD,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAgBlF;AAGD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAqBjD;AAGD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAU/D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CASzD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAQ3D;AAGD,OAAO,EAAE,MAAM,EAAE,CAAC;AAClB,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/lib/sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACvG,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAShD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EAiCtC,CAAC;AAIF,eAAO,MAAM,oBAAoB;;cAC2B,cAAc;WAAsB,WAAW;YAAwB,YAAY;GAO9I,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,EAAE,CAAC,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC,CAuD/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,CAAC,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4EhF;AAGD,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAgBlF;AAGD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAqBjD;AAGD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAU/D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CASzD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAQ3D;AAGD,OAAO,EAAE,MAAM,EAAE,CAAC;AAClB,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";;;;;AA2MA,wBAAgD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";;;;;AAkPA,wBAAgD"}
@@ -2083,14 +2083,64 @@ async function applyConfigs(configs, opts = {}) {
2083
2083
  return results;
2084
2084
  }
2085
2085
 
2086
- // src/lib/sync.ts
2087
- import { extname, join as join3 } from "path";
2088
- import { homedir as homedir3 } from "os";
2089
-
2090
2086
  // src/lib/sync-dir.ts
2091
2087
  import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
2092
- import { join as join2, relative } from "path";
2088
+ import { join as join3, relative } from "path";
2089
+ import { homedir as homedir3 } from "os";
2090
+
2091
+ // src/lib/sync.ts
2092
+ import { extname, join as join2 } from "path";
2093
2093
  import { homedir as homedir2 } from "os";
2094
+ function detectCategory(filePath) {
2095
+ const p = filePath.toLowerCase().replace(homedir2(), "~");
2096
+ if (p.includes("/.claude/rules/") || p.endsWith("claude.md") || p.endsWith("agents.md") || p.endsWith("gemini.md"))
2097
+ return "rules";
2098
+ if (p.includes("/.claude/") || p.includes("/.codex/") || p.includes("/.gemini/") || p.includes("/.cursor/"))
2099
+ return "agent";
2100
+ if (p.includes(".mcp.json") || p.includes("mcp"))
2101
+ return "mcp";
2102
+ if (p.includes(".zshrc") || p.includes(".zprofile") || p.includes(".bashrc") || p.includes(".bash_profile"))
2103
+ return "shell";
2104
+ if (p.includes(".gitconfig") || p.includes(".gitignore"))
2105
+ return "git";
2106
+ if (p.includes(".npmrc") || p.includes("tsconfig") || p.includes("bunfig"))
2107
+ return "tools";
2108
+ if (p.includes(".secrets"))
2109
+ return "secrets_schema";
2110
+ return "tools";
2111
+ }
2112
+ function detectAgent(filePath) {
2113
+ const p = filePath.toLowerCase().replace(homedir2(), "~");
2114
+ if (p.includes("/.claude/") || p.endsWith("claude.md"))
2115
+ return "claude";
2116
+ if (p.includes("/.codex/") || p.endsWith("agents.md"))
2117
+ return "codex";
2118
+ if (p.includes("/.gemini/") || p.endsWith("gemini.md"))
2119
+ return "gemini";
2120
+ if (p.includes(".zshrc") || p.includes(".zprofile") || p.includes(".bashrc"))
2121
+ return "zsh";
2122
+ if (p.includes(".gitconfig") || p.includes(".gitignore"))
2123
+ return "git";
2124
+ if (p.includes(".npmrc"))
2125
+ return "npm";
2126
+ return "global";
2127
+ }
2128
+ function detectFormat(filePath) {
2129
+ const ext = extname(filePath).toLowerCase();
2130
+ if (ext === ".json")
2131
+ return "json";
2132
+ if (ext === ".toml")
2133
+ return "toml";
2134
+ if (ext === ".yaml" || ext === ".yml")
2135
+ return "yaml";
2136
+ if (ext === ".md" || ext === ".markdown")
2137
+ return "markdown";
2138
+ if (ext === ".ini" || ext === ".cfg")
2139
+ return "ini";
2140
+ return "text";
2141
+ }
2142
+
2143
+ // src/lib/sync-dir.ts
2094
2144
  var SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
2095
2145
  function shouldSkip(p) {
2096
2146
  return SKIP.some((s) => p.includes(s));
@@ -2100,9 +2150,9 @@ async function syncFromDir(dir, opts = {}) {
2100
2150
  const absDir = expandPath(dir);
2101
2151
  if (!existsSync3(absDir))
2102
2152
  return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
2103
- const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join2(absDir, f)).filter((f) => statSync(f).isFile());
2153
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join3(absDir, f)).filter((f) => statSync(f).isFile());
2104
2154
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
2105
- const home = homedir2();
2155
+ const home = homedir3();
2106
2156
  const allConfigs = listConfigs(undefined, d);
2107
2157
  for (const file of files) {
2108
2158
  if (shouldSkip(file)) {
@@ -2136,7 +2186,7 @@ async function syncFromDir(dir, opts = {}) {
2136
2186
  }
2137
2187
  async function syncToDir(dir, opts = {}) {
2138
2188
  const d = opts.db || getDatabase();
2139
- const home = homedir2();
2189
+ const home = homedir3();
2140
2190
  const absDir = expandPath(dir);
2141
2191
  const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
2142
2192
  const configs = listConfigs(undefined, d).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
@@ -2155,7 +2205,7 @@ async function syncToDir(dir, opts = {}) {
2155
2205
  }
2156
2206
  function walkDir(dir, files = []) {
2157
2207
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
2158
- const full = join2(dir, entry.name);
2208
+ const full = join3(dir, entry.name);
2159
2209
  if (shouldSkip(full))
2160
2210
  continue;
2161
2211
  if (entry.isDirectory())
@@ -2165,57 +2215,10 @@ function walkDir(dir, files = []) {
2165
2215
  }
2166
2216
  return files;
2167
2217
  }
2168
- // src/lib/sync.ts
2169
- function detectCategory(filePath) {
2170
- const p = filePath.toLowerCase().replace(homedir3(), "~");
2171
- if (p.includes("/.claude/rules/") || p.endsWith("claude.md") || p.endsWith("agents.md") || p.endsWith("gemini.md"))
2172
- return "rules";
2173
- if (p.includes("/.claude/") || p.includes("/.codex/") || p.includes("/.gemini/") || p.includes("/.cursor/"))
2174
- return "agent";
2175
- if (p.includes(".mcp.json") || p.includes("mcp"))
2176
- return "mcp";
2177
- if (p.includes(".zshrc") || p.includes(".zprofile") || p.includes(".bashrc") || p.includes(".bash_profile"))
2178
- return "shell";
2179
- if (p.includes(".gitconfig") || p.includes(".gitignore"))
2180
- return "git";
2181
- if (p.includes(".npmrc") || p.includes("tsconfig") || p.includes("bunfig"))
2182
- return "tools";
2183
- if (p.includes(".secrets"))
2184
- return "secrets_schema";
2185
- return "tools";
2186
- }
2187
- function detectAgent(filePath) {
2188
- const p = filePath.toLowerCase().replace(homedir3(), "~");
2189
- if (p.includes("/.claude/") || p.endsWith("claude.md"))
2190
- return "claude";
2191
- if (p.includes("/.codex/") || p.endsWith("agents.md"))
2192
- return "codex";
2193
- if (p.includes("/.gemini/") || p.endsWith("gemini.md"))
2194
- return "gemini";
2195
- if (p.includes(".zshrc") || p.includes(".zprofile") || p.includes(".bashrc"))
2196
- return "zsh";
2197
- if (p.includes(".gitconfig") || p.includes(".gitignore"))
2198
- return "git";
2199
- if (p.includes(".npmrc"))
2200
- return "npm";
2201
- return "global";
2202
- }
2203
- function detectFormat(filePath) {
2204
- const ext = extname(filePath).toLowerCase();
2205
- if (ext === ".json")
2206
- return "json";
2207
- if (ext === ".toml")
2208
- return "toml";
2209
- if (ext === ".yaml" || ext === ".yml")
2210
- return "yaml";
2211
- if (ext === ".md" || ext === ".markdown")
2212
- return "markdown";
2213
- if (ext === ".ini" || ext === ".cfg")
2214
- return "ini";
2215
- return "text";
2216
- }
2217
2218
 
2218
2219
  // src/server/index.ts
2220
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
2221
+ import { join as join4, extname as extname2 } from "path";
2219
2222
  var PORT = Number(process.env["CONFIGS_PORT"] ?? 3457);
2220
2223
  function pickFields(obj, fields) {
2221
2224
  if (!fields)
@@ -2376,8 +2379,38 @@ app.post("/api/machines", async (c) => {
2376
2379
  return c.json({ error: e instanceof Error ? e.message : String(e) }, 422);
2377
2380
  }
2378
2381
  });
2379
- app.get("/health", (c) => c.json({ ok: true, version: "0.1.0" }));
2380
- console.log(`configs-serve listening on http://localhost:${PORT}`);
2382
+ app.get("/health", (c) => c.json({ ok: true, version: "0.1.5" }));
2383
+ var MIME = { ".html": "text/html", ".js": "application/javascript", ".css": "text/css", ".json": "application/json", ".svg": "image/svg+xml", ".png": "image/png", ".ico": "image/x-icon" };
2384
+ function findDashboardDir() {
2385
+ const candidates = [
2386
+ join4(import.meta.dir, "../../dashboard/dist"),
2387
+ join4(import.meta.dir, "../dashboard/dist"),
2388
+ join4(import.meta.dir, "../../../dashboard/dist")
2389
+ ];
2390
+ for (const dir of candidates) {
2391
+ if (existsSync4(join4(dir, "index.html")))
2392
+ return dir;
2393
+ }
2394
+ return null;
2395
+ }
2396
+ var dashDir = findDashboardDir();
2397
+ if (dashDir) {
2398
+ app.get("/*", (c) => {
2399
+ const url = new URL(c.req.url);
2400
+ let filePath = url.pathname === "/" ? "/index.html" : url.pathname;
2401
+ let absPath = join4(dashDir, filePath);
2402
+ if (!existsSync4(absPath))
2403
+ absPath = join4(dashDir, "index.html");
2404
+ if (!existsSync4(absPath))
2405
+ return c.json({ error: "Not found" }, 404);
2406
+ const content = readFileSync3(absPath);
2407
+ const ext = extname2(absPath);
2408
+ return new Response(content, {
2409
+ headers: { "Content-Type": MIME[ext] || "application/octet-stream" }
2410
+ });
2411
+ });
2412
+ }
2413
+ console.log(`configs-serve listening on http://localhost:${PORT}${dashDir ? " (dashboard: /" : " (no dashboard found)"}`);
2381
2414
  var server_default = { port: PORT, fetch: app.fetch };
2382
2415
  export {
2383
2416
  server_default as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/configs",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
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",