@hivecommons/promptargs 0.6.0 → 0.8.0

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/ui.html CHANGED
@@ -649,9 +649,19 @@
649
649
  </div>
650
650
  </div>
651
651
 
652
- <script>
652
+ <script type="importmap">
653
+ { "imports": { "mustache": "./mustache.mjs" } }
654
+ </script>
655
+ <script type="module">
656
+ // Import the canonical {{var}} grammar and iteration logic from the same
657
+ // compiled modules the CLI runs, instead of hand-reimplementing them here
658
+ // (see issue #69). Only splitValues/esc/escHtml stay UI-local: they encode
659
+ // browser-only concerns (no glob/file access, HTML escaping) with no CLI
660
+ // counterpart to drift from.
661
+ import { parseVars, expand as sharedExpand, VAR_PATTERN } from './parser.js';
662
+ import { cartesian as sharedCartesian, zip as sharedZip } from './iterate.js';
663
+
653
664
  (function () {
654
- const VAR_RE = /\{\{(\w+)(?:=([^}]*))?\}\}/g;
655
665
  const DEBOUNCE_MS = 120;
656
666
 
657
667
  const PRESETS = [
@@ -726,20 +736,6 @@
726
736
  const copyBtn = document.getElementById('copy-btn');
727
737
  const modeDesc = document.getElementById('mode-desc');
728
738
 
729
- function parseVarsFromTemplate(text) {
730
- const seen = new Set();
731
- const vars = [];
732
- let m;
733
- VAR_RE.lastIndex = 0;
734
- while ((m = VAR_RE.exec(text)) !== null) {
735
- if (!seen.has(m[1])) {
736
- seen.add(m[1]);
737
- vars.push({ name: m[1], defaultValue: m[2] });
738
- }
739
- }
740
- return vars;
741
- }
742
-
743
739
  function getRowData() {
744
740
  const rows = [];
745
741
  for (const tr of varBody.querySelectorAll('tr')) {
@@ -753,7 +749,12 @@
753
749
 
754
750
  function syncVarsFromTemplate() {
755
751
  const text = tmpl.value;
756
- const parsed = parseVarsFromTemplate(text);
752
+ let parsed;
753
+ try {
754
+ parsed = parseVars(text);
755
+ } catch {
756
+ return; // update() reports the parse error in the preview
757
+ }
757
758
  const existing = getRowData();
758
759
  const existingNames = new Set(existing.map(r => r.name));
759
760
 
@@ -814,41 +815,9 @@
814
815
  return raw.split(',').map(s => s.trim()).filter(Boolean);
815
816
  }
816
817
 
817
- function cartesian(arrays) {
818
- if (arrays.length === 0) return [[]];
819
- const first = arrays[0];
820
- const rest = cartesian(arrays.slice(1));
821
- const out = [];
822
- for (const v of first) {
823
- for (const r of rest) {
824
- out.push([v, ...r]);
825
- }
826
- }
827
- return out;
828
- }
829
-
830
- function zip(arrays) {
831
- const maxLen = Math.max(...arrays.map(a => a.length), 1);
832
- const out = [];
833
- for (let i = 0; i < maxLen; i++) {
834
- out.push(arrays.map(a => a[Math.min(i, a.length - 1)]));
835
- }
836
- return out;
837
- }
838
-
839
- function expand(template, values) {
840
- let result = template;
841
- VAR_RE.lastIndex = 0;
842
- result = result.replace(VAR_RE, (match, name, def) => {
843
- if (name in values) return values[name];
844
- if (def !== undefined) return def;
845
- return match;
846
- });
847
- return result;
848
- }
849
-
850
818
  function highlightUnfilled(html) {
851
- return html.replace(/\{\{(\w+)(?:=[^}]*)?\}\}/g, '<span class="unfilled">$&</span>');
819
+ VAR_PATTERN.lastIndex = 0;
820
+ return html.replace(VAR_PATTERN, '<span class="unfilled">$&</span>');
852
821
  }
853
822
 
854
823
  function update() {
@@ -879,16 +848,24 @@
879
848
  if (arrayVars.length === 0) {
880
849
  combos = [[]];
881
850
  } else if (mode === 'cross') {
882
- combos = cartesian(arrayVars.map(v => v.values));
851
+ combos = sharedCartesian(arrayVars.map(v => v.values));
883
852
  } else {
884
- combos = zip(arrayVars.map(v => v.values));
853
+ combos = sharedZip(arrayVars.map(v => v.values));
885
854
  }
886
855
 
887
856
  const results = [];
888
- for (const combo of combos) {
889
- const vals = { ...scalarValues };
890
- arrayVars.forEach((v, i) => { vals[v.name] = combo[i]; });
891
- results.push({ values: { ...vals }, expanded: expand(text, vals) });
857
+ try {
858
+ for (const combo of combos) {
859
+ const vals = { ...scalarValues };
860
+ arrayVars.forEach((v, i) => { vals[v.name] = combo[i]; });
861
+ results.push({ values: { ...vals }, expanded: sharedExpand(text, vals) });
862
+ }
863
+ } catch (err) {
864
+ previewBody.innerHTML = `<span class="preview-empty">${escHtml(err instanceof Error ? err.message : String(err))}</span>`;
865
+ previewCount.textContent = 'error';
866
+ previewCount.className = 'preview-count empty';
867
+ cliText.innerHTML = '<span class="dollar">$</span>promptargs';
868
+ return;
892
869
  }
893
870
 
894
871
  const count = results.length;
@@ -908,16 +885,25 @@
908
885
  }
909
886
  previewBody.innerHTML = html || '<span class="preview-empty">No output</span>';
910
887
 
911
- // Build CLI command
888
+ // Build CLI command. Every value must survive a POSIX shell unchanged:
889
+ // a bare word is left as-is, anything else is single-quoted (the only
890
+ // quoting under which $, backtick, backslash and whitespace are all
891
+ // literal). The inlined template stays double-quoted but also escapes
892
+ // the characters the shell expands inside double quotes.
893
+ const bareWord = /^[A-Za-z0-9_@%+=:,.\/-]+$/;
894
+ const shellQuote = s => bareWord.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
895
+ const dquote = s => `"${s.replace(/[\\"$`]/g, '\\$&')}"`;
912
896
  let cli = 'promptargs';
913
897
  const hasInline = !text.includes('\n') && text.length < 200;
914
898
  if (hasInline) {
915
- cli += ` "${text.replace(/"/g, '\\"')}"`;
899
+ cli += ` ${dquote(text)}`;
916
900
  }
917
901
  for (const row of rows) {
918
902
  if (!row.values) continue;
919
903
  const prefix = row.source === 'file' ? '@' : '';
920
- const val = row.source === 'glob' ? `"${row.values}"` : `${prefix}${row.values}`;
904
+ // Globs keep their double quotes so the CLI, not the shell, expands them;
905
+ // the same escaping as the template keeps the pattern itself literal.
906
+ const val = row.source === 'glob' ? dquote(row.values) : shellQuote(`${prefix}${row.values}`);
921
907
  cli += ` --${row.name}=${val}`;
922
908
  }
923
909
  if (mode === 'cross' && arrayVars.length > 1) {
package/dist/ui.js CHANGED
@@ -2,7 +2,8 @@ import { createServer } from 'node:http';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { join, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { autodetect, AUTODETECT_VARS } from './autodetect.js';
5
+ import { autodetect, isSensitiveEnvName, isSensitiveEnvValue, AUTODETECT_VARS } from './autodetect.js';
6
+ import { diag, classifyError, jsonLogEnabled, logRequest } from './log.js';
6
7
  const DEFAULT_PORT = 3700;
7
8
  const OPEN_DELAY_MS = 500;
8
9
  const DIFF_PREVIEW_MAX_CHARS = 60;
@@ -18,15 +19,15 @@ const SKIP_ENV_EXACT = new Set([
18
19
  'TERM_PROGRAM_VERSION', 'TERM_SESSION_ID', 'Apple_PubSub_Socket_Render',
19
20
  'MallocNanoZone', 'ORIGINAL_XDG_CURRENT_DESKTOP', 'GIT_ASKPASS',
20
21
  ]);
21
- function shouldSkipEnv(key) {
22
+ export function shouldSkipEnv(key) {
22
23
  if (SKIP_ENV_EXACT.has(key))
23
24
  return true;
24
25
  return SKIP_ENV_PREFIXES.some(p => key.startsWith(p));
25
26
  }
26
- function truncateValue(val, max) {
27
+ export function truncateValue(val, max) {
27
28
  return val.length > max ? val.slice(0, max) + '...' : val;
28
29
  }
29
- function collectEnvVars() {
30
+ export function collectEnvVars() {
30
31
  const git = {};
31
32
  for (const name of AUTODETECT_VARS) {
32
33
  const value = autodetect(name);
@@ -38,13 +39,31 @@ function collectEnvVars() {
38
39
  }
39
40
  const terminal = {};
40
41
  for (const [key, val] of Object.entries(process.env)) {
41
- if (!val || shouldSkipEnv(key))
42
+ // Never embed credential-looking names or credential-shaped values in the
43
+ // served page.
44
+ if (!val || shouldSkipEnv(key) || isSensitiveEnvName(key) || isSensitiveEnvValue(val))
42
45
  continue;
43
46
  terminal[key] = truncateValue(val, ENV_VAL_PREVIEW_MAX_CHARS);
44
47
  }
45
48
  return { git, terminal };
46
49
  }
47
- export function startUI(port = DEFAULT_PORT) {
50
+ export function buildUIHtml(rawHtml, envVars) {
51
+ // Escape "<" so env values cannot break out of the script tag (e.g. "</script>").
52
+ const envJson = JSON.stringify(envVars).replace(/</g, '\\u003c');
53
+ const envScript = `<script>window.__PROMPTARGS_ENV__ = ${envJson};</script>`;
54
+ // Function replacer: a replacement *string* would expand `$&`, "$`" and "$'"
55
+ // found in env values into surrounding raw HTML, undoing the escaping above.
56
+ return rawHtml.replace(/<script\b/, (match) => `${envScript}\n${match}`);
57
+ }
58
+ // Browser-facing modules the builder UI imports directly instead of
59
+ // hand-reimplementing their logic (see buildUIHtml / ui.html). Serving the
60
+ // exact compiled files means the browser runs the same code the CLI does.
61
+ const STATIC_JS_ROUTES = {
62
+ '/parser.js': 'parser.js',
63
+ '/iterate.js': 'iterate.js',
64
+ '/mustache.mjs': 'mustache.mjs',
65
+ };
66
+ export function startUI(port = DEFAULT_PORT, options = {}) {
48
67
  const __dirname = dirname(fileURLToPath(import.meta.url));
49
68
  const htmlPath = join(__dirname, 'ui.html');
50
69
  let rawHtml;
@@ -52,34 +71,83 @@ export function startUI(port = DEFAULT_PORT) {
52
71
  rawHtml = readFileSync(htmlPath, 'utf-8');
53
72
  }
54
73
  catch {
55
- console.error('UI file not found. Reinstall @hivecommons/promptargs.');
74
+ diag('ui_start_error', { error_class: 'not_found' }, 'UI file not found. Reinstall @hivecommons/promptargs.');
56
75
  process.exit(1);
57
76
  }
58
- const envVars = collectEnvVars();
59
- const envScript = `<script>window.__PROMPTARGS_ENV__ = ${JSON.stringify(envVars)};</script>`;
60
- const html = rawHtml.replace('<script>', envScript + '\n<script>');
61
- const server = createServer((_req, res) => {
77
+ const html = buildUIHtml(rawHtml, collectEnvVars());
78
+ // Filled once the server is bound, because port 0 means the OS picks one.
79
+ const ALLOWED_HOSTS = new Set();
80
+ let rejectedHosts = 0;
81
+ const server = createServer((req, res) => {
82
+ if (jsonLogEnabled()) {
83
+ const startedAt = process.hrtime.bigint();
84
+ res.on('finish', () => logRequest(res.statusCode, startedAt));
85
+ }
86
+ // Reject non-local Host headers to block DNS-rebinding reads of env data.
87
+ if (!req.headers.host || !ALLOWED_HOSTS.has(req.headers.host)) {
88
+ // Never log the Host header or URL: both are attacker-controlled.
89
+ if (rejectedHosts++ === 0) {
90
+ diag('ui_host_rejected', { status: 403 }, 'promptargs: rejected request with unexpected Host header (further rejections suppressed)');
91
+ }
92
+ res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
93
+ res.end('Forbidden');
94
+ return;
95
+ }
96
+ const file = req.url ? STATIC_JS_ROUTES[req.url] : undefined;
97
+ if (file) {
98
+ try {
99
+ const js = readFileSync(join(__dirname, file), 'utf-8');
100
+ res.writeHead(200, { 'Content-Type': 'text/javascript; charset=utf-8' });
101
+ res.end(js);
102
+ }
103
+ catch (err) {
104
+ diag('ui_asset_error', { status: 404, error_class: classifyError(err) }, `promptargs: failed to load static asset ${req.url} (${err.code ?? 'unknown'})`);
105
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
106
+ res.end('Not found');
107
+ }
108
+ return;
109
+ }
62
110
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
63
111
  res.end(html);
64
112
  });
65
- server.listen(port, () => {
66
- const url = `http://localhost:${port}`;
113
+ // Bind loopback only: the page embeds environment variables and must never
114
+ // be reachable from other machines on the network.
115
+ server.listen(port, '127.0.0.1', () => {
116
+ const boundPort = server.address().port;
117
+ ALLOWED_HOSTS.add(`localhost:${boundPort}`);
118
+ ALLOWED_HOSTS.add(`127.0.0.1:${boundPort}`);
119
+ ALLOWED_HOSTS.add(`[::1]:${boundPort}`);
120
+ const url = `http://localhost:${boundPort}`;
67
121
  console.log(`promptargs builder running at ${url}`);
68
122
  console.log('Press Ctrl+C to stop.\n');
69
- setTimeout(() => {
70
- import('node:child_process').then(({ exec }) => {
71
- const cmd = process.platform === 'darwin' ? 'open' :
72
- process.platform === 'win32' ? 'start' : 'xdg-open';
73
- exec(`${cmd} ${url}`);
74
- });
75
- }, OPEN_DELAY_MS);
123
+ if (options.openBrowser ?? true) {
124
+ setTimeout(() => {
125
+ import('node:child_process').then(({ exec }) => {
126
+ const cmd = process.platform === 'darwin' ? 'open' :
127
+ process.platform === 'win32' ? 'start' : 'xdg-open';
128
+ exec(`${cmd} ${url}`, err => {
129
+ if (!err)
130
+ return;
131
+ // Through a shell, a missing opener surfaces as exit status 127 rather than ENOENT.
132
+ const why = err.code === 127 ? 'not found' : String(err.code ?? err.message);
133
+ diag('ui_browser_open_failed', { error_class: classifyError(err) }, `promptargs: could not open a browser (${cmd}: ${why}); open ${url} manually`);
134
+ });
135
+ });
136
+ }, OPEN_DELAY_MS);
137
+ }
138
+ });
139
+ server.on('close', () => {
140
+ if (rejectedHosts > 1) {
141
+ diag('ui_host_rejected_total', { count: rejectedHosts }, `promptargs: rejected ${rejectedHosts} requests with unexpected Host headers in total`);
142
+ }
76
143
  });
77
144
  server.on('error', (err) => {
78
145
  if (err.code === 'EADDRINUSE') {
79
- console.error(`Port ${port} is in use. Try: promptargs ui --port=${port + 1}`);
146
+ diag('ui_start_error', { error_class: 'port_in_use' }, `Port ${port} is in use. Try: promptargs ui --port=${port + 1}`);
80
147
  process.exit(1);
81
148
  }
82
149
  throw err;
83
150
  });
151
+ return server;
84
152
  }
85
153
  //# sourceMappingURL=ui.js.map
package/dist/ui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@hivecommons/promptargs",
3
- "version": "0.6.0",
4
- "description": "Template arguments for AI prompts — variable substitution for Claude Code, Copilot, and more",
3
+ "version": "0.8.0",
4
+ "description": "Template arguments for AI prompts — variable substitution for Claude Code, Copilot, Goose, Bob, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "promptargs": "dist/cli.js"
9
9
  },
10
10
  "scripts": {
11
- "build": "tsc && cp src/ui.html dist/ui.html",
11
+ "build": "tsc && cp src/ui.html dist/ui.html && cp node_modules/mustache/mustache.mjs dist/mustache.mjs",
12
12
  "dev": "tsc --watch",
13
- "test": "node --test dist/**/*.test.js",
13
+ "test": "node scripts/run-tests.mjs",
14
+ "test:coverage": "node scripts/run-tests.mjs --coverage",
14
15
  "lint": "tsc --noEmit"
15
16
  },
16
17
  "keywords": [
@@ -20,7 +21,12 @@
20
21
  "claude",
21
22
  "copilot",
22
23
  "args",
23
- "cli"
24
+ "cli",
25
+ "claude-code",
26
+ "goose",
27
+ "bob",
28
+ "agent-skills",
29
+ "prompt-templates"
24
30
  ],
25
31
  "author": "hivecommons",
26
32
  "license": "Apache-2.0",
@@ -33,10 +39,14 @@
33
39
  },
34
40
  "homepage": "https://github.com/hivecommons/promptargs#readme",
35
41
  "engines": {
36
- "node": ">=18"
42
+ "node": ">=22"
37
43
  },
38
44
  "files": [
39
45
  "dist",
46
+ "!dist/**/*.test.js",
47
+ "!dist/**/*.test.js.map",
48
+ "!dist/**/*.test.d.ts",
49
+ "!dist/ui-harness.*",
40
50
  "README.md",
41
51
  "LICENSE"
42
52
  ],
@@ -1 +0,0 @@
1
- export {};
@@ -1,56 +0,0 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert';
3
- import { parseVars, expand } from './parser.js';
4
- describe('parseVars', () => {
5
- it('finds simple variables', () => {
6
- const vars = parseVars('Hello {{name}}, welcome to {{place}}');
7
- assert.strictEqual(vars.length, 2);
8
- assert.strictEqual(vars[0].name, 'name');
9
- assert.strictEqual(vars[1].name, 'place');
10
- });
11
- it('finds variables with defaults', () => {
12
- const vars = parseVars('Be {{tone=concise}} please');
13
- assert.strictEqual(vars.length, 1);
14
- assert.strictEqual(vars[0].name, 'tone');
15
- assert.strictEqual(vars[0].defaultValue, 'concise');
16
- });
17
- it('deduplicates variables', () => {
18
- const vars = parseVars('{{name}} said hi to {{name}}');
19
- assert.strictEqual(vars.length, 1);
20
- });
21
- it('returns empty for no variables', () => {
22
- const vars = parseVars('No variables here');
23
- assert.strictEqual(vars.length, 0);
24
- });
25
- });
26
- describe('expand', () => {
27
- it('substitutes values', () => {
28
- const result = expand('Hello {{name}}!', { name: 'World' });
29
- assert.strictEqual(result, 'Hello World!');
30
- });
31
- it('uses defaults when no value provided', () => {
32
- const result = expand('Be {{tone=concise}}', {});
33
- assert.strictEqual(result, 'Be concise');
34
- });
35
- it('overrides defaults with provided values', () => {
36
- const result = expand('Be {{tone=concise}}', { tone: 'verbose' });
37
- assert.strictEqual(result, 'Be verbose');
38
- });
39
- it('leaves unfilled vars as-is', () => {
40
- const result = expand('Hello {{name}}', {});
41
- assert.strictEqual(result, 'Hello {{name}}');
42
- });
43
- it('handles multiple vars', () => {
44
- const result = expand('Review {{file}} for {{focus=correctness}}. Be {{tone=concise}}.', { file: 'main.go' });
45
- assert.strictEqual(result, 'Review main.go for correctness. Be concise.');
46
- });
47
- it('handles arrays in comma-separated values', () => {
48
- const result = expand('Check {{file}}', { file: 'a.go' });
49
- assert.strictEqual(result, 'Check a.go');
50
- });
51
- it('does not HTML-escape values', () => {
52
- const result = expand('{{code}}', { code: '<div class="test">' });
53
- assert.strictEqual(result, '<div class="test">');
54
- });
55
- });
56
- //# sourceMappingURL=parser.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"parser.test.js","sourceRoot":"","sources":["../src/parser.test.ts"],"names":[],"mappings":"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"}