@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,175 @@
1
+ import { canonicalHost } from "./credentials-BTv2IfUZ.js";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+
5
+ //#region src/dev-app-cache.ts
6
+ const CACHE_DIR = ".anna";
7
+ const CACHE_FILE = "dev-app.json";
8
+ function cachePath(cwd) {
9
+ return resolve(cwd, CACHE_DIR, CACHE_FILE);
10
+ }
11
+ function readDevAppCache(cwd) {
12
+ const p = cachePath(cwd);
13
+ if (!existsSync(p)) return null;
14
+ try {
15
+ const raw = JSON.parse(readFileSync(p, "utf-8"));
16
+ if (typeof raw.host === "string" && typeof raw.slug === "string" && typeof raw.app_id === "number") return {
17
+ host: raw.host,
18
+ slug: raw.slug,
19
+ app_id: raw.app_id,
20
+ name: raw.name ?? raw.slug,
21
+ registered_at: raw.registered_at ?? ""
22
+ };
23
+ } catch {}
24
+ return null;
25
+ }
26
+ function writeDevAppCache(cwd, entry) {
27
+ const p = cachePath(cwd);
28
+ mkdirSync(dirname(p), { recursive: true });
29
+ writeFileSync(p, JSON.stringify(entry, null, 2) + "\n", "utf-8");
30
+ }
31
+ /** Call POST /api/v1/anna-apps/dev/apps/register. Idempotent server-side. */
32
+ async function registerDevApp(args) {
33
+ const url = `${canonicalHost(args.host)}/api/v1/anna-apps/dev/apps/register`;
34
+ const res = await fetch(url, {
35
+ method: "POST",
36
+ headers: { "content-type": "application/json" },
37
+ body: JSON.stringify({
38
+ pat: args.pat,
39
+ slug: args.input.slug,
40
+ name: args.input.name,
41
+ category: args.input.category,
42
+ tagline: args.input.tagline
43
+ })
44
+ });
45
+ if (!res.ok) {
46
+ const text = await res.text().catch(() => "");
47
+ throw new Error(`/dev/apps/register failed: HTTP ${res.status} ${text}`);
48
+ }
49
+ return await res.json();
50
+ }
51
+ /** Call GET /api/v1/anna-apps/dev/apps. */
52
+ async function listDevApps(args) {
53
+ const url = new URL(`${canonicalHost(args.host)}/api/v1/anna-apps/dev/apps`);
54
+ url.searchParams.set("pat", args.pat);
55
+ const res = await fetch(url, { method: "GET" });
56
+ if (!res.ok) {
57
+ const text = await res.text().catch(() => "");
58
+ throw new Error(`/dev/apps failed: HTTP ${res.status} ${text}`);
59
+ }
60
+ const body = await res.json();
61
+ return body.apps;
62
+ }
63
+ /**
64
+ * Convenience helper for `anna-app dev`: returns a valid cached entry if
65
+ * it still matches the manifest slug, otherwise hits the server to
66
+ * (re-)register. Throws if no PAT is available on disk.
67
+ */
68
+ async function ensureDevAppRegistered(args) {
69
+ const canonical = canonicalHost(args.host);
70
+ const cached = readDevAppCache(args.cwd);
71
+ if (cached && cached.host === canonical && cached.slug === args.input.slug) return cached;
72
+ const r = await registerDevApp({
73
+ host: args.host,
74
+ pat: args.pat,
75
+ input: args.input
76
+ });
77
+ const entry = {
78
+ host: canonical,
79
+ slug: r.slug,
80
+ app_id: r.app_id,
81
+ name: r.name,
82
+ registered_at: new Date().toISOString()
83
+ };
84
+ writeDevAppCache(args.cwd, entry);
85
+ return entry;
86
+ }
87
+ const _internal = {
88
+ cachePath,
89
+ CACHE_DIR: join(CACHE_DIR, CACHE_FILE)
90
+ };
91
+ const EXECUTA_CACHE_FILE = "dev-executa.json";
92
+ /**
93
+ * Bump this whenever the server-side dev-executa grant shape changes in
94
+ * a way that requires a re-register to pick up. Cached entries written
95
+ * with a lower `cache_version` are treated as stale and re-POSTed.
96
+ *
97
+ * 1 — initial (llm_grant only)
98
+ * 2 — adds image_grant + upload_grant; fixes `allowedPurposes` to
99
+ * protocol whitelist {image_input,image_reference,user_artifact}
100
+ */
101
+ const EXECUTA_CACHE_VERSION = 2;
102
+ function executaCachePath(cwd) {
103
+ return resolve(cwd, CACHE_DIR, EXECUTA_CACHE_FILE);
104
+ }
105
+ function readDevExecutaCache(cwd) {
106
+ const p = executaCachePath(cwd);
107
+ if (!existsSync(p)) return null;
108
+ try {
109
+ const raw = JSON.parse(readFileSync(p, "utf-8"));
110
+ if (typeof raw.host === "string" && typeof raw.slug === "string" && typeof raw.app_id === "number" && typeof raw.tool_id === "string") return {
111
+ host: raw.host,
112
+ slug: raw.slug,
113
+ app_id: raw.app_id,
114
+ tool_id: raw.tool_id,
115
+ name: raw.name ?? raw.tool_id,
116
+ registered_at: raw.registered_at ?? "",
117
+ cache_version: typeof raw.cache_version === "number" ? raw.cache_version : 0
118
+ };
119
+ } catch {}
120
+ return null;
121
+ }
122
+ function writeDevExecutaCache(cwd, entry) {
123
+ const p = executaCachePath(cwd);
124
+ mkdirSync(dirname(p), { recursive: true });
125
+ writeFileSync(p, JSON.stringify(entry, null, 2) + "\n", "utf-8");
126
+ }
127
+ /** Call `POST /api/v1/anna-apps/dev/executas/register`. Idempotent. */
128
+ async function registerDevExecuta(args) {
129
+ const url = `${canonicalHost(args.host)}/api/v1/anna-apps/dev/executas/register`;
130
+ const res = await fetch(url, {
131
+ method: "POST",
132
+ headers: { "content-type": "application/json" },
133
+ body: JSON.stringify({
134
+ pat: args.pat,
135
+ tool_id: args.toolId,
136
+ slug: args.slug,
137
+ name: args.name
138
+ })
139
+ });
140
+ if (!res.ok) {
141
+ const text = await res.text().catch(() => "");
142
+ throw new Error(`/dev/executas/register failed: HTTP ${res.status} ${text}`);
143
+ }
144
+ return await res.json();
145
+ }
146
+ /**
147
+ * Convenience: return a cached executa registration if still valid for
148
+ * this host+tool_id, otherwise hit the server. Throws if no PAT.
149
+ */
150
+ async function ensureDevExecutaRegistered(args) {
151
+ const canonical = canonicalHost(args.host);
152
+ const cached = readDevExecutaCache(args.cwd);
153
+ if (cached && cached.host === canonical && cached.tool_id === args.toolId && cached.cache_version >= EXECUTA_CACHE_VERSION && (!args.slug || cached.slug === args.slug)) return cached;
154
+ const r = await registerDevExecuta({
155
+ host: args.host,
156
+ pat: args.pat,
157
+ toolId: args.toolId,
158
+ slug: args.slug,
159
+ name: args.name
160
+ });
161
+ const entry = {
162
+ host: canonical,
163
+ slug: r.slug,
164
+ app_id: r.app_id,
165
+ tool_id: r.tool_id,
166
+ name: r.name,
167
+ registered_at: new Date().toISOString(),
168
+ cache_version: EXECUTA_CACHE_VERSION
169
+ };
170
+ writeDevExecutaCache(args.cwd, entry);
171
+ return entry;
172
+ }
173
+
174
+ //#endregion
175
+ export { ensureDevAppRegistered, ensureDevExecutaRegistered, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache };
@@ -0,0 +1,4 @@
1
+ import "./credentials-BTv2IfUZ.js";
2
+ import { ensureDevAppRegistered, ensureDevExecutaRegistered, listDevApps, readDevAppCache, readDevExecutaCache, registerDevApp, registerDevExecuta, writeDevAppCache, writeDevExecutaCache } from "./dev-app-cache-DAHcq46m.js";
3
+
4
+ export { ensureDevAppRegistered, ensureDevExecutaRegistered };
@@ -1,4 +1,4 @@
1
- import { PINNED_RUNTIME_VERSION } from "./bridge-BQUo6ehX.js";
1
+ import { PINNED_RUNTIME_VERSION } from "./bridge-nqQ3-j-t.js";
2
2
  import { dirname, isAbsolute, resolve } from "node:path";
3
3
  import { existsSync, statSync } from "node:fs";
4
4
  import { spawnSync } from "node:child_process";
@@ -0,0 +1,259 @@
1
+ import { parseExecutaSpec } from "./dev-C6v5yRV2.js";
2
+ import { isAbsolute, resolve } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
5
+ import * as readline from "node:readline";
6
+ import { createInterface as createInterface$1 } from "node:readline/promises";
7
+
8
+ //#region src/commands/executa-dev.ts
9
+ /**
10
+ * Mutable hook the REPL installs while it's waiting at a prompt, so
11
+ * out-of-band stderr lines from the executa subprocess don't garble
12
+ * the prompt. When set, the stderr sink clears the current readline
13
+ * line on stdout, prints the message, then asks readline to redraw
14
+ * the prompt + any pending user input.
15
+ */
16
+ let activeRepl = null;
17
+ async function runExecutaDev(opts) {
18
+ const cwd = process.cwd();
19
+ const dir = opts.dir ? isAbsolute(opts.dir) ? opts.dir : resolve(cwd, opts.dir) : cwd;
20
+ if (!existsSync(dir)) {
21
+ console.error(red(`✗ dir not found: ${dir}`));
22
+ return 2;
23
+ }
24
+ const specStr = opts.spec ? prependDirIfMissing(opts.spec, dir) : `dir=${dir}`;
25
+ const parsed = parseExecutaSpec(specStr, cwd);
26
+ if (parsed instanceof Error) {
27
+ console.error(red(`✗ ${parsed.message}`));
28
+ return 2;
29
+ }
30
+ if (!parsed.command || parsed.command.length === 0) {
31
+ console.error(red(`✗ could not derive a launch command for ${dir}`));
32
+ return 2;
33
+ }
34
+ const oneShot = !!(opts.describe || opts.health || opts.invoke);
35
+ const quiet = oneShot && (opts.json ?? false);
36
+ const { getAccount } = await import("./credentials-DDqx6XMQ.js");
37
+ const { ensureDevExecutaRegistered } = await import("./dev-app-cache-DGF2Kuzd.js");
38
+ const needsRealMint = !opts.noSampling && !opts.mockSampling || !opts.noAgent && !opts.mockAgent || !opts.noImage && !opts.mockImage || !opts.noUpload && !opts.mockUpload || opts.storage === "real";
39
+ let effectiveAppSlug = opts.appSlug;
40
+ let autoRegistered = false;
41
+ if (!effectiveAppSlug && needsRealMint) {
42
+ const acc = getAccount(opts.samplingAccount);
43
+ if (acc) try {
44
+ const reg = await ensureDevExecutaRegistered({
45
+ cwd: parsed.project_dir,
46
+ host: acc.host,
47
+ pat: acc.pat,
48
+ toolId: parsed.tool_id
49
+ });
50
+ effectiveAppSlug = reg.slug;
51
+ autoRegistered = true;
52
+ } catch (e) {
53
+ if (!quiet) {
54
+ console.warn(yellow(`! executa auto-register failed: ${e.message}`));
55
+ console.warn(yellow(" reverse-RPC bridges (sampling/agent/image/upload) will run in disabled mode. Pass `--app-slug <slug>` to override, or `--no-sampling --no-agent --no-image --no-upload` to silence."));
56
+ }
57
+ }
58
+ }
59
+ const { SamplingBridge } = await import("./sampling-CJUDG-mf.js");
60
+ const sampling = opts.noSampling ? new SamplingBridge({ mode: "off" }) : opts.mockSampling ? new SamplingBridge({
61
+ mode: "mock",
62
+ mockFile: opts.mockSampling
63
+ }) : effectiveAppSlug ? new SamplingBridge({
64
+ mode: "real",
65
+ account: opts.samplingAccount,
66
+ appSlug: effectiveAppSlug
67
+ }) : new SamplingBridge({ mode: "off" });
68
+ const { AgentBridge } = await import("./agent-DUmINbo4.js");
69
+ const agent = opts.noAgent ? new AgentBridge({ mode: "off" }) : opts.mockAgent ? new AgentBridge({
70
+ mode: "mock",
71
+ mockFile: opts.mockAgent
72
+ }) : effectiveAppSlug ? new AgentBridge({
73
+ mode: "real",
74
+ account: opts.agentAccount ?? opts.samplingAccount,
75
+ appSlug: effectiveAppSlug
76
+ }) : new AgentBridge({ mode: "off" });
77
+ const { StorageBridge } = await import("./storage-EQJA_0UW.js");
78
+ const storageMode = opts.storage ?? (opts.mockStorage ? "mock" : "memory");
79
+ const storage = new StorageBridge({
80
+ mode: storageMode,
81
+ mockFile: opts.mockStorage,
82
+ account: opts.storageAccount ?? opts.samplingAccount,
83
+ appSlug: effectiveAppSlug,
84
+ scopes: opts.storageScopes ? opts.storageScopes.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
85
+ pluginName: parsed.tool_id
86
+ });
87
+ const { ExecutaRunner } = await import("./runner-DmGLdat0.js");
88
+ const { ImageBridge } = await import("./image-bwolX7pa.js");
89
+ const image = opts.noImage ? new ImageBridge({ mode: "off" }) : opts.mockImage ? new ImageBridge({
90
+ mode: "mock",
91
+ mockFile: opts.mockImage
92
+ }) : effectiveAppSlug ? new ImageBridge({
93
+ mode: "real",
94
+ account: opts.imageAccount ?? opts.samplingAccount,
95
+ appSlug: effectiveAppSlug
96
+ }) : new ImageBridge({ mode: "off" });
97
+ const { HostUploadBridge } = await import("./host_upload-C_pGOS6p.js");
98
+ const hostUpload = opts.noUpload ? new HostUploadBridge({ mode: "off" }) : opts.mockUpload ? new HostUploadBridge({
99
+ mode: "mock",
100
+ mockFile: opts.mockUpload
101
+ }) : effectiveAppSlug ? new HostUploadBridge({
102
+ mode: "real",
103
+ account: opts.uploadAccount ?? opts.samplingAccount,
104
+ appSlug: effectiveAppSlug
105
+ }) : new HostUploadBridge({ mode: "off" });
106
+ const runner = new ExecutaRunner({
107
+ command: parsed.command,
108
+ cwd: parsed.project_dir,
109
+ sampling,
110
+ agent,
111
+ storage,
112
+ image,
113
+ hostUpload,
114
+ onStderr: (line) => {
115
+ const text = quiet ? `${line}\n` : dim(`[executa] ${line}\n`);
116
+ writeStderrCooperative(text);
117
+ }
118
+ });
119
+ if (!quiet) {
120
+ const slugTag = (mode) => effectiveAppSlug ? `${mode} → app_slug=${effectiveAppSlug}${autoRegistered ? " (auto)" : ""}` : "disabled (no fixture, no --app-slug, no PAT)";
121
+ console.log(bold(cyan("anna-app executa dev")));
122
+ console.log(` tool_id ${dim(parsed.tool_id)}`);
123
+ console.log(` dir ${dim(parsed.project_dir)}`);
124
+ console.log(` command ${dim(parsed.command.join(" "))}`);
125
+ if (autoRegistered && effectiveAppSlug) console.log(` executa ${dim(`auto-registered as ${effectiveAppSlug}`)}`);
126
+ console.log(` sampling ${dim(opts.noSampling ? "disabled (--no-sampling)" : opts.mockSampling ? `mock (${opts.mockSampling})` : slugTag("real"))}`);
127
+ console.log(` agent ${dim(opts.noAgent ? "disabled (--no-agent)" : opts.mockAgent ? `mock (${opts.mockAgent})` : slugTag("real"))}`);
128
+ console.log(` storage ${dim(storageMode === "mock" ? `mock (${opts.mockStorage ?? "<no-fixture>"})` : storageMode === "real" ? `real → app_slug=${effectiveAppSlug ?? "<unset>"}` : storageMode)}`);
129
+ console.log(` image ${dim(opts.noImage ? "disabled (--no-image)" : opts.mockImage ? `mock (${opts.mockImage})` : slugTag("real"))}`);
130
+ console.log(` upload ${dim(opts.noUpload ? "disabled (--no-upload)" : opts.mockUpload ? `mock (${opts.mockUpload})` : slugTag("real"))}`);
131
+ }
132
+ let init;
133
+ try {
134
+ init = await runner.start();
135
+ } catch (e) {
136
+ console.error(red(`✗ executa failed to start: ${e.message}`));
137
+ return 2;
138
+ }
139
+ if (!quiet) console.log(` ${green("✓")} negotiated protocol ${bold(init.protocolVersion)}`);
140
+ let exitCode = 0;
141
+ try {
142
+ if (oneShot) exitCode = await runOneShot(runner, opts, quiet);
143
+ else exitCode = await runRepl(runner);
144
+ } finally {
145
+ await runner.stop();
146
+ }
147
+ return exitCode;
148
+ }
149
+ function prependDirIfMissing(spec, dir) {
150
+ if (/\bdir\s*=/.test(spec)) return spec;
151
+ return `dir=${dir},${spec}`;
152
+ }
153
+ async function runOneShot(runner, opts, quiet) {
154
+ const print = (v) => {
155
+ process.stdout.write(`${JSON.stringify(v, null, quiet ? 0 : 2)}\n`);
156
+ };
157
+ try {
158
+ if (opts.describe) {
159
+ print(await runner.describe());
160
+ return 0;
161
+ }
162
+ if (opts.health) {
163
+ print(await runner.health());
164
+ return 0;
165
+ }
166
+ if (opts.invoke) {
167
+ let args = {};
168
+ if (opts.args) try {
169
+ const parsed = JSON.parse(opts.args);
170
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("--args must be a JSON object");
171
+ args = parsed;
172
+ } catch (e) {
173
+ console.error(red(`✗ --args: ${e.message}`));
174
+ return 2;
175
+ }
176
+ print(await runner.invoke(opts.invoke, args));
177
+ return 0;
178
+ }
179
+ return 0;
180
+ } catch (e) {
181
+ console.error(red(`✗ ${e.message}`));
182
+ return 1;
183
+ }
184
+ }
185
+ async function runRepl(runner) {
186
+ const rl = createInterface$1({
187
+ input: process.stdin,
188
+ output: process.stdout
189
+ });
190
+ console.log(yellow(" REPL ready. Commands: describe | health | invoke <tool> <json?> | quit"));
191
+ activeRepl = { rl };
192
+ for (;;) {
193
+ let line;
194
+ try {
195
+ line = (await rl.question(cyan("executa> "))).trim();
196
+ } catch {
197
+ break;
198
+ }
199
+ if (!line) continue;
200
+ if (line === "quit" || line === "exit" || line === ".q") break;
201
+ try {
202
+ if (line === "describe") {
203
+ const out = await runner.describe();
204
+ process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
205
+ } else if (line === "health") {
206
+ const out = await runner.health();
207
+ process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
208
+ } else if (line.startsWith("invoke")) {
209
+ const rest = line.slice(6).trim();
210
+ const sp = rest.indexOf(" ");
211
+ const tool = sp === -1 ? rest : rest.slice(0, sp);
212
+ const argsStr = sp === -1 ? "{}" : rest.slice(sp + 1).trim() || "{}";
213
+ if (!tool) {
214
+ console.error(red("usage: invoke <tool> <json-args?>"));
215
+ continue;
216
+ }
217
+ let args;
218
+ try {
219
+ const parsed = JSON.parse(argsStr);
220
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("args must be a JSON object");
221
+ args = parsed;
222
+ } catch (e) {
223
+ console.error(red(`bad args: ${e.message}`));
224
+ continue;
225
+ }
226
+ const out = await runner.invoke(tool, args);
227
+ process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
228
+ } else console.error(yellow(`unknown command: ${line}`));
229
+ } catch (e) {
230
+ console.error(red(`✗ ${e.message}`));
231
+ }
232
+ }
233
+ activeRepl = null;
234
+ rl.close();
235
+ return 0;
236
+ }
237
+ /**
238
+ * Write to stderr without trampling the REPL prompt. When the REPL is
239
+ * idle (no `activeRepl`) or stdout is not a TTY, this is a plain
240
+ * `process.stderr.write`. When the REPL is at a prompt, we clear the
241
+ * prompt line on stdout, emit the stderr text, then redraw the prompt
242
+ * with any user input the reader had already buffered.
243
+ */
244
+ function writeStderrCooperative(text) {
245
+ const repl = activeRepl;
246
+ if (!repl || !process.stdout.isTTY) {
247
+ process.stderr.write(text);
248
+ return;
249
+ }
250
+ readline.cursorTo(process.stdout, 0);
251
+ readline.clearLine(process.stdout, 0);
252
+ process.stderr.write(text);
253
+ const rl = repl.rl;
254
+ if (typeof rl._refreshLine === "function") rl._refreshLine();
255
+ else process.stdout.write(cyan("executa> "));
256
+ }
257
+
258
+ //#endregion
259
+ export { runExecutaDev };
@@ -0,0 +1,68 @@
1
+ import { printMascot } from "./mascot-wlYTJqMs.js";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import kleur from "kleur";
6
+
7
+ //#region src/commands/executa-init.ts
8
+ const here = dirname(fileURLToPath(import.meta.url));
9
+ function templateRoot(template) {
10
+ for (const cand of [resolve(here, "..", "..", "templates", "executa", template), resolve(here, "..", "templates", "executa", template)]) if (existsSync(cand)) return cand;
11
+ throw new Error(`executa template not found: ${template}`);
12
+ }
13
+ function substitute(content, slug, toolId) {
14
+ const slugPy = slug.replace(/-/g, "_");
15
+ return content.replace(/__SLUG_PY__/g, slugPy).replace(/__SLUG__/g, slug).replace(/__TOOL_ID__/g, toolId);
16
+ }
17
+ function copyDirWithSubst(src, dst, slug, toolId) {
18
+ mkdirSync(dst, { recursive: true });
19
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
20
+ const s = join(src, entry.name);
21
+ const d = join(dst, substitute(entry.name, slug, toolId));
22
+ if (entry.isDirectory()) copyDirWithSubst(s, d, slug, toolId);
23
+ else if (entry.isFile()) {
24
+ const stat = statSync(s);
25
+ if (stat.size < 256 * 1024) {
26
+ const buf = readFileSync(s);
27
+ if (!buf.includes(0)) {
28
+ writeFileSync(d, substitute(buf.toString("utf-8"), slug, toolId), "utf-8");
29
+ continue;
30
+ }
31
+ }
32
+ cpSync(s, d);
33
+ }
34
+ }
35
+ }
36
+ function runExecutaInit(opts) {
37
+ const target = resolve(process.cwd(), opts.targetDir);
38
+ if (existsSync(target) && !opts.force) {
39
+ if (readdirSync(target).filter((n) => !n.startsWith(".")).length > 0) {
40
+ console.error(kleur.red(`✗ target dir not empty: ${target} (use --force to override)`));
41
+ return 1;
42
+ }
43
+ }
44
+ if (!/^[a-z][a-z0-9-]{1,40}$/.test(opts.slug)) {
45
+ console.error(kleur.red(`✗ invalid slug "${opts.slug}": must match /^[a-z][a-z0-9-]{1,40}$/`));
46
+ return 1;
47
+ }
48
+ const toolId = opts.toolId ?? `tool-dev-${opts.slug}`;
49
+ if (!/^[a-z][a-z0-9-]{1,80}$/.test(toolId)) {
50
+ console.error(kleur.red(`✗ invalid tool_id "${toolId}": must match /^[a-z][a-z0-9-]{1,80}$/`));
51
+ return 1;
52
+ }
53
+ let tplRoot;
54
+ try {
55
+ tplRoot = templateRoot(opts.template);
56
+ } catch (e) {
57
+ console.error(kleur.red(`✗ ${e.message}`));
58
+ return 1;
59
+ }
60
+ copyDirWithSubst(tplRoot, target, opts.slug, toolId);
61
+ printMascot(`scaffolded an executa — happy hacking!`);
62
+ console.log(kleur.green(`✓ scaffolded "${opts.slug}" (${opts.template}) at ${target}`));
63
+ console.log(kleur.gray(` next: cd ${opts.targetDir} && anna-app executa dev --describe`));
64
+ return 0;
65
+ }
66
+
67
+ //#endregion
68
+ export { runExecutaInit };
@@ -0,0 +1,47 @@
1
+ import { getAccount } from "./credentials-BTv2IfUZ.js";
2
+ import { bold, cyan, dim, green, red } from "kleur/colors";
3
+
4
+ //#region src/commands/executa-register.ts
5
+ async function runExecutaRegister(opts) {
6
+ const acc = getAccount(opts.account);
7
+ if (!acc) {
8
+ console.error(red("✗ no PAT on disk — run `anna-app login --host <nexus-url>` first."));
9
+ return 2;
10
+ }
11
+ const body = {
12
+ pat: acc.pat,
13
+ tool_id: opts.toolId,
14
+ slug: opts.slug,
15
+ name: opts.name
16
+ };
17
+ const url = `${acc.host.replace(/\/$/, "")}/api/v1/anna-apps/dev/executas/register`;
18
+ let res;
19
+ try {
20
+ res = await fetch(url, {
21
+ method: "POST",
22
+ headers: { "content-type": "application/json" },
23
+ body: JSON.stringify(body)
24
+ });
25
+ } catch (e) {
26
+ console.error(red(`✗ network error: ${e.message}`));
27
+ return 2;
28
+ }
29
+ if (res.status === 404) {
30
+ console.error(red("✗ your nexus does not expose POST /api/v1/anna-apps/dev/executas/register — upgrade matrix-nexus to a version that ships this endpoint."));
31
+ return 2;
32
+ }
33
+ if (!res.ok) {
34
+ const text = await res.text().catch(() => "");
35
+ console.error(red(`✗ HTTP ${res.status}: ${text}`));
36
+ return 1;
37
+ }
38
+ const out = await res.json();
39
+ console.log(`${green(out.created ? "✓ registered" : "✓ exists")} ${bold(out.slug)} ${dim(`(app_id=${out.app_id}, kind=${out.kind})`)}`);
40
+ console.log(` tool_id ${dim(out.tool_id)}`);
41
+ console.log(` name ${dim(out.name)}`);
42
+ console.log(` host ${cyan(acc.host)}`);
43
+ return 0;
44
+ }
45
+
46
+ //#endregion
47
+ export { runExecutaRegister };