@anna-ai/cli 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/README.md +149 -0
- package/dist/bridge-CzEs7jaN.js +145 -0
- package/dist/bridge-t2Qqu3hC.js +3 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +121 -0
- package/dist/dashboard.html +426 -0
- package/dist/dev-Bgku5ngL.js +163 -0
- package/dist/doctor-D3z4YslL.js +69 -0
- package/dist/fixture-BGjMtqWA.js +278 -0
- package/dist/server-B6-Qdluv.js +255 -0
- package/dist/test/index.d.ts +151 -0
- package/dist/test/index.js +260 -0
- package/package.json +53 -0
- package/templates/minimal/README.md +9 -0
- package/templates/minimal/app.json +7 -0
- package/templates/minimal/bundle/app.js +36 -0
- package/templates/minimal/bundle/index.html +14 -0
- package/templates/minimal/executas/__SLUG__/plugin.py +60 -0
- package/templates/minimal/executas/__SLUG__/pyproject.toml +12 -0
- package/templates/minimal/manifest.json +38 -0
- package/vendor/anna-app-schema/README.md +22 -0
- package/vendor/anna-app-schema/dispatcher_version.txt +1 -0
- package/vendor/anna-app-schema/events/AnnaAppEvent.json +38 -0
- package/vendor/anna-app-schema/host_api/methods.json +170 -0
- package/vendor/anna-app-schema/manifest/AppManifest.json +471 -0
- package/vendor/anna-app-schema/manifest/UiManifestSection.json +273 -0
- package/vendor/anna-app-schema/package.json +25 -0
- package/vendor/anna-app-schema/pyproject.toml +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# @anna-ai/cli
|
|
2
|
+
|
|
3
|
+
Anna App developer CLI — scaffold, validate, run a local dev harness, drive
|
|
4
|
+
fixture-based regression tests, and program against the harness from
|
|
5
|
+
`vitest`/`pytest`. Phases 2–9 (MVP) are done.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
End-user install (after first npm publish):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g @anna-ai/cli
|
|
13
|
+
anna-app --help
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The `dev` and `fixture replay` commands transparently fetch the pinned Python
|
|
17
|
+
runtime via `uvx` (no matrix-nexus checkout required). Install
|
|
18
|
+
[uv](https://docs.astral.sh/uv/) once:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Local dev (this repo):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm install
|
|
28
|
+
pnpm sync:schema # vendors @anna-ai/app-schema from sibling matrix-nexus checkout
|
|
29
|
+
pnpm build
|
|
30
|
+
node dist/cli.js --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
### `anna-app init <dir> [--slug <slug>] [--template minimal] [--force]`
|
|
36
|
+
|
|
37
|
+
Scaffolds a new Anna App project (manifest + bundle + a stdio plugin
|
|
38
|
+
template). `__TOOL_ID__` becomes `tool-dev-<slug>`.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
anna-app init my-app --slug my-app
|
|
42
|
+
cd my-app
|
|
43
|
+
anna-app validate
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `anna-app validate [--manifest manifest.json] [--bundle bundle] [--strict]`
|
|
47
|
+
|
|
48
|
+
Layered fail-fast checks: JSON Schema → `ui` static → cross-file `tool_id`
|
|
49
|
+
linter (with Levenshtein-1 typo detection) → `--strict` host_api ACL grep
|
|
50
|
+
of bundle JS/TS.
|
|
51
|
+
|
|
52
|
+
### `anna-app dev [--manifest …] [--bundle …] [--port 5180] [--matrix-nexus-root <path>]`
|
|
53
|
+
|
|
54
|
+
Boots the local harness:
|
|
55
|
+
|
|
56
|
+
- Spawns the Python `anna-app-bridge` (production dispatcher reused via
|
|
57
|
+
`WindowStoreProtocol`).
|
|
58
|
+
- Serves a mock dashboard at `http://localhost:<port>/`.
|
|
59
|
+
- Auto-discovers `<manifest-dir>/executas/<name>/pyproject.toml` and
|
|
60
|
+
registers them in the in-process `ExecutaPool`.
|
|
61
|
+
- Hot-reloads the bundle on disk changes (use `--no-watch` to disable).
|
|
62
|
+
|
|
63
|
+
Two runtime modes (auto-selected):
|
|
64
|
+
|
|
65
|
+
| Mode | When | Command |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `uvx` (default) | No matrix-nexus checkout nearby | `uvx anna-app-runtime-local@<pinned> anna-app-bridge` |
|
|
68
|
+
| `nexus-source` | `--matrix-nexus-root` set, `$ANNA_NEXUS_ROOT` set, or running from inside a checkout | `uv run --project <root> python -m anna_app_runtime_local.bridge` |
|
|
69
|
+
|
|
70
|
+
The pinned wheel version is exported as `PINNED_RUNTIME_VERSION` from
|
|
71
|
+
[src/harness/bridge.ts](src/harness/bridge.ts) — bump in lock-step with
|
|
72
|
+
`packages/VERSIONS.md` in matrix-nexus.
|
|
73
|
+
|
|
74
|
+
### `anna-app doctor [--matrix-nexus-root <path>]`
|
|
75
|
+
|
|
76
|
+
Environment sanity check: `uv` on PATH, `uvx` cache state for the pinned
|
|
77
|
+
runtime, optional matrix-nexus checkout, dev signing key permissions.
|
|
78
|
+
Exit `0` if all required checks pass.
|
|
79
|
+
|
|
80
|
+
### `anna-app fixture verify <file.jsonl>`
|
|
81
|
+
|
|
82
|
+
Strict syntactic + semantic validation of a recorded fixture (envelope
|
|
83
|
+
ordering, response⇄request pairing, host_api allowlist).
|
|
84
|
+
|
|
85
|
+
### `anna-app fixture summarize <file.jsonl>`
|
|
86
|
+
|
|
87
|
+
Pretty stats: counts per `(ns, method)`, error breakdown, event totals.
|
|
88
|
+
|
|
89
|
+
### `anna-app fixture replay <file.jsonl> [--manifest …]`
|
|
90
|
+
|
|
91
|
+
Dry-run replay (MVP): re-executes the recorded request sequence against
|
|
92
|
+
a fresh `LocalDispatcherSession` and diffs each response. Live executa
|
|
93
|
+
re-invocation is deferred to a future minor.
|
|
94
|
+
|
|
95
|
+
## Programmatic harness (vitest)
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { mountBundle } from "@anna-ai/cli/test";
|
|
99
|
+
|
|
100
|
+
const h = await mountBundle({
|
|
101
|
+
manifest: "./examples/hello/manifest.json",
|
|
102
|
+
bundle: "./examples/hello/bundle",
|
|
103
|
+
});
|
|
104
|
+
await h.call("storage", "set", { key: "k", value: 1 });
|
|
105
|
+
const events = h.drainEvents();
|
|
106
|
+
await h.close();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
See [src/test/index.ts](src/test/index.ts) for the full surface
|
|
110
|
+
(`call`, `expectAcl`, `drainEvents`, `dispose`).
|
|
111
|
+
|
|
112
|
+
## Pytest plugin (`anna-executa-test`)
|
|
113
|
+
|
|
114
|
+
For executa authors. Lives in matrix-nexus at
|
|
115
|
+
`packages/anna-executa-test/`. Provides `executa_session` /
|
|
116
|
+
`executa_invoke` fixtures — see that package's README.
|
|
117
|
+
|
|
118
|
+
## Schema bundle sync
|
|
119
|
+
|
|
120
|
+
The CLI vendors `@anna-ai/app-schema` until the npm bundle ships:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pnpm sync:schema # default: ../matrix-nexus
|
|
124
|
+
pnpm sync:schema -- --from /path/to/... # override
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Roadmap
|
|
128
|
+
|
|
129
|
+
| Phase | Status | Scope |
|
|
130
|
+
| --- | --- | --- |
|
|
131
|
+
| 2 — `init` + `validate` | ✅ MVP | Schema/ACL checks |
|
|
132
|
+
| 3 — `dev` harness | ✅ MVP | Stdio bridge, dashboard, hot-reload |
|
|
133
|
+
| 4 — `@anna-ai/cli/test` | ✅ MVP | Programmatic `mountBundle` |
|
|
134
|
+
| 5 — `anna-executa-test` (pytest) | ✅ MVP | Plugin shipped from matrix-nexus |
|
|
135
|
+
| 6 — `fixture {verify,summarize,replay}` | ✅ MVP (dry-run replay) | Live re-invoke deferred |
|
|
136
|
+
| 7 — Publish & distribution | ✅ done | uvx default + full `anna-app-core@0.2.0a1` extraction; end-users no longer need a matrix-nexus checkout |
|
|
137
|
+
| 8 — `manifest.dev` block + init template + 4 docs | ✅ done | See [matrix-nexus/docs/developers/apps/](https://github.com/openclaw/matrix-nexus/tree/main/docs/developers/apps): `local-dev.md`, `testing-bundle.md`, `testing-plugin.md`, `recording-replay.md` |
|
|
138
|
+
| 9 — Reference example upgraded | ✅ done | [`anna-executa-examples/examples/anna-app-focus-flow`](https://github.com/openclaw/anna-executa-examples/tree/main/examples/anna-app-focus-flow) ships `tests/{bundle,plugin}/`, fixtures, and `.github/workflows/anna-app.yml` |
|
|
139
|
+
|
|
140
|
+
## Pinned versions
|
|
141
|
+
|
|
142
|
+
- `PINNED_RUNTIME_VERSION = "0.2.0a1"` ([src/harness/bridge.ts](src/harness/bridge.ts))
|
|
143
|
+
- See `matrix-nexus/packages/VERSIONS.md` for the full toolchain matrix.
|
|
144
|
+
|
|
145
|
+
## Tests
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pnpm test
|
|
149
|
+
```
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { delimiter, resolve } from "node:path";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
|
|
5
|
+
//#region src/harness/bridge.ts
|
|
6
|
+
/**
|
|
7
|
+
* Pinned `anna-app-runtime-local` version for uvx mode. Bump in lock-step
|
|
8
|
+
* with `packages/VERSIONS.md` (matrix-nexus). The bridge passes this to
|
|
9
|
+
* `uvx <pkg>@<version>` so end users always run the dispatcher version
|
|
10
|
+
* the CLI was tested against.
|
|
11
|
+
*/
|
|
12
|
+
const PINNED_RUNTIME_VERSION = "0.2.0a1";
|
|
13
|
+
var PythonBridge = class {
|
|
14
|
+
proc = null;
|
|
15
|
+
nextId = 1;
|
|
16
|
+
pending = new Map();
|
|
17
|
+
readyPromise = null;
|
|
18
|
+
closed = false;
|
|
19
|
+
constructor(opts) {
|
|
20
|
+
this.opts = opts;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build the default launch command from `opts.mode`. uvx by default; for
|
|
24
|
+
* `nexus-source` mode, `matrixNexusRoot` must be set.
|
|
25
|
+
*/
|
|
26
|
+
buildDefaultCommand() {
|
|
27
|
+
const mode = this.opts.mode ?? "uvx";
|
|
28
|
+
if (mode === "nexus-source") {
|
|
29
|
+
if (!this.opts.matrixNexusRoot) throw new Error("BridgeOptions.matrixNexusRoot is required when mode=\"nexus-source\"");
|
|
30
|
+
return [
|
|
31
|
+
"uv",
|
|
32
|
+
"run",
|
|
33
|
+
"--project",
|
|
34
|
+
this.opts.matrixNexusRoot,
|
|
35
|
+
"python",
|
|
36
|
+
"-m",
|
|
37
|
+
"anna_app_runtime_local.bridge"
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
const version = this.opts.runtimeVersion ?? PINNED_RUNTIME_VERSION;
|
|
41
|
+
return [
|
|
42
|
+
"uvx",
|
|
43
|
+
`anna-app-runtime-local@${version}`,
|
|
44
|
+
"anna-app-bridge"
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
/** Spawn the Python process and wait for the `_ready` notification. */
|
|
48
|
+
async start() {
|
|
49
|
+
if (this.proc) return;
|
|
50
|
+
const cmd = this.opts.runtimeCommand ?? this.buildDefaultCommand();
|
|
51
|
+
const [bin, ...args] = cmd;
|
|
52
|
+
const useNexusPathInjection = !this.opts.runtimeCommand && (this.opts.mode ?? "uvx") === "nexus-source" && !!this.opts.matrixNexusRoot;
|
|
53
|
+
const env = { ...process.env };
|
|
54
|
+
if (useNexusPathInjection) {
|
|
55
|
+
const wheelSrc = resolve(this.opts.matrixNexusRoot, "packages/anna-app-runtime-local/src");
|
|
56
|
+
const existingPath = process.env.PYTHONPATH ?? "";
|
|
57
|
+
env.PYTHONPATH = existingPath ? `${wheelSrc}${delimiter}${existingPath}` : wheelSrc;
|
|
58
|
+
}
|
|
59
|
+
const cwd = (this.opts.mode ?? "uvx") === "nexus-source" ? this.opts.matrixNexusRoot : process.cwd();
|
|
60
|
+
if (!bin) throw new Error("PythonBridge: empty launch command (no executable)");
|
|
61
|
+
this.proc = spawn(bin, args, {
|
|
62
|
+
cwd,
|
|
63
|
+
stdio: [
|
|
64
|
+
"pipe",
|
|
65
|
+
"pipe",
|
|
66
|
+
"pipe"
|
|
67
|
+
],
|
|
68
|
+
env
|
|
69
|
+
});
|
|
70
|
+
this.proc.on("exit", (code, signal) => {
|
|
71
|
+
this.closed = true;
|
|
72
|
+
const err = new Error(`python bridge exited (code=${code} signal=${signal ?? "-"})`);
|
|
73
|
+
for (const p of this.pending.values()) p.reject(err);
|
|
74
|
+
this.pending.clear();
|
|
75
|
+
});
|
|
76
|
+
const stderrSink = this.opts.onStderr ?? ((l) => process.stderr.write(`[bridge] ${l}\n`));
|
|
77
|
+
createInterface({ input: this.proc.stderr }).on("line", stderrSink);
|
|
78
|
+
this.readyPromise = new Promise((resolve$1, reject) => {
|
|
79
|
+
const rl = createInterface({ input: this.proc.stdout });
|
|
80
|
+
let ready = false;
|
|
81
|
+
rl.on("line", (line) => {
|
|
82
|
+
if (!line.trim()) return;
|
|
83
|
+
let env$1;
|
|
84
|
+
try {
|
|
85
|
+
env$1 = JSON.parse(line);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
stderrSink(`non-json from bridge stdout: ${line}`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!ready && env$1.method === "_ready") {
|
|
91
|
+
ready = true;
|
|
92
|
+
resolve$1();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.handleResponse(env$1);
|
|
96
|
+
});
|
|
97
|
+
this.proc.on("error", (e) => reject(e));
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
if (!ready) reject(new Error("python bridge did not signal ready in 8s"));
|
|
100
|
+
}, 8e3);
|
|
101
|
+
});
|
|
102
|
+
await this.readyPromise;
|
|
103
|
+
}
|
|
104
|
+
handleResponse(env) {
|
|
105
|
+
const id = env.id;
|
|
106
|
+
if (id == null) return;
|
|
107
|
+
const pending = this.pending.get(id);
|
|
108
|
+
if (!pending) return;
|
|
109
|
+
this.pending.delete(id);
|
|
110
|
+
if (env.error) {
|
|
111
|
+
const e = env.error;
|
|
112
|
+
const err = new Error(`[${e.code}] ${e.message}`);
|
|
113
|
+
err.rpc = e;
|
|
114
|
+
pending.reject(err);
|
|
115
|
+
} else pending.resolve(env.result);
|
|
116
|
+
}
|
|
117
|
+
/** Send one JSON-RPC request and await its response. */
|
|
118
|
+
call(method, params = {}) {
|
|
119
|
+
if (this.closed || !this.proc) return Promise.reject(new Error("python bridge not running"));
|
|
120
|
+
const id = this.nextId++;
|
|
121
|
+
const env = {
|
|
122
|
+
jsonrpc: "2.0",
|
|
123
|
+
id,
|
|
124
|
+
method,
|
|
125
|
+
params
|
|
126
|
+
};
|
|
127
|
+
return new Promise((resolve$1, reject) => {
|
|
128
|
+
this.pending.set(id, {
|
|
129
|
+
resolve: (v) => resolve$1(v),
|
|
130
|
+
reject
|
|
131
|
+
});
|
|
132
|
+
this.proc.stdin.write(`${JSON.stringify(env)}\n`);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async stop() {
|
|
136
|
+
if (!this.proc) return;
|
|
137
|
+
this.closed = true;
|
|
138
|
+
this.proc.stdin.end();
|
|
139
|
+
this.proc.kill("SIGTERM");
|
|
140
|
+
this.proc = null;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
export { PINNED_RUNTIME_VERSION, PythonBridge };
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import kleur from "kleur";
|
|
7
|
+
|
|
8
|
+
//#region src/commands/init.ts
|
|
9
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
function templateRoot(template) {
|
|
11
|
+
for (const cand of [resolve(here, "..", "..", "templates", template), resolve(here, "..", "templates", template)]) if (existsSync(cand)) return cand;
|
|
12
|
+
throw new Error(`template not found: ${template}`);
|
|
13
|
+
}
|
|
14
|
+
function substitute(content, slug) {
|
|
15
|
+
const toolId = `tool-dev-${slug}`;
|
|
16
|
+
return content.replace(/__SLUG__/g, slug).replace(/__TOOL_ID__/g, toolId);
|
|
17
|
+
}
|
|
18
|
+
function copyDirWithSubst(src, dst, slug) {
|
|
19
|
+
mkdirSync(dst, { recursive: true });
|
|
20
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
21
|
+
const s = join(src, entry.name);
|
|
22
|
+
const d = join(dst, entry.name);
|
|
23
|
+
if (entry.isDirectory()) copyDirWithSubst(s, d, slug);
|
|
24
|
+
else if (entry.isFile()) {
|
|
25
|
+
const stat = statSync(s);
|
|
26
|
+
if (stat.size < 256 * 1024) {
|
|
27
|
+
const buf = readFileSync(s);
|
|
28
|
+
const looksText = !buf.includes(0);
|
|
29
|
+
if (looksText) {
|
|
30
|
+
writeFileSync(d, substitute(buf.toString("utf-8"), slug), "utf-8");
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
cpSync(s, d);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function runInit(opts) {
|
|
39
|
+
const target = resolve(process.cwd(), opts.targetDir);
|
|
40
|
+
if (existsSync(target) && !opts.force) {
|
|
41
|
+
if (readdirSync(target).filter((n) => !n.startsWith(".")).length > 0) {
|
|
42
|
+
console.error(kleur.red(`✗ target dir not empty: ${target} (use --force to override)`));
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!/^[a-z][a-z0-9-]{1,40}$/.test(opts.slug)) {
|
|
47
|
+
console.error(kleur.red(`✗ invalid slug "${opts.slug}": must match /^[a-z][a-z0-9-]{1,40}$/`));
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
const tplRoot = templateRoot(opts.template);
|
|
51
|
+
copyDirWithSubst(tplRoot, target, opts.slug);
|
|
52
|
+
console.log(kleur.green(`✓ scaffolded "${opts.slug}" at ${target}`));
|
|
53
|
+
console.log(kleur.gray(` next: cd ${opts.targetDir} && anna-app validate`));
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/cli.ts
|
|
59
|
+
const program = new Command();
|
|
60
|
+
program.name("anna-app").description("Anna App developer CLI (scaffold, validate, harness)").version("0.1.0");
|
|
61
|
+
program.command("init <dir>").description("Scaffold a new Anna App project").option("--slug <slug>", "App slug (lowercase, hyphens)", "").option("--template <name>", "Template to use", "minimal").option("--force", "Overwrite existing dir if non-empty", false).action((dir, opts) => {
|
|
62
|
+
const slug = opts.slug || dir.split(/[\\/]/).pop() || "my-anna-app";
|
|
63
|
+
const code = runInit({
|
|
64
|
+
targetDir: dir,
|
|
65
|
+
slug,
|
|
66
|
+
template: opts.template,
|
|
67
|
+
force: opts.force
|
|
68
|
+
});
|
|
69
|
+
process.exit(code);
|
|
70
|
+
});
|
|
71
|
+
program.command("dev").description("Run a local harness (in-process dispatcher + iframe + SSE relay)").option("--manifest <path>", "manifest.json path", "manifest.json").option("--bundle <dir>", "bundle directory (default: ./bundle)").option("--slug <slug>", "App slug (overrides manifest.slug/name)").option("--view <name>", "View name to open (default: manifest default)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (auto-detected if omitted; can also use $ANNA_NEXUS_ROOT)").option("--port <number>", "HTTP port", "5180").option("--user-id <id>", "Harness user_id", "1").option("--cwd <dir>", "Project root (default: cwd)").option("--no-watch", "Disable bundle file watcher (default: enabled)").action(async (opts) => {
|
|
72
|
+
const { runDev } = await import("./dev-Bgku5ngL.js");
|
|
73
|
+
const code = await runDev({
|
|
74
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
75
|
+
manifestPath: opts.manifest,
|
|
76
|
+
bundleDir: opts.bundle,
|
|
77
|
+
slug: opts.slug,
|
|
78
|
+
view: opts.view,
|
|
79
|
+
matrixNexusRoot: opts.matrixNexusRoot,
|
|
80
|
+
port: Number.parseInt(opts.port, 10),
|
|
81
|
+
userId: Number.parseInt(opts.userId, 10),
|
|
82
|
+
noWatch: opts.watch === false
|
|
83
|
+
});
|
|
84
|
+
process.exit(code);
|
|
85
|
+
});
|
|
86
|
+
const fixture = program.command("fixture").description("Inspect / replay harness recordings (Phase 6)");
|
|
87
|
+
fixture.command("verify <file>").description("Schema + invariant checks on a harness JSONL recording").option("--json", "Emit machine-readable JSON", false).action(async (file, opts) => {
|
|
88
|
+
const { runFixtureVerify } = await import("./fixture-BGjMtqWA.js");
|
|
89
|
+
const code = await runFixtureVerify({
|
|
90
|
+
file,
|
|
91
|
+
json: opts.json
|
|
92
|
+
});
|
|
93
|
+
process.exit(code);
|
|
94
|
+
});
|
|
95
|
+
fixture.command("summarize <file>").description("Print a human-readable digest of a harness recording").option("--json", "Emit machine-readable JSON", false).action(async (file, opts) => {
|
|
96
|
+
const { runFixtureSummarize } = await import("./fixture-BGjMtqWA.js");
|
|
97
|
+
const code = await runFixtureSummarize({
|
|
98
|
+
file,
|
|
99
|
+
json: opts.json
|
|
100
|
+
});
|
|
101
|
+
process.exit(code);
|
|
102
|
+
});
|
|
103
|
+
fixture.command("replay <file>").description("Dry-run replay of a harness recording (Phase 6 MVP)").option("--manifest <path>", "manifest.json path", "manifest.json").action(async (file, opts) => {
|
|
104
|
+
const { runFixtureReplay } = await import("./fixture-BGjMtqWA.js");
|
|
105
|
+
const code = await runFixtureReplay({
|
|
106
|
+
file,
|
|
107
|
+
manifest: opts.manifest
|
|
108
|
+
});
|
|
109
|
+
process.exit(code);
|
|
110
|
+
});
|
|
111
|
+
program.command("doctor").description("Check environment for `anna-app dev` (uv, matrix-nexus, dev key)").option("--matrix-nexus-root <path>", "matrix-nexus checkout (optional)").action(async (opts) => {
|
|
112
|
+
const { runDoctor } = await import("./doctor-D3z4YslL.js");
|
|
113
|
+
const code = await runDoctor({ matrixNexusRoot: opts.matrixNexusRoot });
|
|
114
|
+
process.exit(code);
|
|
115
|
+
});
|
|
116
|
+
program.parseAsync(process.argv).catch((e) => {
|
|
117
|
+
console.error(e);
|
|
118
|
+
process.exit(2);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
//#endregion
|