Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,18 @@ jobs:
- run: npm ci
- run: npm run check
- run: npm run test:package
smoke-platforms:
name: Live smoke isolation (${{ matrix.os }}, Node ${{ matrix.node }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest]
node: [22, 24]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm run build
- run: npx vitest run tests/scripts/smokeLive.test.ts tests/scripts/smokeLive.integration.test.ts
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,34 @@ The published and deployed checks are release gates. They are expected to fail b
published or the documentation deployment reaches production. The scenario catalog is stored in
`evals/claude-code-name-lookup.json` so the same prompt variants remain visible and reviewable.

### Testing

```bash
npm test # Unit and behavioral tests with a mocked API (no credentials)
npm run check # Typecheck, build, tests and the public artifact guard
DM_API_URL=https://api-staging.v2.dealmachine.com/v1 DM_API_KEY=<key> npm run smoke:live:coverage
```

`npm test` never calls an API. `smoke:live:coverage` builds the CLI and runs the real `dm` binary against
the API named by `DM_API_URL`, authenticated with `DM_API_KEY`. Both are required; the key is never
printed. Each Command runs with a temporary home directory, so the smoke never reads or changes
your own `dm login` credentials. It runs only read-only, credit-free Commands:

| Command | Checks |
| ----------------------------------------------------------- | ---------------------------------------------------------- |
| `dm --version` | Prints the package version without a network request |
| `dm account --json` | Organization, user auth type and plan are present |
| `dm filters --source-type properties --per-page 5 --json` | Filters have IDs, names, operators and pagination |
| `dm fields --source-type properties --per-page 5 --json` | Fields have IDs, names and pagination |
| `dm properties search --body <ZIP 78704> --estimate-cost --json` | Returns an estimate with no records and no charged credits |

Requests pass through a local relay that refuses anything outside those read-only Endpoints
(property search must carry `estimate_cost: true`) and confirms each request sends
`X-DealMachine-Source: cli` and the CLI `User-Agent`. The smoke prints one `PASS` or `FAIL` line per
Command and exits 0 only when every Command passes.

`npm run smoke:live` retains the existing three-Command Factory baseline for account identity, field discovery and a credit-free estimate. The expanded coverage command is separate so that the registered baseline and its packed-install checks keep their existing behavior.

### Standalone Binary

The compiled `dist/index.js` includes a `#!/usr/bin/env node` shebang and is declared in `package.json` under `bin.dm`. When installed globally via npm, it becomes available as `dm` on the PATH.
Expand Down
6 changes: 4 additions & 2 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ npm run test:package
npm run dev
```

`build` creates JavaScript, declarations and source maps in `dist`, cleaning old output first. It bundles `playbook/PLAYBOOK.md` for offline agent onboarding. `dev` watches TypeScript; run `build` once before the watcher and again after changing the Playbook. From a second terminal, run `npm start -- --help` or `npm start -- agents playbook`.
`build` creates JavaScript, declarations and source maps in `dist`, cleaning old output first. It runs the installed TypeScript compiler through Node so it also works on Windows without relying on a shell shim. It bundles `playbook/PLAYBOOK.md` for offline agent onboarding. `dev` watches TypeScript; run `build` once before the watcher and again after changing the Playbook. From a second terminal, run `npm start -- --help` or `npm start -- agents playbook`.

The [Command reference](commands.md) describes CLI usage and JSON output. Tests under `tests/` exercise request behavior and output. `test:package` installs both npm archives into an empty temporary project and checks the executable, module import and project-local Claude Code Playbook installation. It does not write to your personal agent setup or call the API.

For local API work, start the API in its owning checkout, then use `DM_API_URL=http://localhost:3001/v1 npm start -- account`. Supply your development API key through the approved local environment or `dm login`; never commit it. Normal local build and tests need no credentials. Production Commands can read or mutate live data and consume credits, so choose an environment deliberately.
For local API work, start the API in its owning checkout, then use `DM_API_URL=http://localhost:3001/v1 npm start -- account`. Supply your development API key through the approved local environment or `dm login`; never commit it. Normal local build and tests need no credentials. `npm run smoke:live:coverage` runs the built binary against a real API with `DM_API_URL` and `DM_API_KEY`, using only read-only, credit-free Commands and a temporary home directory; see the README's Testing section. Production Commands can read or mutate live data and consume credits, so choose an environment deliberately.

The public agent plugin manifests and `skills/dealmachine` are retained in this repository. The hosted MCP server is a separate service. This extraction does not make MCP implementation or docs-site deployment part of CLI publication.

Expand All @@ -28,3 +28,5 @@ Every `master` push runs the production npm workflow; its publishing job stays s
`npm run smoke:live` requires explicit `DM_API_URL` ending in `/v1` and `DM_API_KEY` for an approved QA workspace. It builds this candidate and runs real Commands for account identity, field discovery and a credit-free property estimate. It isolates configuration and the credential store, removes its temporary files and does not read your saved login. Missing configuration exits 2; a failed request or response assertion exits 1. It does not prove writes, paid data, OAuth consent, every Command or the published npm version.

Factory registers these as `cli:test:package` and `cli:smoke:live`. Keep executable checks here and the shared regression case, reviewed source version, runner delivery and private credentials in Factory. When adding or changing a Command, assess both suites and update the relevant public behavior check alongside its acceptance criteria. A local fixture pass never substitutes for an authenticated live result.

`npm run smoke:live:coverage` adds version and filter checks plus request-header and read-only relay assertions. It requires the same approved QA target and key, rejects non-HTTPS remote targets and non-plain `/v1` bases, and explicitly disables the OS credential store in child processes. Its built-CLI integration test uses a synthetic local API and runs in CI on Linux, macOS and Windows with Node 22 and 24. It checks missing credentials, request authentication, unchanged caller settings and temporary-directory cleanup, including paths with spaces and `#`. The isolation module is loaded by file URL so Windows drive letters and URL-significant path characters work. Authenticated live staging remains a separate operator check with the approved QA workspace reserved for the run; CI uses no live credentials.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"eval:cold-start:published": "node scripts/eval-cold-start.mjs published",
"eval:cold-start:deployed": "node scripts/eval-cold-start.mjs deployed",
"eval:cold-start:all": "npm run build && node scripts/eval-cold-start.mjs all",
"smoke:live": "npm run build && node scripts/smoke-live.mjs",
"prepublishOnly": "npm run check",
"typecheck": "tsc --noEmit --composite false --incremental false",
"check": "npm run typecheck && npm run build && npm test && npm run check:artifact",
Expand All @@ -41,7 +42,7 @@
"release:auto:dry-run": "node scripts/release-auto.mjs dry-run",
"release:auto:publish": "node scripts/release-auto.mjs publish",
"prepack": "npm run build && npm run check:artifact",
"smoke:live": "npm run build && node scripts/smoke-live.mjs"
"smoke:live:coverage": "npm run build && node scripts/smoke-live-coverage.mjs"
},
"dependencies": {
"chalk": "^5.3.0",
Expand Down
4 changes: 3 additions & 1 deletion scripts/build.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { rmSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';

const cwd = fileURLToPath(new URL('..', import.meta.url));
// A previous private build must never leave files in the public npm artifact.
rmSync(new URL('../dist', import.meta.url), { recursive: true, force: true });
execFileSync('tsc', ['--build', '--force'], { cwd, stdio: 'inherit' });
const compiler = createRequire(import.meta.url).resolve('typescript/bin/tsc');
execFileSync(process.execPath, [compiler, '--build', '--force'], { cwd, stdio: 'inherit' });
await import('./copy-agent-assets.mjs');
Loading
Loading