@hasna/configs 0.1.2 → 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.
- package/dist/cli/index.js +390 -216
- 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
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
class ConfigApplyError extends Error {
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
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
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
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
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
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 =
|
|
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 (!
|
|
2742
|
+
if (!existsSync4(absDir)) {
|
|
2722
2743
|
result.skipped.push(known.rulesDir);
|
|
2723
2744
|
continue;
|
|
2724
2745
|
}
|
|
2725
|
-
const mdFiles =
|
|
2746
|
+
const mdFiles = readdirSync2(absDir).filter((f) => f.endsWith(".md"));
|
|
2726
2747
|
for (const f of mdFiles) {
|
|
2727
|
-
const abs2 =
|
|
2748
|
+
const abs2 = join3(absDir, f);
|
|
2728
2749
|
const targetPath = abs2.replace(home, "~");
|
|
2729
|
-
const raw =
|
|
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 (!
|
|
2770
|
+
if (!existsSync4(abs)) {
|
|
2750
2771
|
result.skipped.push(known.path);
|
|
2751
2772
|
continue;
|
|
2752
2773
|
}
|
|
2753
2774
|
try {
|
|
2754
|
-
const rawContent =
|
|
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 (!
|
|
2832
|
+
if (!existsSync4(path))
|
|
2812
2833
|
return `(file not found on disk: ${path})`;
|
|
2813
|
-
const diskContent =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
2889
|
-
|
|
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 =
|
|
2896
|
-
const contentsDir =
|
|
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(
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
2928
|
-
|
|
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 =
|
|
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 =
|
|
2948
|
-
if (!
|
|
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(
|
|
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 =
|
|
2955
|
-
const content =
|
|
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 (
|
|
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;
|
|
@@ -3008,7 +3155,8 @@ function extractTemplateVars(content) {
|
|
|
3008
3155
|
}
|
|
3009
3156
|
|
|
3010
3157
|
// src/cli/index.tsx
|
|
3011
|
-
|
|
3158
|
+
import { createRequire } from "module";
|
|
3159
|
+
var pkg = createRequire(import.meta.url)("../../package.json");
|
|
3012
3160
|
function fmtConfig(c, format) {
|
|
3013
3161
|
if (format === "json")
|
|
3014
3162
|
return JSON.stringify(c, null, 2);
|
|
@@ -3069,14 +3217,14 @@ program.command("show <id>").description("Show a config's content and metadata")
|
|
|
3069
3217
|
});
|
|
3070
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) => {
|
|
3071
3219
|
const abs = resolve5(filePath);
|
|
3072
|
-
if (!
|
|
3220
|
+
if (!existsSync7(abs)) {
|
|
3073
3221
|
console.error(chalk.red(`File not found: ${abs}`));
|
|
3074
3222
|
process.exit(1);
|
|
3075
3223
|
}
|
|
3076
|
-
const rawContent =
|
|
3224
|
+
const rawContent = readFileSync5(abs, "utf-8");
|
|
3077
3225
|
const fmt = detectFormat(abs);
|
|
3078
3226
|
const { content, redacted, isTemplate } = redactContent(rawContent, fmt);
|
|
3079
|
-
const targetPath = abs.startsWith(
|
|
3227
|
+
const targetPath = abs.startsWith(homedir4()) ? abs.replace(homedir4(), "~") : abs;
|
|
3080
3228
|
const name = opts.name || filePath.split("/").pop();
|
|
3081
3229
|
const config = createConfig({
|
|
3082
3230
|
name,
|
|
@@ -3162,7 +3310,7 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
|
|
|
3162
3310
|
}
|
|
3163
3311
|
});
|
|
3164
3312
|
program.command("whoami").description("Show setup summary").action(async () => {
|
|
3165
|
-
const dbPath = process.env["CONFIGS_DB_PATH"] ||
|
|
3313
|
+
const dbPath = process.env["CONFIGS_DB_PATH"] || join6(homedir4(), ".configs", "configs.db");
|
|
3166
3314
|
const stats = getConfigStats();
|
|
3167
3315
|
console.log(chalk.bold("@hasna/configs") + chalk.dim(" v" + pkg.version));
|
|
3168
3316
|
console.log(chalk.cyan("DB:") + " " + dbPath);
|
|
@@ -3320,25 +3468,51 @@ templateCmd.command("vars <id>").description("Show template variables").action(a
|
|
|
3320
3468
|
process.exit(1);
|
|
3321
3469
|
}
|
|
3322
3470
|
});
|
|
3323
|
-
program.command("scan [id]").description("Scan configs for secrets.
|
|
3324
|
-
|
|
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
|
+
}
|
|
3325
3494
|
let total = 0;
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
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
|
+
}
|
|
3338
3512
|
}
|
|
3339
3513
|
}
|
|
3340
3514
|
if (total === 0) {
|
|
3341
|
-
console.log(chalk.green("\u2713") +
|
|
3515
|
+
console.log(chalk.green("\u2713") + ` No secrets detected${opts.all ? "" : " (known configs). Use --all to scan entire DB"}.`);
|
|
3342
3516
|
} else if (!opts.fix) {
|
|
3343
3517
|
console.log(chalk.yellow(`
|
|
3344
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
|
+
"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",
|