@hasna/configs 0.1.0 → 0.1.2
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 +376 -115
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +399 -108
- package/dist/lib/redact.d.ts +29 -0
- package/dist/lib/redact.d.ts.map +1 -0
- package/dist/lib/redact.test.d.ts +2 -0
- package/dist/lib/redact.test.d.ts.map +1 -0
- package/dist/lib/sync-dir.d.ts +13 -0
- package/dist/lib/sync-dir.d.ts.map +1 -0
- package/dist/lib/sync.d.ts +24 -8
- package/dist/lib/sync.d.ts.map +1 -1
- package/dist/mcp/index.js +82 -88
- package/dist/server/index.js +82 -88
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2073,7 +2073,7 @@ var {
|
|
|
2073
2073
|
import chalk from "chalk";
|
|
2074
2074
|
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
2075
2075
|
import { homedir as homedir3 } from "os";
|
|
2076
|
-
import { join as join5, resolve as
|
|
2076
|
+
import { join as join5, resolve as resolve5 } from "path";
|
|
2077
2077
|
|
|
2078
2078
|
// src/types/index.ts
|
|
2079
2079
|
class ConfigNotFoundError extends Error {
|
|
@@ -2503,138 +2503,303 @@ async function applyConfigs(configs, opts = {}) {
|
|
|
2503
2503
|
}
|
|
2504
2504
|
|
|
2505
2505
|
// src/lib/sync.ts
|
|
2506
|
-
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2
|
|
2507
|
-
import { extname, join as join2
|
|
2506
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
2507
|
+
import { extname, join as join2 } from "path";
|
|
2508
2508
|
import { homedir as homedir2 } from "os";
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2509
|
+
|
|
2510
|
+
// 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
|
+
function redactShell(content) {
|
|
2524
|
+
const redacted = [];
|
|
2525
|
+
const lines = content.split(`
|
|
2526
|
+
`);
|
|
2527
|
+
const out = [];
|
|
2528
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2529
|
+
const line = lines[i];
|
|
2530
|
+
const m = line.match(/^(\s*(?:export\s+)?)([A-Z][A-Z0-9_]*)(\s*=\s*)(['"]?)(.+?)\4\s*$/);
|
|
2531
|
+
if (m) {
|
|
2532
|
+
const [, prefix, key, eq, quote, value] = m;
|
|
2533
|
+
if (shouldRedactKeyValue(key, value)) {
|
|
2534
|
+
const reason = reasonFor(key, value);
|
|
2535
|
+
redacted.push({ varName: key, line: i + 1, reason });
|
|
2536
|
+
out.push(`${prefix}${key}${eq}${quote}{{${key}}}${quote}`);
|
|
2537
|
+
continue;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
out.push(line);
|
|
2541
|
+
}
|
|
2542
|
+
return { content: out.join(`
|
|
2543
|
+
`), redacted, isTemplate: redacted.length > 0 };
|
|
2542
2544
|
}
|
|
2543
|
-
function
|
|
2544
|
-
const
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2545
|
+
function redactJson(content) {
|
|
2546
|
+
const redacted = [];
|
|
2547
|
+
const lines = content.split(`
|
|
2548
|
+
`);
|
|
2549
|
+
const out = [];
|
|
2550
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2551
|
+
const line = lines[i];
|
|
2552
|
+
const m = line.match(/^(\s*"([^"]+)"\s*:\s*)"([^"]+)"(,?)(\s*)$/);
|
|
2553
|
+
if (m) {
|
|
2554
|
+
const [, prefix, key, value, comma, trail] = m;
|
|
2555
|
+
if (shouldRedactKeyValue(key, value)) {
|
|
2556
|
+
const varName = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
2557
|
+
redacted.push({ varName, line: i + 1, reason: reasonFor(key, value) });
|
|
2558
|
+
out.push(`${prefix}"{{${varName}}}"${comma}${trail}`);
|
|
2559
|
+
continue;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
let newLine = line;
|
|
2563
|
+
for (const { re, reason } of VALUE_PATTERNS) {
|
|
2564
|
+
newLine = newLine.replace(re, (match) => {
|
|
2565
|
+
const varName = `REDACTED_${reason.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
2566
|
+
redacted.push({ varName, line: i + 1, reason });
|
|
2567
|
+
return `{{${varName}}}`;
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2570
|
+
out.push(newLine);
|
|
2571
|
+
}
|
|
2572
|
+
return { content: out.join(`
|
|
2573
|
+
`), redacted, isTemplate: redacted.length > 0 };
|
|
2556
2574
|
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2575
|
+
function redactToml(content) {
|
|
2576
|
+
const redacted = [];
|
|
2577
|
+
const lines = content.split(`
|
|
2578
|
+
`);
|
|
2579
|
+
const out = [];
|
|
2580
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2581
|
+
const line = lines[i];
|
|
2582
|
+
const m = line.match(/^(\s*)([a-zA-Z][a-zA-Z0-9_\-]*)(\s*=\s*)(['"]?)(.+?)\4\s*$/);
|
|
2583
|
+
if (m) {
|
|
2584
|
+
const [, indent, key, eq, quote, value] = m;
|
|
2585
|
+
if (shouldRedactKeyValue(key, value)) {
|
|
2586
|
+
const varName = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
2587
|
+
redacted.push({ varName, line: i + 1, reason: reasonFor(key, value) });
|
|
2588
|
+
out.push(`${indent}${key}${eq}${quote}{{${varName}}}${quote}`);
|
|
2589
|
+
continue;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
out.push(line);
|
|
2593
|
+
}
|
|
2594
|
+
return { content: out.join(`
|
|
2595
|
+
`), redacted, isTemplate: redacted.length > 0 };
|
|
2560
2596
|
}
|
|
2561
|
-
function
|
|
2562
|
-
const
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2597
|
+
function redactIni(content) {
|
|
2598
|
+
const redacted = [];
|
|
2599
|
+
const lines = content.split(`
|
|
2600
|
+
`);
|
|
2601
|
+
const out = [];
|
|
2602
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2603
|
+
const line = lines[i];
|
|
2604
|
+
const authM = line.match(/^(\/\/[^:]+:_authToken=)(.+)$/);
|
|
2605
|
+
if (authM) {
|
|
2606
|
+
redacted.push({ varName: "NPM_AUTH_TOKEN", line: i + 1, reason: "npm auth token" });
|
|
2607
|
+
out.push(`${authM[1]}{{NPM_AUTH_TOKEN}}`);
|
|
2566
2608
|
continue;
|
|
2567
|
-
if (entry.isDirectory()) {
|
|
2568
|
-
walkDir(full, files);
|
|
2569
|
-
} else if (entry.isFile()) {
|
|
2570
|
-
files.push(full);
|
|
2571
2609
|
}
|
|
2610
|
+
const m = line.match(/^(\s*)([a-zA-Z][a-zA-Z0-9_\-]*)(\s*=\s*)(.+?)\s*$/);
|
|
2611
|
+
if (m) {
|
|
2612
|
+
const [, indent, key, eq, value] = m;
|
|
2613
|
+
if (shouldRedactKeyValue(key, value)) {
|
|
2614
|
+
const varName = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
2615
|
+
redacted.push({ varName, line: i + 1, reason: reasonFor(key, value) });
|
|
2616
|
+
out.push(`${indent}${key}${eq}{{${varName}}}`);
|
|
2617
|
+
continue;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
out.push(line);
|
|
2572
2621
|
}
|
|
2573
|
-
return
|
|
2622
|
+
return { content: out.join(`
|
|
2623
|
+
`), redacted, isTemplate: redacted.length > 0 };
|
|
2574
2624
|
}
|
|
2575
|
-
|
|
2576
|
-
const
|
|
2577
|
-
const
|
|
2578
|
-
|
|
2579
|
-
|
|
2625
|
+
function redactGeneric(content) {
|
|
2626
|
+
const redacted = [];
|
|
2627
|
+
const lines = content.split(`
|
|
2628
|
+
`);
|
|
2629
|
+
const out = [];
|
|
2630
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2631
|
+
let line = lines[i];
|
|
2632
|
+
for (const { re, reason } of VALUE_PATTERNS) {
|
|
2633
|
+
line = line.replace(re, (match) => {
|
|
2634
|
+
const varName = reason.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
2635
|
+
redacted.push({ varName, line: i + 1, reason });
|
|
2636
|
+
return `{{${varName}}}`;
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
out.push(line);
|
|
2640
|
+
}
|
|
2641
|
+
return { content: out.join(`
|
|
2642
|
+
`), redacted, isTemplate: redacted.length > 0 };
|
|
2643
|
+
}
|
|
2644
|
+
function shouldRedactKeyValue(key, value) {
|
|
2645
|
+
if (!value || value.startsWith("{{"))
|
|
2646
|
+
return false;
|
|
2647
|
+
if (value.length < MIN_SECRET_VALUE_LEN)
|
|
2648
|
+
return false;
|
|
2649
|
+
if (/^(true|false|yes|no|on|off|null|undefined|\d+)$/i.test(value))
|
|
2650
|
+
return false;
|
|
2651
|
+
if (SECRET_KEY_PATTERN.test(key))
|
|
2652
|
+
return true;
|
|
2653
|
+
for (const { re } of VALUE_PATTERNS) {
|
|
2654
|
+
if (re.test(value))
|
|
2655
|
+
return true;
|
|
2580
2656
|
}
|
|
2581
|
-
|
|
2657
|
+
return false;
|
|
2658
|
+
}
|
|
2659
|
+
function reasonFor(key, value) {
|
|
2660
|
+
if (SECRET_KEY_PATTERN.test(key))
|
|
2661
|
+
return `secret key name: ${key}`;
|
|
2662
|
+
for (const { re, reason } of VALUE_PATTERNS) {
|
|
2663
|
+
if (re.test(value))
|
|
2664
|
+
return reason;
|
|
2665
|
+
}
|
|
2666
|
+
return "secret value pattern";
|
|
2667
|
+
}
|
|
2668
|
+
function redactContent(content, format) {
|
|
2669
|
+
switch (format) {
|
|
2670
|
+
case "shell":
|
|
2671
|
+
return redactShell(content);
|
|
2672
|
+
case "json":
|
|
2673
|
+
return redactJson(content);
|
|
2674
|
+
case "toml":
|
|
2675
|
+
return redactToml(content);
|
|
2676
|
+
case "ini":
|
|
2677
|
+
return redactIni(content);
|
|
2678
|
+
default:
|
|
2679
|
+
return redactGeneric(content);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
function scanSecrets(content, format) {
|
|
2683
|
+
const r = redactContent(content, format);
|
|
2684
|
+
return r.redacted;
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
// 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
|
+
];
|
|
2708
|
+
async function syncKnown(opts = {}) {
|
|
2709
|
+
const d = opts.db || getDatabase();
|
|
2582
2710
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
2711
|
+
const home = homedir2();
|
|
2712
|
+
let targets = KNOWN_CONFIGS;
|
|
2713
|
+
if (opts.agent)
|
|
2714
|
+
targets = targets.filter((k) => k.agent === opts.agent);
|
|
2715
|
+
if (opts.category)
|
|
2716
|
+
targets = targets.filter((k) => k.category === opts.category);
|
|
2583
2717
|
const allConfigs = listConfigs(undefined, d);
|
|
2584
|
-
for (const
|
|
2585
|
-
if (
|
|
2586
|
-
|
|
2718
|
+
for (const known of targets) {
|
|
2719
|
+
if (known.rulesDir) {
|
|
2720
|
+
const absDir = expandPath(known.rulesDir);
|
|
2721
|
+
if (!existsSync3(absDir)) {
|
|
2722
|
+
result.skipped.push(known.rulesDir);
|
|
2723
|
+
continue;
|
|
2724
|
+
}
|
|
2725
|
+
const mdFiles = readdirSync(absDir).filter((f) => f.endsWith(".md"));
|
|
2726
|
+
for (const f of mdFiles) {
|
|
2727
|
+
const abs2 = join2(absDir, f);
|
|
2728
|
+
const targetPath = abs2.replace(home, "~");
|
|
2729
|
+
const raw = readFileSync2(abs2, "utf-8");
|
|
2730
|
+
const { content, isTemplate } = redactContent(raw, "markdown");
|
|
2731
|
+
const name = `claude-rules-${f}`;
|
|
2732
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2733
|
+
const existing = allConfigs.find((c) => c.target_path === targetPath || c.slug === slug);
|
|
2734
|
+
if (!existing) {
|
|
2735
|
+
if (!opts.dryRun)
|
|
2736
|
+
createConfig({ name, category: "rules", agent: "claude", format: "markdown", content, target_path: targetPath, is_template: isTemplate }, d);
|
|
2737
|
+
result.added++;
|
|
2738
|
+
} else if (existing.content !== content) {
|
|
2739
|
+
if (!opts.dryRun)
|
|
2740
|
+
updateConfig(existing.id, { content, is_template: isTemplate }, d);
|
|
2741
|
+
result.updated++;
|
|
2742
|
+
} else {
|
|
2743
|
+
result.unchanged++;
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
continue;
|
|
2747
|
+
}
|
|
2748
|
+
const abs = expandPath(known.path);
|
|
2749
|
+
if (!existsSync3(abs)) {
|
|
2750
|
+
result.skipped.push(known.path);
|
|
2587
2751
|
continue;
|
|
2588
2752
|
}
|
|
2589
2753
|
try {
|
|
2590
|
-
const
|
|
2591
|
-
|
|
2592
|
-
|
|
2754
|
+
const rawContent = readFileSync2(abs, "utf-8");
|
|
2755
|
+
if (rawContent.length > 500000) {
|
|
2756
|
+
result.skipped.push(known.path + " (too large)");
|
|
2757
|
+
continue;
|
|
2758
|
+
}
|
|
2759
|
+
const fmt = known.format ?? detectFormat(abs);
|
|
2760
|
+
const { content, isTemplate } = redactContent(rawContent, fmt);
|
|
2761
|
+
const targetPath = abs.replace(home, "~");
|
|
2762
|
+
const existing = allConfigs.find((c) => c.target_path === targetPath || c.slug === known.name);
|
|
2593
2763
|
if (!existing) {
|
|
2594
2764
|
if (!opts.dryRun) {
|
|
2595
|
-
const name = relative(absDir, file);
|
|
2596
2765
|
createConfig({
|
|
2597
|
-
name,
|
|
2598
|
-
category:
|
|
2599
|
-
agent:
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2766
|
+
name: known.name,
|
|
2767
|
+
category: known.category,
|
|
2768
|
+
agent: known.agent,
|
|
2769
|
+
format: fmt,
|
|
2770
|
+
content,
|
|
2771
|
+
target_path: known.kind === "reference" ? null : targetPath,
|
|
2772
|
+
kind: known.kind ?? "file",
|
|
2773
|
+
description: known.description,
|
|
2774
|
+
is_template: isTemplate
|
|
2603
2775
|
}, d);
|
|
2604
2776
|
}
|
|
2605
2777
|
result.added++;
|
|
2606
2778
|
} else if (existing.content !== content) {
|
|
2607
|
-
if (!opts.dryRun)
|
|
2608
|
-
updateConfig(existing.id, { content }, d);
|
|
2609
|
-
}
|
|
2779
|
+
if (!opts.dryRun)
|
|
2780
|
+
updateConfig(existing.id, { content, is_template: isTemplate }, d);
|
|
2610
2781
|
result.updated++;
|
|
2611
2782
|
} else {
|
|
2612
2783
|
result.unchanged++;
|
|
2613
2784
|
}
|
|
2614
2785
|
} catch {
|
|
2615
|
-
result.skipped.push(
|
|
2786
|
+
result.skipped.push(known.path);
|
|
2616
2787
|
}
|
|
2617
2788
|
}
|
|
2618
2789
|
return result;
|
|
2619
2790
|
}
|
|
2620
|
-
async function
|
|
2791
|
+
async function syncToDisk(opts = {}) {
|
|
2621
2792
|
const d = opts.db || getDatabase();
|
|
2622
|
-
const absDir = expandPath(dir);
|
|
2623
|
-
const normalizedDir = dir.startsWith("~/") ? dir : absDir.replace(homedir2(), "~");
|
|
2624
|
-
const configs = listConfigs(undefined, d).filter((c) => c.target_path && (c.target_path.startsWith(normalizedDir) || c.target_path.startsWith(absDir)));
|
|
2625
2793
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
2794
|
+
let configs = listConfigs({ kind: "file", ...opts.agent ? { agent: opts.agent } : {}, ...opts.category ? { category: opts.category } : {} }, d);
|
|
2626
2795
|
for (const config of configs) {
|
|
2627
|
-
if (config.
|
|
2796
|
+
if (!config.target_path)
|
|
2628
2797
|
continue;
|
|
2629
2798
|
try {
|
|
2630
2799
|
const r = await applyConfig(config, { dryRun: opts.dryRun, db: d });
|
|
2631
|
-
|
|
2632
|
-
existsSync3(expandPath(config.target_path)) ? result.updated++ : result.added++;
|
|
2633
|
-
} else {
|
|
2634
|
-
result.unchanged++;
|
|
2635
|
-
}
|
|
2800
|
+
r.changed ? result.updated++ : result.unchanged++;
|
|
2636
2801
|
} catch {
|
|
2637
|
-
result.skipped.push(config.target_path
|
|
2802
|
+
result.skipped.push(config.target_path);
|
|
2638
2803
|
}
|
|
2639
2804
|
}
|
|
2640
2805
|
return result;
|
|
@@ -2656,29 +2821,77 @@ function diffConfig(config) {
|
|
|
2656
2821
|
const maxLen = Math.max(stored.length, disk.length);
|
|
2657
2822
|
for (let i = 0;i < maxLen; i++) {
|
|
2658
2823
|
const s = stored[i];
|
|
2659
|
-
const
|
|
2660
|
-
if (s ===
|
|
2824
|
+
const dk = disk[i];
|
|
2825
|
+
if (s === dk) {
|
|
2661
2826
|
if (s !== undefined)
|
|
2662
2827
|
lines.push(` ${s}`);
|
|
2663
2828
|
} else {
|
|
2664
2829
|
if (s !== undefined)
|
|
2665
2830
|
lines.push(`-${s}`);
|
|
2666
|
-
if (
|
|
2667
|
-
lines.push(`+${
|
|
2831
|
+
if (dk !== undefined)
|
|
2832
|
+
lines.push(`+${dk}`);
|
|
2668
2833
|
}
|
|
2669
2834
|
}
|
|
2670
2835
|
return lines.join(`
|
|
2671
2836
|
`);
|
|
2672
2837
|
}
|
|
2838
|
+
function detectCategory(filePath) {
|
|
2839
|
+
const p = filePath.toLowerCase().replace(homedir2(), "~");
|
|
2840
|
+
if (p.includes("/.claude/rules/") || p.endsWith("claude.md") || p.endsWith("agents.md") || p.endsWith("gemini.md"))
|
|
2841
|
+
return "rules";
|
|
2842
|
+
if (p.includes("/.claude/") || p.includes("/.codex/") || p.includes("/.gemini/") || p.includes("/.cursor/"))
|
|
2843
|
+
return "agent";
|
|
2844
|
+
if (p.includes(".mcp.json") || p.includes("mcp"))
|
|
2845
|
+
return "mcp";
|
|
2846
|
+
if (p.includes(".zshrc") || p.includes(".zprofile") || p.includes(".bashrc") || p.includes(".bash_profile"))
|
|
2847
|
+
return "shell";
|
|
2848
|
+
if (p.includes(".gitconfig") || p.includes(".gitignore"))
|
|
2849
|
+
return "git";
|
|
2850
|
+
if (p.includes(".npmrc") || p.includes("tsconfig") || p.includes("bunfig"))
|
|
2851
|
+
return "tools";
|
|
2852
|
+
if (p.includes(".secrets"))
|
|
2853
|
+
return "secrets_schema";
|
|
2854
|
+
return "tools";
|
|
2855
|
+
}
|
|
2856
|
+
function detectAgent(filePath) {
|
|
2857
|
+
const p = filePath.toLowerCase().replace(homedir2(), "~");
|
|
2858
|
+
if (p.includes("/.claude/") || p.endsWith("claude.md"))
|
|
2859
|
+
return "claude";
|
|
2860
|
+
if (p.includes("/.codex/") || p.endsWith("agents.md"))
|
|
2861
|
+
return "codex";
|
|
2862
|
+
if (p.includes("/.gemini/") || p.endsWith("gemini.md"))
|
|
2863
|
+
return "gemini";
|
|
2864
|
+
if (p.includes(".zshrc") || p.includes(".zprofile") || p.includes(".bashrc"))
|
|
2865
|
+
return "zsh";
|
|
2866
|
+
if (p.includes(".gitconfig") || p.includes(".gitignore"))
|
|
2867
|
+
return "git";
|
|
2868
|
+
if (p.includes(".npmrc"))
|
|
2869
|
+
return "npm";
|
|
2870
|
+
return "global";
|
|
2871
|
+
}
|
|
2872
|
+
function detectFormat(filePath) {
|
|
2873
|
+
const ext = extname(filePath).toLowerCase();
|
|
2874
|
+
if (ext === ".json")
|
|
2875
|
+
return "json";
|
|
2876
|
+
if (ext === ".toml")
|
|
2877
|
+
return "toml";
|
|
2878
|
+
if (ext === ".yaml" || ext === ".yml")
|
|
2879
|
+
return "yaml";
|
|
2880
|
+
if (ext === ".md" || ext === ".markdown")
|
|
2881
|
+
return "markdown";
|
|
2882
|
+
if (ext === ".ini" || ext === ".cfg")
|
|
2883
|
+
return "ini";
|
|
2884
|
+
return "text";
|
|
2885
|
+
}
|
|
2673
2886
|
|
|
2674
2887
|
// src/lib/export.ts
|
|
2675
2888
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2676
|
-
import { join as join3, resolve as
|
|
2889
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
2677
2890
|
import { tmpdir } from "os";
|
|
2678
2891
|
async function exportConfigs(outputPath, opts = {}) {
|
|
2679
2892
|
const d = opts.db || getDatabase();
|
|
2680
2893
|
const configs = listConfigs(opts.filter, d);
|
|
2681
|
-
const absOutput =
|
|
2894
|
+
const absOutput = resolve3(outputPath);
|
|
2682
2895
|
const tmpDir = join3(tmpdir(), `configs-export-${Date.now()}`);
|
|
2683
2896
|
const contentsDir = join3(tmpDir, "contents");
|
|
2684
2897
|
try {
|
|
@@ -2712,12 +2925,12 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
2712
2925
|
|
|
2713
2926
|
// src/lib/import.ts
|
|
2714
2927
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync as rmSync2 } from "fs";
|
|
2715
|
-
import { join as join4, resolve as
|
|
2928
|
+
import { join as join4, resolve as resolve4 } from "path";
|
|
2716
2929
|
import { tmpdir as tmpdir2 } from "os";
|
|
2717
2930
|
async function importConfigs(bundlePath, opts = {}) {
|
|
2718
2931
|
const d = opts.db || getDatabase();
|
|
2719
2932
|
const conflict = opts.conflict ?? "skip";
|
|
2720
|
-
const absPath =
|
|
2933
|
+
const absPath = resolve4(bundlePath);
|
|
2721
2934
|
const tmpDir = join4(tmpdir2(), `configs-import-${Date.now()}`);
|
|
2722
2935
|
const result = { created: 0, updated: 0, skipped: 0, errors: [] };
|
|
2723
2936
|
try {
|
|
@@ -2855,12 +3068,14 @@ program.command("show <id>").description("Show a config's content and metadata")
|
|
|
2855
3068
|
}
|
|
2856
3069
|
});
|
|
2857
3070
|
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) => {
|
|
2858
|
-
const abs =
|
|
3071
|
+
const abs = resolve5(filePath);
|
|
2859
3072
|
if (!existsSync6(abs)) {
|
|
2860
3073
|
console.error(chalk.red(`File not found: ${abs}`));
|
|
2861
3074
|
process.exit(1);
|
|
2862
3075
|
}
|
|
2863
|
-
const
|
|
3076
|
+
const rawContent = readFileSync4(abs, "utf-8");
|
|
3077
|
+
const fmt = detectFormat(abs);
|
|
3078
|
+
const { content, redacted, isTemplate } = redactContent(rawContent, fmt);
|
|
2864
3079
|
const targetPath = abs.startsWith(homedir3()) ? abs.replace(homedir3(), "~") : abs;
|
|
2865
3080
|
const name = opts.name || filePath.split("/").pop();
|
|
2866
3081
|
const config = createConfig({
|
|
@@ -2869,11 +3084,17 @@ program.command("add <path>").description("Ingest a file into the config DB").op
|
|
|
2869
3084
|
category: opts.category ?? detectCategory(abs),
|
|
2870
3085
|
agent: opts.agent ?? detectAgent(abs),
|
|
2871
3086
|
target_path: opts.kind === "reference" ? null : targetPath,
|
|
2872
|
-
format:
|
|
3087
|
+
format: fmt,
|
|
2873
3088
|
content,
|
|
2874
|
-
is_template: opts.template ?? false
|
|
3089
|
+
is_template: (opts.template ?? false) || isTemplate
|
|
2875
3090
|
});
|
|
2876
3091
|
console.log(chalk.green("\u2713") + ` Added: ${chalk.bold(config.name)} ${chalk.dim(`(${config.slug})`)}`);
|
|
3092
|
+
if (redacted.length > 0) {
|
|
3093
|
+
console.log(chalk.yellow(` \u26A0 Redacted ${redacted.length} secret(s):`));
|
|
3094
|
+
for (const r of redacted)
|
|
3095
|
+
console.log(chalk.yellow(` line ${r.line}: {{${r.varName}}} \u2014 ${r.reason}`));
|
|
3096
|
+
console.log(chalk.dim(" Config stored as a template. Use `configs template vars` to see placeholders."));
|
|
3097
|
+
}
|
|
2877
3098
|
});
|
|
2878
3099
|
program.command("apply <id>").description("Apply a config to its target_path on disk").option("--dry-run", "preview without writing").option("--force", "overwrite even if unchanged").action(async (id, opts) => {
|
|
2879
3100
|
try {
|
|
@@ -2897,14 +3118,30 @@ program.command("diff <id>").description("Show diff between stored config and di
|
|
|
2897
3118
|
process.exit(1);
|
|
2898
3119
|
}
|
|
2899
3120
|
});
|
|
2900
|
-
program.command("sync").description("
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
3121
|
+
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) => {
|
|
3122
|
+
if (opts.list) {
|
|
3123
|
+
const targets = KNOWN_CONFIGS.filter((k) => {
|
|
3124
|
+
if (opts.agent && k.agent !== opts.agent)
|
|
3125
|
+
return false;
|
|
3126
|
+
if (opts.category && k.category !== opts.category)
|
|
3127
|
+
return false;
|
|
3128
|
+
return true;
|
|
3129
|
+
});
|
|
3130
|
+
console.log(chalk.bold(`Known configs (${targets.length}):`));
|
|
3131
|
+
for (const k of targets) {
|
|
3132
|
+
console.log(` ${chalk.cyan(k.rulesDir ? k.rulesDir + "/*.md" : k.path)} ${chalk.dim(`[${k.category}/${k.agent}]`)}`);
|
|
3133
|
+
}
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
if (opts.toDisk) {
|
|
3137
|
+
const result = await syncToDisk({ dryRun: opts.dryRun, agent: opts.agent, category: opts.category });
|
|
3138
|
+
console.log(chalk.green("\u2713") + ` Written to disk: updated:${result.updated} unchanged:${result.unchanged} skipped:${result.skipped.length}`);
|
|
2905
3139
|
} else {
|
|
2906
|
-
const result = await
|
|
2907
|
-
console.log(chalk.green(
|
|
3140
|
+
const result = await syncKnown({ dryRun: opts.dryRun, agent: opts.agent, category: opts.category });
|
|
3141
|
+
console.log(chalk.green("\u2713") + ` Synced: +${result.added} updated:${result.updated} unchanged:${result.unchanged} skipped:${result.skipped.length}`);
|
|
3142
|
+
if (result.skipped.length > 0) {
|
|
3143
|
+
console.log(chalk.dim(" skipped (not found): " + result.skipped.join(", ")));
|
|
3144
|
+
}
|
|
2908
3145
|
}
|
|
2909
3146
|
});
|
|
2910
3147
|
program.command("export").description("Export configs as a tar.gz bundle").option("-o, --output <path>", "output file", "./configs-export.tar.gz").option("-c, --category <cat>", "filter by category").action(async (opts) => {
|
|
@@ -3083,5 +3320,29 @@ templateCmd.command("vars <id>").description("Show template variables").action(a
|
|
|
3083
3320
|
process.exit(1);
|
|
3084
3321
|
}
|
|
3085
3322
|
});
|
|
3323
|
+
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) => {
|
|
3324
|
+
const configs = id ? [getConfig(id)] : listConfigs({ kind: "file" });
|
|
3325
|
+
let total = 0;
|
|
3326
|
+
for (const c of configs) {
|
|
3327
|
+
const secrets = scanSecrets(c.content, c.format);
|
|
3328
|
+
if (secrets.length === 0)
|
|
3329
|
+
continue;
|
|
3330
|
+
total += secrets.length;
|
|
3331
|
+
console.log(chalk.yellow(`\u26A0 ${c.slug}`) + chalk.dim(` \u2014 ${secrets.length} secret(s):`));
|
|
3332
|
+
for (const s of secrets)
|
|
3333
|
+
console.log(` line ${s.line}: ${chalk.red(s.varName)} \u2014 ${s.reason}`);
|
|
3334
|
+
if (opts.fix) {
|
|
3335
|
+
const { content, isTemplate } = redactContent(c.content, c.format);
|
|
3336
|
+
updateConfig(c.id, { content, is_template: isTemplate });
|
|
3337
|
+
console.log(chalk.green(" \u2713 Redacted and updated."));
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
if (total === 0) {
|
|
3341
|
+
console.log(chalk.green("\u2713") + " No secrets detected.");
|
|
3342
|
+
} else if (!opts.fix) {
|
|
3343
|
+
console.log(chalk.yellow(`
|
|
3344
|
+
Run with --fix to redact in-place.`));
|
|
3345
|
+
}
|
|
3346
|
+
});
|
|
3086
3347
|
program.version(pkg.version).name("configs");
|
|
3087
3348
|
program.parse(process.argv);
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,10 @@ export { registerMachine, updateMachineApplied, listMachines, currentHostname, c
|
|
|
6
6
|
export { getDatabase, resetDatabase, uuid, now, slugify } from "./db/database.js";
|
|
7
7
|
export { applyConfig, applyConfigs, expandPath } from "./lib/apply.js";
|
|
8
8
|
export type { ApplyOptions } from "./lib/apply.js";
|
|
9
|
-
export {
|
|
10
|
-
export
|
|
9
|
+
export { syncKnown, syncToDisk, diffConfig, detectCategory, detectAgent, detectFormat, KNOWN_CONFIGS } from "./lib/sync.js";
|
|
10
|
+
export { syncFromDir, syncToDir } from "./lib/sync-dir.js";
|
|
11
|
+
export type { SyncKnownOptions, SyncToDiskOptions } from "./lib/sync.js";
|
|
12
|
+
export type { SyncFromDirOptions } from "./lib/sync-dir.js";
|
|
11
13
|
export { exportConfigs } from "./lib/export.js";
|
|
12
14
|
export { importConfigs } from "./lib/import.js";
|
|
13
15
|
export type { ExportOptions } from "./lib/export.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,MAAM,kBAAkB,CAAC;AAGzK,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGnH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGlF,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACvE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,EAAE,
|
|
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,MAAM,kBAAkB,CAAC;AAGzK,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGnH,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGlF,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACvE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC5H,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACzE,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"}
|