@hasna/configs 0.1.5 → 0.2.0
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 +375 -11
- package/dist/lib/sync.d.ts +12 -0
- package/dist/lib/sync.d.ts.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +93 -60
- package/package.json +1 -1
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 {
|
|
@@ -2715,17 +2718,81 @@ var exports_sync = {};
|
|
|
2715
2718
|
__export(exports_sync, {
|
|
2716
2719
|
syncToDisk: () => syncToDisk,
|
|
2717
2720
|
syncToDir: () => syncToDir,
|
|
2721
|
+
syncProject: () => syncProject,
|
|
2718
2722
|
syncKnown: () => syncKnown,
|
|
2719
2723
|
syncFromDir: () => syncFromDir,
|
|
2720
2724
|
diffConfig: () => diffConfig,
|
|
2721
2725
|
detectFormat: () => detectFormat,
|
|
2722
2726
|
detectCategory: () => detectCategory,
|
|
2723
2727
|
detectAgent: () => detectAgent,
|
|
2728
|
+
PROJECT_CONFIG_FILES: () => PROJECT_CONFIG_FILES,
|
|
2724
2729
|
KNOWN_CONFIGS: () => KNOWN_CONFIGS
|
|
2725
2730
|
});
|
|
2726
2731
|
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
2727
2732
|
import { extname, join as join3 } from "path";
|
|
2728
2733
|
import { homedir as homedir3 } from "os";
|
|
2734
|
+
async function syncProject(opts) {
|
|
2735
|
+
const d = opts.db || getDatabase();
|
|
2736
|
+
const absDir = expandPath(opts.projectDir);
|
|
2737
|
+
const projectName = absDir.split("/").pop() || "project";
|
|
2738
|
+
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
2739
|
+
const allConfigs = listConfigs(undefined, d);
|
|
2740
|
+
for (const pf of PROJECT_CONFIG_FILES) {
|
|
2741
|
+
const abs = join3(absDir, pf.file);
|
|
2742
|
+
if (!existsSync4(abs))
|
|
2743
|
+
continue;
|
|
2744
|
+
try {
|
|
2745
|
+
const rawContent = readFileSync3(abs, "utf-8");
|
|
2746
|
+
if (rawContent.length > 500000) {
|
|
2747
|
+
result.skipped.push(pf.file);
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
const { content, isTemplate } = redactContent(rawContent, pf.format);
|
|
2751
|
+
const name = `${projectName}/${pf.file}`;
|
|
2752
|
+
const targetPath = abs.replace(homedir3(), "~");
|
|
2753
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2754
|
+
const existing = allConfigs.find((c) => c.target_path === targetPath || c.slug === slug);
|
|
2755
|
+
if (!existing) {
|
|
2756
|
+
if (!opts.dryRun)
|
|
2757
|
+
createConfig({ name, category: pf.category, agent: pf.agent, format: pf.format, content, target_path: targetPath, is_template: isTemplate }, d);
|
|
2758
|
+
result.added++;
|
|
2759
|
+
} else if (existing.content !== content) {
|
|
2760
|
+
if (!opts.dryRun)
|
|
2761
|
+
updateConfig(existing.id, { content, is_template: isTemplate }, d);
|
|
2762
|
+
result.updated++;
|
|
2763
|
+
} else {
|
|
2764
|
+
result.unchanged++;
|
|
2765
|
+
}
|
|
2766
|
+
} catch {
|
|
2767
|
+
result.skipped.push(pf.file);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
const rulesDir = join3(absDir, ".claude", "rules");
|
|
2771
|
+
if (existsSync4(rulesDir)) {
|
|
2772
|
+
const mdFiles = readdirSync2(rulesDir).filter((f) => f.endsWith(".md"));
|
|
2773
|
+
for (const f of mdFiles) {
|
|
2774
|
+
const abs = join3(rulesDir, f);
|
|
2775
|
+
const raw = readFileSync3(abs, "utf-8");
|
|
2776
|
+
const { content, isTemplate } = redactContent(raw, "markdown");
|
|
2777
|
+
const name = `${projectName}/rules/${f}`;
|
|
2778
|
+
const targetPath = abs.replace(homedir3(), "~");
|
|
2779
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2780
|
+
const existing = allConfigs.find((c) => c.target_path === targetPath || c.slug === slug);
|
|
2781
|
+
if (!existing) {
|
|
2782
|
+
if (!opts.dryRun)
|
|
2783
|
+
createConfig({ name, category: "rules", agent: "claude", format: "markdown", content, target_path: targetPath, is_template: isTemplate }, d);
|
|
2784
|
+
result.added++;
|
|
2785
|
+
} else if (existing.content !== content) {
|
|
2786
|
+
if (!opts.dryRun)
|
|
2787
|
+
updateConfig(existing.id, { content, is_template: isTemplate }, d);
|
|
2788
|
+
result.updated++;
|
|
2789
|
+
} else {
|
|
2790
|
+
result.unchanged++;
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
return result;
|
|
2795
|
+
}
|
|
2729
2796
|
async function syncKnown(opts = {}) {
|
|
2730
2797
|
const d = opts.db || getDatabase();
|
|
2731
2798
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
@@ -2904,7 +2971,7 @@ function detectFormat(filePath) {
|
|
|
2904
2971
|
return "ini";
|
|
2905
2972
|
return "text";
|
|
2906
2973
|
}
|
|
2907
|
-
var KNOWN_CONFIGS;
|
|
2974
|
+
var KNOWN_CONFIGS, PROJECT_CONFIG_FILES;
|
|
2908
2975
|
var init_sync = __esm(() => {
|
|
2909
2976
|
init_database();
|
|
2910
2977
|
init_configs();
|
|
@@ -2931,6 +2998,15 @@ var init_sync = __esm(() => {
|
|
|
2931
2998
|
{ path: "~/.npmrc", name: "npmrc", category: "tools", agent: "npm", format: "ini" },
|
|
2932
2999
|
{ path: "~/.bunfig.toml", name: "bunfig", category: "tools", agent: "global", format: "toml" }
|
|
2933
3000
|
];
|
|
3001
|
+
PROJECT_CONFIG_FILES = [
|
|
3002
|
+
{ file: "CLAUDE.md", category: "rules", agent: "claude", format: "markdown" },
|
|
3003
|
+
{ file: ".claude/settings.json", category: "agent", agent: "claude", format: "json" },
|
|
3004
|
+
{ file: ".claude/settings.local.json", category: "agent", agent: "claude", format: "json" },
|
|
3005
|
+
{ file: ".mcp.json", category: "mcp", agent: "claude", format: "json" },
|
|
3006
|
+
{ file: "AGENTS.md", category: "rules", agent: "codex", format: "markdown" },
|
|
3007
|
+
{ file: ".codex/AGENTS.md", category: "rules", agent: "codex", format: "markdown" },
|
|
3008
|
+
{ file: "GEMINI.md", category: "rules", agent: "gemini", format: "markdown" }
|
|
3009
|
+
];
|
|
2934
3010
|
});
|
|
2935
3011
|
|
|
2936
3012
|
// node_modules/commander/esm.mjs
|
|
@@ -3022,6 +3098,7 @@ function getProfileConfigs(profileIdOrSlug, db) {
|
|
|
3022
3098
|
|
|
3023
3099
|
// src/cli/index.tsx
|
|
3024
3100
|
init_snapshots();
|
|
3101
|
+
init_database();
|
|
3025
3102
|
init_apply();
|
|
3026
3103
|
init_sync();
|
|
3027
3104
|
init_redact();
|
|
@@ -3171,7 +3248,8 @@ function fmtConfig(c, format) {
|
|
|
3171
3248
|
].filter(Boolean).join(`
|
|
3172
3249
|
`);
|
|
3173
3250
|
}
|
|
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) => {
|
|
3251
|
+
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) => {
|
|
3252
|
+
const fmt = opts.brief ? "compact" : opts.format;
|
|
3175
3253
|
const configs = listConfigs({
|
|
3176
3254
|
category: opts.category,
|
|
3177
3255
|
agent: opts.agent,
|
|
@@ -3183,13 +3261,13 @@ program.command("list").alias("ls").description("List stored configs").option("-
|
|
|
3183
3261
|
console.log(chalk.dim("No configs found."));
|
|
3184
3262
|
return;
|
|
3185
3263
|
}
|
|
3186
|
-
if (
|
|
3264
|
+
if (fmt === "json") {
|
|
3187
3265
|
console.log(JSON.stringify(configs, null, 2));
|
|
3188
3266
|
return;
|
|
3189
3267
|
}
|
|
3190
3268
|
for (const c of configs) {
|
|
3191
|
-
console.log(fmtConfig(c,
|
|
3192
|
-
if (
|
|
3269
|
+
console.log(fmtConfig(c, fmt));
|
|
3270
|
+
if (fmt === "table")
|
|
3193
3271
|
console.log();
|
|
3194
3272
|
}
|
|
3195
3273
|
console.log(chalk.dim(`${configs.length} config(s)`));
|
|
@@ -3256,17 +3334,33 @@ program.command("apply <id>").description("Apply a config to its target_path on
|
|
|
3256
3334
|
process.exit(1);
|
|
3257
3335
|
}
|
|
3258
3336
|
});
|
|
3259
|
-
program.command("diff
|
|
3337
|
+
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
3338
|
try {
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3339
|
+
if (id) {
|
|
3340
|
+
const config = getConfig(id);
|
|
3341
|
+
console.log(diffConfig(config));
|
|
3342
|
+
return;
|
|
3343
|
+
}
|
|
3344
|
+
const configs = listConfigs({ kind: "file" });
|
|
3345
|
+
let drifted = 0;
|
|
3346
|
+
for (const c of configs) {
|
|
3347
|
+
if (!c.target_path)
|
|
3348
|
+
continue;
|
|
3349
|
+
const diff = diffConfig(c);
|
|
3350
|
+
if (diff.includes("no diff") || diff.includes("not found"))
|
|
3351
|
+
continue;
|
|
3352
|
+
drifted++;
|
|
3353
|
+
console.log(chalk.bold(c.slug) + chalk.dim(` (${c.target_path})`));
|
|
3354
|
+
console.log(diff);
|
|
3355
|
+
console.log();
|
|
3356
|
+
}
|
|
3357
|
+
console.log(chalk.dim(`${drifted}/${configs.length} drifted`));
|
|
3264
3358
|
} catch (e) {
|
|
3265
3359
|
console.error(chalk.red(e instanceof Error ? e.message : String(e)));
|
|
3266
3360
|
process.exit(1);
|
|
3267
3361
|
}
|
|
3268
3362
|
});
|
|
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) => {
|
|
3363
|
+
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
3364
|
if (opts.list) {
|
|
3271
3365
|
const targets = KNOWN_CONFIGS.filter((k) => {
|
|
3272
3366
|
if (opts.agent && k.agent !== opts.agent)
|
|
@@ -3281,6 +3375,12 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
3281
3375
|
}
|
|
3282
3376
|
return;
|
|
3283
3377
|
}
|
|
3378
|
+
if (opts.project) {
|
|
3379
|
+
const dir = typeof opts.project === "string" ? opts.project : process.cwd();
|
|
3380
|
+
const result = await syncProject({ projectDir: dir, dryRun: opts.dryRun });
|
|
3381
|
+
console.log(chalk.green("\u2713") + ` Project sync: +${result.added} updated:${result.updated} unchanged:${result.unchanged} skipped:${result.skipped.length}`);
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3284
3384
|
if (opts.toDisk) {
|
|
3285
3385
|
const result = await syncToDisk({ dryRun: opts.dryRun, agent: opts.agent, category: opts.category });
|
|
3286
3386
|
console.log(chalk.green("\u2713") + ` Written to disk: updated:${result.updated} unchanged:${result.unchanged} skipped:${result.skipped.length}`);
|
|
@@ -3332,13 +3432,22 @@ program.command("whoami").description("Show setup summary").action(async () => {
|
|
|
3332
3432
|
}
|
|
3333
3433
|
});
|
|
3334
3434
|
var profileCmd = program.command("profile").description("Manage config profiles (named bundles)");
|
|
3335
|
-
profileCmd.command("list").description("List all profiles").action(async () => {
|
|
3435
|
+
profileCmd.command("list").description("List all profiles").option("--brief", "compact one-line output").option("-f, --format <fmt>", "table|json|compact", "table").action(async (opts) => {
|
|
3436
|
+
const fmt = opts.brief ? "compact" : opts.format;
|
|
3336
3437
|
const profiles = listProfiles();
|
|
3337
3438
|
if (profiles.length === 0) {
|
|
3338
3439
|
console.log(chalk.dim("No profiles."));
|
|
3339
3440
|
return;
|
|
3340
3441
|
}
|
|
3442
|
+
if (fmt === "json") {
|
|
3443
|
+
console.log(JSON.stringify(profiles, null, 2));
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3341
3446
|
for (const p of profiles) {
|
|
3447
|
+
if (fmt === "compact") {
|
|
3448
|
+
console.log(`${p.slug} ${getProfileConfigs(p.id).length} configs`);
|
|
3449
|
+
continue;
|
|
3450
|
+
}
|
|
3342
3451
|
const configs = getProfileConfigs(p.id);
|
|
3343
3452
|
console.log(`${chalk.bold(p.name)} ${chalk.dim(`(${p.slug})`)} \u2014 ${configs.length} config(s)`);
|
|
3344
3453
|
if (p.description)
|
|
@@ -3518,5 +3627,260 @@ program.command("scan [id]").description("Scan configs for secrets. Defaults to
|
|
|
3518
3627
|
Run with --fix to redact in-place.`));
|
|
3519
3628
|
}
|
|
3520
3629
|
});
|
|
3630
|
+
var mcpCmd = program.command("mcp").description("Install/remove MCP server for AI agents");
|
|
3631
|
+
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) => {
|
|
3632
|
+
const targets = opts.all ? ["claude", "codex", "gemini"] : [
|
|
3633
|
+
...opts.claude ? ["claude"] : [],
|
|
3634
|
+
...opts.codex ? ["codex"] : [],
|
|
3635
|
+
...opts.gemini ? ["gemini"] : []
|
|
3636
|
+
];
|
|
3637
|
+
if (targets.length === 0) {
|
|
3638
|
+
console.log(chalk.dim("Specify --claude, --codex, --gemini, or --all"));
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
3641
|
+
for (const target of targets) {
|
|
3642
|
+
try {
|
|
3643
|
+
if (target === "claude") {
|
|
3644
|
+
const proc = Bun.spawn(["claude", "mcp", "add", "--transport", "stdio", "--scope", "user", "configs", "--", "configs-mcp"], { stdout: "inherit", stderr: "inherit" });
|
|
3645
|
+
await proc.exited;
|
|
3646
|
+
console.log(chalk.green("\u2713") + " Installed into Claude Code");
|
|
3647
|
+
} else if (target === "codex") {
|
|
3648
|
+
const { appendFileSync, existsSync: ex } = await import("fs");
|
|
3649
|
+
const { join: j } = await import("path");
|
|
3650
|
+
const configPath = j(homedir4(), ".codex", "config.toml");
|
|
3651
|
+
const block = `
|
|
3652
|
+
[mcp_servers.configs]
|
|
3653
|
+
command = "configs-mcp"
|
|
3654
|
+
args = []
|
|
3655
|
+
`;
|
|
3656
|
+
if (ex(configPath)) {
|
|
3657
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
3658
|
+
if (content.includes("[mcp_servers.configs]")) {
|
|
3659
|
+
console.log(chalk.dim("= Already installed in Codex"));
|
|
3660
|
+
continue;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
appendFileSync(configPath, block);
|
|
3664
|
+
console.log(chalk.green("\u2713") + " Installed into Codex");
|
|
3665
|
+
} else if (target === "gemini") {
|
|
3666
|
+
const { readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
|
|
3667
|
+
const { join: j } = await import("path");
|
|
3668
|
+
const configPath = j(homedir4(), ".gemini", "settings.json");
|
|
3669
|
+
let settings = {};
|
|
3670
|
+
if (ex(configPath)) {
|
|
3671
|
+
try {
|
|
3672
|
+
settings = JSON.parse(rf(configPath, "utf-8"));
|
|
3673
|
+
} catch {}
|
|
3674
|
+
}
|
|
3675
|
+
const mcpServers = settings["mcpServers"] ?? {};
|
|
3676
|
+
mcpServers["configs"] = { command: "configs-mcp", args: [] };
|
|
3677
|
+
settings["mcpServers"] = mcpServers;
|
|
3678
|
+
wf(configPath, JSON.stringify(settings, null, 2) + `
|
|
3679
|
+
`, "utf-8");
|
|
3680
|
+
console.log(chalk.green("\u2713") + " Installed into Gemini");
|
|
3681
|
+
}
|
|
3682
|
+
} catch (e) {
|
|
3683
|
+
console.error(chalk.red(`\u2717 Failed to install into ${target}: ${e instanceof Error ? e.message : String(e)}`));
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
});
|
|
3687
|
+
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) => {
|
|
3688
|
+
if (opts.claude || opts.all) {
|
|
3689
|
+
const proc = Bun.spawn(["claude", "mcp", "remove", "configs"], { stdout: "inherit", stderr: "inherit" });
|
|
3690
|
+
await proc.exited;
|
|
3691
|
+
console.log(chalk.green("\u2713") + " Removed from Claude Code");
|
|
3692
|
+
}
|
|
3693
|
+
});
|
|
3694
|
+
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) => {
|
|
3695
|
+
const dbPath = join6(homedir4(), ".configs", "configs.db");
|
|
3696
|
+
if (opts.force && existsSync7(dbPath)) {
|
|
3697
|
+
const { rmSync: rmSync3 } = await import("fs");
|
|
3698
|
+
rmSync3(dbPath);
|
|
3699
|
+
console.log(chalk.dim("Deleted existing DB."));
|
|
3700
|
+
resetDatabase();
|
|
3701
|
+
}
|
|
3702
|
+
console.log(chalk.bold(`@hasna/configs \u2014 initializing
|
|
3703
|
+
`));
|
|
3704
|
+
const result = await syncKnown({});
|
|
3705
|
+
console.log(chalk.green("\u2713") + ` Synced: +${result.added} updated:${result.updated} unchanged:${result.unchanged}`);
|
|
3706
|
+
if (result.skipped.length > 0) {
|
|
3707
|
+
console.log(chalk.dim(" skipped: " + result.skipped.join(", ")));
|
|
3708
|
+
}
|
|
3709
|
+
const refs = [
|
|
3710
|
+
{ slug: "workspace-structure", name: "Workspace Structure", category: "workspace", content: `# Workspace Structure
|
|
3711
|
+
|
|
3712
|
+
See ~/.claude/rules/workspace.md for full conventions.`, desc: "~/Workspace/ hierarchy and naming" },
|
|
3713
|
+
{ slug: "secrets-schema", name: "Secrets Schema", category: "secrets_schema", content: `# .secrets Schema
|
|
3714
|
+
|
|
3715
|
+
Location: ~/.secrets (sourced by ~/.zshrc)
|
|
3716
|
+
Format: export KEY_NAME="value"
|
|
3717
|
+
|
|
3718
|
+
Keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, EXA_API_KEY, NPM_TOKEN, GITHUB_TOKEN`, desc: "Shape of ~/.secrets (no values)" }
|
|
3719
|
+
];
|
|
3720
|
+
for (const ref of refs) {
|
|
3721
|
+
try {
|
|
3722
|
+
getConfig(ref.slug);
|
|
3723
|
+
} catch {
|
|
3724
|
+
createConfig({ name: ref.name, category: ref.category, agent: "global", format: "markdown", content: ref.content, kind: "reference", description: ref.desc });
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
try {
|
|
3728
|
+
getProfile("my-setup");
|
|
3729
|
+
} catch {
|
|
3730
|
+
const p = createProfile({ name: "my-setup", description: "Default profile with all known configs" });
|
|
3731
|
+
const allConfigs = listConfigs();
|
|
3732
|
+
for (const c of allConfigs)
|
|
3733
|
+
addConfigToProfile(p.id, c.id);
|
|
3734
|
+
console.log(chalk.green("\u2713") + ` Created profile "my-setup" with ${allConfigs.length} configs`);
|
|
3735
|
+
}
|
|
3736
|
+
const stats = getConfigStats();
|
|
3737
|
+
console.log(chalk.bold(`
|
|
3738
|
+
DB stats:`));
|
|
3739
|
+
for (const [key, count] of Object.entries(stats)) {
|
|
3740
|
+
if (count > 0)
|
|
3741
|
+
console.log(` ${key.padEnd(18)} ${count}`);
|
|
3742
|
+
}
|
|
3743
|
+
console.log(chalk.dim(`
|
|
3744
|
+
DB: ${dbPath}`));
|
|
3745
|
+
});
|
|
3746
|
+
program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").action(async () => {
|
|
3747
|
+
const dbPath = join6(homedir4(), ".configs", "configs.db");
|
|
3748
|
+
const stats = getConfigStats();
|
|
3749
|
+
const { statSync: st } = await import("fs");
|
|
3750
|
+
const dbSize = existsSync7(dbPath) ? st(dbPath).size : 0;
|
|
3751
|
+
console.log(chalk.bold("@hasna/configs") + chalk.dim(` v${pkg.version}`));
|
|
3752
|
+
console.log(chalk.cyan("DB:") + ` ${dbPath} (${(dbSize / 1024).toFixed(1)}KB)`);
|
|
3753
|
+
console.log(chalk.cyan("Total:") + ` ${stats["total"] || 0} configs
|
|
3754
|
+
`);
|
|
3755
|
+
const allKnown = listConfigs({ kind: "file" });
|
|
3756
|
+
let drifted = 0;
|
|
3757
|
+
let missing = 0;
|
|
3758
|
+
let secrets = 0;
|
|
3759
|
+
let templates = 0;
|
|
3760
|
+
for (const c of allKnown) {
|
|
3761
|
+
if (!c.target_path)
|
|
3762
|
+
continue;
|
|
3763
|
+
const path = expandPath(c.target_path);
|
|
3764
|
+
if (!existsSync7(path)) {
|
|
3765
|
+
missing++;
|
|
3766
|
+
continue;
|
|
3767
|
+
}
|
|
3768
|
+
const disk = readFileSync5(path, "utf-8");
|
|
3769
|
+
const { content: redactedDisk } = redactContent(disk, c.format);
|
|
3770
|
+
if (redactedDisk !== c.content)
|
|
3771
|
+
drifted++;
|
|
3772
|
+
if (c.is_template)
|
|
3773
|
+
templates++;
|
|
3774
|
+
const found = scanSecrets(c.content, c.format);
|
|
3775
|
+
secrets += found.length;
|
|
3776
|
+
}
|
|
3777
|
+
console.log(chalk.cyan("Drifted:") + ` ${drifted === 0 ? chalk.green("0") : chalk.yellow(String(drifted))} (stored \u2260 disk)`);
|
|
3778
|
+
console.log(chalk.cyan("Missing:") + ` ${missing === 0 ? chalk.green("0") : chalk.yellow(String(missing))} (file not on disk)`);
|
|
3779
|
+
console.log(chalk.cyan("Secrets:") + ` ${secrets === 0 ? chalk.green("0 \u2713") : chalk.red(String(secrets) + " \u26A0")} unredacted`);
|
|
3780
|
+
console.log(chalk.cyan("Templates:") + ` ${templates} (with {{VAR}} placeholders)`);
|
|
3781
|
+
});
|
|
3782
|
+
program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
|
|
3783
|
+
const { mkdirSync: mk } = await import("fs");
|
|
3784
|
+
const backupDir = join6(homedir4(), ".configs", "backups");
|
|
3785
|
+
mk(backupDir, { recursive: true });
|
|
3786
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
|
|
3787
|
+
const outPath = join6(backupDir, `configs-${ts}.tar.gz`);
|
|
3788
|
+
const result = await exportConfigs(outPath);
|
|
3789
|
+
const { statSync: st } = await import("fs");
|
|
3790
|
+
const size = st(outPath).size;
|
|
3791
|
+
console.log(chalk.green("\u2713") + ` Backup: ${result.count} configs \u2192 ${outPath} (${(size / 1024).toFixed(1)}KB)`);
|
|
3792
|
+
});
|
|
3793
|
+
program.command("restore <file>").description("Restore configs from a backup file").option("--overwrite", "overwrite existing configs (default: skip)").action(async (file, opts) => {
|
|
3794
|
+
const result = await importConfigs(file, { conflict: opts.overwrite ? "overwrite" : "skip" });
|
|
3795
|
+
console.log(chalk.green("\u2713") + ` Restored: +${result.created} updated:${result.updated} skipped:${result.skipped}`);
|
|
3796
|
+
if (result.errors.length > 0) {
|
|
3797
|
+
for (const e of result.errors)
|
|
3798
|
+
console.log(chalk.red(" " + e));
|
|
3799
|
+
}
|
|
3800
|
+
});
|
|
3801
|
+
program.command("doctor").description("Validate configs: syntax, permissions, missing files, secrets").action(async () => {
|
|
3802
|
+
let issues = 0;
|
|
3803
|
+
const pass = (msg) => console.log(chalk.green(" \u2713 ") + msg);
|
|
3804
|
+
const fail = (msg) => {
|
|
3805
|
+
issues++;
|
|
3806
|
+
console.log(chalk.red(" \u2717 ") + msg);
|
|
3807
|
+
};
|
|
3808
|
+
console.log(chalk.bold(`Config Doctor
|
|
3809
|
+
`));
|
|
3810
|
+
console.log(chalk.cyan("Known files on disk:"));
|
|
3811
|
+
for (const k of KNOWN_CONFIGS) {
|
|
3812
|
+
if (k.rulesDir) {
|
|
3813
|
+
existsSync7(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : fail(`${k.rulesDir}/ not found`);
|
|
3814
|
+
} else {
|
|
3815
|
+
existsSync7(expandPath(k.path)) ? pass(k.path) : fail(`${k.path} not found`);
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
const allConfigs = listConfigs();
|
|
3819
|
+
console.log(chalk.cyan(`
|
|
3820
|
+
Stored configs (${allConfigs.length}):`));
|
|
3821
|
+
let validCount = 0;
|
|
3822
|
+
for (const c of allConfigs) {
|
|
3823
|
+
if (c.format === "json") {
|
|
3824
|
+
try {
|
|
3825
|
+
JSON.parse(c.content);
|
|
3826
|
+
validCount++;
|
|
3827
|
+
} catch {
|
|
3828
|
+
fail(`${c.slug}: invalid JSON`);
|
|
3829
|
+
}
|
|
3830
|
+
} else {
|
|
3831
|
+
validCount++;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
pass(`${validCount}/${allConfigs.length} valid syntax`);
|
|
3835
|
+
let secretCount = 0;
|
|
3836
|
+
for (const c of allConfigs) {
|
|
3837
|
+
const found = scanSecrets(c.content, c.format);
|
|
3838
|
+
secretCount += found.length;
|
|
3839
|
+
}
|
|
3840
|
+
secretCount === 0 ? pass("No unredacted secrets") : fail(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
|
|
3841
|
+
console.log(`
|
|
3842
|
+
${issues === 0 ? chalk.green("\u2713 All checks passed") : chalk.yellow(`${issues} issue(s) found`)}`);
|
|
3843
|
+
});
|
|
3844
|
+
program.command("completions [shell]").description("Output shell completion script (zsh or bash)").action(async (shell) => {
|
|
3845
|
+
const sh = shell || "zsh";
|
|
3846
|
+
if (sh === "zsh") {
|
|
3847
|
+
console.log(`#compdef configs
|
|
3848
|
+
_configs() {
|
|
3849
|
+
local -a commands
|
|
3850
|
+
commands=(
|
|
3851
|
+
'list:List stored configs'
|
|
3852
|
+
'show:Show a config'
|
|
3853
|
+
'add:Ingest a file into the DB'
|
|
3854
|
+
'apply:Apply a config to disk'
|
|
3855
|
+
'diff:Show diff stored vs disk'
|
|
3856
|
+
'sync:Sync known configs from disk'
|
|
3857
|
+
'export:Export as tar.gz'
|
|
3858
|
+
'import:Import from tar.gz'
|
|
3859
|
+
'whoami:Setup summary'
|
|
3860
|
+
'status:Health check'
|
|
3861
|
+
'init:First-time setup'
|
|
3862
|
+
'scan:Scan for secrets'
|
|
3863
|
+
'profile:Manage profiles'
|
|
3864
|
+
'snapshot:Version history'
|
|
3865
|
+
'template:Template operations'
|
|
3866
|
+
'mcp:Install MCP server'
|
|
3867
|
+
'backup:Export to timestamped backup'
|
|
3868
|
+
'restore:Import from backup'
|
|
3869
|
+
'doctor:Validate configs'
|
|
3870
|
+
'completions:Output shell completions'
|
|
3871
|
+
)
|
|
3872
|
+
_describe 'command' commands
|
|
3873
|
+
}
|
|
3874
|
+
compdef _configs configs`);
|
|
3875
|
+
} else {
|
|
3876
|
+
console.log(`# bash completion for configs
|
|
3877
|
+
_configs_completions() {
|
|
3878
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
3879
|
+
local commands="list show add apply diff sync export import whoami status init scan profile snapshot template mcp backup restore doctor completions"
|
|
3880
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
3881
|
+
}
|
|
3882
|
+
complete -F _configs_completions configs`);
|
|
3883
|
+
}
|
|
3884
|
+
});
|
|
3521
3885
|
program.version(pkg.version).name("configs");
|
|
3522
3886
|
program.parse(process.argv);
|
package/dist/lib/sync.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/sync.d.ts.map
CHANGED
|
@@ -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":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";;;;;AAkPA,wBAAgD"}
|
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
2380
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|