@hasna/configs 0.2.1 → 0.2.3

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/cli/index.js +164 -22
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -2064,7 +2064,7 @@ var require_commander = __commonJS((exports) => {
2064
2064
  });
2065
2065
 
2066
2066
  // src/types/index.ts
2067
- var ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError;
2067
+ var ConfigNotFoundError, ProfileNotFoundError, ConfigApplyError, TemplateRenderError;
2068
2068
  var init_types = __esm(() => {
2069
2069
  ConfigNotFoundError = class ConfigNotFoundError extends Error {
2070
2070
  constructor(id) {
@@ -2084,6 +2084,12 @@ var init_types = __esm(() => {
2084
2084
  this.name = "ConfigApplyError";
2085
2085
  }
2086
2086
  };
2087
+ TemplateRenderError = class TemplateRenderError extends Error {
2088
+ constructor(message) {
2089
+ super(message);
2090
+ this.name = "TemplateRenderError";
2091
+ }
2092
+ };
2087
2093
  });
2088
2094
 
2089
2095
  // src/db/database.ts
@@ -3015,6 +3021,61 @@ var init_sync = __esm(() => {
3015
3021
  ];
3016
3022
  });
3017
3023
 
3024
+ // src/lib/template.ts
3025
+ var exports_template = {};
3026
+ __export(exports_template, {
3027
+ renderTemplate: () => renderTemplate,
3028
+ parseTemplateVars: () => parseTemplateVars,
3029
+ isTemplate: () => isTemplate,
3030
+ extractTemplateVars: () => extractTemplateVars
3031
+ });
3032
+ function parseTemplateVars(content) {
3033
+ const names = new Set;
3034
+ let match;
3035
+ VAR_PATTERN.lastIndex = 0;
3036
+ while ((match = VAR_PATTERN.exec(content)) !== null) {
3037
+ names.add(match[1]);
3038
+ }
3039
+ return Array.from(names);
3040
+ }
3041
+ function extractTemplateVars(content) {
3042
+ const vars = new Map;
3043
+ let match;
3044
+ VAR_PATTERN.lastIndex = 0;
3045
+ while ((match = VAR_PATTERN.exec(content)) !== null) {
3046
+ const name = match[1];
3047
+ const description = match[2] ?? null;
3048
+ if (!vars.has(name)) {
3049
+ vars.set(name, { name, description, required: true });
3050
+ }
3051
+ }
3052
+ return Array.from(vars.values());
3053
+ }
3054
+ function renderTemplate(content, vars) {
3055
+ const missing = [];
3056
+ VAR_PATTERN.lastIndex = 0;
3057
+ let match;
3058
+ while ((match = VAR_PATTERN.exec(content)) !== null) {
3059
+ const name = match[1];
3060
+ if (!(name in vars))
3061
+ missing.push(name);
3062
+ }
3063
+ if (missing.length > 0) {
3064
+ throw new TemplateRenderError(`Missing required template variables: ${missing.join(", ")}`);
3065
+ }
3066
+ VAR_PATTERN.lastIndex = 0;
3067
+ return content.replace(VAR_PATTERN, (_match, name) => vars[name] ?? "");
3068
+ }
3069
+ function isTemplate(content) {
3070
+ VAR_PATTERN.lastIndex = 0;
3071
+ return VAR_PATTERN.test(content);
3072
+ }
3073
+ var VAR_PATTERN;
3074
+ var init_template = __esm(() => {
3075
+ init_types();
3076
+ VAR_PATTERN = /\{\{([A-Z0-9_]+)(?::([^}]*))?\}\}/g;
3077
+ });
3078
+
3018
3079
  // node_modules/commander/esm.mjs
3019
3080
  var import__ = __toESM(require_commander(), 1);
3020
3081
  var {
@@ -3220,24 +3281,8 @@ async function importConfigs(bundlePath, opts = {}) {
3220
3281
  }
3221
3282
  }
3222
3283
 
3223
- // src/lib/template.ts
3224
- init_types();
3225
- var VAR_PATTERN = /\{\{([A-Z0-9_]+)(?::([^}]*))?\}\}/g;
3226
- function extractTemplateVars(content) {
3227
- const vars = new Map;
3228
- let match;
3229
- VAR_PATTERN.lastIndex = 0;
3230
- while ((match = VAR_PATTERN.exec(content)) !== null) {
3231
- const name = match[1];
3232
- const description = match[2] ?? null;
3233
- if (!vars.has(name)) {
3234
- vars.set(name, { name, description, required: true });
3235
- }
3236
- }
3237
- return Array.from(vars.values());
3238
- }
3239
-
3240
3284
  // src/cli/index.tsx
3285
+ init_template();
3241
3286
  import { createRequire } from "module";
3242
3287
  var pkg = createRequire(import.meta.url)("../../package.json");
3243
3288
  function fmtConfig(c, format) {
@@ -3307,7 +3352,7 @@ program.command("add <path>").description("Ingest a file into the config DB").op
3307
3352
  }
3308
3353
  const rawContent = readFileSync5(abs, "utf-8");
3309
3354
  const fmt = detectFormat(abs);
3310
- const { content, redacted, isTemplate } = redactContent(rawContent, fmt);
3355
+ const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
3311
3356
  const targetPath = abs.startsWith(homedir4()) ? abs.replace(homedir4(), "~") : abs;
3312
3357
  const name = opts.name || filePath.split("/").pop();
3313
3358
  const config = createConfig({
@@ -3318,7 +3363,7 @@ program.command("add <path>").description("Ingest a file into the config DB").op
3318
3363
  target_path: opts.kind === "reference" ? null : targetPath,
3319
3364
  format: fmt,
3320
3365
  content,
3321
- is_template: (opts.template ?? false) || isTemplate
3366
+ is_template: (opts.template ?? false) || isTemplate2
3322
3367
  });
3323
3368
  console.log(chalk.green("\u2713") + ` Added: ${chalk.bold(config.name)} ${chalk.dim(`(${config.slug})`)}`);
3324
3369
  if (redacted.length > 0) {
@@ -3583,6 +3628,54 @@ templateCmd.command("vars <id>").description("Show template variables").action(a
3583
3628
  process.exit(1);
3584
3629
  }
3585
3630
  });
3631
+ templateCmd.command("render <id>").description("Render a template config with variables and optionally apply to disk").option("--var <vars...>", "set variables as KEY=VALUE pairs").option("--env", "use environment variables to fill template vars").option("--apply", "write rendered output to target_path").option("--dry-run", "preview rendered output without writing").action(async (id, opts) => {
3632
+ try {
3633
+ const { renderTemplate: renderTemplate2 } = await Promise.resolve().then(() => (init_template(), exports_template));
3634
+ const c = getConfig(id);
3635
+ const vars = {};
3636
+ if (opts.var) {
3637
+ for (const kv of opts.var) {
3638
+ const eq = kv.indexOf("=");
3639
+ if (eq === -1) {
3640
+ console.error(chalk.red(`Invalid --var: ${kv} (expected KEY=VALUE)`));
3641
+ process.exit(1);
3642
+ }
3643
+ vars[kv.slice(0, eq)] = kv.slice(eq + 1);
3644
+ }
3645
+ }
3646
+ if (opts.env) {
3647
+ const { extractTemplateVars: extractTemplateVars2 } = await Promise.resolve().then(() => (init_template(), exports_template));
3648
+ for (const v of extractTemplateVars2(c.content)) {
3649
+ if (!(v.name in vars) && process.env[v.name]) {
3650
+ vars[v.name] = process.env[v.name];
3651
+ }
3652
+ }
3653
+ }
3654
+ const rendered = renderTemplate2(c.content, vars);
3655
+ if (opts.apply || opts.dryRun) {
3656
+ if (!c.target_path) {
3657
+ console.error(chalk.red("No target_path \u2014 cannot apply reference configs"));
3658
+ process.exit(1);
3659
+ }
3660
+ if (opts.dryRun) {
3661
+ console.log(chalk.yellow("[dry-run]") + ` Would write to ${expandPath(c.target_path)}`);
3662
+ console.log(rendered);
3663
+ } else {
3664
+ const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync5 } = await import("fs");
3665
+ const { dirname: dirname3 } = await import("path");
3666
+ const path = expandPath(c.target_path);
3667
+ mkdirSync5(dirname3(path), { recursive: true });
3668
+ writeFileSync3(path, rendered, "utf-8");
3669
+ console.log(chalk.green("\u2713") + ` Rendered and applied to ${path}`);
3670
+ }
3671
+ } else {
3672
+ console.log(rendered);
3673
+ }
3674
+ } catch (e) {
3675
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
3676
+ process.exit(1);
3677
+ }
3678
+ });
3586
3679
  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) => {
3587
3680
  let configs;
3588
3681
  if (id) {
@@ -3620,8 +3713,8 @@ program.command("scan [id]").description("Scan configs for secrets. Defaults to
3620
3713
  for (const s of secrets)
3621
3714
  console.log(` line ${s.line}: ${chalk.red(s.varName)} \u2014 ${s.reason}`);
3622
3715
  if (opts.fix) {
3623
- const { content, isTemplate } = redactContent(c.content, fmt);
3624
- updateConfig(c.id, { content, is_template: isTemplate });
3716
+ const { content, isTemplate: isTemplate2 } = redactContent(c.content, fmt);
3717
+ updateConfig(c.id, { content, is_template: isTemplate2 });
3625
3718
  console.log(chalk.green(" \u2713 Redacted."));
3626
3719
  }
3627
3720
  }
@@ -3888,6 +3981,47 @@ _configs_completions() {
3888
3981
  complete -F _configs_completions configs`);
3889
3982
  }
3890
3983
  });
3984
+ program.command("compare <a> <b>").description("Diff two stored configs against each other").action(async (a, b) => {
3985
+ try {
3986
+ const configA = getConfig(a);
3987
+ const configB = getConfig(b);
3988
+ console.log(chalk.bold(`${configA.slug}`) + chalk.dim(` (${configA.category}/${configA.agent})`));
3989
+ console.log(chalk.bold(`${configB.slug}`) + chalk.dim(` (${configB.category}/${configB.agent})`));
3990
+ console.log();
3991
+ const linesA = configA.content.split(`
3992
+ `);
3993
+ const linesB = configB.content.split(`
3994
+ `);
3995
+ const maxLen = Math.max(linesA.length, linesB.length);
3996
+ const lines = [`--- ${configA.slug}`, `+++ ${configB.slug}`];
3997
+ let diffs = 0;
3998
+ for (let i = 0;i < maxLen; i++) {
3999
+ const la = linesA[i];
4000
+ const lb = linesB[i];
4001
+ if (la === lb) {
4002
+ if (la !== undefined)
4003
+ lines.push(` ${la}`);
4004
+ } else {
4005
+ diffs++;
4006
+ if (la !== undefined)
4007
+ lines.push(chalk.red(`-${la}`));
4008
+ if (lb !== undefined)
4009
+ lines.push(chalk.green(`+${lb}`));
4010
+ }
4011
+ }
4012
+ if (diffs === 0) {
4013
+ console.log(chalk.green("\u2713") + " Identical content");
4014
+ } else {
4015
+ console.log(lines.join(`
4016
+ `));
4017
+ console.log(chalk.dim(`
4018
+ ${diffs} difference(s)`));
4019
+ }
4020
+ } catch (e) {
4021
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
4022
+ process.exit(1);
4023
+ }
4024
+ });
3891
4025
  program.command("watch").description("Watch known config files for changes and auto-sync to DB").option("-i, --interval <ms>", "poll interval in milliseconds", "3000").action(async (opts) => {
3892
4026
  const interval = Number(opts.interval);
3893
4027
  const { statSync: st } = await import("fs");
@@ -3933,5 +4067,13 @@ program.command("watch").description("Watch known config files for changes and a
3933
4067
  setInterval(tick, interval);
3934
4068
  await new Promise(() => {});
3935
4069
  });
4070
+ program.command("pull").description("Alias for sync (read from disk into DB)").option("-a, --agent <agent>", "only sync this agent").option("--dry-run", "preview without writing").action(async (opts) => {
4071
+ const result = await syncKnown({ dryRun: opts.dryRun, agent: opts.agent });
4072
+ console.log(chalk.green("\u2713") + ` Pulled: +${result.added} updated:${result.updated} unchanged:${result.unchanged}`);
4073
+ });
4074
+ program.command("push").description("Alias for sync --to-disk (write DB configs to disk)").option("-a, --agent <agent>", "only push this agent").option("--dry-run", "preview without writing").action(async (opts) => {
4075
+ const result = await syncToDisk({ dryRun: opts.dryRun, agent: opts.agent });
4076
+ console.log(chalk.green("\u2713") + ` Pushed: updated:${result.updated} unchanged:${result.unchanged} skipped:${result.skipped.length}`);
4077
+ });
3936
4078
  program.version(pkg.version).name("configs");
3937
4079
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/configs",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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",