@victor-software-house/exa-cli 0.0.0 → 0.0.2
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 +19 -0
- package/README.md +50 -13
- package/bin/exa.mjs +80 -0
- package/package.json +12 -62
- package/skills/exa/SKILL.md +38 -13
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -1546
- package/dist/cli.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Apply the documented dynamic Context token default and accept its observed cost response shape ([`83dd937`](https://github.com/victor-software-house/exa-cli/commit/83dd9375ad85618adfdd5ec8ac04a1da05773113)).
|
|
8
|
+
|
|
9
|
+
- Redesign generated help with concise command menus, focused errors, practical examples, and nested `agent` commands ([`2b9b5bf`](https://github.com/victor-software-house/exa-cli/commit/2b9b5bfa0efe04bd4b8152573b137b604f0f65c1)).
|
|
10
|
+
|
|
11
|
+
## 0.0.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Flag-driven output, per-key cache, `exa cache`, and real npm binaries ([`d0bb475`](https://github.com/victor-software-house/exa-cli/commit/d0bb4750b2874466541996a86e0d98db8e65a14d)).
|
|
16
|
+
|
|
17
|
+
- Output resolution is now flags-only: `--pretty` implies JSON and wins, `--json` or `-o` select JSON, otherwise human text prints even when piped. `-o` always writes JSON. No TTY or file-extension inference — pass `--json` where scripts used to rely on pipe detection.
|
|
18
|
+
- Cache keys now include a truncated SHA-256 of the API key; two accounts no longer share cached responses. Old entries silently miss — run `exa cache clear`.
|
|
19
|
+
- New `exa cache path|clear|prune` for cache management.
|
|
20
|
+
- npm now ships prebuilt per-platform binaries behind a Node launcher (`@victor-software-house/exa-cli-<platform>` optionalDependencies); the package runs anywhere Node 20+ exists, no Bun install required.
|
package/README.md
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
# @victor-software-house/exa-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Agent-friendly CLI for the [Exa](https://exa.ai/) API: search, contents, answer, similar, context, and agent runs, backed by a local SQLite request cache.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
npm ships a launcher that pulls a prebuilt binary for your platform. Node 20+ is enough — Bun is embedded in the binary:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add -g @victor-software-house/exa-cli
|
|
11
|
+
npm install -g @victor-software-house/exa-cli
|
|
12
|
+
bunx @victor-software-house/exa-cli --help
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Agents and operator checkouts install the rolling GitHub Release binary through mise. Pin `0.0.0` — that tag is a rolling channel, not a frozen npm version:
|
|
8
16
|
|
|
9
17
|
```toml
|
|
10
18
|
[tools]
|
|
@@ -16,7 +24,7 @@ mise install
|
|
|
16
24
|
exa --help
|
|
17
25
|
```
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
Source checkout:
|
|
20
28
|
|
|
21
29
|
```bash
|
|
22
30
|
mise install
|
|
@@ -36,21 +44,48 @@ Operator checkouts with the global fnox profile `exa` get the key from mise (`mi
|
|
|
36
44
|
```bash
|
|
37
45
|
exa search "Exa search type auto vs neural official docs" --include-domain exa.ai
|
|
38
46
|
exa contents https://exa.ai/docs/reference/search.md
|
|
39
|
-
exa answer "
|
|
47
|
+
exa answer "when did Exa ship the context endpoint"
|
|
48
|
+
exa similar https://exa.ai/docs/reference/search.md
|
|
49
|
+
exa context "how to use React hooks for state management" --tokens-num 500
|
|
50
|
+
exa agent create "narrow research question"
|
|
51
|
+
exa agent create "narrow research question" --wait --timeout 600
|
|
52
|
+
exa agent get agent_run_…
|
|
53
|
+
exa agent wait agent_run_… --timeout 600
|
|
54
|
+
exa agent cancel agent_run_…
|
|
40
55
|
exa doctor
|
|
56
|
+
exa cache path|clear|prune
|
|
41
57
|
```
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
Every command also accepts `--request '<json>'` with the raw provider body (validated against the generated schema for that endpoint) instead of the flag surface.
|
|
60
|
+
|
|
61
|
+
## Output
|
|
44
62
|
|
|
45
|
-
|
|
63
|
+
Format is flag-driven. There is no TTY sniffing and no file-extension inference: what you pass is what you get.
|
|
46
64
|
|
|
47
|
-
|
|
65
|
+
| Flags | Result |
|
|
66
|
+
|:--|:--|
|
|
67
|
+
| none | Human-readable text on stdout, even when piped |
|
|
68
|
+
| `--json` | Compact provider JSON |
|
|
69
|
+
| `--pretty` | Indented JSON (implies JSON, beats `--json`) |
|
|
70
|
+
| `-o FILE` | Always writes JSON to the file; `--pretty` indents it |
|
|
71
|
+
| `--envelope` | Wraps JSON output with `{ data, cache: { hit, ageMs } }` |
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
- `--no-cache` skips read and write
|
|
51
|
-
- `--ttl` overrides the TTL in seconds
|
|
73
|
+
Writing to a file never transforms content — a `.md` output path still gets JSON. Progress, cache hits, and errors go to stderr, so `exa search … --json | jq .results` is always safe.
|
|
52
74
|
|
|
53
|
-
|
|
75
|
+
Color applies to the text render only: on by default on a TTY, controlled by `--color` / `--no-color` and `NO_COLOR` / `FORCE_COLOR`.
|
|
76
|
+
|
|
77
|
+
## Cache and cost
|
|
78
|
+
|
|
79
|
+
Every uncached call spends Exa credits. Identical requests within the TTL are served free from a local SQLite cache at `$XDG_CACHE_HOME/exa-cli/` or `~/.cache/exa-cli/`, announced as `cache hit age=…` on stderr.
|
|
80
|
+
|
|
81
|
+
- Cache keys include a digest of the API key — two accounts never share cached responses.
|
|
82
|
+
- Default TTL is 24 hours; `--ttl SECONDS` overrides per call.
|
|
83
|
+
- `--refresh` skips the read and overwrites the entry. Use only when staleness provably matters.
|
|
84
|
+
- `--no-cache` skips reads and writes.
|
|
85
|
+
- `exa cache prune` deletes expired entries; `exa cache clear` deletes all; `exa cache path` prints the database location.
|
|
86
|
+
- `agent create`, `agent get`, `agent wait`, and `agent cancel` never cache: create and cancel mutate, get and wait poll.
|
|
87
|
+
|
|
88
|
+
Do not repeat an identical call in a loop — the first response is already stored.
|
|
54
89
|
|
|
55
90
|
## Agent skill
|
|
56
91
|
|
|
@@ -62,6 +97,8 @@ npx skills add victor-software-house/exa-cli
|
|
|
62
97
|
|
|
63
98
|
```bash
|
|
64
99
|
mise install
|
|
65
|
-
mise run verify
|
|
66
|
-
|
|
100
|
+
mise run verify # lint + typecheck + unit tests + build
|
|
101
|
+
mise -E test run test:live # paid Exa calls; skips without EXA_API_KEY
|
|
67
102
|
```
|
|
103
|
+
|
|
104
|
+
Releases are CI-owned: merge a changeset, the Release workflow opens a Version Packages PR, and merging it publishes to npm (six platform packages plus the launcher umbrella), tags, and uploads versioned binaries. A new npm package name needs a one-time `mise run release:bootstrap` first (browser login; same staged platforms CI publishes).
|
package/bin/exa.mjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Node launcher for exa-cli: resolves the platform binary from the
|
|
3
|
+
// optionalDependencies-selected package and execs it. The binary embeds Bun,
|
|
4
|
+
// so no runtime beyond Node >=20 is required here.
|
|
5
|
+
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
|
|
13
|
+
const scope = '@victor-software-house';
|
|
14
|
+
|
|
15
|
+
function platformPackages() {
|
|
16
|
+
const id = `${process.platform}-${process.arch}`;
|
|
17
|
+
if (id === 'win32-x64') {
|
|
18
|
+
return [`${scope}/exa-cli-windows-x64`];
|
|
19
|
+
}
|
|
20
|
+
if (id === 'linux-x64') {
|
|
21
|
+
// os/cpu cannot distinguish glibc from musl; npm selects one via libc,
|
|
22
|
+
// so probe both and use whichever package resolved.
|
|
23
|
+
return [`${scope}/exa-cli-linux-x64`, `${scope}/exa-cli-linux-x64-musl`];
|
|
24
|
+
}
|
|
25
|
+
if (id === 'darwin-x64' || id === 'darwin-arm64' || id === 'linux-arm64') {
|
|
26
|
+
return [`${scope}/exa-cli-${id}`];
|
|
27
|
+
}
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function findBinary() {
|
|
32
|
+
for (const pkg of platformPackages()) {
|
|
33
|
+
try {
|
|
34
|
+
const manifest = require.resolve(`${pkg}/package.json`);
|
|
35
|
+
const binary = join(
|
|
36
|
+
dirname(manifest),
|
|
37
|
+
'bin',
|
|
38
|
+
process.platform === 'win32' ? 'exa.exe' : 'exa',
|
|
39
|
+
);
|
|
40
|
+
if (existsSync(binary)) {
|
|
41
|
+
return binary;
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// platform package not installed; try the next candidate
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const binary = findBinary();
|
|
51
|
+
if (binary === undefined) {
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
`exa-cli: no binary for ${process.platform}-${process.arch}.\n` +
|
|
54
|
+
'Reinstall with `bun add -g @victor-software-house/exa-cli` or `npm install -g @victor-software-house/exa-cli`.\n',
|
|
55
|
+
);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
60
|
+
|
|
61
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
62
|
+
process.on(signal, () => {
|
|
63
|
+
if (!child.killed) {
|
|
64
|
+
child.kill(signal);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
child.on('error', (error) => {
|
|
70
|
+
process.stderr.write(`${error}\n`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
child.on('exit', (code, signal) => {
|
|
75
|
+
if (signal) {
|
|
76
|
+
process.kill(process.pid, signal);
|
|
77
|
+
} else {
|
|
78
|
+
process.exit(code ?? 1);
|
|
79
|
+
}
|
|
80
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@victor-software-house/exa-cli",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Agent-friendly CLI for the Exa API with a local SQLite request cache.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Victor Araújo",
|
|
@@ -9,38 +9,11 @@
|
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
11
|
"bin": {
|
|
12
|
-
"exa": "./
|
|
13
|
-
},
|
|
14
|
-
"exports": {
|
|
15
|
-
".": {
|
|
16
|
-
"types": "./dist/cli.d.mts",
|
|
17
|
-
"import": "./dist/cli.mjs"
|
|
18
|
-
},
|
|
19
|
-
"./package.json": "./package.json"
|
|
20
|
-
},
|
|
21
|
-
"files": [
|
|
22
|
-
"dist",
|
|
23
|
-
"skills",
|
|
24
|
-
"README.md",
|
|
25
|
-
"CHANGELOG.md",
|
|
26
|
-
"LICENSE"
|
|
27
|
-
],
|
|
28
|
-
"scripts": {
|
|
29
|
-
"prepublishOnly": "mise run verify"
|
|
12
|
+
"exa": "./bin/exa.mjs"
|
|
30
13
|
},
|
|
14
|
+
"files": ["bin", "skills", "README.md", "CHANGELOG.md", "LICENSE"],
|
|
31
15
|
"engines": {
|
|
32
|
-
"node": ">=
|
|
33
|
-
},
|
|
34
|
-
"packageManager": "bun@1.4.0",
|
|
35
|
-
"devEngines": {
|
|
36
|
-
"runtime": {
|
|
37
|
-
"name": "node",
|
|
38
|
-
"version": ">=26"
|
|
39
|
-
},
|
|
40
|
-
"packageManager": {
|
|
41
|
-
"name": "bun",
|
|
42
|
-
"version": ">=1.4.0 <2.0.0"
|
|
43
|
-
}
|
|
16
|
+
"node": ">=20"
|
|
44
17
|
},
|
|
45
18
|
"publishConfig": {
|
|
46
19
|
"access": "public",
|
|
@@ -60,35 +33,12 @@
|
|
|
60
33
|
"search",
|
|
61
34
|
"optique"
|
|
62
35
|
],
|
|
63
|
-
"
|
|
64
|
-
"@
|
|
65
|
-
"@
|
|
66
|
-
"@
|
|
67
|
-
"@
|
|
68
|
-
"@
|
|
69
|
-
"
|
|
70
|
-
"ts-pattern": "5.9.0",
|
|
71
|
-
"zod": "4.5.2"
|
|
72
|
-
},
|
|
73
|
-
"devDependencies": {
|
|
74
|
-
"@biomejs/biome": "2.5.10",
|
|
75
|
-
"@changesets/cli": "2.31.1",
|
|
76
|
-
"@changesets/get-github-info": "0.8.0",
|
|
77
|
-
"@changesets/types": "6.1.0",
|
|
78
|
-
"@commitlint/cli": "21.2.2",
|
|
79
|
-
"@commitlint/config-conventional": "21.2.2",
|
|
80
|
-
"@hey-api/openapi-ts": "0.0.0-next-20260824173136",
|
|
81
|
-
"@limegrass/eslint-plugin-import-alias": "1.6.1",
|
|
82
|
-
"@oxlint/plugins": "1.80.0",
|
|
83
|
-
"@types/bun": "1.4.0",
|
|
84
|
-
"@types/node": "26.4.0",
|
|
85
|
-
"@victor-software-house/anti-slop": "0.0.1",
|
|
86
|
-
"bun-release": "0.0.0",
|
|
87
|
-
"eslint-plugin-zod": "4.10.0",
|
|
88
|
-
"liquidjs": "10.27.2",
|
|
89
|
-
"oxlint": "1.80.0",
|
|
90
|
-
"oxlint-tsgolint": "7.0.2001",
|
|
91
|
-
"tsdown": "0.22.14",
|
|
92
|
-
"typescript": "7.0.2"
|
|
36
|
+
"optionalDependencies": {
|
|
37
|
+
"@victor-software-house/exa-cli-darwin-arm64": "0.0.2",
|
|
38
|
+
"@victor-software-house/exa-cli-darwin-x64": "0.0.2",
|
|
39
|
+
"@victor-software-house/exa-cli-linux-x64": "0.0.2",
|
|
40
|
+
"@victor-software-house/exa-cli-linux-arm64": "0.0.2",
|
|
41
|
+
"@victor-software-house/exa-cli-linux-x64-musl": "0.0.2",
|
|
42
|
+
"@victor-software-house/exa-cli-windows-x64": "0.0.2"
|
|
93
43
|
}
|
|
94
44
|
}
|
package/skills/exa/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: exa
|
|
3
|
-
description: Use the Exa CLI for
|
|
3
|
+
description: Use the Exa CLI for current web evidence, known-page retrieval, cited answers, coding context, or asynchronous Exa Agent research. Prefer it over ad-hoc HTTP; use GitHub tooling or an existing clone or submodule for GitHub implementation truth.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Exa CLI
|
|
7
7
|
|
|
8
|
-
Install the GitHub Release binary with mise (`"github:victor-software-house/exa-cli" = "0.0.0"`). The CLI binary is `exa`.
|
|
8
|
+
Install the GitHub Release binary with mise (`"github:victor-software-house/exa-cli" = "0.0.0"`) or globally from npm (`bun add -g @victor-software-house/exa-cli`, Node 20+). The CLI binary is `exa`.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
npx skills add victor-software-house/exa-cli
|
|
@@ -13,33 +13,58 @@ npx skills add victor-software-house/exa-cli
|
|
|
13
13
|
|
|
14
14
|
Auth is `EXA_API_KEY` or `--api-key`. Do not print the key.
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Cost discipline
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Every uncached call spends Exa credits. The local SQLite cache makes identical requests within the TTL free — a hit prints `cache hit age=…` on stderr. Rules:
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
- Never re-issue an identical request; the response is already cached.
|
|
21
|
+
- `--refresh` only when a cached answer is provably stale.
|
|
22
|
+
- `--no-cache` almost never.
|
|
23
|
+
- Agent commands (`agent create`, `agent get`, `agent wait`, `agent cancel`) are never cached.
|
|
24
|
+
- Housekeeping: `exa cache path`, `exa cache prune` (expired entries), `exa cache clear`.
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
The cache key includes a digest of the API key, so switching accounts never serves another account's responses.
|
|
27
|
+
|
|
28
|
+
## Output
|
|
29
|
+
|
|
30
|
+
Without flags the CLI prints human-readable text, even when piped. For machine consumption always pass `--json` (compact) or `--pretty` (indented; implies JSON). `-o FILE` always writes JSON regardless of the file extension. `--envelope` wraps JSON with cache metadata.
|
|
23
31
|
|
|
24
|
-
|
|
32
|
+
## Retention workflow
|
|
33
|
+
|
|
34
|
+
Capture the complete response once, project from the file, never re-fetch to re-project:
|
|
25
35
|
|
|
26
36
|
```bash
|
|
27
|
-
|
|
37
|
+
out_dir="${CLAUDE_JOB_DIR:-$(mktemp -d)}/exa"
|
|
38
|
+
mkdir -p "$out_dir"
|
|
39
|
+
|
|
40
|
+
exa search "precise query with entity and source qualifier" \
|
|
41
|
+
--include-domain exa.ai \
|
|
42
|
+
--json -o "$out_dir/search.json"
|
|
43
|
+
|
|
44
|
+
jq '.results[] | {url, title}' "$out_dir/search.json"
|
|
28
45
|
```
|
|
29
46
|
|
|
30
|
-
|
|
47
|
+
## Commands
|
|
31
48
|
|
|
32
49
|
```bash
|
|
50
|
+
exa search "precise query with entity and source qualifier" --include-domain exa.ai --json
|
|
33
51
|
exa contents https://exa.ai/docs/reference/search.md --json
|
|
52
|
+
exa answer "narrow question" --json
|
|
53
|
+
exa similar https://exa.ai/docs/reference/search.md --json
|
|
54
|
+
exa context "how to use React hooks for state management" --tokens-num 500 --json
|
|
34
55
|
```
|
|
35
56
|
|
|
36
|
-
|
|
57
|
+
Agent runs (create is not cached; block with `--wait` or `agent wait` instead of a manual poll loop):
|
|
37
58
|
|
|
38
59
|
```bash
|
|
39
|
-
exa
|
|
60
|
+
exa agent create "narrow research question" --json
|
|
61
|
+
exa agent create "narrow research question" --wait --timeout 600 --json
|
|
62
|
+
exa agent get agent_run_… --json
|
|
63
|
+
exa agent wait agent_run_… --timeout 600 --json
|
|
64
|
+
exa agent cancel agent_run_… --json
|
|
40
65
|
```
|
|
41
66
|
|
|
42
|
-
Raw body is JSON for that command
|
|
67
|
+
Raw body is JSON text for that command's generated Hey API schema. Optique parses it; do not pass a file path:
|
|
43
68
|
|
|
44
69
|
```bash
|
|
45
70
|
exa search --request '{"query":"Exa search type auto","contents":{"highlights":true}}' --json
|
|
@@ -47,4 +72,4 @@ exa search --request '{"query":"Exa search type auto","contents":{"highlights":t
|
|
|
47
72
|
|
|
48
73
|
Do not treat snippets, citations, or generated answers as proof. Read the cited URL before making a factual claim.
|
|
49
74
|
|
|
50
|
-
`/context`
|
|
75
|
+
`/context` is `exa context`. Agent runs are `exa agent create`, `exa agent get`, `exa agent wait`, and `exa agent cancel`. GitHub implementation truth still stays `gh` or a pinned submodule.
|
package/dist/cli.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import "zod/compile";
|