keepgreen 0.0.0-stage → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release.
6
+
7
+ - `keepgreen explore`: looks at your app (playwright-e2e-mcp `inspect-page`), reads which tests already exist, asks your AI for the most important user flows with no test, and writes a test for each with AutoHeal's `cover`. A test is kept only if it passes the new-test guard (real assertions, no fakes, skips or fixed waits) and runs green several times in a row. One spend cap covers the whole run.
8
+ - `keepgreen heal`: AutoHeal's local `fix` (run the tests, fix what broke, leave the changes for review).
9
+ - `keepgreen init`: writes `.keepgreen.yml`, the explore workflow (read-only explore job + publish job that only accepts new test files) and AutoHeal's heal workflow.
10
+ - `keepgreen doctor`, `keepgreen autoheal <command>`, `keepgreen check-patch`.
11
+ - GitHub Action (`xsignon-secure/keepgreen@v0.1.0`) and a Docker Compose bundle for the self-hosted history server and AutoHeal dashboard.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 XSignOn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,157 @@
1
- # Temporary Holding Version
1
+ # KeepGreen
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Your AI test agent that writes, fixes and keeps your end-to-end tests green.**
4
+
5
+ KeepGreen is free and MIT licensed. It runs on your own CI runner or laptop with your own AI key
6
+ (Anthropic, OpenAI or any OpenAI-compatible endpoint, including local models). There is no
7
+ KeepGreen server, no account and no telemetry. You pay only your AI provider, and one spend cap
8
+ covers every run.
9
+
10
+ It combines two XSignOn projects into one agent:
11
+
12
+ - [playwright-e2e-mcp](https://github.com/xsignon-secure/E2E) is the toolbox. It opens pages,
13
+ finds stable locators, runs tests and reads failures.
14
+ - [AutoHeal](https://github.com/xsignon-secure/AutoHeal-AI) is the guarded fixer and test writer,
15
+ with its anti-cheat guard and stability proof.
16
+
17
+ | Recipe | What it does | Command |
18
+ | --- | --- | --- |
19
+ | **Explore** | Looks at your app, picks the most important user flows that have **no test yet**, and writes a test for each. A test is kept only if it makes real assertions and passes every run (5 by default). | `npx keepgreen explore` |
20
+ | **Heal** | Runs your tests, finds what broke, fixes it and proves the fix. App bugs are reported, not "fixed". | `npx keepgreen heal` (local) · AutoHeal workflow (CI) |
21
+
22
+ Works with Playwright Test and pytest-playwright. Healing also covers Cypress and WebdriverIO.
23
+
24
+ ## Quick start (your machine)
25
+
26
+ ```bash
27
+ npm install -D keepgreen # or use npx keepgreen ... without installing
28
+ export ANTHROPIC_API_KEY=sk-ant-... # your own key
29
+ npx keepgreen init # writes .keepgreen.yml + workflows, checks your setup
30
+ npx keepgreen explore --url http://localhost:3000
31
+ ```
32
+
33
+ Explore prints a table of the flows it tried and which tests it kept. New files are named
34
+ `tests/keepgreen-<flow>.spec.ts`. Review them with `git diff` and commit the ones you like.
35
+
36
+ Try it without writing anything first:
37
+
38
+ ```bash
39
+ npx keepgreen explore --url http://localhost:3000 --dry-run # plan only: shows the flows it would test
40
+ ```
41
+
42
+ Let KeepGreen start your app:
43
+
44
+ ```bash
45
+ npx keepgreen explore --start-command "npm run dev" --start-url http://localhost:3000
46
+ ```
47
+
48
+ Fix broken tests:
49
+
50
+ ```bash
51
+ npx keepgreen heal # = AutoHeal's `autoheal fix`; changes stay in your working tree for review
52
+ ```
53
+
54
+ ## In GitHub Actions
55
+
56
+ `npx keepgreen init` writes two workflows:
57
+
58
+ - **`.github/workflows/keepgreen-explore.yml`** runs from *Actions > KeepGreen explore > Run
59
+ workflow*, or weekly if you uncomment the schedule. It opens **one PR** with the new tests that
60
+ passed. It has two jobs. The explore job runs the AI-written tests with a read-only token. The
61
+ publish job accepts the result only if it just adds test files, then opens the PR.
62
+ - **`.github/workflows/autoheal.yml`** is written by AutoHeal's own `init`. It runs after your
63
+ test workflow fails and opens a fix PR.
64
+
65
+ Then:
66
+
67
+ 1. Add `ANTHROPIC_API_KEY` as a repository secret. For OpenAI, add `OPENAI_API_KEY` and a
68
+ repository variable `AUTOHEAL_MODEL`.
69
+ 2. Go to *Settings > Actions > General > Workflow permissions* and tick "Allow GitHub Actions to
70
+ create and approve pull requests".
71
+ 3. Make the explore job's install steps match your test workflow (Node or Python, browsers,
72
+ services).
73
+
74
+ You can also use the action directly:
75
+
76
+ ```yaml
77
+ - uses: xsignon-secure/keepgreen@v0.1.0
78
+ with:
79
+ url: http://localhost:3000
80
+ anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
81
+ # leaves keepgreen-out/ (report.md, summary.json, new-tests.patch); output `written` = tests kept
82
+ ```
83
+
84
+ ## Settings
85
+
86
+ `.keepgreen.yml` at the repo root. Command-line flags override it.
87
+
88
+ ```yaml
89
+ url: http://localhost:3000 # the running app, or:
90
+ # start-command: npm run dev
91
+ # start-url: http://localhost:3000
92
+ pages: # extra pages to look at when choosing flows
93
+ - /login
94
+ - /pricing
95
+ max-flows: 3 # new tests per explore run (1-10)
96
+ max-usd: 3 # AI spend cap for one explore run
97
+ runs: 5 # passing runs in a row a new test needs (2-20)
98
+ framework: auto # auto | playwright | pytest | cypress | webdriverio
99
+ ```
100
+
101
+ AI settings use AutoHeal's variables:
102
+
103
+ | Variable | Meaning |
104
+ | --- | --- |
105
+ | `ANTHROPIC_API_KEY` | Use Anthropic (default model `claude-opus-5-5`) |
106
+ | `OPENAI_API_KEY` + `AUTOHEAL_MODEL` | Use OpenAI |
107
+ | `AUTOHEAL_BASE_URL` + `AUTOHEAL_MODEL` | Any OpenAI-compatible endpoint, e.g. Ollama at `http://localhost:11434/v1` |
108
+ | `AUTOHEAL_EFFORT` | Anthropic effort: `low` … `max` (default `medium`) |
109
+ | `AUTOHEAL_PRICE_IN` / `AUTOHEAL_PRICE_OUT` | USD per 1M tokens, for models without a listed price |
110
+
111
+ ## How explore works
112
+
113
+ 1. **Look.** It opens your app's start page and every path in `pages:` with playwright-e2e-mcp's
114
+ `inspect-page`, which lists the interactive elements and their locators.
115
+ 2. **Plan.** It reads the titles of your existing tests and asks your AI for the most important
116
+ user flows that have no test yet. Flows that pay money, send email or delete data are skipped.
117
+ 3. **Write.** For each flow, AutoHeal's `cover` explores the pages, checks every locator, and
118
+ writes one test in your project's own style.
119
+ 4. **Prove.** The new-test guard rejects tests that cannot fail, or that use skips, faked network
120
+ answers, fixed waits, forced clicks or retries. The test must then pass every run in a row.
121
+ Anything less is thrown away.
122
+
123
+ One `max-usd` cap covers the whole run. Each flow gets an even share of what is left, so money one
124
+ flow saves goes to the next.
125
+
126
+ ## All commands
127
+
128
+ ```
129
+ keepgreen init [--force] [--dry-run]
130
+ keepgreen doctor
131
+ keepgreen explore [--url <app>] [--start-command <cmd> --start-url <url>] [--pages /a,/b]
132
+ [--max-flows 3] [--max-usd 3] [--runs 5] [--dry-run] [--out <dir>]
133
+ keepgreen heal [AutoHeal fix options]
134
+ keepgreen autoheal <command> [...] # any AutoHeal command: learn, metrics, dashboard, ...
135
+ keepgreen check-patch <file> # CI publish step
136
+ keepgreen --version
137
+ ```
138
+
139
+ ## Self-host bundle (optional)
140
+
141
+ KeepGreen needs no server. If you want **test history across all CI runs** (flaky ranking, the
142
+ commit where a test went bad) and **AutoHeal's dashboard** (fixes, blocked cheats, AI spend),
143
+ [`selfhost/`](selfhost/) starts both servers with one command:
144
+
145
+ ```bash
146
+ cd selfhost && sh make-env.sh your-org/your-repo && docker compose up -d --build
147
+ ```
148
+
149
+ ## Security
150
+
151
+ See [SECURITY.md](SECURITY.md). In short: your key goes only to your AI provider, AI-written tests
152
+ never run with a write token or your AI key, the publish step only accepts new test files, and
153
+ every change arrives as a PR for a person to review.
154
+
155
+ ## License
156
+
157
+ MIT. Free for any use, with no paid tier.
package/SECURITY.md ADDED
@@ -0,0 +1,41 @@
1
+ # Security
2
+
3
+ KeepGreen runs an AI model against your app and your tests, so it is built on the assumption that
4
+ the model can be wrong or manipulated. Text on a page or in a test file could try to instruct it;
5
+ every prompt marks such text as data.
6
+
7
+ ## What protects you
8
+
9
+ - **Your key, your runner.** KeepGreen has no server, no account and no telemetry. The only
10
+ network calls go to the AI provider you choose, your own app, and (in CI) GitHub to open the PR.
11
+ - **Two jobs in CI.** The explore job, where AI-written tests run, has a read-only token and
12
+ checks out with `persist-credentials: false`. It uploads the new tests as a patch. The publish
13
+ job never runs your project's code. `keepgreen check-patch` accepts the patch only if it just
14
+ **adds** plain test files (`*.spec.ts`, `*.test.js`, `*.cy.ts`, `test_*.py`, ...): no changes
15
+ to existing files, no deletes, renames, mode changes or binary data, nothing under hidden
16
+ folders such as `.github/`, nothing in `node_modules`, at most 10 files. Then git applies it
17
+ with hooks off and the PR is opened.
18
+ - **Secrets stay out of test runs.** New tests, and an app started with `start-command`, run in a
19
+ child process without the AI key or any GitHub / Actions token (AutoHeal's scrubbed engine
20
+ environment).
21
+ - **New-test guard.** AutoHeal's `scanNewTest` rejects tests without real assertions, or with
22
+ skips, faked network answers (`page.route`), injected scripts, fixed waits, `force: true`,
23
+ extra retries or timeouts, file, environment or network access. A test must then pass every
24
+ one of `runs` runs in a row. The scans are pattern based, so every test still arrives as a PR
25
+ for a person to review.
26
+ - **Pages stay on your app.** Planning only opens the app URL and the paths you list in
27
+ `pages:`; `cover` only opens pages on the same origin.
28
+ - **Spend cap.** One `max-usd` limit covers a whole explore run (planning plus every flow).
29
+
30
+ ## Your part
31
+
32
+ - Use an AI key with a spend limit; it is present in the explore job.
33
+ - Run explore on your own branches only. The template runs on `workflow_dispatch` and an
34
+ optional schedule, never on pull requests from forks.
35
+ - Review every KeepGreen PR like any other change.
36
+ - For the self-host bundle, put the servers behind HTTPS and keep `.env` out of git.
37
+
38
+ ## Reporting a vulnerability
39
+
40
+ Please use GitHub's private vulnerability reporting on this repository
41
+ (Security > Report a vulnerability). Do not open a public issue.
package/action.yml ADDED
@@ -0,0 +1,102 @@
1
+ name: KeepGreen explore
2
+ description: Free, self-hosted AI test agent. Finds important user flows with no test and writes Playwright tests for them, with your own AI key.
3
+ author: xsignon-secure
4
+ branding:
5
+ icon: check-circle
6
+ color: green
7
+
8
+ # Runs `keepgreen explore` and leaves keepgreen-out/ (report.md, summary.json,
9
+ # new-tests.patch) for an upload step. It opens no PR itself: the template's
10
+ # publish job does that with a separate, write-only step. See
11
+ # templates/keepgreen-explore.yml.
12
+
13
+ inputs:
14
+ url:
15
+ description: App URL to explore (blank = url in .keepgreen.yml)
16
+ required: false
17
+ default: ""
18
+ start-command:
19
+ description: Command that starts your app (with start-url)
20
+ required: false
21
+ default: ""
22
+ start-url:
23
+ description: Address to wait for after start-command
24
+ required: false
25
+ default: ""
26
+ max-flows:
27
+ description: New tests to try (1-10; blank = .keepgreen.yml or 3)
28
+ required: false
29
+ default: ""
30
+ max-usd:
31
+ description: AI spend cap for the run in USD (blank = .keepgreen.yml or 3)
32
+ required: false
33
+ default: ""
34
+ runs:
35
+ description: Passing runs in a row a new test needs (blank = .keepgreen.yml or 5)
36
+ required: false
37
+ default: ""
38
+ anthropic-api-key:
39
+ description: Anthropic API key
40
+ required: false
41
+ default: ""
42
+ openai-api-key:
43
+ description: OpenAI API key (also set model)
44
+ required: false
45
+ default: ""
46
+ model:
47
+ description: Model id (optional for Anthropic)
48
+ required: false
49
+ default: ""
50
+ working-directory:
51
+ description: Project folder
52
+ required: false
53
+ default: "."
54
+ version:
55
+ description: keepgreen version from npm
56
+ required: false
57
+ default: "0.1.0"
58
+
59
+ outputs:
60
+ written:
61
+ description: Number of new tests kept
62
+ value: ${{ steps.run.outputs.written }}
63
+
64
+ runs:
65
+ using: composite
66
+ steps:
67
+ - id: run
68
+ shell: bash
69
+ working-directory: ${{ inputs.working-directory }}
70
+ env:
71
+ ANTHROPIC_API_KEY: ${{ inputs.anthropic-api-key }}
72
+ OPENAI_API_KEY: ${{ inputs.openai-api-key }}
73
+ AUTOHEAL_MODEL: ${{ inputs.model }}
74
+ KG_VERSION: ${{ inputs.version }}
75
+ KG_URL: ${{ inputs.url }}
76
+ KG_START_COMMAND: ${{ inputs.start-command }}
77
+ KG_START_URL: ${{ inputs.start-url }}
78
+ KG_MAX_FLOWS: ${{ inputs.max-flows }}
79
+ KG_MAX_USD: ${{ inputs.max-usd }}
80
+ KG_RUNS: ${{ inputs.runs }}
81
+ OUT: ${{ github.workspace }}/keepgreen-out
82
+ run: |
83
+ [ -n "$ANTHROPIC_API_KEY" ] || unset ANTHROPIC_API_KEY
84
+ [ -n "$OPENAI_API_KEY" ] || unset OPENAI_API_KEY
85
+ [ -n "$AUTOHEAL_MODEL" ] || unset AUTOHEAL_MODEL
86
+ if ! [[ "$KG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::version must be like 1.2.3"; exit 2; fi
87
+ args=(explore --out "$OUT")
88
+ [ -n "$KG_URL" ] && args+=(--url "$KG_URL")
89
+ [ -n "$KG_START_COMMAND" ] && args+=(--start-command "$KG_START_COMMAND")
90
+ [ -n "$KG_START_URL" ] && args+=(--start-url "$KG_START_URL")
91
+ [ -n "$KG_MAX_FLOWS" ] && args+=(--max-flows "$KG_MAX_FLOWS")
92
+ [ -n "$KG_MAX_USD" ] && args+=(--max-usd "$KG_MAX_USD")
93
+ [ -n "$KG_RUNS" ] && args+=(--runs "$KG_RUNS")
94
+ set +e
95
+ npx -y "keepgreen@$KG_VERSION" "${args[@]}"
96
+ code=$?
97
+ set -e
98
+ written=0
99
+ [ -f "$OUT/summary.json" ] && written="$(node -p 'require(process.env.OUT + "/summary.json").written.length')"
100
+ echo "written=$written" >> "$GITHUB_OUTPUT"
101
+ # 1 = ran fine but kept no test; that is not a failed job.
102
+ if [ "$code" -gt 1 ] || { [ "$code" -eq 1 ] && [ ! -f "$OUT/summary.json" ]; }; then exit "$code"; fi
package/dist/cli.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * keepgreen: one command for the whole agent.
4
+ * explore writes new tests (KeepGreen's own recipe, built on AutoHeal's cover)
5
+ * heal fixes broken tests on your machine (AutoHeal's `fix`)
6
+ * init writes .keepgreen.yml, the explore workflow and AutoHeal's heal workflow
7
+ * doctor checks the setup
8
+ */
9
+ export declare const USAGE: string;
10
+ /** Path of AutoHeal's CLI inside the installed xsignon-autoheal package. */
11
+ export declare function autohealCli(): string;
12
+ export declare function main(argv: string[]): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * keepgreen: one command for the whole agent.
4
+ * explore writes new tests (KeepGreen's own recipe, built on AutoHeal's cover)
5
+ * heal fixes broken tests on your machine (AutoHeal's `fix`)
6
+ * init writes .keepgreen.yml, the explore workflow and AutoHeal's heal workflow
7
+ * doctor checks the setup
8
+ */
9
+ import { spawnSync } from 'node:child_process';
10
+ import { realpathSync } from 'node:fs';
11
+ import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
12
+ import path from 'node:path';
13
+ import { fileURLToPath, pathToFileURL } from 'node:url';
14
+ import { parseArgs } from 'node:util';
15
+ import { ConfigError, CypressTools, McpTools, WdioTools, checkAppUrl, cover, frameworkSetting, isCypressProject, isPytestProject, isWdioProject, priceFor, providerFromEnv, startApp, } from 'xsignon-autoheal';
16
+ import { ConfigFileError, loadConfigFile, resolveConfig } from './config.js';
17
+ import { existingTitles, explore, exploreReport } from './explore.js';
18
+ import { init } from './init.js';
19
+ import { newFilesPatch } from './newfiles.js';
20
+ import { checkPatch } from './patch.js';
21
+ import { VERSION } from './version.js';
22
+ export const USAGE = `keepgreen ${VERSION}: free, self-hosted AI test agent. It writes new end-to-end tests
23
+ for your app and fixes the ones that break, with your own AI key (Anthropic, OpenAI or any
24
+ OpenAI-compatible endpoint). Built on playwright-e2e-mcp and AutoHeal. MIT, no account, no telemetry.
25
+
26
+ keepgreen init [--force] [--dry-run]
27
+ write .keepgreen.yml, the explore workflow and AutoHeal's heal workflow (GitHub Actions)
28
+ keepgreen doctor
29
+ check this project and machine
30
+ keepgreen explore [--url <app>] [--start-command <cmd> --start-url <url>] [--pages /a,/b]
31
+ [--max-flows 3] [--max-usd 3] [--runs 5] [--dry-run] [--out <dir>]
32
+ find important user flows with no test yet and write a test for each; a test is kept
33
+ only if it makes real assertions and passes --runs times in a row
34
+ keepgreen heal [AutoHeal fix options]
35
+ run your tests, fix what broke, leave the changes for you to review
36
+ keepgreen autoheal <command> [...]
37
+ run any AutoHeal command (heal, publish, learn, metrics, dashboard, ...)
38
+ keepgreen check-patch <file>
39
+ CI publish step: accept a patch only if it just adds test files
40
+ keepgreen --version
41
+
42
+ Settings come from .keepgreen.yml; flags win. The AI key comes from ANTHROPIC_API_KEY,
43
+ OPENAI_API_KEY (+ AUTOHEAL_MODEL) or AUTOHEAL_BASE_URL (+ AUTOHEAL_MODEL).
44
+ `;
45
+ const log = (line) => process.stderr.write(`[keepgreen] ${line}\n`);
46
+ /** Path of AutoHeal's CLI inside the installed xsignon-autoheal package. */
47
+ export function autohealCli() {
48
+ const entry = fileURLToPath(import.meta.resolve('xsignon-autoheal'));
49
+ return path.join(path.dirname(entry), 'cli.js');
50
+ }
51
+ function runAutoheal(args) {
52
+ const r = spawnSync(process.execPath, [autohealCli(), ...args], { stdio: 'inherit' });
53
+ if (r.error)
54
+ throw r.error;
55
+ return r.status ?? 1;
56
+ }
57
+ async function detectFramework(root, value) {
58
+ let setting;
59
+ try {
60
+ setting = frameworkSetting(value ?? process.env.AUTOHEAL_FRAMEWORK);
61
+ }
62
+ catch (error) {
63
+ throw new ConfigError(error instanceof Error ? error.message : String(error));
64
+ }
65
+ if (setting !== 'auto')
66
+ return setting;
67
+ if (await isWdioProject(root))
68
+ return 'webdriverio';
69
+ if (await isCypressProject(root))
70
+ return 'cypress';
71
+ return (await isPytestProject(root)) ? 'pytest' : 'playwright';
72
+ }
73
+ async function cmdExplore(argv) {
74
+ const { values } = parseArgs({
75
+ args: argv,
76
+ options: {
77
+ url: { type: 'string' },
78
+ 'start-command': { type: 'string' },
79
+ 'start-url': { type: 'string' },
80
+ pages: { type: 'string' },
81
+ 'max-flows': { type: 'string' },
82
+ 'max-usd': { type: 'string' },
83
+ runs: { type: 'string' },
84
+ framework: { type: 'string' },
85
+ project: { type: 'string' },
86
+ out: { type: 'string' },
87
+ 'dry-run': { type: 'boolean' },
88
+ },
89
+ strict: true,
90
+ });
91
+ const projectRoot = path.resolve(values.project ?? '.');
92
+ const { project: _p, out: _o, 'dry-run': _d, ...flags } = values;
93
+ const config = resolveConfig(await loadConfigFile(projectRoot), flags);
94
+ const env = process.env;
95
+ const provider = providerFromEnv(env);
96
+ if (!provider)
97
+ throw new ConfigError('keepgreen explore needs an AI key: set ANTHROPIC_API_KEY, OPENAI_API_KEY (and AUTOHEAL_MODEL) or AUTOHEAL_BASE_URL.');
98
+ const framework = await detectFramework(projectRoot, config.framework);
99
+ let app = null;
100
+ let appUrl = config.url;
101
+ let engine = null;
102
+ let runner = null;
103
+ try {
104
+ if (config.startCommand) {
105
+ const url = config.startUrl ?? config.url;
106
+ if (!url)
107
+ throw new ConfigError('start-command needs start-url: the address to wait for.');
108
+ // startApp gives your app the tests' scrubbed environment: no AI key, no CI tokens.
109
+ app = await startApp({ command: config.startCommand, url, cwd: projectRoot, sandbox: null, log });
110
+ appUrl = app.url;
111
+ }
112
+ if (!appUrl)
113
+ throw new ConfigError('Tell KeepGreen where your app runs: --url http://localhost:3000, or url: in .keepgreen.yml (or start-command + start-url).');
114
+ checkAppUrl(appUrl);
115
+ log(`KeepGreen ${VERSION}: exploring ${appUrl} (${framework}, ${provider.name} ${provider.model}, limit $${config.maxUsd})`);
116
+ engine = await McpTools.start(projectRoot);
117
+ runner = framework === 'cypress' ? new CypressTools(projectRoot, { log }) : framework === 'webdriverio' ? new WdioTools(projectRoot, { log }) : engine;
118
+ const result = await explore({
119
+ projectRoot,
120
+ provider,
121
+ engine,
122
+ runner,
123
+ appUrl,
124
+ pages: config.pages,
125
+ existing: await existingTitles(projectRoot),
126
+ maxFlows: config.maxFlows,
127
+ maxUsd: config.maxUsd,
128
+ runs: config.runs,
129
+ price: priceFor(provider.model, env),
130
+ framework,
131
+ cover,
132
+ log,
133
+ dryRun: values['dry-run'] === true,
134
+ });
135
+ const report = exploreReport(result, config.runs);
136
+ process.stdout.write(`\n${report}\n`);
137
+ const written = result.results.filter((r) => r.result.status === 'written').map((r) => r.result.file);
138
+ if (values.out) {
139
+ const out = path.resolve(values.out);
140
+ await mkdir(out, { recursive: true });
141
+ await writeFile(path.join(out, 'report.md'), `${report}\n`);
142
+ await writeFile(path.join(out, 'summary.json'), `${JSON.stringify({ version: VERSION, written, flows: result.flows, usd: result.usd }, null, 2)}\n`);
143
+ await writeFile(path.join(out, 'new-tests.patch'), await newFilesPatch(projectRoot, written));
144
+ log(`Wrote ${path.relative(process.cwd(), out) || '.'}/report.md, summary.json and new-tests.patch`);
145
+ }
146
+ if (env.GITHUB_STEP_SUMMARY)
147
+ await appendFile(env.GITHUB_STEP_SUMMARY, `${report}\n`).catch(() => undefined);
148
+ return values['dry-run'] || written.length ? 0 : 1;
149
+ }
150
+ finally {
151
+ if (runner && runner !== engine)
152
+ await runner.close().catch(() => undefined);
153
+ await engine?.close();
154
+ await app?.stop().catch(() => undefined);
155
+ }
156
+ }
157
+ async function cmdInit(argv) {
158
+ const { values } = parseArgs({ args: argv, options: { project: { type: 'string' }, force: { type: 'boolean' }, 'dry-run': { type: 'boolean' } }, strict: true });
159
+ const root = path.resolve(values.project ?? '.');
160
+ const dryRun = values['dry-run'] === true;
161
+ const r = await init(root, { force: values.force === true, dryRun });
162
+ const out = [`KeepGreen ${VERSION}`, ''];
163
+ for (const w of r.written)
164
+ out.push(`${dryRun ? 'would write' : 'wrote'} ${w}`);
165
+ for (const w of r.skipped)
166
+ out.push(`kept ${w} (exists; --force to overwrite)`);
167
+ out.push('', 'Now AutoHeal sets up healing:', '');
168
+ process.stdout.write(out.join('\n'));
169
+ const code = runAutoheal(['init', '--ci', 'github', '--project', root, ...(values.force ? ['--force'] : []), ...(dryRun ? ['--dry-run'] : [])]);
170
+ process.stdout.write([
171
+ '',
172
+ 'For KeepGreen explore, also:',
173
+ ' 1. Set url: in .keepgreen.yml (or start-command + start-url) so KeepGreen can reach your app.',
174
+ ' 2. Add ANTHROPIC_API_KEY (or OPENAI_API_KEY + the AUTOHEAL_MODEL variable) as a repository secret.',
175
+ ' 3. Settings > Actions > General: allow GitHub Actions to create pull requests.',
176
+ ' 4. Run it: Actions > KeepGreen explore > Run workflow (or `npx keepgreen explore` on your machine).',
177
+ '',
178
+ ].join('\n'));
179
+ return code;
180
+ }
181
+ async function cmdDoctor(argv) {
182
+ const { values } = parseArgs({ args: argv, options: { project: { type: 'string' } }, strict: true });
183
+ const root = path.resolve(values.project ?? '.');
184
+ const lines = [`KeepGreen ${VERSION} doctor`, ''];
185
+ let failed = false;
186
+ try {
187
+ const config = resolveConfig(await loadConfigFile(root), {});
188
+ const target = config.startCommand ? `start-command "${config.startCommand}" then ${config.startUrl ?? config.url ?? '(no start-url!)'}` : (config.url ?? null);
189
+ lines.push(target ? `ok explore target: ${target}` : 'warn no url in .keepgreen.yml: pass --url to keepgreen explore');
190
+ if (config.startCommand && !(config.startUrl ?? config.url))
191
+ failed = true;
192
+ }
193
+ catch (error) {
194
+ failed = true;
195
+ lines.push(`fail .keepgreen.yml: ${error instanceof Error ? error.message : String(error)}`);
196
+ }
197
+ lines.push('', 'AutoHeal checks (keys, framework, CI):', '');
198
+ process.stdout.write(lines.join('\n'));
199
+ const code = runAutoheal(['doctor', '--project', root]);
200
+ return failed ? 1 : code;
201
+ }
202
+ async function cmdCheckPatch(argv) {
203
+ const file = argv[0];
204
+ if (!file || argv.length > 1)
205
+ throw new ConfigError('Usage: keepgreen check-patch <file>');
206
+ const result = checkPatch(await readFile(file, 'utf8'));
207
+ if (result.ok) {
208
+ process.stdout.write(`${result.files.join('\n')}\n`);
209
+ return 0;
210
+ }
211
+ log(result.files.length || result.problems.length ? `Patch refused:\n ${(result.problems.length ? result.problems : ['no files']).join('\n ')}` : 'Patch is empty.');
212
+ return 1;
213
+ }
214
+ export async function main(argv) {
215
+ const [command, ...rest] = argv;
216
+ try {
217
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
218
+ process.stdout.write(USAGE);
219
+ return command ? 0 : 2;
220
+ }
221
+ if (command === '--version' || command === '-v' || command === 'version') {
222
+ process.stdout.write(`${VERSION}\n`);
223
+ return 0;
224
+ }
225
+ if (command === 'explore')
226
+ return await cmdExplore(rest);
227
+ if (command === 'heal')
228
+ return runAutoheal(['fix', ...rest]);
229
+ if (command === 'autoheal')
230
+ return runAutoheal(rest);
231
+ if (command === 'init')
232
+ return await cmdInit(rest);
233
+ if (command === 'doctor')
234
+ return await cmdDoctor(rest);
235
+ if (command === 'check-patch')
236
+ return await cmdCheckPatch(rest);
237
+ process.stderr.write(`Unknown command "${command}".\n\n${USAGE}`);
238
+ return 2;
239
+ }
240
+ catch (error) {
241
+ if (error instanceof ConfigError || error instanceof ConfigFileError || error.code?.startsWith('ERR_PARSE_ARGS')) {
242
+ log(error.message);
243
+ return 2;
244
+ }
245
+ log(error instanceof Error ? (error.stack ?? error.message) : String(error));
246
+ return 1;
247
+ }
248
+ }
249
+ const invoked = process.argv[1] ? pathToFileURL(realpathSync(process.argv[1])).href : '';
250
+ if (invoked === import.meta.url) {
251
+ main(process.argv.slice(2)).then((code) => {
252
+ process.exitCode = code;
253
+ });
254
+ }
255
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `.keepgreen.yml`: the one settings file. It is a small, flat YAML subset
3
+ * (key: value lines, and `pages:` as a list of `- /path` lines), so it needs
4
+ * no YAML library. Command-line flags win over the file.
5
+ *
6
+ * url: http://localhost:3000 # the running app to explore
7
+ * start-command: npm run dev # or: start it for me
8
+ * start-url: http://localhost:3000 # address to wait for after start-command
9
+ * pages: # extra pages to look at when planning
10
+ * - /login
11
+ * - /pricing
12
+ * max-flows: 3 # new tests per explore run
13
+ * max-usd: 3 # AI spend cap for one explore run
14
+ * runs: 5 # passing runs in a row a new test needs
15
+ * framework: auto # auto | playwright | pytest | cypress | webdriverio
16
+ */
17
+ export declare const CONFIG_FILE = ".keepgreen.yml";
18
+ export interface KeepGreenConfig {
19
+ url?: string;
20
+ startCommand?: string;
21
+ startUrl?: string;
22
+ pages: string[];
23
+ maxFlows: number;
24
+ maxUsd: number;
25
+ runs: number;
26
+ framework?: string;
27
+ }
28
+ export declare const DEFAULTS: KeepGreenConfig;
29
+ export declare class ConfigFileError extends Error {
30
+ constructor(message: string);
31
+ }
32
+ export declare function parseConfig(text: string): Record<string, string | string[]>;
33
+ /** Merges defaults, the file and flags (flags win), and checks the values. */
34
+ export declare function resolveConfig(file: Record<string, string | string[]>, flags: Record<string, string | undefined>): KeepGreenConfig;
35
+ export declare function loadConfigFile(projectRoot: string): Promise<Record<string, string | string[]>>;