@openape/apes 1.13.1 → 1.15.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/cli.js
CHANGED
|
@@ -2643,6 +2643,12 @@ if dscl . -read "/Users/$NAME" >/dev/null 2>&1; then
|
|
|
2643
2643
|
exit 1
|
|
2644
2644
|
fi
|
|
2645
2645
|
|
|
2646
|
+
# Phase G: agent home dirs live under /var/openape/homes/, not
|
|
2647
|
+
# /Users/. Pre-create the parent and chmod it world-traversable
|
|
2648
|
+
# so per-agent dirs can be reached by their respective uids.
|
|
2649
|
+
mkdir -p /var/openape/homes
|
|
2650
|
+
chmod 755 /var/openape/homes
|
|
2651
|
+
|
|
2646
2652
|
# Pick the next free UID in the [200, 500) hidden service-account range.
|
|
2647
2653
|
# Starts the running max at 199 so an empty range yields 200 after the
|
|
2648
2654
|
# floor check; otherwise NEXT_UID = max(existing in-range UIDs) + 1.
|
|
@@ -2846,10 +2852,12 @@ function readMacOSUser(name) {
|
|
|
2846
2852
|
}
|
|
2847
2853
|
const uidMatch = output.match(/UniqueID:\s*(\d+)/);
|
|
2848
2854
|
const shellMatch = output.match(/UserShell:\s*(\S.*)$/m);
|
|
2855
|
+
const homeMatch = output.match(/NFSHomeDirectory:\s*(\S.*)$/m);
|
|
2849
2856
|
return {
|
|
2850
2857
|
name,
|
|
2851
2858
|
uid: uidMatch ? Number.parseInt(uidMatch[1], 10) : null,
|
|
2852
|
-
shell: shellMatch ? shellMatch[1].trim() : null
|
|
2859
|
+
shell: shellMatch ? shellMatch[1].trim() : null,
|
|
2860
|
+
homeDir: homeMatch ? homeMatch[1].trim() : null
|
|
2853
2861
|
};
|
|
2854
2862
|
}
|
|
2855
2863
|
function listMacOSUserNames() {
|
|
@@ -2955,12 +2963,61 @@ function shQuote2(s) {
|
|
|
2955
2963
|
|
|
2956
2964
|
// src/commands/agents/destroy.ts
|
|
2957
2965
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
2958
|
-
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
2966
|
+
import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2959
2967
|
import { tmpdir, userInfo } from "os";
|
|
2960
|
-
import { join as
|
|
2968
|
+
import { join as join3 } from "path";
|
|
2961
2969
|
import { defineCommand as defineCommand21 } from "citty";
|
|
2962
2970
|
import consola19 from "consola";
|
|
2963
2971
|
|
|
2972
|
+
// src/lib/nest-registry.ts
|
|
2973
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
2974
|
+
import { homedir as homedir4 } from "os";
|
|
2975
|
+
import { join as join2 } from "path";
|
|
2976
|
+
function resolveRegistryPath() {
|
|
2977
|
+
if (existsSync4("/var/openape/nest/agents.json")) return "/var/openape/nest/agents.json";
|
|
2978
|
+
if (existsSync4("/var/openape/nest")) return "/var/openape/nest/agents.json";
|
|
2979
|
+
return join2(homedir4(), ".openape", "nest", "agents.json");
|
|
2980
|
+
}
|
|
2981
|
+
function emptyRegistry() {
|
|
2982
|
+
return { version: 1, agents: [] };
|
|
2983
|
+
}
|
|
2984
|
+
function readNestRegistry() {
|
|
2985
|
+
const path2 = resolveRegistryPath();
|
|
2986
|
+
if (!existsSync4(path2)) return emptyRegistry();
|
|
2987
|
+
try {
|
|
2988
|
+
const parsed = JSON.parse(readFileSync3(path2, "utf8"));
|
|
2989
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.agents)) return emptyRegistry();
|
|
2990
|
+
return parsed;
|
|
2991
|
+
} catch {
|
|
2992
|
+
return emptyRegistry();
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
function writeNestRegistry(reg) {
|
|
2996
|
+
const path2 = resolveRegistryPath();
|
|
2997
|
+
const dir = path2.replace(/\/agents\.json$/, "");
|
|
2998
|
+
try {
|
|
2999
|
+
mkdirSync(dir, { recursive: true });
|
|
3000
|
+
} catch {
|
|
3001
|
+
}
|
|
3002
|
+
writeFileSync(path2, `${JSON.stringify(reg, null, 2)}
|
|
3003
|
+
`, { mode: 432 });
|
|
3004
|
+
}
|
|
3005
|
+
function upsertNestAgent(entry) {
|
|
3006
|
+
const reg = readNestRegistry();
|
|
3007
|
+
const existing = reg.agents.findIndex((a) => a.name === entry.name);
|
|
3008
|
+
if (existing >= 0) reg.agents[existing] = entry;
|
|
3009
|
+
else reg.agents.push(entry);
|
|
3010
|
+
writeNestRegistry(reg);
|
|
3011
|
+
}
|
|
3012
|
+
function removeNestAgent(name) {
|
|
3013
|
+
const reg = readNestRegistry();
|
|
3014
|
+
const before = reg.agents.length;
|
|
3015
|
+
reg.agents = reg.agents.filter((a) => a.name !== name);
|
|
3016
|
+
if (reg.agents.length === before) return false;
|
|
3017
|
+
writeNestRegistry(reg);
|
|
3018
|
+
return true;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
2964
3021
|
// src/lib/silent-password.ts
|
|
2965
3022
|
function readPasswordSilent(prompt) {
|
|
2966
3023
|
if (!process.stdin.isTTY) {
|
|
@@ -3058,14 +3115,18 @@ var destroyAgentCommand = defineCommand21({
|
|
|
3058
3115
|
const owned = await apiFetch("/api/my-agents", { idp });
|
|
3059
3116
|
const idpAgent = owned.find((u) => u.name === name);
|
|
3060
3117
|
const idpExists = idpAgent !== void 0;
|
|
3061
|
-
const
|
|
3118
|
+
const osUser = isDarwin() ? readMacOSUser(name) : null;
|
|
3119
|
+
const osUserExists = !args["keep-os-user"] && osUser !== null;
|
|
3062
3120
|
if (!idpExists && !osUserExists) {
|
|
3063
3121
|
consola19.info(`Nothing to destroy: no IdP agent and no OS user named "${name}".`);
|
|
3064
3122
|
return;
|
|
3065
3123
|
}
|
|
3066
3124
|
if (!args.force) {
|
|
3067
3125
|
const consequences = [];
|
|
3068
|
-
if (osUserExists)
|
|
3126
|
+
if (osUserExists) {
|
|
3127
|
+
const home = osUser?.homeDir ?? `/Users/${name}`;
|
|
3128
|
+
consequences.push(`\u2022 Remove macOS user ${name} and rm -rf ${home}`);
|
|
3129
|
+
}
|
|
3069
3130
|
if (idpExists) {
|
|
3070
3131
|
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`);
|
|
3071
3132
|
}
|
|
@@ -3100,11 +3161,12 @@ ${consequences.join("\n")}`);
|
|
|
3100
3161
|
}
|
|
3101
3162
|
const adminUser = userInfo().username;
|
|
3102
3163
|
const adminPassword = await collectAdminPassword({ adminUser });
|
|
3103
|
-
const scratch = mkdtempSync(
|
|
3104
|
-
const scriptPath =
|
|
3164
|
+
const scratch = mkdtempSync(join3(tmpdir(), `apes-destroy-${name}-`));
|
|
3165
|
+
const scriptPath = join3(scratch, "teardown.sh");
|
|
3105
3166
|
try {
|
|
3106
|
-
const
|
|
3107
|
-
|
|
3167
|
+
const homeDir = osUser?.homeDir ?? `/Users/${name}`;
|
|
3168
|
+
const script = buildDestroyTeardownScript({ name, homeDir, adminUser });
|
|
3169
|
+
writeFileSync2(scriptPath, script, { mode: 448 });
|
|
3108
3170
|
consola19.start("Running teardown via sudo\u2026");
|
|
3109
3171
|
execFileSync4(sudo, ["-S", "--prompt=", "--", "bash", scriptPath], {
|
|
3110
3172
|
input: `${adminPassword}
|
|
@@ -3118,6 +3180,11 @@ ${adminPassword}
|
|
|
3118
3180
|
} else if (!args["keep-os-user"] && isDarwin()) {
|
|
3119
3181
|
consola19.info("No macOS user to remove (skipped).");
|
|
3120
3182
|
}
|
|
3183
|
+
try {
|
|
3184
|
+
removeNestAgent(name);
|
|
3185
|
+
} catch (err) {
|
|
3186
|
+
consola19.warn(`Could not update nest registry: ${err instanceof Error ? err.message : String(err)}`);
|
|
3187
|
+
}
|
|
3121
3188
|
consola19.success(`Destroyed ${name}.`);
|
|
3122
3189
|
}
|
|
3123
3190
|
});
|
|
@@ -3161,13 +3228,17 @@ var listAgentsCommand = defineCommand22({
|
|
|
3161
3228
|
const all = await apiFetch("/api/my-agents", { idp });
|
|
3162
3229
|
const filtered = args["include-inactive"] ? all : all.filter((u) => u.isActive !== false);
|
|
3163
3230
|
const osUsers = isDarwin() ? listMacOSUserNames() : /* @__PURE__ */ new Set();
|
|
3164
|
-
const
|
|
3231
|
+
const homeOf = (name) => {
|
|
3232
|
+
if (!osUsers.has(name)) return null;
|
|
3233
|
+
const u = readMacOSUser(name);
|
|
3234
|
+
return u?.homeDir ?? `/Users/${name}`;
|
|
3235
|
+
};
|
|
3165
3236
|
const rows = filtered.map((u) => ({
|
|
3166
3237
|
name: u.name,
|
|
3167
3238
|
email: u.email,
|
|
3168
3239
|
isActive: u.isActive !== false,
|
|
3169
3240
|
osUser: osUsers.has(u.name),
|
|
3170
|
-
home:
|
|
3241
|
+
home: homeOf(u.name)
|
|
3171
3242
|
}));
|
|
3172
3243
|
if (args.json) {
|
|
3173
3244
|
process.stdout.write(`${JSON.stringify(rows, null, 2)}
|
|
@@ -3193,7 +3264,7 @@ var listAgentsCommand = defineCommand22({
|
|
|
3193
3264
|
});
|
|
3194
3265
|
|
|
3195
3266
|
// src/commands/agents/register.ts
|
|
3196
|
-
import { existsSync as
|
|
3267
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
3197
3268
|
import { defineCommand as defineCommand23 } from "citty";
|
|
3198
3269
|
import consola21 from "consola";
|
|
3199
3270
|
var registerAgentCommand = defineCommand23({
|
|
@@ -3241,10 +3312,10 @@ var registerAgentCommand = defineCommand23({
|
|
|
3241
3312
|
throw new CliError("Pass either --public-key or --public-key-file, not both.");
|
|
3242
3313
|
}
|
|
3243
3314
|
if (!publicKey && keyFile) {
|
|
3244
|
-
if (!
|
|
3315
|
+
if (!existsSync5(keyFile)) {
|
|
3245
3316
|
throw new CliError(`Public-key file not found: ${keyFile}`);
|
|
3246
3317
|
}
|
|
3247
|
-
publicKey =
|
|
3318
|
+
publicKey = readFileSync4(keyFile, "utf-8").trim();
|
|
3248
3319
|
}
|
|
3249
3320
|
if (!publicKey) {
|
|
3250
3321
|
throw new CliError('Provide --public-key "<ssh-ed25519 line>" or --public-key-file <path>.');
|
|
@@ -3279,9 +3350,9 @@ var registerAgentCommand = defineCommand23({
|
|
|
3279
3350
|
});
|
|
3280
3351
|
|
|
3281
3352
|
// src/commands/agents/run.ts
|
|
3282
|
-
import { existsSync as
|
|
3283
|
-
import { homedir as
|
|
3284
|
-
import { join as
|
|
3353
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
3354
|
+
import { homedir as homedir5 } from "os";
|
|
3355
|
+
import { join as join4 } from "path";
|
|
3285
3356
|
import { defineCommand as defineCommand24 } from "citty";
|
|
3286
3357
|
import consola22 from "consola";
|
|
3287
3358
|
|
|
@@ -3343,13 +3414,13 @@ function resolveTroopUrl(override) {
|
|
|
3343
3414
|
}
|
|
3344
3415
|
|
|
3345
3416
|
// src/commands/agents/run.ts
|
|
3346
|
-
var AUTH_PATH =
|
|
3347
|
-
var TASK_CACHE_DIR =
|
|
3417
|
+
var AUTH_PATH = join4(homedir5(), ".config", "apes", "auth.json");
|
|
3418
|
+
var TASK_CACHE_DIR = join4(homedir5(), ".openape", "agent", "tasks");
|
|
3348
3419
|
function readAuth() {
|
|
3349
|
-
if (!
|
|
3420
|
+
if (!existsSync6(AUTH_PATH)) {
|
|
3350
3421
|
throw new CliError(`No agent auth found at ${AUTH_PATH}. Run \`apes agents spawn <name>\` first.`);
|
|
3351
3422
|
}
|
|
3352
|
-
const parsed = JSON.parse(
|
|
3423
|
+
const parsed = JSON.parse(readFileSync5(AUTH_PATH, "utf8"));
|
|
3353
3424
|
if (!parsed.access_token) throw new CliError("auth.json missing access_token");
|
|
3354
3425
|
return parsed;
|
|
3355
3426
|
}
|
|
@@ -3385,26 +3456,26 @@ ${msg}`.slice(0, 9e3);
|
|
|
3385
3456
|
}
|
|
3386
3457
|
}
|
|
3387
3458
|
function readTaskSpec(taskId) {
|
|
3388
|
-
const path2 =
|
|
3389
|
-
if (!
|
|
3459
|
+
const path2 = join4(TASK_CACHE_DIR, `${taskId}.json`);
|
|
3460
|
+
if (!existsSync6(path2)) {
|
|
3390
3461
|
throw new CliError(`No cached task spec at ${path2}. Run \`apes agents sync\` first to pull the task list from troop.`);
|
|
3391
3462
|
}
|
|
3392
|
-
return JSON.parse(
|
|
3463
|
+
return JSON.parse(readFileSync5(path2, "utf8"));
|
|
3393
3464
|
}
|
|
3394
|
-
var AGENT_CONFIG_PATH =
|
|
3465
|
+
var AGENT_CONFIG_PATH = join4(homedir5(), ".openape", "agent", "agent.json");
|
|
3395
3466
|
function readAgentConfig() {
|
|
3396
|
-
if (!
|
|
3467
|
+
if (!existsSync6(AGENT_CONFIG_PATH)) return { systemPrompt: "" };
|
|
3397
3468
|
try {
|
|
3398
|
-
return JSON.parse(
|
|
3469
|
+
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf8"));
|
|
3399
3470
|
} catch {
|
|
3400
3471
|
return { systemPrompt: "" };
|
|
3401
3472
|
}
|
|
3402
3473
|
}
|
|
3403
3474
|
function readLitellmConfig(model) {
|
|
3404
|
-
const envPath =
|
|
3475
|
+
const envPath = join4(homedir5(), "litellm", ".env");
|
|
3405
3476
|
const env = {};
|
|
3406
|
-
if (
|
|
3407
|
-
for (const line of
|
|
3477
|
+
if (existsSync6(envPath)) {
|
|
3478
|
+
for (const line of readFileSync5(envPath, "utf8").split(/\r?\n/)) {
|
|
3408
3479
|
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
3409
3480
|
if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
|
|
3410
3481
|
}
|
|
@@ -3509,17 +3580,17 @@ var runAgentCommand = defineCommand24({
|
|
|
3509
3580
|
});
|
|
3510
3581
|
|
|
3511
3582
|
// src/commands/agents/serve.ts
|
|
3512
|
-
import { existsSync as
|
|
3513
|
-
import { homedir as
|
|
3514
|
-
import { join as
|
|
3583
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
3584
|
+
import { homedir as homedir6 } from "os";
|
|
3585
|
+
import { join as join5 } from "path";
|
|
3515
3586
|
import { createInterface } from "readline";
|
|
3516
3587
|
import { defineCommand as defineCommand25 } from "citty";
|
|
3517
|
-
var AUTH_PATH2 =
|
|
3588
|
+
var AUTH_PATH2 = join5(homedir6(), ".config", "apes", "auth.json");
|
|
3518
3589
|
function readLitellmConfig2(model) {
|
|
3519
|
-
const envPath =
|
|
3590
|
+
const envPath = join5(homedir6(), "litellm", ".env");
|
|
3520
3591
|
const env = {};
|
|
3521
|
-
if (
|
|
3522
|
-
for (const line of
|
|
3592
|
+
if (existsSync7(envPath)) {
|
|
3593
|
+
for (const line of readFileSync6(envPath, "utf8").split(/\r?\n/)) {
|
|
3523
3594
|
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
3524
3595
|
if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
|
|
3525
3596
|
}
|
|
@@ -3551,9 +3622,9 @@ var serveAgentCommand = defineCommand25({
|
|
|
3551
3622
|
if (!args.rpc) {
|
|
3552
3623
|
throw new CliError("apes agents serve currently only supports --rpc mode");
|
|
3553
3624
|
}
|
|
3554
|
-
if (
|
|
3625
|
+
if (existsSync7(AUTH_PATH2)) {
|
|
3555
3626
|
try {
|
|
3556
|
-
JSON.parse(
|
|
3627
|
+
JSON.parse(readFileSync6(AUTH_PATH2, "utf8"));
|
|
3557
3628
|
} catch {
|
|
3558
3629
|
}
|
|
3559
3630
|
}
|
|
@@ -3629,9 +3700,9 @@ async function handleInbound(msg, sessions) {
|
|
|
3629
3700
|
|
|
3630
3701
|
// src/commands/agents/spawn.ts
|
|
3631
3702
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
3632
|
-
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as
|
|
3703
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
3633
3704
|
import { tmpdir as tmpdir2 } from "os";
|
|
3634
|
-
import { join as
|
|
3705
|
+
import { join as join7 } from "path";
|
|
3635
3706
|
import { defineCommand as defineCommand26 } from "citty";
|
|
3636
3707
|
import consola23 from "consola";
|
|
3637
3708
|
|
|
@@ -3689,12 +3760,12 @@ ${envBlock} <key>StartInterval</key>
|
|
|
3689
3760
|
|
|
3690
3761
|
// src/lib/keygen.ts
|
|
3691
3762
|
import { Buffer as Buffer4 } from "buffer";
|
|
3692
|
-
import { existsSync as
|
|
3763
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
3693
3764
|
import { generateKeyPairSync } from "crypto";
|
|
3694
|
-
import { homedir as
|
|
3765
|
+
import { homedir as homedir7 } from "os";
|
|
3695
3766
|
import { dirname, resolve as resolve2 } from "path";
|
|
3696
3767
|
function resolveKeyPath(p2) {
|
|
3697
|
-
return resolve2(p2.replace(/^~/,
|
|
3768
|
+
return resolve2(p2.replace(/^~/, homedir7()));
|
|
3698
3769
|
}
|
|
3699
3770
|
function buildSshEd25519Line(rawPub) {
|
|
3700
3771
|
const keyTypeStr = "ssh-ed25519";
|
|
@@ -3707,10 +3778,10 @@ function buildSshEd25519Line(rawPub) {
|
|
|
3707
3778
|
}
|
|
3708
3779
|
function readPublicKey(keyPath) {
|
|
3709
3780
|
const pubPath = `${keyPath}.pub`;
|
|
3710
|
-
if (
|
|
3711
|
-
return
|
|
3781
|
+
if (existsSync8(pubPath)) {
|
|
3782
|
+
return readFileSync7(pubPath, "utf-8").trim();
|
|
3712
3783
|
}
|
|
3713
|
-
const keyContent =
|
|
3784
|
+
const keyContent = readFileSync7(keyPath, "utf-8");
|
|
3714
3785
|
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
3715
3786
|
const jwk = privateKey.export({ format: "jwk" });
|
|
3716
3787
|
const pubBytes = Buffer4.from(jwk.x, "base64url");
|
|
@@ -3719,16 +3790,16 @@ function readPublicKey(keyPath) {
|
|
|
3719
3790
|
function generateAndSaveKey(keyPath) {
|
|
3720
3791
|
const resolved = resolveKeyPath(keyPath);
|
|
3721
3792
|
const dir = dirname(resolved);
|
|
3722
|
-
if (!
|
|
3723
|
-
|
|
3793
|
+
if (!existsSync8(dir)) {
|
|
3794
|
+
mkdirSync2(dir, { recursive: true });
|
|
3724
3795
|
}
|
|
3725
3796
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
3726
3797
|
const privatePem = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
3727
|
-
|
|
3798
|
+
writeFileSync3(resolved, privatePem, { mode: 384 });
|
|
3728
3799
|
const jwk = publicKey.export({ format: "jwk" });
|
|
3729
3800
|
const pubBytes = Buffer4.from(jwk.x, "base64url");
|
|
3730
3801
|
const pubKeyStr = buildSshEd25519Line(pubBytes);
|
|
3731
|
-
|
|
3802
|
+
writeFileSync3(`${resolved}.pub`, `${pubKeyStr}
|
|
3732
3803
|
`, { mode: 420 });
|
|
3733
3804
|
return pubKeyStr;
|
|
3734
3805
|
}
|
|
@@ -3745,14 +3816,14 @@ function generateKeyPairInMemory() {
|
|
|
3745
3816
|
|
|
3746
3817
|
// src/lib/llm-bridge.ts
|
|
3747
3818
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
3748
|
-
import { existsSync as
|
|
3749
|
-
import { homedir as
|
|
3750
|
-
import { dirname as dirname2, join as
|
|
3819
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
3820
|
+
import { homedir as homedir8 } from "os";
|
|
3821
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
3751
3822
|
var PLIST_LABEL_PREFIX = "eco.hofmann.apes.bridge";
|
|
3752
|
-
function readLitellmEnv(envPath =
|
|
3753
|
-
if (!
|
|
3823
|
+
function readLitellmEnv(envPath = join6(homedir8(), "litellm", ".env")) {
|
|
3824
|
+
if (!existsSync9(envPath)) return null;
|
|
3754
3825
|
try {
|
|
3755
|
-
const text =
|
|
3826
|
+
const text = readFileSync8(envPath, "utf8");
|
|
3756
3827
|
const out = {};
|
|
3757
3828
|
for (const line of text.split("\n")) {
|
|
3758
3829
|
const trimmed = line.trim();
|
|
@@ -3881,6 +3952,14 @@ function buildBridgePlist(agentName, homeDir, ownerEmail, hostBinDirs) {
|
|
|
3881
3952
|
}
|
|
3882
3953
|
|
|
3883
3954
|
// src/commands/agents/spawn.ts
|
|
3955
|
+
function readMacOSUidOrNull(name) {
|
|
3956
|
+
try {
|
|
3957
|
+
const u = readMacOSUser(name);
|
|
3958
|
+
return u?.uid ?? null;
|
|
3959
|
+
} catch {
|
|
3960
|
+
return null;
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3884
3963
|
var spawnAgentCommand = defineCommand26({
|
|
3885
3964
|
meta: {
|
|
3886
3965
|
name: "spawn",
|
|
@@ -3967,9 +4046,9 @@ and try again.`
|
|
|
3967
4046
|
if (existing) {
|
|
3968
4047
|
throw new CliError(`macOS user "${name}" already exists (uid=${existing.uid ?? "?"}). Refusing to overwrite.`);
|
|
3969
4048
|
}
|
|
3970
|
-
const homeDir = `/
|
|
3971
|
-
const scratch = mkdtempSync2(
|
|
3972
|
-
const scriptPath =
|
|
4049
|
+
const homeDir = `/var/openape/homes/${name}`;
|
|
4050
|
+
const scratch = mkdtempSync2(join7(tmpdir2(), `apes-spawn-${name}-`));
|
|
4051
|
+
const scriptPath = join7(scratch, "setup.sh");
|
|
3973
4052
|
try {
|
|
3974
4053
|
consola23.start(`Generating keypair for ${name}\u2026`);
|
|
3975
4054
|
const { privatePem, publicSshLine } = generateKeyPairInMemory();
|
|
@@ -4039,10 +4118,27 @@ and try again.`
|
|
|
4039
4118
|
bridge,
|
|
4040
4119
|
troop
|
|
4041
4120
|
});
|
|
4042
|
-
|
|
4121
|
+
writeFileSync4(scriptPath, script, { mode: 448 });
|
|
4043
4122
|
consola23.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
|
|
4044
4123
|
consola23.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
|
|
4045
4124
|
execFileSync6(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
|
|
4125
|
+
try {
|
|
4126
|
+
const uid = readMacOSUidOrNull(name);
|
|
4127
|
+
upsertNestAgent({
|
|
4128
|
+
name,
|
|
4129
|
+
uid: uid ?? -1,
|
|
4130
|
+
home: homeDir,
|
|
4131
|
+
email: registration.email,
|
|
4132
|
+
registeredAt: Math.floor(Date.now() / 1e3),
|
|
4133
|
+
bridge: args.bridge ? {
|
|
4134
|
+
baseUrl: typeof args["bridge-base-url"] === "string" ? args["bridge-base-url"] : void 0,
|
|
4135
|
+
apiKey: typeof args["bridge-key"] === "string" ? args["bridge-key"] : void 0,
|
|
4136
|
+
model: typeof args["bridge-model"] === "string" ? args["bridge-model"] : void 0
|
|
4137
|
+
} : void 0
|
|
4138
|
+
});
|
|
4139
|
+
} catch (err) {
|
|
4140
|
+
consola23.warn(`Could not write to nest registry: ${err instanceof Error ? err.message : String(err)}`);
|
|
4141
|
+
}
|
|
4046
4142
|
consola23.success(`Agent ${name} spawned.`);
|
|
4047
4143
|
consola23.info(`\u{1F517} Troop: https://troop.openape.ai/agents/${name}`);
|
|
4048
4144
|
if (args.bridge) {
|
|
@@ -4081,9 +4177,9 @@ async function resolveClaudeToken(opts) {
|
|
|
4081
4177
|
}
|
|
4082
4178
|
|
|
4083
4179
|
// src/commands/agents/sync.ts
|
|
4084
|
-
import { chownSync, existsSync as
|
|
4085
|
-
import { homedir as
|
|
4086
|
-
import { join as
|
|
4180
|
+
import { chownSync, existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync9, statSync, writeFileSync as writeFileSync5 } from "fs";
|
|
4181
|
+
import { homedir as homedir9 } from "os";
|
|
4182
|
+
import { join as join8 } from "path";
|
|
4087
4183
|
import { defineCommand as defineCommand27 } from "citty";
|
|
4088
4184
|
import consola24 from "consola";
|
|
4089
4185
|
|
|
@@ -4112,15 +4208,15 @@ function getHostname() {
|
|
|
4112
4208
|
}
|
|
4113
4209
|
|
|
4114
4210
|
// src/commands/agents/sync.ts
|
|
4115
|
-
var AUTH_PATH3 =
|
|
4116
|
-
var TASK_CACHE_DIR2 =
|
|
4211
|
+
var AUTH_PATH3 = join8(homedir9(), ".config", "apes", "auth.json");
|
|
4212
|
+
var TASK_CACHE_DIR2 = join8(homedir9(), ".openape", "agent", "tasks");
|
|
4117
4213
|
function readAuthJson() {
|
|
4118
|
-
if (!
|
|
4214
|
+
if (!existsSync10(AUTH_PATH3)) {
|
|
4119
4215
|
throw new CliError(
|
|
4120
4216
|
`No agent auth found at ${AUTH_PATH3}. Run \`apes agents spawn <name>\` to provision an agent first.`
|
|
4121
4217
|
);
|
|
4122
4218
|
}
|
|
4123
|
-
const raw =
|
|
4219
|
+
const raw = readFileSync9(AUTH_PATH3, "utf8");
|
|
4124
4220
|
let parsed;
|
|
4125
4221
|
try {
|
|
4126
4222
|
parsed = JSON.parse(raw);
|
|
@@ -4181,7 +4277,7 @@ var syncAgentCommand = defineCommand27({
|
|
|
4181
4277
|
let agentGid = null;
|
|
4182
4278
|
if (process.geteuid?.() === 0) {
|
|
4183
4279
|
try {
|
|
4184
|
-
const homeStat = statSync(
|
|
4280
|
+
const homeStat = statSync(homedir9());
|
|
4185
4281
|
agentUid = homeStat.uid;
|
|
4186
4282
|
agentGid = homeStat.gid;
|
|
4187
4283
|
} catch {
|
|
@@ -4195,23 +4291,23 @@ var syncAgentCommand = defineCommand27({
|
|
|
4195
4291
|
}
|
|
4196
4292
|
}
|
|
4197
4293
|
}
|
|
4198
|
-
const agentDir =
|
|
4199
|
-
|
|
4200
|
-
chownToAgent(
|
|
4294
|
+
const agentDir = join8(homedir9(), ".openape", "agent");
|
|
4295
|
+
mkdirSync3(agentDir, { recursive: true });
|
|
4296
|
+
chownToAgent(join8(homedir9(), ".openape"));
|
|
4201
4297
|
chownToAgent(agentDir);
|
|
4202
|
-
const agentJsonPath =
|
|
4203
|
-
|
|
4298
|
+
const agentJsonPath = join8(agentDir, "agent.json");
|
|
4299
|
+
writeFileSync5(
|
|
4204
4300
|
agentJsonPath,
|
|
4205
4301
|
`${JSON.stringify({ systemPrompt }, null, 2)}
|
|
4206
4302
|
`,
|
|
4207
4303
|
{ mode: 384 }
|
|
4208
4304
|
);
|
|
4209
4305
|
chownToAgent(agentJsonPath);
|
|
4210
|
-
|
|
4306
|
+
mkdirSync3(TASK_CACHE_DIR2, { recursive: true });
|
|
4211
4307
|
chownToAgent(TASK_CACHE_DIR2);
|
|
4212
4308
|
for (const task of tasks) {
|
|
4213
|
-
const path2 =
|
|
4214
|
-
|
|
4309
|
+
const path2 = join8(TASK_CACHE_DIR2, `${task.taskId}.json`);
|
|
4310
|
+
writeFileSync5(path2, `${JSON.stringify(task, null, 2)}
|
|
4215
4311
|
`, { mode: 384 });
|
|
4216
4312
|
chownToAgent(path2);
|
|
4217
4313
|
}
|
|
@@ -4242,18 +4338,18 @@ import { defineCommand as defineCommand36 } from "citty";
|
|
|
4242
4338
|
|
|
4243
4339
|
// src/commands/nest/authorize.ts
|
|
4244
4340
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
4245
|
-
import { existsSync as
|
|
4246
|
-
import { join as
|
|
4341
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
4342
|
+
import { join as join10 } from "path";
|
|
4247
4343
|
import { defineCommand as defineCommand30 } from "citty";
|
|
4248
4344
|
import consola26 from "consola";
|
|
4249
4345
|
|
|
4250
4346
|
// src/commands/nest/enroll.ts
|
|
4251
|
-
import { hostname as hostname4, homedir as
|
|
4252
|
-
import { existsSync as
|
|
4253
|
-
import { join as
|
|
4347
|
+
import { hostname as hostname4, homedir as homedir10 } from "os";
|
|
4348
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, chmodSync } from "fs";
|
|
4349
|
+
import { join as join9 } from "path";
|
|
4254
4350
|
import { defineCommand as defineCommand29 } from "citty";
|
|
4255
4351
|
import consola25 from "consola";
|
|
4256
|
-
var NEST_DATA_DIR =
|
|
4352
|
+
var NEST_DATA_DIR = join9(homedir10(), ".openape", "nest");
|
|
4257
4353
|
function nestAgentName() {
|
|
4258
4354
|
const raw = hostname4().toLowerCase();
|
|
4259
4355
|
const head = raw.split(".")[0] ?? raw;
|
|
@@ -4286,19 +4382,19 @@ var enrollNestCommand = defineCommand29({
|
|
|
4286
4382
|
throw new CliError("Run `apes login <email>` first \u2014 nest enroll attaches the new identity to your owner account.");
|
|
4287
4383
|
}
|
|
4288
4384
|
const name = args.name || nestAgentName();
|
|
4289
|
-
const authPath =
|
|
4290
|
-
if (
|
|
4385
|
+
const authPath = join9(NEST_DATA_DIR, ".config", "apes", "auth.json");
|
|
4386
|
+
if (existsSync11(authPath) && !args.force) {
|
|
4291
4387
|
throw new CliError(`Nest already enrolled at ${authPath}. Pass --force to re-enroll.`);
|
|
4292
4388
|
}
|
|
4293
|
-
const sshDir =
|
|
4294
|
-
const configDir =
|
|
4295
|
-
|
|
4296
|
-
|
|
4389
|
+
const sshDir = join9(NEST_DATA_DIR, ".ssh");
|
|
4390
|
+
const configDir = join9(NEST_DATA_DIR, ".config", "apes");
|
|
4391
|
+
mkdirSync4(sshDir, { recursive: true });
|
|
4392
|
+
mkdirSync4(configDir, { recursive: true });
|
|
4297
4393
|
consola25.start(`Generating keypair for ${name}\u2026`);
|
|
4298
4394
|
const { privatePem, publicSshLine } = generateKeyPairInMemory();
|
|
4299
|
-
|
|
4395
|
+
writeFileSync6(join9(sshDir, "id_ed25519"), `${privatePem.trimEnd()}
|
|
4300
4396
|
`, { mode: 384 });
|
|
4301
|
-
|
|
4397
|
+
writeFileSync6(join9(sshDir, "id_ed25519.pub"), `${publicSshLine}
|
|
4302
4398
|
`, { mode: 420 });
|
|
4303
4399
|
chmodSync(sshDir, 448);
|
|
4304
4400
|
consola25.start(`Registering nest at ${idp}\u2026`);
|
|
@@ -4315,10 +4411,10 @@ var enrollNestCommand = defineCommand29({
|
|
|
4315
4411
|
accessToken: token,
|
|
4316
4412
|
email: registration.email,
|
|
4317
4413
|
expiresAt: Math.floor(Date.now() / 1e3) + expiresIn,
|
|
4318
|
-
keyPath:
|
|
4414
|
+
keyPath: join9(sshDir, "id_ed25519"),
|
|
4319
4415
|
ownerEmail: ownerAuth.email
|
|
4320
4416
|
});
|
|
4321
|
-
|
|
4417
|
+
writeFileSync6(authPath, authJson, { mode: 384 });
|
|
4322
4418
|
chmodSync(configDir, 448);
|
|
4323
4419
|
consola25.success(`Nest enrolled \u2014 auth.json at ${authPath}`);
|
|
4324
4420
|
consola25.info("");
|
|
@@ -4384,11 +4480,11 @@ var authorizeNestCommand = defineCommand30({
|
|
|
4384
4480
|
}
|
|
4385
4481
|
},
|
|
4386
4482
|
async run({ args }) {
|
|
4387
|
-
const nestAuthPath =
|
|
4388
|
-
if (!
|
|
4483
|
+
const nestAuthPath = join10(NEST_DATA_DIR, ".config", "apes", "auth.json");
|
|
4484
|
+
if (!existsSync12(nestAuthPath)) {
|
|
4389
4485
|
throw new CliError("Nest not enrolled. Run `apes nest enroll` first.");
|
|
4390
4486
|
}
|
|
4391
|
-
const nestAuth = JSON.parse(
|
|
4487
|
+
const nestAuth = JSON.parse(readFileSync10(nestAuthPath, "utf8"));
|
|
4392
4488
|
if (!nestAuth.email) throw new CliError(`${nestAuthPath} has no email`);
|
|
4393
4489
|
const allow = args.allow ?? DEFAULT_ALLOW_PATTERNS.join(",");
|
|
4394
4490
|
consola26.info(`Configuring YOLO-policy on ${nestAuth.email} via \`apes yolo set\`\u2026`);
|
|
@@ -4415,102 +4511,31 @@ var authorizeNestCommand = defineCommand30({
|
|
|
4415
4511
|
});
|
|
4416
4512
|
|
|
4417
4513
|
// src/commands/nest/destroy.ts
|
|
4514
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
4418
4515
|
import { defineCommand as defineCommand31 } from "citty";
|
|
4419
4516
|
import consola27 from "consola";
|
|
4420
|
-
|
|
4421
|
-
// src/lib/nest-intent.ts
|
|
4422
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync2, unlinkSync, writeFileSync as writeFileSync6 } from "fs";
|
|
4423
|
-
import { homedir as homedir10 } from "os";
|
|
4424
|
-
import { join as join10 } from "path";
|
|
4425
|
-
import { randomUUID } from "crypto";
|
|
4426
|
-
var POLL_INTERVAL_MS = 200;
|
|
4427
|
-
var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4428
|
-
function resolveIntentDir() {
|
|
4429
|
-
if (process.env.OPENAPE_NEST_INTENT_DIR) return process.env.OPENAPE_NEST_INTENT_DIR;
|
|
4430
|
-
if (existsSync12("/var/openape/nest/intents")) return "/var/openape/nest/intents";
|
|
4431
|
-
return join10(homedir10(), ".openape", "nest", "intents");
|
|
4432
|
-
}
|
|
4433
|
-
async function dispatchIntent(intent, opts = {}) {
|
|
4434
|
-
const id = randomUUID();
|
|
4435
|
-
const dir = resolveIntentDir();
|
|
4436
|
-
if (!existsSync12(dir)) {
|
|
4437
|
-
throw new CliError(`Nest intent dir does not exist: ${dir}
|
|
4438
|
-
Is the nest daemon running? Try \`ps aux | grep openape-nest\`.`);
|
|
4439
|
-
}
|
|
4440
|
-
const intentPath = join10(dir, `${id}.json`);
|
|
4441
|
-
const responsePath = join10(dir, `${id}.response`);
|
|
4442
|
-
const tmpPath = `${intentPath}.tmp`;
|
|
4443
|
-
writeFileSync6(tmpPath, `${JSON.stringify({ id, ...intent })}
|
|
4444
|
-
`, { mode: 432 });
|
|
4445
|
-
try {
|
|
4446
|
-
const fs = await import("fs");
|
|
4447
|
-
fs.renameSync(tmpPath, intentPath);
|
|
4448
|
-
} catch (err) {
|
|
4449
|
-
try {
|
|
4450
|
-
unlinkSync(tmpPath);
|
|
4451
|
-
} catch {
|
|
4452
|
-
}
|
|
4453
|
-
throw err;
|
|
4454
|
-
}
|
|
4455
|
-
const deadline = Date.now() + (opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
4456
|
-
while (Date.now() < deadline) {
|
|
4457
|
-
if (existsSync12(responsePath)) {
|
|
4458
|
-
let raw;
|
|
4459
|
-
try {
|
|
4460
|
-
const st = statSync2(responsePath);
|
|
4461
|
-
if (Date.now() - st.mtimeMs < 50) {
|
|
4462
|
-
await sleep(50);
|
|
4463
|
-
}
|
|
4464
|
-
raw = readFileSync10(responsePath, "utf8");
|
|
4465
|
-
} catch {
|
|
4466
|
-
await sleep(POLL_INTERVAL_MS);
|
|
4467
|
-
continue;
|
|
4468
|
-
}
|
|
4469
|
-
try {
|
|
4470
|
-
unlinkSync(responsePath);
|
|
4471
|
-
} catch {
|
|
4472
|
-
}
|
|
4473
|
-
let parsed;
|
|
4474
|
-
try {
|
|
4475
|
-
parsed = JSON.parse(raw);
|
|
4476
|
-
} catch (err) {
|
|
4477
|
-
throw new CliError(`malformed nest response: ${err instanceof Error ? err.message : String(err)}`);
|
|
4478
|
-
}
|
|
4479
|
-
if (!parsed.ok) {
|
|
4480
|
-
throw new CliError(`nest: ${parsed.error}`);
|
|
4481
|
-
}
|
|
4482
|
-
return parsed.result;
|
|
4483
|
-
}
|
|
4484
|
-
await sleep(POLL_INTERVAL_MS);
|
|
4485
|
-
}
|
|
4486
|
-
try {
|
|
4487
|
-
unlinkSync(intentPath);
|
|
4488
|
-
} catch {
|
|
4489
|
-
}
|
|
4490
|
-
throw new CliError(`nest intent timeout (${(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS) / 1e3}s) \u2014 Nest daemon may not be running or is stuck.`);
|
|
4491
|
-
}
|
|
4492
|
-
function sleep(ms) {
|
|
4493
|
-
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
4494
|
-
}
|
|
4495
|
-
|
|
4496
|
-
// src/commands/nest/destroy.ts
|
|
4497
4517
|
var destroyNestCommand = defineCommand31({
|
|
4498
4518
|
meta: {
|
|
4499
4519
|
name: "destroy",
|
|
4500
|
-
description: "
|
|
4520
|
+
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."
|
|
4501
4521
|
},
|
|
4502
4522
|
args: {
|
|
4503
4523
|
name: { type: "positional", required: true, description: "Agent name to destroy" }
|
|
4504
4524
|
},
|
|
4505
4525
|
async run({ args }) {
|
|
4506
4526
|
const name = String(args.name);
|
|
4507
|
-
|
|
4508
|
-
|
|
4527
|
+
try {
|
|
4528
|
+
execFileSync9("apes", ["run", "--as", "root", "--wait", "--", "apes", "agents", "destroy", name, "--force"], { stdio: "inherit" });
|
|
4529
|
+
consola27.success(`Nest will tear down ${name}'s pm2 process on its next reconcile (\u22642s).`);
|
|
4530
|
+
} catch (err) {
|
|
4531
|
+
const status = err.status ?? 1;
|
|
4532
|
+
throw new CliExit(status);
|
|
4533
|
+
}
|
|
4509
4534
|
}
|
|
4510
4535
|
});
|
|
4511
4536
|
|
|
4512
4537
|
// src/commands/nest/install.ts
|
|
4513
|
-
import { execFileSync as
|
|
4538
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
4514
4539
|
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
4515
4540
|
import { homedir as homedir11, userInfo as userInfo2 } from "os";
|
|
4516
4541
|
import { dirname as dirname3, join as join11 } from "path";
|
|
@@ -4708,10 +4733,10 @@ var installNestCommand = defineCommand32({
|
|
|
4708
4733
|
}
|
|
4709
4734
|
const uid = userInfo2().uid;
|
|
4710
4735
|
try {
|
|
4711
|
-
|
|
4736
|
+
execFileSync10("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL}`], { stdio: "ignore" });
|
|
4712
4737
|
} catch {
|
|
4713
4738
|
}
|
|
4714
|
-
|
|
4739
|
+
execFileSync10("/bin/launchctl", ["bootstrap", `gui/${uid}`, plistPath()], { stdio: "inherit" });
|
|
4715
4740
|
consola28.success(`Nest daemon bootstrapped \u2014 http://127.0.0.1:${port}`);
|
|
4716
4741
|
consola28.info("");
|
|
4717
4742
|
consola28.info("Next steps for zero-prompt spawn \u2014 both one-time:");
|
|
@@ -4729,23 +4754,23 @@ import consola29 from "consola";
|
|
|
4729
4754
|
var listNestCommand = defineCommand33({
|
|
4730
4755
|
meta: {
|
|
4731
4756
|
name: "list",
|
|
4732
|
-
description: "List agents registered with the local nest.
|
|
4757
|
+
description: "List agents registered with the local nest. Reads /var/openape/nest/agents.json directly."
|
|
4733
4758
|
},
|
|
4734
4759
|
args: {
|
|
4735
4760
|
json: { type: "boolean", description: "JSON output for scripts" }
|
|
4736
4761
|
},
|
|
4737
4762
|
async run({ args }) {
|
|
4738
|
-
const
|
|
4763
|
+
const reg = readNestRegistry();
|
|
4739
4764
|
if (args.json) {
|
|
4740
|
-
console.log(JSON.stringify(
|
|
4765
|
+
console.log(JSON.stringify(reg, null, 2));
|
|
4741
4766
|
return;
|
|
4742
4767
|
}
|
|
4743
|
-
if (
|
|
4768
|
+
if (reg.agents.length === 0) {
|
|
4744
4769
|
consola29.info("(no agents registered with this nest)");
|
|
4745
4770
|
return;
|
|
4746
4771
|
}
|
|
4747
|
-
consola29.info(`${
|
|
4748
|
-
for (const a of
|
|
4772
|
+
consola29.info(`${reg.agents.length} agent(s) registered with this nest:`);
|
|
4773
|
+
for (const a of reg.agents) {
|
|
4749
4774
|
const bridge = a.bridge ? " bridge=on" : "";
|
|
4750
4775
|
consola29.info(` ${a.name.padEnd(16)} uid=${String(a.uid).padEnd(5)} home=${a.home}${bridge}`);
|
|
4751
4776
|
}
|
|
@@ -4753,38 +4778,51 @@ var listNestCommand = defineCommand33({
|
|
|
4753
4778
|
});
|
|
4754
4779
|
|
|
4755
4780
|
// src/commands/nest/spawn.ts
|
|
4781
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
4756
4782
|
import { defineCommand as defineCommand34 } from "citty";
|
|
4757
4783
|
import consola30 from "consola";
|
|
4758
4784
|
var spawnNestCommand = defineCommand34({
|
|
4759
4785
|
meta: {
|
|
4760
4786
|
name: "spawn",
|
|
4761
|
-
description: "Spawn a new agent
|
|
4787
|
+
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."
|
|
4762
4788
|
},
|
|
4763
4789
|
args: {
|
|
4764
4790
|
name: { type: "positional", required: true, description: "Agent name (lowercase, [a-z0-9-], max 24 chars)" },
|
|
4765
4791
|
"no-bridge": { type: "boolean", description: "Skip installing the chat-bridge daemon (default: install it)" },
|
|
4766
4792
|
"bridge-key": { type: "string", description: "Override LITELLM_API_KEY (default: read from ~/litellm/.env)" },
|
|
4767
|
-
"bridge-base-url": { type: "string", description: "Override LITELLM_BASE_URL
|
|
4793
|
+
"bridge-base-url": { type: "string", description: "Override LITELLM_BASE_URL" },
|
|
4768
4794
|
"bridge-model": { type: "string", description: "Override APE_CHAT_BRIDGE_MODEL" }
|
|
4769
4795
|
},
|
|
4770
4796
|
async run({ args }) {
|
|
4771
4797
|
const name = String(args.name);
|
|
4772
|
-
const
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4798
|
+
const apesArgs = [
|
|
4799
|
+
"run",
|
|
4800
|
+
"--as",
|
|
4801
|
+
"root",
|
|
4802
|
+
"--wait",
|
|
4803
|
+
"--",
|
|
4804
|
+
"apes",
|
|
4805
|
+
"agents",
|
|
4806
|
+
"spawn",
|
|
4807
|
+
name
|
|
4808
|
+
];
|
|
4809
|
+
if (!args["no-bridge"]) apesArgs.push("--bridge");
|
|
4810
|
+
if (typeof args["bridge-key"] === "string") apesArgs.push("--bridge-key", args["bridge-key"]);
|
|
4811
|
+
if (typeof args["bridge-base-url"] === "string") apesArgs.push("--bridge-base-url", args["bridge-base-url"]);
|
|
4812
|
+
if (typeof args["bridge-model"] === "string") apesArgs.push("--bridge-model", args["bridge-model"]);
|
|
4813
|
+
try {
|
|
4814
|
+
execFileSync11("apes", apesArgs, { stdio: "inherit" });
|
|
4815
|
+
consola30.success(`Nest will pick up ${name} on its next reconcile (\u22642s).`);
|
|
4816
|
+
} catch (err) {
|
|
4817
|
+
const status = err.status ?? 1;
|
|
4818
|
+
throw new CliExit(status);
|
|
4819
|
+
}
|
|
4782
4820
|
}
|
|
4783
4821
|
});
|
|
4784
4822
|
|
|
4785
4823
|
// src/commands/nest/uninstall.ts
|
|
4786
|
-
import { execFileSync as
|
|
4787
|
-
import { existsSync as existsSync14, unlinkSync
|
|
4824
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
4825
|
+
import { existsSync as existsSync14, unlinkSync } from "fs";
|
|
4788
4826
|
import { homedir as homedir12, userInfo as userInfo3 } from "os";
|
|
4789
4827
|
import { join as join12 } from "path";
|
|
4790
4828
|
import { defineCommand as defineCommand35 } from "citty";
|
|
@@ -4799,13 +4837,13 @@ var uninstallNestCommand = defineCommand35({
|
|
|
4799
4837
|
const uid = userInfo3().uid;
|
|
4800
4838
|
const path2 = join12(homedir12(), "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
|
|
4801
4839
|
try {
|
|
4802
|
-
|
|
4840
|
+
execFileSync12("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL2}`], { stdio: "ignore" });
|
|
4803
4841
|
consola31.success("Nest daemon stopped");
|
|
4804
4842
|
} catch {
|
|
4805
4843
|
consola31.info("Nest daemon was not loaded");
|
|
4806
4844
|
}
|
|
4807
4845
|
if (existsSync14(path2)) {
|
|
4808
|
-
|
|
4846
|
+
unlinkSync(path2);
|
|
4809
4847
|
consola31.success(`Removed ${path2}`);
|
|
4810
4848
|
}
|
|
4811
4849
|
consola31.info("Registry at ~/.openape/nest/agents.json kept \u2014 re-run `apes nest install` to resume supervision.");
|
|
@@ -5353,7 +5391,7 @@ var adapterCommand = defineCommand41({
|
|
|
5353
5391
|
});
|
|
5354
5392
|
|
|
5355
5393
|
// src/commands/run.ts
|
|
5356
|
-
import { execFileSync as
|
|
5394
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
5357
5395
|
import { hostname as hostname5 } from "os";
|
|
5358
5396
|
import { basename } from "path";
|
|
5359
5397
|
import { defineCommand as defineCommand42 } from "citty";
|
|
@@ -5631,7 +5669,7 @@ function execShellCommand(command) {
|
|
|
5631
5669
|
throw new CliError("No command to execute");
|
|
5632
5670
|
try {
|
|
5633
5671
|
const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
|
|
5634
|
-
|
|
5672
|
+
execFileSync13(command[0], command.slice(1), {
|
|
5635
5673
|
stdio: "inherit",
|
|
5636
5674
|
env: inheritedEnv
|
|
5637
5675
|
});
|
|
@@ -5783,7 +5821,7 @@ async function runAudienceMode(audience, action, args) {
|
|
|
5783
5821
|
consola36.info(`Executing: ${command.join(" ")}`);
|
|
5784
5822
|
try {
|
|
5785
5823
|
const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
|
|
5786
|
-
|
|
5824
|
+
execFileSync13(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
|
|
5787
5825
|
stdio: "inherit",
|
|
5788
5826
|
env: inheritedEnv
|
|
5789
5827
|
});
|
|
@@ -6285,7 +6323,7 @@ var mcpCommand = defineCommand48({
|
|
|
6285
6323
|
if (transport !== "stdio" && transport !== "sse") {
|
|
6286
6324
|
throw new Error('Transport must be "stdio" or "sse"');
|
|
6287
6325
|
}
|
|
6288
|
-
const { startMcpServer } = await import("./server-
|
|
6326
|
+
const { startMcpServer } = await import("./server-A24A4ILW.js");
|
|
6289
6327
|
await startMcpServer(transport, port);
|
|
6290
6328
|
}
|
|
6291
6329
|
});
|
|
@@ -6293,7 +6331,7 @@ var mcpCommand = defineCommand48({
|
|
|
6293
6331
|
// src/commands/init/index.ts
|
|
6294
6332
|
import { existsSync as existsSync15, copyFileSync, writeFileSync as writeFileSync9 } from "fs";
|
|
6295
6333
|
import { randomBytes } from "crypto";
|
|
6296
|
-
import { execFileSync as
|
|
6334
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
6297
6335
|
import { join as join14 } from "path";
|
|
6298
6336
|
import { defineCommand as defineCommand49 } from "citty";
|
|
6299
6337
|
import consola40 from "consola";
|
|
@@ -6305,11 +6343,11 @@ async function downloadTemplate(repo, targetDir) {
|
|
|
6305
6343
|
function installDeps(dir) {
|
|
6306
6344
|
const hasLockFile = (name) => existsSync15(join14(dir, name));
|
|
6307
6345
|
if (hasLockFile("pnpm-lock.yaml")) {
|
|
6308
|
-
|
|
6346
|
+
execFileSync14("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
|
|
6309
6347
|
} else if (hasLockFile("bun.lockb")) {
|
|
6310
|
-
|
|
6348
|
+
execFileSync14("bun", ["install"], { cwd: dir, stdio: "inherit" });
|
|
6311
6349
|
} else {
|
|
6312
|
-
|
|
6350
|
+
execFileSync14("npm", ["install"], { cwd: dir, stdio: "inherit" });
|
|
6313
6351
|
}
|
|
6314
6352
|
}
|
|
6315
6353
|
async function promptChoice(message, choices) {
|
|
@@ -6923,7 +6961,7 @@ async function bestEffortGrantCount(idp) {
|
|
|
6923
6961
|
}
|
|
6924
6962
|
}
|
|
6925
6963
|
async function runHealth(args) {
|
|
6926
|
-
const version = true ? "1.
|
|
6964
|
+
const version = true ? "1.15.0" : "0.0.0";
|
|
6927
6965
|
const auth = loadAuth();
|
|
6928
6966
|
if (!auth) {
|
|
6929
6967
|
throw new CliError("Not logged in. Run `apes login` first.", 1);
|
|
@@ -7196,10 +7234,10 @@ if (shellRewrite) {
|
|
|
7196
7234
|
if (shellRewrite.action === "rewrite") {
|
|
7197
7235
|
process.argv = shellRewrite.argv;
|
|
7198
7236
|
} else if (shellRewrite.action === "version") {
|
|
7199
|
-
console.log(`ape-shell ${"1.
|
|
7237
|
+
console.log(`ape-shell ${"1.15.0"} (OpenApe DDISA shell wrapper)`);
|
|
7200
7238
|
process.exit(0);
|
|
7201
7239
|
} else if (shellRewrite.action === "help") {
|
|
7202
|
-
console.log(`ape-shell ${"1.
|
|
7240
|
+
console.log(`ape-shell ${"1.15.0"} \u2014 OpenApe DDISA shell wrapper`);
|
|
7203
7241
|
console.log("");
|
|
7204
7242
|
console.log("Usage:");
|
|
7205
7243
|
console.log(" ape-shell Start interactive grant-mediated REPL");
|
|
@@ -7257,7 +7295,7 @@ var configCommand = defineCommand60({
|
|
|
7257
7295
|
var main = defineCommand60({
|
|
7258
7296
|
meta: {
|
|
7259
7297
|
name: "apes",
|
|
7260
|
-
version: "1.
|
|
7298
|
+
version: "1.15.0",
|
|
7261
7299
|
description: "Unified CLI for OpenApe"
|
|
7262
7300
|
},
|
|
7263
7301
|
subCommands: {
|
|
@@ -7314,7 +7352,7 @@ async function maybeRefreshAuth() {
|
|
|
7314
7352
|
}
|
|
7315
7353
|
}
|
|
7316
7354
|
await maybeRefreshAuth();
|
|
7317
|
-
await maybeWarnStaleVersion("1.
|
|
7355
|
+
await maybeWarnStaleVersion("1.15.0").catch(() => {
|
|
7318
7356
|
});
|
|
7319
7357
|
runMain(main).catch((err) => {
|
|
7320
7358
|
if (err instanceof CliExit) {
|