@hasna/configs 0.2.9 → 0.2.11
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 +50 -1
- package/dist/mcp/index.js +34 -2
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2364,6 +2364,11 @@ function updateConfig(idOrSlug, input, db) {
|
|
|
2364
2364
|
d.run(`UPDATE configs SET ${updates.join(", ")} WHERE id = ?`, params);
|
|
2365
2365
|
return getConfigById(existing.id, d);
|
|
2366
2366
|
}
|
|
2367
|
+
function deleteConfig(idOrSlug, db) {
|
|
2368
|
+
const d = db || getDatabase();
|
|
2369
|
+
const existing = getConfig(idOrSlug, d);
|
|
2370
|
+
d.run("DELETE FROM configs WHERE id = ?", [existing.id]);
|
|
2371
|
+
}
|
|
2367
2372
|
function getConfigStats(db) {
|
|
2368
2373
|
const d = db || getDatabase();
|
|
2369
2374
|
const rows = d.query("SELECT category, COUNT(*) as count FROM configs GROUP BY category").all();
|
|
@@ -4083,15 +4088,59 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
4083
4088
|
mtimes.set(abs, newMtime);
|
|
4084
4089
|
}
|
|
4085
4090
|
}
|
|
4091
|
+
const { readdirSync: rd } = await import("fs");
|
|
4092
|
+
for (const k of KNOWN_CONFIGS) {
|
|
4093
|
+
if (k.rulesDir) {
|
|
4094
|
+
const absDir = expandPath2(k.rulesDir);
|
|
4095
|
+
if (!existsSync7(absDir))
|
|
4096
|
+
continue;
|
|
4097
|
+
for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
4098
|
+
const abs = join6(absDir, f);
|
|
4099
|
+
if (!mtimes.has(abs)) {
|
|
4100
|
+
mtimes.set(abs, st(abs).mtimeMs);
|
|
4101
|
+
changed++;
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
} else {
|
|
4105
|
+
const abs = expandPath2(k.path);
|
|
4106
|
+
if (existsSync7(abs) && !mtimes.has(abs)) {
|
|
4107
|
+
mtimes.set(abs, st(abs).mtimeMs);
|
|
4108
|
+
changed++;
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4086
4112
|
if (changed > 0) {
|
|
4087
4113
|
const result = await syncKnown({});
|
|
4088
4114
|
const ts = new Date().toLocaleTimeString();
|
|
4089
|
-
console.log(`${chalk.dim(ts)} ${chalk.green("\u2713")} ${changed} file(s) changed \u2192 synced +${result.added} updated:${result.updated}`);
|
|
4115
|
+
console.log(`${chalk.dim(ts)} ${chalk.green("\u2713")} ${changed} file(s) changed/new \u2192 synced +${result.added} updated:${result.updated}`);
|
|
4090
4116
|
}
|
|
4091
4117
|
};
|
|
4092
4118
|
setInterval(tick, interval);
|
|
4093
4119
|
await new Promise(() => {});
|
|
4094
4120
|
});
|
|
4121
|
+
program.command("clean").description("Remove configs from DB whose target files no longer exist on disk").option("--dry-run", "show what would be removed").action(async (opts) => {
|
|
4122
|
+
const configs = listConfigs({ kind: "file" });
|
|
4123
|
+
let removed = 0;
|
|
4124
|
+
for (const c of configs) {
|
|
4125
|
+
if (!c.target_path)
|
|
4126
|
+
continue;
|
|
4127
|
+
const abs = expandPath(c.target_path);
|
|
4128
|
+
if (!existsSync7(abs)) {
|
|
4129
|
+
if (opts.dryRun) {
|
|
4130
|
+
console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${c.target_path})`)}`);
|
|
4131
|
+
} else {
|
|
4132
|
+
deleteConfig(c.id);
|
|
4133
|
+
console.log(chalk.red(" removed:") + ` ${c.slug} ${chalk.dim(`(${c.target_path})`)}`);
|
|
4134
|
+
}
|
|
4135
|
+
removed++;
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
if (removed === 0)
|
|
4139
|
+
console.log(chalk.green("\u2713") + " All stored configs still exist on disk.");
|
|
4140
|
+
else
|
|
4141
|
+
console.log(chalk.dim(`
|
|
4142
|
+
${removed} orphaned config(s) ${opts.dryRun ? "found" : "removed"}`));
|
|
4143
|
+
});
|
|
4095
4144
|
program.command("bootstrap").description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs").option("--dry-run", "show what would be installed without doing it").option("--skip-mcp", "skip MCP server registration").action(async (opts) => {
|
|
4096
4145
|
const packages = [
|
|
4097
4146
|
{ name: "@hasna/todos", bin: "todos", mcp: "todos-mcp" },
|
package/dist/mcp/index.js
CHANGED
|
@@ -395,6 +395,12 @@ var init_apply = __esm(() => {
|
|
|
395
395
|
});
|
|
396
396
|
|
|
397
397
|
// src/lib/redact.ts
|
|
398
|
+
var exports_redact = {};
|
|
399
|
+
__export(exports_redact, {
|
|
400
|
+
scanSecrets: () => scanSecrets,
|
|
401
|
+
redactContent: () => redactContent,
|
|
402
|
+
hasSecrets: () => hasSecrets
|
|
403
|
+
});
|
|
398
404
|
function redactShell(content) {
|
|
399
405
|
const redacted = [];
|
|
400
406
|
const lines = content.split(`
|
|
@@ -554,6 +560,13 @@ function redactContent(content, format) {
|
|
|
554
560
|
return redactGeneric(content);
|
|
555
561
|
}
|
|
556
562
|
}
|
|
563
|
+
function scanSecrets(content, format) {
|
|
564
|
+
const r = redactContent(content, format);
|
|
565
|
+
return r.redacted;
|
|
566
|
+
}
|
|
567
|
+
function hasSecrets(content, format) {
|
|
568
|
+
return scanSecrets(content, format).length > 0;
|
|
569
|
+
}
|
|
557
570
|
var SECRET_KEY_PATTERN, VALUE_PATTERNS, MIN_SECRET_VALUE_LEN = 8;
|
|
558
571
|
var init_redact = __esm(() => {
|
|
559
572
|
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;
|
|
@@ -1000,14 +1013,15 @@ var TOOL_DOCS = {
|
|
|
1000
1013
|
list_profiles: "List all profiles. Returns array of profile objects.",
|
|
1001
1014
|
apply_profile: "Apply all configs in a profile to disk. Params: id_or_slug, dry_run?. Returns array of apply results.",
|
|
1002
1015
|
get_snapshot: "Get snapshot(s) for a config. Params: config_id_or_slug, version?. Returns latest snapshot or specific version.",
|
|
1003
|
-
get_status: "Single-call orientation. Returns: total configs, counts by category,
|
|
1016
|
+
get_status: "Single-call orientation. Returns: total configs, counts by category, templates, DB path.",
|
|
1017
|
+
scan_secrets: "Scan configs for unredacted secrets. Params: id_or_slug? (omit for all known), fix? (true to redact in-place). Returns findings.",
|
|
1004
1018
|
sync_known: "Sync all known config files from disk into DB. Params: agent?, category?. Replaces sync_directory for standard use.",
|
|
1005
1019
|
search_tools: "Search tool descriptions. Params: query. Returns matching tool names and descriptions.",
|
|
1006
1020
|
describe_tools: "Get full descriptions for tools. Params: names? (array). Returns tool docs."
|
|
1007
1021
|
};
|
|
1008
1022
|
var PROFILES = {
|
|
1009
1023
|
minimal: ["get_status", "get_config", "sync_known"],
|
|
1010
|
-
standard: ["list_configs", "get_config", "create_config", "update_config", "apply_config", "sync_known", "get_status", "list_profiles", "apply_profile", "search_tools", "describe_tools"],
|
|
1024
|
+
standard: ["list_configs", "get_config", "create_config", "update_config", "apply_config", "sync_known", "get_status", "scan_secrets", "list_profiles", "apply_profile", "search_tools", "describe_tools"],
|
|
1011
1025
|
full: []
|
|
1012
1026
|
};
|
|
1013
1027
|
var activeProfile = process.env["CONFIGS_PROFILE"] || "full";
|
|
@@ -1024,6 +1038,7 @@ var ALL_LEAN_TOOLS = [
|
|
|
1024
1038
|
{ name: "get_snapshot", inputSchema: { type: "object", properties: { config_id_or_slug: { type: "string" }, version: { type: "number" } }, required: ["config_id_or_slug"] } },
|
|
1025
1039
|
{ name: "get_status", inputSchema: { type: "object", properties: {} } },
|
|
1026
1040
|
{ name: "sync_known", inputSchema: { type: "object", properties: { agent: { type: "string" }, category: { type: "string" } } } },
|
|
1041
|
+
{ name: "scan_secrets", inputSchema: { type: "object", properties: { id_or_slug: { type: "string" }, fix: { type: "boolean" } } } },
|
|
1027
1042
|
{ name: "search_tools", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
|
|
1028
1043
|
{ name: "describe_tools", inputSchema: { type: "object", properties: { names: { type: "array", items: { type: "string" } } } } }
|
|
1029
1044
|
];
|
|
@@ -1131,6 +1146,23 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1131
1146
|
});
|
|
1132
1147
|
return ok(result);
|
|
1133
1148
|
}
|
|
1149
|
+
case "scan_secrets": {
|
|
1150
|
+
const { scanSecrets: scanSecrets2, redactContent: redactContent2 } = await Promise.resolve().then(() => (init_redact(), exports_redact));
|
|
1151
|
+
const configs = args["id_or_slug"] ? [getConfig(args["id_or_slug"])] : listConfigs({ kind: "file" });
|
|
1152
|
+
const findings = [];
|
|
1153
|
+
for (const c of configs) {
|
|
1154
|
+
const fmt = c.format;
|
|
1155
|
+
const secrets = scanSecrets2(c.content, fmt);
|
|
1156
|
+
if (secrets.length > 0) {
|
|
1157
|
+
findings.push({ slug: c.slug, secrets: secrets.length, vars: secrets.map((s) => s.varName) });
|
|
1158
|
+
if (args["fix"]) {
|
|
1159
|
+
const { content, isTemplate } = redactContent2(c.content, fmt);
|
|
1160
|
+
updateConfig(c.id, { content, is_template: isTemplate });
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return ok({ clean: findings.length === 0, findings, fixed: !!args["fix"] });
|
|
1165
|
+
}
|
|
1134
1166
|
case "search_tools": {
|
|
1135
1167
|
const query = (args["query"] || "").toLowerCase();
|
|
1136
1168
|
const matches = Object.entries(TOOL_DOCS).filter(([k, v]) => k.includes(query) || v.toLowerCase().includes(query)).map(([name2, description]) => ({ name: name2, description }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/configs",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
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",
|