@openape/apes 1.26.0 → 1.28.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/dist/{chunk-VHZEVW2N.js → chunk-AFTJZVOQ.js} +8 -1
- package/dist/cli.js +946 -486
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +3 -1
- package/dist/{server-64H5O3CG.js → server-UKHYMZ7X.js} +2 -2
- package/package.json +3 -3
- /package/dist/{chunk-VHZEVW2N.js.map → chunk-AFTJZVOQ.js.map} +0 -0
- /package/dist/{server-64H5O3CG.js.map → server-UKHYMZ7X.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -3,10 +3,17 @@ import {
|
|
|
3
3
|
CliError,
|
|
4
4
|
CliExit,
|
|
5
5
|
RpcSessionMap,
|
|
6
|
+
buildCreateCommand,
|
|
7
|
+
buildIssueGet,
|
|
8
|
+
buildPrCreate,
|
|
9
|
+
buildPrMerge,
|
|
10
|
+
detectForge,
|
|
6
11
|
parseDuration,
|
|
12
|
+
runApeShell,
|
|
7
13
|
runLoop,
|
|
8
|
-
taskTools
|
|
9
|
-
|
|
14
|
+
taskTools,
|
|
15
|
+
worktreePathFor
|
|
16
|
+
} from "./chunk-AFTJZVOQ.js";
|
|
10
17
|
import {
|
|
11
18
|
loadEd25519PrivateKey,
|
|
12
19
|
readPublicKeyComment
|
|
@@ -66,7 +73,7 @@ import {
|
|
|
66
73
|
} from "./chunk-OBF7IMQ2.js";
|
|
67
74
|
|
|
68
75
|
// src/cli.ts
|
|
69
|
-
import
|
|
76
|
+
import consola52 from "consola";
|
|
70
77
|
|
|
71
78
|
// src/ape-shell.ts
|
|
72
79
|
import path from "path";
|
|
@@ -96,7 +103,7 @@ function rewriteApeShellArgs(argv, argv0) {
|
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
// src/cli.ts
|
|
99
|
-
import { defineCommand as
|
|
106
|
+
import { defineCommand as defineCommand64, runMain } from "citty";
|
|
100
107
|
|
|
101
108
|
// src/commands/auth/login.ts
|
|
102
109
|
import { Buffer as Buffer2 } from "buffer";
|
|
@@ -382,7 +389,7 @@ async function loginWithPKCE(idp) {
|
|
|
382
389
|
consola2.success(`Logged in as ${payload.email || payload.sub}`);
|
|
383
390
|
}
|
|
384
391
|
async function loginWithKey(idp, keyPath, agentEmail) {
|
|
385
|
-
const { readFileSync:
|
|
392
|
+
const { readFileSync: readFileSync18 } = await import("fs");
|
|
386
393
|
const { sign: sign3 } = await import("crypto");
|
|
387
394
|
const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-YBNNG5K5.js");
|
|
388
395
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
@@ -395,7 +402,7 @@ async function loginWithKey(idp, keyPath, agentEmail) {
|
|
|
395
402
|
throw new CliError(`Challenge failed: ${await challengeResp.text()}`);
|
|
396
403
|
}
|
|
397
404
|
const { challenge } = await challengeResp.json();
|
|
398
|
-
const keyContent =
|
|
405
|
+
const keyContent = readFileSync18(keyPath, "utf-8");
|
|
399
406
|
const privateKey = loadEd25519PrivateKey2(keyContent);
|
|
400
407
|
const signature = sign3(null, Buffer2.from(challenge), privateKey).toString("base64");
|
|
401
408
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
@@ -1955,7 +1962,7 @@ var agentCommand = defineCommand21({
|
|
|
1955
1962
|
});
|
|
1956
1963
|
|
|
1957
1964
|
// src/commands/agents/index.ts
|
|
1958
|
-
import { defineCommand as
|
|
1965
|
+
import { defineCommand as defineCommand32 } from "citty";
|
|
1959
1966
|
|
|
1960
1967
|
// src/commands/agents/allow.ts
|
|
1961
1968
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
@@ -2619,31 +2626,483 @@ var cleanupOrphansCommand = defineCommand23({
|
|
|
2619
2626
|
}
|
|
2620
2627
|
});
|
|
2621
2628
|
|
|
2629
|
+
// src/commands/agents/code.ts
|
|
2630
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
2631
|
+
import { homedir as homedir4 } from "os";
|
|
2632
|
+
import { join as join2 } from "path";
|
|
2633
|
+
import process2 from "process";
|
|
2634
|
+
import { defineCommand as defineCommand24 } from "citty";
|
|
2635
|
+
import { consola as consola21 } from "consola";
|
|
2636
|
+
|
|
2637
|
+
// src/lib/coding/issue-task.ts
|
|
2638
|
+
var DEFAULT_TEMPLATE = "{type}/issue-{number}-{slug}";
|
|
2639
|
+
var DEFAULT_TYPE = "fix";
|
|
2640
|
+
var DEFAULT_SLUG_MAX = 48;
|
|
2641
|
+
var DEFAULT_INSTRUCTIONS = [
|
|
2642
|
+
"Work in the provided worktree. When done:",
|
|
2643
|
+
"- ensure the verification command passes (no PR on red)",
|
|
2644
|
+
"- open a PR that references this issue",
|
|
2645
|
+
"- leave risk-path / agent-judged-risky changes for human approval"
|
|
2646
|
+
].join("\n");
|
|
2647
|
+
var TYPE_RE = /^[a-z]{2,12}$/;
|
|
2648
|
+
function slugify(title, max = DEFAULT_SLUG_MAX) {
|
|
2649
|
+
return title.toLowerCase().normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, max).replace(/-$/, "");
|
|
2650
|
+
}
|
|
2651
|
+
function buildBranchName(issue, naming = {}) {
|
|
2652
|
+
const num = String(issue.number).replace(/\D/g, "");
|
|
2653
|
+
if (!num) throw new Error("issue number required");
|
|
2654
|
+
const type = issue.type && TYPE_RE.test(issue.type) ? issue.type : naming.defaultType ?? DEFAULT_TYPE;
|
|
2655
|
+
const slug = slugify(issue.title, naming.slugMax ?? DEFAULT_SLUG_MAX) || "task";
|
|
2656
|
+
return (naming.template ?? DEFAULT_TEMPLATE).replace(/\{type\}/g, type).replace(/\{number\}/g, num).replace(/\{slug\}/g, slug);
|
|
2657
|
+
}
|
|
2658
|
+
function buildTaskPrompt(issue, framing = {}) {
|
|
2659
|
+
const num = String(issue.number);
|
|
2660
|
+
const parts = [];
|
|
2661
|
+
if (framing.persona?.trim()) parts.push(framing.persona.trim());
|
|
2662
|
+
parts.push(`Issue #${num}: ${issue.title}`);
|
|
2663
|
+
parts.push(issue.body?.trim() ? issue.body.trim() : "(no description provided)");
|
|
2664
|
+
parts.push(framing.instructions?.trim() ? framing.instructions.trim() : DEFAULT_INSTRUCTIONS);
|
|
2665
|
+
return parts.join("\n\n");
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
// src/lib/coding/merge-policy.ts
|
|
2669
|
+
var SECURE_DEFAULT_POLICY = {
|
|
2670
|
+
autoMergeEnabled: false,
|
|
2671
|
+
autoPaths: [],
|
|
2672
|
+
riskPaths: []
|
|
2673
|
+
};
|
|
2674
|
+
function globToRegExp(glob) {
|
|
2675
|
+
let re = "";
|
|
2676
|
+
for (let i = 0; i < glob.length; i++) {
|
|
2677
|
+
const c = glob[i];
|
|
2678
|
+
if (c === "*") {
|
|
2679
|
+
if (glob[i + 1] === "*") {
|
|
2680
|
+
re += ".*";
|
|
2681
|
+
i++;
|
|
2682
|
+
if (glob[i + 1] === "/") i++;
|
|
2683
|
+
} else {
|
|
2684
|
+
re += "[^/]*";
|
|
2685
|
+
}
|
|
2686
|
+
} else if (c === "?") {
|
|
2687
|
+
re += "[^/]";
|
|
2688
|
+
} else if (".+^${}()|[]\\".includes(c)) {
|
|
2689
|
+
re += `\\${c}`;
|
|
2690
|
+
} else {
|
|
2691
|
+
re += c;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
return new RegExp(`^${re}$`);
|
|
2695
|
+
}
|
|
2696
|
+
function matchesAny(path2, globs) {
|
|
2697
|
+
return globs.some((g) => globToRegExp(g).test(path2));
|
|
2698
|
+
}
|
|
2699
|
+
function classifyChange(paths, policy = SECURE_DEFAULT_POLICY) {
|
|
2700
|
+
if (paths.length === 0) return "code";
|
|
2701
|
+
if (paths.some((p) => matchesAny(p, policy.riskPaths))) return "risk";
|
|
2702
|
+
if (policy.autoPaths.length > 0 && paths.every((p) => matchesAny(p, policy.autoPaths))) return "chore";
|
|
2703
|
+
return "code";
|
|
2704
|
+
}
|
|
2705
|
+
function decideMerge(paths, policy = SECURE_DEFAULT_POLICY, agentRisk) {
|
|
2706
|
+
const globClass = classifyChange(paths, policy);
|
|
2707
|
+
const isRisk = globClass === "risk" || agentRisk?.risky === true;
|
|
2708
|
+
const classification = isRisk ? "risk" : globClass;
|
|
2709
|
+
if (!policy.autoMergeEnabled) {
|
|
2710
|
+
return { classification, autoMerge: false, needsReview: false, needsHuman: true, reason: "auto-merge not enabled for this repo \u2014 human approval required (set autoMergeEnabled in .openape/coding.json to opt in)" };
|
|
2711
|
+
}
|
|
2712
|
+
if (isRisk) {
|
|
2713
|
+
const why = agentRisk?.risky ? `agent judged this risky${agentRisk.reason ? `: ${agentRisk.reason}` : ""}` : "matches a repo/derived risk path";
|
|
2714
|
+
return { classification, autoMerge: false, needsReview: false, needsHuman: true, reason: `${why} \u2014 human approval required` };
|
|
2715
|
+
}
|
|
2716
|
+
if (classification === "chore") {
|
|
2717
|
+
return { classification, autoMerge: true, needsReview: false, needsHuman: false, reason: "chore/docs only \u2014 auto-merge on green CI" };
|
|
2718
|
+
}
|
|
2719
|
+
return { classification, autoMerge: true, needsReview: true, needsHuman: false, reason: "code change \u2014 auto-merge after reviewer-agent approval" };
|
|
2720
|
+
}
|
|
2721
|
+
async function loadMergePolicy(worktreeDir) {
|
|
2722
|
+
const { readFile } = await import("fs/promises");
|
|
2723
|
+
const { join: join20 } = await import("path");
|
|
2724
|
+
try {
|
|
2725
|
+
const raw = await readFile(join20(worktreeDir, ".openape", "coding.json"), "utf8");
|
|
2726
|
+
const parsed = JSON.parse(raw);
|
|
2727
|
+
const mp = parsed.mergePolicy;
|
|
2728
|
+
if (!mp) return SECURE_DEFAULT_POLICY;
|
|
2729
|
+
return {
|
|
2730
|
+
autoMergeEnabled: mp.autoMergeEnabled === true,
|
|
2731
|
+
autoPaths: Array.isArray(mp.autoPaths) ? mp.autoPaths.filter((g) => typeof g === "string") : [],
|
|
2732
|
+
riskPaths: Array.isArray(mp.riskPaths) ? mp.riskPaths.filter((g) => typeof g === "string") : []
|
|
2733
|
+
};
|
|
2734
|
+
} catch {
|
|
2735
|
+
return SECURE_DEFAULT_POLICY;
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
// src/lib/coding/review-gate.ts
|
|
2740
|
+
async function gateMerge(decision, req, reviewer) {
|
|
2741
|
+
if (decision.needsHuman) {
|
|
2742
|
+
return { armMerge: false, awaitingHuman: true, reason: "risk-path change \u2014 handed off to human reviewer" };
|
|
2743
|
+
}
|
|
2744
|
+
if (!decision.needsReview) {
|
|
2745
|
+
return { armMerge: true, awaitingHuman: false, reason: "chore change \u2014 no reviewer gate" };
|
|
2746
|
+
}
|
|
2747
|
+
const verdict = await reviewer({ ...req, classification: decision.classification });
|
|
2748
|
+
if (verdict.approved) {
|
|
2749
|
+
return { armMerge: true, awaitingHuman: false, reason: `reviewer approved${verdict.reason ? `: ${verdict.reason}` : ""}` };
|
|
2750
|
+
}
|
|
2751
|
+
return { armMerge: false, awaitingHuman: false, reason: `reviewer blocked${verdict.reason ? `: ${verdict.reason}` : ""}` };
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/lib/coding/coding-loop.ts
|
|
2755
|
+
var DIFF_CAP = 60 * 1024;
|
|
2756
|
+
function taskIdFromIssue(issue) {
|
|
2757
|
+
const num = String(issue.number).replace(/\D/g, "");
|
|
2758
|
+
return `issue-${num || "x"}`;
|
|
2759
|
+
}
|
|
2760
|
+
async function runCodingTask(input, deps) {
|
|
2761
|
+
const shell = deps.shell ?? (async (cmd, t) => {
|
|
2762
|
+
const r = await runApeShell(cmd, t);
|
|
2763
|
+
return { stdout: r.stdout, stderr: r.stderr, exit_code: r.exit_code };
|
|
2764
|
+
});
|
|
2765
|
+
const loop = deps.runLoopImpl ?? runLoop;
|
|
2766
|
+
const log = deps.log ?? (() => {
|
|
2767
|
+
});
|
|
2768
|
+
const branch = buildBranchName(input.issue, deps.branchNaming);
|
|
2769
|
+
const taskId = taskIdFromIssue(input.issue);
|
|
2770
|
+
const worktree = worktreePathFor(taskId);
|
|
2771
|
+
log(`[coding] creating worktree ${worktree} on ${branch}`);
|
|
2772
|
+
const wt = await shell(buildCreateCommand(input.repo, taskId, branch));
|
|
2773
|
+
if (wt.exit_code !== 0) {
|
|
2774
|
+
return { branch, worktree, runStatus: "error", changedFiles: [], outcome: "run-failed", reason: `worktree create failed: ${wt.stderr.slice(0, 300)}` };
|
|
2775
|
+
}
|
|
2776
|
+
const policy = deps.resolvePolicy ? await deps.resolvePolicy(worktree) : deps.policy ?? SECURE_DEFAULT_POLICY;
|
|
2777
|
+
const prompt = buildTaskPrompt(input.issue, { persona: deps.persona });
|
|
2778
|
+
const run = await loop({
|
|
2779
|
+
config: deps.runtimeConfig,
|
|
2780
|
+
systemPrompt: deps.persona,
|
|
2781
|
+
userMessage: `${prompt}
|
|
2782
|
+
|
|
2783
|
+
Worktree: ${worktree}`,
|
|
2784
|
+
tools: deps.tools,
|
|
2785
|
+
maxSteps: deps.maxSteps
|
|
2786
|
+
});
|
|
2787
|
+
if (run.status !== "ok") {
|
|
2788
|
+
return { branch, worktree, runStatus: "error", changedFiles: [], outcome: "run-failed", reason: `coding loop errored after ${run.stepCount} steps` };
|
|
2789
|
+
}
|
|
2790
|
+
const namesRes = await shell(`git -C '${worktree}' add -A && git -C '${worktree}' diff --cached --name-only`);
|
|
2791
|
+
const changedFiles = namesRes.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
2792
|
+
if (changedFiles.length === 0) {
|
|
2793
|
+
return { branch, worktree, runStatus: "ok", changedFiles: [], outcome: "run-failed", reason: "no changes produced \u2014 nothing to PR" };
|
|
2794
|
+
}
|
|
2795
|
+
const diffRes = await shell(`git -C '${worktree}' diff --cached`);
|
|
2796
|
+
const diff = diffRes.stdout.slice(0, DIFF_CAP);
|
|
2797
|
+
const agentRisk = await deps.riskAssessor({ paths: changedFiles, diff });
|
|
2798
|
+
const decision = decideMerge(changedFiles, policy, agentRisk);
|
|
2799
|
+
log(`[coding] decision=${decision.classification} (${decision.reason})`);
|
|
2800
|
+
await shell(`git -C '${worktree}' commit -m ${shqMsg(input.issue)} && git -C '${worktree}' push -u origin '${branch}'`);
|
|
2801
|
+
const prCmd = buildPrCreate({ forge: input.forge, title: prTitle(input.issue), body: prBody(input.issue), head: branch });
|
|
2802
|
+
const prRes = await shell(`cd '${worktree}' && ${prCmd}`);
|
|
2803
|
+
const prRef = prRes.stdout.match(/\/pull\/(\d+)|!(\d+)|\bpr\/(\d+)/i)?.slice(1).find(Boolean) ?? branch;
|
|
2804
|
+
if (decision.needsHuman) {
|
|
2805
|
+
return { branch, worktree, runStatus: "ok", changedFiles, decision, outcome: "awaiting-human", prRef, reason: decision.reason };
|
|
2806
|
+
}
|
|
2807
|
+
const gate = await gateMerge(decision, { prRef, diff }, deps.reviewer);
|
|
2808
|
+
if (!gate.armMerge) {
|
|
2809
|
+
return { branch, worktree, runStatus: "ok", changedFiles, decision, outcome: "reviewer-blocked", prRef, reason: gate.reason };
|
|
2810
|
+
}
|
|
2811
|
+
const mergeCmd = buildPrMerge({ forge: input.forge, ref: prRef, auto: true, squash: deps.squash, deleteBranch: true });
|
|
2812
|
+
await shell(`cd '${worktree}' && ${mergeCmd}`);
|
|
2813
|
+
return { branch, worktree, runStatus: "ok", changedFiles, decision, outcome: "auto-armed", prRef, reason: gate.reason };
|
|
2814
|
+
}
|
|
2815
|
+
function prTitle(issue) {
|
|
2816
|
+
return `${issue.type ?? "fix"}: ${issue.title} (#${String(issue.number).replace(/\D/g, "")})`;
|
|
2817
|
+
}
|
|
2818
|
+
function prBody(issue) {
|
|
2819
|
+
return `Resolves #${String(issue.number).replace(/\D/g, "")}.
|
|
2820
|
+
|
|
2821
|
+
Automated by the OpenApe coding agent.`;
|
|
2822
|
+
}
|
|
2823
|
+
function shqMsg(issue) {
|
|
2824
|
+
return `'${prTitle(issue).replace(/'/g, "'\\''")}'`;
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
// src/lib/coding/llm-review.ts
|
|
2828
|
+
var DIFF_CAP2 = 48 * 1024;
|
|
2829
|
+
async function jsonCompletion(config, system, user, fetchImpl = fetch) {
|
|
2830
|
+
try {
|
|
2831
|
+
const res = await fetchImpl(`${config.apiBase}/chat/completions`, {
|
|
2832
|
+
method: "POST",
|
|
2833
|
+
headers: { "authorization": `Bearer ${config.apiKey}`, "content-type": "application/json" },
|
|
2834
|
+
body: JSON.stringify({
|
|
2835
|
+
model: config.model,
|
|
2836
|
+
messages: [{ role: "system", content: system }, { role: "user", content: user }],
|
|
2837
|
+
response_format: { type: "json_object" }
|
|
2838
|
+
})
|
|
2839
|
+
});
|
|
2840
|
+
if (!res.ok) return null;
|
|
2841
|
+
const data = await res.json();
|
|
2842
|
+
const content = data.choices?.[0]?.message?.content;
|
|
2843
|
+
if (!content) return null;
|
|
2844
|
+
return JSON.parse(content);
|
|
2845
|
+
} catch {
|
|
2846
|
+
return null;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
var RISK_SYSTEM = [
|
|
2850
|
+
"You are a security/risk classifier for an autonomous coding agent.",
|
|
2851
|
+
"Given a diff + changed file paths, decide whether merging it WITHOUT a human is risky.",
|
|
2852
|
+
"Risky = touches authentication, authorization, secrets/credentials, payment, data migrations,",
|
|
2853
|
+
"deploy/release/CI config, cryptography, deletion of data, or anything whose failure is hard to",
|
|
2854
|
+
"reverse in production. Routine code/tests/docs/refactors are NOT risky.",
|
|
2855
|
+
'Respond ONLY as JSON: {"risky": boolean, "reason": string}.'
|
|
2856
|
+
].join(" ");
|
|
2857
|
+
function createLlmRiskAssessor(config, fetchImpl) {
|
|
2858
|
+
return async ({ paths, diff }) => {
|
|
2859
|
+
const user = `Changed files:
|
|
2860
|
+
${paths.join("\n")}
|
|
2861
|
+
|
|
2862
|
+
Diff (truncated):
|
|
2863
|
+
${diff.slice(0, DIFF_CAP2)}`;
|
|
2864
|
+
const out = await jsonCompletion(config, RISK_SYSTEM, user, fetchImpl);
|
|
2865
|
+
if (!out || typeof out.risky !== "boolean") {
|
|
2866
|
+
return { risky: true, reason: "risk classifier unavailable/unparseable \u2014 treating as risky (fail-safe)" };
|
|
2867
|
+
}
|
|
2868
|
+
return { risky: out.risky, reason: typeof out.reason === "string" ? out.reason : void 0 };
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
var REVIEW_SYSTEM = [
|
|
2872
|
+
"You are a code reviewer for an autonomous coding agent.",
|
|
2873
|
+
"Given a PR diff, decide whether it is correct, safe, and complete enough to auto-merge.",
|
|
2874
|
+
"Approve only if you would be comfortable shipping it without further human review.",
|
|
2875
|
+
"Block if you see bugs, missing tests, security issues, or incomplete work.",
|
|
2876
|
+
'Respond ONLY as JSON: {"approved": boolean, "reason": string}.'
|
|
2877
|
+
].join(" ");
|
|
2878
|
+
function createLlmReviewer(config, fetchImpl) {
|
|
2879
|
+
return async ({ prRef, diff }) => {
|
|
2880
|
+
const user = `PR ${String(prRef)} diff (truncated):
|
|
2881
|
+
${diff.slice(0, DIFF_CAP2)}`;
|
|
2882
|
+
const out = await jsonCompletion(config, REVIEW_SYSTEM, user, fetchImpl);
|
|
2883
|
+
if (!out || typeof out.approved !== "boolean") {
|
|
2884
|
+
return { approved: false, reason: "reviewer unavailable/unparseable \u2014 blocking (fail-safe)" };
|
|
2885
|
+
}
|
|
2886
|
+
return { approved: out.approved, reason: typeof out.reason === "string" ? out.reason : void 0 };
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/lib/coding/derive-policy.ts
|
|
2891
|
+
function indentOf(line) {
|
|
2892
|
+
return line.length - line.trimStart().length;
|
|
2893
|
+
}
|
|
2894
|
+
function unquote(s) {
|
|
2895
|
+
return s.trim().replace(/^['"]|['"]$/g, "").trim();
|
|
2896
|
+
}
|
|
2897
|
+
function extractWorkflowPaths(yamlText) {
|
|
2898
|
+
const out = [];
|
|
2899
|
+
const lines = yamlText.split("\n");
|
|
2900
|
+
let inBlock = false;
|
|
2901
|
+
let keyIndent = -1;
|
|
2902
|
+
for (const line of lines) {
|
|
2903
|
+
if (inBlock) {
|
|
2904
|
+
const m = /^\s*-\s*(\S.*)$/.exec(line);
|
|
2905
|
+
if (m && indentOf(line) > keyIndent) {
|
|
2906
|
+
out.push(unquote(m[1]));
|
|
2907
|
+
continue;
|
|
2908
|
+
}
|
|
2909
|
+
if (line.trim() !== "") inBlock = false;
|
|
2910
|
+
}
|
|
2911
|
+
const inline = /^\s*paths:\s*\[(.+)\]\s*$/.exec(line);
|
|
2912
|
+
if (inline) {
|
|
2913
|
+
for (const tok of inline[1].split(",")) {
|
|
2914
|
+
const g = unquote(tok);
|
|
2915
|
+
if (g) out.push(g);
|
|
2916
|
+
}
|
|
2917
|
+
continue;
|
|
2918
|
+
}
|
|
2919
|
+
if (/^\s*paths:\s*$/.test(line)) {
|
|
2920
|
+
inBlock = true;
|
|
2921
|
+
keyIndent = indentOf(line);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
return [...new Set(out)];
|
|
2925
|
+
}
|
|
2926
|
+
function parseCodeowners(text) {
|
|
2927
|
+
const out = [];
|
|
2928
|
+
for (const raw of text.split("\n")) {
|
|
2929
|
+
const line = raw.trim();
|
|
2930
|
+
if (!line || line.startsWith("#")) continue;
|
|
2931
|
+
const pattern = line.split(/\s+/)[0];
|
|
2932
|
+
if (!pattern || pattern.startsWith("@")) continue;
|
|
2933
|
+
let g = pattern.replace(/^\//, "");
|
|
2934
|
+
if (g.endsWith("/")) g += "**";
|
|
2935
|
+
if (g) out.push(g);
|
|
2936
|
+
}
|
|
2937
|
+
return [...new Set(out)];
|
|
2938
|
+
}
|
|
2939
|
+
async function deriveRiskGlobs(worktreeDir) {
|
|
2940
|
+
const { readFile, readdir } = await import("fs/promises");
|
|
2941
|
+
const { join: join20 } = await import("path");
|
|
2942
|
+
const globs = /* @__PURE__ */ new Set();
|
|
2943
|
+
try {
|
|
2944
|
+
const wfDir = join20(worktreeDir, ".github", "workflows");
|
|
2945
|
+
const files = await readdir(wfDir);
|
|
2946
|
+
for (const f of files) {
|
|
2947
|
+
if (!/deploy.*\.ya?ml$/i.test(f)) continue;
|
|
2948
|
+
try {
|
|
2949
|
+
const text = await readFile(join20(wfDir, f), "utf8");
|
|
2950
|
+
for (const g of extractWorkflowPaths(text)) globs.add(g);
|
|
2951
|
+
} catch {
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
} catch {
|
|
2955
|
+
}
|
|
2956
|
+
for (const loc of [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"]) {
|
|
2957
|
+
try {
|
|
2958
|
+
const text = await readFile(join20(worktreeDir, loc), "utf8");
|
|
2959
|
+
for (const g of parseCodeowners(text)) globs.add(g);
|
|
2960
|
+
break;
|
|
2961
|
+
} catch {
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
return [...globs];
|
|
2965
|
+
}
|
|
2966
|
+
async function resolveMergePolicy(worktreeDir) {
|
|
2967
|
+
const explicit = await loadMergePolicy(worktreeDir).catch(() => SECURE_DEFAULT_POLICY);
|
|
2968
|
+
const derived = await deriveRiskGlobs(worktreeDir).catch(() => []);
|
|
2969
|
+
return {
|
|
2970
|
+
autoMergeEnabled: explicit.autoMergeEnabled,
|
|
2971
|
+
autoPaths: explicit.autoPaths,
|
|
2972
|
+
riskPaths: [.../* @__PURE__ */ new Set([...explicit.riskPaths, ...derived])]
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
// src/commands/agents/code.ts
|
|
2977
|
+
var CliError2 = class extends Error {
|
|
2978
|
+
};
|
|
2979
|
+
var CODING_TOOLS = ["file.read", "file.write", "file.edit", "bash", "git.worktree", "verify", "forge.issue.get", "forge.pr.status"];
|
|
2980
|
+
var DEFAULT_PERSONA = [
|
|
2981
|
+
"You are an autonomous coding agent. You implement an assigned issue in",
|
|
2982
|
+
"the provided git worktree: read the relevant code, make small targeted",
|
|
2983
|
+
"edits with file.edit, and make the repo verification command pass via",
|
|
2984
|
+
"the verify tool. Do not open or merge PRs \u2014 the orchestrator does that.",
|
|
2985
|
+
"No change is done until verify is green."
|
|
2986
|
+
].join(" ");
|
|
2987
|
+
function readLitellmConfig(model) {
|
|
2988
|
+
const env = {};
|
|
2989
|
+
const envPath = join2(homedir4(), "litellm", ".env");
|
|
2990
|
+
if (existsSync4(envPath)) {
|
|
2991
|
+
for (const raw of readFileSync3(envPath, "utf8").split("\n")) {
|
|
2992
|
+
const line = raw.trim();
|
|
2993
|
+
const m = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line);
|
|
2994
|
+
if (m) env[m[1]] = m[2].trim().replace(/^["']|["']$/g, "");
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
for (const k of ["LITELLM_API_KEY", "LITELLM_MASTER_KEY", "LITELLM_BASE_URL"]) {
|
|
2998
|
+
if (process2.env[k]) env[k] = process2.env[k];
|
|
2999
|
+
}
|
|
3000
|
+
const apiKey = env.LITELLM_API_KEY || env.LITELLM_MASTER_KEY;
|
|
3001
|
+
const apiBase = (env.LITELLM_BASE_URL || "http://127.0.0.1:4000/v1").replace(/\/$/, "");
|
|
3002
|
+
if (!apiKey) throw new CliError2("No LITELLM_API_KEY / LITELLM_MASTER_KEY in ~/litellm/.env or env.");
|
|
3003
|
+
return { apiBase, apiKey, model: model || process2.env.APE_CHAT_BRIDGE_MODEL || "claude-haiku-4-5" };
|
|
3004
|
+
}
|
|
3005
|
+
function readPersona(file) {
|
|
3006
|
+
if (file && existsSync4(file)) return readFileSync3(file, "utf8");
|
|
3007
|
+
const agentJson = join2(homedir4(), ".openape", "agent", "agent.json");
|
|
3008
|
+
if (existsSync4(agentJson)) {
|
|
3009
|
+
try {
|
|
3010
|
+
const p = JSON.parse(readFileSync3(agentJson, "utf8"));
|
|
3011
|
+
if (p.systemPrompt?.trim()) return p.systemPrompt;
|
|
3012
|
+
} catch {
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
return DEFAULT_PERSONA;
|
|
3016
|
+
}
|
|
3017
|
+
async function fetchIssue(forge, ref) {
|
|
3018
|
+
const res = await runApeShell(buildIssueGet(forge, ref));
|
|
3019
|
+
if (res.exit_code !== 0) throw new CliError2(`could not fetch issue ${ref}: ${res.stderr.slice(0, 200)}`);
|
|
3020
|
+
const j = JSON.parse(res.stdout);
|
|
3021
|
+
const number = j.number ?? j.id ?? Number(ref);
|
|
3022
|
+
const title = j.title ?? j.fields?.["System.Title"] ?? `issue ${ref}`;
|
|
3023
|
+
return { number, title, body: j.body };
|
|
3024
|
+
}
|
|
3025
|
+
var codeAgentCommand = defineCommand24({
|
|
3026
|
+
meta: {
|
|
3027
|
+
name: "code",
|
|
3028
|
+
description: "Run a coding task: issue to worktree to edit to verify to PR (policy-gated merge). The agent never self-merges."
|
|
3029
|
+
},
|
|
3030
|
+
args: {
|
|
3031
|
+
"issue": { type: "string", description: "Issue / work-item ref to work on." },
|
|
3032
|
+
"repo": { type: "string", description: "Git remote URL of the target repo.", required: true },
|
|
3033
|
+
"forge": { type: "string", description: "github | azure | registered forge. Auto-detected from --repo if omitted." },
|
|
3034
|
+
"model": { type: "string", description: "Override LLM model." },
|
|
3035
|
+
"max-steps": { type: "string", description: "Max tool-call rounds (default 40)." },
|
|
3036
|
+
"persona-file": { type: "string", description: "File with the agent persona/system prompt." },
|
|
3037
|
+
"poll-label": { type: "string", description: "Poll mode: work all open issues with this label." }
|
|
3038
|
+
},
|
|
3039
|
+
async run({ args }) {
|
|
3040
|
+
const repo = args.repo;
|
|
3041
|
+
const forge = args.forge || detectForge(repo);
|
|
3042
|
+
const config = readLitellmConfig(args.model);
|
|
3043
|
+
const persona = readPersona(args["persona-file"]);
|
|
3044
|
+
const maxSteps = Number(args["max-steps"]) > 0 ? Number(args["max-steps"]) : 40;
|
|
3045
|
+
const tools = taskTools(CODING_TOOLS);
|
|
3046
|
+
const deps = {
|
|
3047
|
+
runtimeConfig: config,
|
|
3048
|
+
tools,
|
|
3049
|
+
persona,
|
|
3050
|
+
maxSteps,
|
|
3051
|
+
resolvePolicy: (worktree) => resolveMergePolicy(worktree),
|
|
3052
|
+
reviewer: createLlmReviewer(config),
|
|
3053
|
+
riskAssessor: createLlmRiskAssessor(config),
|
|
3054
|
+
log: (l) => consola21.info(l)
|
|
3055
|
+
};
|
|
3056
|
+
const refs = [];
|
|
3057
|
+
if (args["poll-label"]) {
|
|
3058
|
+
const slug = repo.replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
|
|
3059
|
+
const list = await runApeShell(`gh issue list --repo ${slug} --label '${args["poll-label"]}' --state open --json number --jq '.[].number'`);
|
|
3060
|
+
refs.push(...list.stdout.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
3061
|
+
if (refs.length === 0) {
|
|
3062
|
+
consola21.info("no open issues with that label");
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
} else if (args.issue) {
|
|
3066
|
+
refs.push(args.issue);
|
|
3067
|
+
} else {
|
|
3068
|
+
throw new CliError2("provide --issue <ref> or --poll-label <label>");
|
|
3069
|
+
}
|
|
3070
|
+
for (const ref of refs) {
|
|
3071
|
+
const issue = await fetchIssue(forge, ref);
|
|
3072
|
+
consola21.start(`coding issue #${issue.number}: ${issue.title}`);
|
|
3073
|
+
const result = await runCodingTask({ issue, repo, forge }, deps);
|
|
3074
|
+
consola21.box(`#${issue.number} -> ${result.outcome}
|
|
3075
|
+
PR: ${result.prRef ?? "(none)"}
|
|
3076
|
+
${result.reason}`);
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
});
|
|
3080
|
+
|
|
2622
3081
|
// src/commands/agents/destroy.ts
|
|
2623
3082
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
2624
3083
|
import { mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2625
3084
|
import { tmpdir, userInfo } from "os";
|
|
2626
|
-
import { join as
|
|
2627
|
-
import { defineCommand as
|
|
2628
|
-
import
|
|
3085
|
+
import { join as join4 } from "path";
|
|
3086
|
+
import { defineCommand as defineCommand25 } from "citty";
|
|
3087
|
+
import consola22 from "consola";
|
|
2629
3088
|
|
|
2630
3089
|
// src/lib/nest-registry.ts
|
|
2631
|
-
import { existsSync as
|
|
2632
|
-
import { homedir as
|
|
2633
|
-
import { join as
|
|
3090
|
+
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
3091
|
+
import { homedir as homedir5 } from "os";
|
|
3092
|
+
import { join as join3 } from "path";
|
|
2634
3093
|
function resolveRegistryPath() {
|
|
2635
|
-
if (
|
|
2636
|
-
if (
|
|
2637
|
-
return
|
|
3094
|
+
if (existsSync5("/var/openape/nest/agents.json")) return "/var/openape/nest/agents.json";
|
|
3095
|
+
if (existsSync5("/var/openape/nest")) return "/var/openape/nest/agents.json";
|
|
3096
|
+
return join3(homedir5(), ".openape", "nest", "agents.json");
|
|
2638
3097
|
}
|
|
2639
3098
|
function emptyRegistry() {
|
|
2640
3099
|
return { version: 1, agents: [] };
|
|
2641
3100
|
}
|
|
2642
3101
|
function readNestRegistry() {
|
|
2643
3102
|
const path2 = resolveRegistryPath();
|
|
2644
|
-
if (!
|
|
3103
|
+
if (!existsSync5(path2)) return emptyRegistry();
|
|
2645
3104
|
try {
|
|
2646
|
-
const parsed = JSON.parse(
|
|
3105
|
+
const parsed = JSON.parse(readFileSync4(path2, "utf8"));
|
|
2647
3106
|
if (parsed?.version !== 1 || !Array.isArray(parsed.agents)) return emptyRegistry();
|
|
2648
3107
|
return parsed;
|
|
2649
3108
|
} catch {
|
|
@@ -2731,7 +3190,7 @@ function readPasswordSilent(prompt) {
|
|
|
2731
3190
|
}
|
|
2732
3191
|
|
|
2733
3192
|
// src/commands/agents/destroy.ts
|
|
2734
|
-
var destroyAgentCommand =
|
|
3193
|
+
var destroyAgentCommand = defineCommand25({
|
|
2735
3194
|
meta: {
|
|
2736
3195
|
name: "destroy",
|
|
2737
3196
|
description: "Tear down an agent: remove macOS user, hard-delete IdP agent, drop all SSH keys"
|
|
@@ -2773,7 +3232,7 @@ var destroyAgentCommand = defineCommand24({
|
|
|
2773
3232
|
const resolved = lookupMacOSUserForAgent(name);
|
|
2774
3233
|
const macOSUsername = resolved?.name ?? macOSUsernameForAgent(name);
|
|
2775
3234
|
const homeDir = resolved?.homeDir ?? `/var/openape/homes/${macOSUsername}`;
|
|
2776
|
-
|
|
3235
|
+
consola22.start(`Running teardown for ${name} (Phase-G, root-stage)\u2026`);
|
|
2777
3236
|
runPhaseGTeardownInProcess({ name, homeDir, macOSUsername });
|
|
2778
3237
|
return;
|
|
2779
3238
|
}
|
|
@@ -2791,7 +3250,7 @@ var destroyAgentCommand = defineCommand24({
|
|
|
2791
3250
|
const osUser = isDarwin() ? lookupMacOSUserForAgent(name) : null;
|
|
2792
3251
|
const osUserExists = !args["keep-os-user"] && osUser !== null;
|
|
2793
3252
|
if (!idpExists && !osUserExists) {
|
|
2794
|
-
|
|
3253
|
+
consola22.info(`Nothing to destroy: no IdP agent and no OS user for "${name}".`);
|
|
2795
3254
|
return;
|
|
2796
3255
|
}
|
|
2797
3256
|
if (!args.force) {
|
|
@@ -2803,14 +3262,14 @@ var destroyAgentCommand = defineCommand24({
|
|
|
2803
3262
|
if (idpExists) {
|
|
2804
3263
|
consequences.push(args.soft ? `\u2022 Deactivate IdP agent ${idpAgent.email} (PATCH isActive=false)` : `\u2022 Hard-delete IdP agent ${idpAgent.email} and all its SSH keys`);
|
|
2805
3264
|
}
|
|
2806
|
-
|
|
3265
|
+
consola22.warn(`About to destroy "${name}":
|
|
2807
3266
|
${consequences.join("\n")}`);
|
|
2808
3267
|
if (!process.stdin.isTTY) {
|
|
2809
3268
|
throw new CliError(
|
|
2810
3269
|
"No TTY available for the interactive confirmation. Re-run with --force to skip the prompt (this is the same flag CI uses)."
|
|
2811
3270
|
);
|
|
2812
3271
|
}
|
|
2813
|
-
const confirmed = await
|
|
3272
|
+
const confirmed = await consola22.prompt("Proceed?", { type: "confirm", initial: false });
|
|
2814
3273
|
if (typeof confirmed === "symbol" || !confirmed) {
|
|
2815
3274
|
throw new CliExit(0);
|
|
2816
3275
|
}
|
|
@@ -2819,13 +3278,13 @@ ${consequences.join("\n")}`);
|
|
|
2819
3278
|
const id = encodeURIComponent(idpAgent.email);
|
|
2820
3279
|
if (args.soft) {
|
|
2821
3280
|
await apiFetch(`/api/my-agents/${id}`, { method: "PATCH", body: { isActive: false }, idp });
|
|
2822
|
-
|
|
3281
|
+
consola22.success(`Deactivated IdP agent ${idpAgent.email}`);
|
|
2823
3282
|
} else {
|
|
2824
3283
|
await apiFetch(`/api/my-agents/${id}`, { method: "DELETE", idp });
|
|
2825
|
-
|
|
3284
|
+
consola22.success(`Deleted IdP agent ${idpAgent.email}`);
|
|
2826
3285
|
}
|
|
2827
3286
|
} else {
|
|
2828
|
-
|
|
3287
|
+
consola22.info("No IdP agent to remove (skipped).");
|
|
2829
3288
|
}
|
|
2830
3289
|
if (osUserExists) {
|
|
2831
3290
|
const macOSUsername = osUser?.name ?? macOSUsernameForAgent(name);
|
|
@@ -2834,10 +3293,10 @@ ${consequences.join("\n")}`);
|
|
|
2834
3293
|
const isPhaseG = homeDir.startsWith("/var/openape/homes/");
|
|
2835
3294
|
if (isPhaseG) {
|
|
2836
3295
|
if (process.geteuid?.() === 0) {
|
|
2837
|
-
|
|
3296
|
+
consola22.start("Running teardown (Phase G \u2014 already root, no grant needed)\u2026");
|
|
2838
3297
|
runPhaseGTeardownInProcess({ name, homeDir, macOSUsername });
|
|
2839
3298
|
} else {
|
|
2840
|
-
|
|
3299
|
+
consola22.start("Running teardown (Phase G \u2014 no admin password needed)\u2026");
|
|
2841
3300
|
execFileSync6("apes", [
|
|
2842
3301
|
"run",
|
|
2843
3302
|
"--as",
|
|
@@ -2852,7 +3311,7 @@ ${consequences.join("\n")}`);
|
|
|
2852
3311
|
"--root-stage"
|
|
2853
3312
|
], { stdio: "inherit" });
|
|
2854
3313
|
}
|
|
2855
|
-
|
|
3314
|
+
consola22.info(`dscl record /Users/${macOSUsername} kept as tombstone (hidden, no home). Run \`sudo apes agents cleanup-orphans\` to sweep accumulated tombstones.`);
|
|
2856
3315
|
} else {
|
|
2857
3316
|
const sudo = whichBinary("sudo");
|
|
2858
3317
|
if (!sudo) {
|
|
@@ -2865,19 +3324,19 @@ ${consequences.join("\n")}`);
|
|
|
2865
3324
|
} catch (err) {
|
|
2866
3325
|
const headless = !process.stdin.isTTY && !process.env.APES_ADMIN_PASSWORD;
|
|
2867
3326
|
if (headless) {
|
|
2868
|
-
|
|
3327
|
+
consola22.warn(`Legacy OS teardown for ${name} requires a TTY or APES_ADMIN_PASSWORD; skipping. Run \`apes agents destroy ${name}\` from a shell later to fully clean up /Users/${name} + dscl record.`);
|
|
2869
3328
|
adminPassword = "";
|
|
2870
3329
|
} else {
|
|
2871
3330
|
throw err;
|
|
2872
3331
|
}
|
|
2873
3332
|
}
|
|
2874
3333
|
if (adminPassword) {
|
|
2875
|
-
const scratch = mkdtempSync(
|
|
2876
|
-
const scriptPath =
|
|
3334
|
+
const scratch = mkdtempSync(join4(tmpdir(), `apes-destroy-${name}-`));
|
|
3335
|
+
const scriptPath = join4(scratch, "teardown.sh");
|
|
2877
3336
|
try {
|
|
2878
3337
|
const script = buildDestroyTeardownScript({ name, homeDir, adminUser });
|
|
2879
3338
|
writeFileSync2(scriptPath, script, { mode: 448 });
|
|
2880
|
-
|
|
3339
|
+
consola22.start("Running teardown via sudo\u2026");
|
|
2881
3340
|
execFileSync6(sudo, ["-S", "--prompt=", "--", "bash", scriptPath], {
|
|
2882
3341
|
input: `${adminPassword}
|
|
2883
3342
|
${adminPassword}
|
|
@@ -2890,14 +3349,14 @@ ${adminPassword}
|
|
|
2890
3349
|
}
|
|
2891
3350
|
}
|
|
2892
3351
|
} else if (!args["keep-os-user"] && isDarwin()) {
|
|
2893
|
-
|
|
3352
|
+
consola22.info("No macOS user to remove (skipped).");
|
|
2894
3353
|
}
|
|
2895
3354
|
try {
|
|
2896
3355
|
removeNestAgent(name);
|
|
2897
3356
|
} catch (err) {
|
|
2898
|
-
|
|
3357
|
+
consola22.warn(`Could not update nest registry: ${err instanceof Error ? err.message : String(err)}`);
|
|
2899
3358
|
}
|
|
2900
|
-
|
|
3359
|
+
consola22.success(`Destroyed ${name}.`);
|
|
2901
3360
|
}
|
|
2902
3361
|
});
|
|
2903
3362
|
async function collectAdminPassword(opts) {
|
|
@@ -2911,9 +3370,9 @@ async function collectAdminPassword(opts) {
|
|
|
2911
3370
|
}
|
|
2912
3371
|
|
|
2913
3372
|
// src/commands/agents/list.ts
|
|
2914
|
-
import { defineCommand as
|
|
2915
|
-
import
|
|
2916
|
-
var listAgentsCommand =
|
|
3373
|
+
import { defineCommand as defineCommand26 } from "citty";
|
|
3374
|
+
import consola23 from "consola";
|
|
3375
|
+
var listAgentsCommand = defineCommand26({
|
|
2917
3376
|
meta: {
|
|
2918
3377
|
name: "list",
|
|
2919
3378
|
description: "List agents owned by the current user, with local OS-user status"
|
|
@@ -2964,7 +3423,7 @@ var listAgentsCommand = defineCommand25({
|
|
|
2964
3423
|
return;
|
|
2965
3424
|
}
|
|
2966
3425
|
if (rows.length === 0) {
|
|
2967
|
-
|
|
3426
|
+
consola23.info(args["include-inactive"] ? "No agents found." : "No active agents found. Use --include-inactive to show deactivated.");
|
|
2968
3427
|
return;
|
|
2969
3428
|
}
|
|
2970
3429
|
const nameW = Math.max(4, ...rows.map((r) => r.name.length));
|
|
@@ -2982,10 +3441,10 @@ var listAgentsCommand = defineCommand25({
|
|
|
2982
3441
|
});
|
|
2983
3442
|
|
|
2984
3443
|
// src/commands/agents/register.ts
|
|
2985
|
-
import { existsSync as
|
|
2986
|
-
import { defineCommand as
|
|
2987
|
-
import
|
|
2988
|
-
var registerAgentCommand =
|
|
3444
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
3445
|
+
import { defineCommand as defineCommand27 } from "citty";
|
|
3446
|
+
import consola24 from "consola";
|
|
3447
|
+
var registerAgentCommand = defineCommand27({
|
|
2989
3448
|
meta: {
|
|
2990
3449
|
name: "register",
|
|
2991
3450
|
description: "Register an agent at the IdP using a supplied public key"
|
|
@@ -3030,10 +3489,10 @@ var registerAgentCommand = defineCommand26({
|
|
|
3030
3489
|
throw new CliError("Pass either --public-key or --public-key-file, not both.");
|
|
3031
3490
|
}
|
|
3032
3491
|
if (!publicKey && keyFile) {
|
|
3033
|
-
if (!
|
|
3492
|
+
if (!existsSync6(keyFile)) {
|
|
3034
3493
|
throw new CliError(`Public-key file not found: ${keyFile}`);
|
|
3035
3494
|
}
|
|
3036
|
-
publicKey =
|
|
3495
|
+
publicKey = readFileSync5(keyFile, "utf-8").trim();
|
|
3037
3496
|
}
|
|
3038
3497
|
if (!publicKey) {
|
|
3039
3498
|
throw new CliError('Provide --public-key "<ssh-ed25519 line>" or --public-key-file <path>.');
|
|
@@ -3055,7 +3514,7 @@ var registerAgentCommand = defineCommand26({
|
|
|
3055
3514
|
`);
|
|
3056
3515
|
return;
|
|
3057
3516
|
}
|
|
3058
|
-
|
|
3517
|
+
consola24.success("Agent registered.");
|
|
3059
3518
|
console.log(` Name: ${result.name}`);
|
|
3060
3519
|
console.log(` Email: ${result.email}`);
|
|
3061
3520
|
console.log(` IdP: ${idp}`);
|
|
@@ -3068,28 +3527,28 @@ var registerAgentCommand = defineCommand26({
|
|
|
3068
3527
|
});
|
|
3069
3528
|
|
|
3070
3529
|
// src/commands/agents/run.ts
|
|
3071
|
-
import { existsSync as
|
|
3072
|
-
import { homedir as
|
|
3073
|
-
import { join as
|
|
3074
|
-
import { defineCommand as
|
|
3075
|
-
import
|
|
3530
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
3531
|
+
import { homedir as homedir7 } from "os";
|
|
3532
|
+
import { join as join6 } from "path";
|
|
3533
|
+
import { defineCommand as defineCommand28 } from "citty";
|
|
3534
|
+
import consola25 from "consola";
|
|
3076
3535
|
|
|
3077
3536
|
// src/lib/agent-secrets-runtime.ts
|
|
3078
|
-
import { existsSync as
|
|
3079
|
-
import { homedir as
|
|
3080
|
-
import { join as
|
|
3537
|
+
import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync6, watch } from "fs";
|
|
3538
|
+
import { homedir as homedir6 } from "os";
|
|
3539
|
+
import { join as join5 } from "path";
|
|
3081
3540
|
import { openString } from "@openape/core";
|
|
3082
|
-
var CONFIG_DIR2 =
|
|
3083
|
-
var SECRETS_DIR =
|
|
3084
|
-
var X25519_KEY_PATH =
|
|
3541
|
+
var CONFIG_DIR2 = join5(homedir6(), ".config", "openape");
|
|
3542
|
+
var SECRETS_DIR = join5(CONFIG_DIR2, "secrets.d");
|
|
3543
|
+
var X25519_KEY_PATH = join5(CONFIG_DIR2, "agent-x25519.key");
|
|
3085
3544
|
function envNameFromFile(file) {
|
|
3086
3545
|
if (!file.endsWith(".blob")) return null;
|
|
3087
3546
|
const env = file.slice(0, -".blob".length);
|
|
3088
3547
|
return /^[A-Z][A-Z0-9_]*$/.test(env) ? env : null;
|
|
3089
3548
|
}
|
|
3090
3549
|
function readAgentEncryptionKey(keyPath = X25519_KEY_PATH) {
|
|
3091
|
-
if (!
|
|
3092
|
-
const k =
|
|
3550
|
+
if (!existsSync7(keyPath)) return null;
|
|
3551
|
+
const k = readFileSync6(keyPath, "utf8").trim();
|
|
3093
3552
|
return k.length > 0 ? k : null;
|
|
3094
3553
|
}
|
|
3095
3554
|
function materializeSecrets(opts = {}) {
|
|
@@ -3100,12 +3559,12 @@ function materializeSecrets(opts = {}) {
|
|
|
3100
3559
|
const applied = [];
|
|
3101
3560
|
const failed = [];
|
|
3102
3561
|
const key = readAgentEncryptionKey(opts.keyPath);
|
|
3103
|
-
const files = key &&
|
|
3562
|
+
const files = key && existsSync7(dir) ? readdirSync(dir) : [];
|
|
3104
3563
|
for (const file of files) {
|
|
3105
3564
|
const name = envNameFromFile(file);
|
|
3106
3565
|
if (!name) continue;
|
|
3107
3566
|
try {
|
|
3108
|
-
const box = JSON.parse(
|
|
3567
|
+
const box = JSON.parse(readFileSync6(join5(dir, file), "utf8"));
|
|
3109
3568
|
env[name] = openString(box, key);
|
|
3110
3569
|
applied.push(name);
|
|
3111
3570
|
} catch (e) {
|
|
@@ -3132,7 +3591,7 @@ function startSecretsWatcher(opts = {}) {
|
|
|
3132
3591
|
appliedNames = new Set(r.applied);
|
|
3133
3592
|
};
|
|
3134
3593
|
run();
|
|
3135
|
-
if (!
|
|
3594
|
+
if (!existsSync7(dir)) return () => {
|
|
3136
3595
|
};
|
|
3137
3596
|
let timer = null;
|
|
3138
3597
|
const watcher = watch(dir, () => {
|
|
@@ -3147,13 +3606,13 @@ function startSecretsWatcher(opts = {}) {
|
|
|
3147
3606
|
}
|
|
3148
3607
|
|
|
3149
3608
|
// src/commands/agents/run.ts
|
|
3150
|
-
var AUTH_PATH =
|
|
3151
|
-
var TASK_CACHE_DIR =
|
|
3609
|
+
var AUTH_PATH = join6(homedir7(), ".config", "apes", "auth.json");
|
|
3610
|
+
var TASK_CACHE_DIR = join6(homedir7(), ".openape", "agent", "tasks");
|
|
3152
3611
|
function readAuth() {
|
|
3153
|
-
if (!
|
|
3612
|
+
if (!existsSync8(AUTH_PATH)) {
|
|
3154
3613
|
throw new CliError(`No agent auth found at ${AUTH_PATH}. Run \`apes agents spawn <name>\` first.`);
|
|
3155
3614
|
}
|
|
3156
|
-
const parsed = JSON.parse(
|
|
3615
|
+
const parsed = JSON.parse(readFileSync7(AUTH_PATH, "utf8"));
|
|
3157
3616
|
if (!parsed.access_token) throw new CliError("auth.json missing access_token");
|
|
3158
3617
|
return parsed;
|
|
3159
3618
|
}
|
|
@@ -3168,7 +3627,7 @@ async function postRunResultToChat(opts) {
|
|
|
3168
3627
|
const ownerLower = opts.ownerEmail.toLowerCase();
|
|
3169
3628
|
const ownerRow = contacts.find((c) => c.peerEmail.toLowerCase() === ownerLower && c.connected && c.roomId);
|
|
3170
3629
|
if (!ownerRow?.roomId) {
|
|
3171
|
-
|
|
3630
|
+
consola25.info("chat DM skipped \u2014 no active room with owner (accept the contact request in chat to enable)");
|
|
3172
3631
|
return;
|
|
3173
3632
|
}
|
|
3174
3633
|
const prefix = opts.status === "ok" ? "\u2705" : "\u274C";
|
|
@@ -3182,33 +3641,33 @@ ${msg}`.slice(0, 9e3);
|
|
|
3182
3641
|
body: JSON.stringify({ body })
|
|
3183
3642
|
});
|
|
3184
3643
|
if (!postRes.ok) {
|
|
3185
|
-
|
|
3644
|
+
consola25.warn(`chat DM post failed: ${postRes.status}`);
|
|
3186
3645
|
}
|
|
3187
3646
|
} catch (err) {
|
|
3188
|
-
|
|
3647
|
+
consola25.warn(`chat DM error: ${err.message}`);
|
|
3189
3648
|
}
|
|
3190
3649
|
}
|
|
3191
3650
|
function readTaskSpec(taskId) {
|
|
3192
|
-
const path2 =
|
|
3193
|
-
if (!
|
|
3651
|
+
const path2 = join6(TASK_CACHE_DIR, `${taskId}.json`);
|
|
3652
|
+
if (!existsSync8(path2)) {
|
|
3194
3653
|
throw new CliError(`No cached task spec at ${path2}. Run \`apes agents sync\` first to pull the task list from troop.`);
|
|
3195
3654
|
}
|
|
3196
|
-
return JSON.parse(
|
|
3655
|
+
return JSON.parse(readFileSync7(path2, "utf8"));
|
|
3197
3656
|
}
|
|
3198
|
-
var AGENT_CONFIG_PATH =
|
|
3657
|
+
var AGENT_CONFIG_PATH = join6(homedir7(), ".openape", "agent", "agent.json");
|
|
3199
3658
|
function readAgentConfig() {
|
|
3200
|
-
if (!
|
|
3659
|
+
if (!existsSync8(AGENT_CONFIG_PATH)) return { systemPrompt: "" };
|
|
3201
3660
|
try {
|
|
3202
|
-
return JSON.parse(
|
|
3661
|
+
return JSON.parse(readFileSync7(AGENT_CONFIG_PATH, "utf8"));
|
|
3203
3662
|
} catch {
|
|
3204
3663
|
return { systemPrompt: "" };
|
|
3205
3664
|
}
|
|
3206
3665
|
}
|
|
3207
|
-
function
|
|
3208
|
-
const envPath =
|
|
3666
|
+
function readLitellmConfig2(model) {
|
|
3667
|
+
const envPath = join6(homedir7(), "litellm", ".env");
|
|
3209
3668
|
const env = {};
|
|
3210
|
-
if (
|
|
3211
|
-
for (const line of
|
|
3669
|
+
if (existsSync8(envPath)) {
|
|
3670
|
+
for (const line of readFileSync7(envPath, "utf8").split(/\r?\n/)) {
|
|
3212
3671
|
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
3213
3672
|
if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
|
|
3214
3673
|
}
|
|
@@ -3223,7 +3682,7 @@ function readLitellmConfig(model) {
|
|
|
3223
3682
|
}
|
|
3224
3683
|
return { apiBase, apiKey, model: model || "claude-haiku-4-5" };
|
|
3225
3684
|
}
|
|
3226
|
-
var runAgentCommand =
|
|
3685
|
+
var runAgentCommand = defineCommand28({
|
|
3227
3686
|
meta: {
|
|
3228
3687
|
name: "run",
|
|
3229
3688
|
description: "Execute one task (typically launchd-invoked). Reports the run record to troop."
|
|
@@ -3246,10 +3705,10 @@ var runAgentCommand = defineCommand27({
|
|
|
3246
3705
|
async run({ args }) {
|
|
3247
3706
|
const taskId = args["task-id"];
|
|
3248
3707
|
const auth = readAuth();
|
|
3249
|
-
materializeSecrets({ log: (l) =>
|
|
3708
|
+
materializeSecrets({ log: (l) => consola25.info(l) });
|
|
3250
3709
|
const spec = readTaskSpec(taskId);
|
|
3251
3710
|
const agentCfg = readAgentConfig();
|
|
3252
|
-
const config =
|
|
3711
|
+
const config = readLitellmConfig2(args.model);
|
|
3253
3712
|
let tools;
|
|
3254
3713
|
try {
|
|
3255
3714
|
tools = taskTools(spec.tools);
|
|
@@ -3258,7 +3717,7 @@ var runAgentCommand = defineCommand27({
|
|
|
3258
3717
|
}
|
|
3259
3718
|
const troop = new TroopClient(resolveTroopUrl(args["troop-url"]), auth.access_token);
|
|
3260
3719
|
const { id: runId } = await troop.startRun(taskId);
|
|
3261
|
-
|
|
3720
|
+
consola25.info(`Run ${runId} started for task ${taskId}`);
|
|
3262
3721
|
try {
|
|
3263
3722
|
const result = await runLoop({
|
|
3264
3723
|
config,
|
|
@@ -3277,7 +3736,7 @@ var runAgentCommand = defineCommand27({
|
|
|
3277
3736
|
step_count: result.stepCount,
|
|
3278
3737
|
trace: result.trace
|
|
3279
3738
|
});
|
|
3280
|
-
|
|
3739
|
+
consola25.success(`Run ${runId} ${result.status} (${result.stepCount} steps)`);
|
|
3281
3740
|
if (auth.owner_email) {
|
|
3282
3741
|
await postRunResultToChat({
|
|
3283
3742
|
authToken: auth.access_token,
|
|
@@ -3314,17 +3773,17 @@ var runAgentCommand = defineCommand27({
|
|
|
3314
3773
|
});
|
|
3315
3774
|
|
|
3316
3775
|
// src/commands/agents/serve.ts
|
|
3317
|
-
import { existsSync as
|
|
3318
|
-
import { homedir as
|
|
3319
|
-
import { join as
|
|
3776
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
3777
|
+
import { homedir as homedir8 } from "os";
|
|
3778
|
+
import { join as join7 } from "path";
|
|
3320
3779
|
import { createInterface } from "readline";
|
|
3321
|
-
import { defineCommand as
|
|
3322
|
-
var AUTH_PATH2 =
|
|
3323
|
-
function
|
|
3324
|
-
const envPath =
|
|
3780
|
+
import { defineCommand as defineCommand29 } from "citty";
|
|
3781
|
+
var AUTH_PATH2 = join7(homedir8(), ".config", "apes", "auth.json");
|
|
3782
|
+
function readLitellmConfig3(model) {
|
|
3783
|
+
const envPath = join7(homedir8(), "litellm", ".env");
|
|
3325
3784
|
const env = {};
|
|
3326
|
-
if (
|
|
3327
|
-
for (const line of
|
|
3785
|
+
if (existsSync9(envPath)) {
|
|
3786
|
+
for (const line of readFileSync8(envPath, "utf8").split(/\r?\n/)) {
|
|
3328
3787
|
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
3329
3788
|
if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
|
|
3330
3789
|
}
|
|
@@ -3341,7 +3800,7 @@ function emit(event) {
|
|
|
3341
3800
|
process.stdout.write(`${JSON.stringify(event)}
|
|
3342
3801
|
`);
|
|
3343
3802
|
}
|
|
3344
|
-
var serveAgentCommand =
|
|
3803
|
+
var serveAgentCommand = defineCommand29({
|
|
3345
3804
|
meta: {
|
|
3346
3805
|
name: "serve",
|
|
3347
3806
|
description: "Long-running stdio RPC server for chat-bridge subprocess use."
|
|
@@ -3356,9 +3815,9 @@ var serveAgentCommand = defineCommand28({
|
|
|
3356
3815
|
if (!args.rpc) {
|
|
3357
3816
|
throw new CliError("apes agents serve currently only supports --rpc mode");
|
|
3358
3817
|
}
|
|
3359
|
-
if (
|
|
3818
|
+
if (existsSync9(AUTH_PATH2)) {
|
|
3360
3819
|
try {
|
|
3361
|
-
JSON.parse(
|
|
3820
|
+
JSON.parse(readFileSync8(AUTH_PATH2, "utf8"));
|
|
3362
3821
|
} catch {
|
|
3363
3822
|
}
|
|
3364
3823
|
}
|
|
@@ -3396,7 +3855,7 @@ var serveAgentCommand = defineCommand28({
|
|
|
3396
3855
|
}
|
|
3397
3856
|
});
|
|
3398
3857
|
async function handleInbound(msg, sessions) {
|
|
3399
|
-
const config =
|
|
3858
|
+
const config = readLitellmConfig3(msg.model);
|
|
3400
3859
|
const tools = taskTools(msg.tools ?? []);
|
|
3401
3860
|
const maxSteps = msg.max_steps ?? 10;
|
|
3402
3861
|
let session = sessions.get(msg.session_id);
|
|
@@ -3441,9 +3900,9 @@ async function handleInbound(msg, sessions) {
|
|
|
3441
3900
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
3442
3901
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3443
3902
|
import { tmpdir as tmpdir2 } from "os";
|
|
3444
|
-
import { join as
|
|
3445
|
-
import { defineCommand as
|
|
3446
|
-
import
|
|
3903
|
+
import { join as join9 } from "path";
|
|
3904
|
+
import { defineCommand as defineCommand30 } from "citty";
|
|
3905
|
+
import consola26 from "consola";
|
|
3447
3906
|
|
|
3448
3907
|
// src/lib/troop-bootstrap.ts
|
|
3449
3908
|
var SYNC_LABEL_PREFIX = "openape.troop.sync";
|
|
@@ -3499,13 +3958,13 @@ ${envBlock} <key>StartInterval</key>
|
|
|
3499
3958
|
|
|
3500
3959
|
// src/lib/keygen.ts
|
|
3501
3960
|
import { Buffer as Buffer4 } from "buffer";
|
|
3502
|
-
import { existsSync as
|
|
3961
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
|
|
3503
3962
|
import { generateKeyPairSync } from "crypto";
|
|
3504
|
-
import { homedir as
|
|
3963
|
+
import { homedir as homedir9 } from "os";
|
|
3505
3964
|
import { dirname, resolve as resolve2 } from "path";
|
|
3506
3965
|
import { generateX25519KeyPair } from "@openape/core";
|
|
3507
3966
|
function resolveKeyPath(p) {
|
|
3508
|
-
return resolve2(p.replace(/^~/,
|
|
3967
|
+
return resolve2(p.replace(/^~/, homedir9()));
|
|
3509
3968
|
}
|
|
3510
3969
|
function buildSshEd25519Line(rawPub) {
|
|
3511
3970
|
const keyTypeStr = "ssh-ed25519";
|
|
@@ -3518,10 +3977,10 @@ function buildSshEd25519Line(rawPub) {
|
|
|
3518
3977
|
}
|
|
3519
3978
|
function readPublicKey(keyPath) {
|
|
3520
3979
|
const pubPath = `${keyPath}.pub`;
|
|
3521
|
-
if (
|
|
3522
|
-
return
|
|
3980
|
+
if (existsSync10(pubPath)) {
|
|
3981
|
+
return readFileSync9(pubPath, "utf-8").trim();
|
|
3523
3982
|
}
|
|
3524
|
-
const keyContent =
|
|
3983
|
+
const keyContent = readFileSync9(keyPath, "utf-8");
|
|
3525
3984
|
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
3526
3985
|
const jwk = privateKey.export({ format: "jwk" });
|
|
3527
3986
|
const pubBytes = Buffer4.from(jwk.x, "base64url");
|
|
@@ -3530,7 +3989,7 @@ function readPublicKey(keyPath) {
|
|
|
3530
3989
|
function generateAndSaveKey(keyPath) {
|
|
3531
3990
|
const resolved = resolveKeyPath(keyPath);
|
|
3532
3991
|
const dir = dirname(resolved);
|
|
3533
|
-
if (!
|
|
3992
|
+
if (!existsSync10(dir)) {
|
|
3534
3993
|
mkdirSync2(dir, { recursive: true });
|
|
3535
3994
|
}
|
|
3536
3995
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
@@ -3559,14 +4018,14 @@ function generateKeyPairInMemory() {
|
|
|
3559
4018
|
|
|
3560
4019
|
// src/lib/llm-bridge.ts
|
|
3561
4020
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
3562
|
-
import { existsSync as
|
|
3563
|
-
import { homedir as
|
|
3564
|
-
import { dirname as dirname2, join as
|
|
4021
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
4022
|
+
import { homedir as homedir10 } from "os";
|
|
4023
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
3565
4024
|
var PLIST_LABEL_PREFIX = "eco.hofmann.apes.bridge";
|
|
3566
|
-
function readLitellmEnv(envPath =
|
|
3567
|
-
if (!
|
|
4025
|
+
function readLitellmEnv(envPath = join8(homedir10(), "litellm", ".env")) {
|
|
4026
|
+
if (!existsSync11(envPath)) return null;
|
|
3568
4027
|
try {
|
|
3569
|
-
const text =
|
|
4028
|
+
const text = readFileSync10(envPath, "utf8");
|
|
3570
4029
|
const out = {};
|
|
3571
4030
|
for (const line of text.split("\n")) {
|
|
3572
4031
|
const trimmed = line.trim();
|
|
@@ -3703,7 +4162,7 @@ function readMacOSUidOrNull(name) {
|
|
|
3703
4162
|
return null;
|
|
3704
4163
|
}
|
|
3705
4164
|
}
|
|
3706
|
-
var spawnAgentCommand =
|
|
4165
|
+
var spawnAgentCommand = defineCommand30({
|
|
3707
4166
|
meta: {
|
|
3708
4167
|
name: "spawn",
|
|
3709
4168
|
description: "Provision a local macOS agent end-to-end (OS user, keypair, IdP agent, Claude hook)"
|
|
@@ -3791,15 +4250,15 @@ and try again.`
|
|
|
3791
4250
|
throw new CliError(`macOS user "${existing.name}" already exists (uid=${existing.uid ?? "?"}). Refusing to overwrite.`);
|
|
3792
4251
|
}
|
|
3793
4252
|
const homeDir = `/var/openape/homes/${macOSUsername}`;
|
|
3794
|
-
const scratch = mkdtempSync2(
|
|
3795
|
-
const scriptPath =
|
|
4253
|
+
const scratch = mkdtempSync2(join9(tmpdir2(), `apes-spawn-${name}-`));
|
|
4254
|
+
const scriptPath = join9(scratch, "setup.sh");
|
|
3796
4255
|
try {
|
|
3797
|
-
|
|
4256
|
+
consola26.start(`Generating keypair for ${name}\u2026`);
|
|
3798
4257
|
const { privatePem, publicSshLine, x25519PrivateKey, x25519PublicKey } = generateKeyPairInMemory();
|
|
3799
|
-
|
|
4258
|
+
consola26.start(`Registering agent at ${idp}\u2026`);
|
|
3800
4259
|
const registration = await registerAgentAtIdp({ name, publicKey: publicSshLine, idp });
|
|
3801
|
-
|
|
3802
|
-
|
|
4260
|
+
consola26.success(`Registered as ${registration.email}`);
|
|
4261
|
+
consola26.start("Issuing agent access token\u2026");
|
|
3803
4262
|
const { token, expiresIn } = await issueAgentToken({
|
|
3804
4263
|
idp,
|
|
3805
4264
|
agentEmail: registration.email,
|
|
@@ -3869,11 +4328,11 @@ and try again.`
|
|
|
3869
4328
|
writeFileSync4(scriptPath, script, { mode: 448 });
|
|
3870
4329
|
const alreadyRoot = process.getuid?.() === 0;
|
|
3871
4330
|
if (alreadyRoot) {
|
|
3872
|
-
|
|
4331
|
+
consola26.start("Running privileged setup directly (already root)\u2026");
|
|
3873
4332
|
execFileSync8("bash", [scriptPath], { stdio: "inherit" });
|
|
3874
4333
|
} else {
|
|
3875
|
-
|
|
3876
|
-
|
|
4334
|
+
consola26.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
|
|
4335
|
+
consola26.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
|
|
3877
4336
|
execFileSync8(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
|
|
3878
4337
|
}
|
|
3879
4338
|
try {
|
|
@@ -3891,13 +4350,13 @@ and try again.`
|
|
|
3891
4350
|
} : void 0
|
|
3892
4351
|
});
|
|
3893
4352
|
} catch (err) {
|
|
3894
|
-
|
|
4353
|
+
consola26.warn(`Could not write to nest registry: ${err instanceof Error ? err.message : String(err)}`);
|
|
3895
4354
|
}
|
|
3896
|
-
|
|
3897
|
-
|
|
4355
|
+
consola26.success(`Agent ${name} spawned.`);
|
|
4356
|
+
consola26.info(`\u{1F517} Troop: https://troop.openape.ai/agents/${name}`);
|
|
3898
4357
|
if (withBridge) {
|
|
3899
|
-
|
|
3900
|
-
|
|
4358
|
+
consola26.info(`On first boot, the bridge will send you a contact request from ${registration.email}.`);
|
|
4359
|
+
consola26.info("Open chat.openape.ai and accept it to start chatting with the agent.");
|
|
3901
4360
|
}
|
|
3902
4361
|
console.log("");
|
|
3903
4362
|
console.log("Run as the agent with:");
|
|
@@ -3931,11 +4390,11 @@ async function resolveClaudeToken(opts) {
|
|
|
3931
4390
|
}
|
|
3932
4391
|
|
|
3933
4392
|
// src/commands/agents/sync.ts
|
|
3934
|
-
import { chownSync, existsSync as
|
|
3935
|
-
import { homedir as
|
|
3936
|
-
import { join as
|
|
3937
|
-
import { defineCommand as
|
|
3938
|
-
import
|
|
4393
|
+
import { chownSync, existsSync as existsSync12, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync11, rmSync as rmSync4, statSync, writeFileSync as writeFileSync5 } from "fs";
|
|
4394
|
+
import { homedir as homedir11 } from "os";
|
|
4395
|
+
import { join as join10 } from "path";
|
|
4396
|
+
import { defineCommand as defineCommand31 } from "citty";
|
|
4397
|
+
import consola27 from "consola";
|
|
3939
4398
|
|
|
3940
4399
|
// src/lib/macos-host.ts
|
|
3941
4400
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
@@ -3962,15 +4421,15 @@ function getHostname() {
|
|
|
3962
4421
|
}
|
|
3963
4422
|
|
|
3964
4423
|
// src/commands/agents/sync.ts
|
|
3965
|
-
var AUTH_PATH3 =
|
|
3966
|
-
var TASK_CACHE_DIR2 =
|
|
4424
|
+
var AUTH_PATH3 = join10(homedir11(), ".config", "apes", "auth.json");
|
|
4425
|
+
var TASK_CACHE_DIR2 = join10(homedir11(), ".openape", "agent", "tasks");
|
|
3967
4426
|
function readAuthJson() {
|
|
3968
|
-
if (!
|
|
4427
|
+
if (!existsSync12(AUTH_PATH3)) {
|
|
3969
4428
|
throw new CliError(
|
|
3970
4429
|
`No agent auth found at ${AUTH_PATH3}. Run \`apes agents spawn <name>\` to provision an agent first.`
|
|
3971
4430
|
);
|
|
3972
4431
|
}
|
|
3973
|
-
const raw =
|
|
4432
|
+
const raw = readFileSync11(AUTH_PATH3, "utf8");
|
|
3974
4433
|
let parsed;
|
|
3975
4434
|
try {
|
|
3976
4435
|
parsed = JSON.parse(raw);
|
|
@@ -3994,7 +4453,7 @@ function agentNameFromEmail(email) {
|
|
|
3994
4453
|
}
|
|
3995
4454
|
return before.slice(0, dashIdx);
|
|
3996
4455
|
}
|
|
3997
|
-
var syncAgentCommand =
|
|
4456
|
+
var syncAgentCommand = defineCommand31({
|
|
3998
4457
|
meta: {
|
|
3999
4458
|
name: "sync",
|
|
4000
4459
|
description: "Pull this agent's task list from troop.openape.ai and reconcile launchd plists"
|
|
@@ -4018,22 +4477,22 @@ var syncAgentCommand = defineCommand30({
|
|
|
4018
4477
|
if (!auth.owner_email) {
|
|
4019
4478
|
throw new CliError(`${AUTH_PATH3} is missing owner_email \u2014 re-run \`apes agents spawn\` to update.`);
|
|
4020
4479
|
}
|
|
4021
|
-
|
|
4480
|
+
consola27.start(`Syncing ${agentName} (${host}, hostId ${hostId.slice(0, 8)}\u2026) with ${troopUrl}`);
|
|
4022
4481
|
const sync = await client.sync({
|
|
4023
4482
|
hostname: host,
|
|
4024
4483
|
hostId,
|
|
4025
4484
|
ownerEmail: auth.owner_email
|
|
4026
4485
|
});
|
|
4027
|
-
|
|
4486
|
+
consola27.info(sync.first_sync ? "\u2713 first sync \u2014 agent registered" : "\u2713 presence updated");
|
|
4028
4487
|
const { system_prompt: systemPrompt, tools, skills, tasks } = await client.listTasks();
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4488
|
+
consola27.info(`Pulled ${tasks.length} task${tasks.length === 1 ? "" : "s"}`);
|
|
4489
|
+
consola27.info(`Tools enabled: ${tools.length === 0 ? "(none)" : tools.join(", ")}`);
|
|
4490
|
+
consola27.info(`Skills: ${skills.length === 0 ? "(none)" : skills.map((s) => s.name).join(", ")}`);
|
|
4032
4491
|
let agentUid = null;
|
|
4033
4492
|
let agentGid = null;
|
|
4034
4493
|
if (process.geteuid?.() === 0) {
|
|
4035
4494
|
try {
|
|
4036
|
-
const homeStat = statSync(
|
|
4495
|
+
const homeStat = statSync(homedir11());
|
|
4037
4496
|
agentUid = homeStat.uid;
|
|
4038
4497
|
agentGid = homeStat.gid;
|
|
4039
4498
|
} catch {
|
|
@@ -4047,11 +4506,11 @@ var syncAgentCommand = defineCommand30({
|
|
|
4047
4506
|
}
|
|
4048
4507
|
}
|
|
4049
4508
|
}
|
|
4050
|
-
const agentDir =
|
|
4509
|
+
const agentDir = join10(homedir11(), ".openape", "agent");
|
|
4051
4510
|
mkdirSync3(agentDir, { recursive: true });
|
|
4052
|
-
chownToAgent(
|
|
4511
|
+
chownToAgent(join10(homedir11(), ".openape"));
|
|
4053
4512
|
chownToAgent(agentDir);
|
|
4054
|
-
const agentJsonPath =
|
|
4513
|
+
const agentJsonPath = join10(agentDir, "agent.json");
|
|
4055
4514
|
writeFileSync5(
|
|
4056
4515
|
agentJsonPath,
|
|
4057
4516
|
`${JSON.stringify({ systemPrompt, tools }, null, 2)}
|
|
@@ -4062,12 +4521,12 @@ var syncAgentCommand = defineCommand30({
|
|
|
4062
4521
|
mkdirSync3(TASK_CACHE_DIR2, { recursive: true });
|
|
4063
4522
|
chownToAgent(TASK_CACHE_DIR2);
|
|
4064
4523
|
for (const task of tasks) {
|
|
4065
|
-
const path2 =
|
|
4524
|
+
const path2 = join10(TASK_CACHE_DIR2, `${task.taskId}.json`);
|
|
4066
4525
|
writeFileSync5(path2, `${JSON.stringify(task, null, 2)}
|
|
4067
4526
|
`, { mode: 384 });
|
|
4068
4527
|
chownToAgent(path2);
|
|
4069
4528
|
}
|
|
4070
|
-
const skillsDir =
|
|
4529
|
+
const skillsDir = join10(agentDir, "skills");
|
|
4071
4530
|
mkdirSync3(skillsDir, { recursive: true });
|
|
4072
4531
|
chownToAgent(skillsDir);
|
|
4073
4532
|
const incomingNames = new Set(skills.map((s) => s.name));
|
|
@@ -4075,30 +4534,30 @@ var syncAgentCommand = defineCommand30({
|
|
|
4075
4534
|
for (const entry of readdirSync2(skillsDir)) {
|
|
4076
4535
|
if (incomingNames.has(entry)) continue;
|
|
4077
4536
|
try {
|
|
4078
|
-
rmSync4(
|
|
4537
|
+
rmSync4(join10(skillsDir, entry), { recursive: true, force: true });
|
|
4079
4538
|
} catch {
|
|
4080
4539
|
}
|
|
4081
4540
|
}
|
|
4082
4541
|
} catch {
|
|
4083
4542
|
}
|
|
4084
4543
|
for (const skill of skills) {
|
|
4085
|
-
const skillDir =
|
|
4544
|
+
const skillDir = join10(skillsDir, skill.name);
|
|
4086
4545
|
mkdirSync3(skillDir, { recursive: true });
|
|
4087
4546
|
chownToAgent(skillDir);
|
|
4088
|
-
const skillPath =
|
|
4547
|
+
const skillPath = join10(skillDir, "SKILL.md");
|
|
4089
4548
|
writeFileSync5(skillPath, skill.body.endsWith("\n") ? skill.body : `${skill.body}
|
|
4090
4549
|
`, { mode: 384 });
|
|
4091
4550
|
chownToAgent(skillPath);
|
|
4092
4551
|
}
|
|
4093
|
-
|
|
4552
|
+
consola27.success("Sync complete.");
|
|
4094
4553
|
}
|
|
4095
4554
|
});
|
|
4096
4555
|
|
|
4097
4556
|
// src/commands/agents/index.ts
|
|
4098
|
-
var agentsCommand =
|
|
4557
|
+
var agentsCommand = defineCommand32({
|
|
4099
4558
|
meta: {
|
|
4100
4559
|
name: "agents",
|
|
4101
|
-
description: "Manage owned agents (register, spawn, list, destroy, allow, sync, run, serve, cleanup-orphans)"
|
|
4560
|
+
description: "Manage owned agents (register, spawn, list, destroy, allow, sync, run, serve, code, cleanup-orphans)"
|
|
4102
4561
|
},
|
|
4103
4562
|
subCommands: {
|
|
4104
4563
|
register: registerAgentCommand,
|
|
@@ -4109,27 +4568,28 @@ var agentsCommand = defineCommand31({
|
|
|
4109
4568
|
sync: syncAgentCommand,
|
|
4110
4569
|
run: runAgentCommand,
|
|
4111
4570
|
serve: serveAgentCommand,
|
|
4571
|
+
code: codeAgentCommand,
|
|
4112
4572
|
"cleanup-orphans": cleanupOrphansCommand
|
|
4113
4573
|
}
|
|
4114
4574
|
});
|
|
4115
4575
|
|
|
4116
4576
|
// src/commands/nest/index.ts
|
|
4117
|
-
import { defineCommand as
|
|
4577
|
+
import { defineCommand as defineCommand40 } from "citty";
|
|
4118
4578
|
|
|
4119
4579
|
// src/commands/nest/authorize.ts
|
|
4120
4580
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
4121
|
-
import { existsSync as
|
|
4581
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
4582
|
+
import { join as join12 } from "path";
|
|
4583
|
+
import { defineCommand as defineCommand34 } from "citty";
|
|
4584
|
+
import consola29 from "consola";
|
|
4585
|
+
|
|
4586
|
+
// src/commands/nest/enroll.ts
|
|
4587
|
+
import { hostname as hostname4, homedir as homedir12 } from "os";
|
|
4588
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, chmodSync } from "fs";
|
|
4122
4589
|
import { join as join11 } from "path";
|
|
4123
4590
|
import { defineCommand as defineCommand33 } from "citty";
|
|
4124
4591
|
import consola28 from "consola";
|
|
4125
|
-
|
|
4126
|
-
// src/commands/nest/enroll.ts
|
|
4127
|
-
import { hostname as hostname4, homedir as homedir11 } from "os";
|
|
4128
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, chmodSync } from "fs";
|
|
4129
|
-
import { join as join10 } from "path";
|
|
4130
|
-
import { defineCommand as defineCommand32 } from "citty";
|
|
4131
|
-
import consola27 from "consola";
|
|
4132
|
-
var NEST_DATA_DIR = join10(homedir11(), ".openape", "nest");
|
|
4592
|
+
var NEST_DATA_DIR = join11(homedir12(), ".openape", "nest");
|
|
4133
4593
|
function nestAgentName() {
|
|
4134
4594
|
const raw = hostname4().toLowerCase();
|
|
4135
4595
|
const head = raw.split(".")[0] ?? raw;
|
|
@@ -4137,7 +4597,7 @@ function nestAgentName() {
|
|
|
4137
4597
|
const trimmed = safe.slice(0, 16);
|
|
4138
4598
|
return `nest-${trimmed || "host"}`;
|
|
4139
4599
|
}
|
|
4140
|
-
var enrollNestCommand =
|
|
4600
|
+
var enrollNestCommand = defineCommand33({
|
|
4141
4601
|
meta: {
|
|
4142
4602
|
name: "enroll",
|
|
4143
4603
|
description: "Register the local nest as a DDISA agent at the IdP. One-time per machine. Required before `apes nest authorize` so YOLO-policies have a target identity."
|
|
@@ -4162,25 +4622,25 @@ var enrollNestCommand = defineCommand32({
|
|
|
4162
4622
|
throw new CliError("Run `apes login <email>` first \u2014 nest enroll attaches the new identity to your owner account.");
|
|
4163
4623
|
}
|
|
4164
4624
|
const name = args.name || nestAgentName();
|
|
4165
|
-
const authPath =
|
|
4166
|
-
if (
|
|
4625
|
+
const authPath = join11(NEST_DATA_DIR, ".config", "apes", "auth.json");
|
|
4626
|
+
if (existsSync13(authPath) && !args.force) {
|
|
4167
4627
|
throw new CliError(`Nest already enrolled at ${authPath}. Pass --force to re-enroll.`);
|
|
4168
4628
|
}
|
|
4169
|
-
const sshDir =
|
|
4170
|
-
const configDir =
|
|
4629
|
+
const sshDir = join11(NEST_DATA_DIR, ".ssh");
|
|
4630
|
+
const configDir = join11(NEST_DATA_DIR, ".config", "apes");
|
|
4171
4631
|
mkdirSync4(sshDir, { recursive: true });
|
|
4172
4632
|
mkdirSync4(configDir, { recursive: true });
|
|
4173
|
-
|
|
4633
|
+
consola28.start(`Generating keypair for ${name}\u2026`);
|
|
4174
4634
|
const { privatePem, publicSshLine } = generateKeyPairInMemory();
|
|
4175
|
-
writeFileSync6(
|
|
4635
|
+
writeFileSync6(join11(sshDir, "id_ed25519"), `${privatePem.trimEnd()}
|
|
4176
4636
|
`, { mode: 384 });
|
|
4177
|
-
writeFileSync6(
|
|
4637
|
+
writeFileSync6(join11(sshDir, "id_ed25519.pub"), `${publicSshLine}
|
|
4178
4638
|
`, { mode: 420 });
|
|
4179
4639
|
chmodSync(sshDir, 448);
|
|
4180
|
-
|
|
4640
|
+
consola28.start(`Registering nest at ${idp}\u2026`);
|
|
4181
4641
|
const registration = await registerAgentAtIdp({ name, publicKey: publicSshLine, idp });
|
|
4182
|
-
|
|
4183
|
-
|
|
4642
|
+
consola28.success(`Registered as ${registration.email}`);
|
|
4643
|
+
consola28.start("Issuing nest access token\u2026");
|
|
4184
4644
|
const { token, expiresIn } = await issueAgentToken({
|
|
4185
4645
|
idp,
|
|
4186
4646
|
agentEmail: registration.email,
|
|
@@ -4191,16 +4651,16 @@ var enrollNestCommand = defineCommand32({
|
|
|
4191
4651
|
accessToken: token,
|
|
4192
4652
|
email: registration.email,
|
|
4193
4653
|
expiresAt: Math.floor(Date.now() / 1e3) + expiresIn,
|
|
4194
|
-
keyPath:
|
|
4654
|
+
keyPath: join11(sshDir, "id_ed25519"),
|
|
4195
4655
|
ownerEmail: ownerAuth.email
|
|
4196
4656
|
});
|
|
4197
4657
|
writeFileSync6(authPath, authJson, { mode: 384 });
|
|
4198
4658
|
chmodSync(configDir, 448);
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4659
|
+
consola28.success(`Nest enrolled \u2014 auth.json at ${authPath}`);
|
|
4660
|
+
consola28.info("");
|
|
4661
|
+
consola28.info("Next: configure the YOLO-policy so the nest can spawn/destroy without prompts:");
|
|
4662
|
+
consola28.info("");
|
|
4663
|
+
consola28.info(" apes nest authorize");
|
|
4204
4664
|
}
|
|
4205
4665
|
});
|
|
4206
4666
|
|
|
@@ -4247,7 +4707,7 @@ var DEFAULT_ALLOW_PATTERNS = [
|
|
|
4247
4707
|
"pm2 delete openape-bridge-*",
|
|
4248
4708
|
"pm2 jlist"
|
|
4249
4709
|
];
|
|
4250
|
-
var authorizeNestCommand =
|
|
4710
|
+
var authorizeNestCommand = defineCommand34({
|
|
4251
4711
|
meta: {
|
|
4252
4712
|
name: "authorize",
|
|
4253
4713
|
description: "Set the YOLO-policy that lets the local nest spawn/destroy without per-call DDISA prompts (wraps `apes yolo set`)"
|
|
@@ -4263,14 +4723,14 @@ var authorizeNestCommand = defineCommand33({
|
|
|
4263
4723
|
}
|
|
4264
4724
|
},
|
|
4265
4725
|
async run({ args }) {
|
|
4266
|
-
const nestAuthPath =
|
|
4267
|
-
if (!
|
|
4726
|
+
const nestAuthPath = join12(NEST_DATA_DIR, ".config", "apes", "auth.json");
|
|
4727
|
+
if (!existsSync14(nestAuthPath)) {
|
|
4268
4728
|
throw new CliError("Nest not enrolled. Run `apes nest enroll` first.");
|
|
4269
4729
|
}
|
|
4270
|
-
const nestAuth = JSON.parse(
|
|
4730
|
+
const nestAuth = JSON.parse(readFileSync12(nestAuthPath, "utf8"));
|
|
4271
4731
|
if (!nestAuth.email) throw new CliError(`${nestAuthPath} has no email`);
|
|
4272
4732
|
const allow = args.allow ?? DEFAULT_ALLOW_PATTERNS.join(",");
|
|
4273
|
-
|
|
4733
|
+
consola29.info(`Configuring YOLO-policy on ${nestAuth.email} via \`apes yolo set\`\u2026`);
|
|
4274
4734
|
const cmdArgs = [
|
|
4275
4735
|
"yolo",
|
|
4276
4736
|
"set",
|
|
@@ -4288,16 +4748,16 @@ var authorizeNestCommand = defineCommand33({
|
|
|
4288
4748
|
} catch (err) {
|
|
4289
4749
|
throw new CliError(err instanceof Error ? err.message : String(err));
|
|
4290
4750
|
}
|
|
4291
|
-
|
|
4292
|
-
|
|
4751
|
+
consola29.success("Nest-driven agent lifecycle is now zero-prompt.");
|
|
4752
|
+
consola29.info("Test: apes agents spawn <name> via the nest API \u2192 no DDISA prompt.");
|
|
4293
4753
|
}
|
|
4294
4754
|
});
|
|
4295
4755
|
|
|
4296
4756
|
// src/commands/nest/destroy.ts
|
|
4297
4757
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
4298
|
-
import { defineCommand as
|
|
4299
|
-
import
|
|
4300
|
-
var destroyNestCommand =
|
|
4758
|
+
import { defineCommand as defineCommand35 } from "citty";
|
|
4759
|
+
import consola30 from "consola";
|
|
4760
|
+
var destroyNestCommand = defineCommand35({
|
|
4301
4761
|
meta: {
|
|
4302
4762
|
name: "destroy",
|
|
4303
4763
|
description: "Destroy a local agent. Wraps `apes run --as root -- apes agents destroy <name>`; the Nest watches its registry and pm2-deletes the bridge automatically."
|
|
@@ -4309,7 +4769,7 @@ var destroyNestCommand = defineCommand34({
|
|
|
4309
4769
|
const name = String(args.name);
|
|
4310
4770
|
try {
|
|
4311
4771
|
execFileSync11("apes", ["run", "--as", "root", "--wait", "--", "apes", "agents", "destroy", name, "--force"], { stdio: "inherit" });
|
|
4312
|
-
|
|
4772
|
+
consola30.success(`Nest will tear down ${name}'s pm2 process on its next reconcile (\u22642s).`);
|
|
4313
4773
|
} catch (err) {
|
|
4314
4774
|
const status = err.status ?? 1;
|
|
4315
4775
|
throw new CliExit(status);
|
|
@@ -4319,11 +4779,11 @@ var destroyNestCommand = defineCommand34({
|
|
|
4319
4779
|
|
|
4320
4780
|
// src/commands/nest/install.ts
|
|
4321
4781
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
4322
|
-
import { existsSync as
|
|
4323
|
-
import { homedir as
|
|
4324
|
-
import { dirname as dirname3, join as
|
|
4325
|
-
import { defineCommand as
|
|
4326
|
-
import
|
|
4782
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
|
|
4783
|
+
import { homedir as homedir13, userInfo as userInfo2 } from "os";
|
|
4784
|
+
import { dirname as dirname3, join as join13 } from "path";
|
|
4785
|
+
import { defineCommand as defineCommand36 } from "citty";
|
|
4786
|
+
import consola31 from "consola";
|
|
4327
4787
|
|
|
4328
4788
|
// src/commands/nest/apes-agents-adapter.ts
|
|
4329
4789
|
var APES_AGENTS_ADAPTER_TOML = `schema = "openape-shapes/v1"
|
|
@@ -4390,13 +4850,13 @@ resource_chain = ["agents:name={name}", "allowlist:email={peer_email}"]
|
|
|
4390
4850
|
// src/commands/nest/install.ts
|
|
4391
4851
|
var PLIST_LABEL = "ai.openape.nest";
|
|
4392
4852
|
function plistPath() {
|
|
4393
|
-
return
|
|
4853
|
+
return join13(homedir13(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
4394
4854
|
}
|
|
4395
4855
|
function escape2(s) {
|
|
4396
4856
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4397
4857
|
}
|
|
4398
4858
|
function buildPlist(args) {
|
|
4399
|
-
const logsDir =
|
|
4859
|
+
const logsDir = join13(args.userHome, "Library", "Logs");
|
|
4400
4860
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4401
4861
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4402
4862
|
<plist version="1.0">
|
|
@@ -4431,25 +4891,25 @@ function buildPlist(args) {
|
|
|
4431
4891
|
`;
|
|
4432
4892
|
}
|
|
4433
4893
|
function installAdapter2() {
|
|
4434
|
-
const target =
|
|
4894
|
+
const target = join13(homedir13(), ".openape", "shapes", "adapters", "apes-agents.toml");
|
|
4435
4895
|
mkdirSync5(dirname3(target), { recursive: true });
|
|
4436
4896
|
let existing = "";
|
|
4437
4897
|
try {
|
|
4438
|
-
existing =
|
|
4898
|
+
existing = readFileSync13(target, "utf8");
|
|
4439
4899
|
} catch {
|
|
4440
4900
|
}
|
|
4441
4901
|
if (existing === APES_AGENTS_ADAPTER_TOML) return false;
|
|
4442
4902
|
writeFileSync7(target, APES_AGENTS_ADAPTER_TOML, { mode: 420 });
|
|
4443
|
-
|
|
4903
|
+
consola31.success(`Wrote shapes adapter ${target}`);
|
|
4444
4904
|
return true;
|
|
4445
4905
|
}
|
|
4446
4906
|
function writeBridgeModelDefault(model) {
|
|
4447
|
-
for (const envDir of [
|
|
4448
|
-
const envFile =
|
|
4907
|
+
for (const envDir of [join13(homedir13(), "litellm"), join13(NEST_DATA_DIR, "litellm")]) {
|
|
4908
|
+
const envFile = join13(envDir, ".env");
|
|
4449
4909
|
mkdirSync5(envDir, { recursive: true });
|
|
4450
4910
|
let lines = [];
|
|
4451
|
-
if (
|
|
4452
|
-
lines =
|
|
4911
|
+
if (existsSync15(envFile)) {
|
|
4912
|
+
lines = readFileSync13(envFile, "utf8").split("\n").filter((l) => !l.startsWith("APE_CHAT_BRIDGE_MODEL="));
|
|
4453
4913
|
}
|
|
4454
4914
|
lines.push(`APE_CHAT_BRIDGE_MODEL=${model}`);
|
|
4455
4915
|
while (lines.length > 0 && lines.at(-1).trim() === "") lines.pop();
|
|
@@ -4459,17 +4919,17 @@ function writeBridgeModelDefault(model) {
|
|
|
4459
4919
|
}
|
|
4460
4920
|
function findBinary(name) {
|
|
4461
4921
|
for (const dir of [
|
|
4462
|
-
|
|
4922
|
+
join13(homedir13(), ".bun", "bin"),
|
|
4463
4923
|
"/opt/homebrew/bin",
|
|
4464
4924
|
"/usr/local/bin",
|
|
4465
4925
|
"/usr/bin"
|
|
4466
4926
|
]) {
|
|
4467
|
-
const p =
|
|
4468
|
-
if (
|
|
4927
|
+
const p = join13(dir, name);
|
|
4928
|
+
if (existsSync15(p)) return p;
|
|
4469
4929
|
}
|
|
4470
4930
|
throw new Error(`could not locate ${name} on PATH; install it first`);
|
|
4471
4931
|
}
|
|
4472
|
-
var installNestCommand =
|
|
4932
|
+
var installNestCommand = defineCommand36({
|
|
4473
4933
|
meta: {
|
|
4474
4934
|
name: "install",
|
|
4475
4935
|
description: "Install + start the local nest-daemon (idempotent \u2014 re-running just restarts)"
|
|
@@ -4485,35 +4945,35 @@ var installNestCommand = defineCommand35({
|
|
|
4485
4945
|
}
|
|
4486
4946
|
},
|
|
4487
4947
|
async run({ args }) {
|
|
4488
|
-
const homeDir =
|
|
4948
|
+
const homeDir = homedir13();
|
|
4489
4949
|
const port = Number(args.port ?? 9091);
|
|
4490
4950
|
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
4491
4951
|
throw new Error(`invalid port ${port}`);
|
|
4492
4952
|
}
|
|
4493
4953
|
const nestBin = findBinary("openape-nest");
|
|
4494
4954
|
const apesBin = findBinary("apes");
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4955
|
+
consola31.info(`Installing nest at ${plistPath()}`);
|
|
4956
|
+
consola31.info(` nest binary: ${nestBin}`);
|
|
4957
|
+
consola31.info(` apes binary: ${apesBin}`);
|
|
4958
|
+
consola31.info(` HTTP port: ${port}`);
|
|
4499
4959
|
if (typeof args["bridge-model"] === "string" && args["bridge-model"]) {
|
|
4500
4960
|
writeBridgeModelDefault(args["bridge-model"]);
|
|
4501
|
-
|
|
4961
|
+
consola31.success(`Default bridge model set to ${args["bridge-model"]} (in ~/litellm/.env)`);
|
|
4502
4962
|
}
|
|
4503
4963
|
installAdapter2();
|
|
4504
|
-
mkdirSync5(
|
|
4964
|
+
mkdirSync5(join13(homeDir, "Library", "LaunchAgents"), { recursive: true });
|
|
4505
4965
|
mkdirSync5(NEST_DATA_DIR, { recursive: true });
|
|
4506
4966
|
const desired = buildPlist({ nestBin, apesBin, userHome: homeDir, nestHome: NEST_DATA_DIR, port });
|
|
4507
4967
|
let existing = "";
|
|
4508
4968
|
try {
|
|
4509
|
-
existing =
|
|
4969
|
+
existing = readFileSync13(plistPath(), "utf8");
|
|
4510
4970
|
} catch {
|
|
4511
4971
|
}
|
|
4512
4972
|
if (existing !== desired) {
|
|
4513
4973
|
writeFileSync7(plistPath(), desired, { mode: 420 });
|
|
4514
|
-
|
|
4974
|
+
consola31.success("Wrote launchd plist");
|
|
4515
4975
|
} else {
|
|
4516
|
-
|
|
4976
|
+
consola31.info("plist already up to date");
|
|
4517
4977
|
}
|
|
4518
4978
|
const uid = userInfo2().uid;
|
|
4519
4979
|
try {
|
|
@@ -4521,21 +4981,21 @@ var installNestCommand = defineCommand35({
|
|
|
4521
4981
|
} catch {
|
|
4522
4982
|
}
|
|
4523
4983
|
execFileSync12("/bin/launchctl", ["bootstrap", `gui/${uid}`, plistPath()], { stdio: "inherit" });
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4984
|
+
consola31.success(`Nest daemon bootstrapped \u2014 http://127.0.0.1:${port}`);
|
|
4985
|
+
consola31.info("");
|
|
4986
|
+
consola31.info("Next steps for zero-prompt spawn \u2014 both one-time:");
|
|
4987
|
+
consola31.info("");
|
|
4988
|
+
consola31.info(" 1. apes nest enroll # register nest as DDISA agent (creates own auth.json)");
|
|
4989
|
+
consola31.info(" 2. apes nest authorize # set YOLO-policy on the nest agent");
|
|
4990
|
+
consola31.info("");
|
|
4991
|
+
consola31.info("After that, every `POST http://127.0.0.1:9091/agents` runs without DDISA prompts.");
|
|
4532
4992
|
}
|
|
4533
4993
|
});
|
|
4534
4994
|
|
|
4535
4995
|
// src/commands/nest/list.ts
|
|
4536
|
-
import { defineCommand as
|
|
4537
|
-
import
|
|
4538
|
-
var listNestCommand =
|
|
4996
|
+
import { defineCommand as defineCommand37 } from "citty";
|
|
4997
|
+
import consola32 from "consola";
|
|
4998
|
+
var listNestCommand = defineCommand37({
|
|
4539
4999
|
meta: {
|
|
4540
5000
|
name: "list",
|
|
4541
5001
|
description: "List agents registered with the local nest. Reads /var/openape/nest/agents.json directly."
|
|
@@ -4550,22 +5010,22 @@ var listNestCommand = defineCommand36({
|
|
|
4550
5010
|
return;
|
|
4551
5011
|
}
|
|
4552
5012
|
if (reg.agents.length === 0) {
|
|
4553
|
-
|
|
5013
|
+
consola32.info("(no agents registered with this nest)");
|
|
4554
5014
|
return;
|
|
4555
5015
|
}
|
|
4556
|
-
|
|
5016
|
+
consola32.info(`${reg.agents.length} agent(s) registered with this nest:`);
|
|
4557
5017
|
for (const a of reg.agents) {
|
|
4558
5018
|
const bridge = a.bridge ? " bridge=on" : "";
|
|
4559
|
-
|
|
5019
|
+
consola32.info(` ${a.name.padEnd(16)} uid=${String(a.uid).padEnd(5)} home=${a.home}${bridge}`);
|
|
4560
5020
|
}
|
|
4561
5021
|
}
|
|
4562
5022
|
});
|
|
4563
5023
|
|
|
4564
5024
|
// src/commands/nest/spawn.ts
|
|
4565
5025
|
import { execFileSync as execFileSync13 } from "child_process";
|
|
4566
|
-
import { defineCommand as
|
|
4567
|
-
import
|
|
4568
|
-
var spawnNestCommand =
|
|
5026
|
+
import { defineCommand as defineCommand38 } from "citty";
|
|
5027
|
+
import consola33 from "consola";
|
|
5028
|
+
var spawnNestCommand = defineCommand38({
|
|
4569
5029
|
meta: {
|
|
4570
5030
|
name: "spawn",
|
|
4571
5031
|
description: "Spawn a new agent locally. Wraps `apes run --as root -- apes agents spawn <name>`; the Nest watches its registry and starts the bridge in pm2 automatically."
|
|
@@ -4596,7 +5056,7 @@ var spawnNestCommand = defineCommand37({
|
|
|
4596
5056
|
if (typeof args["bridge-model"] === "string") apesArgs.push("--bridge-model", args["bridge-model"]);
|
|
4597
5057
|
try {
|
|
4598
5058
|
execFileSync13("apes", apesArgs, { stdio: "inherit" });
|
|
4599
|
-
|
|
5059
|
+
consola33.success(`Nest will pick up ${name} on its next reconcile (\u22642s).`);
|
|
4600
5060
|
} catch (err) {
|
|
4601
5061
|
const status = err.status ?? 1;
|
|
4602
5062
|
throw new CliExit(status);
|
|
@@ -4606,36 +5066,36 @@ var spawnNestCommand = defineCommand37({
|
|
|
4606
5066
|
|
|
4607
5067
|
// src/commands/nest/uninstall.ts
|
|
4608
5068
|
import { execFileSync as execFileSync14 } from "child_process";
|
|
4609
|
-
import { existsSync as
|
|
4610
|
-
import { homedir as
|
|
4611
|
-
import { join as
|
|
4612
|
-
import { defineCommand as
|
|
4613
|
-
import
|
|
5069
|
+
import { existsSync as existsSync16, unlinkSync } from "fs";
|
|
5070
|
+
import { homedir as homedir14, userInfo as userInfo3 } from "os";
|
|
5071
|
+
import { join as join14 } from "path";
|
|
5072
|
+
import { defineCommand as defineCommand39 } from "citty";
|
|
5073
|
+
import consola34 from "consola";
|
|
4614
5074
|
var PLIST_LABEL2 = "ai.openape.nest";
|
|
4615
|
-
var uninstallNestCommand =
|
|
5075
|
+
var uninstallNestCommand = defineCommand39({
|
|
4616
5076
|
meta: {
|
|
4617
5077
|
name: "uninstall",
|
|
4618
5078
|
description: "Stop + remove the local nest-daemon (registry + agents preserved)"
|
|
4619
5079
|
},
|
|
4620
5080
|
async run() {
|
|
4621
5081
|
const uid = userInfo3().uid;
|
|
4622
|
-
const path2 =
|
|
5082
|
+
const path2 = join14(homedir14(), "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
|
|
4623
5083
|
try {
|
|
4624
5084
|
execFileSync14("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL2}`], { stdio: "ignore" });
|
|
4625
|
-
|
|
5085
|
+
consola34.success("Nest daemon stopped");
|
|
4626
5086
|
} catch {
|
|
4627
|
-
|
|
5087
|
+
consola34.info("Nest daemon was not loaded");
|
|
4628
5088
|
}
|
|
4629
|
-
if (
|
|
5089
|
+
if (existsSync16(path2)) {
|
|
4630
5090
|
unlinkSync(path2);
|
|
4631
|
-
|
|
5091
|
+
consola34.success(`Removed ${path2}`);
|
|
4632
5092
|
}
|
|
4633
|
-
|
|
5093
|
+
consola34.info("Registry at ~/.openape/nest/agents.json kept \u2014 re-run `apes nest install` to resume supervision.");
|
|
4634
5094
|
}
|
|
4635
5095
|
});
|
|
4636
5096
|
|
|
4637
5097
|
// src/commands/nest/index.ts
|
|
4638
|
-
var nestCommand =
|
|
5098
|
+
var nestCommand = defineCommand40({
|
|
4639
5099
|
meta: {
|
|
4640
5100
|
name: "nest",
|
|
4641
5101
|
description: "Manage the local Nest control-plane daemon. One-time setup: `install` \u2192 `enroll` \u2192 `authorize`. Day-to-day: `list` / `spawn` / `destroy`. As of Phase D the Nest is a long-running CLIENT \u2014 commands talk to it via filesystem intent files in $NEST_HOME/intents (mode 770, group _openape_nest) instead of HTTP."
|
|
@@ -4652,12 +5112,12 @@ var nestCommand = defineCommand39({
|
|
|
4652
5112
|
});
|
|
4653
5113
|
|
|
4654
5114
|
// src/commands/yolo/index.ts
|
|
4655
|
-
import { defineCommand as
|
|
5115
|
+
import { defineCommand as defineCommand44 } from "citty";
|
|
4656
5116
|
|
|
4657
5117
|
// src/commands/yolo/clear.ts
|
|
4658
|
-
import { defineCommand as
|
|
4659
|
-
import
|
|
4660
|
-
var yoloClearCommand =
|
|
5118
|
+
import { defineCommand as defineCommand41 } from "citty";
|
|
5119
|
+
import consola35 from "consola";
|
|
5120
|
+
var yoloClearCommand = defineCommand41({
|
|
4661
5121
|
meta: {
|
|
4662
5122
|
name: "clear",
|
|
4663
5123
|
description: "Remove the YOLO-policy from a DDISA agent (subsequent grants need human approval)"
|
|
@@ -4686,15 +5146,15 @@ var yoloClearCommand = defineCommand40({
|
|
|
4686
5146
|
const text = await res.text().catch(() => "");
|
|
4687
5147
|
throw new CliError(`DELETE /yolo-policy failed (${res.status}): ${text}`);
|
|
4688
5148
|
}
|
|
4689
|
-
|
|
5149
|
+
consola35.success(`YOLO-policy cleared on ${email}`);
|
|
4690
5150
|
}
|
|
4691
5151
|
});
|
|
4692
5152
|
|
|
4693
5153
|
// src/commands/yolo/set.ts
|
|
4694
|
-
import { defineCommand as
|
|
4695
|
-
import
|
|
5154
|
+
import { defineCommand as defineCommand42 } from "citty";
|
|
5155
|
+
import consola36 from "consola";
|
|
4696
5156
|
var VALID_MODES = ["allow-list", "deny-list"];
|
|
4697
|
-
var yoloSetCommand =
|
|
5157
|
+
var yoloSetCommand = defineCommand42({
|
|
4698
5158
|
meta: {
|
|
4699
5159
|
name: "set",
|
|
4700
5160
|
description: "Write a YOLO-policy on a DDISA agent you own"
|
|
@@ -4742,12 +5202,12 @@ var yoloSetCommand = defineCommand41({
|
|
|
4742
5202
|
const denyPatterns = parseList(args.deny);
|
|
4743
5203
|
const denyRiskThreshold = args["deny-risk"] ?? null;
|
|
4744
5204
|
const expiresAt = parseExpiresIn(args["expires-in"]);
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
if (allowPatterns.length)
|
|
4748
|
-
if (denyPatterns.length)
|
|
4749
|
-
if (denyRiskThreshold)
|
|
4750
|
-
if (expiresAt)
|
|
5205
|
+
consola36.info(`Setting YOLO-policy on ${email}`);
|
|
5206
|
+
consola36.info(` mode: ${mode}`);
|
|
5207
|
+
if (allowPatterns.length) consola36.info(` allow_patterns: ${allowPatterns.join(", ")}`);
|
|
5208
|
+
if (denyPatterns.length) consola36.info(` deny_patterns: ${denyPatterns.join(", ")}`);
|
|
5209
|
+
if (denyRiskThreshold) consola36.info(` deny_risk: ${denyRiskThreshold}`);
|
|
5210
|
+
if (expiresAt) consola36.info(` expires_at: ${new Date(expiresAt * 1e3).toISOString()}`);
|
|
4751
5211
|
const url = `${idp}/api/users/${encodeURIComponent(email)}/yolo-policy`;
|
|
4752
5212
|
const res = await fetch(url, {
|
|
4753
5213
|
method: "PUT",
|
|
@@ -4767,7 +5227,7 @@ var yoloSetCommand = defineCommand41({
|
|
|
4767
5227
|
const text = await res.text().catch(() => "");
|
|
4768
5228
|
throw new CliError(`PUT /yolo-policy failed (${res.status}): ${text}`);
|
|
4769
5229
|
}
|
|
4770
|
-
|
|
5230
|
+
consola36.success(`YOLO-policy applied to ${email}`);
|
|
4771
5231
|
}
|
|
4772
5232
|
});
|
|
4773
5233
|
function parseList(s) {
|
|
@@ -4785,9 +5245,9 @@ function parseExpiresIn(s) {
|
|
|
4785
5245
|
}
|
|
4786
5246
|
|
|
4787
5247
|
// src/commands/yolo/show.ts
|
|
4788
|
-
import { defineCommand as
|
|
4789
|
-
import
|
|
4790
|
-
var yoloShowCommand =
|
|
5248
|
+
import { defineCommand as defineCommand43 } from "citty";
|
|
5249
|
+
import consola37 from "consola";
|
|
5250
|
+
var yoloShowCommand = defineCommand43({
|
|
4791
5251
|
meta: {
|
|
4792
5252
|
name: "show",
|
|
4793
5253
|
description: "Print the YOLO-policy currently set on a DDISA agent"
|
|
@@ -4824,17 +5284,17 @@ var yoloShowCommand = defineCommand42({
|
|
|
4824
5284
|
console.log(JSON.stringify(policy, null, 2));
|
|
4825
5285
|
return;
|
|
4826
5286
|
}
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
5287
|
+
consola37.info(`YOLO-policy for ${email}`);
|
|
5288
|
+
consola37.info(` mode: ${policy.mode}`);
|
|
5289
|
+
consola37.info(` allow_patterns: ${policy.allowPatterns.length ? policy.allowPatterns.join(", ") : "(none)"}`);
|
|
5290
|
+
consola37.info(` deny_patterns: ${policy.denyPatterns.length ? policy.denyPatterns.join(", ") : "(none)"}`);
|
|
5291
|
+
consola37.info(` deny_risk: ${policy.denyRiskThreshold ?? "(none)"}`);
|
|
5292
|
+
consola37.info(` expires_at: ${policy.expiresAt ? new Date(policy.expiresAt * 1e3).toISOString() : "(never)"}`);
|
|
4833
5293
|
}
|
|
4834
5294
|
});
|
|
4835
5295
|
|
|
4836
5296
|
// src/commands/yolo/index.ts
|
|
4837
|
-
var yoloCommand =
|
|
5297
|
+
var yoloCommand = defineCommand44({
|
|
4838
5298
|
meta: {
|
|
4839
5299
|
name: "yolo",
|
|
4840
5300
|
description: "Manage YOLO-policies on DDISA agents you own \u2014 auto-approve grant patterns at the IdP layer (allow-list) or block dangerous ones outright (deny-list)."
|
|
@@ -4847,15 +5307,15 @@ var yoloCommand = defineCommand43({
|
|
|
4847
5307
|
});
|
|
4848
5308
|
|
|
4849
5309
|
// src/commands/adapter/index.ts
|
|
4850
|
-
import { defineCommand as
|
|
4851
|
-
import
|
|
4852
|
-
var adapterCommand =
|
|
5310
|
+
import { defineCommand as defineCommand45 } from "citty";
|
|
5311
|
+
import consola38 from "consola";
|
|
5312
|
+
var adapterCommand = defineCommand45({
|
|
4853
5313
|
meta: {
|
|
4854
5314
|
name: "adapter",
|
|
4855
5315
|
description: "Manage CLI adapters"
|
|
4856
5316
|
},
|
|
4857
5317
|
subCommands: {
|
|
4858
|
-
list:
|
|
5318
|
+
list: defineCommand45({
|
|
4859
5319
|
meta: {
|
|
4860
5320
|
name: "list",
|
|
4861
5321
|
description: "List available adapters"
|
|
@@ -4886,7 +5346,7 @@ var adapterCommand = defineCommand44({
|
|
|
4886
5346
|
`);
|
|
4887
5347
|
return;
|
|
4888
5348
|
}
|
|
4889
|
-
|
|
5349
|
+
consola38.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
|
|
4890
5350
|
for (const a of index2.adapters) {
|
|
4891
5351
|
const installed = isInstalled(a.id, false) ? " [installed]" : "";
|
|
4892
5352
|
console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
|
|
@@ -4908,7 +5368,7 @@ var adapterCommand = defineCommand44({
|
|
|
4908
5368
|
return;
|
|
4909
5369
|
}
|
|
4910
5370
|
if (local.length === 0) {
|
|
4911
|
-
|
|
5371
|
+
consola38.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
|
|
4912
5372
|
return;
|
|
4913
5373
|
}
|
|
4914
5374
|
for (const a of local) {
|
|
@@ -4916,7 +5376,7 @@ var adapterCommand = defineCommand44({
|
|
|
4916
5376
|
}
|
|
4917
5377
|
}
|
|
4918
5378
|
}),
|
|
4919
|
-
install:
|
|
5379
|
+
install: defineCommand45({
|
|
4920
5380
|
meta: {
|
|
4921
5381
|
name: "install",
|
|
4922
5382
|
description: "Install an adapter from the registry"
|
|
@@ -4945,24 +5405,24 @@ var adapterCommand = defineCommand44({
|
|
|
4945
5405
|
for (const id of ids) {
|
|
4946
5406
|
const entry = findAdapter(index, id);
|
|
4947
5407
|
if (!entry) {
|
|
4948
|
-
|
|
5408
|
+
consola38.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
|
|
4949
5409
|
continue;
|
|
4950
5410
|
}
|
|
4951
5411
|
const conflicts = findConflictingAdapters(entry.executable, id);
|
|
4952
5412
|
if (conflicts.length > 0) {
|
|
4953
5413
|
for (const c of conflicts) {
|
|
4954
|
-
|
|
4955
|
-
|
|
5414
|
+
consola38.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
|
|
5415
|
+
consola38.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
|
|
4956
5416
|
}
|
|
4957
5417
|
}
|
|
4958
5418
|
const result = await installAdapter(entry, { local });
|
|
4959
5419
|
const verb = result.updated ? "Updated" : "Installed";
|
|
4960
|
-
|
|
4961
|
-
|
|
5420
|
+
consola38.success(`${verb} ${result.id} \u2192 ${result.path}`);
|
|
5421
|
+
consola38.info(`Digest: ${result.digest}`);
|
|
4962
5422
|
}
|
|
4963
5423
|
}
|
|
4964
5424
|
}),
|
|
4965
|
-
remove:
|
|
5425
|
+
remove: defineCommand45({
|
|
4966
5426
|
meta: {
|
|
4967
5427
|
name: "remove",
|
|
4968
5428
|
description: "Remove an installed adapter"
|
|
@@ -4985,9 +5445,9 @@ var adapterCommand = defineCommand44({
|
|
|
4985
5445
|
let failed = false;
|
|
4986
5446
|
for (const id of ids) {
|
|
4987
5447
|
if (removeAdapter(id, local)) {
|
|
4988
|
-
|
|
5448
|
+
consola38.success(`Removed adapter: ${id}`);
|
|
4989
5449
|
} else {
|
|
4990
|
-
|
|
5450
|
+
consola38.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
|
|
4991
5451
|
failed = true;
|
|
4992
5452
|
}
|
|
4993
5453
|
}
|
|
@@ -4995,7 +5455,7 @@ var adapterCommand = defineCommand44({
|
|
|
4995
5455
|
throw new CliError("Some adapters could not be removed");
|
|
4996
5456
|
}
|
|
4997
5457
|
}),
|
|
4998
|
-
info:
|
|
5458
|
+
info: defineCommand45({
|
|
4999
5459
|
meta: {
|
|
5000
5460
|
name: "info",
|
|
5001
5461
|
description: "Show detailed adapter information"
|
|
@@ -5037,7 +5497,7 @@ var adapterCommand = defineCommand44({
|
|
|
5037
5497
|
}
|
|
5038
5498
|
}
|
|
5039
5499
|
}),
|
|
5040
|
-
search:
|
|
5500
|
+
search: defineCommand45({
|
|
5041
5501
|
meta: {
|
|
5042
5502
|
name: "search",
|
|
5043
5503
|
description: "Search adapters in the registry"
|
|
@@ -5069,7 +5529,7 @@ var adapterCommand = defineCommand44({
|
|
|
5069
5529
|
return;
|
|
5070
5530
|
}
|
|
5071
5531
|
if (results.length === 0) {
|
|
5072
|
-
|
|
5532
|
+
consola38.info(`No adapters matching "${query}"`);
|
|
5073
5533
|
return;
|
|
5074
5534
|
}
|
|
5075
5535
|
for (const a of results) {
|
|
@@ -5078,7 +5538,7 @@ var adapterCommand = defineCommand44({
|
|
|
5078
5538
|
}
|
|
5079
5539
|
}
|
|
5080
5540
|
}),
|
|
5081
|
-
update:
|
|
5541
|
+
update: defineCommand45({
|
|
5082
5542
|
meta: {
|
|
5083
5543
|
name: "update",
|
|
5084
5544
|
description: "Update installed adapters"
|
|
@@ -5104,33 +5564,33 @@ var adapterCommand = defineCommand44({
|
|
|
5104
5564
|
const targetId = args.id ? String(args.id) : void 0;
|
|
5105
5565
|
const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
|
|
5106
5566
|
if (targets.length === 0) {
|
|
5107
|
-
|
|
5567
|
+
consola38.info("No adapters installed to update.");
|
|
5108
5568
|
return;
|
|
5109
5569
|
}
|
|
5110
5570
|
for (const id of targets) {
|
|
5111
5571
|
const entry = findAdapter(index, id);
|
|
5112
5572
|
if (!entry) {
|
|
5113
|
-
|
|
5573
|
+
consola38.warn(`${id}: not found in registry, skipping`);
|
|
5114
5574
|
continue;
|
|
5115
5575
|
}
|
|
5116
5576
|
const localDigest = getInstalledDigest(id, false);
|
|
5117
5577
|
if (localDigest === entry.digest) {
|
|
5118
|
-
|
|
5578
|
+
consola38.info(`${id}: already up to date`);
|
|
5119
5579
|
continue;
|
|
5120
5580
|
}
|
|
5121
5581
|
if (localDigest && !args.yes) {
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5582
|
+
consola38.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
|
|
5583
|
+
consola38.info(` Old: ${localDigest}`);
|
|
5584
|
+
consola38.info(` New: ${entry.digest}`);
|
|
5585
|
+
consola38.info(" Use --yes to confirm");
|
|
5126
5586
|
continue;
|
|
5127
5587
|
}
|
|
5128
5588
|
const result = await installAdapter(entry);
|
|
5129
|
-
|
|
5589
|
+
consola38.success(`Updated ${result.id} \u2192 ${result.path}`);
|
|
5130
5590
|
}
|
|
5131
5591
|
}
|
|
5132
5592
|
}),
|
|
5133
|
-
verify:
|
|
5593
|
+
verify: defineCommand45({
|
|
5134
5594
|
meta: {
|
|
5135
5595
|
name: "verify",
|
|
5136
5596
|
description: "Verify installed adapter against registry digest"
|
|
@@ -5163,7 +5623,7 @@ var adapterCommand = defineCommand44({
|
|
|
5163
5623
|
if (!localDigest)
|
|
5164
5624
|
throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
|
|
5165
5625
|
if (localDigest === entry.digest) {
|
|
5166
|
-
|
|
5626
|
+
consola38.success(`${id}: digest matches registry`);
|
|
5167
5627
|
} else {
|
|
5168
5628
|
console.log(` Local: ${localDigest}`);
|
|
5169
5629
|
console.log(` Registry: ${entry.digest}`);
|
|
@@ -5178,8 +5638,8 @@ var adapterCommand = defineCommand44({
|
|
|
5178
5638
|
import { execFileSync as execFileSync15 } from "child_process";
|
|
5179
5639
|
import { hostname as hostname5 } from "os";
|
|
5180
5640
|
import { basename } from "path";
|
|
5181
|
-
import { defineCommand as
|
|
5182
|
-
import
|
|
5641
|
+
import { defineCommand as defineCommand46 } from "citty";
|
|
5642
|
+
import consola39 from "consola";
|
|
5183
5643
|
function resolveRunAsTarget(runAs) {
|
|
5184
5644
|
if (!runAs) return runAs;
|
|
5185
5645
|
if (!isDarwin()) return runAs;
|
|
@@ -5225,7 +5685,7 @@ function printPendingGrantInfo(grant, idp) {
|
|
|
5225
5685
|
const statusCmd = `apes grants status ${grant.id}`;
|
|
5226
5686
|
const executeCmd = `apes grants run ${grant.id}`;
|
|
5227
5687
|
if (mode === "human") {
|
|
5228
|
-
|
|
5688
|
+
consola39.success(`Grant ${grant.id} created \u2014 awaiting your approval`);
|
|
5229
5689
|
console.log(` Approve in browser: ${approveUrl}`);
|
|
5230
5690
|
console.log(` Check status: ${statusCmd}`);
|
|
5231
5691
|
console.log(` Run after approval: ${executeCmd}`);
|
|
@@ -5235,7 +5695,7 @@ function printPendingGrantInfo(grant, idp) {
|
|
|
5235
5695
|
return;
|
|
5236
5696
|
}
|
|
5237
5697
|
const maxMin = getPollMaxMinutes();
|
|
5238
|
-
|
|
5698
|
+
consola39.success(`Grant ${grant.id} created (pending approval)`);
|
|
5239
5699
|
console.log(` Approve: ${approveUrl}`);
|
|
5240
5700
|
console.log(` Status: ${statusCmd} [--json]`);
|
|
5241
5701
|
console.log(` Execute: ${executeCmd} --wait`);
|
|
@@ -5257,7 +5717,7 @@ function printPendingGrantInfo(grant, idp) {
|
|
|
5257
5717
|
console.log(' Tip: Approve as "timed" or "always" in the browser to let this');
|
|
5258
5718
|
console.log(" grant be reused on subsequent invocations without re-approval.");
|
|
5259
5719
|
}
|
|
5260
|
-
var runCommand =
|
|
5720
|
+
var runCommand = defineCommand46({
|
|
5261
5721
|
meta: {
|
|
5262
5722
|
name: "run",
|
|
5263
5723
|
description: "Execute a grant-secured command"
|
|
@@ -5360,7 +5820,7 @@ async function runShellMode(command, args) {
|
|
|
5360
5820
|
}
|
|
5361
5821
|
} catch {
|
|
5362
5822
|
}
|
|
5363
|
-
|
|
5823
|
+
consola39.info(`Requesting ape-shell session grant on ${targetHost}`);
|
|
5364
5824
|
const grant = await apiFetch(grantsUrl, {
|
|
5365
5825
|
method: "POST",
|
|
5366
5826
|
body: {
|
|
@@ -5380,8 +5840,8 @@ async function runShellMode(command, args) {
|
|
|
5380
5840
|
host: targetHost
|
|
5381
5841
|
});
|
|
5382
5842
|
if (shouldWaitForGrant(args)) {
|
|
5383
|
-
|
|
5384
|
-
|
|
5843
|
+
consola39.info(`Grant requested: ${grant.id}`);
|
|
5844
|
+
consola39.info("Waiting for approval...");
|
|
5385
5845
|
const maxWait = 3e5;
|
|
5386
5846
|
const interval = 3e3;
|
|
5387
5847
|
const start = Date.now();
|
|
@@ -5412,13 +5872,13 @@ async function tryAdapterModeFromShell(command, idp, args) {
|
|
|
5412
5872
|
try {
|
|
5413
5873
|
resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv]);
|
|
5414
5874
|
} catch (err) {
|
|
5415
|
-
|
|
5875
|
+
consola39.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
|
|
5416
5876
|
return false;
|
|
5417
5877
|
}
|
|
5418
5878
|
try {
|
|
5419
5879
|
const existingGrantId = await findExistingGrant(resolved, idp);
|
|
5420
5880
|
if (existingGrantId) {
|
|
5421
|
-
|
|
5881
|
+
consola39.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
|
|
5422
5882
|
const token = await fetchGrantToken(idp, existingGrantId);
|
|
5423
5883
|
await verifyAndExecute(token, resolved, existingGrantId);
|
|
5424
5884
|
return true;
|
|
@@ -5426,7 +5886,7 @@ async function tryAdapterModeFromShell(command, idp, args) {
|
|
|
5426
5886
|
} catch {
|
|
5427
5887
|
}
|
|
5428
5888
|
const approval = args.approval ?? "once";
|
|
5429
|
-
|
|
5889
|
+
consola39.info(`Requesting grant for: ${resolved.detail.display}`);
|
|
5430
5890
|
const grant = await createShapesGrant(resolved, {
|
|
5431
5891
|
idp,
|
|
5432
5892
|
approval,
|
|
@@ -5434,8 +5894,8 @@ async function tryAdapterModeFromShell(command, idp, args) {
|
|
|
5434
5894
|
});
|
|
5435
5895
|
if (grant.similar_grants?.similar_grants?.length) {
|
|
5436
5896
|
const n = grant.similar_grants.similar_grants.length;
|
|
5437
|
-
|
|
5438
|
-
|
|
5897
|
+
consola39.info("");
|
|
5898
|
+
consola39.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
|
|
5439
5899
|
}
|
|
5440
5900
|
notifyGrantPending({
|
|
5441
5901
|
grantId: grant.id,
|
|
@@ -5445,8 +5905,8 @@ async function tryAdapterModeFromShell(command, idp, args) {
|
|
|
5445
5905
|
host: args.host || hostname5()
|
|
5446
5906
|
});
|
|
5447
5907
|
if (shouldWaitForGrant(args)) {
|
|
5448
|
-
|
|
5449
|
-
|
|
5908
|
+
consola39.info(`Grant requested: ${grant.id}`);
|
|
5909
|
+
consola39.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
|
|
5450
5910
|
const status = await waitForGrantStatus(idp, grant.id);
|
|
5451
5911
|
if (status !== "approved")
|
|
5452
5912
|
throw new CliError(`Grant ${status}`);
|
|
@@ -5520,7 +5980,7 @@ async function runAdapterMode(command, rawArgs, args) {
|
|
|
5520
5980
|
try {
|
|
5521
5981
|
const existingGrantId = await findExistingGrant(resolved, idp);
|
|
5522
5982
|
if (existingGrantId) {
|
|
5523
|
-
|
|
5983
|
+
consola39.info(`Reusing existing grant: ${existingGrantId}`);
|
|
5524
5984
|
const token = await fetchGrantToken(idp, existingGrantId);
|
|
5525
5985
|
await verifyAndExecute(token, resolved, existingGrantId);
|
|
5526
5986
|
return;
|
|
@@ -5534,17 +5994,17 @@ async function runAdapterMode(command, rawArgs, args) {
|
|
|
5534
5994
|
});
|
|
5535
5995
|
if (grant.similar_grants?.similar_grants?.length) {
|
|
5536
5996
|
const n = grant.similar_grants.similar_grants.length;
|
|
5537
|
-
|
|
5538
|
-
|
|
5997
|
+
consola39.info("");
|
|
5998
|
+
consola39.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
|
|
5539
5999
|
if (grant.similar_grants.widened_details?.length) {
|
|
5540
6000
|
const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
|
|
5541
|
-
|
|
6001
|
+
consola39.info(` Broader scope: ${wider}`);
|
|
5542
6002
|
}
|
|
5543
|
-
|
|
6003
|
+
consola39.info("");
|
|
5544
6004
|
}
|
|
5545
6005
|
if (shouldWaitForGrant(args)) {
|
|
5546
|
-
|
|
5547
|
-
|
|
6006
|
+
consola39.info(`Grant requested: ${grant.id}`);
|
|
6007
|
+
consola39.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
|
|
5548
6008
|
const status = await waitForGrantStatus(idp, grant.id);
|
|
5549
6009
|
if (status !== "approved")
|
|
5550
6010
|
throw new Error(`Grant ${status}`);
|
|
@@ -5577,7 +6037,7 @@ async function runAudienceMode(audience, action, args) {
|
|
|
5577
6037
|
const { authz_jwt: authz_jwt2 } = await apiFetch(`${grantsUrl}/${reusableId}/token`, { method: "POST" });
|
|
5578
6038
|
return executeWithGrantToken({ audience, command, args, token: authz_jwt2 });
|
|
5579
6039
|
}
|
|
5580
|
-
|
|
6040
|
+
consola39.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
|
|
5581
6041
|
const grant = await apiFetch(grantsUrl, {
|
|
5582
6042
|
method: "POST",
|
|
5583
6043
|
body: {
|
|
@@ -5594,9 +6054,9 @@ async function runAudienceMode(audience, action, args) {
|
|
|
5594
6054
|
printPendingGrantInfo(grant, idp);
|
|
5595
6055
|
throw new CliExit(getAsyncExitCode());
|
|
5596
6056
|
}
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
6057
|
+
consola39.success(`Grant requested: ${grant.id}`);
|
|
6058
|
+
consola39.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
|
|
6059
|
+
consola39.info("Waiting for approval...");
|
|
5600
6060
|
const maxWait = 15 * 60 * 1e3;
|
|
5601
6061
|
const interval = 3e3;
|
|
5602
6062
|
const start = Date.now();
|
|
@@ -5604,7 +6064,7 @@ async function runAudienceMode(audience, action, args) {
|
|
|
5604
6064
|
while (Date.now() - start < maxWait) {
|
|
5605
6065
|
const status = await apiFetch(`${grantsUrl}/${grant.id}`);
|
|
5606
6066
|
if (status.status === "approved") {
|
|
5607
|
-
|
|
6067
|
+
consola39.success("Grant approved!");
|
|
5608
6068
|
approved = true;
|
|
5609
6069
|
break;
|
|
5610
6070
|
}
|
|
@@ -5619,7 +6079,7 @@ async function runAudienceMode(audience, action, args) {
|
|
|
5619
6079
|
`Grant approval timed out after ${minutes} min (still pending). Check your DDISA inbox at ${idp}/grant-approval?grant_id=${grant.id} \u2014 if approved later, re-run the same \`apes run\` command and it will reuse the grant.`
|
|
5620
6080
|
);
|
|
5621
6081
|
}
|
|
5622
|
-
|
|
6082
|
+
consola39.info("Fetching grant token...");
|
|
5623
6083
|
const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
|
|
5624
6084
|
method: "POST"
|
|
5625
6085
|
});
|
|
@@ -5628,7 +6088,7 @@ async function runAudienceMode(audience, action, args) {
|
|
|
5628
6088
|
function executeWithGrantToken(opts) {
|
|
5629
6089
|
const { audience, command, args, token } = opts;
|
|
5630
6090
|
if (audience === "escapes") {
|
|
5631
|
-
|
|
6091
|
+
consola39.info(`Executing: ${command.join(" ")}`);
|
|
5632
6092
|
try {
|
|
5633
6093
|
const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
|
|
5634
6094
|
execFileSync15(args["escapes-path"] || "escapes", ["--grant", token, "--", ...command], {
|
|
@@ -5667,11 +6127,11 @@ async function findReusableAudienceGrant(opts) {
|
|
|
5667
6127
|
|
|
5668
6128
|
// src/commands/proxy.ts
|
|
5669
6129
|
import { spawn as spawn2 } from "child_process";
|
|
5670
|
-
import { existsSync as
|
|
5671
|
-
import { homedir as
|
|
5672
|
-
import { join as
|
|
5673
|
-
import { defineCommand as
|
|
5674
|
-
import
|
|
6130
|
+
import { existsSync as existsSync18 } from "fs";
|
|
6131
|
+
import { homedir as homedir15 } from "os";
|
|
6132
|
+
import { join as join17 } from "path";
|
|
6133
|
+
import { defineCommand as defineCommand47 } from "citty";
|
|
6134
|
+
import consola40 from "consola";
|
|
5675
6135
|
|
|
5676
6136
|
// src/proxy/config.ts
|
|
5677
6137
|
function buildDefaultProxyConfigToml(opts) {
|
|
@@ -5708,7 +6168,7 @@ import { spawn } from "child_process";
|
|
|
5708
6168
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
5709
6169
|
import { createRequire } from "module";
|
|
5710
6170
|
import { tmpdir as tmpdir3 } from "os";
|
|
5711
|
-
import { dirname as dirname4, join as
|
|
6171
|
+
import { dirname as dirname4, join as join15, resolve as resolve3 } from "path";
|
|
5712
6172
|
var require2 = createRequire(import.meta.url);
|
|
5713
6173
|
function findProxyBin() {
|
|
5714
6174
|
const pkgPath = require2.resolve("@openape/proxy/package.json");
|
|
@@ -5720,8 +6180,8 @@ function findProxyBin() {
|
|
|
5720
6180
|
return resolve3(dirname4(pkgPath), binRel);
|
|
5721
6181
|
}
|
|
5722
6182
|
async function startEphemeralProxy(configToml) {
|
|
5723
|
-
const tmpDir = mkdtempSync3(
|
|
5724
|
-
const configPath =
|
|
6183
|
+
const tmpDir = mkdtempSync3(join15(tmpdir3(), "openape-proxy-"));
|
|
6184
|
+
const configPath = join15(tmpDir, "config.toml");
|
|
5725
6185
|
writeFileSync8(configPath, configToml, { mode: 384 });
|
|
5726
6186
|
const binPath = findProxyBin();
|
|
5727
6187
|
const child = spawn(process.execPath, [binPath, "-c", configPath], {
|
|
@@ -5804,9 +6264,9 @@ function waitForListenLine(child) {
|
|
|
5804
6264
|
}
|
|
5805
6265
|
|
|
5806
6266
|
// src/proxy/trust-bundle.ts
|
|
5807
|
-
import { existsSync as
|
|
6267
|
+
import { existsSync as existsSync17, mkdtempSync as mkdtempSync4, readFileSync as readFileSync14, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
|
|
5808
6268
|
import { tmpdir as tmpdir4 } from "os";
|
|
5809
|
-
import { join as
|
|
6269
|
+
import { join as join16 } from "path";
|
|
5810
6270
|
var CANDIDATES = [
|
|
5811
6271
|
"/etc/ssl/cert.pem",
|
|
5812
6272
|
// macOS
|
|
@@ -5819,17 +6279,17 @@ var CANDIDATES = [
|
|
|
5819
6279
|
];
|
|
5820
6280
|
function detectSystemCaPath() {
|
|
5821
6281
|
for (const p of CANDIDATES) {
|
|
5822
|
-
if (
|
|
6282
|
+
if (existsSync17(p)) return p;
|
|
5823
6283
|
}
|
|
5824
6284
|
throw new Error(
|
|
5825
6285
|
`Could not locate a system CA bundle. Tried: ${CANDIDATES.join(", ")}. Set NODE_EXTRA_CA_CERTS yourself or pass --allow-no-system-ca.`
|
|
5826
6286
|
);
|
|
5827
6287
|
}
|
|
5828
6288
|
function buildTrustBundle(opts) {
|
|
5829
|
-
const dir = mkdtempSync4(
|
|
5830
|
-
const path2 =
|
|
5831
|
-
const sys =
|
|
5832
|
-
const local =
|
|
6289
|
+
const dir = mkdtempSync4(join16(tmpdir4(), "openape-trust-"));
|
|
6290
|
+
const path2 = join16(dir, "bundle.pem");
|
|
6291
|
+
const sys = readFileSync14(opts.systemCaPath, "utf-8");
|
|
6292
|
+
const local = readFileSync14(opts.localCaPath, "utf-8");
|
|
5833
6293
|
writeFileSync9(path2, `${sys.trimEnd()}
|
|
5834
6294
|
${local.trimEnd()}
|
|
5835
6295
|
`, { mode: 384 });
|
|
@@ -5859,10 +6319,10 @@ function resolveProxyConfigOptions() {
|
|
|
5859
6319
|
77
|
|
5860
6320
|
);
|
|
5861
6321
|
}
|
|
5862
|
-
|
|
6322
|
+
consola40.info(`[apes proxy] IdP-mediated mode \u2014 agent=${auth.email}, idp=${auth.idp}`);
|
|
5863
6323
|
return { agentEmail: auth.email, idpUrl: auth.idp, mediated: true };
|
|
5864
6324
|
}
|
|
5865
|
-
var proxyCommand =
|
|
6325
|
+
var proxyCommand = defineCommand47({
|
|
5866
6326
|
meta: {
|
|
5867
6327
|
name: "proxy",
|
|
5868
6328
|
description: "Run a command with HTTPS_PROXY routed through the OpenApe egress proxy."
|
|
@@ -5886,9 +6346,9 @@ var proxyCommand = defineCommand46({
|
|
|
5886
6346
|
let close = null;
|
|
5887
6347
|
if (reuseHostPort) {
|
|
5888
6348
|
proxyUrl = `http://${reuseHostPort}`;
|
|
5889
|
-
|
|
5890
|
-
const localCaPath =
|
|
5891
|
-
if (!
|
|
6349
|
+
consola40.info(`[apes proxy] using long-running daemon at ${proxyUrl}`);
|
|
6350
|
+
const localCaPath = join17(homedir15(), ".openape", "proxy", "ca.crt");
|
|
6351
|
+
if (!existsSync18(localCaPath)) {
|
|
5892
6352
|
throw new CliError(
|
|
5893
6353
|
`OPENAPE_PROXY is set but no local CA found at ${localCaPath}. Start the daemon (sudo -u <agent> apes proxy --global < secrets.toml) first.`
|
|
5894
6354
|
);
|
|
@@ -5897,15 +6357,15 @@ var proxyCommand = defineCommand46({
|
|
|
5897
6357
|
systemCaPath: detectSystemCaPath(),
|
|
5898
6358
|
localCaPath
|
|
5899
6359
|
});
|
|
5900
|
-
|
|
6360
|
+
consola40.debug(`[apes proxy] trust bundle: ${bundle.path}`);
|
|
5901
6361
|
} else if (reuseUrl) {
|
|
5902
6362
|
proxyUrl = reuseUrl;
|
|
5903
|
-
|
|
6363
|
+
consola40.info(`[apes proxy] reusing existing proxy at ${proxyUrl}`);
|
|
5904
6364
|
} else {
|
|
5905
6365
|
const ephemeral = await startEphemeralProxy(buildDefaultProxyConfigToml(resolveProxyConfigOptions()));
|
|
5906
6366
|
proxyUrl = ephemeral.url;
|
|
5907
6367
|
close = ephemeral.close;
|
|
5908
|
-
|
|
6368
|
+
consola40.info(`[apes proxy] started ephemeral proxy at ${proxyUrl}`);
|
|
5909
6369
|
}
|
|
5910
6370
|
const noProxy = process.env.NO_PROXY ?? process.env.no_proxy ?? "127.0.0.1,localhost";
|
|
5911
6371
|
const childEnv = {
|
|
@@ -5946,7 +6406,7 @@ var proxyCommand = defineCommand46({
|
|
|
5946
6406
|
else resolveExit(code ?? 0);
|
|
5947
6407
|
});
|
|
5948
6408
|
child.once("error", (err) => {
|
|
5949
|
-
|
|
6409
|
+
consola40.error(`[apes proxy] failed to spawn '${wrapped[0]}':`, err.message);
|
|
5950
6410
|
resolveExit(127);
|
|
5951
6411
|
});
|
|
5952
6412
|
});
|
|
@@ -5963,8 +6423,8 @@ function signalNumber(signal) {
|
|
|
5963
6423
|
}
|
|
5964
6424
|
|
|
5965
6425
|
// src/commands/explain.ts
|
|
5966
|
-
import { defineCommand as
|
|
5967
|
-
var explainCommand =
|
|
6426
|
+
import { defineCommand as defineCommand48 } from "citty";
|
|
6427
|
+
var explainCommand = defineCommand48({
|
|
5968
6428
|
meta: {
|
|
5969
6429
|
name: "explain",
|
|
5970
6430
|
description: "Show what permission a command would need"
|
|
@@ -6002,9 +6462,9 @@ var explainCommand = defineCommand47({
|
|
|
6002
6462
|
});
|
|
6003
6463
|
|
|
6004
6464
|
// src/commands/config/get.ts
|
|
6005
|
-
import { defineCommand as
|
|
6006
|
-
import
|
|
6007
|
-
var configGetCommand =
|
|
6465
|
+
import { defineCommand as defineCommand49 } from "citty";
|
|
6466
|
+
import consola41 from "consola";
|
|
6467
|
+
var configGetCommand = defineCommand49({
|
|
6008
6468
|
meta: {
|
|
6009
6469
|
name: "get",
|
|
6010
6470
|
description: "Get a configuration value"
|
|
@@ -6024,7 +6484,7 @@ var configGetCommand = defineCommand48({
|
|
|
6024
6484
|
if (idp)
|
|
6025
6485
|
console.log(idp);
|
|
6026
6486
|
else
|
|
6027
|
-
|
|
6487
|
+
consola41.info("No IdP configured.");
|
|
6028
6488
|
break;
|
|
6029
6489
|
}
|
|
6030
6490
|
case "email": {
|
|
@@ -6032,7 +6492,7 @@ var configGetCommand = defineCommand48({
|
|
|
6032
6492
|
if (auth?.email)
|
|
6033
6493
|
console.log(auth.email);
|
|
6034
6494
|
else
|
|
6035
|
-
|
|
6495
|
+
consola41.info("Not logged in.");
|
|
6036
6496
|
break;
|
|
6037
6497
|
}
|
|
6038
6498
|
default: {
|
|
@@ -6045,7 +6505,7 @@ var configGetCommand = defineCommand48({
|
|
|
6045
6505
|
if (sectionObj && field in sectionObj) {
|
|
6046
6506
|
console.log(sectionObj[field]);
|
|
6047
6507
|
} else {
|
|
6048
|
-
|
|
6508
|
+
consola41.info(`Key "${key}" not set.`);
|
|
6049
6509
|
}
|
|
6050
6510
|
} else {
|
|
6051
6511
|
throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
|
|
@@ -6056,9 +6516,9 @@ var configGetCommand = defineCommand48({
|
|
|
6056
6516
|
});
|
|
6057
6517
|
|
|
6058
6518
|
// src/commands/config/set.ts
|
|
6059
|
-
import { defineCommand as
|
|
6060
|
-
import
|
|
6061
|
-
var configSetCommand =
|
|
6519
|
+
import { defineCommand as defineCommand50 } from "citty";
|
|
6520
|
+
import consola42 from "consola";
|
|
6521
|
+
var configSetCommand = defineCommand50({
|
|
6062
6522
|
meta: {
|
|
6063
6523
|
name: "set",
|
|
6064
6524
|
description: "Set a configuration value"
|
|
@@ -6094,12 +6554,12 @@ var configSetCommand = defineCommand49({
|
|
|
6094
6554
|
throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
|
|
6095
6555
|
}
|
|
6096
6556
|
saveConfig(config);
|
|
6097
|
-
|
|
6557
|
+
consola42.success(`Set ${key} = ${value}`);
|
|
6098
6558
|
}
|
|
6099
6559
|
});
|
|
6100
6560
|
|
|
6101
6561
|
// src/commands/fetch/index.ts
|
|
6102
|
-
import { defineCommand as
|
|
6562
|
+
import { defineCommand as defineCommand51 } from "citty";
|
|
6103
6563
|
async function doRequest(method, url, body, contentType, raw, showHeaders) {
|
|
6104
6564
|
const token = getAuthToken();
|
|
6105
6565
|
if (!token) {
|
|
@@ -6135,13 +6595,13 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
|
|
|
6135
6595
|
throw new CliError(`HTTP ${response.status} ${response.statusText}`);
|
|
6136
6596
|
}
|
|
6137
6597
|
}
|
|
6138
|
-
var fetchCommand =
|
|
6598
|
+
var fetchCommand = defineCommand51({
|
|
6139
6599
|
meta: {
|
|
6140
6600
|
name: "fetch",
|
|
6141
6601
|
description: "Make authenticated HTTP requests"
|
|
6142
6602
|
},
|
|
6143
6603
|
subCommands: {
|
|
6144
|
-
get:
|
|
6604
|
+
get: defineCommand51({
|
|
6145
6605
|
meta: {
|
|
6146
6606
|
name: "get",
|
|
6147
6607
|
description: "GET request with auth token"
|
|
@@ -6167,7 +6627,7 @@ var fetchCommand = defineCommand50({
|
|
|
6167
6627
|
await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
|
|
6168
6628
|
}
|
|
6169
6629
|
}),
|
|
6170
|
-
post:
|
|
6630
|
+
post: defineCommand51({
|
|
6171
6631
|
meta: {
|
|
6172
6632
|
name: "post",
|
|
6173
6633
|
description: "POST request with auth token"
|
|
@@ -6206,8 +6666,8 @@ var fetchCommand = defineCommand50({
|
|
|
6206
6666
|
});
|
|
6207
6667
|
|
|
6208
6668
|
// src/commands/mcp/index.ts
|
|
6209
|
-
import { defineCommand as
|
|
6210
|
-
var mcpCommand =
|
|
6669
|
+
import { defineCommand as defineCommand52 } from "citty";
|
|
6670
|
+
var mcpCommand = defineCommand52({
|
|
6211
6671
|
meta: {
|
|
6212
6672
|
name: "mcp",
|
|
6213
6673
|
description: "Start MCP server for AI agents"
|
|
@@ -6230,25 +6690,25 @@ var mcpCommand = defineCommand51({
|
|
|
6230
6690
|
if (transport !== "stdio" && transport !== "sse") {
|
|
6231
6691
|
throw new Error('Transport must be "stdio" or "sse"');
|
|
6232
6692
|
}
|
|
6233
|
-
const { startMcpServer } = await import("./server-
|
|
6693
|
+
const { startMcpServer } = await import("./server-UKHYMZ7X.js");
|
|
6234
6694
|
await startMcpServer(transport, port);
|
|
6235
6695
|
}
|
|
6236
6696
|
});
|
|
6237
6697
|
|
|
6238
6698
|
// src/commands/init/index.ts
|
|
6239
|
-
import { existsSync as
|
|
6699
|
+
import { existsSync as existsSync19, copyFileSync, writeFileSync as writeFileSync10 } from "fs";
|
|
6240
6700
|
import { randomBytes } from "crypto";
|
|
6241
6701
|
import { execFileSync as execFileSync16 } from "child_process";
|
|
6242
|
-
import { join as
|
|
6243
|
-
import { defineCommand as
|
|
6244
|
-
import
|
|
6702
|
+
import { join as join18 } from "path";
|
|
6703
|
+
import { defineCommand as defineCommand53 } from "citty";
|
|
6704
|
+
import consola43 from "consola";
|
|
6245
6705
|
var DEFAULT_IDP_URL = "https://id.openape.at";
|
|
6246
6706
|
async function downloadTemplate(repo, targetDir) {
|
|
6247
6707
|
const { downloadTemplate: gigetDownload } = await import("giget");
|
|
6248
6708
|
await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
|
|
6249
6709
|
}
|
|
6250
6710
|
function installDeps(dir) {
|
|
6251
|
-
const hasLockFile = (name) =>
|
|
6711
|
+
const hasLockFile = (name) => existsSync19(join18(dir, name));
|
|
6252
6712
|
if (hasLockFile("pnpm-lock.yaml")) {
|
|
6253
6713
|
execFileSync16("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
|
|
6254
6714
|
} else if (hasLockFile("bun.lockb")) {
|
|
@@ -6258,20 +6718,20 @@ function installDeps(dir) {
|
|
|
6258
6718
|
}
|
|
6259
6719
|
}
|
|
6260
6720
|
async function promptChoice(message, choices) {
|
|
6261
|
-
const result = await
|
|
6721
|
+
const result = await consola43.prompt(message, { type: "select", options: choices });
|
|
6262
6722
|
if (typeof result === "symbol") {
|
|
6263
6723
|
throw new CliExit(0);
|
|
6264
6724
|
}
|
|
6265
6725
|
return result;
|
|
6266
6726
|
}
|
|
6267
6727
|
async function promptText(message, defaultValue) {
|
|
6268
|
-
const result = await
|
|
6728
|
+
const result = await consola43.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
|
|
6269
6729
|
if (typeof result === "symbol") {
|
|
6270
6730
|
throw new CliExit(0);
|
|
6271
6731
|
}
|
|
6272
6732
|
return result || defaultValue || "";
|
|
6273
6733
|
}
|
|
6274
|
-
var initCommand =
|
|
6734
|
+
var initCommand = defineCommand53({
|
|
6275
6735
|
meta: {
|
|
6276
6736
|
name: "init",
|
|
6277
6737
|
description: "Scaffold a new OpenApe project"
|
|
@@ -6313,23 +6773,23 @@ var initCommand = defineCommand52({
|
|
|
6313
6773
|
});
|
|
6314
6774
|
async function initSP(targetDir) {
|
|
6315
6775
|
const dir = targetDir || "my-app";
|
|
6316
|
-
if (
|
|
6776
|
+
if (existsSync19(join18(dir, "package.json"))) {
|
|
6317
6777
|
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
6318
6778
|
}
|
|
6319
|
-
|
|
6779
|
+
consola43.start("Scaffolding SP starter...");
|
|
6320
6780
|
await downloadTemplate("openape-ai/openape-sp-starter", dir);
|
|
6321
|
-
|
|
6322
|
-
|
|
6781
|
+
consola43.success("Scaffolded from openape-sp-starter");
|
|
6782
|
+
consola43.start("Installing dependencies...");
|
|
6323
6783
|
installDeps(dir);
|
|
6324
|
-
|
|
6325
|
-
const envExample =
|
|
6326
|
-
const envFile =
|
|
6327
|
-
if (
|
|
6784
|
+
consola43.success("Dependencies installed");
|
|
6785
|
+
const envExample = join18(dir, ".env.example");
|
|
6786
|
+
const envFile = join18(dir, ".env");
|
|
6787
|
+
if (existsSync19(envExample) && !existsSync19(envFile)) {
|
|
6328
6788
|
copyFileSync(envExample, envFile);
|
|
6329
|
-
|
|
6789
|
+
consola43.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
|
|
6330
6790
|
}
|
|
6331
6791
|
console.log("");
|
|
6332
|
-
|
|
6792
|
+
consola43.box([
|
|
6333
6793
|
`cd ${dir}`,
|
|
6334
6794
|
"npm run dev",
|
|
6335
6795
|
"",
|
|
@@ -6338,7 +6798,7 @@ async function initSP(targetDir) {
|
|
|
6338
6798
|
}
|
|
6339
6799
|
async function initIdP(targetDir) {
|
|
6340
6800
|
const dir = targetDir || "my-idp";
|
|
6341
|
-
if (
|
|
6801
|
+
if (existsSync19(join18(dir, "package.json"))) {
|
|
6342
6802
|
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
6343
6803
|
}
|
|
6344
6804
|
const domain = await promptText("Domain for the IdP", "localhost");
|
|
@@ -6348,15 +6808,15 @@ async function initIdP(targetDir) {
|
|
|
6348
6808
|
"s3 (S3-compatible)"
|
|
6349
6809
|
]);
|
|
6350
6810
|
const adminEmail = await promptText("Admin email");
|
|
6351
|
-
|
|
6811
|
+
consola43.start("Scaffolding IdP starter...");
|
|
6352
6812
|
await downloadTemplate("openape-ai/openape-idp-starter", dir);
|
|
6353
|
-
|
|
6354
|
-
|
|
6813
|
+
consola43.success("Scaffolded from openape-idp-starter");
|
|
6814
|
+
consola43.start("Installing dependencies...");
|
|
6355
6815
|
installDeps(dir);
|
|
6356
|
-
|
|
6816
|
+
consola43.success("Dependencies installed");
|
|
6357
6817
|
const sessionSecret = randomBytes(32).toString("hex");
|
|
6358
6818
|
const managementToken = randomBytes(32).toString("hex");
|
|
6359
|
-
|
|
6819
|
+
consola43.success("Secrets generated");
|
|
6360
6820
|
const isLocalhost = domain === "localhost";
|
|
6361
6821
|
const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
|
|
6362
6822
|
const envContent = [
|
|
@@ -6370,11 +6830,11 @@ async function initIdP(targetDir) {
|
|
|
6370
6830
|
`NUXT_OPENAPE_RP_ID=${domain}`,
|
|
6371
6831
|
`NUXT_OPENAPE_RP_ORIGIN=${origin}`
|
|
6372
6832
|
].join("\n");
|
|
6373
|
-
writeFileSync10(
|
|
6833
|
+
writeFileSync10(join18(dir, ".env"), `${envContent}
|
|
6374
6834
|
`, { mode: 384 });
|
|
6375
|
-
|
|
6835
|
+
consola43.success(".env created");
|
|
6376
6836
|
console.log("");
|
|
6377
|
-
|
|
6837
|
+
consola43.box([
|
|
6378
6838
|
`cd ${dir}`,
|
|
6379
6839
|
"npm run dev",
|
|
6380
6840
|
"",
|
|
@@ -6391,11 +6851,11 @@ async function initIdP(targetDir) {
|
|
|
6391
6851
|
|
|
6392
6852
|
// src/commands/enroll.ts
|
|
6393
6853
|
import { Buffer as Buffer5 } from "buffer";
|
|
6394
|
-
import { existsSync as
|
|
6854
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
|
|
6395
6855
|
import { execFile as execFile2 } from "child_process";
|
|
6396
6856
|
import { sign as sign2 } from "crypto";
|
|
6397
|
-
import { defineCommand as
|
|
6398
|
-
import
|
|
6857
|
+
import { defineCommand as defineCommand54 } from "citty";
|
|
6858
|
+
import consola44 from "consola";
|
|
6399
6859
|
var DEFAULT_IDP_URL2 = "https://id.openape.at";
|
|
6400
6860
|
var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
|
|
6401
6861
|
var POLL_INTERVAL = 3e3;
|
|
@@ -6407,7 +6867,7 @@ function openBrowser2(url) {
|
|
|
6407
6867
|
}
|
|
6408
6868
|
async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
6409
6869
|
const resolvedKey = resolveKeyPath(keyPath);
|
|
6410
|
-
const keyContent =
|
|
6870
|
+
const keyContent = readFileSync15(resolvedKey, "utf-8");
|
|
6411
6871
|
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
6412
6872
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
6413
6873
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
@@ -6438,7 +6898,7 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
|
6438
6898
|
}
|
|
6439
6899
|
throw new Error("Enrollment timed out. Please check the browser and try again.");
|
|
6440
6900
|
}
|
|
6441
|
-
var enrollCommand =
|
|
6901
|
+
var enrollCommand = defineCommand54({
|
|
6442
6902
|
meta: {
|
|
6443
6903
|
name: "enroll",
|
|
6444
6904
|
description: "Enroll an agent with an Identity Provider"
|
|
@@ -6458,38 +6918,38 @@ var enrollCommand = defineCommand53({
|
|
|
6458
6918
|
}
|
|
6459
6919
|
},
|
|
6460
6920
|
async run({ args }) {
|
|
6461
|
-
const idp = args.idp || await
|
|
6921
|
+
const idp = args.idp || await consola44.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
|
|
6462
6922
|
if (typeof r === "symbol") throw new CliExit(0);
|
|
6463
6923
|
return r;
|
|
6464
6924
|
}) || DEFAULT_IDP_URL2;
|
|
6465
|
-
const agentName = args.name || await
|
|
6925
|
+
const agentName = args.name || await consola44.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
|
|
6466
6926
|
if (typeof r === "symbol") throw new CliExit(0);
|
|
6467
6927
|
return r;
|
|
6468
6928
|
});
|
|
6469
6929
|
if (!agentName) {
|
|
6470
6930
|
throw new CliError("Agent name is required.");
|
|
6471
6931
|
}
|
|
6472
|
-
const keyPath = args.key || await
|
|
6932
|
+
const keyPath = args.key || await consola44.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
|
|
6473
6933
|
if (typeof r === "symbol") throw new CliExit(0);
|
|
6474
6934
|
return r;
|
|
6475
6935
|
}) || DEFAULT_KEY_PATH;
|
|
6476
6936
|
const resolvedKey = resolveKeyPath(keyPath);
|
|
6477
6937
|
let publicKey;
|
|
6478
|
-
if (
|
|
6938
|
+
if (existsSync20(resolvedKey)) {
|
|
6479
6939
|
publicKey = readPublicKey(resolvedKey);
|
|
6480
|
-
|
|
6940
|
+
consola44.success(`Using existing key ${keyPath}`);
|
|
6481
6941
|
} else {
|
|
6482
|
-
|
|
6942
|
+
consola44.start(`Generating Ed25519 key pair at ${keyPath}...`);
|
|
6483
6943
|
publicKey = generateAndSaveKey(keyPath);
|
|
6484
|
-
|
|
6944
|
+
consola44.success(`Key pair generated at ${keyPath}`);
|
|
6485
6945
|
}
|
|
6486
6946
|
const encodedKey = encodeURIComponent(publicKey);
|
|
6487
6947
|
const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
|
|
6488
|
-
|
|
6489
|
-
|
|
6948
|
+
consola44.info("Opening browser for enrollment...");
|
|
6949
|
+
consola44.info(`\u2192 ${idp}/enroll`);
|
|
6490
6950
|
openBrowser2(enrollUrl);
|
|
6491
6951
|
console.log("");
|
|
6492
|
-
const agentEmail = await
|
|
6952
|
+
const agentEmail = await consola44.prompt(
|
|
6493
6953
|
"Agent email (shown in browser after enrollment)",
|
|
6494
6954
|
{ type: "text", placeholder: `agent+${agentName}@...` }
|
|
6495
6955
|
).then((r) => {
|
|
@@ -6499,7 +6959,7 @@ var enrollCommand = defineCommand53({
|
|
|
6499
6959
|
if (!agentEmail) {
|
|
6500
6960
|
throw new CliError("Agent email is required to verify enrollment.");
|
|
6501
6961
|
}
|
|
6502
|
-
|
|
6962
|
+
consola44.start("Verifying enrollment...");
|
|
6503
6963
|
const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
|
|
6504
6964
|
saveAuth({
|
|
6505
6965
|
idp,
|
|
@@ -6511,18 +6971,18 @@ var enrollCommand = defineCommand53({
|
|
|
6511
6971
|
config.defaults = { ...config.defaults, idp };
|
|
6512
6972
|
config.agent = { key: keyPath, email: agentEmail };
|
|
6513
6973
|
saveConfig(config);
|
|
6514
|
-
|
|
6515
|
-
|
|
6974
|
+
consola44.success(`Agent enrolled as ${agentEmail}`);
|
|
6975
|
+
consola44.success("Config saved to ~/.config/apes/");
|
|
6516
6976
|
console.log("");
|
|
6517
|
-
|
|
6977
|
+
consola44.info("Verify with: apes whoami");
|
|
6518
6978
|
}
|
|
6519
6979
|
});
|
|
6520
6980
|
|
|
6521
6981
|
// src/commands/register-user.ts
|
|
6522
|
-
import { existsSync as
|
|
6523
|
-
import { defineCommand as
|
|
6524
|
-
import
|
|
6525
|
-
var registerUserCommand =
|
|
6982
|
+
import { existsSync as existsSync21, readFileSync as readFileSync16 } from "fs";
|
|
6983
|
+
import { defineCommand as defineCommand55 } from "citty";
|
|
6984
|
+
import consola45 from "consola";
|
|
6985
|
+
var registerUserCommand = defineCommand55({
|
|
6526
6986
|
meta: {
|
|
6527
6987
|
name: "register-user",
|
|
6528
6988
|
description: "Register a sub-user with SSH key"
|
|
@@ -6558,8 +7018,8 @@ var registerUserCommand = defineCommand54({
|
|
|
6558
7018
|
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
6559
7019
|
}
|
|
6560
7020
|
let publicKey = args.key;
|
|
6561
|
-
if (
|
|
6562
|
-
publicKey =
|
|
7021
|
+
if (existsSync21(args.key)) {
|
|
7022
|
+
publicKey = readFileSync16(args.key, "utf-8").trim();
|
|
6563
7023
|
}
|
|
6564
7024
|
if (!publicKey.startsWith("ssh-ed25519 ")) {
|
|
6565
7025
|
throw new CliError("Public key must be in ssh-ed25519 format.");
|
|
@@ -6577,18 +7037,18 @@ var registerUserCommand = defineCommand54({
|
|
|
6577
7037
|
...userType ? { type: userType } : {}
|
|
6578
7038
|
}
|
|
6579
7039
|
});
|
|
6580
|
-
|
|
7040
|
+
consola45.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
|
|
6581
7041
|
}
|
|
6582
7042
|
});
|
|
6583
7043
|
|
|
6584
7044
|
// src/commands/utils/index.ts
|
|
6585
|
-
import { defineCommand as
|
|
7045
|
+
import { defineCommand as defineCommand57 } from "citty";
|
|
6586
7046
|
|
|
6587
7047
|
// src/commands/utils/dig.ts
|
|
6588
|
-
import { defineCommand as
|
|
6589
|
-
import
|
|
7048
|
+
import { defineCommand as defineCommand56 } from "citty";
|
|
7049
|
+
import consola46 from "consola";
|
|
6590
7050
|
import { resolveDDISA as resolveDDISA2 } from "@openape/core";
|
|
6591
|
-
var digCommand =
|
|
7051
|
+
var digCommand = defineCommand56({
|
|
6592
7052
|
meta: {
|
|
6593
7053
|
name: "dig",
|
|
6594
7054
|
description: "Resolve DDISA IdP for a domain or email (admin/diag tool)"
|
|
@@ -6661,12 +7121,12 @@ var digCommand = defineCommand55({
|
|
|
6661
7121
|
console.log(` domain: ${domain}`);
|
|
6662
7122
|
console.log("");
|
|
6663
7123
|
if (!result.ddisa.found) {
|
|
6664
|
-
|
|
7124
|
+
consola46.warn(`No DDISA record at _ddisa.${domain}`);
|
|
6665
7125
|
if (result.hint) console.log(`
|
|
6666
7126
|
${result.hint}`);
|
|
6667
7127
|
throw new CliError(`No DDISA record found for ${domain}`);
|
|
6668
7128
|
}
|
|
6669
|
-
|
|
7129
|
+
consola46.success(`_ddisa.${domain} \u2192 ${result.ddisa.idp}`);
|
|
6670
7130
|
console.log(` Version: ${result.ddisa.version || "ddisa1"}`);
|
|
6671
7131
|
console.log(` IdP URL: ${result.ddisa.idp}`);
|
|
6672
7132
|
if (result.ddisa.mode) console.log(` Mode: ${result.ddisa.mode}`);
|
|
@@ -6676,13 +7136,13 @@ ${result.hint}`);
|
|
|
6676
7136
|
return;
|
|
6677
7137
|
}
|
|
6678
7138
|
if (result.idpDiscovery.ok) {
|
|
6679
|
-
|
|
7139
|
+
consola46.success(`IdP reachable (${result.idpDiscovery.status ?? 200})`);
|
|
6680
7140
|
if (result.idpDiscovery.issuer) console.log(` Issuer: ${result.idpDiscovery.issuer}`);
|
|
6681
7141
|
if (result.idpDiscovery.ddisaVersion) console.log(` DDISA: v${result.idpDiscovery.ddisaVersion}`);
|
|
6682
7142
|
if (result.idpDiscovery.authMethods?.length) console.log(` Auth: ${result.idpDiscovery.authMethods.join(", ")}`);
|
|
6683
7143
|
if (result.idpDiscovery.grantTypes?.length) console.log(` Grants: ${result.idpDiscovery.grantTypes.join(", ")}`);
|
|
6684
7144
|
} else {
|
|
6685
|
-
|
|
7145
|
+
consola46.warn(`IdP discovery failed${result.idpDiscovery.status ? ` (HTTP ${result.idpDiscovery.status})` : ""}`);
|
|
6686
7146
|
if (result.hint) console.log(`
|
|
6687
7147
|
${result.hint}`);
|
|
6688
7148
|
throw new CliError(`IdP at ${result.ddisa.idp} not reachable`);
|
|
@@ -6691,7 +7151,7 @@ ${result.hint}`);
|
|
|
6691
7151
|
});
|
|
6692
7152
|
|
|
6693
7153
|
// src/commands/utils/index.ts
|
|
6694
|
-
var utilsCommand =
|
|
7154
|
+
var utilsCommand = defineCommand57({
|
|
6695
7155
|
meta: {
|
|
6696
7156
|
name: "utils",
|
|
6697
7157
|
description: "Admin/diagnostic utilities (dig, \u2026)"
|
|
@@ -6702,12 +7162,12 @@ var utilsCommand = defineCommand56({
|
|
|
6702
7162
|
});
|
|
6703
7163
|
|
|
6704
7164
|
// src/commands/sessions/index.ts
|
|
6705
|
-
import { defineCommand as
|
|
7165
|
+
import { defineCommand as defineCommand60 } from "citty";
|
|
6706
7166
|
|
|
6707
7167
|
// src/commands/sessions/list.ts
|
|
6708
|
-
import { defineCommand as
|
|
6709
|
-
import
|
|
6710
|
-
var sessionsListCommand =
|
|
7168
|
+
import { defineCommand as defineCommand58 } from "citty";
|
|
7169
|
+
import consola47 from "consola";
|
|
7170
|
+
var sessionsListCommand = defineCommand58({
|
|
6711
7171
|
meta: {
|
|
6712
7172
|
name: "list",
|
|
6713
7173
|
description: "List your active refresh-token families (one per logged-in device)."
|
|
@@ -6725,7 +7185,7 @@ var sessionsListCommand = defineCommand57({
|
|
|
6725
7185
|
return;
|
|
6726
7186
|
}
|
|
6727
7187
|
if (result.data.length === 0) {
|
|
6728
|
-
|
|
7188
|
+
consola47.info("No active sessions.");
|
|
6729
7189
|
return;
|
|
6730
7190
|
}
|
|
6731
7191
|
for (const f of result.data) {
|
|
@@ -6737,9 +7197,9 @@ var sessionsListCommand = defineCommand57({
|
|
|
6737
7197
|
});
|
|
6738
7198
|
|
|
6739
7199
|
// src/commands/sessions/remove.ts
|
|
6740
|
-
import { defineCommand as
|
|
6741
|
-
import
|
|
6742
|
-
var sessionsRemoveCommand =
|
|
7200
|
+
import { defineCommand as defineCommand59 } from "citty";
|
|
7201
|
+
import consola48 from "consola";
|
|
7202
|
+
var sessionsRemoveCommand = defineCommand59({
|
|
6743
7203
|
meta: {
|
|
6744
7204
|
name: "remove",
|
|
6745
7205
|
description: "Revoke one of your active refresh-token families by id."
|
|
@@ -6755,12 +7215,12 @@ var sessionsRemoveCommand = defineCommand58({
|
|
|
6755
7215
|
const id = String(args.familyId).trim();
|
|
6756
7216
|
if (!id) throw new CliError("familyId required");
|
|
6757
7217
|
await apiFetch(`/api/me/sessions/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
6758
|
-
|
|
7218
|
+
consola48.success(`Session ${id} revoked. The device using it will need to \`apes login\` again on its next refresh.`);
|
|
6759
7219
|
}
|
|
6760
7220
|
});
|
|
6761
7221
|
|
|
6762
7222
|
// src/commands/sessions/index.ts
|
|
6763
|
-
var sessionsCommand =
|
|
7223
|
+
var sessionsCommand = defineCommand60({
|
|
6764
7224
|
meta: {
|
|
6765
7225
|
name: "sessions",
|
|
6766
7226
|
description: "Manage your active refresh-token sessions across devices"
|
|
@@ -6772,10 +7232,10 @@ var sessionsCommand = defineCommand59({
|
|
|
6772
7232
|
});
|
|
6773
7233
|
|
|
6774
7234
|
// src/commands/dns-check.ts
|
|
6775
|
-
import { defineCommand as
|
|
6776
|
-
import
|
|
7235
|
+
import { defineCommand as defineCommand61 } from "citty";
|
|
7236
|
+
import consola49 from "consola";
|
|
6777
7237
|
import { resolveDDISA as resolveDDISA3 } from "@openape/core";
|
|
6778
|
-
var dnsCheckCommand =
|
|
7238
|
+
var dnsCheckCommand = defineCommand61({
|
|
6779
7239
|
meta: {
|
|
6780
7240
|
name: "dns-check",
|
|
6781
7241
|
description: "Validate DDISA DNS TXT records for a domain"
|
|
@@ -6789,7 +7249,7 @@ var dnsCheckCommand = defineCommand60({
|
|
|
6789
7249
|
},
|
|
6790
7250
|
async run({ args }) {
|
|
6791
7251
|
const domain = args.domain;
|
|
6792
|
-
|
|
7252
|
+
consola49.start(`Checking _ddisa.${domain}...`);
|
|
6793
7253
|
try {
|
|
6794
7254
|
const result = await resolveDDISA3(domain);
|
|
6795
7255
|
if (!result) {
|
|
@@ -6798,7 +7258,7 @@ var dnsCheckCommand = defineCommand60({
|
|
|
6798
7258
|
console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
|
|
6799
7259
|
throw new CliError(`No DDISA record found for ${domain}`);
|
|
6800
7260
|
}
|
|
6801
|
-
|
|
7261
|
+
consola49.success(`_ddisa.${domain} \u2192 ${result.idp}`);
|
|
6802
7262
|
console.log("");
|
|
6803
7263
|
console.log(` Version: ${result.version || "ddisa1"}`);
|
|
6804
7264
|
console.log(` IdP URL: ${result.idp}`);
|
|
@@ -6807,14 +7267,14 @@ var dnsCheckCommand = defineCommand60({
|
|
|
6807
7267
|
if (result.priority !== void 0)
|
|
6808
7268
|
console.log(` Priority: ${result.priority}`);
|
|
6809
7269
|
console.log("");
|
|
6810
|
-
|
|
7270
|
+
consola49.start(`Verifying IdP at ${result.idp}...`);
|
|
6811
7271
|
const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
|
|
6812
7272
|
if (!discoResp.ok) {
|
|
6813
|
-
|
|
7273
|
+
consola49.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
|
|
6814
7274
|
return;
|
|
6815
7275
|
}
|
|
6816
7276
|
const disco = await discoResp.json();
|
|
6817
|
-
|
|
7277
|
+
consola49.success(`IdP is reachable`);
|
|
6818
7278
|
console.log(` Issuer: ${disco.issuer}`);
|
|
6819
7279
|
console.log(` DDISA: v${disco.ddisa_version || "?"}`);
|
|
6820
7280
|
if (disco.ddisa_auth_methods_supported) {
|
|
@@ -6832,7 +7292,7 @@ var dnsCheckCommand = defineCommand60({
|
|
|
6832
7292
|
// src/commands/health.ts
|
|
6833
7293
|
import { exec } from "child_process";
|
|
6834
7294
|
import { promisify } from "util";
|
|
6835
|
-
import { defineCommand as
|
|
7295
|
+
import { defineCommand as defineCommand62 } from "citty";
|
|
6836
7296
|
var execAsync = promisify(exec);
|
|
6837
7297
|
async function resolveApeShellPath() {
|
|
6838
7298
|
try {
|
|
@@ -6868,7 +7328,7 @@ async function bestEffortGrantCount(idp) {
|
|
|
6868
7328
|
}
|
|
6869
7329
|
}
|
|
6870
7330
|
async function runHealth(args) {
|
|
6871
|
-
const version = true ? "1.
|
|
7331
|
+
const version = true ? "1.28.0" : "0.0.0";
|
|
6872
7332
|
const auth = loadAuth();
|
|
6873
7333
|
if (!auth) {
|
|
6874
7334
|
throw new CliError("Not logged in. Run `apes login` first.", 1);
|
|
@@ -6931,7 +7391,7 @@ async function runHealth(args) {
|
|
|
6931
7391
|
throw new CliError(`IdP ${auth.idp} unreachable: ${idpProbe.error}`, 1);
|
|
6932
7392
|
}
|
|
6933
7393
|
}
|
|
6934
|
-
var healthCommand =
|
|
7394
|
+
var healthCommand = defineCommand62({
|
|
6935
7395
|
meta: {
|
|
6936
7396
|
name: "health",
|
|
6937
7397
|
description: "Report CLI diagnostic state (auth, IdP, grants, binaries)"
|
|
@@ -6949,8 +7409,8 @@ var healthCommand = defineCommand61({
|
|
|
6949
7409
|
});
|
|
6950
7410
|
|
|
6951
7411
|
// src/commands/workflows.ts
|
|
6952
|
-
import { defineCommand as
|
|
6953
|
-
import
|
|
7412
|
+
import { defineCommand as defineCommand63 } from "citty";
|
|
7413
|
+
import consola50 from "consola";
|
|
6954
7414
|
|
|
6955
7415
|
// src/guides/index.ts
|
|
6956
7416
|
var guides = [
|
|
@@ -7000,7 +7460,7 @@ var guides = [
|
|
|
7000
7460
|
];
|
|
7001
7461
|
|
|
7002
7462
|
// src/commands/workflows.ts
|
|
7003
|
-
var workflowsCommand =
|
|
7463
|
+
var workflowsCommand = defineCommand63({
|
|
7004
7464
|
meta: {
|
|
7005
7465
|
name: "workflows",
|
|
7006
7466
|
description: "Discover workflow guides"
|
|
@@ -7021,7 +7481,7 @@ var workflowsCommand = defineCommand62({
|
|
|
7021
7481
|
if (args.id) {
|
|
7022
7482
|
const guide = guides.find((g) => g.id === String(args.id));
|
|
7023
7483
|
if (!guide) {
|
|
7024
|
-
|
|
7484
|
+
consola50.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
|
|
7025
7485
|
throw new CliError(`Guide not found: ${args.id}`);
|
|
7026
7486
|
}
|
|
7027
7487
|
if (args.json) {
|
|
@@ -7061,25 +7521,25 @@ var workflowsCommand = defineCommand62({
|
|
|
7061
7521
|
});
|
|
7062
7522
|
|
|
7063
7523
|
// src/version-check.ts
|
|
7064
|
-
import { existsSync as
|
|
7065
|
-
import { homedir as
|
|
7066
|
-
import { join as
|
|
7067
|
-
import
|
|
7524
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
7525
|
+
import { homedir as homedir16 } from "os";
|
|
7526
|
+
import { join as join19 } from "path";
|
|
7527
|
+
import consola51 from "consola";
|
|
7068
7528
|
var PACKAGE_NAME = "@openape/apes";
|
|
7069
7529
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7070
|
-
var CACHE_FILE =
|
|
7530
|
+
var CACHE_FILE = join19(homedir16(), ".config", "apes", ".version-check.json");
|
|
7071
7531
|
function readCache() {
|
|
7072
|
-
if (!
|
|
7532
|
+
if (!existsSync22(CACHE_FILE)) return null;
|
|
7073
7533
|
try {
|
|
7074
|
-
return JSON.parse(
|
|
7534
|
+
return JSON.parse(readFileSync17(CACHE_FILE, "utf-8"));
|
|
7075
7535
|
} catch {
|
|
7076
7536
|
return null;
|
|
7077
7537
|
}
|
|
7078
7538
|
}
|
|
7079
7539
|
function writeCache(entry) {
|
|
7080
7540
|
try {
|
|
7081
|
-
const dir =
|
|
7082
|
-
if (!
|
|
7541
|
+
const dir = join19(homedir16(), ".config", "apes");
|
|
7542
|
+
if (!existsSync22(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
7083
7543
|
writeFileSync11(CACHE_FILE, JSON.stringify(entry), { mode: 384 });
|
|
7084
7544
|
} catch {
|
|
7085
7545
|
}
|
|
@@ -7109,7 +7569,7 @@ async function fetchLatestVersion() {
|
|
|
7109
7569
|
}
|
|
7110
7570
|
function warnIfBehind(currentVersion, latest) {
|
|
7111
7571
|
if (compareSemver(currentVersion, latest) < 0) {
|
|
7112
|
-
|
|
7572
|
+
consola51.warn(
|
|
7113
7573
|
`apes ${currentVersion} is behind latest @openape/apes@${latest}. Run \`npm i -g @openape/apes@latest\` to update. (Suppress with APES_NO_UPDATE_CHECK=1.)`
|
|
7114
7574
|
);
|
|
7115
7575
|
}
|
|
@@ -7141,10 +7601,10 @@ if (shellRewrite) {
|
|
|
7141
7601
|
if (shellRewrite.action === "rewrite") {
|
|
7142
7602
|
process.argv = shellRewrite.argv;
|
|
7143
7603
|
} else if (shellRewrite.action === "version") {
|
|
7144
|
-
console.log(`ape-shell ${"1.
|
|
7604
|
+
console.log(`ape-shell ${"1.28.0"} (OpenApe DDISA shell wrapper)`);
|
|
7145
7605
|
process.exit(0);
|
|
7146
7606
|
} else if (shellRewrite.action === "help") {
|
|
7147
|
-
console.log(`ape-shell ${"1.
|
|
7607
|
+
console.log(`ape-shell ${"1.28.0"} \u2014 OpenApe DDISA shell wrapper`);
|
|
7148
7608
|
console.log("");
|
|
7149
7609
|
console.log("Usage:");
|
|
7150
7610
|
console.log(" ape-shell Start interactive grant-mediated REPL");
|
|
@@ -7168,7 +7628,7 @@ if (shellRewrite) {
|
|
|
7168
7628
|
}
|
|
7169
7629
|
}
|
|
7170
7630
|
var debug = process.argv.includes("--debug");
|
|
7171
|
-
var grantsCommand =
|
|
7631
|
+
var grantsCommand = defineCommand64({
|
|
7172
7632
|
meta: {
|
|
7173
7633
|
name: "grants",
|
|
7174
7634
|
description: "Grant management"
|
|
@@ -7189,7 +7649,7 @@ var grantsCommand = defineCommand63({
|
|
|
7189
7649
|
"delegation-revoke": delegationRevokeCommand
|
|
7190
7650
|
}
|
|
7191
7651
|
});
|
|
7192
|
-
var configCommand =
|
|
7652
|
+
var configCommand = defineCommand64({
|
|
7193
7653
|
meta: {
|
|
7194
7654
|
name: "config",
|
|
7195
7655
|
description: "Configuration management"
|
|
@@ -7199,10 +7659,10 @@ var configCommand = defineCommand63({
|
|
|
7199
7659
|
set: configSetCommand
|
|
7200
7660
|
}
|
|
7201
7661
|
});
|
|
7202
|
-
var main =
|
|
7662
|
+
var main = defineCommand64({
|
|
7203
7663
|
meta: {
|
|
7204
7664
|
name: "apes",
|
|
7205
|
-
version: "1.
|
|
7665
|
+
version: "1.28.0",
|
|
7206
7666
|
description: "Unified CLI for OpenApe"
|
|
7207
7667
|
},
|
|
7208
7668
|
subCommands: {
|
|
@@ -7260,20 +7720,20 @@ async function maybeRefreshAuth() {
|
|
|
7260
7720
|
}
|
|
7261
7721
|
}
|
|
7262
7722
|
await maybeRefreshAuth();
|
|
7263
|
-
await maybeWarnStaleVersion("1.
|
|
7723
|
+
await maybeWarnStaleVersion("1.28.0").catch(() => {
|
|
7264
7724
|
});
|
|
7265
7725
|
runMain(main).catch((err) => {
|
|
7266
7726
|
if (err instanceof CliExit) {
|
|
7267
7727
|
process.exit(err.exitCode);
|
|
7268
7728
|
}
|
|
7269
7729
|
if (err instanceof CliError) {
|
|
7270
|
-
|
|
7730
|
+
consola52.error(err.message);
|
|
7271
7731
|
process.exit(err.exitCode);
|
|
7272
7732
|
}
|
|
7273
7733
|
if (debug) {
|
|
7274
|
-
|
|
7734
|
+
consola52.error(err);
|
|
7275
7735
|
} else {
|
|
7276
|
-
|
|
7736
|
+
consola52.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
|
|
7277
7737
|
}
|
|
7278
7738
|
process.exit(1);
|
|
7279
7739
|
});
|