@hasna/configs 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +388 -215
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -17,6 +17,16 @@ var __toESM = (mod, isNodeMode, target) => {
17
17
  return to;
18
18
  };
19
19
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
20
30
  var __require = import.meta.require;
21
31
 
22
32
  // node_modules/commander/lib/error.js
@@ -2053,49 +2063,28 @@ var require_commander = __commonJS((exports) => {
2053
2063
  exports.InvalidOptionArgumentError = InvalidArgumentError;
2054
2064
  });
2055
2065
 
2056
- // node_modules/commander/esm.mjs
2057
- var import__ = __toESM(require_commander(), 1);
2058
- var {
2059
- program,
2060
- createCommand,
2061
- createArgument,
2062
- createOption,
2063
- CommanderError,
2064
- InvalidArgumentError,
2065
- InvalidOptionArgumentError,
2066
- Command,
2067
- Argument,
2068
- Option,
2069
- Help
2070
- } = import__.default;
2071
-
2072
- // src/cli/index.tsx
2073
- import chalk from "chalk";
2074
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
2075
- import { homedir as homedir3 } from "os";
2076
- import { join as join5, resolve as resolve5 } from "path";
2077
-
2078
2066
  // src/types/index.ts
2079
- class ConfigNotFoundError extends Error {
2080
- constructor(id) {
2081
- super(`Config not found: ${id}`);
2082
- this.name = "ConfigNotFoundError";
2083
- }
2084
- }
2085
-
2086
- class ProfileNotFoundError extends Error {
2087
- constructor(id) {
2088
- super(`Profile not found: ${id}`);
2089
- this.name = "ProfileNotFoundError";
2090
- }
2091
- }
2092
-
2093
- class ConfigApplyError extends Error {
2094
- constructor(message) {
2095
- super(message);
2096
- this.name = "ConfigApplyError";
2097
- }
2098
- }
2067
+ var ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError;
2068
+ var init_types = __esm(() => {
2069
+ ConfigNotFoundError = class ConfigNotFoundError extends Error {
2070
+ constructor(id) {
2071
+ super(`Config not found: ${id}`);
2072
+ this.name = "ConfigNotFoundError";
2073
+ }
2074
+ };
2075
+ ProfileNotFoundError = class ProfileNotFoundError extends Error {
2076
+ constructor(id) {
2077
+ super(`Profile not found: ${id}`);
2078
+ this.name = "ProfileNotFoundError";
2079
+ }
2080
+ };
2081
+ ConfigApplyError = class ConfigApplyError extends Error {
2082
+ constructor(message) {
2083
+ super(message);
2084
+ this.name = "ConfigApplyError";
2085
+ }
2086
+ };
2087
+ });
2099
2088
 
2100
2089
  // src/db/database.ts
2101
2090
  import { Database } from "bun:sqlite";
@@ -2126,8 +2115,35 @@ function now() {
2126
2115
  function slugify(name) {
2127
2116
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
2128
2117
  }
2129
- var MIGRATIONS = [
2130
- `
2118
+ function getDatabase(path) {
2119
+ if (_db)
2120
+ return _db;
2121
+ const dbPath = path || getDbPath();
2122
+ ensureDir(dbPath);
2123
+ const db = new Database(dbPath);
2124
+ db.run("PRAGMA journal_mode = WAL");
2125
+ db.run("PRAGMA foreign_keys = ON");
2126
+ applyMigrations(db);
2127
+ _db = db;
2128
+ return db;
2129
+ }
2130
+ function applyMigrations(db) {
2131
+ let currentVersion = 0;
2132
+ try {
2133
+ const row = db.query("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get();
2134
+ currentVersion = row?.version ?? 0;
2135
+ } catch {
2136
+ currentVersion = 0;
2137
+ }
2138
+ for (let i = currentVersion;i < MIGRATIONS.length; i++) {
2139
+ db.run(MIGRATIONS[i]);
2140
+ db.run(`INSERT OR REPLACE INTO schema_version (version) VALUES (${i + 1})`);
2141
+ }
2142
+ }
2143
+ var MIGRATIONS, _db = null;
2144
+ var init_database = __esm(() => {
2145
+ MIGRATIONS = [
2146
+ `
2131
2147
  CREATE TABLE IF NOT EXISTS configs (
2132
2148
  id TEXT PRIMARY KEY,
2133
2149
  name TEXT NOT NULL,
@@ -2185,33 +2201,8 @@ var MIGRATIONS = [
2185
2201
 
2186
2202
  INSERT OR IGNORE INTO schema_version (version) VALUES (1);
2187
2203
  `
2188
- ];
2189
- var _db = null;
2190
- function getDatabase(path) {
2191
- if (_db)
2192
- return _db;
2193
- const dbPath = path || getDbPath();
2194
- ensureDir(dbPath);
2195
- const db = new Database(dbPath);
2196
- db.run("PRAGMA journal_mode = WAL");
2197
- db.run("PRAGMA foreign_keys = ON");
2198
- applyMigrations(db);
2199
- _db = db;
2200
- return db;
2201
- }
2202
- function applyMigrations(db) {
2203
- let currentVersion = 0;
2204
- try {
2205
- const row = db.query("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get();
2206
- currentVersion = row?.version ?? 0;
2207
- } catch {
2208
- currentVersion = 0;
2209
- }
2210
- for (let i = currentVersion;i < MIGRATIONS.length; i++) {
2211
- db.run(MIGRATIONS[i]);
2212
- db.run(`INSERT OR REPLACE INTO schema_version (version) VALUES (${i + 1})`);
2213
- }
2214
- }
2204
+ ];
2205
+ });
2215
2206
 
2216
2207
  // src/db/configs.ts
2217
2208
  function rowToConfig(row) {
@@ -2374,67 +2365,10 @@ function getConfigStats(db) {
2374
2365
  }
2375
2366
  return stats;
2376
2367
  }
2377
-
2378
- // src/db/profiles.ts
2379
- function rowToProfile(row) {
2380
- return { ...row };
2381
- }
2382
- function uniqueProfileSlug(name, db, excludeId) {
2383
- const base = slugify(name);
2384
- let slug = base;
2385
- let i = 1;
2386
- while (true) {
2387
- const existing = db.query("SELECT id FROM profiles WHERE slug = ?").get(slug);
2388
- if (!existing || existing.id === excludeId)
2389
- return slug;
2390
- slug = `${base}-${i++}`;
2391
- }
2392
- }
2393
- function createProfile(input, db) {
2394
- const d = db || getDatabase();
2395
- const id = uuid();
2396
- const ts = now();
2397
- const slug = uniqueProfileSlug(input.name, d);
2398
- d.run("INSERT INTO profiles (id, name, slug, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", [id, input.name, slug, input.description ?? null, ts, ts]);
2399
- return getProfile(id, d);
2400
- }
2401
- function getProfile(idOrSlug, db) {
2402
- const d = db || getDatabase();
2403
- const row = d.query("SELECT * FROM profiles WHERE id = ? OR slug = ?").get(idOrSlug, idOrSlug);
2404
- if (!row)
2405
- throw new ProfileNotFoundError(idOrSlug);
2406
- return rowToProfile(row);
2407
- }
2408
- function listProfiles(db) {
2409
- const d = db || getDatabase();
2410
- return d.query("SELECT * FROM profiles ORDER BY name").all().map(rowToProfile);
2411
- }
2412
- function deleteProfile(idOrSlug, db) {
2413
- const d = db || getDatabase();
2414
- const existing = getProfile(idOrSlug, d);
2415
- d.run("DELETE FROM profiles WHERE id = ?", [existing.id]);
2416
- }
2417
- function addConfigToProfile(profileIdOrSlug, configId, db) {
2418
- const d = db || getDatabase();
2419
- const profile = getProfile(profileIdOrSlug, d);
2420
- const maxRow = d.query("SELECT MAX(sort_order) as max_order FROM profile_configs WHERE profile_id = ?").get(profile.id);
2421
- const order = (maxRow?.max_order ?? -1) + 1;
2422
- d.run("INSERT OR IGNORE INTO profile_configs (profile_id, config_id, sort_order) VALUES (?, ?, ?)", [profile.id, configId, order]);
2423
- }
2424
- function removeConfigFromProfile(profileIdOrSlug, configId, db) {
2425
- const d = db || getDatabase();
2426
- const profile = getProfile(profileIdOrSlug, d);
2427
- d.run("DELETE FROM profile_configs WHERE profile_id = ? AND config_id = ?", [profile.id, configId]);
2428
- }
2429
- function getProfileConfigs(profileIdOrSlug, db) {
2430
- const d = db || getDatabase();
2431
- const profile = getProfile(profileIdOrSlug, d);
2432
- const rows = d.query("SELECT config_id FROM profile_configs WHERE profile_id = ? ORDER BY sort_order").all(profile.id);
2433
- if (rows.length === 0)
2434
- return [];
2435
- const ids = rows.map((r) => r.config_id);
2436
- return listConfigs(undefined, d).filter((c) => ids.includes(c.id));
2437
- }
2368
+ var init_configs = __esm(() => {
2369
+ init_types();
2370
+ init_database();
2371
+ });
2438
2372
 
2439
2373
  // src/db/snapshots.ts
2440
2374
  function createSnapshot(configId, content, version, db) {
@@ -2452,6 +2386,9 @@ function getSnapshot(id, db) {
2452
2386
  const d = db || getDatabase();
2453
2387
  return d.query("SELECT * FROM config_snapshots WHERE id = ?").get(id);
2454
2388
  }
2389
+ var init_snapshots = __esm(() => {
2390
+ init_database();
2391
+ });
2455
2392
 
2456
2393
  // src/lib/apply.ts
2457
2394
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
@@ -2501,25 +2438,14 @@ async function applyConfigs(configs, opts = {}) {
2501
2438
  }
2502
2439
  return results;
2503
2440
  }
2504
-
2505
- // src/lib/sync.ts
2506
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "fs";
2507
- import { extname, join as join2 } from "path";
2508
- import { homedir as homedir2 } from "os";
2441
+ var init_apply = __esm(() => {
2442
+ init_types();
2443
+ init_database();
2444
+ init_configs();
2445
+ init_snapshots();
2446
+ });
2509
2447
 
2510
2448
  // src/lib/redact.ts
2511
- 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;
2512
- var VALUE_PATTERNS = [
2513
- { re: /npm_[A-Za-z0-9]{36,}/, reason: "npm token" },
2514
- { re: /gh[pousr]_[A-Za-z0-9_]{36,}/, reason: "GitHub token" },
2515
- { re: /sk-ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
2516
- { re: /sk-[A-Za-z0-9]{48,}/, reason: "OpenAI API key" },
2517
- { re: /xoxb-[0-9]+-[A-Za-z0-9\-]+/, reason: "Slack bot token" },
2518
- { re: /AIza[0-9A-Za-z\-_]{35}/, reason: "Google API key" },
2519
- { re: /ey[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}\./, reason: "JWT token" },
2520
- { re: /AKIA[0-9A-Z]{16}/, reason: "AWS access key" }
2521
- ];
2522
- var MIN_SECRET_VALUE_LEN = 8;
2523
2449
  function redactShell(content) {
2524
2450
  const redacted = [];
2525
2451
  const lines = content.split(`
@@ -2683,32 +2609,127 @@ function scanSecrets(content, format) {
2683
2609
  const r = redactContent(content, format);
2684
2610
  return r.redacted;
2685
2611
  }
2612
+ var SECRET_KEY_PATTERN, VALUE_PATTERNS, MIN_SECRET_VALUE_LEN = 8;
2613
+ var init_redact = __esm(() => {
2614
+ 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;
2615
+ VALUE_PATTERNS = [
2616
+ { re: /npm_[A-Za-z0-9]{36,}/, reason: "npm token" },
2617
+ { re: /gh[pousr]_[A-Za-z0-9_]{36,}/, reason: "GitHub token" },
2618
+ { re: /sk-ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
2619
+ { re: /sk-[A-Za-z0-9]{48,}/, reason: "OpenAI API key" },
2620
+ { re: /xoxb-[0-9]+-[A-Za-z0-9\-]+/, reason: "Slack bot token" },
2621
+ { re: /AIza[0-9A-Za-z\-_]{35}/, reason: "Google API key" },
2622
+ { re: /ey[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}\./, reason: "JWT token" },
2623
+ { re: /AKIA[0-9A-Z]{16}/, reason: "AWS access key" }
2624
+ ];
2625
+ });
2626
+
2627
+ // src/lib/sync-dir.ts
2628
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
2629
+ import { join as join2, relative } from "path";
2630
+ import { homedir as homedir2 } from "os";
2631
+ function shouldSkip(p) {
2632
+ return SKIP.some((s) => p.includes(s));
2633
+ }
2634
+ async function syncFromDir(dir, opts = {}) {
2635
+ const d = opts.db || getDatabase();
2636
+ const absDir = expandPath(dir);
2637
+ if (!existsSync3(absDir))
2638
+ return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
2639
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join2(absDir, f)).filter((f) => statSync(f).isFile());
2640
+ const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
2641
+ const home = homedir2();
2642
+ const allConfigs = listConfigs(undefined, d);
2643
+ for (const file of files) {
2644
+ if (shouldSkip(file)) {
2645
+ result.skipped.push(file);
2646
+ continue;
2647
+ }
2648
+ try {
2649
+ const content = readFileSync2(file, "utf-8");
2650
+ if (content.length > 500000) {
2651
+ result.skipped.push(file + " (too large)");
2652
+ continue;
2653
+ }
2654
+ const targetPath = file.replace(home, "~");
2655
+ const existing = allConfigs.find((c) => c.target_path === targetPath);
2656
+ if (!existing) {
2657
+ if (!opts.dryRun)
2658
+ createConfig({ name: relative(absDir, file), category: detectCategory(file), agent: detectAgent(file), target_path: targetPath, format: detectFormat(file), content }, d);
2659
+ result.added++;
2660
+ } else if (existing.content !== content) {
2661
+ if (!opts.dryRun)
2662
+ updateConfig(existing.id, { content }, d);
2663
+ result.updated++;
2664
+ } else {
2665
+ result.unchanged++;
2666
+ }
2667
+ } catch {
2668
+ result.skipped.push(file);
2669
+ }
2670
+ }
2671
+ return result;
2672
+ }
2673
+ async function syncToDir(dir, opts = {}) {
2674
+ const d = opts.db || getDatabase();
2675
+ const home = homedir2();
2676
+ const absDir = expandPath(dir);
2677
+ const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
2678
+ const configs = listConfigs(undefined, d).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
2679
+ const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
2680
+ for (const config of configs) {
2681
+ if (config.kind === "reference")
2682
+ continue;
2683
+ try {
2684
+ const r = await applyConfig(config, { dryRun: opts.dryRun, db: d });
2685
+ r.changed ? result.updated++ : result.unchanged++;
2686
+ } catch {
2687
+ result.skipped.push(config.target_path || config.id);
2688
+ }
2689
+ }
2690
+ return result;
2691
+ }
2692
+ function walkDir(dir, files = []) {
2693
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
2694
+ const full = join2(dir, entry.name);
2695
+ if (shouldSkip(full))
2696
+ continue;
2697
+ if (entry.isDirectory())
2698
+ walkDir(full, files);
2699
+ else if (entry.isFile())
2700
+ files.push(full);
2701
+ }
2702
+ return files;
2703
+ }
2704
+ var SKIP;
2705
+ var init_sync_dir = __esm(() => {
2706
+ init_database();
2707
+ init_configs();
2708
+ init_apply();
2709
+ init_sync();
2710
+ SKIP = [".db", ".db-shm", ".db-wal", ".log", ".lock", ".DS_Store", "node_modules", ".git"];
2711
+ });
2686
2712
 
2687
2713
  // src/lib/sync.ts
2688
- var KNOWN_CONFIGS = [
2689
- { path: "~/.claude/CLAUDE.md", name: "claude-claude-md", category: "rules", agent: "claude", format: "markdown" },
2690
- { path: "~/.claude/settings.json", name: "claude-settings", category: "agent", agent: "claude", format: "json" },
2691
- { path: "~/.claude/settings.local.json", name: "claude-settings-local", category: "agent", agent: "claude", format: "json" },
2692
- { path: "~/.claude/keybindings.json", name: "claude-keybindings", category: "agent", agent: "claude", format: "json" },
2693
- { path: "~/.claude/rules", name: "claude-rules", category: "rules", agent: "claude", rulesDir: "~/.claude/rules" },
2694
- { path: "~/.codex/config.toml", name: "codex-config", category: "agent", agent: "codex", format: "toml" },
2695
- { path: "~/.codex/AGENTS.md", name: "codex-agents-md", category: "rules", agent: "codex", format: "markdown" },
2696
- { path: "~/.gemini/settings.json", name: "gemini-settings", category: "agent", agent: "gemini", format: "json" },
2697
- { path: "~/.gemini/GEMINI.md", name: "gemini-gemini-md", category: "rules", agent: "gemini", format: "markdown" },
2698
- { path: "~/.claude.json", name: "claude-json", category: "mcp", agent: "claude", format: "json", description: "Claude Code global config (includes MCP server entries)" },
2699
- { path: "~/.zshrc", name: "zshrc", category: "shell", agent: "zsh" },
2700
- { path: "~/.zprofile", name: "zprofile", category: "shell", agent: "zsh" },
2701
- { path: "~/.bashrc", name: "bashrc", category: "shell", agent: "zsh" },
2702
- { path: "~/.bash_profile", name: "bash-profile", category: "shell", agent: "zsh" },
2703
- { path: "~/.gitconfig", name: "gitconfig", category: "git", agent: "git", format: "ini" },
2704
- { path: "~/.gitignore_global", name: "gitignore-global", category: "git", agent: "git" },
2705
- { path: "~/.npmrc", name: "npmrc", category: "tools", agent: "npm", format: "ini" },
2706
- { path: "~/.bunfig.toml", name: "bunfig", category: "tools", agent: "global", format: "toml" }
2707
- ];
2714
+ var exports_sync = {};
2715
+ __export(exports_sync, {
2716
+ syncToDisk: () => syncToDisk,
2717
+ syncToDir: () => syncToDir,
2718
+ syncKnown: () => syncKnown,
2719
+ syncFromDir: () => syncFromDir,
2720
+ diffConfig: () => diffConfig,
2721
+ detectFormat: () => detectFormat,
2722
+ detectCategory: () => detectCategory,
2723
+ detectAgent: () => detectAgent,
2724
+ KNOWN_CONFIGS: () => KNOWN_CONFIGS
2725
+ });
2726
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
2727
+ import { extname, join as join3 } from "path";
2728
+ import { homedir as homedir3 } from "os";
2708
2729
  async function syncKnown(opts = {}) {
2709
2730
  const d = opts.db || getDatabase();
2710
2731
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
2711
- const home = homedir2();
2732
+ const home = homedir3();
2712
2733
  let targets = KNOWN_CONFIGS;
2713
2734
  if (opts.agent)
2714
2735
  targets = targets.filter((k) => k.agent === opts.agent);
@@ -2718,15 +2739,15 @@ async function syncKnown(opts = {}) {
2718
2739
  for (const known of targets) {
2719
2740
  if (known.rulesDir) {
2720
2741
  const absDir = expandPath(known.rulesDir);
2721
- if (!existsSync3(absDir)) {
2742
+ if (!existsSync4(absDir)) {
2722
2743
  result.skipped.push(known.rulesDir);
2723
2744
  continue;
2724
2745
  }
2725
- const mdFiles = readdirSync(absDir).filter((f) => f.endsWith(".md"));
2746
+ const mdFiles = readdirSync2(absDir).filter((f) => f.endsWith(".md"));
2726
2747
  for (const f of mdFiles) {
2727
- const abs2 = join2(absDir, f);
2748
+ const abs2 = join3(absDir, f);
2728
2749
  const targetPath = abs2.replace(home, "~");
2729
- const raw = readFileSync2(abs2, "utf-8");
2750
+ const raw = readFileSync3(abs2, "utf-8");
2730
2751
  const { content, isTemplate } = redactContent(raw, "markdown");
2731
2752
  const name = `claude-rules-${f}`;
2732
2753
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
@@ -2746,12 +2767,12 @@ async function syncKnown(opts = {}) {
2746
2767
  continue;
2747
2768
  }
2748
2769
  const abs = expandPath(known.path);
2749
- if (!existsSync3(abs)) {
2770
+ if (!existsSync4(abs)) {
2750
2771
  result.skipped.push(known.path);
2751
2772
  continue;
2752
2773
  }
2753
2774
  try {
2754
- const rawContent = readFileSync2(abs, "utf-8");
2775
+ const rawContent = readFileSync3(abs, "utf-8");
2755
2776
  if (rawContent.length > 500000) {
2756
2777
  result.skipped.push(known.path + " (too large)");
2757
2778
  continue;
@@ -2808,9 +2829,9 @@ function diffConfig(config) {
2808
2829
  if (!config.target_path)
2809
2830
  return "(reference \u2014 no target path)";
2810
2831
  const path = expandPath(config.target_path);
2811
- if (!existsSync3(path))
2832
+ if (!existsSync4(path))
2812
2833
  return `(file not found on disk: ${path})`;
2813
- const diskContent = readFileSync2(path, "utf-8");
2834
+ const diskContent = readFileSync3(path, "utf-8");
2814
2835
  if (diskContent === config.content)
2815
2836
  return "(no diff \u2014 identical)";
2816
2837
  const stored = config.content.split(`
@@ -2836,7 +2857,7 @@ function diffConfig(config) {
2836
2857
  `);
2837
2858
  }
2838
2859
  function detectCategory(filePath) {
2839
- const p = filePath.toLowerCase().replace(homedir2(), "~");
2860
+ const p = filePath.toLowerCase().replace(homedir3(), "~");
2840
2861
  if (p.includes("/.claude/rules/") || p.endsWith("claude.md") || p.endsWith("agents.md") || p.endsWith("gemini.md"))
2841
2862
  return "rules";
2842
2863
  if (p.includes("/.claude/") || p.includes("/.codex/") || p.includes("/.gemini/") || p.includes("/.cursor/"))
@@ -2854,7 +2875,7 @@ function detectCategory(filePath) {
2854
2875
  return "tools";
2855
2876
  }
2856
2877
  function detectAgent(filePath) {
2857
- const p = filePath.toLowerCase().replace(homedir2(), "~");
2878
+ const p = filePath.toLowerCase().replace(homedir3(), "~");
2858
2879
  if (p.includes("/.claude/") || p.endsWith("claude.md"))
2859
2880
  return "claude";
2860
2881
  if (p.includes("/.codex/") || p.endsWith("agents.md"))
@@ -2883,17 +2904,140 @@ function detectFormat(filePath) {
2883
2904
  return "ini";
2884
2905
  return "text";
2885
2906
  }
2907
+ var KNOWN_CONFIGS;
2908
+ var init_sync = __esm(() => {
2909
+ init_database();
2910
+ init_configs();
2911
+ init_apply();
2912
+ init_redact();
2913
+ init_sync_dir();
2914
+ KNOWN_CONFIGS = [
2915
+ { path: "~/.claude/CLAUDE.md", name: "claude-claude-md", category: "rules", agent: "claude", format: "markdown" },
2916
+ { path: "~/.claude/settings.json", name: "claude-settings", category: "agent", agent: "claude", format: "json" },
2917
+ { path: "~/.claude/settings.local.json", name: "claude-settings-local", category: "agent", agent: "claude", format: "json" },
2918
+ { path: "~/.claude/keybindings.json", name: "claude-keybindings", category: "agent", agent: "claude", format: "json" },
2919
+ { path: "~/.claude/rules", name: "claude-rules", category: "rules", agent: "claude", rulesDir: "~/.claude/rules" },
2920
+ { path: "~/.codex/config.toml", name: "codex-config", category: "agent", agent: "codex", format: "toml" },
2921
+ { path: "~/.codex/AGENTS.md", name: "codex-agents-md", category: "rules", agent: "codex", format: "markdown" },
2922
+ { path: "~/.gemini/settings.json", name: "gemini-settings", category: "agent", agent: "gemini", format: "json" },
2923
+ { path: "~/.gemini/GEMINI.md", name: "gemini-gemini-md", category: "rules", agent: "gemini", format: "markdown" },
2924
+ { path: "~/.claude.json", name: "claude-json", category: "mcp", agent: "claude", format: "json", description: "Claude Code global config (includes MCP server entries)" },
2925
+ { path: "~/.zshrc", name: "zshrc", category: "shell", agent: "zsh" },
2926
+ { path: "~/.zprofile", name: "zprofile", category: "shell", agent: "zsh" },
2927
+ { path: "~/.bashrc", name: "bashrc", category: "shell", agent: "zsh" },
2928
+ { path: "~/.bash_profile", name: "bash-profile", category: "shell", agent: "zsh" },
2929
+ { path: "~/.gitconfig", name: "gitconfig", category: "git", agent: "git", format: "ini" },
2930
+ { path: "~/.gitignore_global", name: "gitignore-global", category: "git", agent: "git" },
2931
+ { path: "~/.npmrc", name: "npmrc", category: "tools", agent: "npm", format: "ini" },
2932
+ { path: "~/.bunfig.toml", name: "bunfig", category: "tools", agent: "global", format: "toml" }
2933
+ ];
2934
+ });
2935
+
2936
+ // node_modules/commander/esm.mjs
2937
+ var import__ = __toESM(require_commander(), 1);
2938
+ var {
2939
+ program,
2940
+ createCommand,
2941
+ createArgument,
2942
+ createOption,
2943
+ CommanderError,
2944
+ InvalidArgumentError,
2945
+ InvalidOptionArgumentError,
2946
+ Command,
2947
+ Argument,
2948
+ Option,
2949
+ Help
2950
+ } = import__.default;
2951
+
2952
+ // src/cli/index.tsx
2953
+ init_configs();
2954
+ import chalk from "chalk";
2955
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
2956
+ import { homedir as homedir4 } from "os";
2957
+ import { join as join6, resolve as resolve5 } from "path";
2958
+
2959
+ // src/db/profiles.ts
2960
+ init_types();
2961
+ init_database();
2962
+ init_configs();
2963
+ function rowToProfile(row) {
2964
+ return { ...row };
2965
+ }
2966
+ function uniqueProfileSlug(name, db, excludeId) {
2967
+ const base = slugify(name);
2968
+ let slug = base;
2969
+ let i = 1;
2970
+ while (true) {
2971
+ const existing = db.query("SELECT id FROM profiles WHERE slug = ?").get(slug);
2972
+ if (!existing || existing.id === excludeId)
2973
+ return slug;
2974
+ slug = `${base}-${i++}`;
2975
+ }
2976
+ }
2977
+ function createProfile(input, db) {
2978
+ const d = db || getDatabase();
2979
+ const id = uuid();
2980
+ const ts = now();
2981
+ const slug = uniqueProfileSlug(input.name, d);
2982
+ d.run("INSERT INTO profiles (id, name, slug, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", [id, input.name, slug, input.description ?? null, ts, ts]);
2983
+ return getProfile(id, d);
2984
+ }
2985
+ function getProfile(idOrSlug, db) {
2986
+ const d = db || getDatabase();
2987
+ const row = d.query("SELECT * FROM profiles WHERE id = ? OR slug = ?").get(idOrSlug, idOrSlug);
2988
+ if (!row)
2989
+ throw new ProfileNotFoundError(idOrSlug);
2990
+ return rowToProfile(row);
2991
+ }
2992
+ function listProfiles(db) {
2993
+ const d = db || getDatabase();
2994
+ return d.query("SELECT * FROM profiles ORDER BY name").all().map(rowToProfile);
2995
+ }
2996
+ function deleteProfile(idOrSlug, db) {
2997
+ const d = db || getDatabase();
2998
+ const existing = getProfile(idOrSlug, d);
2999
+ d.run("DELETE FROM profiles WHERE id = ?", [existing.id]);
3000
+ }
3001
+ function addConfigToProfile(profileIdOrSlug, configId, db) {
3002
+ const d = db || getDatabase();
3003
+ const profile = getProfile(profileIdOrSlug, d);
3004
+ const maxRow = d.query("SELECT MAX(sort_order) as max_order FROM profile_configs WHERE profile_id = ?").get(profile.id);
3005
+ const order = (maxRow?.max_order ?? -1) + 1;
3006
+ d.run("INSERT OR IGNORE INTO profile_configs (profile_id, config_id, sort_order) VALUES (?, ?, ?)", [profile.id, configId, order]);
3007
+ }
3008
+ function removeConfigFromProfile(profileIdOrSlug, configId, db) {
3009
+ const d = db || getDatabase();
3010
+ const profile = getProfile(profileIdOrSlug, d);
3011
+ d.run("DELETE FROM profile_configs WHERE profile_id = ? AND config_id = ?", [profile.id, configId]);
3012
+ }
3013
+ function getProfileConfigs(profileIdOrSlug, db) {
3014
+ const d = db || getDatabase();
3015
+ const profile = getProfile(profileIdOrSlug, d);
3016
+ const rows = d.query("SELECT config_id FROM profile_configs WHERE profile_id = ? ORDER BY sort_order").all(profile.id);
3017
+ if (rows.length === 0)
3018
+ return [];
3019
+ const ids = rows.map((r) => r.config_id);
3020
+ return listConfigs(undefined, d).filter((c) => ids.includes(c.id));
3021
+ }
3022
+
3023
+ // src/cli/index.tsx
3024
+ init_snapshots();
3025
+ init_apply();
3026
+ init_sync();
3027
+ init_redact();
2886
3028
 
2887
3029
  // src/lib/export.ts
2888
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
2889
- import { join as join3, resolve as resolve3 } from "path";
3030
+ init_database();
3031
+ init_configs();
3032
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
3033
+ import { join as join4, resolve as resolve3 } from "path";
2890
3034
  import { tmpdir } from "os";
2891
3035
  async function exportConfigs(outputPath, opts = {}) {
2892
3036
  const d = opts.db || getDatabase();
2893
3037
  const configs = listConfigs(opts.filter, d);
2894
3038
  const absOutput = resolve3(outputPath);
2895
- const tmpDir = join3(tmpdir(), `configs-export-${Date.now()}`);
2896
- const contentsDir = join3(tmpDir, "contents");
3039
+ const tmpDir = join4(tmpdir(), `configs-export-${Date.now()}`);
3040
+ const contentsDir = join4(tmpDir, "contents");
2897
3041
  try {
2898
3042
  mkdirSync3(contentsDir, { recursive: true });
2899
3043
  const manifest = {
@@ -2901,10 +3045,10 @@ async function exportConfigs(outputPath, opts = {}) {
2901
3045
  exported_at: now(),
2902
3046
  configs: configs.map(({ content: _content, ...meta }) => meta)
2903
3047
  };
2904
- writeFileSync2(join3(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
3048
+ writeFileSync2(join4(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
2905
3049
  for (const config of configs) {
2906
3050
  const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
2907
- writeFileSync2(join3(contentsDir, fileName), config.content, "utf-8");
3051
+ writeFileSync2(join4(contentsDir, fileName), config.content, "utf-8");
2908
3052
  }
2909
3053
  const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
2910
3054
  stdout: "pipe",
@@ -2917,21 +3061,23 @@ async function exportConfigs(outputPath, opts = {}) {
2917
3061
  }
2918
3062
  return { path: absOutput, count: configs.length };
2919
3063
  } finally {
2920
- if (existsSync4(tmpDir)) {
3064
+ if (existsSync5(tmpDir)) {
2921
3065
  rmSync(tmpDir, { recursive: true, force: true });
2922
3066
  }
2923
3067
  }
2924
3068
  }
2925
3069
 
2926
3070
  // src/lib/import.ts
2927
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync as rmSync2 } from "fs";
2928
- import { join as join4, resolve as resolve4 } from "path";
3071
+ init_database();
3072
+ init_configs();
3073
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync as rmSync2 } from "fs";
3074
+ import { join as join5, resolve as resolve4 } from "path";
2929
3075
  import { tmpdir as tmpdir2 } from "os";
2930
3076
  async function importConfigs(bundlePath, opts = {}) {
2931
3077
  const d = opts.db || getDatabase();
2932
3078
  const conflict = opts.conflict ?? "skip";
2933
3079
  const absPath = resolve4(bundlePath);
2934
- const tmpDir = join4(tmpdir2(), `configs-import-${Date.now()}`);
3080
+ const tmpDir = join5(tmpdir2(), `configs-import-${Date.now()}`);
2935
3081
  const result = { created: 0, updated: 0, skipped: 0, errors: [] };
2936
3082
  try {
2937
3083
  mkdirSync4(tmpDir, { recursive: true });
@@ -2944,15 +3090,15 @@ async function importConfigs(bundlePath, opts = {}) {
2944
3090
  const stderr = await new Response(proc.stderr).text();
2945
3091
  throw new Error(`tar extraction failed: ${stderr}`);
2946
3092
  }
2947
- const manifestPath = join4(tmpDir, "manifest.json");
2948
- if (!existsSync5(manifestPath))
3093
+ const manifestPath = join5(tmpDir, "manifest.json");
3094
+ if (!existsSync6(manifestPath))
2949
3095
  throw new Error("Invalid bundle: missing manifest.json");
2950
- const manifest = JSON.parse(readFileSync3(manifestPath, "utf-8"));
3096
+ const manifest = JSON.parse(readFileSync4(manifestPath, "utf-8"));
2951
3097
  for (const meta of manifest.configs) {
2952
3098
  try {
2953
3099
  const ext = meta.format === "text" ? "txt" : meta.format;
2954
- const contentFile = join4(tmpDir, "contents", `${meta.slug}.${ext}`);
2955
- const content = existsSync5(contentFile) ? readFileSync3(contentFile, "utf-8") : "";
3100
+ const contentFile = join5(tmpDir, "contents", `${meta.slug}.${ext}`);
3101
+ const content = existsSync6(contentFile) ? readFileSync4(contentFile, "utf-8") : "";
2956
3102
  let existing = null;
2957
3103
  try {
2958
3104
  existing = getConfig(meta.slug, d);
@@ -2985,13 +3131,14 @@ async function importConfigs(bundlePath, opts = {}) {
2985
3131
  }
2986
3132
  return result;
2987
3133
  } finally {
2988
- if (existsSync5(tmpDir)) {
3134
+ if (existsSync6(tmpDir)) {
2989
3135
  rmSync2(tmpDir, { recursive: true, force: true });
2990
3136
  }
2991
3137
  }
2992
3138
  }
2993
3139
 
2994
3140
  // src/lib/template.ts
3141
+ init_types();
2995
3142
  var VAR_PATTERN = /\{\{([A-Z0-9_]+)(?::([^}]*))?\}\}/g;
2996
3143
  function extractTemplateVars(content) {
2997
3144
  const vars = new Map;
@@ -3070,14 +3217,14 @@ program.command("show <id>").description("Show a config's content and metadata")
3070
3217
  });
3071
3218
  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) => {
3072
3219
  const abs = resolve5(filePath);
3073
- if (!existsSync6(abs)) {
3220
+ if (!existsSync7(abs)) {
3074
3221
  console.error(chalk.red(`File not found: ${abs}`));
3075
3222
  process.exit(1);
3076
3223
  }
3077
- const rawContent = readFileSync4(abs, "utf-8");
3224
+ const rawContent = readFileSync5(abs, "utf-8");
3078
3225
  const fmt = detectFormat(abs);
3079
3226
  const { content, redacted, isTemplate } = redactContent(rawContent, fmt);
3080
- const targetPath = abs.startsWith(homedir3()) ? abs.replace(homedir3(), "~") : abs;
3227
+ const targetPath = abs.startsWith(homedir4()) ? abs.replace(homedir4(), "~") : abs;
3081
3228
  const name = opts.name || filePath.split("/").pop();
3082
3229
  const config = createConfig({
3083
3230
  name,
@@ -3163,7 +3310,7 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
3163
3310
  }
3164
3311
  });
3165
3312
  program.command("whoami").description("Show setup summary").action(async () => {
3166
- const dbPath = process.env["CONFIGS_DB_PATH"] || join5(homedir3(), ".configs", "configs.db");
3313
+ const dbPath = process.env["CONFIGS_DB_PATH"] || join6(homedir4(), ".configs", "configs.db");
3167
3314
  const stats = getConfigStats();
3168
3315
  console.log(chalk.bold("@hasna/configs") + chalk.dim(" v" + pkg.version));
3169
3316
  console.log(chalk.cyan("DB:") + " " + dbPath);
@@ -3321,25 +3468,51 @@ templateCmd.command("vars <id>").description("Show template variables").action(a
3321
3468
  process.exit(1);
3322
3469
  }
3323
3470
  });
3324
- program.command("scan [id]").description("Scan configs for secrets. Omit id to scan all.").option("--fix", "redact found secrets in-place").action(async (id, opts) => {
3325
- const configs = id ? [getConfig(id)] : listConfigs({ kind: "file" });
3471
+ program.command("scan [id]").description("Scan configs for secrets. Defaults to known configs only.").option("--fix", "redact found secrets in-place").option("--all", "scan every config in the DB (slow on large DBs)").option("-c, --category <cat>", "scan only a specific category").action(async (id, opts) => {
3472
+ let configs;
3473
+ if (id) {
3474
+ configs = [getConfig(id)];
3475
+ } else if (opts.all) {
3476
+ configs = listConfigs(opts.category ? { kind: "file", category: opts.category } : { kind: "file" });
3477
+ } else {
3478
+ const { KNOWN_CONFIGS: KNOWN_CONFIGS2 } = await Promise.resolve().then(() => (init_sync(), exports_sync));
3479
+ const slugs = [
3480
+ ...KNOWN_CONFIGS2.filter((k) => !k.rulesDir).map((k) => k.name)
3481
+ ];
3482
+ const fetched = [];
3483
+ for (const slug of slugs) {
3484
+ try {
3485
+ fetched.push(getConfig(slug));
3486
+ } catch {}
3487
+ }
3488
+ const rules = listConfigs({ category: "rules", agent: "claude" });
3489
+ for (const r of rules)
3490
+ if (!fetched.find((c) => c.id === r.id))
3491
+ fetched.push(r);
3492
+ configs = fetched;
3493
+ }
3326
3494
  let total = 0;
3327
- for (const c of configs) {
3328
- const secrets = scanSecrets(c.content, c.format);
3329
- if (secrets.length === 0)
3330
- continue;
3331
- total += secrets.length;
3332
- console.log(chalk.yellow(`\u26A0 ${c.slug}`) + chalk.dim(` \u2014 ${secrets.length} secret(s):`));
3333
- for (const s of secrets)
3334
- console.log(` line ${s.line}: ${chalk.red(s.varName)} \u2014 ${s.reason}`);
3335
- if (opts.fix) {
3336
- const { content, isTemplate } = redactContent(c.content, c.format);
3337
- updateConfig(c.id, { content, is_template: isTemplate });
3338
- console.log(chalk.green(" \u2713 Redacted and updated."));
3495
+ const BATCH = 200;
3496
+ for (let i = 0;i < configs.length; i += BATCH) {
3497
+ const batch = configs.slice(i, i + BATCH);
3498
+ for (const c of batch) {
3499
+ const fmt = c.format;
3500
+ const secrets = scanSecrets(c.content, fmt);
3501
+ if (secrets.length === 0)
3502
+ continue;
3503
+ total += secrets.length;
3504
+ console.log(chalk.yellow(`\u26A0 ${c.slug}`) + chalk.dim(` \u2014 ${secrets.length} secret(s):`));
3505
+ for (const s of secrets)
3506
+ console.log(` line ${s.line}: ${chalk.red(s.varName)} \u2014 ${s.reason}`);
3507
+ if (opts.fix) {
3508
+ const { content, isTemplate } = redactContent(c.content, fmt);
3509
+ updateConfig(c.id, { content, is_template: isTemplate });
3510
+ console.log(chalk.green(" \u2713 Redacted."));
3511
+ }
3339
3512
  }
3340
3513
  }
3341
3514
  if (total === 0) {
3342
- console.log(chalk.green("\u2713") + " No secrets detected.");
3515
+ console.log(chalk.green("\u2713") + ` No secrets detected${opts.all ? "" : " (known configs). Use --all to scan entire DB"}.`);
3343
3516
  } else if (!opts.fix) {
3344
3517
  console.log(chalk.yellow(`
3345
3518
  Run with --fix to redact in-place.`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/configs",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",