devharness 0.9.15 → 0.9.17

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 (52) hide show
  1. package/build/github/issue-body.d.ts +4 -4
  2. package/build/helpers/env-file.d.ts +35 -0
  3. package/build/helpers/env-file.d.ts.map +1 -0
  4. package/build/helpers/env-file.js +53 -0
  5. package/build/helpers/env-file.js.map +1 -0
  6. package/build/tools/interpolation.d.ts +20 -3
  7. package/build/tools/interpolation.d.ts.map +1 -1
  8. package/build/tools/interpolation.js +52 -14
  9. package/build/tools/interpolation.js.map +1 -1
  10. package/build/tools/replay-env-file.test.d.ts +2 -0
  11. package/build/tools/replay-env-file.test.d.ts.map +1 -0
  12. package/build/tools/replay-env-file.test.js +137 -0
  13. package/build/tools/replay-env-file.test.js.map +1 -0
  14. package/build/tools/replay-env-token.test.d.ts +2 -0
  15. package/build/tools/replay-env-token.test.d.ts.map +1 -0
  16. package/build/tools/replay-env-token.test.js +113 -0
  17. package/build/tools/replay-env-token.test.js.map +1 -0
  18. package/build/tools/replay-executor.d.ts +27 -0
  19. package/build/tools/replay-executor.d.ts.map +1 -1
  20. package/build/tools/replay-executor.js +25 -7
  21. package/build/tools/replay-executor.js.map +1 -1
  22. package/build/tools/replay-nested-rebase.test.d.ts +15 -0
  23. package/build/tools/replay-nested-rebase.test.d.ts.map +1 -0
  24. package/build/tools/replay-nested-rebase.test.js +139 -0
  25. package/build/tools/replay-nested-rebase.test.js.map +1 -0
  26. package/build/tools/replay-nested-variables.test.d.ts +2 -0
  27. package/build/tools/replay-nested-variables.test.d.ts.map +1 -0
  28. package/build/tools/replay-nested-variables.test.js +139 -0
  29. package/build/tools/replay-nested-variables.test.js.map +1 -0
  30. package/build/tools/replay-rebase.test.js +25 -0
  31. package/build/tools/replay-rebase.test.js.map +1 -1
  32. package/build/tools/replay-run-all-parity.test.d.ts +2 -0
  33. package/build/tools/replay-run-all-parity.test.d.ts.map +1 -0
  34. package/build/tools/replay-run-all-parity.test.js +90 -0
  35. package/build/tools/replay-run-all-parity.test.js.map +1 -0
  36. package/build/tools/replay-tools.d.ts +25 -21
  37. package/build/tools/replay-tools.d.ts.map +1 -1
  38. package/build/tools/replay-tools.js +202 -14
  39. package/build/tools/replay-tools.js.map +1 -1
  40. package/build/tools/replay-typed-text-variables.test.d.ts +2 -0
  41. package/build/tools/replay-typed-text-variables.test.d.ts.map +1 -0
  42. package/build/tools/replay-typed-text-variables.test.js +119 -0
  43. package/build/tools/replay-typed-text-variables.test.js.map +1 -0
  44. package/build/tools/replay-variable-key-validation.test.d.ts +2 -0
  45. package/build/tools/replay-variable-key-validation.test.d.ts.map +1 -0
  46. package/build/tools/replay-variable-key-validation.test.js +124 -0
  47. package/build/tools/replay-variable-key-validation.test.js.map +1 -0
  48. package/docs/instructions.md +5 -2
  49. package/docs/replay.md +99 -6
  50. package/package.json +1 -1
  51. package/plugin/skills/devharness/SKILL.md +2 -2
  52. package/plugin/skills/devharness/references/sequences.md +33 -3
@@ -115,8 +115,6 @@ declare const remoteSequenceSchema: z.ZodObject<{
115
115
  }[];
116
116
  name?: string | undefined;
117
117
  startUrl?: string | undefined;
118
- description?: string | undefined;
119
- expectedOutcome?: string | undefined;
120
118
  requiredConnections?: {
121
119
  reference: string;
122
120
  profile?: string | undefined;
@@ -124,6 +122,8 @@ declare const remoteSequenceSchema: z.ZodObject<{
124
122
  forceNewInstance?: boolean | undefined;
125
123
  role?: string | undefined;
126
124
  }[] | undefined;
125
+ description?: string | undefined;
126
+ expectedOutcome?: string | undefined;
127
127
  tags?: string[] | undefined;
128
128
  requiredSockets?: string[] | undefined;
129
129
  teardown?: {
@@ -141,8 +141,6 @@ declare const remoteSequenceSchema: z.ZodObject<{
141
141
  }[];
142
142
  name?: string | undefined;
143
143
  startUrl?: string | undefined;
144
- description?: string | undefined;
145
- expectedOutcome?: string | undefined;
146
144
  requiredConnections?: {
147
145
  reference: string;
148
146
  profile?: string | undefined;
@@ -150,6 +148,8 @@ declare const remoteSequenceSchema: z.ZodObject<{
150
148
  forceNewInstance?: boolean | undefined;
151
149
  role?: string | undefined;
152
150
  }[] | undefined;
151
+ description?: string | undefined;
152
+ expectedOutcome?: string | undefined;
153
153
  tags?: string[] | undefined;
154
154
  requiredSockets?: string[] | undefined;
155
155
  teardown?: {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Parse a `KEY=value` environment file for a single replay run.
3
+ *
4
+ * A run reads its credentials from a file the caller names rather than from
5
+ * the ambient process environment, so the values never enter the sequence
6
+ * file, never travel in the tool call, and never need the MCP client
7
+ * restarted to change - the environment of a running server is fixed at the
8
+ * moment it starts.
9
+ *
10
+ * The parse is deliberately narrow. There is no `$VAR` expansion inside
11
+ * values: a password containing a `$` is ordinary, and expanding it would
12
+ * substitute something else silently and type the wrong credential.
13
+ */
14
+ /** One line's worth of syntax the parser refuses, with the line number. */
15
+ export interface EnvFileProblem {
16
+ line: number;
17
+ text: string;
18
+ }
19
+ export interface ParsedEnvFile {
20
+ values: Record<string, string>;
21
+ problems: EnvFileProblem[];
22
+ }
23
+ /**
24
+ * Read `KEY=value` pairs out of the file's text.
25
+ *
26
+ * Accepted per line: a blank line, a `#` comment, or `NAME=value` with an
27
+ * optional `export ` prefix. Surrounding single or double quotes are stripped
28
+ * from the value; everything else in it is kept verbatim, trailing whitespace
29
+ * included where it was quoted. A name that does not match
30
+ * `[A-Za-z_][A-Za-z0-9_]*` is reported rather than stored, because
31
+ * {{env:NAME}} could never reference it and a silently skipped line reads as
32
+ * a set variable.
33
+ */
34
+ export declare function parseEnvFile(text: string): ParsedEnvFile;
35
+ //# sourceMappingURL=env-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-file.d.ts","sourceRoot":"","sources":["../../src/helpers/env-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,2EAA2E;AAC3E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CA8BxD"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Parse a `KEY=value` environment file for a single replay run.
3
+ *
4
+ * A run reads its credentials from a file the caller names rather than from
5
+ * the ambient process environment, so the values never enter the sequence
6
+ * file, never travel in the tool call, and never need the MCP client
7
+ * restarted to change - the environment of a running server is fixed at the
8
+ * moment it starts.
9
+ *
10
+ * The parse is deliberately narrow. There is no `$VAR` expansion inside
11
+ * values: a password containing a `$` is ordinary, and expanding it would
12
+ * substitute something else silently and type the wrong credential.
13
+ */
14
+ const NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
15
+ /**
16
+ * Read `KEY=value` pairs out of the file's text.
17
+ *
18
+ * Accepted per line: a blank line, a `#` comment, or `NAME=value` with an
19
+ * optional `export ` prefix. Surrounding single or double quotes are stripped
20
+ * from the value; everything else in it is kept verbatim, trailing whitespace
21
+ * included where it was quoted. A name that does not match
22
+ * `[A-Za-z_][A-Za-z0-9_]*` is reported rather than stored, because
23
+ * {{env:NAME}} could never reference it and a silently skipped line reads as
24
+ * a set variable.
25
+ */
26
+ export function parseEnvFile(text) {
27
+ const values = {};
28
+ const problems = [];
29
+ text.split(/\r?\n/).forEach((raw, i) => {
30
+ const line = raw.trim();
31
+ if (line === '' || line.startsWith('#'))
32
+ return;
33
+ const body = line.startsWith('export ') ? line.slice('export '.length).trim() : line;
34
+ const eq = body.indexOf('=');
35
+ if (eq <= 0) {
36
+ problems.push({ line: i + 1, text: line });
37
+ return;
38
+ }
39
+ const name = body.slice(0, eq).trim();
40
+ if (!NAME_RE.test(name)) {
41
+ problems.push({ line: i + 1, text: line });
42
+ return;
43
+ }
44
+ let value = body.slice(eq + 1).trim();
45
+ const quoted = (value.startsWith('"') && value.endsWith('"') && value.length >= 2)
46
+ || (value.startsWith("'") && value.endsWith("'") && value.length >= 2);
47
+ if (quoted)
48
+ value = value.slice(1, -1);
49
+ values[name] = value;
50
+ });
51
+ return { values, problems };
52
+ }
53
+ //# sourceMappingURL=env-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-file.js","sourceRoot":"","sources":["../../src/helpers/env-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,MAAM,OAAO,GAAG,0BAA0B,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAqB,EAAE,CAAC;IAEtC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO;QAEhD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACrF,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACZ,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;eAC7E,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QACzE,IAAI,MAAM;YAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAEvC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Sequence-step parameter interpolation.
3
- * Resolves {{var:name.path.to.field}} and {{timestamp}} tokens inside a
4
- * command's params before the executor runs it.
3
+ * Resolves {{var:name.path.to.field}}, {{timestamp}} and {{env:NAME}} tokens
4
+ * inside a command's params before the executor runs it.
5
5
  *
6
6
  * Whole-string-token rule: if a string param's entire value is exactly one
7
7
  * token (e.g. params.right === "{{var:r.body.available}}"), the resolved
@@ -16,7 +16,22 @@
16
16
  * {{var:...}} paths are plain dot-separated segments (a.b.c) except where a
17
17
  * real key contains a literal dot (e.g. a response field keyed 'exec.t1.s2')
18
18
  * - use bracket notation for that segment: a.b['exec.t1.s2'].c
19
+ *
20
+ * {{env:NAME}} reads the run's environment at run time - the file named by
21
+ * `envFile` first, then process.env. It is how a credential is kept
22
+ * OUT of the sequence file: the file holds the token, the value lives in the
23
+ * environment, and neither the file nor the tool call carries the secret. An
24
+ * unset or empty variable fails the step rather than resolving to '' - a blank
25
+ * password submits the form and surfaces as a confusing downstream failure
26
+ * instead of the missing configuration that caused it. Environment values are
27
+ * strings, so a whole-string {{env:PORT}} yields "3000", not 3000.
28
+ */
29
+ /**
30
+ * Whether a string carries any interpolation token. A step already
31
+ * parameterised this way needs no `variables` prompt: its value is supplied at
32
+ * run time by definition.
19
33
  */
34
+ export declare function hasTemplateToken(value: string): boolean;
20
35
  export declare class InterpolationError extends Error {
21
36
  token: string;
22
37
  constructor(token: string, reason: string);
@@ -26,5 +41,7 @@ export declare class InterpolationError extends Error {
26
41
  * runTimestamp must be computed once per sequence run (not per call) so
27
42
  * {{timestamp}} is stable across multiple steps in the same run.
28
43
  */
29
- export declare function interpolateParams(params: Record<string, any>, store: Record<string, any>, runTimestamp: number): Record<string, any>;
44
+ export declare function interpolateParams(params: Record<string, any>, store: Record<string, any>, runTimestamp: number,
45
+ /** The run's `envFile` values, checked before process.env by {{env:NAME}}. */
46
+ runEnv?: Record<string, string>): Record<string, any>;
30
47
  //# sourceMappingURL=interpolation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"interpolation.d.ts","sourceRoot":"","sources":["../../src/tools/interpolation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,qBAAa,kBAAmB,SAAQ,KAAK;IACxB,KAAK,EAAE,MAAM;gBAAb,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAGjD;AAkHD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,YAAY,EAAE,MAAM,GACnB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAErB"}
1
+ {"version":3,"file":"interpolation.d.ts","sourceRoot":"","sources":["../../src/tools/interpolation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IACxB,KAAK,EAAE,MAAM;gBAAb,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAGjD;AA8ID;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,YAAY,EAAE,MAAM;AACpB,8EAA8E;AAC9E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAErB"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Sequence-step parameter interpolation.
3
- * Resolves {{var:name.path.to.field}} and {{timestamp}} tokens inside a
4
- * command's params before the executor runs it.
3
+ * Resolves {{var:name.path.to.field}}, {{timestamp}} and {{env:NAME}} tokens
4
+ * inside a command's params before the executor runs it.
5
5
  *
6
6
  * Whole-string-token rule: if a string param's entire value is exactly one
7
7
  * token (e.g. params.right === "{{var:r.body.available}}"), the resolved
@@ -16,9 +16,28 @@
16
16
  * {{var:...}} paths are plain dot-separated segments (a.b.c) except where a
17
17
  * real key contains a literal dot (e.g. a response field keyed 'exec.t1.s2')
18
18
  * - use bracket notation for that segment: a.b['exec.t1.s2'].c
19
+ *
20
+ * {{env:NAME}} reads the run's environment at run time - the file named by
21
+ * `envFile` first, then process.env. It is how a credential is kept
22
+ * OUT of the sequence file: the file holds the token, the value lives in the
23
+ * environment, and neither the file nor the tool call carries the secret. An
24
+ * unset or empty variable fails the step rather than resolving to '' - a blank
25
+ * password submits the form and surfaces as a confusing downstream failure
26
+ * instead of the missing configuration that caused it. Environment values are
27
+ * strings, so a whole-string {{env:PORT}} yields "3000", not 3000.
28
+ */
29
+ const TOKEN_BODY = String.raw `var:[^}]+|timestamp(?:[+-]\d+)?|env:[A-Za-z_][A-Za-z0-9_]*`;
30
+ const TOKEN_RE = new RegExp(String.raw `\{\{\s*(${TOKEN_BODY})\s*\}\}`, 'g');
31
+ const WHOLE_TOKEN_RE = new RegExp(String.raw `^\{\{\s*(${TOKEN_BODY})\s*\}\}$`);
32
+ /**
33
+ * Whether a string carries any interpolation token. A step already
34
+ * parameterised this way needs no `variables` prompt: its value is supplied at
35
+ * run time by definition.
19
36
  */
20
- const TOKEN_RE = /\{\{\s*(var:[^}]+|timestamp(?:[+-]\d+)?)\s*\}\}/g;
21
- const WHOLE_TOKEN_RE = /^\{\{\s*(var:[^}]+|timestamp(?:[+-]\d+)?)\s*\}\}$/;
37
+ export function hasTemplateToken(value) {
38
+ TOKEN_RE.lastIndex = 0;
39
+ return TOKEN_RE.test(value);
40
+ }
22
41
  export class InterpolationError extends Error {
23
42
  token;
24
43
  constructor(token, reason) {
@@ -56,12 +75,29 @@ function tokenizePath(path) {
56
75
  /**
57
76
  * Resolve a single token (without the {{ }} wrapper) against the variable store.
58
77
  */
59
- function resolveToken(token, store, runTimestamp) {
78
+ function resolveToken(token, store, runTimestamp, runEnv) {
60
79
  if (token.startsWith('timestamp')) {
61
80
  const offsetMatch = token.match(/^timestamp([+-]\d+)?$/);
62
81
  const offset = offsetMatch?.[1] ? parseInt(offsetMatch[1], 10) : 0;
63
82
  return runTimestamp + offset;
64
83
  }
84
+ if (token.startsWith('env:')) {
85
+ const name = token.slice('env:'.length);
86
+ // The named file wins over the ambient environment. The caller named this
87
+ // file for this run; a stale variable in the server's own environment
88
+ // shadowing it would substitute a different credential with nothing in the
89
+ // output to say so.
90
+ const value = runEnv && name in runEnv ? runEnv[name] : process.env[name];
91
+ // The reason is the variable NAME and its state, never its value: this
92
+ // message reaches the run output and the debug log.
93
+ if (value === undefined) {
94
+ throw new InterpolationError(token, `${name} is not set${runEnv ? ' in the run\'s envFile or' : ' in'} this process's environment - add it to the envFile, or export it before the run (the sequence file deliberately holds no value for it)`);
95
+ }
96
+ if (value === '') {
97
+ throw new InterpolationError(token, `${name} is set but empty - an empty value would be typed or sent as-is, so the step fails here instead`);
98
+ }
99
+ return value;
100
+ }
65
101
  const path = token.slice('var:'.length);
66
102
  let parts;
67
103
  try {
@@ -101,10 +137,10 @@ function resolveToken(token, store, runTimestamp) {
101
137
  * Whole-string token -> typed value passthrough.
102
138
  * Embedded token(s) -> String-coerced substitution.
103
139
  */
104
- function resolveString(value, store, runTimestamp) {
140
+ function resolveString(value, store, runTimestamp, runEnv) {
105
141
  const wholeMatch = value.match(WHOLE_TOKEN_RE);
106
142
  if (wholeMatch) {
107
- return resolveToken(wholeMatch[1], store, runTimestamp);
143
+ return resolveToken(wholeMatch[1], store, runTimestamp, runEnv);
108
144
  }
109
145
  TOKEN_RE.lastIndex = 0;
110
146
  if (!TOKEN_RE.test(value)) {
@@ -112,21 +148,21 @@ function resolveString(value, store, runTimestamp) {
112
148
  }
113
149
  TOKEN_RE.lastIndex = 0;
114
150
  return value.replace(TOKEN_RE, (_match, token) => {
115
- const resolved = resolveToken(token, store, runTimestamp);
151
+ const resolved = resolveToken(token, store, runTimestamp, runEnv);
116
152
  return typeof resolved === 'object' ? JSON.stringify(resolved) : String(resolved);
117
153
  });
118
154
  }
119
- function walk(value, store, runTimestamp) {
155
+ function walk(value, store, runTimestamp, runEnv) {
120
156
  if (typeof value === 'string') {
121
- return resolveString(value, store, runTimestamp);
157
+ return resolveString(value, store, runTimestamp, runEnv);
122
158
  }
123
159
  if (Array.isArray(value)) {
124
- return value.map(item => walk(item, store, runTimestamp));
160
+ return value.map(item => walk(item, store, runTimestamp, runEnv));
125
161
  }
126
162
  if (value !== null && typeof value === 'object') {
127
163
  const result = {};
128
164
  for (const key of Object.keys(value)) {
129
- result[key] = walk(value[key], store, runTimestamp);
165
+ result[key] = walk(value[key], store, runTimestamp, runEnv);
130
166
  }
131
167
  return result;
132
168
  }
@@ -137,7 +173,9 @@ function walk(value, store, runTimestamp) {
137
173
  * runTimestamp must be computed once per sequence run (not per call) so
138
174
  * {{timestamp}} is stable across multiple steps in the same run.
139
175
  */
140
- export function interpolateParams(params, store, runTimestamp) {
141
- return walk(params, store, runTimestamp);
176
+ export function interpolateParams(params, store, runTimestamp,
177
+ /** The run's `envFile` values, checked before process.env by {{env:NAME}}. */
178
+ runEnv) {
179
+ return walk(params, store, runTimestamp, runEnv);
142
180
  }
143
181
  //# sourceMappingURL=interpolation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../../src/tools/interpolation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interpolation.js","sourceRoot":"","sources":["../../src/tools/interpolation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=replay-env-file.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-env-file.test.d.ts","sourceRoot":"","sources":["../../src/tools/replay-env-file.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * `envFile` names a KEY=value file for one run, so a credential lives neither
3
+ * in the sequence file nor in the tool call, and changing it needs no MCP
4
+ * client restart - the environment of a running server is fixed when it starts.
5
+ *
6
+ * Two rules the alternatives get wrong. The file WINS over the server's own
7
+ * environment: the caller named this file for this run, and a stale ambient
8
+ * variable shadowing it would substitute a different credential with nothing
9
+ * in the output to say so. And process.env is never written: concurrent
10
+ * background runs may name different files, so a global write would let one
11
+ * run's credentials resolve inside the other.
12
+ */
13
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
14
+ import { promises as fs } from 'fs';
15
+ import { tmpdir } from 'os';
16
+ import { join } from 'path';
17
+ import { parseEnvFile } from '../helpers/env-file.js';
18
+ import { CommandRecorder } from '../command-recorder.js';
19
+ import { createReplayTools } from './replay-tools.js';
20
+ import { getProjectDir } from '../helpers/paths.js';
21
+ import { productionShaped } from '../test-support/fake-execute-tool-call.js';
22
+ describe('parseEnvFile', () => {
23
+ it('reads KEY=value, skipping blanks and # comments', () => {
24
+ const { values, problems } = parseEnvFile('# a comment\n\nAPP_PASSWORD=hunter2\nAPP_USER=alice\n');
25
+ expect(values).toEqual({ APP_PASSWORD: 'hunter2', APP_USER: 'alice' });
26
+ expect(problems).toEqual([]);
27
+ });
28
+ it('accepts an export prefix and strips surrounding quotes', () => {
29
+ const { values } = parseEnvFile('export A="one two"\nB=\'three\'\n');
30
+ expect(values).toEqual({ A: 'one two', B: 'three' });
31
+ });
32
+ it('keeps a value containing = and $ verbatim, with no expansion', () => {
33
+ const { values } = parseEnvFile('TOKEN=ab=cd$HOME$\n');
34
+ expect(values).toEqual({ TOKEN: 'ab=cd$HOME$' });
35
+ });
36
+ it('reports a line that is not NAME=value rather than skipping it', () => {
37
+ const { values, problems } = parseEnvFile('GOOD=1\njust some words\n2BAD=x\n');
38
+ expect(values).toEqual({ GOOD: '1' });
39
+ expect(problems.map(p => p.line)).toEqual([2, 3]);
40
+ });
41
+ it('keeps an empty value, which is a set-but-empty variable', () => {
42
+ expect(parseEnvFile('EMPTY=\n').values).toEqual({ EMPTY: '' });
43
+ });
44
+ });
45
+ describe('envFile through a run', () => {
46
+ let dir;
47
+ let recorder;
48
+ let replay;
49
+ let typed;
50
+ const original = { ...process.env };
51
+ beforeEach(async () => {
52
+ dir = await fs.mkdtemp(join(tmpdir(), 'cdp-envfile-'));
53
+ recorder = new CommandRecorder();
54
+ typed = [];
55
+ vi.spyOn(recorder, 'getSequencesDir').mockReturnValue(dir);
56
+ const fieldValues = new Map();
57
+ ({ replay } = createReplayTools(recorder, vi.fn(productionShaped(async (tool, params) => {
58
+ if (tool === 'input' && params.action === 'type') {
59
+ typed.push(String(params.text));
60
+ if (params.selector)
61
+ fieldValues.set(String(params.selector), String(params.text));
62
+ }
63
+ if (tool === 'inspect' && params.action === 'evaluateExpression') {
64
+ const selector = String(params.expression).match(/querySelector\('([^']*)'\)/)?.[1] ?? '';
65
+ return { content: [{ type: 'text', text: '```json\n' + JSON.stringify(fieldValues.get(selector) ?? '') + '\n```' }] };
66
+ }
67
+ return { content: [{ type: 'text', text: '' }] };
68
+ })), async () => null, async () => 9222, undefined));
69
+ await fs.writeFile(join(dir, 'login.json'), JSON.stringify({
70
+ id: 'seq-login', name: 'login', createdAt: 1,
71
+ commands: [{ tool: 'input', params: { action: 'type', selector: '#password', text: '{{env:APP_PASSWORD}}', connectionReason: 'app' } }],
72
+ }));
73
+ await recorder.loadSequenceFromDisk(join(dir, 'login.json'));
74
+ });
75
+ afterEach(async () => {
76
+ process.env = { ...original };
77
+ recorder.stopSequenceWatch();
78
+ await fs.rm(dir, { recursive: true, force: true });
79
+ });
80
+ const run = (extra) => replay.handler({ action: 'run', wait: true, name: 'login', connectionReason: 'app', ...extra });
81
+ const text = (res) => res.content.map((c) => c.text).join('\n');
82
+ it('resolves a token from the file at an absolute path', async () => {
83
+ const envPath = join(dir, 'sequences.env');
84
+ await fs.writeFile(envPath, 'APP_PASSWORD=from-file\n');
85
+ const res = await run({ envFile: envPath });
86
+ expect(res.isError).toBeUndefined();
87
+ expect(typed).toEqual(['from-file']);
88
+ });
89
+ it('resolves a relative path against the project directory', async () => {
90
+ const rel = 'test-sequences.env';
91
+ const abs = join(getProjectDir(), rel);
92
+ await fs.writeFile(abs, 'APP_PASSWORD=from-relative\n');
93
+ try {
94
+ await run({ envFile: rel });
95
+ expect(typed).toEqual(['from-relative']);
96
+ }
97
+ finally {
98
+ await fs.rm(abs, { force: true });
99
+ }
100
+ });
101
+ it('wins over a value already in the process environment', async () => {
102
+ process.env.APP_PASSWORD = 'from-ambient';
103
+ const envPath = join(dir, 'sequences.env');
104
+ await fs.writeFile(envPath, 'APP_PASSWORD=from-file\n');
105
+ await run({ envFile: envPath });
106
+ expect(typed).toEqual(['from-file']);
107
+ });
108
+ it('falls through to the process environment for a name the file omits', async () => {
109
+ process.env.APP_PASSWORD = 'from-ambient';
110
+ const envPath = join(dir, 'sequences.env');
111
+ await fs.writeFile(envPath, 'SOMETHING_ELSE=x\n');
112
+ await run({ envFile: envPath });
113
+ expect(typed).toEqual(['from-ambient']);
114
+ });
115
+ it('does not write the file values into process.env', async () => {
116
+ delete process.env.APP_PASSWORD;
117
+ const envPath = join(dir, 'sequences.env');
118
+ await fs.writeFile(envPath, 'APP_PASSWORD=from-file\n');
119
+ await run({ envFile: envPath });
120
+ expect(process.env.APP_PASSWORD).toBeUndefined();
121
+ });
122
+ it('fails as a parameter error before any step runs when the file is missing', async () => {
123
+ const res = await run({ envFile: join(dir, 'nope.env') });
124
+ expect(res.isError).toBe(true);
125
+ expect(text(res)).toContain('no such file');
126
+ expect(typed).toEqual([]);
127
+ });
128
+ it('fails before any step runs on a malformed line, naming the line number', async () => {
129
+ const envPath = join(dir, 'bad.env');
130
+ await fs.writeFile(envPath, 'APP_PASSWORD=ok\njust some words\n');
131
+ const res = await run({ envFile: envPath });
132
+ expect(res.isError).toBe(true);
133
+ expect(text(res)).toContain('line 2');
134
+ expect(typed).toEqual([]);
135
+ });
136
+ });
137
+ //# sourceMappingURL=replay-env-file.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-env-file.test.js","sourceRoot":"","sources":["../../src/tools/replay-env-file.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=replay-env-token.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-env-token.test.d.ts","sourceRoot":"","sources":["../../src/tools/replay-env-token.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `{{env:NAME}}` keeps a credential OUT of the sequence file: the file holds
3
+ * the token, the value lives in the environment, and neither the file nor the
4
+ * tool call carries the secret.
5
+ *
6
+ * Two behaviours the alternative gets wrong. An unset or empty variable FAILS
7
+ * the step - resolving to '' would submit a blank password and surface as a
8
+ * confusing downstream failure instead of the missing configuration that
9
+ * caused it. And a token-bearing step does not hold `run` open for a
10
+ * `variables` answer: its value arrives at run time by definition.
11
+ */
12
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
13
+ import { promises as fs } from 'fs';
14
+ import { tmpdir } from 'os';
15
+ import { join } from 'path';
16
+ import { interpolateParams, hasTemplateToken, InterpolationError } from './interpolation.js';
17
+ import { CommandRecorder } from '../command-recorder.js';
18
+ import { createReplayTools } from './replay-tools.js';
19
+ import { productionShaped } from '../test-support/fake-execute-tool-call.js';
20
+ describe('{{env:NAME}} resolution', () => {
21
+ const original = { ...process.env };
22
+ afterEach(() => { process.env = { ...original }; });
23
+ it('resolves from the process environment', () => {
24
+ process.env.APP_PASSWORD = 'from-env';
25
+ expect(interpolateParams({ text: '{{env:APP_PASSWORD}}' }, {}, 0)).toEqual({ text: 'from-env' });
26
+ });
27
+ it('substitutes inside a larger string', () => {
28
+ process.env.APP_TOKEN = 'abc';
29
+ expect(interpolateParams({ h: 'Bearer {{env:APP_TOKEN}}' }, {}, 0)).toEqual({ h: 'Bearer abc' });
30
+ });
31
+ it('fails the step when the variable is unset, naming the variable and not a value', () => {
32
+ delete process.env.APP_PASSWORD;
33
+ expect(() => interpolateParams({ text: '{{env:APP_PASSWORD}}' }, {}, 0))
34
+ .toThrow(InterpolationError);
35
+ expect(() => interpolateParams({ text: '{{env:APP_PASSWORD}}' }, {}, 0))
36
+ .toThrow(/APP_PASSWORD is not set/);
37
+ });
38
+ it('fails on an empty variable rather than typing an empty value', () => {
39
+ process.env.APP_PASSWORD = '';
40
+ expect(() => interpolateParams({ text: '{{env:APP_PASSWORD}}' }, {}, 0))
41
+ .toThrow(/APP_PASSWORD is set but empty/);
42
+ });
43
+ it('yields a string for a whole-string token, since environment values are strings', () => {
44
+ process.env.APP_PORT = '3000';
45
+ expect(interpolateParams({ port: '{{env:APP_PORT}}' }, {}, 0)).toEqual({ port: '3000' });
46
+ });
47
+ it('leaves the other token kinds working alongside it', () => {
48
+ process.env.APP_USER = 'alice';
49
+ const out = interpolateParams({ a: '{{env:APP_USER}}', b: '{{var:r.id}}', c: '{{timestamp}}' }, { r: { id: 7 } }, 1234);
50
+ expect(out).toEqual({ a: 'alice', b: 7, c: 1234 });
51
+ });
52
+ it('does not treat a lowercase or hyphenated name as a token', () => {
53
+ expect(hasTemplateToken('{{env:app-password}}')).toBe(false);
54
+ expect(interpolateParams({ t: '{{env:app-password}}' }, {}, 0)).toEqual({ t: '{{env:app-password}}' });
55
+ });
56
+ });
57
+ describe('a token-bearing step does not prompt for variables', () => {
58
+ let dir;
59
+ let recorder;
60
+ let replay;
61
+ let typed;
62
+ const original = { ...process.env };
63
+ beforeEach(async () => {
64
+ dir = await fs.mkdtemp(join(tmpdir(), 'cdp-env-token-'));
65
+ recorder = new CommandRecorder();
66
+ typed = [];
67
+ vi.spyOn(recorder, 'getSequencesDir').mockReturnValue(dir);
68
+ const fieldValues = new Map();
69
+ ({ replay } = createReplayTools(recorder, vi.fn(productionShaped(async (tool, params) => {
70
+ if (tool === 'input' && params.action === 'type') {
71
+ typed.push(String(params.text));
72
+ if (params.selector)
73
+ fieldValues.set(String(params.selector), String(params.text));
74
+ }
75
+ if (tool === 'inspect' && params.action === 'evaluateExpression') {
76
+ const selector = String(params.expression).match(/querySelector\('([^']*)'\)/)?.[1] ?? '';
77
+ return { content: [{ type: 'text', text: '```json\n' + JSON.stringify(fieldValues.get(selector) ?? '') + '\n```' }] };
78
+ }
79
+ return { content: [{ type: 'text', text: '' }] };
80
+ })), async () => null, async () => 9222, undefined));
81
+ await fs.writeFile(join(dir, 'login.json'), JSON.stringify({
82
+ id: 'seq-login', name: 'login', createdAt: 1,
83
+ commands: [{ tool: 'input', params: { action: 'type', selector: '#password', text: '{{env:APP_PASSWORD}}', connectionReason: 'suite' } }],
84
+ }));
85
+ await recorder.loadSequenceFromDisk(join(dir, 'login.json'));
86
+ });
87
+ afterEach(async () => {
88
+ process.env = { ...original };
89
+ recorder.stopSequenceWatch();
90
+ await fs.rm(dir, { recursive: true, force: true });
91
+ });
92
+ it('runs without variables and types the environment value', async () => {
93
+ process.env.APP_PASSWORD = 'from-env';
94
+ const res = await replay.handler({ action: 'run', wait: true, name: 'login', connectionReason: 'suite' });
95
+ expect(res._meta?.replay?.prompted).toBeUndefined();
96
+ expect(typed).toEqual(['from-env']);
97
+ });
98
+ it('lets an explicitly supplied value win over the environment', async () => {
99
+ process.env.APP_PASSWORD = 'from-env';
100
+ await replay.handler({
101
+ action: 'run', wait: true, name: 'login', connectionReason: 'suite',
102
+ variables: { var_0__password: 'from-caller' },
103
+ });
104
+ expect(typed).toEqual(['from-caller']);
105
+ });
106
+ it('fails the run when the variable is unset', async () => {
107
+ delete process.env.APP_PASSWORD;
108
+ const res = await replay.handler({ action: 'run', wait: true, name: 'login', connectionReason: 'suite' });
109
+ expect(res._meta?.replay?.success).toBe(false);
110
+ expect(typed).toEqual([]);
111
+ });
112
+ });
113
+ //# sourceMappingURL=replay-env-token.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-env-token.test.js","sourceRoot":"","sources":["../../src/tools/replay-env-token.test.ts"],"names":[],"mappings":"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"}