@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.
Files changed (2) hide show
  1. package/dist/mcp/index.js +113 -3
  2. 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, drifted count, unredacted secrets, templates, DB path.",
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.10",
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",