@openape/apes 0.22.0 → 0.23.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
|
@@ -379,7 +379,7 @@ async function loginWithPKCE(idp) {
|
|
|
379
379
|
consola2.success(`Logged in as ${payload.email || payload.sub}`);
|
|
380
380
|
}
|
|
381
381
|
async function loginWithKey(idp, keyPath, agentEmail) {
|
|
382
|
-
const { readFileSync:
|
|
382
|
+
const { readFileSync: readFileSync9 } = await import("fs");
|
|
383
383
|
const { sign: sign3 } = await import("crypto");
|
|
384
384
|
const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-YBNNG5K5.js");
|
|
385
385
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
@@ -392,7 +392,7 @@ async function loginWithKey(idp, keyPath, agentEmail) {
|
|
|
392
392
|
throw new CliError(`Challenge failed: ${await challengeResp.text()}`);
|
|
393
393
|
}
|
|
394
394
|
const { challenge } = await challengeResp.json();
|
|
395
|
-
const keyContent =
|
|
395
|
+
const keyContent = readFileSync9(keyPath, "utf-8");
|
|
396
396
|
const privateKey = loadEd25519PrivateKey2(keyContent);
|
|
397
397
|
const signature = sign3(null, Buffer2.from(challenge), privateKey).toString("base64");
|
|
398
398
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
@@ -1748,7 +1748,7 @@ import { defineCommand as defineCommand24 } from "citty";
|
|
|
1748
1748
|
// src/commands/agents/destroy.ts
|
|
1749
1749
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1750
1750
|
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
1751
|
-
import { tmpdir } from "os";
|
|
1751
|
+
import { tmpdir, userInfo } from "os";
|
|
1752
1752
|
import { join as join2 } from "path";
|
|
1753
1753
|
import { defineCommand as defineCommand20 } from "citty";
|
|
1754
1754
|
import consola18 from "consola";
|
|
@@ -1873,7 +1873,7 @@ mkdir -p "$HOME_DIR/.ssh" "$HOME_DIR/.config/apes"
|
|
|
1873
1873
|
cat > "$HOME_DIR/.ssh/id_ed25519" ${shHeredoc(privatePemForHeredoc.trimEnd())}
|
|
1874
1874
|
cat > "$HOME_DIR/.ssh/id_ed25519.pub" ${shHeredoc(`${input.publicKeySshLine}`)}
|
|
1875
1875
|
cat > "$HOME_DIR/.config/apes/auth.json" ${shHeredoc(input.authJson)}
|
|
1876
|
-
${claudeBlock}${claudeTokenBlock}
|
|
1876
|
+
${claudeBlock}${claudeTokenBlock}${buildBridgeBlock(input.bridge)}
|
|
1877
1877
|
chown -R "$NAME:staff" "$HOME_DIR"
|
|
1878
1878
|
chmod 700 "$HOME_DIR/.ssh"
|
|
1879
1879
|
chmod 700 "$HOME_DIR/.config"
|
|
@@ -1886,10 +1886,32 @@ if [ -f "$HOME_DIR/.config/openape/claude-token.env" ]; then
|
|
|
1886
1886
|
fi
|
|
1887
1887
|
|
|
1888
1888
|
echo "OK $NAME uid=$NEXT_UID home=$HOME_DIR"
|
|
1889
|
+
${buildBridgeBootstrapBlock(input.bridge)}`;
|
|
1890
|
+
}
|
|
1891
|
+
function buildBridgeBlock(bridge) {
|
|
1892
|
+
if (!bridge) return "";
|
|
1893
|
+
return `
|
|
1894
|
+
mkdir -p "$HOME_DIR/Library/LaunchAgents" "$HOME_DIR/Library/Application Support/openape/bridge" "$HOME_DIR/Library/Logs" "$HOME_DIR/.pi/agent"
|
|
1895
|
+
cat > "$HOME_DIR/.pi/agent/.env" ${shHeredoc(bridge.envFile)}
|
|
1896
|
+
cat > "$HOME_DIR/Library/Application Support/openape/bridge/start.sh" ${shHeredoc(bridge.startScript)}
|
|
1897
|
+
chmod 755 "$HOME_DIR/Library/Application Support/openape/bridge/start.sh"
|
|
1898
|
+
cat > "$HOME_DIR/Library/LaunchAgents/${bridge.plistLabel}.plist" ${shHeredoc(bridge.plistContent)}
|
|
1899
|
+
chmod 600 "$HOME_DIR/.pi/agent/.env"
|
|
1900
|
+
`;
|
|
1901
|
+
}
|
|
1902
|
+
function buildBridgeBootstrapBlock(bridge) {
|
|
1903
|
+
if (!bridge) return "";
|
|
1904
|
+
return `
|
|
1905
|
+
# Load the bridge launchd job into the agent's gui domain. Runs as root
|
|
1906
|
+
# from the spawn setup script so we target gui/<agent-uid> explicitly.
|
|
1907
|
+
# Failure here is non-fatal \u2014 the plist still lands and launchd will pick
|
|
1908
|
+
# it up next time the agent logs in.
|
|
1909
|
+
launchctl bootstrap "gui/$NEXT_UID" "$HOME_DIR/Library/LaunchAgents/${bridge.plistLabel}.plist" || \\
|
|
1910
|
+
echo "warn: bridge bootstrap failed for gui/$NEXT_UID; loads on next login"
|
|
1889
1911
|
`;
|
|
1890
1912
|
}
|
|
1891
1913
|
function buildDestroyTeardownScript(input) {
|
|
1892
|
-
const { name, homeDir } = input;
|
|
1914
|
+
const { name, homeDir, adminUser } = input;
|
|
1893
1915
|
return `#!/bin/bash
|
|
1894
1916
|
# Best-effort teardown. set -u catches typos; we deliberately do NOT use -e
|
|
1895
1917
|
# because pkill / launchctl are allowed to fail when the user has no live
|
|
@@ -1898,6 +1920,16 @@ set -u
|
|
|
1898
1920
|
|
|
1899
1921
|
NAME=${shQuote(name)}
|
|
1900
1922
|
HOME_DIR=${shQuote(homeDir)}
|
|
1923
|
+
ADMIN_USER=${shQuote(adminUser)}
|
|
1924
|
+
|
|
1925
|
+
# Read the admin password from stdin (line 1). The caller pipes it in.
|
|
1926
|
+
# We never accept it as an argv element so it can't show up in process
|
|
1927
|
+
# listings or escapes' audit log.
|
|
1928
|
+
read -r ADMIN_PASSWORD
|
|
1929
|
+
if [ -z "$ADMIN_PASSWORD" ]; then
|
|
1930
|
+
echo "ERROR: no admin password on stdin (expected one line)." >&2
|
|
1931
|
+
exit 2
|
|
1932
|
+
fi
|
|
1901
1933
|
|
|
1902
1934
|
UID_OF=$(dscl . -read "/Users/$NAME" UniqueID 2>/dev/null | awk '/UniqueID:/ {print $2}')
|
|
1903
1935
|
|
|
@@ -1910,25 +1942,32 @@ if [ -d "$HOME_DIR" ] && [ "$HOME_DIR" != "/" ] && [ "$HOME_DIR" != "" ]; then
|
|
|
1910
1942
|
rm -rf "$HOME_DIR"
|
|
1911
1943
|
fi
|
|
1912
1944
|
|
|
1913
|
-
#
|
|
1914
|
-
#
|
|
1915
|
-
#
|
|
1916
|
-
#
|
|
1917
|
-
#
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1945
|
+
# \`escapes\` is a plain setuid binary \u2014 opendirectoryd sees no audit/PAM
|
|
1946
|
+
# session attached (AUDIT_SESSION_ID=unset) and rejects DirectoryService
|
|
1947
|
+
# writes from this context: a bare \`sysadminctl -deleteUser\` or
|
|
1948
|
+
# \`dscl . -delete\` hangs ~5 minutes and exits with eUndefinedError -14987
|
|
1949
|
+
# at DSRecord.m:563. Passing explicit -adminUser/-adminPassword bypasses
|
|
1950
|
+
# opendirectoryd's implicit "is current session admin?" check and
|
|
1951
|
+
# authenticates against DirectoryService directly \u2014 the delete then
|
|
1952
|
+
# completes in ~1 second.
|
|
1953
|
+
if ! command -v sysadminctl >/dev/null 2>&1; then
|
|
1954
|
+
echo "ERROR: sysadminctl not available; cannot delete user record." >&2
|
|
1955
|
+
exit 1
|
|
1956
|
+
fi
|
|
1957
|
+
|
|
1958
|
+
sysadminctl \\
|
|
1959
|
+
-deleteUser "$NAME" \\
|
|
1960
|
+
-adminUser "$ADMIN_USER" \\
|
|
1961
|
+
-adminPassword "$ADMIN_PASSWORD"
|
|
1962
|
+
SYSAD_EC=$?
|
|
1963
|
+
unset ADMIN_PASSWORD
|
|
1964
|
+
|
|
1965
|
+
if [ $SYSAD_EC -ne 0 ]; then
|
|
1966
|
+
echo "ERROR: sysadminctl -deleteUser failed (exit=$SYSAD_EC)." >&2
|
|
1967
|
+
echo " Common causes: wrong admin password, admin user '$ADMIN_USER'" >&2
|
|
1968
|
+
echo " not in admin group, or target user '$NAME' is the last secure" >&2
|
|
1969
|
+
echo " token holder (run \\\`sysadminctl -secureTokenStatus $NAME\\\`)." >&2
|
|
1970
|
+
exit 1
|
|
1932
1971
|
fi
|
|
1933
1972
|
|
|
1934
1973
|
# Verify the record is actually gone.
|
|
@@ -2122,14 +2161,20 @@ ${consequences.join("\n")}`);
|
|
|
2122
2161
|
if (!escapes) {
|
|
2123
2162
|
throw new CliError("`escapes` not found on PATH; OS teardown requires escapes.");
|
|
2124
2163
|
}
|
|
2164
|
+
const adminUser = userInfo().username;
|
|
2165
|
+
const adminPassword = await collectAdminPassword({ adminUser, force: !!args.force });
|
|
2125
2166
|
const scratch = mkdtempSync(join2(tmpdir(), `apes-destroy-${name}-`));
|
|
2126
2167
|
const scriptPath = join2(scratch, "teardown.sh");
|
|
2127
2168
|
try {
|
|
2128
|
-
const script = buildDestroyTeardownScript({ name, homeDir: `/Users/${name}
|
|
2169
|
+
const script = buildDestroyTeardownScript({ name, homeDir: `/Users/${name}`, adminUser });
|
|
2129
2170
|
writeFileSync(scriptPath, script, { mode: 448 });
|
|
2130
2171
|
consola18.start("Running teardown as root via `apes run --as root --wait`\u2026");
|
|
2131
2172
|
consola18.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
|
|
2132
|
-
execFileSync3(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], {
|
|
2173
|
+
execFileSync3(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], {
|
|
2174
|
+
input: `${adminPassword}
|
|
2175
|
+
`,
|
|
2176
|
+
stdio: ["pipe", "inherit", "inherit"]
|
|
2177
|
+
});
|
|
2133
2178
|
} finally {
|
|
2134
2179
|
rmSync(scratch, { recursive: true, force: true });
|
|
2135
2180
|
}
|
|
@@ -2139,6 +2184,21 @@ ${consequences.join("\n")}`);
|
|
|
2139
2184
|
consola18.success(`Destroyed ${name}.`);
|
|
2140
2185
|
}
|
|
2141
2186
|
});
|
|
2187
|
+
async function collectAdminPassword(opts) {
|
|
2188
|
+
const fromEnv = process.env.APES_ADMIN_PASSWORD;
|
|
2189
|
+
if (fromEnv && fromEnv.length > 0) return fromEnv;
|
|
2190
|
+
if (!process.stdin.isTTY) {
|
|
2191
|
+
throw new CliError(
|
|
2192
|
+
`Admin password required for sysadminctl -deleteUser. No TTY available for the silent prompt; set APES_ADMIN_PASSWORD in the environment (local admin password for ${opts.adminUser}). The teardown reads it from stdin and never stores it.`
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
consola18.info(`Local admin password for ${opts.adminUser} (used for sysadminctl -deleteUser; not stored):`);
|
|
2196
|
+
const pw = await consola18.prompt("Admin password", { type: "text", mask: "*" });
|
|
2197
|
+
if (typeof pw === "symbol" || !pw || pw.length === 0) {
|
|
2198
|
+
throw new CliExit(0);
|
|
2199
|
+
}
|
|
2200
|
+
return pw;
|
|
2201
|
+
}
|
|
2142
2202
|
|
|
2143
2203
|
// src/commands/agents/list.ts
|
|
2144
2204
|
import { defineCommand as defineCommand21 } from "citty";
|
|
@@ -2291,7 +2351,7 @@ var registerAgentCommand = defineCommand22({
|
|
|
2291
2351
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
2292
2352
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2293
2353
|
import { tmpdir as tmpdir2 } from "os";
|
|
2294
|
-
import { join as
|
|
2354
|
+
import { join as join4 } from "path";
|
|
2295
2355
|
import { defineCommand as defineCommand23 } from "citty";
|
|
2296
2356
|
import consola21 from "consola";
|
|
2297
2357
|
|
|
@@ -2351,6 +2411,103 @@ function generateKeyPairInMemory() {
|
|
|
2351
2411
|
};
|
|
2352
2412
|
}
|
|
2353
2413
|
|
|
2414
|
+
// src/lib/llm-bridge.ts
|
|
2415
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
2416
|
+
import { homedir as homedir5 } from "os";
|
|
2417
|
+
import { join as join3 } from "path";
|
|
2418
|
+
var BRIDGE_PLIST_LABEL = "eco.hofmann.apes.bridge";
|
|
2419
|
+
function readLitellmEnv(envPath = join3(homedir5(), "litellm", ".env")) {
|
|
2420
|
+
if (!existsSync6(envPath)) return null;
|
|
2421
|
+
try {
|
|
2422
|
+
const text = readFileSync5(envPath, "utf8");
|
|
2423
|
+
const out = {};
|
|
2424
|
+
for (const line of text.split("\n")) {
|
|
2425
|
+
const trimmed = line.trim();
|
|
2426
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2427
|
+
const eq = trimmed.indexOf("=");
|
|
2428
|
+
if (eq < 0) continue;
|
|
2429
|
+
const key = trimmed.slice(0, eq).trim();
|
|
2430
|
+
const value = trimmed.slice(eq + 1).trim();
|
|
2431
|
+
if (key === "LITELLM_MASTER_KEY" || key === "LITELLM_API_KEY") out.apiKey = value;
|
|
2432
|
+
if (key === "LITELLM_BASE_URL") out.baseUrl = value;
|
|
2433
|
+
}
|
|
2434
|
+
return out;
|
|
2435
|
+
} catch {
|
|
2436
|
+
return null;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
function resolveBridgeConfig(opts) {
|
|
2440
|
+
const env = readLitellmEnv(opts.envPath);
|
|
2441
|
+
const apiKey = opts.cliKey ?? env?.apiKey;
|
|
2442
|
+
const baseUrl = opts.cliBaseUrl ?? env?.baseUrl ?? "http://127.0.0.1:4000/v1";
|
|
2443
|
+
if (!apiKey) {
|
|
2444
|
+
throw new Error(
|
|
2445
|
+
"No LITELLM_API_KEY resolved. Pass --bridge-key sk-\u2026 or write LITELLM_MASTER_KEY into ~/litellm/.env first."
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
return { baseUrl, apiKey };
|
|
2449
|
+
}
|
|
2450
|
+
function buildBridgeEnvFile(cfg) {
|
|
2451
|
+
return `# Auto-generated by 'apes agents spawn --bridge'.
|
|
2452
|
+
# Read by the chat-bridge daemon at boot to talk to the local LLM proxy.
|
|
2453
|
+
LITELLM_BASE_URL=${cfg.baseUrl}
|
|
2454
|
+
LITELLM_API_KEY=${cfg.apiKey}
|
|
2455
|
+
`;
|
|
2456
|
+
}
|
|
2457
|
+
function buildBridgeStartScript() {
|
|
2458
|
+
return `#!/usr/bin/env bash
|
|
2459
|
+
# Auto-generated by 'apes agents spawn --bridge'.
|
|
2460
|
+
# Idempotent installer + launcher. First boot installs @openape/chat-bridge
|
|
2461
|
+
# globally via bun (homebrew-provided); subsequent boots skip the install.
|
|
2462
|
+
set -euo pipefail
|
|
2463
|
+
export PATH="/opt/homebrew/bin:$HOME/.bun/bin:$PATH"
|
|
2464
|
+
if ! command -v openape-chat-bridge >/dev/null 2>&1; then
|
|
2465
|
+
bun add -g @openape/chat-bridge
|
|
2466
|
+
fi
|
|
2467
|
+
set -a
|
|
2468
|
+
. "$HOME/.pi/agent/.env"
|
|
2469
|
+
set +a
|
|
2470
|
+
exec openape-chat-bridge
|
|
2471
|
+
`;
|
|
2472
|
+
}
|
|
2473
|
+
function buildBridgePlist(homeDir) {
|
|
2474
|
+
const startScript = `${homeDir}/Library/Application Support/openape/bridge/start.sh`;
|
|
2475
|
+
const stdoutLog = `${homeDir}/Library/Logs/openape-chat-bridge.log`;
|
|
2476
|
+
const stderrLog = `${homeDir}/Library/Logs/openape-chat-bridge.err.log`;
|
|
2477
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2478
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2479
|
+
<plist version="1.0">
|
|
2480
|
+
<dict>
|
|
2481
|
+
<key>Label</key>
|
|
2482
|
+
<string>${BRIDGE_PLIST_LABEL}</string>
|
|
2483
|
+
<key>ProgramArguments</key>
|
|
2484
|
+
<array>
|
|
2485
|
+
<string>${startScript}</string>
|
|
2486
|
+
</array>
|
|
2487
|
+
<key>WorkingDirectory</key>
|
|
2488
|
+
<string>${homeDir}</string>
|
|
2489
|
+
<key>RunAtLoad</key>
|
|
2490
|
+
<true/>
|
|
2491
|
+
<key>KeepAlive</key>
|
|
2492
|
+
<true/>
|
|
2493
|
+
<key>ThrottleInterval</key>
|
|
2494
|
+
<integer>10</integer>
|
|
2495
|
+
<key>StandardOutPath</key>
|
|
2496
|
+
<string>${stdoutLog}</string>
|
|
2497
|
+
<key>StandardErrorPath</key>
|
|
2498
|
+
<string>${stderrLog}</string>
|
|
2499
|
+
<key>EnvironmentVariables</key>
|
|
2500
|
+
<dict>
|
|
2501
|
+
<key>HOME</key>
|
|
2502
|
+
<string>${homeDir}</string>
|
|
2503
|
+
<key>PATH</key>
|
|
2504
|
+
<string>/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
2505
|
+
</dict>
|
|
2506
|
+
</dict>
|
|
2507
|
+
</plist>
|
|
2508
|
+
`;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2354
2511
|
// src/commands/agents/spawn.ts
|
|
2355
2512
|
var spawnAgentCommand = defineCommand23({
|
|
2356
2513
|
meta: {
|
|
@@ -2378,6 +2535,18 @@ var spawnAgentCommand = defineCommand23({
|
|
|
2378
2535
|
"claude-token-stdin": {
|
|
2379
2536
|
type: "boolean",
|
|
2380
2537
|
description: "Read the Claude Code OAuth token from stdin (paranoid form of --claude-token)."
|
|
2538
|
+
},
|
|
2539
|
+
"bridge": {
|
|
2540
|
+
type: "boolean",
|
|
2541
|
+
description: "Install the openape-chat-bridge daemon for this agent: drops a launchd plist that runs `@openape/chat-bridge` so the agent answers chat.openape.ai messages. Reads LITELLM_API_KEY/BASE_URL defaults from ~/litellm/.env; override via --bridge-key / --bridge-base-url."
|
|
2542
|
+
},
|
|
2543
|
+
"bridge-key": {
|
|
2544
|
+
type: "string",
|
|
2545
|
+
description: "Override LITELLM_API_KEY for the bridge (default: read from ~/litellm/.env)."
|
|
2546
|
+
},
|
|
2547
|
+
"bridge-base-url": {
|
|
2548
|
+
type: "string",
|
|
2549
|
+
description: "Override LITELLM_BASE_URL for the bridge (default: read from ~/litellm/.env or http://127.0.0.1:4000/v1)."
|
|
2381
2550
|
}
|
|
2382
2551
|
},
|
|
2383
2552
|
async run({ args }) {
|
|
@@ -2423,8 +2592,8 @@ and try again.`
|
|
|
2423
2592
|
throw new CliError(`macOS user "${name}" already exists (uid=${existing.uid ?? "?"}). Refusing to overwrite.`);
|
|
2424
2593
|
}
|
|
2425
2594
|
const homeDir = `/Users/${name}`;
|
|
2426
|
-
const scratch = mkdtempSync2(
|
|
2427
|
-
const scriptPath =
|
|
2595
|
+
const scratch = mkdtempSync2(join4(tmpdir2(), `apes-spawn-${name}-`));
|
|
2596
|
+
const scriptPath = join4(scratch, "setup.sh");
|
|
2428
2597
|
try {
|
|
2429
2598
|
consola21.start(`Generating keypair for ${name}\u2026`);
|
|
2430
2599
|
const { privatePem, publicSshLine } = generateKeyPairInMemory();
|
|
@@ -2448,6 +2617,18 @@ and try again.`
|
|
|
2448
2617
|
flag: typeof args["claude-token"] === "string" ? args["claude-token"] : void 0,
|
|
2449
2618
|
fromStdin: !!args["claude-token-stdin"]
|
|
2450
2619
|
});
|
|
2620
|
+
const bridge = args.bridge ? (() => {
|
|
2621
|
+
const cfg = resolveBridgeConfig({
|
|
2622
|
+
cliKey: typeof args["bridge-key"] === "string" ? args["bridge-key"] : void 0,
|
|
2623
|
+
cliBaseUrl: typeof args["bridge-base-url"] === "string" ? args["bridge-base-url"] : void 0
|
|
2624
|
+
});
|
|
2625
|
+
return {
|
|
2626
|
+
plistLabel: BRIDGE_PLIST_LABEL,
|
|
2627
|
+
plistContent: buildBridgePlist(homeDir),
|
|
2628
|
+
startScript: buildBridgeStartScript(),
|
|
2629
|
+
envFile: buildBridgeEnvFile(cfg)
|
|
2630
|
+
};
|
|
2631
|
+
})() : null;
|
|
2451
2632
|
const script = buildSpawnSetupScript({
|
|
2452
2633
|
name,
|
|
2453
2634
|
homeDir,
|
|
@@ -2457,7 +2638,8 @@ and try again.`
|
|
|
2457
2638
|
authJson,
|
|
2458
2639
|
claudeSettingsJson: includeClaudeHook ? CLAUDE_SETTINGS_JSON : null,
|
|
2459
2640
|
hookScriptSource: includeClaudeHook ? BASH_VIA_APE_SHELL_HOOK_SOURCE : null,
|
|
2460
|
-
claudeOauthToken
|
|
2641
|
+
claudeOauthToken,
|
|
2642
|
+
bridge
|
|
2461
2643
|
});
|
|
2462
2644
|
writeFileSync3(scriptPath, script, { mode: 448 });
|
|
2463
2645
|
consola21.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
|
|
@@ -3321,7 +3503,7 @@ import { spawn } from "child_process";
|
|
|
3321
3503
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3322
3504
|
import { createRequire } from "module";
|
|
3323
3505
|
import { tmpdir as tmpdir3 } from "os";
|
|
3324
|
-
import { dirname as dirname2, join as
|
|
3506
|
+
import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
|
|
3325
3507
|
var require2 = createRequire(import.meta.url);
|
|
3326
3508
|
function findProxyBin() {
|
|
3327
3509
|
const pkgPath = require2.resolve("@openape/proxy/package.json");
|
|
@@ -3333,8 +3515,8 @@ function findProxyBin() {
|
|
|
3333
3515
|
return resolve3(dirname2(pkgPath), binRel);
|
|
3334
3516
|
}
|
|
3335
3517
|
async function startEphemeralProxy(configToml) {
|
|
3336
|
-
const tmpDir = mkdtempSync3(
|
|
3337
|
-
const configPath =
|
|
3518
|
+
const tmpDir = mkdtempSync3(join5(tmpdir3(), "openape-proxy-"));
|
|
3519
|
+
const configPath = join5(tmpDir, "config.toml");
|
|
3338
3520
|
writeFileSync4(configPath, configToml, { mode: 384 });
|
|
3339
3521
|
const binPath = findProxyBin();
|
|
3340
3522
|
const child = spawn(process.execPath, [binPath, "-c", configPath], {
|
|
@@ -3770,16 +3952,16 @@ var mcpCommand = defineCommand32({
|
|
|
3770
3952
|
if (transport !== "stdio" && transport !== "sse") {
|
|
3771
3953
|
throw new Error('Transport must be "stdio" or "sse"');
|
|
3772
3954
|
}
|
|
3773
|
-
const { startMcpServer } = await import("./server-
|
|
3955
|
+
const { startMcpServer } = await import("./server-D5EQSCWF.js");
|
|
3774
3956
|
await startMcpServer(transport, port);
|
|
3775
3957
|
}
|
|
3776
3958
|
});
|
|
3777
3959
|
|
|
3778
3960
|
// src/commands/init/index.ts
|
|
3779
|
-
import { existsSync as
|
|
3961
|
+
import { existsSync as existsSync7, copyFileSync, writeFileSync as writeFileSync5 } from "fs";
|
|
3780
3962
|
import { randomBytes } from "crypto";
|
|
3781
3963
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
3782
|
-
import { join as
|
|
3964
|
+
import { join as join6 } from "path";
|
|
3783
3965
|
import { defineCommand as defineCommand33 } from "citty";
|
|
3784
3966
|
import consola27 from "consola";
|
|
3785
3967
|
var DEFAULT_IDP_URL = "https://id.openape.at";
|
|
@@ -3788,7 +3970,7 @@ async function downloadTemplate(repo, targetDir) {
|
|
|
3788
3970
|
await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
|
|
3789
3971
|
}
|
|
3790
3972
|
function installDeps(dir) {
|
|
3791
|
-
const hasLockFile = (name) =>
|
|
3973
|
+
const hasLockFile = (name) => existsSync7(join6(dir, name));
|
|
3792
3974
|
if (hasLockFile("pnpm-lock.yaml")) {
|
|
3793
3975
|
execFileSync6("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
|
|
3794
3976
|
} else if (hasLockFile("bun.lockb")) {
|
|
@@ -3853,7 +4035,7 @@ var initCommand = defineCommand33({
|
|
|
3853
4035
|
});
|
|
3854
4036
|
async function initSP(targetDir) {
|
|
3855
4037
|
const dir = targetDir || "my-app";
|
|
3856
|
-
if (
|
|
4038
|
+
if (existsSync7(join6(dir, "package.json"))) {
|
|
3857
4039
|
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
3858
4040
|
}
|
|
3859
4041
|
consola27.start("Scaffolding SP starter...");
|
|
@@ -3862,9 +4044,9 @@ async function initSP(targetDir) {
|
|
|
3862
4044
|
consola27.start("Installing dependencies...");
|
|
3863
4045
|
installDeps(dir);
|
|
3864
4046
|
consola27.success("Dependencies installed");
|
|
3865
|
-
const envExample =
|
|
3866
|
-
const envFile =
|
|
3867
|
-
if (
|
|
4047
|
+
const envExample = join6(dir, ".env.example");
|
|
4048
|
+
const envFile = join6(dir, ".env");
|
|
4049
|
+
if (existsSync7(envExample) && !existsSync7(envFile)) {
|
|
3868
4050
|
copyFileSync(envExample, envFile);
|
|
3869
4051
|
consola27.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
|
|
3870
4052
|
}
|
|
@@ -3878,7 +4060,7 @@ async function initSP(targetDir) {
|
|
|
3878
4060
|
}
|
|
3879
4061
|
async function initIdP(targetDir) {
|
|
3880
4062
|
const dir = targetDir || "my-idp";
|
|
3881
|
-
if (
|
|
4063
|
+
if (existsSync7(join6(dir, "package.json"))) {
|
|
3882
4064
|
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
3883
4065
|
}
|
|
3884
4066
|
const domain = await promptText("Domain for the IdP", "localhost");
|
|
@@ -3910,7 +4092,7 @@ async function initIdP(targetDir) {
|
|
|
3910
4092
|
`NUXT_OPENAPE_RP_ID=${domain}`,
|
|
3911
4093
|
`NUXT_OPENAPE_RP_ORIGIN=${origin}`
|
|
3912
4094
|
].join("\n");
|
|
3913
|
-
writeFileSync5(
|
|
4095
|
+
writeFileSync5(join6(dir, ".env"), `${envContent}
|
|
3914
4096
|
`, { mode: 384 });
|
|
3915
4097
|
consola27.success(".env created");
|
|
3916
4098
|
console.log("");
|
|
@@ -3931,7 +4113,7 @@ async function initIdP(targetDir) {
|
|
|
3931
4113
|
|
|
3932
4114
|
// src/commands/enroll.ts
|
|
3933
4115
|
import { Buffer as Buffer5 } from "buffer";
|
|
3934
|
-
import { existsSync as
|
|
4116
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
3935
4117
|
import { execFile as execFile2 } from "child_process";
|
|
3936
4118
|
import { sign as sign2 } from "crypto";
|
|
3937
4119
|
import { defineCommand as defineCommand34 } from "citty";
|
|
@@ -3947,7 +4129,7 @@ function openBrowser2(url) {
|
|
|
3947
4129
|
}
|
|
3948
4130
|
async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
3949
4131
|
const resolvedKey = resolveKeyPath(keyPath);
|
|
3950
|
-
const keyContent =
|
|
4132
|
+
const keyContent = readFileSync6(resolvedKey, "utf-8");
|
|
3951
4133
|
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
3952
4134
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
3953
4135
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
@@ -4015,7 +4197,7 @@ var enrollCommand = defineCommand34({
|
|
|
4015
4197
|
}) || DEFAULT_KEY_PATH;
|
|
4016
4198
|
const resolvedKey = resolveKeyPath(keyPath);
|
|
4017
4199
|
let publicKey;
|
|
4018
|
-
if (
|
|
4200
|
+
if (existsSync8(resolvedKey)) {
|
|
4019
4201
|
publicKey = readPublicKey(resolvedKey);
|
|
4020
4202
|
consola28.success(`Using existing key ${keyPath}`);
|
|
4021
4203
|
} else {
|
|
@@ -4059,7 +4241,7 @@ var enrollCommand = defineCommand34({
|
|
|
4059
4241
|
});
|
|
4060
4242
|
|
|
4061
4243
|
// src/commands/register-user.ts
|
|
4062
|
-
import { existsSync as
|
|
4244
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
4063
4245
|
import { defineCommand as defineCommand35 } from "citty";
|
|
4064
4246
|
import consola29 from "consola";
|
|
4065
4247
|
var registerUserCommand = defineCommand35({
|
|
@@ -4098,8 +4280,8 @@ var registerUserCommand = defineCommand35({
|
|
|
4098
4280
|
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
4099
4281
|
}
|
|
4100
4282
|
let publicKey = args.key;
|
|
4101
|
-
if (
|
|
4102
|
-
publicKey =
|
|
4283
|
+
if (existsSync9(args.key)) {
|
|
4284
|
+
publicKey = readFileSync7(args.key, "utf-8").trim();
|
|
4103
4285
|
}
|
|
4104
4286
|
if (!publicKey.startsWith("ssh-ed25519 ")) {
|
|
4105
4287
|
throw new CliError("Public key must be in ssh-ed25519 format.");
|
|
@@ -4408,7 +4590,7 @@ async function bestEffortGrantCount(idp) {
|
|
|
4408
4590
|
}
|
|
4409
4591
|
}
|
|
4410
4592
|
async function runHealth(args) {
|
|
4411
|
-
const version = true ? "0.
|
|
4593
|
+
const version = true ? "0.23.0" : "0.0.0";
|
|
4412
4594
|
const auth = loadAuth();
|
|
4413
4595
|
if (!auth) {
|
|
4414
4596
|
throw new CliError("Not logged in. Run `apes login` first.", 1);
|
|
@@ -4601,25 +4783,25 @@ var workflowsCommand = defineCommand43({
|
|
|
4601
4783
|
});
|
|
4602
4784
|
|
|
4603
4785
|
// src/version-check.ts
|
|
4604
|
-
import { existsSync as
|
|
4605
|
-
import { homedir as
|
|
4606
|
-
import { join as
|
|
4786
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
4787
|
+
import { homedir as homedir6 } from "os";
|
|
4788
|
+
import { join as join7 } from "path";
|
|
4607
4789
|
import consola35 from "consola";
|
|
4608
4790
|
var PACKAGE_NAME = "@openape/apes";
|
|
4609
4791
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4610
|
-
var CACHE_FILE =
|
|
4792
|
+
var CACHE_FILE = join7(homedir6(), ".config", "apes", ".version-check.json");
|
|
4611
4793
|
function readCache() {
|
|
4612
|
-
if (!
|
|
4794
|
+
if (!existsSync10(CACHE_FILE)) return null;
|
|
4613
4795
|
try {
|
|
4614
|
-
return JSON.parse(
|
|
4796
|
+
return JSON.parse(readFileSync8(CACHE_FILE, "utf-8"));
|
|
4615
4797
|
} catch {
|
|
4616
4798
|
return null;
|
|
4617
4799
|
}
|
|
4618
4800
|
}
|
|
4619
4801
|
function writeCache(entry) {
|
|
4620
4802
|
try {
|
|
4621
|
-
const dir =
|
|
4622
|
-
if (!
|
|
4803
|
+
const dir = join7(homedir6(), ".config", "apes");
|
|
4804
|
+
if (!existsSync10(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
4623
4805
|
writeFileSync6(CACHE_FILE, JSON.stringify(entry), { mode: 384 });
|
|
4624
4806
|
} catch {
|
|
4625
4807
|
}
|
|
@@ -4681,10 +4863,10 @@ if (shellRewrite) {
|
|
|
4681
4863
|
if (shellRewrite.action === "rewrite") {
|
|
4682
4864
|
process.argv = shellRewrite.argv;
|
|
4683
4865
|
} else if (shellRewrite.action === "version") {
|
|
4684
|
-
console.log(`ape-shell ${"0.
|
|
4866
|
+
console.log(`ape-shell ${"0.23.0"} (OpenApe DDISA shell wrapper)`);
|
|
4685
4867
|
process.exit(0);
|
|
4686
4868
|
} else if (shellRewrite.action === "help") {
|
|
4687
|
-
console.log(`ape-shell ${"0.
|
|
4869
|
+
console.log(`ape-shell ${"0.23.0"} \u2014 OpenApe DDISA shell wrapper`);
|
|
4688
4870
|
console.log("");
|
|
4689
4871
|
console.log("Usage:");
|
|
4690
4872
|
console.log(" ape-shell Start interactive grant-mediated REPL");
|
|
@@ -4742,7 +4924,7 @@ var configCommand = defineCommand44({
|
|
|
4742
4924
|
var main = defineCommand44({
|
|
4743
4925
|
meta: {
|
|
4744
4926
|
name: "apes",
|
|
4745
|
-
version: "0.
|
|
4927
|
+
version: "0.23.0",
|
|
4746
4928
|
description: "Unified CLI for OpenApe"
|
|
4747
4929
|
},
|
|
4748
4930
|
subCommands: {
|
|
@@ -4797,7 +4979,7 @@ async function maybeRefreshAuth() {
|
|
|
4797
4979
|
}
|
|
4798
4980
|
}
|
|
4799
4981
|
await maybeRefreshAuth();
|
|
4800
|
-
await maybeWarnStaleVersion("0.
|
|
4982
|
+
await maybeWarnStaleVersion("0.23.0").catch(() => {
|
|
4801
4983
|
});
|
|
4802
4984
|
runMain(main).catch((err) => {
|
|
4803
4985
|
if (err instanceof CliExit) {
|