Skip to content
Merged
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
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,32 @@ 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.

### 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
4 changes: 3 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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.

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 routine CI on Ubuntu 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. Factory runs the registered live smoke against staging with its approved QA workspace reserved for the run. Full macOS and Windows verification is performed separately by the authorized local coding session or an approved platform runner; routine CI uses no live credentials.
1 change: 1 addition & 0 deletions 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:coverage": "npm run build && node scripts/smoke-live-coverage.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 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');
3 changes: 2 additions & 1 deletion scripts/check-public-artifact.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { runNpm } from './run-npm.mjs';
import assert from 'node:assert/strict';
import { execFileSync, spawnSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
Expand All @@ -6,7 +7,7 @@ import { fileURLToPath } from 'node:url';

const cli = fileURLToPath(new URL('..', import.meta.url));
const run = (command, args, cwd = cli) => execFileSync(command, args, { cwd, encoding: 'utf8' });
const [artifact] = JSON.parse(run('npm', ['pack', '--dry-run', '--ignore-scripts', '--json'], cli));
const [artifact] = JSON.parse(runNpm(['pack', '--dry-run', '--ignore-scripts', '--json'], { cwd: cli, encoding: 'utf8' }));
for (const file of artifact.files) {
assert.doesNotMatch(
file.path,
Expand Down
8 changes: 8 additions & 0 deletions scripts/run-npm.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { execFileSync } from 'node:child_process';

// npm exposes its JS entrypoint to lifecycle scripts on every supported OS.
// Running that entrypoint avoids trying to execute Windows npm.cmd as a binary.
export function runNpm(args, options) {
if (process.env.npm_execpath) return execFileSync(process.execPath, [process.env.npm_execpath, ...args], options);
return execFileSync('npm', args, options);
}
Loading
Loading