@hasna/configs 0.2.10 → 0.2.12
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/mcp/index.js +113 -3
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -13,7 +13,7 @@ var __export = (target, all) => {
|
|
|
13
13
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
14
14
|
|
|
15
15
|
// src/types/index.ts
|
|
16
|
-
var ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError;
|
|
16
|
+
var ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError, TemplateRenderError;
|
|
17
17
|
var init_types = __esm(() => {
|
|
18
18
|
ConfigNotFoundError = class ConfigNotFoundError extends Error {
|
|
19
19
|
constructor(id) {
|
|
@@ -33,6 +33,12 @@ var init_types = __esm(() => {
|
|
|
33
33
|
this.name = "ConfigApplyError";
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
|
+
TemplateRenderError = class TemplateRenderError extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "TemplateRenderError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
36
42
|
});
|
|
37
43
|
|
|
38
44
|
// src/db/database.ts
|
|
@@ -395,6 +401,12 @@ var init_apply = __esm(() => {
|
|
|
395
401
|
});
|
|
396
402
|
|
|
397
403
|
// src/lib/redact.ts
|
|
404
|
+
var exports_redact = {};
|
|
405
|
+
__export(exports_redact, {
|
|
406
|
+
scanSecrets: () => scanSecrets,
|
|
407
|
+
redactContent: () => redactContent,
|
|
408
|
+
hasSecrets: () => hasSecrets
|
|
409
|
+
});
|
|
398
410
|
function redactShell(content) {
|
|
399
411
|
const redacted = [];
|
|
400
412
|
const lines = content.split(`
|
|
@@ -554,6 +566,13 @@ function redactContent(content, format) {
|
|
|
554
566
|
return redactGeneric(content);
|
|
555
567
|
}
|
|
556
568
|
}
|
|
569
|
+
function scanSecrets(content, format) {
|
|
570
|
+
const r = redactContent(content, format);
|
|
571
|
+
return r.redacted;
|
|
572
|
+
}
|
|
573
|
+
function hasSecrets(content, format) {
|
|
574
|
+
return scanSecrets(content, format).length > 0;
|
|
575
|
+
}
|
|
557
576
|
var SECRET_KEY_PATTERN, VALUE_PATTERNS, MIN_SECRET_VALUE_LEN = 8;
|
|
558
577
|
var init_redact = __esm(() => {
|
|
559
578
|
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;
|
|
@@ -951,6 +970,61 @@ var init_sync = __esm(() => {
|
|
|
951
970
|
];
|
|
952
971
|
});
|
|
953
972
|
|
|
973
|
+
// src/lib/template.ts
|
|
974
|
+
var exports_template = {};
|
|
975
|
+
__export(exports_template, {
|
|
976
|
+
renderTemplate: () => renderTemplate,
|
|
977
|
+
parseTemplateVars: () => parseTemplateVars,
|
|
978
|
+
isTemplate: () => isTemplate,
|
|
979
|
+
extractTemplateVars: () => extractTemplateVars
|
|
980
|
+
});
|
|
981
|
+
function parseTemplateVars(content) {
|
|
982
|
+
const names = new Set;
|
|
983
|
+
let match;
|
|
984
|
+
VAR_PATTERN.lastIndex = 0;
|
|
985
|
+
while ((match = VAR_PATTERN.exec(content)) !== null) {
|
|
986
|
+
names.add(match[1]);
|
|
987
|
+
}
|
|
988
|
+
return Array.from(names);
|
|
989
|
+
}
|
|
990
|
+
function extractTemplateVars(content) {
|
|
991
|
+
const vars = new Map;
|
|
992
|
+
let match;
|
|
993
|
+
VAR_PATTERN.lastIndex = 0;
|
|
994
|
+
while ((match = VAR_PATTERN.exec(content)) !== null) {
|
|
995
|
+
const name = match[1];
|
|
996
|
+
const description = match[2] ?? null;
|
|
997
|
+
if (!vars.has(name)) {
|
|
998
|
+
vars.set(name, { name, description, required: true });
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return Array.from(vars.values());
|
|
1002
|
+
}
|
|
1003
|
+
function renderTemplate(content, vars) {
|
|
1004
|
+
const missing = [];
|
|
1005
|
+
VAR_PATTERN.lastIndex = 0;
|
|
1006
|
+
let match;
|
|
1007
|
+
while ((match = VAR_PATTERN.exec(content)) !== null) {
|
|
1008
|
+
const name = match[1];
|
|
1009
|
+
if (!(name in vars))
|
|
1010
|
+
missing.push(name);
|
|
1011
|
+
}
|
|
1012
|
+
if (missing.length > 0) {
|
|
1013
|
+
throw new TemplateRenderError(`Missing required template variables: ${missing.join(", ")}`);
|
|
1014
|
+
}
|
|
1015
|
+
VAR_PATTERN.lastIndex = 0;
|
|
1016
|
+
return content.replace(VAR_PATTERN, (_match, name) => vars[name] ?? "");
|
|
1017
|
+
}
|
|
1018
|
+
function isTemplate(content) {
|
|
1019
|
+
VAR_PATTERN.lastIndex = 0;
|
|
1020
|
+
return VAR_PATTERN.test(content);
|
|
1021
|
+
}
|
|
1022
|
+
var VAR_PATTERN;
|
|
1023
|
+
var init_template = __esm(() => {
|
|
1024
|
+
init_types();
|
|
1025
|
+
VAR_PATTERN = /\{\{([A-Z0-9_]+)(?::([^}]*))?\}\}/g;
|
|
1026
|
+
});
|
|
1027
|
+
|
|
954
1028
|
// src/mcp/index.ts
|
|
955
1029
|
init_configs();
|
|
956
1030
|
init_apply();
|
|
@@ -1000,14 +1074,16 @@ var TOOL_DOCS = {
|
|
|
1000
1074
|
list_profiles: "List all profiles. Returns array of profile objects.",
|
|
1001
1075
|
apply_profile: "Apply all configs in a profile to disk. Params: id_or_slug, dry_run?. Returns array of apply results.",
|
|
1002
1076
|
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,
|
|
1077
|
+
get_status: "Single-call orientation. Returns: total configs, counts by category, templates, DB path.",
|
|
1078
|
+
render_template: "Render a template config with variable substitution. Params: id_or_slug, vars? (object of KEY:VALUE), use_env? (fill from env vars). Returns rendered content.",
|
|
1079
|
+
scan_secrets: "Scan configs for unredacted secrets. Params: id_or_slug? (omit for all known), fix? (true to redact in-place). Returns findings.",
|
|
1004
1080
|
sync_known: "Sync all known config files from disk into DB. Params: agent?, category?. Replaces sync_directory for standard use.",
|
|
1005
1081
|
search_tools: "Search tool descriptions. Params: query. Returns matching tool names and descriptions.",
|
|
1006
1082
|
describe_tools: "Get full descriptions for tools. Params: names? (array). Returns tool docs."
|
|
1007
1083
|
};
|
|
1008
1084
|
var PROFILES = {
|
|
1009
1085
|
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"],
|
|
1086
|
+
standard: ["list_configs", "get_config", "create_config", "update_config", "apply_config", "sync_known", "get_status", "render_template", "scan_secrets", "list_profiles", "apply_profile", "search_tools", "describe_tools"],
|
|
1011
1087
|
full: []
|
|
1012
1088
|
};
|
|
1013
1089
|
var activeProfile = process.env["CONFIGS_PROFILE"] || "full";
|
|
@@ -1024,6 +1100,8 @@ var ALL_LEAN_TOOLS = [
|
|
|
1024
1100
|
{ name: "get_snapshot", inputSchema: { type: "object", properties: { config_id_or_slug: { type: "string" }, version: { type: "number" } }, required: ["config_id_or_slug"] } },
|
|
1025
1101
|
{ name: "get_status", inputSchema: { type: "object", properties: {} } },
|
|
1026
1102
|
{ name: "sync_known", inputSchema: { type: "object", properties: { agent: { type: "string" }, category: { type: "string" } } } },
|
|
1103
|
+
{ name: "render_template", inputSchema: { type: "object", properties: { id_or_slug: { type: "string" }, vars: { type: "object" }, use_env: { type: "boolean" } }, required: ["id_or_slug"] } },
|
|
1104
|
+
{ name: "scan_secrets", inputSchema: { type: "object", properties: { id_or_slug: { type: "string" }, fix: { type: "boolean" } } } },
|
|
1027
1105
|
{ name: "search_tools", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
|
|
1028
1106
|
{ name: "describe_tools", inputSchema: { type: "object", properties: { names: { type: "array", items: { type: "string" } } } } }
|
|
1029
1107
|
];
|
|
@@ -1131,6 +1209,38 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1131
1209
|
});
|
|
1132
1210
|
return ok(result);
|
|
1133
1211
|
}
|
|
1212
|
+
case "render_template": {
|
|
1213
|
+
const { renderTemplate: renderTemplate2 } = await Promise.resolve().then(() => (init_template(), exports_template));
|
|
1214
|
+
const config = getConfig(args["id_or_slug"]);
|
|
1215
|
+
const vars = args["vars"] || {};
|
|
1216
|
+
if (args["use_env"]) {
|
|
1217
|
+
const { extractTemplateVars: extractTemplateVars2 } = await Promise.resolve().then(() => (init_template(), exports_template));
|
|
1218
|
+
for (const v of extractTemplateVars2(config.content)) {
|
|
1219
|
+
if (!(v.name in vars) && process.env[v.name]) {
|
|
1220
|
+
vars[v.name] = process.env[v.name];
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const rendered = renderTemplate2(config.content, vars);
|
|
1225
|
+
return ok({ rendered, config_id: config.id, slug: config.slug });
|
|
1226
|
+
}
|
|
1227
|
+
case "scan_secrets": {
|
|
1228
|
+
const { scanSecrets: scanSecrets2, redactContent: redactContent2 } = await Promise.resolve().then(() => (init_redact(), exports_redact));
|
|
1229
|
+
const configs = args["id_or_slug"] ? [getConfig(args["id_or_slug"])] : listConfigs({ kind: "file" });
|
|
1230
|
+
const findings = [];
|
|
1231
|
+
for (const c of configs) {
|
|
1232
|
+
const fmt = c.format;
|
|
1233
|
+
const secrets = scanSecrets2(c.content, fmt);
|
|
1234
|
+
if (secrets.length > 0) {
|
|
1235
|
+
findings.push({ slug: c.slug, secrets: secrets.length, vars: secrets.map((s) => s.varName) });
|
|
1236
|
+
if (args["fix"]) {
|
|
1237
|
+
const { content, isTemplate: isTemplate2 } = redactContent2(c.content, fmt);
|
|
1238
|
+
updateConfig(c.id, { content, is_template: isTemplate2 });
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return ok({ clean: findings.length === 0, findings, fixed: !!args["fix"] });
|
|
1243
|
+
}
|
|
1134
1244
|
case "search_tools": {
|
|
1135
1245
|
const query = (args["query"] || "").toLowerCase();
|
|
1136
1246
|
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.12",
|
|
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",
|