testronaut 1.3.6 โ†’ 1.4.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.
@@ -0,0 +1,11 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "npm" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
@@ -0,0 +1,58 @@
1
+ name: Periapsis Vulnerability Daily Check
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 14 * * *"
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ security-events: read
11
+ pull-requests: read
12
+
13
+ jobs:
14
+ periapsis-vulnerability:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - name: Install periapsis
19
+ run: npm install -g periapsis
20
+ - name: Run vulnerability governance check
21
+ run: periapsis vulnerability check --report
22
+ env:
23
+ GITHUB_TOKEN: ${{ secrets.PERIAPSIS_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
24
+ - name: Upload vulnerability report
25
+ if: always()
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: vulnerability-report
29
+ path: policy/reports/
30
+ retention-days: 90
31
+ - name: Send vulnerability notifications
32
+ run: periapsis vulnerability notify
33
+ env:
34
+ GITHUB_TOKEN: ${{ secrets.PERIAPSIS_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
35
+ PERIAPSIS_SLACK_WEBHOOK: ${{ secrets.PERIAPSIS_SLACK_WEBHOOK }}
36
+ - name: Read breach count from report
37
+ id: report
38
+ run: |
39
+ REPORT=$(ls policy/reports/vulnerability-report-*.json 2>/dev/null | sort | tail -n1)
40
+ if [ -z "$REPORT" ]; then
41
+ echo "breached=0" >> $GITHUB_OUTPUT
42
+ else
43
+ BREACHED=$(node -e "const r=JSON.parse(require('fs').readFileSync('$REPORT','utf8')); process.stdout.write(String(r.summary.breached))")
44
+ echo "breached=$BREACHED" >> $GITHUB_OUTPUT
45
+ echo "md=${REPORT%.json}.md" >> $GITHUB_OUTPUT
46
+ fi
47
+ - name: Send breach notification email
48
+ if: steps.report.outputs.breached != '0'
49
+ uses: dawidd6/action-send-mail@v3
50
+ with:
51
+ server_address: ${{ secrets.PERIAPSIS_SMTP_HOST }}
52
+ server_port: ${{ secrets.PERIAPSIS_SMTP_PORT || 587 }}
53
+ username: ${{ secrets.PERIAPSIS_SMTP_USER }}
54
+ password: ${{ secrets.PERIAPSIS_SMTP_PASSWORD }}
55
+ subject: "[${{ github.repository }}] ${{ steps.report.outputs.breached }} vulnerability SLA breach(es) detected"
56
+ to: ${{ secrets.PERIAPSIS_EMAIL_TO }}
57
+ from: ${{ secrets.PERIAPSIS_EMAIL_FROM || secrets.PERIAPSIS_SMTP_USER }}
58
+ body: file://${{ steps.report.outputs.md }}
@@ -0,0 +1,86 @@
1
+ name: Periapsis Vulnerability Exception Request
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ repo:
7
+ description: "Repository, e.g. owner/repo"
8
+ required: true
9
+ alertNumber:
10
+ description: "Dependabot alert number"
11
+ required: false
12
+ exceptionType:
13
+ description: "alert, package, repo_severity, ecosystem"
14
+ required: true
15
+ package:
16
+ description: "Package name"
17
+ required: false
18
+ ecosystem:
19
+ description: "Package ecosystem"
20
+ required: false
21
+ dependencyScope:
22
+ description: "prod, dev, unknown"
23
+ required: false
24
+ severities:
25
+ description: "Comma-separated severities"
26
+ required: true
27
+ reason:
28
+ description: "Reason for exception"
29
+ required: true
30
+ acceptedUntil:
31
+ description: "Expiry date YYYY-MM-DD or ISO datetime"
32
+ required: true
33
+ approvedBy:
34
+ description: "Approver"
35
+ required: true
36
+ evidenceRef:
37
+ description: "Evidence reference"
38
+ required: true
39
+
40
+ permissions:
41
+ contents: write
42
+ pull-requests: write
43
+
44
+ jobs:
45
+ exception-request:
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+ - name: Install periapsis
50
+ run: npm install -g periapsis
51
+ - name: Add vulnerability exception
52
+ env:
53
+ INPUT_REPO: ${{ inputs.repo }}
54
+ INPUT_ALERT_NUMBER: ${{ inputs.alertNumber }}
55
+ INPUT_TYPE: ${{ inputs.exceptionType }}
56
+ INPUT_PACKAGE: ${{ inputs.package }}
57
+ INPUT_ECOSYSTEM: ${{ inputs.ecosystem }}
58
+ INPUT_SCOPE: ${{ inputs.dependencyScope }}
59
+ INPUT_SEVERITIES: ${{ inputs.severities }}
60
+ INPUT_REASON: ${{ inputs.reason }}
61
+ INPUT_UNTIL: ${{ inputs.acceptedUntil }}
62
+ INPUT_APPROVED_BY: ${{ inputs.approvedBy }}
63
+ INPUT_EVIDENCE: ${{ inputs.evidenceRef }}
64
+ run: |
65
+ periapsis vulnerability exceptions add \
66
+ --non-interactive \
67
+ --repo "$INPUT_REPO" \
68
+ --alert-number "$INPUT_ALERT_NUMBER" \
69
+ --type "$INPUT_TYPE" \
70
+ ${INPUT_PACKAGE:+--package "$INPUT_PACKAGE"} \
71
+ ${INPUT_ECOSYSTEM:+--ecosystem "$INPUT_ECOSYSTEM"} \
72
+ ${INPUT_SCOPE:+--dependency-scope "$INPUT_SCOPE"} \
73
+ --severities "$INPUT_SEVERITIES" \
74
+ --reason "$INPUT_REASON" \
75
+ --accepted-until "$INPUT_UNTIL" \
76
+ --approved-by "$INPUT_APPROVED_BY" \
77
+ --evidence-ref "$INPUT_EVIDENCE"
78
+ - name: Validate vulnerability policy
79
+ run: periapsis vulnerability validate
80
+ - name: Create pull request
81
+ uses: peter-evans/create-pull-request@v6
82
+ with:
83
+ commit-message: "Add vulnerability SLA exception"
84
+ title: "Add vulnerability SLA exception"
85
+ body: "Adds a structured Periapsis vulnerability exception. Review required before merge."
86
+ branch: "periapsis/vulnerability-exception-request"
@@ -0,0 +1,23 @@
1
+ name: Periapsis Vulnerability PR Check
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened, ready_for_review]
6
+
7
+ permissions:
8
+ contents: read
9
+ security-events: read
10
+ pull-requests: read
11
+ checks: write
12
+
13
+ jobs:
14
+ periapsis-vulnerability-pr-check:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - name: Install periapsis
19
+ run: npm install -g periapsis
20
+ - name: Run vulnerability PR check
21
+ run: periapsis vulnerability pr-check
22
+ env:
23
+ GITHUB_TOKEN: ${{ secrets.PERIAPSIS_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
package/README.md CHANGED
@@ -44,6 +44,7 @@ Includes:
44
44
 
45
45
  ## ๐Ÿ“ฆ Installation
46
46
 
47
+ **Global install** (recommended โ€” use `testronaut` directly):
47
48
  ```bash
48
49
  npm install -g testronaut
49
50
  ```
@@ -51,11 +52,17 @@ npm install -g testronaut
51
52
  Then initialize your project:
52
53
 
53
54
  ```bash
54
- npx testronaut --init
55
+ testronaut --init
55
56
  ```
56
57
 
57
58
  Run the sample mission:
58
59
  ```bash
60
+ testronaut welcome.mission.js
61
+ ```
62
+
63
+ **One-off / no install** (use `npx` to run without installing):
64
+ ```bash
65
+ npx testronaut --init
59
66
  npx testronaut welcome.mission.js
60
67
  ```
61
68
 
@@ -117,10 +124,10 @@ Choose your preferred LLM at init or via environment variables.
117
124
 
118
125
  ```bash
119
126
  # During init
120
- npx testronaut --init
127
+ testronaut --init
121
128
 
122
129
  # Or override anytime
123
- TESTRONAUT_PROVIDER=gemini TESTRONAUT_MODEL=gemini-2.5-pro npx testronaut
130
+ TESTRONAUT_PROVIDER=gemini TESTRONAUT_MODEL=gemini-2.5-pro testronaut
124
131
  ```
125
132
 
126
133
  Current supported providers:
@@ -138,12 +145,12 @@ More providers coming soon (Anthropic, Mistral, etc.).
138
145
 
139
146
  Run all missions:
140
147
  ```bash
141
- npx testronaut
148
+ testronaut
142
149
  ```
143
150
 
144
151
  Run a specific mission:
145
152
  ```bash
146
- npx testronaut login.mission.js
153
+ testronaut login.mission.js
147
154
  ```
148
155
 
149
156
  Chain missions together:
@@ -161,12 +168,12 @@ await runMissions({
161
168
 
162
169
  Use the staging API base URL:
163
170
  ```bash
164
- npx testronaut --dev
171
+ testronaut --dev
165
172
  ```
166
173
 
167
174
  If the staging deployment is protected by Vercel, pass the bypass secret:
168
175
  ```bash
169
- npx testronaut --dev --vercel-bypass=YOUR_SECRET login
176
+ testronaut --dev --vercel-bypass=YOUR_SECRET login
170
177
  ```
171
178
 
172
179
  You can also set the bypass secret via environment variables:
@@ -175,6 +182,63 @@ export VERCEL_AUTOMATION_BYPASS_SECRET=YOUR_SECRET
175
182
  # or
176
183
  export TESTRONAUT_VERCEL_BYPASS=YOUR_SECRET
177
184
  ```
185
+
186
+ ---
187
+
188
+ ## ๐Ÿ” Automated MFA Codes
189
+
190
+ Testronaut can retrieve a stored TOTP MFA code from the Testronaut API during a mission. This uses the `sessionToken` saved by `testronaut login` in the project root `testronaut-config.json`.
191
+
192
+ The agent will prefer the automated `get_mfa_code` tool when an MFA nickname is known, then fall back to the manual human input tool if the code is unavailable, the entry is missing, the feature is disabled, or the API session cannot access it.
193
+
194
+ If the nickname is missing or does not match exactly, the tool can call the MFA list endpoint to see available nicknames. It will retry simple case, spacing, or punctuation mismatches, such as `rudy poo` matching `rudy-poo`.
195
+
196
+ Add a default MFA nickname to `testronaut-config.json`:
197
+
198
+ ```json
199
+ {
200
+ "sessionToken": "eyJ...",
201
+ "mfaName": "github-test-mfa"
202
+ }
203
+ ```
204
+
205
+ Or pass the nickname for a single run:
206
+
207
+ ```bash
208
+ testronaut login.mission.js -o mfa=github-test-mfa
209
+ ```
210
+
211
+ Mission text can also name the MFA entry:
212
+
213
+ ```js
214
+ export const loginMission = `
215
+ Log in to GitHub.
216
+ When prompted for MFA, use the MFA nickname github-test-mfa.
217
+ `;
218
+ ```
219
+
220
+ Use staging API endpoints with the same developer flag:
221
+
222
+ ```bash
223
+ testronaut --dev login.mission.js -o mfa=github-test-mfa
224
+ ```
225
+
226
+ To inspect MFA API traffic during a run, enable debug logging:
227
+
228
+ ```bash
229
+ testronaut --debug --dev login.mission.js -o mfa=github-test-mfa
230
+ # or
231
+ TESTRONAUT_API_DEBUG=1 testronaut --dev login.mission.js -o mfa=github-test-mfa
232
+ ```
233
+
234
+ This writes sanitized request and response details to `missions/mission_reports/api-debug.log`, including the resolved endpoint URL, status, content type, response keys, body preview, parsed response shape, and list endpoint nicknames. Session tokens, bypass secrets, and MFA code values are redacted.
235
+
236
+ Notes:
237
+ - Run `testronaut login` first so `sessionToken` exists.
238
+ - The CLI calls the API host, not the app host.
239
+ - The MFA API feature flag must be enabled.
240
+ - Paid access is required by the API for paid-gated MFA operations. If the API returns a payment or access error, the mission can still ask for a manual code when human input is enabled.
241
+
178
242
  ---
179
243
 
180
244
  ## ๐Ÿ“‹ Reports
package/TODO.md ADDED
@@ -0,0 +1,15 @@
1
+ # TODO
2
+
3
+ ## Automated MFA Follow-Ups
4
+
5
+ - [ ] Add an integration smoke test against a local or staging API fixture for `get_mfa_code`.
6
+ - [ ] Add a staging smoke-test checklist that covers `--dev`, `--vercel-bypass`, paid access, free-user fallback, missing nickname, and expired session token behavior.
7
+ - [ ] Consider a dedicated `testronaut mfa list` command to verify session token access and available MFA nicknames before running missions.
8
+ - [ ] Consider a lightweight MFA preflight warning when `--dev` is used without `VERCEL_AUTOMATION_BYPASS_SECRET`, `TESTRONAUT_VERCEL_BYPASS`, or `--vercel-bypass`.
9
+ - [ ] Consider allowing `testronaut mfa list/get` to accept `--api-base` for quick diagnostics against staging, preview, or local API hosts.
10
+ - [ ] Track automated MFA outcomes in reports without storing raw codes.
11
+ - [ ] Add explicit report/log redaction assertions for MFA codes, session tokens, and Vercel bypass secrets.
12
+ - [ ] Decide how long `missions/mission_reports/api-debug.log` should be retained and whether it should rotate, append per run, or be copied into each run report bundle.
13
+ - [ ] Decide whether `-o/--options` should support more structured formats once more run options exist.
14
+ - [ ] Revisit whether `request_human_input` should return the code to the model or fill fields through a dedicated browser action.
15
+ - [ ] Document a troubleshooting matrix for MFA failures: premium required, feature disabled, invalid session, missing nickname, non-JSON/app-shell response, not found, rate limited, and network error.
package/bin/cli.js CHANGED
@@ -63,6 +63,27 @@ const DEV_API_BASE = 'https://staging.api.testronaut.app';
63
63
 
64
64
  let args = process.argv.slice(2);
65
65
 
66
+ // Detect how the CLI was invoked so help text matches the actual command
67
+ function detectCliName(npmCommand = process.env.npm_command, argv1 = process.argv[1]) {
68
+ // npx sets npm_command to 'exec'
69
+ if (npmCommand === 'exec') return 'npx testronaut';
70
+ // global install: argv[1] is the bin symlink named 'testronaut'
71
+ if (path.basename(argv1 || '') === 'testronaut') return 'testronaut';
72
+ return 'npx testronaut';
73
+ }
74
+
75
+ function isDirectInvocation(argv1 = process.argv[1], moduleUrl = import.meta.url) {
76
+ if (!argv1) return false;
77
+ const modulePath = fileURLToPath(moduleUrl);
78
+ try {
79
+ return fs.realpathSync(argv1) === fs.realpathSync(modulePath);
80
+ } catch {
81
+ return path.resolve(argv1) === path.resolve(modulePath);
82
+ }
83
+ }
84
+
85
+ const cliName = detectCliName();
86
+
66
87
  // Look for --dev / --developer / --developer-mode / --staging
67
88
  const devFlagIndex = args.findIndex(a =>
68
89
  a === '--dev' ||
@@ -76,6 +97,7 @@ if (devFlagIndex >= 0) {
76
97
  console.log(`๐Ÿงช Developer mode: using ${apiBase}`);
77
98
  args.splice(devFlagIndex, 1);
78
99
  }
100
+ process.env.TESTRONAUT_API_BASE_EFFECTIVE = apiBase;
79
101
 
80
102
  function parseVercelBypassArgs(argsList) {
81
103
  const nextArgs = [...argsList];
@@ -138,6 +160,56 @@ function parseProviderArgs(argsList) {
138
160
  return { provider, args: nextArgs, invalid };
139
161
  }
140
162
 
163
+ function parseRunOptionsArgs(argsList) {
164
+ const nextArgs = [...argsList];
165
+ const options = {};
166
+ let invalid = false;
167
+
168
+ for (let idx = 0; idx < nextArgs.length;) {
169
+ const rawArg = nextArgs[idx];
170
+ const isInline =
171
+ rawArg.startsWith('--options=') ||
172
+ rawArg.startsWith('--option=') ||
173
+ rawArg.startsWith('-o=');
174
+ const isSeparate =
175
+ rawArg === '--options' ||
176
+ rawArg === '--option' ||
177
+ rawArg === '-o';
178
+
179
+ if (!isInline && !isSeparate) {
180
+ idx += 1;
181
+ continue;
182
+ }
183
+
184
+ const rawValue = isInline
185
+ ? rawArg.slice(rawArg.indexOf('=') + 1)
186
+ : nextArgs[idx + 1] && !nextArgs[idx + 1].startsWith('-')
187
+ ? nextArgs[idx + 1]
188
+ : '';
189
+
190
+ if (!rawValue) {
191
+ invalid = true;
192
+ nextArgs.splice(idx, 1);
193
+ continue;
194
+ }
195
+
196
+ for (const part of rawValue.split(',')) {
197
+ const [keyRaw, ...valueParts] = part.split('=');
198
+ const key = String(keyRaw || '').trim();
199
+ const value = valueParts.join('=').trim();
200
+ if (!key || !value) {
201
+ invalid = true;
202
+ continue;
203
+ }
204
+ options[key] = value;
205
+ }
206
+
207
+ nextArgs.splice(idx, isInline ? 1 : 2);
208
+ }
209
+
210
+ return { options, args: nextArgs, invalid };
211
+ }
212
+
141
213
 
142
214
  const vercelBypassResult = parseVercelBypassArgs(args);
143
215
  if (vercelBypassResult.invalid) {
@@ -234,6 +306,9 @@ export const __test__ = {
234
306
  parseVercelBypassArgs,
235
307
  createVercelBypassHeader,
236
308
  parseProviderArgs,
309
+ parseRunOptionsArgs,
310
+ detectCliName,
311
+ isDirectInvocation,
237
312
  };
238
313
 
239
314
  // Look for --model=<id> or --model <id>
@@ -267,6 +342,20 @@ if (providerOverride) {
267
342
  console.log(`๐Ÿงฉ Provider override: ${process.env.TESTRONAUT_PROVIDER}`);
268
343
  }
269
344
 
345
+ const runOptionsResult = parseRunOptionsArgs(args);
346
+ if (runOptionsResult.invalid) {
347
+ console.warn('โš ๏ธ Invalid --options value. Use key=value pairs, for example: -o mfa=github-test-mfa');
348
+ }
349
+ args = runOptionsResult.args;
350
+ const cliMfaName =
351
+ runOptionsResult.options.mfa ||
352
+ runOptionsResult.options.mfaName ||
353
+ runOptionsResult.options['mfa-name'];
354
+ if (cliMfaName) {
355
+ process.env.TESTRONAUT_MFA_NAME = String(cliMfaName).trim();
356
+ console.log(`๐Ÿ” MFA nickname override: ${process.env.TESTRONAUT_MFA_NAME}`);
357
+ }
358
+
270
359
  // Look for --debug / --debug=<bool> / --no-debug
271
360
  // Normalize CLI boolean strings into true/false/null for optional flags.
272
361
  const parseBool = (v) => {
@@ -436,18 +525,19 @@ const HELP_TEXT = `
436
525
  ๐Ÿง‘โ€๐Ÿš€ testronaut - Autonomous Agent Mission Runner
437
526
 
438
527
  Usage:
439
- npx testronaut Run all missions in the ./missions directory
440
- npx testronaut <file> Run a specific mission file (e.g., login.mission.js)
441
- npx testronaut login Log in and store session token
442
- npx testronaut upload Upload the most recent report JSON
443
- npx testronaut serve Serve & open the most recent HTML report (read-only)
444
- npx testronaut view Alias of 'serve'
528
+ ${cliName} Run all missions in the ./missions directory
529
+ ${cliName} <file> Run a specific mission file (e.g., login.mission.js)
530
+ ${cliName} login Log in and store session token
531
+ ${cliName} upload Upload the most recent report JSON
532
+ ${cliName} serve Serve & open the most recent HTML report (read-only)
533
+ ${cliName} view Alias of 'serve'
445
534
 
446
535
  Options:
447
536
  --init Scaffold project folders and a welcome mission
448
537
  --turns=<n> Override max turns for this run (e.g., --turns=30)
449
538
  --debug[=<bool>] Enable verbose debug logs (or set TESTRONAUT_DEBUG=1)
450
539
  --provider=<id> Override LLM provider for this run (e.g., --provider=openai)
540
+ -o, --options key=value Set run options, such as mfa=github-test-mfa
451
541
  --dev Use the staging API base URL
452
542
  --vercel-bypass=<secret> Send Vercel protection bypass header for protected deployments
453
543
  --human-input[=<bool>] Allow the agent to pause for short verification codes (default: true)
@@ -457,13 +547,14 @@ Options:
457
547
  --retry_limit=<n> Override agent turn retry limits (minimum 1, maximum 10)
458
548
 
459
549
  Examples:
460
- npx testronaut
461
- npx testronaut login
462
- npx testronaut upload
463
- npx testronaut serve
464
- npx testronaut --init
550
+ ${cliName}
551
+ ${cliName} login
552
+ ${cliName} upload
553
+ ${cliName} serve
554
+ ${cliName} --init
465
555
  `;
466
556
 
557
+ async function main() {
467
558
  if (args.includes('--init')) {
468
559
  await initializeTestronautProject();
469
560
  await createWelcomeMission();
@@ -494,7 +585,7 @@ Next steps:
494
585
  1. Get an API key from your AI provider
495
586
  2. Add it to your .env file
496
587
  3. Run your first mission:
497
- npx testronaut
588
+ ${cliName}
498
589
 
499
590
  ๐Ÿ“š Docs: https://docs.testronaut.app/docs/guides/cli-auth
500
591
  `);
@@ -611,6 +702,18 @@ fs.mkdirSync(outputDir, { recursive: true });
611
702
  fs.writeFileSync(`${outputDir}/${runId}.json`, JSON.stringify(report, null, 2));
612
703
  generateHtmlReport(report, `${outputDir}/${runId}.html`);
613
704
 
705
+ try {
706
+ if (!process.env.TN_KEEP_TMP && fs.existsSync(TMP_DIR)) {
707
+ fs.rmSync(TMP_DIR, { recursive: true, force: true });
708
+ console.log('๐Ÿงน Cleaned up temporary files.');
709
+ } else {
710
+ console.log('โš ๏ธ Skipped tmp cleanup (TN_KEEP_TMP set).');
711
+ }
712
+ } catch (err) {
713
+ console.warn(`โš ๏ธ Could not remove tmp folder: ${err.message}`);
714
+ }
715
+ }
716
+
614
717
  /**
615
718
  * Parse a fetch Response safely and return JSON or throw a rich error.
616
719
  * @param {Response} res
@@ -1113,13 +1216,6 @@ async function serveLatestReport() {
1113
1216
  await openInBrowser(reportUrl).catch(() => {});
1114
1217
  }
1115
1218
 
1116
- try {
1117
- if (!process.env.TN_KEEP_TMP && fs.existsSync(TMP_DIR)) {
1118
- fs.rmSync(TMP_DIR, { recursive: true, force: true });
1119
- console.log('๐Ÿงน Cleaned up temporary files.');
1120
- } else {
1121
- console.log('โš ๏ธ Skipped tmp cleanup (TN_KEEP_TMP set).');
1122
- }
1123
- } catch (err) {
1124
- console.warn(`โš ๏ธ Could not remove tmp folder: ${err.message}`);
1219
+ if (isDirectInvocation()) {
1220
+ await main();
1125
1221
  }
package/core/agent.js CHANGED
@@ -135,15 +135,28 @@ Use Ground Control as your persistent mission memory about:
135
135
  - Any constraints about staying on the correct site.
136
136
  `.trim();
137
137
 
138
+ systemContent +=
139
+ '\n\n' +
140
+ `
141
+ Verification codes:
142
+ - If the app requires a TOTP/MFA code, first try get_mfa_code when an MFA nickname is known from the mission text, config, or CLI options.
143
+ - Use the returned value promptly. If the app rejects it as expired or invalid, call get_mfa_code once more for a fresh code and retry carefully.
144
+ - Never invent, guess, or reuse placeholder MFA digits. Only enter an MFA value that came from get_mfa_code or request_human_input.
145
+ `.trim();
146
+
138
147
  if (opts.humanInput?.enabled !== false) {
139
148
  systemContent +=
140
- '\n\n' +
149
+ '\n' +
141
150
  `
142
- Human-in-the-loop verification:
143
- - If the app requires a TOTP, SMS, email, or similar short verification code that you cannot retrieve yourself, call request_human_input.
144
- - Do not ask for passwords, API keys, or long free-form text with this tool.
145
- - After receiving the code from the tool result, enter it into the appropriate field and continue the mission.
151
+ - If get_mfa_code returns that MFA is unavailable, not configured, not found, not enabled, or the account lacks access, gracefully fall back to request_human_input when human input is enabled.
152
+ - Use request_human_input for SMS, email, or other short verification codes that cannot be retrieved automatically.
153
+ - Do not ask for passwords, API keys, or long free-form text with request_human_input.
154
+ - After receiving a code from either tool, enter it into the appropriate field and continue the mission.
146
155
  `.trim();
156
+ } else {
157
+ systemContent +=
158
+ '\n' +
159
+ 'Human input is disabled for this run. If get_mfa_code cannot provide a usable MFA code, report a graceful FAILURE with the reason.';
147
160
  }
148
161
 
149
162
  if (groundSummary) {