@anna-ai/cli 0.1.12 → 0.1.16

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 (46) hide show
  1. package/dist/agent-DUmINbo4.js +372 -0
  2. package/dist/{apps-CDe6Fjq2.js → apps-Bt9CT5Sl.js} +1 -1
  3. package/dist/bridge-AJilXBw2.js +3 -0
  4. package/dist/{bridge-BQUo6ehX.js → bridge-nqQ3-j-t.js} +1 -1
  5. package/dist/cli.js +78 -9
  6. package/dist/dev-BRlFgo2I.js +3 -0
  7. package/dist/{dev-DoY58pBM.js → dev-C6v5yRV2.js} +4 -4
  8. package/dist/dev-account-DCyjamBa.js +44 -0
  9. package/dist/dev-app-cache-DAHcq46m.js +175 -0
  10. package/dist/dev-app-cache-DGF2Kuzd.js +4 -0
  11. package/dist/{doctor-DP2UB10l.js → doctor-C8MWfLt8.js} +1 -1
  12. package/dist/executa-dev-4FZ7AJHR.js +259 -0
  13. package/dist/executa-init-COEmKDOE.js +68 -0
  14. package/dist/executa-register-66WKIwQQ.js +47 -0
  15. package/dist/host_upload-C_pGOS6p.js +136 -0
  16. package/dist/image-bwolX7pa.js +131 -0
  17. package/dist/mascot-wlYTJqMs.js +218 -0
  18. package/dist/runner-DmGLdat0.js +322 -0
  19. package/dist/sampling-CJUDG-mf.js +155 -0
  20. package/dist/storage-EQJA_0UW.js +316 -0
  21. package/package.json +1 -1
  22. package/templates/executa/go/README.md +10 -0
  23. package/templates/executa/go/executa.json +4 -0
  24. package/templates/executa/go/go.mod +3 -0
  25. package/templates/executa/go/main.go +148 -0
  26. package/templates/executa/node/README.md +12 -0
  27. package/templates/executa/node/executa.json +4 -0
  28. package/templates/executa/node/package.json +12 -0
  29. package/templates/executa/node/plugin.mjs +126 -0
  30. package/templates/executa/node/sampling-fixture.jsonl +1 -0
  31. package/templates/executa/python/README.md +23 -0
  32. package/templates/executa/python/__SLUG_PY___plugin.py +146 -0
  33. package/templates/executa/python/executa.json +4 -0
  34. package/templates/executa/python/pyproject.toml +15 -0
  35. package/templates/executa/python/sampling-fixture.jsonl +4 -0
  36. package/templates/minimal/bundle/app.js +2 -0
  37. package/templates/minimal/bundle/index.html +1 -2
  38. package/dist/bridge-BEHyfpPI.js +0 -3
  39. package/dist/dev-app-cache-BMfOlTHd.js +0 -93
  40. package/dist/dev-app-cache-cXvO2XwQ.js +0 -4
  41. /package/dist/{credentials-CIOYq2Lm.js → credentials-DDqx6XMQ.js} +0 -0
  42. /package/dist/{fixture-BEu4LXLG.js → fixture-CATHyLLI.js} +0 -0
  43. /package/dist/{login-dl1Zfny8.js → login-CsIVbrmf.js} +0 -0
  44. /package/dist/{logout-DablvlFs.js → logout-gfmKQxMj.js} +0 -0
  45. /package/dist/{server-NXmiWJjX.js → server-D8R6ppOp.js} +0 -0
  46. /package/dist/{whoami-giXOY415.js → whoami-BS5wy-Nh.js} +0 -0
@@ -0,0 +1,322 @@
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
+ image: {},
13
+ "image.edit": {},
14
+ upload: {}
15
+ },
16
+ client_info: {
17
+ name: "anna-app-cli",
18
+ version: "executa-dev"
19
+ }
20
+ };
21
+ var ExecutaRunner = class {
22
+ proc = null;
23
+ nextId = 1;
24
+ pending = new Map();
25
+ exitWaiters = [];
26
+ closed = false;
27
+ /** Resolved by start() after the v2 handshake (or v1 fallback) succeeds. */
28
+ initialized = null;
29
+ constructor(opts) {
30
+ this.opts = opts;
31
+ }
32
+ /** Spawn the subprocess and perform the initialize handshake. */
33
+ async start() {
34
+ if (this.proc) {
35
+ if (!this.initialized) throw new Error("executa already starting");
36
+ return this.initialized;
37
+ }
38
+ const [bin, ...args] = this.opts.command;
39
+ if (!bin) throw new Error("ExecutaRunner: empty command");
40
+ const env = {
41
+ ...process.env,
42
+ ...this.opts.env
43
+ };
44
+ env.ANNA_EXECUTA_DEV = "1";
45
+ this.proc = spawn(bin, args, {
46
+ cwd: this.opts.cwd,
47
+ stdio: [
48
+ "pipe",
49
+ "pipe",
50
+ "pipe"
51
+ ],
52
+ env
53
+ });
54
+ const stderrSink = this.opts.onStderr ?? ((l) => process.stderr.write(`[executa] ${l}\n`));
55
+ createInterface({ input: this.proc.stderr }).on("line", stderrSink);
56
+ this.proc.on("exit", (code) => {
57
+ this.closed = true;
58
+ const waiters = this.exitWaiters.splice(0);
59
+ for (const w of waiters) w(code);
60
+ const err = new Error(`executa exited (code=${code ?? "null"})`);
61
+ for (const p of this.pending.values()) p.reject(err);
62
+ this.pending.clear();
63
+ });
64
+ this.proc.on("error", (e) => {
65
+ this.closed = true;
66
+ for (const p of this.pending.values()) p.reject(e);
67
+ this.pending.clear();
68
+ });
69
+ createInterface({ input: this.proc.stdout }).on("line", (line) => {
70
+ this.handleLine(line, stderrSink);
71
+ });
72
+ const initTimeout = this.opts.initTimeoutMs ?? 8e3;
73
+ try {
74
+ const raw = await this.callWithTimeout("initialize", HOST_INITIALIZE_PARAMS, initTimeout);
75
+ const ver = String(raw.protocolVersion ?? "1.0");
76
+ this.initialized = {
77
+ protocolVersion: ver === "2.0" ? "2.0" : "1.0",
78
+ raw
79
+ };
80
+ } catch (e) {
81
+ const rpc = e.rpc;
82
+ if (rpc && rpc.code === -32601) this.initialized = {
83
+ protocolVersion: "1.0",
84
+ raw: {}
85
+ };
86
+ else {
87
+ await this.stop();
88
+ throw e;
89
+ }
90
+ }
91
+ return this.initialized;
92
+ }
93
+ /** Send a JSON-RPC request and await the response. */
94
+ call(method, params = {}) {
95
+ if (this.closed || !this.proc) return Promise.reject(new Error("executa not running"));
96
+ const id = this.nextId++;
97
+ const env = {
98
+ jsonrpc: "2.0",
99
+ id,
100
+ method,
101
+ params
102
+ };
103
+ return new Promise((resolve, reject) => {
104
+ this.pending.set(id, {
105
+ resolve: (v) => resolve(v),
106
+ reject
107
+ });
108
+ this.proc.stdin.write(`${JSON.stringify(env)}\n`);
109
+ });
110
+ }
111
+ /** Convenience: `describe → MANIFEST`. */
112
+ describe() {
113
+ return this.call("describe");
114
+ }
115
+ /** Convenience: `invoke {tool, arguments}`. */
116
+ invoke(tool, args) {
117
+ return this.call("invoke", {
118
+ tool,
119
+ arguments: args
120
+ });
121
+ }
122
+ /** Convenience: `health → {status}`. */
123
+ health() {
124
+ return this.call("health");
125
+ }
126
+ /** Close stdin (graceful EOF shutdown) then SIGTERM after a grace period. */
127
+ async stop(graceMs = 1500) {
128
+ if (!this.proc) return;
129
+ this.closed = true;
130
+ const p = this.proc;
131
+ try {
132
+ p.stdin.end();
133
+ } catch {}
134
+ const waitExit = new Promise((resolve) => {
135
+ this.exitWaiters.push(resolve);
136
+ if (p.exitCode != null) resolve(p.exitCode);
137
+ });
138
+ const timer = new Promise((resolve) => {
139
+ setTimeout(() => resolve("timeout"), graceMs);
140
+ });
141
+ const winner = await Promise.race([waitExit, timer]);
142
+ if (winner === "timeout") try {
143
+ p.kill("SIGTERM");
144
+ } catch {}
145
+ this.proc = null;
146
+ }
147
+ async callWithTimeout(method, params, ms) {
148
+ const id = this.nextId++;
149
+ const env = {
150
+ jsonrpc: "2.0",
151
+ id,
152
+ method,
153
+ params
154
+ };
155
+ return new Promise((resolve, reject) => {
156
+ const to = setTimeout(() => {
157
+ this.pending.delete(id);
158
+ reject(new Error(`executa ${method} timed out after ${ms}ms`));
159
+ }, ms);
160
+ this.pending.set(id, {
161
+ resolve: (v) => {
162
+ clearTimeout(to);
163
+ resolve(v);
164
+ },
165
+ reject: (e) => {
166
+ clearTimeout(to);
167
+ reject(e);
168
+ }
169
+ });
170
+ this.proc.stdin.write(`${JSON.stringify(env)}\n`);
171
+ });
172
+ }
173
+ handleLine(line, stderrSink) {
174
+ if (!line.trim()) return;
175
+ let env;
176
+ try {
177
+ env = JSON.parse(line);
178
+ } catch {
179
+ stderrSink(`non-json from executa stdout: ${line}`);
180
+ return;
181
+ }
182
+ if (typeof env.method === "string") {
183
+ this.handleReverse(env);
184
+ return;
185
+ }
186
+ const id = env.id;
187
+ if (typeof id !== "number") return;
188
+ const pending = this.pending.get(id);
189
+ if (!pending) return;
190
+ this.pending.delete(id);
191
+ if (env.error) {
192
+ const e = env.error;
193
+ const err = new Error(`[${e.code}] ${e.message}`);
194
+ err.rpc = e;
195
+ pending.reject(err);
196
+ } else pending.resolve(env.result);
197
+ }
198
+ async handleReverse(env) {
199
+ const id = env.id;
200
+ const method = env.method;
201
+ const params = env.params ?? {};
202
+ const respond = (body) => {
203
+ if (id === void 0 || id === null) return;
204
+ if (!this.proc) return;
205
+ const out = {
206
+ jsonrpc: "2.0",
207
+ id,
208
+ ...body
209
+ };
210
+ try {
211
+ this.proc.stdin.write(`${JSON.stringify(out)}\n`);
212
+ } catch {}
213
+ };
214
+ if (method === "sampling/createMessage") {
215
+ if (!this.opts.sampling) {
216
+ respond({ error: {
217
+ code: -32008,
218
+ message: "sampling not configured (use `anna-app login` and rerun without `--no-sampling`, or pass `--mock-sampling`)"
219
+ } });
220
+ return;
221
+ }
222
+ try {
223
+ const result = await this.opts.sampling.createMessage(params);
224
+ respond({ result });
225
+ } catch (e) {
226
+ const code = e.rpcCode ?? -32e3;
227
+ respond({ error: {
228
+ code,
229
+ message: e.message
230
+ } });
231
+ }
232
+ return;
233
+ }
234
+ if (method.startsWith("agent/")) {
235
+ if (!this.opts.agent) {
236
+ respond({ error: {
237
+ code: -32041,
238
+ message: "agent reverse-RPC not configured (rerun without `--no-agent`, or pass `--mock-agent <fixture>`)"
239
+ } });
240
+ return;
241
+ }
242
+ try {
243
+ const result = await this.opts.agent.call(method, params);
244
+ respond({ result });
245
+ } catch (e) {
246
+ const code = e.rpcCode ?? -32e3;
247
+ respond({ error: {
248
+ code,
249
+ message: e.message
250
+ } });
251
+ }
252
+ return;
253
+ }
254
+ if (method === "image/generate" || method === "image/edit") {
255
+ if (!this.opts.image) {
256
+ respond({ error: {
257
+ code: -32101,
258
+ message: "image not configured (rerun without `--no-image`, or pass `--mock-image <fixture>` / `--app-slug`)"
259
+ } });
260
+ return;
261
+ }
262
+ try {
263
+ const result = method === "image/generate" ? await this.opts.image.generate(params) : await this.opts.image.edit(params);
264
+ respond({ result });
265
+ } catch (e) {
266
+ const code = e.rpcCode ?? -32103;
267
+ respond({ error: {
268
+ code,
269
+ message: e.message
270
+ } });
271
+ }
272
+ return;
273
+ }
274
+ if (method === "host/uploadFile") {
275
+ if (!this.opts.hostUpload) {
276
+ respond({ error: {
277
+ code: -32201,
278
+ message: "host upload not configured (rerun without `--no-upload`, or pass `--mock-upload <fixture>` / `--app-slug`)"
279
+ } });
280
+ return;
281
+ }
282
+ try {
283
+ const result = await this.opts.hostUpload.call(method, params);
284
+ respond({ result });
285
+ } catch (e) {
286
+ const code = e.rpcCode ?? -32207;
287
+ respond({ error: {
288
+ code,
289
+ message: e.message
290
+ } });
291
+ }
292
+ return;
293
+ }
294
+ if (method.startsWith("storage/") || method.startsWith("files/")) {
295
+ if (!this.opts.storage) {
296
+ respond({ error: {
297
+ code: -32021,
298
+ message: "storage not configured (rerun with `--storage memory|mock|real`)"
299
+ } });
300
+ return;
301
+ }
302
+ try {
303
+ const result = await this.opts.storage.call(method, params);
304
+ respond({ result });
305
+ } catch (e) {
306
+ const code = e.rpcCode ?? -32e3;
307
+ respond({ error: {
308
+ code,
309
+ message: e.message
310
+ } });
311
+ }
312
+ return;
313
+ }
314
+ respond({ error: {
315
+ code: -32601,
316
+ message: `unknown reverse method: ${method}`
317
+ } });
318
+ }
319
+ };
320
+
321
+ //#endregion
322
+ 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 };