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
11 changes: 11 additions & 0 deletions .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@
"examples/agent-dir/tools/*.ts",
"registry/**/*.ts"
],
"ignoreUnresolvedImports": [
"../dist/index.js"
],
"ignoreExports": [
{
"file": "packages/create-lousho-agent/src/index.ts",
"exports": [
"main"
]
}
],
"ignoreDependencies": [
"@ai-sdk/anthropic",
"@ai-sdk/openai",
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ opt-out.
- **Behavior change:** `POST /chat` to a session paused on an approval is a `409` JSON body (`LOUSHO_SESSION_AWAITING_APPROVAL`, with `approvalId` and `approvalIds`) instead of a `200` stream ending in a generic error.
- A decision on an approval whose winning decision is still streaming is a `409` (`LOUSHO_APPROVAL_CONFLICT`), as docs/auth.md says, instead of a `404`.
- The session routes' `400` bodies carry a `code`: `LOUSHO_REQUEST_INVALID` (invalid JSON, missing or empty fields), `LOUSHO_APPROVAL_DECISION_INVALID` (an approval body with neither `approved` nor `answer`), `LOUSHO_SESSION_ID_INVALID`.
- `lousho chat` prints a failed turn with its error code, the provider's failure category and HTTP status, and the fix (`error: User not found. (auth-failure, HTTP 401)` then `[LOUSHO_PROVIDER_REQUEST_FAILED] ...`), as docs/cli.md says, instead of the bare provider message. The terminal chat of a project scaffolded by `lousho init` / `create-lousho-agent` does the same.
- A `.env` saved with a UTF-8 byte order mark (PowerShell 5.1's `Set-Content -Encoding utf8`) no longer loses its first variable: the `lousho` commands and the scaffolded `src/index.ts` drop the mark before parsing. Variables already set in the environment still win.
- `lousho doctor src/agent.ts` loads a `.ts`/`.js` agent module the way `lousho dev` does instead of failing with `LOUSHO_SPEC_UNSUPPORTED_FORMAT`, and a missing key for the module's provider is a failure. A scaffolded project's `npm run doctor` now runs `lousho doctor src/agent.ts`, so it fails when the agent's key is missing.
- `lousho doctor --ping` checks OpenRouter at `/api/v1/key`, which needs the key (its `/models` answers without one, so a wrong key passed); a key the provider rejects is now a failure, and a ping of a custom `OPENAI_BASE_URL` says it shows reachability only.
- `lousho doctor` fix lines use PowerShell syntax (`$env:NAME="<your key>"`) on Windows.
- The `lousho eval` table shows the scores of passing scored gates (such as `t.check('words', n, atLeast(3))`), not only in `--json`.

### Documentation

Expand Down
21 changes: 17 additions & 4 deletions bin/lousho.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#!/usr/bin/env node
'use strict';

const fs = require('node:fs');
const path = require('node:path');
const { parseEnv } = require('node:util');
const { pathToFileURL } = require('node:url');

const USAGE = [
Expand All @@ -24,8 +26,8 @@ const USAGE = [
' Open Agent Forge, the visual agent builder.',
' lousho mcp <agent.yaml|json|agent-dir|agent.ts> [--http --port N --host H]',
' Serve an agent as an MCP server (stdio by default).',
' lousho doctor [agent.yaml|json|agent-dir] [--json] [--ping]',
' Check Node, peer dependencies, API keys and (optionally) an agent spec.',
' lousho doctor [agent.yaml|json|agent-dir|agent.ts] [--json] [--ping]',
' Check Node, peer dependencies, API keys and (optionally) an agent spec, directory or module.',
' lousho eval [globs...] [--tag t] [--junit path] [--json path] [--strict] [--judge] [--record | --replay | --drift [--drift-usage]] [--url <base> [--token <bearer>]] [--config vitest.config.ts] [--timeout <ms>]',
' Run *.eval.ts files under your vitest and report the results.',
' lousho traces [traceId|prefix] [--dir D] [--limit N] [--json] [--content]',
Expand Down Expand Up @@ -73,10 +75,21 @@ const COMMANDS = new Map([
['traces', { module: 'traces', run: 'runTraces' }],
]);

/** Loads ./.env into process.env (variables already set are kept), like `node --env-file-if-exists=.env`. */
const BYTE_ORDER_MARK = 0xfeff;

/**
* Loads ./.env into process.env (variables already set are kept), like `node --env-file-if-exists=.env`.
* Read by hand, not with process.loadEnvFile(): that keeps a leading byte order mark (PowerShell 5.1's
* `Set-Content -Encoding utf8` writes one), so the first key would be named with a U+FEFF prefix and
* silently missing.
*/
function loadDotEnv() {
try {
process.loadEnvFile(path.resolve('.env'));
const text = fs.readFileSync(path.resolve('.env'), 'utf8');
const vars = parseEnv(text.charCodeAt(0) === BYTE_ORDER_MARK ? text.slice(1) : text);
for (const [name, value] of Object.entries(vars)) {
if (process.env[name] === undefined) process.env[name] = value;
}
} catch (error) {
if (error.code !== 'ENOENT') console.error(`lousho: could not load .env: ${error.message}`);
}
Expand Down
11 changes: 7 additions & 4 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Run it with `npx lousho <command>` inside a project that has the SDK installed.
| Command | What it does | Details |
| ------- | ------------ | ------- |
| `lousho init [dir]` | Scaffold a project: an agent with an example tool, an offline test, `.env.example`; installs dependencies and runs `git init`. | [Installation](./installation.md#scaffolding-a-new-project) |
| `lousho doctor [spec\|agent-dir] [--json] [--ping]` | Check Node, peer packages, provider keys, and optionally a spec file or agent directory; `--ping` also checks each configured provider endpoint answers. Prints a fix for every problem. | [Installation](./installation.md#troubleshooting-lousho-doctor) |
| `lousho doctor [spec\|agent-dir\|agent.ts] [--json] [--ping]` | Check Node, peer packages, provider keys, and optionally a spec file, agent directory or agent module (its provider's key is then required); `--ping` also checks each configured provider endpoint answers and accepts the key. Prints a fix for every problem. | [Installation](./installation.md#troubleshooting-lousho-doctor) |
| `lousho dev <path>` | Local dev server for a spec file, an agent directory or a TS agent: chat UI with a session per tab and streamed events, hot reload on save. | [Below](#lousho-dev) |
| `lousho chat <path>` | Terminal REPL for a spec file, an agent directory or a TS agent: streams replies, shows tool calls, asks for approvals and questions. | [Below](#lousho-chat) |
| `lousho acp <path>` | Serve a spec file, an agent directory or a TS agent to an editor (Zed and other ACP clients) over the Agent Client Protocol on stdio. | [ACP](./acp.md) |
Expand All @@ -33,7 +33,7 @@ lousho add <name> [--registry <url-or-path>] [--dir <agent-dir>] [--yes] [--allo
lousho build <agent-dir|spec|agent.ts> --target=<node-server|cloudflare-worker|docker> [--out=<dir>] (or --agent=<path>)
lousho studio [--port N] [--host H] [--prod|--dev]
lousho mcp <agent.yaml|json|agent-dir|agent.ts> [--http --port N --host H]
lousho doctor [agent.yaml|json|agent-dir] [--json] [--ping]
lousho doctor [agent.yaml|json|agent-dir|agent.ts] [--json] [--ping]
lousho eval [globs...] [--tag t] [--junit path] [--json path] [--strict] [--judge] [--record | --replay | --drift [--drift-usage]] [--url <base> [--token <bearer>]] [--config vitest.config.ts] [--timeout <ms>]
lousho traces [traceId|prefix] [--dir D] [--limit N] [--json] [--content]
lousho --version
Expand All @@ -59,7 +59,8 @@ value, an extra argument, or a missing `--target` or agent path. `lousho build`
**`.env`.** The commands that run an agent (`dev`, `chat`, `acp`, `mcp`),
`doctor`, `eval` (judge evals need provider keys) and `build` load `./.env` from the working directory when it exists, like
`node --env-file-if-exists=.env`: a variable already set in the environment
wins over the file.
wins over the file. A UTF-8 byte order mark at the start of the file (which
PowerShell 5.1's `Set-Content -Encoding utf8` writes) is ignored.

`lousho init` is the exception: it parses its own flags, and a bad flag or
value fails with `InitUsageError` — its message printed as-is, exit code 2 —
Expand Down Expand Up @@ -216,7 +217,9 @@ Done, the report is on its way.
- After each turn `[usage]` shows the tokens and, when every model used has
known pricing, the cost (`~` marks estimated tokens).
- Errors from the SDK print with their `[LOUSHO_...]` code and fix, and the
session continues.
session continues. A provider failure also names its category and HTTP
status, e.g. `error: User not found. (auth-failure, HTTP 401)` for a wrong
API key.
- `--traces` or `--traces=<dir>` writes the session's runs to local trace files,
as for `lousho dev`; a line on stderr says where.

Expand Down
26 changes: 21 additions & 5 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ npm install ./lousho-build-ai-agent-<version>.tgz ai zod
To scaffold a new project the same way, from the checkout:
`node bin/lousho.js init ../my-agent --sdk-path .`.

On Windows, `npx C:\abs\path\create-lousho-agent-<version>.tgz` silently does
nothing (an npm quirk with absolute drive-letter paths). Use a relative path
(`npx ./create-lousho-agent-<version>.tgz my-agent`) or a `file:` spec
(`npx file:C:/abs/path/create-lousho-agent-<version>.tgz my-agent`) instead.

`npm install github:LinuxDevil/agent-sdk` does **not** work: `dist/` is not in
git and the repository has no `prepare` build step, so the install has no
entry points.
Expand Down Expand Up @@ -287,13 +292,24 @@ What it checks:
5. With a spec path (`lousho doctor agent.yaml`): the spec is validated with
field paths for every error, its provider package and key are checked
(missing ones become failures), its `tools` must be built-in tools, and any
`mcpServers` command must be resolvable.
`mcpServers` command must be resolvable. An agent directory or a `.ts`/`.js`
agent module (`lousho doctor src/agent.ts`, what a scaffolded project's
`npm run doctor` runs) is loaded the way `lousho dev` loads it, and its
model's provider package and key are checked the same way. Without a path
a missing key is only a warning, since doctor cannot know which provider
your agent uses.
6. Ollama reachability, only when the spec uses Ollama or `OLLAMA_HOST` is set.
7. Docker availability, a warning only when the spec uses a sandboxed tool.
8. With `--ping`: `GET <base>/models` for each provider whose variable is set
(OpenAI at `OPENAI_BASE_URL` when set, Anthropic, OpenRouter), with a 5 s
timeout. An unreachable server or a rejected key is a warning. Keys are sent
to their own provider only and never printed.
8. With `--ping`: a `GET` for each provider whose variable is set, with a 5 s
timeout, to an endpoint that needs the key: `/models` for OpenAI and
Anthropic, `/api/v1/key` for OpenRouter (its `/models` answers without a
key). A rejected key (HTTP 401/403) is a failure; an unreachable server is a
warning. With `OPENAI_BASE_URL` set, `<base>/models` is pinged and the line
says it shows reachability only, since a local server may not check keys.
Keys are sent to their own provider only and never printed.

On Windows the fix lines use PowerShell syntax (`$env:OPENAI_API_KEY="<your key>"`)
instead of `export`.

The exit code is `1` if any check fails and `0` otherwise (warnings do not
fail), so it can gate CI. Add `--json` for machine-readable output. Colour is
Expand Down
37 changes: 28 additions & 9 deletions llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,11 @@ npm install ./lousho-build-ai-agent-<version>.tgz ai zod
To scaffold a new project the same way, from the checkout:
`node bin/lousho.js init ../my-agent --sdk-path .`.

On Windows, `npx C:\abs\path\create-lousho-agent-<version>.tgz` silently does
nothing (an npm quirk with absolute drive-letter paths). Use a relative path
(`npx ./create-lousho-agent-<version>.tgz my-agent`) or a `file:` spec
(`npx file:C:/abs/path/create-lousho-agent-<version>.tgz my-agent`) instead.

`npm install github:LinuxDevil/agent-sdk` does **not** work: `dist/` is not in
git and the repository has no `prepare` build step, so the install has no
entry points.
Expand Down Expand Up @@ -646,13 +651,24 @@ What it checks:
5. With a spec path (`lousho doctor agent.yaml`): the spec is validated with
field paths for every error, its provider package and key are checked
(missing ones become failures), its `tools` must be built-in tools, and any
`mcpServers` command must be resolvable.
`mcpServers` command must be resolvable. An agent directory or a `.ts`/`.js`
agent module (`lousho doctor src/agent.ts`, what a scaffolded project's
`npm run doctor` runs) is loaded the way `lousho dev` loads it, and its
model's provider package and key are checked the same way. Without a path
a missing key is only a warning, since doctor cannot know which provider
your agent uses.
6. Ollama reachability, only when the spec uses Ollama or `OLLAMA_HOST` is set.
7. Docker availability, a warning only when the spec uses a sandboxed tool.
8. With `--ping`: `GET <base>/models` for each provider whose variable is set
(OpenAI at `OPENAI_BASE_URL` when set, Anthropic, OpenRouter), with a 5 s
timeout. An unreachable server or a rejected key is a warning. Keys are sent
to their own provider only and never printed.
8. With `--ping`: a `GET` for each provider whose variable is set, with a 5 s
timeout, to an endpoint that needs the key: `/models` for OpenAI and
Anthropic, `/api/v1/key` for OpenRouter (its `/models` answers without a
key). A rejected key (HTTP 401/403) is a failure; an unreachable server is a
warning. With `OPENAI_BASE_URL` set, `<base>/models` is pinged and the line
says it shows reachability only, since a local server may not check keys.
Keys are sent to their own provider only and never printed.

On Windows the fix lines use PowerShell syntax (`$env:OPENAI_API_KEY="<your key>"`)
instead of `export`.

The exit code is `1` if any check fails and `0` otherwise (warnings do not
fail), so it can gate CI. Add `--json` for machine-readable output. Colour is
Expand Down Expand Up @@ -4747,7 +4763,7 @@ Run it with `npx lousho <command>` inside a project that has the SDK installed.
| Command | What it does | Details |
| ------- | ------------ | ------- |
| `lousho init [dir]` | Scaffold a project: an agent with an example tool, an offline test, `.env.example`; installs dependencies and runs `git init`. | [Installation](https://github.com/LinuxDevil/agent-sdk/blob/main/docs/installation.md#scaffolding-a-new-project) |
| `lousho doctor [spec\|agent-dir] [--json] [--ping]` | Check Node, peer packages, provider keys, and optionally a spec file or agent directory; `--ping` also checks each configured provider endpoint answers. Prints a fix for every problem. | [Installation](https://github.com/LinuxDevil/agent-sdk/blob/main/docs/installation.md#troubleshooting-lousho-doctor) |
| `lousho doctor [spec\|agent-dir\|agent.ts] [--json] [--ping]` | Check Node, peer packages, provider keys, and optionally a spec file, agent directory or agent module (its provider's key is then required); `--ping` also checks each configured provider endpoint answers and accepts the key. Prints a fix for every problem. | [Installation](https://github.com/LinuxDevil/agent-sdk/blob/main/docs/installation.md#troubleshooting-lousho-doctor) |
| `lousho dev <path>` | Local dev server for a spec file, an agent directory or a TS agent: chat UI with a session per tab and streamed events, hot reload on save. | [Below](#lousho-dev) |
| `lousho chat <path>` | Terminal REPL for a spec file, an agent directory or a TS agent: streams replies, shows tool calls, asks for approvals and questions. | [Below](#lousho-chat) |
| `lousho acp <path>` | Serve a spec file, an agent directory or a TS agent to an editor (Zed and other ACP clients) over the Agent Client Protocol on stdio. | [ACP](https://github.com/LinuxDevil/agent-sdk/blob/main/docs/acp.md) |
Expand All @@ -4772,7 +4788,7 @@ lousho add <name> [--registry <url-or-path>] [--dir <agent-dir>] [--yes] [--allo
lousho build <agent-dir|spec|agent.ts> --target=<node-server|cloudflare-worker|docker> [--out=<dir>] (or --agent=<path>)
lousho studio [--port N] [--host H] [--prod|--dev]
lousho mcp <agent.yaml|json|agent-dir|agent.ts> [--http --port N --host H]
lousho doctor [agent.yaml|json|agent-dir] [--json] [--ping]
lousho doctor [agent.yaml|json|agent-dir|agent.ts] [--json] [--ping]
lousho eval [globs...] [--tag t] [--junit path] [--json path] [--strict] [--judge] [--record | --replay | --drift [--drift-usage]] [--url <base> [--token <bearer>]] [--config vitest.config.ts] [--timeout <ms>]
lousho traces [traceId|prefix] [--dir D] [--limit N] [--json] [--content]
lousho --version
Expand All @@ -4798,7 +4814,8 @@ value, an extra argument, or a missing `--target` or agent path. `lousho build`
**`.env`.** The commands that run an agent (`dev`, `chat`, `acp`, `mcp`),
`doctor`, `eval` (judge evals need provider keys) and `build` load `./.env` from the working directory when it exists, like
`node --env-file-if-exists=.env`: a variable already set in the environment
wins over the file.
wins over the file. A UTF-8 byte order mark at the start of the file (which
PowerShell 5.1's `Set-Content -Encoding utf8` writes) is ignored.

`lousho init` is the exception: it parses its own flags, and a bad flag or
value fails with `InitUsageError` — its message printed as-is, exit code 2 —
Expand Down Expand Up @@ -4955,7 +4972,9 @@ Done, the report is on its way.
- After each turn `[usage]` shows the tokens and, when every model used has
known pricing, the cost (`~` marks estimated tokens).
- Errors from the SDK print with their `[LOUSHO_...]` code and fix, and the
session continues.
session continues. A provider failure also names its category and HTTP
status, e.g. `error: User not found. (auth-failure, HTTP 401)` for a wrong
API key.
- `--traces` or `--traces=<dir>` writes the session's runs to local trace files,
as for `lousho dev`; a line on stderr says where.

Expand Down
2 changes: 2 additions & 0 deletions packages/create-lousho-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ With npm, put the options after `--`:
npm create lousho-agent my-agent -- --yes --provider anthropic --template tools
```

Running a packed `.tgz` of this package on Windows: `npx C:\abs\path\create-lousho-agent-<version>.tgz` silently does nothing (an npm quirk with absolute drive-letter paths). Use a relative path (`npx ./create-lousho-agent-<version>.tgz my-agent`) or a `file:` spec (`npx file:C:/abs/path/create-lousho-agent-<version>.tgz my-agent`).

This package is a thin wrapper: it runs `lousho init` from the SDK, so `npx @lousho/build-ai-agent init --help` lists every option.

## Links
Expand Down
22 changes: 21 additions & 1 deletion src/cli/bin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('bin/lousho.js', () => {

it('keeps the top-level usage in step with the commands', () => {
const banner = lousho('--help').stdout;
expect(banner).toContain('lousho doctor [agent.yaml|json|agent-dir] [--json] [--ping]');
expect(banner).toContain('lousho doctor [agent.yaml|json|agent-dir|agent.ts] [--json] [--ping]');
// One-line descriptions and the build targets (a usage line alone said neither).
expect(banner).toContain('Chat with an agent in the terminal.');
expect(banner).toContain('--target=<node-server|cloudflare-worker|docker>');
Expand Down Expand Up @@ -85,4 +85,24 @@ describe('bin/lousho.js loads ./.env for the agent commands', () => {
}
}
);

it.skipIf(!fs.existsSync(path.resolve(__dirname, '..', '..', 'dist', 'cli', 'doctor.mjs')))(
'keeps the first key of a .env saved with a byte order mark, and a variable already set wins (needs npm run build)',
() => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lousho-dotenv-bom-'));
try {
// What PowerShell 5.1's `Set-Content -Encoding utf8` writes.
fs.writeFileSync(path.join(dir, '.env'), String.fromCharCode(0xfeff) + 'OPENAI_API_KEY=sk-from-dotenv-file\nANTHROPIC_API_KEY=from-file\n');
const env: NodeJS.ProcessEnv = { ...process.env, ANTHROPIC_API_KEY: '' };
delete env.OPENAI_API_KEY;
const res = spawnSync(process.execPath, [BIN, 'doctor', '--json'], { cwd: dir, env, encoding: 'utf8' });
const checks = (JSON.parse(res.stdout) as { checks: { id: string; status: string }[] }).checks;
expect(checks.find((c) => c.id === 'env.openai')?.status).toBe('ok');
// Set (even to empty) in the environment: the file does not override it.
expect(checks.find((c) => c.id === 'env.anthropic')?.status).toBe('warn');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}
);
});
Loading
Loading