@anna-ai/cli 0.1.12 → 0.1.14

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.
Files changed (40) hide show
  1. package/dist/agent-DUmINbo4.js +372 -0
  2. package/dist/{apps-CDe6Fjq2.js → apps-BEJUn9Ws.js} +1 -1
  3. package/dist/bridge-C0DWb5eQ.js +3 -0
  4. package/dist/cli.js +72 -9
  5. package/dist/{dev-DoY58pBM.js → dev-BfLGxpiT.js} +4 -4
  6. package/dist/dev-C81H9c9_.js +3 -0
  7. package/dist/dev-account-DCyjamBa.js +44 -0
  8. package/dist/{dev-app-cache-cXvO2XwQ.js → dev-app-cache-CZ8lIKiw.js} +1 -1
  9. package/dist/{doctor-DP2UB10l.js → doctor-B3u0edUg.js} +1 -1
  10. package/dist/executa-dev-BhouP8jh.js +212 -0
  11. package/dist/executa-init-COEmKDOE.js +68 -0
  12. package/dist/executa-register-66WKIwQQ.js +47 -0
  13. package/dist/mascot-wlYTJqMs.js +218 -0
  14. package/dist/runner-Bral1LFW.js +279 -0
  15. package/dist/sampling-3EfSlDHM.js +155 -0
  16. package/dist/storage-CnWTZqq_.js +316 -0
  17. package/package.json +1 -1
  18. package/templates/executa/go/README.md +10 -0
  19. package/templates/executa/go/executa.json +4 -0
  20. package/templates/executa/go/go.mod +3 -0
  21. package/templates/executa/go/main.go +148 -0
  22. package/templates/executa/node/README.md +12 -0
  23. package/templates/executa/node/executa.json +4 -0
  24. package/templates/executa/node/package.json +12 -0
  25. package/templates/executa/node/plugin.mjs +126 -0
  26. package/templates/executa/node/sampling-fixture.jsonl +1 -0
  27. package/templates/executa/python/README.md +23 -0
  28. package/templates/executa/python/__SLUG_PY___plugin.py +146 -0
  29. package/templates/executa/python/executa.json +4 -0
  30. package/templates/executa/python/pyproject.toml +15 -0
  31. package/templates/executa/python/sampling-fixture.jsonl +4 -0
  32. package/dist/bridge-BEHyfpPI.js +0 -3
  33. /package/dist/{bridge-BQUo6ehX.js → bridge-D6YyP9DM.js} +0 -0
  34. /package/dist/{credentials-CIOYq2Lm.js → credentials-DDqx6XMQ.js} +0 -0
  35. /package/dist/{dev-app-cache-BMfOlTHd.js → dev-app-cache-C3D1Sp_V.js} +0 -0
  36. /package/dist/{fixture-BEu4LXLG.js → fixture-CATHyLLI.js} +0 -0
  37. /package/dist/{login-dl1Zfny8.js → login-CsIVbrmf.js} +0 -0
  38. /package/dist/{logout-DablvlFs.js → logout-gfmKQxMj.js} +0 -0
  39. /package/dist/{server-NXmiWJjX.js → server-q6nKCeEV.js} +0 -0
  40. /package/dist/{whoami-giXOY415.js → whoami-BS5wy-Nh.js} +0 -0
@@ -0,0 +1,279 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createInterface } from "node:readline";
3
+
4
+ //#region src/executa/runner.ts
5
+ /** Spec describing where the host advertises its capabilities. */
6
+ const HOST_INITIALIZE_PARAMS = {
7
+ protocolVersion: "2.0",
8
+ client_capabilities: {
9
+ sampling: {},
10
+ agent: {},
11
+ storage: {}
12
+ },
13
+ client_info: {
14
+ name: "anna-app-cli",
15
+ version: "executa-dev"
16
+ }
17
+ };
18
+ var ExecutaRunner = class {
19
+ proc = null;
20
+ nextId = 1;
21
+ pending = new Map();
22
+ exitWaiters = [];
23
+ closed = false;
24
+ /** Resolved by start() after the v2 handshake (or v1 fallback) succeeds. */
25
+ initialized = null;
26
+ constructor(opts) {
27
+ this.opts = opts;
28
+ }
29
+ /** Spawn the subprocess and perform the initialize handshake. */
30
+ async start() {
31
+ if (this.proc) {
32
+ if (!this.initialized) throw new Error("executa already starting");
33
+ return this.initialized;
34
+ }
35
+ const [bin, ...args] = this.opts.command;
36
+ if (!bin) throw new Error("ExecutaRunner: empty command");
37
+ const env = {
38
+ ...process.env,
39
+ ...this.opts.env
40
+ };
41
+ env.ANNA_EXECUTA_DEV = "1";
42
+ this.proc = spawn(bin, args, {
43
+ cwd: this.opts.cwd,
44
+ stdio: [
45
+ "pipe",
46
+ "pipe",
47
+ "pipe"
48
+ ],
49
+ env
50
+ });
51
+ const stderrSink = this.opts.onStderr ?? ((l) => process.stderr.write(`[executa] ${l}\n`));
52
+ createInterface({ input: this.proc.stderr }).on("line", stderrSink);
53
+ this.proc.on("exit", (code) => {
54
+ this.closed = true;
55
+ const waiters = this.exitWaiters.splice(0);
56
+ for (const w of waiters) w(code);
57
+ const err = new Error(`executa exited (code=${code ?? "null"})`);
58
+ for (const p of this.pending.values()) p.reject(err);
59
+ this.pending.clear();
60
+ });
61
+ this.proc.on("error", (e) => {
62
+ this.closed = true;
63
+ for (const p of this.pending.values()) p.reject(e);
64
+ this.pending.clear();
65
+ });
66
+ createInterface({ input: this.proc.stdout }).on("line", (line) => {
67
+ this.handleLine(line, stderrSink);
68
+ });
69
+ const initTimeout = this.opts.initTimeoutMs ?? 8e3;
70
+ try {
71
+ const raw = await this.callWithTimeout("initialize", HOST_INITIALIZE_PARAMS, initTimeout);
72
+ const ver = String(raw.protocolVersion ?? "1.0");
73
+ this.initialized = {
74
+ protocolVersion: ver === "2.0" ? "2.0" : "1.0",
75
+ raw
76
+ };
77
+ } catch (e) {
78
+ const rpc = e.rpc;
79
+ if (rpc && rpc.code === -32601) this.initialized = {
80
+ protocolVersion: "1.0",
81
+ raw: {}
82
+ };
83
+ else {
84
+ await this.stop();
85
+ throw e;
86
+ }
87
+ }
88
+ return this.initialized;
89
+ }
90
+ /** Send a JSON-RPC request and await the response. */
91
+ call(method, params = {}) {
92
+ if (this.closed || !this.proc) return Promise.reject(new Error("executa not running"));
93
+ const id = this.nextId++;
94
+ const env = {
95
+ jsonrpc: "2.0",
96
+ id,
97
+ method,
98
+ params
99
+ };
100
+ return new Promise((resolve, reject) => {
101
+ this.pending.set(id, {
102
+ resolve: (v) => resolve(v),
103
+ reject
104
+ });
105
+ this.proc.stdin.write(`${JSON.stringify(env)}\n`);
106
+ });
107
+ }
108
+ /** Convenience: `describe → MANIFEST`. */
109
+ describe() {
110
+ return this.call("describe");
111
+ }
112
+ /** Convenience: `invoke {tool, arguments}`. */
113
+ invoke(tool, args) {
114
+ return this.call("invoke", {
115
+ tool,
116
+ arguments: args
117
+ });
118
+ }
119
+ /** Convenience: `health → {status}`. */
120
+ health() {
121
+ return this.call("health");
122
+ }
123
+ /** Close stdin (graceful EOF shutdown) then SIGTERM after a grace period. */
124
+ async stop(graceMs = 1500) {
125
+ if (!this.proc) return;
126
+ this.closed = true;
127
+ const p = this.proc;
128
+ try {
129
+ p.stdin.end();
130
+ } catch {}
131
+ const waitExit = new Promise((resolve) => {
132
+ this.exitWaiters.push(resolve);
133
+ if (p.exitCode != null) resolve(p.exitCode);
134
+ });
135
+ const timer = new Promise((resolve) => {
136
+ setTimeout(() => resolve("timeout"), graceMs);
137
+ });
138
+ const winner = await Promise.race([waitExit, timer]);
139
+ if (winner === "timeout") try {
140
+ p.kill("SIGTERM");
141
+ } catch {}
142
+ this.proc = null;
143
+ }
144
+ async callWithTimeout(method, params, ms) {
145
+ const id = this.nextId++;
146
+ const env = {
147
+ jsonrpc: "2.0",
148
+ id,
149
+ method,
150
+ params
151
+ };
152
+ return new Promise((resolve, reject) => {
153
+ const to = setTimeout(() => {
154
+ this.pending.delete(id);
155
+ reject(new Error(`executa ${method} timed out after ${ms}ms`));
156
+ }, ms);
157
+ this.pending.set(id, {
158
+ resolve: (v) => {
159
+ clearTimeout(to);
160
+ resolve(v);
161
+ },
162
+ reject: (e) => {
163
+ clearTimeout(to);
164
+ reject(e);
165
+ }
166
+ });
167
+ this.proc.stdin.write(`${JSON.stringify(env)}\n`);
168
+ });
169
+ }
170
+ handleLine(line, stderrSink) {
171
+ if (!line.trim()) return;
172
+ let env;
173
+ try {
174
+ env = JSON.parse(line);
175
+ } catch {
176
+ stderrSink(`non-json from executa stdout: ${line}`);
177
+ return;
178
+ }
179
+ if (typeof env.method === "string") {
180
+ this.handleReverse(env);
181
+ return;
182
+ }
183
+ const id = env.id;
184
+ if (typeof id !== "number") return;
185
+ const pending = this.pending.get(id);
186
+ if (!pending) return;
187
+ this.pending.delete(id);
188
+ if (env.error) {
189
+ const e = env.error;
190
+ const err = new Error(`[${e.code}] ${e.message}`);
191
+ err.rpc = e;
192
+ pending.reject(err);
193
+ } else pending.resolve(env.result);
194
+ }
195
+ async handleReverse(env) {
196
+ const id = env.id;
197
+ const method = env.method;
198
+ const params = env.params ?? {};
199
+ const respond = (body) => {
200
+ if (id === void 0 || id === null) return;
201
+ if (!this.proc) return;
202
+ const out = {
203
+ jsonrpc: "2.0",
204
+ id,
205
+ ...body
206
+ };
207
+ try {
208
+ this.proc.stdin.write(`${JSON.stringify(out)}\n`);
209
+ } catch {}
210
+ };
211
+ if (method === "sampling/createMessage") {
212
+ if (!this.opts.sampling) {
213
+ respond({ error: {
214
+ code: -32008,
215
+ message: "sampling not configured (use `anna-app login` and rerun without `--no-sampling`, or pass `--mock-sampling`)"
216
+ } });
217
+ return;
218
+ }
219
+ try {
220
+ const result = await this.opts.sampling.createMessage(params);
221
+ respond({ result });
222
+ } catch (e) {
223
+ const code = e.rpcCode ?? -32e3;
224
+ respond({ error: {
225
+ code,
226
+ message: e.message
227
+ } });
228
+ }
229
+ return;
230
+ }
231
+ if (method.startsWith("agent/")) {
232
+ if (!this.opts.agent) {
233
+ respond({ error: {
234
+ code: -32041,
235
+ message: "agent reverse-RPC not configured (rerun without `--no-agent`, or pass `--mock-agent <fixture>`)"
236
+ } });
237
+ return;
238
+ }
239
+ try {
240
+ const result = await this.opts.agent.call(method, params);
241
+ respond({ result });
242
+ } catch (e) {
243
+ const code = e.rpcCode ?? -32e3;
244
+ respond({ error: {
245
+ code,
246
+ message: e.message
247
+ } });
248
+ }
249
+ return;
250
+ }
251
+ if (method.startsWith("storage/") || method.startsWith("files/")) {
252
+ if (!this.opts.storage) {
253
+ respond({ error: {
254
+ code: -32021,
255
+ message: "storage not configured (rerun with `--storage memory|mock|real`)"
256
+ } });
257
+ return;
258
+ }
259
+ try {
260
+ const result = await this.opts.storage.call(method, params);
261
+ respond({ result });
262
+ } catch (e) {
263
+ const code = e.rpcCode ?? -32e3;
264
+ respond({ error: {
265
+ code,
266
+ message: e.message
267
+ } });
268
+ }
269
+ return;
270
+ }
271
+ respond({ error: {
272
+ code: -32601,
273
+ message: `unknown reverse method: ${method}`
274
+ } });
275
+ }
276
+ };
277
+
278
+ //#endregion
279
+ export { ExecutaRunner };
@@ -0,0 +1,155 @@
1
+ import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
2
+ import { resolve } from "node:path";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+
5
+ //#region src/executa/sampling.ts
6
+ var SamplingBridge = class {
7
+ mocks = [];
8
+ cachedSession = null;
9
+ constructor(opts) {
10
+ this.opts = opts;
11
+ if (opts.mode === "mock" && opts.mockFile) {
12
+ const path = resolve(opts.mockFile);
13
+ if (existsSync(path)) for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
14
+ if (!line.trim() || line.startsWith("#")) continue;
15
+ try {
16
+ this.mocks.push(JSON.parse(line));
17
+ } catch {}
18
+ }
19
+ }
20
+ }
21
+ async createMessage(req) {
22
+ if (this.opts.mode === "off") throw withCode(new Error("sampling disabled — rerun without `--no-sampling`"), -32008);
23
+ if (this.opts.mode === "mock") return this.mockMessage(req);
24
+ return this.realMessage(req);
25
+ }
26
+ mockMessage(req) {
27
+ const content = collectContent(req);
28
+ const entry = this.mocks.find((m) => m.ns === "sampling" && m.method === "createMessage" || m.ns === "llm" && m.method === "complete") ?? null;
29
+ const matched = entry && (!entry.match?.contentIncludes || content.includes(entry.match.contentIncludes)) ? entry : this.mocks.find((m) => m.ns === "sampling" && m.method === "createMessage" || m.ns === "llm" && m.method === "complete");
30
+ if (matched && matched.result) return normaliseSamplingResult(matched.result, "mock-model");
31
+ return {
32
+ role: "assistant",
33
+ content: {
34
+ type: "text",
35
+ text: "(mock) no fixture matched"
36
+ },
37
+ model: "mock-model",
38
+ stopReason: "endTurn"
39
+ };
40
+ }
41
+ account() {
42
+ const acc = getAccount(this.opts.account);
43
+ if (!acc) throw withCode(new Error("no PAT on disk — run `anna-app login --host <nexus-url>` first (or pass `--mock-sampling <fixture>` / `--no-sampling`)"), -32001);
44
+ if (acc.expires_at && acc.expires_at < Math.floor(Date.now() / 1e3)) throw withCode(new Error("PAT expired — run `anna-app login` again"), -32001);
45
+ return acc;
46
+ }
47
+ async mint() {
48
+ if (this.cachedSession && this.cachedSession.expiresAt - 30 > Math.floor(Date.now() / 1e3)) return this.cachedSession;
49
+ const acc = this.account();
50
+ if (!this.opts.appSlug) throw withCode(new Error("sampling bridge has no app_slug — pass `--app-slug <slug>` to `anna-app executa dev` so nexus can attribute the spend"), -32602);
51
+ const url = `${canonicalHost(acc.host)}/api/v1/anna-apps/dev/session/mint`;
52
+ const res = await fetch(url, {
53
+ method: "POST",
54
+ headers: { "content-type": "application/json" },
55
+ body: JSON.stringify({
56
+ pat: acc.pat,
57
+ kind: "complete",
58
+ app_slug: this.opts.appSlug
59
+ })
60
+ });
61
+ if (!res.ok) {
62
+ const text = await res.text().catch(() => "");
63
+ throw withCode(new Error(`session.mint failed: HTTP ${res.status} ${text}`), -32e3);
64
+ }
65
+ const body = await res.json();
66
+ this.cachedSession = {
67
+ appSessionToken: body.app_session_token,
68
+ expiresAt: Math.floor(Date.now() / 1e3) + (body.expires_in || 600)
69
+ };
70
+ return this.cachedSession;
71
+ }
72
+ async realMessage(req) {
73
+ const acc = this.account();
74
+ const session = await this.mint();
75
+ const body = mcpToCompleteBody(req);
76
+ const res = await fetch(`${canonicalHost(acc.host)}/api/v1/copilot/app/complete`, {
77
+ method: "POST",
78
+ headers: {
79
+ "content-type": "application/json",
80
+ authorization: `Bearer ${session.appSessionToken}`
81
+ },
82
+ body: JSON.stringify(body)
83
+ });
84
+ if (!res.ok) {
85
+ const text = await res.text().catch(() => "");
86
+ throw withCode(new Error(`HTTP ${res.status}: ${text}`), -32e3);
87
+ }
88
+ const out = await res.json();
89
+ return normaliseSamplingResult(out, out.model ?? "unknown");
90
+ }
91
+ };
92
+ function withCode(err, code) {
93
+ err.rpcCode = code;
94
+ return err;
95
+ }
96
+ /** Collect a single string view of a sampling request — used for mock matching. */
97
+ function collectContent(req) {
98
+ const parts = [];
99
+ if (req.systemPrompt) parts.push(req.systemPrompt);
100
+ for (const m of req.messages ?? []) {
101
+ const c = m.content;
102
+ if (typeof c === "string") parts.push(c);
103
+ else if (c && typeof c === "object" && "text" in c) parts.push(c.text);
104
+ }
105
+ return parts.join("\n");
106
+ }
107
+ /**
108
+ * Translate an MCP `sampling/createMessage` request to the nexus
109
+ * `/api/v1/copilot/app/complete` body. Mapping is intentionally lossy —
110
+ * we forward only the fields nexus understands.
111
+ */
112
+ function mcpToCompleteBody(req) {
113
+ const messages = (req.messages ?? []).map((m) => ({
114
+ role: m.role,
115
+ content: typeof m.content === "string" ? m.content : m.content.text
116
+ }));
117
+ const out = { messages };
118
+ if (req.systemPrompt) out.system = req.systemPrompt;
119
+ if (req.maxTokens != null) out.max_tokens = req.maxTokens;
120
+ if (req.temperature != null) out.temperature = req.temperature;
121
+ if (req.stopSequences) out.stop = req.stopSequences;
122
+ const hint = req.modelPreferences?.hints?.[0]?.name;
123
+ if (hint) out.model_hint = hint;
124
+ if (req.metadata) out.metadata = req.metadata;
125
+ return out;
126
+ }
127
+ /** Coerce arbitrary nexus / fixture output into a `SamplingResult`. */
128
+ function normaliseSamplingResult(raw, fallbackModel) {
129
+ if (!raw || typeof raw !== "object") return {
130
+ role: "assistant",
131
+ content: {
132
+ type: "text",
133
+ text: String(raw ?? "")
134
+ },
135
+ model: fallbackModel,
136
+ stopReason: "endTurn"
137
+ };
138
+ const r = raw;
139
+ if (r.role === "assistant" && r.content && typeof r.content === "object") return r;
140
+ const text = typeof r.text === "string" && r.text || (typeof r.content === "string" ? r.content : "") || (typeof r.message === "string" ? r.message : "") || "";
141
+ return {
142
+ role: "assistant",
143
+ content: {
144
+ type: "text",
145
+ text
146
+ },
147
+ model: r.model ?? fallbackModel,
148
+ stopReason: r.stop_reason ?? r.stopReason ?? "endTurn",
149
+ usage: r.usage ?? void 0,
150
+ _meta: { source: "nexus" }
151
+ };
152
+ }
153
+
154
+ //#endregion
155
+ export { SamplingBridge };