@anna-ai/cli 0.1.11 → 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.
- package/dist/agent-DUmINbo4.js +372 -0
- package/dist/apps-BEJUn9Ws.js +44 -0
- package/dist/bridge-C0DWb5eQ.js +3 -0
- package/dist/cli.js +81 -10
- package/dist/{credentials-ggdaz_-7.js → credentials-BTv2IfUZ.js} +1 -1
- package/dist/credentials-DDqx6XMQ.js +3 -0
- package/dist/dashboard.html +8 -4
- package/dist/{dev-BPIUX2Nh.js → dev-BfLGxpiT.js} +52 -7
- package/dist/dev-C81H9c9_.js +3 -0
- package/dist/dev-account-DCyjamBa.js +44 -0
- package/dist/dev-app-cache-C3D1Sp_V.js +93 -0
- package/dist/dev-app-cache-CZ8lIKiw.js +4 -0
- package/dist/{doctor-R1pjmBDG.js → doctor-B3u0edUg.js} +1 -1
- package/dist/executa-dev-BhouP8jh.js +212 -0
- package/dist/executa-init-COEmKDOE.js +68 -0
- package/dist/executa-register-66WKIwQQ.js +47 -0
- package/dist/{login-D8cmvBb6.js → login-CsIVbrmf.js} +1 -1
- package/dist/{logout-P6L9VU4W.js → logout-gfmKQxMj.js} +1 -1
- package/dist/mascot-wlYTJqMs.js +218 -0
- package/dist/runner-Bral1LFW.js +279 -0
- package/dist/sampling-3EfSlDHM.js +155 -0
- package/dist/{server-Cd5Lo-2v.js → server-q6nKCeEV.js} +10 -4
- package/dist/storage-CnWTZqq_.js +316 -0
- package/dist/{whoami-jqlQwe7Z.js → whoami-BS5wy-Nh.js} +1 -1
- package/package.json +3 -3
- package/templates/executa/go/README.md +10 -0
- package/templates/executa/go/executa.json +4 -0
- package/templates/executa/go/go.mod +3 -0
- package/templates/executa/go/main.go +148 -0
- package/templates/executa/node/README.md +12 -0
- package/templates/executa/node/executa.json +4 -0
- package/templates/executa/node/package.json +12 -0
- package/templates/executa/node/plugin.mjs +126 -0
- package/templates/executa/node/sampling-fixture.jsonl +1 -0
- package/templates/executa/python/README.md +23 -0
- package/templates/executa/python/__SLUG_PY___plugin.py +146 -0
- package/templates/executa/python/executa.json +4 -0
- package/templates/executa/python/pyproject.toml +15 -0
- package/templates/executa/python/sampling-fixture.jsonl +4 -0
- package/dist/bridge-BIO7ilgO.js +0 -3
- /package/dist/{bridge-Cpm3D2Wk.js → bridge-D6YyP9DM.js} +0 -0
- /package/dist/{fixture-RceUUd84.js → fixture-CATHyLLI.js} +0 -0
|
@@ -31,8 +31,8 @@ async function runDev(opts) {
|
|
|
31
31
|
}
|
|
32
32
|
const matrixNexusRoot = await resolveMatrixNexusRoot(opts.matrixNexusRoot, cwd);
|
|
33
33
|
const mode = matrixNexusRoot ? "nexus-source" : "uvx";
|
|
34
|
-
const { PythonBridge, PINNED_RUNTIME_VERSION } = await import("./bridge-
|
|
35
|
-
const { HarnessServer } = await import("./server-
|
|
34
|
+
const { PythonBridge, PINNED_RUNTIME_VERSION } = await import("./bridge-C0DWb5eQ.js");
|
|
35
|
+
const { HarnessServer } = await import("./server-q6nKCeEV.js");
|
|
36
36
|
const bridge = new PythonBridge({
|
|
37
37
|
mode,
|
|
38
38
|
matrixNexusRoot: matrixNexusRoot ?? void 0,
|
|
@@ -68,12 +68,14 @@ async function runDev(opts) {
|
|
|
68
68
|
const llm = opts.noLlm ? { mode: "off" } : opts.mockLlm ? {
|
|
69
69
|
mode: "mock",
|
|
70
70
|
mockFile: opts.mockLlm
|
|
71
|
-
} : {
|
|
72
|
-
|
|
71
|
+
} : await resolveRealLlm({
|
|
72
|
+
cwd,
|
|
73
73
|
account: opts.llmAccount,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
appSlug: opts.llmAppSlug ?? slug,
|
|
75
|
+
manifest
|
|
76
|
+
});
|
|
77
|
+
if (llm === null) return 2;
|
|
78
|
+
console.log(` llm bridge ${dim(llm.mode === "off" ? "disabled (--no-llm)" : llm.mode === "mock" ? `mock (${opts.mockLlm})` : `real${opts.llmAccount ? ` [${opts.llmAccount}]` : ""} → app_slug=${llm.appSlug}`)}`);
|
|
77
79
|
const server = new HarnessServer({
|
|
78
80
|
slug,
|
|
79
81
|
manifest,
|
|
@@ -111,6 +113,49 @@ async function runDev(opts) {
|
|
|
111
113
|
await new Promise(() => {});
|
|
112
114
|
return 0;
|
|
113
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve the `real` LlmBridgeOptions: load the dev PAT, register
|
|
118
|
+
* (or re-use) an `AnnaApp(is_dev=True)` on nexus, cache the result.
|
|
119
|
+
*
|
|
120
|
+
* Returns `null` (after printing) when registration fails so the caller
|
|
121
|
+
* can exit cleanly. Otherwise returns a bridge config carrying the
|
|
122
|
+
* registered `appSlug`.
|
|
123
|
+
*/
|
|
124
|
+
async function resolveRealLlm(args) {
|
|
125
|
+
const { getAccount } = await import("./credentials-DDqx6XMQ.js");
|
|
126
|
+
const { ensureDevAppRegistered } = await import("./dev-app-cache-CZ8lIKiw.js");
|
|
127
|
+
const acc = getAccount(args.account);
|
|
128
|
+
if (!acc) {
|
|
129
|
+
console.error(red("✗ no developer PAT on disk — run `anna-app login --host <nexus-url>` first.\n (or use `--no-llm` / `--mock-llm <fixture>` to develop offline.)"));
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
if (acc.expires_at && acc.expires_at < Math.floor(Date.now() / 1e3)) {
|
|
133
|
+
console.error(red("✗ PAT expired — run `anna-app login` again."));
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const manifest = args.manifest;
|
|
137
|
+
try {
|
|
138
|
+
const entry = await ensureDevAppRegistered({
|
|
139
|
+
cwd: args.cwd,
|
|
140
|
+
host: acc.host,
|
|
141
|
+
pat: acc.pat,
|
|
142
|
+
input: {
|
|
143
|
+
slug: args.appSlug,
|
|
144
|
+
name: manifest.name,
|
|
145
|
+
category: manifest.category,
|
|
146
|
+
tagline: manifest.tagline
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return {
|
|
150
|
+
mode: "real",
|
|
151
|
+
account: args.account,
|
|
152
|
+
appSlug: entry.slug
|
|
153
|
+
};
|
|
154
|
+
} catch (e) {
|
|
155
|
+
console.error(red(`✗ failed to register dev app on nexus: ${e.message}`));
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
114
159
|
function deriveSlug(manifest, path) {
|
|
115
160
|
const fromMan = manifest.slug ?? manifest.name;
|
|
116
161
|
if (typeof fromMan === "string" && fromMan) return fromMan;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { canonicalHost, getAccount } from "./credentials-BTv2IfUZ.js";
|
|
2
|
+
|
|
3
|
+
//#region src/executa/dev-account.ts
|
|
4
|
+
/** Embed a JSON-RPC error code on a thrown Error so the runner can forward it. */
|
|
5
|
+
function withCode(err, code) {
|
|
6
|
+
err.rpcCode = code;
|
|
7
|
+
return err;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a saved developer PAT for the given account host (or the
|
|
11
|
+
* "current" one when omitted).
|
|
12
|
+
*
|
|
13
|
+
* @throws JSON-RPC `-32001` when no PAT is on disk or it has expired.
|
|
14
|
+
*/
|
|
15
|
+
function requireAccount(account) {
|
|
16
|
+
const acc = getAccount(account);
|
|
17
|
+
if (!acc) throw withCode(new Error("no PAT on disk — run `anna-app login --host <nexus-url>` first (or use a `mock`/`off`/`memory` mode for the affected bridge)"), -32001);
|
|
18
|
+
if (acc.expires_at && acc.expires_at < Math.floor(Date.now() / 1e3)) throw withCode(new Error("PAT expired — run `anna-app login` again"), -32001);
|
|
19
|
+
return acc;
|
|
20
|
+
}
|
|
21
|
+
/** Return the canonical `https://host[:port]` form of a saved account. */
|
|
22
|
+
function hostOf(acc) {
|
|
23
|
+
return canonicalHost(acc.host);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Call `POST /api/v1/anna-apps/dev/session/mint` and parse the response.
|
|
27
|
+
* Thin wrapper used by both sampling and agent bridges.
|
|
28
|
+
*/
|
|
29
|
+
async function mintAppSession(host, body) {
|
|
30
|
+
const url = `${canonicalHost(host)}/api/v1/anna-apps/dev/session/mint`;
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "content-type": "application/json" },
|
|
34
|
+
body: JSON.stringify(body)
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const text = await res.text().catch(() => "");
|
|
38
|
+
throw withCode(new Error(`session.mint failed: HTTP ${res.status} ${text}`), -32e3);
|
|
39
|
+
}
|
|
40
|
+
return await res.json();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { hostOf, mintAppSession, requireAccount, withCode };
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
export { ensureDevAppRegistered, listDevApps, readDevAppCache, registerDevApp, writeDevAppCache };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PINNED_RUNTIME_VERSION } from "./bridge-
|
|
1
|
+
import { PINNED_RUNTIME_VERSION } from "./bridge-D6YyP9DM.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,212 @@
|
|
|
1
|
+
import { parseExecutaSpec } from "./dev-BfLGxpiT.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 { SamplingBridge } = await import("./sampling-3EfSlDHM.js");
|
|
37
|
+
const sampling = opts.noSampling ? new SamplingBridge({ mode: "off" }) : opts.mockSampling ? new SamplingBridge({
|
|
38
|
+
mode: "mock",
|
|
39
|
+
mockFile: opts.mockSampling
|
|
40
|
+
}) : opts.appSlug ? new SamplingBridge({
|
|
41
|
+
mode: "real",
|
|
42
|
+
account: opts.samplingAccount,
|
|
43
|
+
appSlug: opts.appSlug
|
|
44
|
+
}) : new SamplingBridge({ mode: "off" });
|
|
45
|
+
const { AgentBridge } = await import("./agent-DUmINbo4.js");
|
|
46
|
+
const agent = opts.noAgent ? new AgentBridge({ mode: "off" }) : opts.mockAgent ? new AgentBridge({
|
|
47
|
+
mode: "mock",
|
|
48
|
+
mockFile: opts.mockAgent
|
|
49
|
+
}) : opts.appSlug ? new AgentBridge({
|
|
50
|
+
mode: "real",
|
|
51
|
+
account: opts.agentAccount ?? opts.samplingAccount,
|
|
52
|
+
appSlug: opts.appSlug
|
|
53
|
+
}) : new AgentBridge({ mode: "off" });
|
|
54
|
+
const { StorageBridge } = await import("./storage-CnWTZqq_.js");
|
|
55
|
+
const storageMode = opts.storage ?? (opts.mockStorage ? "mock" : "memory");
|
|
56
|
+
const storage = new StorageBridge({
|
|
57
|
+
mode: storageMode,
|
|
58
|
+
mockFile: opts.mockStorage,
|
|
59
|
+
account: opts.storageAccount ?? opts.samplingAccount,
|
|
60
|
+
appSlug: opts.appSlug,
|
|
61
|
+
scopes: opts.storageScopes ? opts.storageScopes.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
|
|
62
|
+
pluginName: parsed.tool_id
|
|
63
|
+
});
|
|
64
|
+
const { ExecutaRunner } = await import("./runner-Bral1LFW.js");
|
|
65
|
+
const runner = new ExecutaRunner({
|
|
66
|
+
command: parsed.command,
|
|
67
|
+
cwd: parsed.project_dir,
|
|
68
|
+
sampling,
|
|
69
|
+
agent,
|
|
70
|
+
storage,
|
|
71
|
+
onStderr: (line) => {
|
|
72
|
+
const text = quiet ? `${line}\n` : dim(`[executa] ${line}\n`);
|
|
73
|
+
writeStderrCooperative(text);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (!quiet) {
|
|
77
|
+
console.log(bold(cyan("anna-app executa dev")));
|
|
78
|
+
console.log(` tool_id ${dim(parsed.tool_id)}`);
|
|
79
|
+
console.log(` dir ${dim(parsed.project_dir)}`);
|
|
80
|
+
console.log(` command ${dim(parsed.command.join(" "))}`);
|
|
81
|
+
console.log(` sampling ${dim(opts.noSampling ? "disabled (--no-sampling)" : opts.mockSampling ? `mock (${opts.mockSampling})` : opts.appSlug ? `real → app_slug=${opts.appSlug}` : "disabled (no fixture, no --app-slug)")}`);
|
|
82
|
+
console.log(` agent ${dim(opts.noAgent ? "disabled (--no-agent)" : opts.mockAgent ? `mock (${opts.mockAgent})` : opts.appSlug ? `real → app_slug=${opts.appSlug}` : "disabled (no fixture, no --app-slug)")}`);
|
|
83
|
+
console.log(` storage ${dim(storageMode === "mock" ? `mock (${opts.mockStorage ?? "<no-fixture>"})` : storageMode === "real" ? `real → app_slug=${opts.appSlug ?? "<unset>"}` : storageMode)}`);
|
|
84
|
+
}
|
|
85
|
+
let init;
|
|
86
|
+
try {
|
|
87
|
+
init = await runner.start();
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error(red(`✗ executa failed to start: ${e.message}`));
|
|
90
|
+
return 2;
|
|
91
|
+
}
|
|
92
|
+
if (!quiet) console.log(` ${green("✓")} negotiated protocol ${bold(init.protocolVersion)}`);
|
|
93
|
+
let exitCode = 0;
|
|
94
|
+
try {
|
|
95
|
+
if (oneShot) exitCode = await runOneShot(runner, opts, quiet);
|
|
96
|
+
else exitCode = await runRepl(runner);
|
|
97
|
+
} finally {
|
|
98
|
+
await runner.stop();
|
|
99
|
+
}
|
|
100
|
+
return exitCode;
|
|
101
|
+
}
|
|
102
|
+
function prependDirIfMissing(spec, dir) {
|
|
103
|
+
if (/\bdir\s*=/.test(spec)) return spec;
|
|
104
|
+
return `dir=${dir},${spec}`;
|
|
105
|
+
}
|
|
106
|
+
async function runOneShot(runner, opts, quiet) {
|
|
107
|
+
const print = (v) => {
|
|
108
|
+
process.stdout.write(`${JSON.stringify(v, null, quiet ? 0 : 2)}\n`);
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
if (opts.describe) {
|
|
112
|
+
print(await runner.describe());
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
if (opts.health) {
|
|
116
|
+
print(await runner.health());
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
if (opts.invoke) {
|
|
120
|
+
let args = {};
|
|
121
|
+
if (opts.args) try {
|
|
122
|
+
const parsed = JSON.parse(opts.args);
|
|
123
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("--args must be a JSON object");
|
|
124
|
+
args = parsed;
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.error(red(`✗ --args: ${e.message}`));
|
|
127
|
+
return 2;
|
|
128
|
+
}
|
|
129
|
+
print(await runner.invoke(opts.invoke, args));
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
return 0;
|
|
133
|
+
} catch (e) {
|
|
134
|
+
console.error(red(`✗ ${e.message}`));
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function runRepl(runner) {
|
|
139
|
+
const rl = createInterface$1({
|
|
140
|
+
input: process.stdin,
|
|
141
|
+
output: process.stdout
|
|
142
|
+
});
|
|
143
|
+
console.log(yellow(" REPL ready. Commands: describe | health | invoke <tool> <json?> | quit"));
|
|
144
|
+
activeRepl = { rl };
|
|
145
|
+
for (;;) {
|
|
146
|
+
let line;
|
|
147
|
+
try {
|
|
148
|
+
line = (await rl.question(cyan("executa> "))).trim();
|
|
149
|
+
} catch {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
if (!line) continue;
|
|
153
|
+
if (line === "quit" || line === "exit" || line === ".q") break;
|
|
154
|
+
try {
|
|
155
|
+
if (line === "describe") {
|
|
156
|
+
const out = await runner.describe();
|
|
157
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
158
|
+
} else if (line === "health") {
|
|
159
|
+
const out = await runner.health();
|
|
160
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
161
|
+
} else if (line.startsWith("invoke")) {
|
|
162
|
+
const rest = line.slice(6).trim();
|
|
163
|
+
const sp = rest.indexOf(" ");
|
|
164
|
+
const tool = sp === -1 ? rest : rest.slice(0, sp);
|
|
165
|
+
const argsStr = sp === -1 ? "{}" : rest.slice(sp + 1).trim() || "{}";
|
|
166
|
+
if (!tool) {
|
|
167
|
+
console.error(red("usage: invoke <tool> <json-args?>"));
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
let args;
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(argsStr);
|
|
173
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("args must be a JSON object");
|
|
174
|
+
args = parsed;
|
|
175
|
+
} catch (e) {
|
|
176
|
+
console.error(red(`bad args: ${e.message}`));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const out = await runner.invoke(tool, args);
|
|
180
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
181
|
+
} else console.error(yellow(`unknown command: ${line}`));
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.error(red(`✗ ${e.message}`));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
activeRepl = null;
|
|
187
|
+
rl.close();
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Write to stderr without trampling the REPL prompt. When the REPL is
|
|
192
|
+
* idle (no `activeRepl`) or stdout is not a TTY, this is a plain
|
|
193
|
+
* `process.stderr.write`. When the REPL is at a prompt, we clear the
|
|
194
|
+
* prompt line on stdout, emit the stderr text, then redraw the prompt
|
|
195
|
+
* with any user input the reader had already buffered.
|
|
196
|
+
*/
|
|
197
|
+
function writeStderrCooperative(text) {
|
|
198
|
+
const repl = activeRepl;
|
|
199
|
+
if (!repl || !process.stdout.isTTY) {
|
|
200
|
+
process.stderr.write(text);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
readline.cursorTo(process.stdout, 0);
|
|
204
|
+
readline.clearLine(process.stdout, 0);
|
|
205
|
+
process.stderr.write(text);
|
|
206
|
+
const rl = repl.rl;
|
|
207
|
+
if (typeof rl._refreshLine === "function") rl._refreshLine();
|
|
208
|
+
else process.stdout.write(cyan("executa> "));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
//#endregion
|
|
212
|
+
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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canonicalHost, getAccount, readCredentials, removeAccount } from "./credentials-
|
|
1
|
+
import { canonicalHost, getAccount, readCredentials, removeAccount } from "./credentials-BTv2IfUZ.js";
|
|
2
2
|
|
|
3
3
|
//#region src/commands/logout.ts
|
|
4
4
|
async function runLogout(opts) {
|