@openape/apes 0.14.3 → 0.15.1
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
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
} from "./chunk-6GPSKAMU.js";
|
|
62
62
|
|
|
63
63
|
// src/cli.ts
|
|
64
|
-
import
|
|
64
|
+
import consola32 from "consola";
|
|
65
65
|
|
|
66
66
|
// src/ape-shell.ts
|
|
67
67
|
import path from "path";
|
|
@@ -91,7 +91,7 @@ function rewriteApeShellArgs(argv, argv0) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// src/cli.ts
|
|
94
|
-
import { defineCommand as
|
|
94
|
+
import { defineCommand as defineCommand39, runMain } from "citty";
|
|
95
95
|
|
|
96
96
|
// src/commands/auth/login.ts
|
|
97
97
|
import { Buffer } from "buffer";
|
|
@@ -329,8 +329,8 @@ async function loginWithPKCE(idp) {
|
|
|
329
329
|
consola2.success(`Logged in as ${payload.email || payload.sub}`);
|
|
330
330
|
}
|
|
331
331
|
async function loginWithKey(idp, keyPath, agentEmail) {
|
|
332
|
-
const { readFileSync:
|
|
333
|
-
const { sign:
|
|
332
|
+
const { readFileSync: readFileSync7 } = await import("fs");
|
|
333
|
+
const { sign: sign3 } = await import("crypto");
|
|
334
334
|
const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-YBNNG5K5.js");
|
|
335
335
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
336
336
|
const challengeResp = await fetch(challengeUrl, {
|
|
@@ -342,9 +342,9 @@ async function loginWithKey(idp, keyPath, agentEmail) {
|
|
|
342
342
|
throw new CliError(`Challenge failed: ${await challengeResp.text()}`);
|
|
343
343
|
}
|
|
344
344
|
const { challenge } = await challengeResp.json();
|
|
345
|
-
const keyContent =
|
|
345
|
+
const keyContent = readFileSync7(keyPath, "utf-8");
|
|
346
346
|
const privateKey = loadEd25519PrivateKey2(keyContent);
|
|
347
|
-
const signature =
|
|
347
|
+
const signature = sign3(null, Buffer.from(challenge), privateKey).toString("base64");
|
|
348
348
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
349
349
|
const authResp = await fetch(authenticateUrl, {
|
|
350
350
|
method: "POST",
|
|
@@ -1692,16 +1692,698 @@ var adminCommand = defineCommand19({
|
|
|
1692
1692
|
}
|
|
1693
1693
|
});
|
|
1694
1694
|
|
|
1695
|
-
// src/commands/
|
|
1695
|
+
// src/commands/agents/index.ts
|
|
1696
|
+
import { defineCommand as defineCommand24 } from "citty";
|
|
1697
|
+
|
|
1698
|
+
// src/commands/agents/destroy.ts
|
|
1699
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
1700
|
+
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
1701
|
+
import { tmpdir } from "os";
|
|
1702
|
+
import { join as join2 } from "path";
|
|
1696
1703
|
import { defineCommand as defineCommand20 } from "citty";
|
|
1697
1704
|
import consola18 from "consola";
|
|
1698
|
-
|
|
1705
|
+
|
|
1706
|
+
// src/lib/agent-bootstrap.ts
|
|
1707
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
1708
|
+
import { createPrivateKey, sign } from "crypto";
|
|
1709
|
+
var AGENT_NAME_REGEX = /^[a-z][a-z0-9-]{0,23}$/;
|
|
1710
|
+
var SSH_ED25519_PREFIX = "ssh-ed25519 ";
|
|
1711
|
+
var SSH_ED25519_REGEX = /^ssh-ed25519 [A-Za-z0-9+/=]+(\s.*)?$/;
|
|
1712
|
+
async function registerAgentAtIdp(input) {
|
|
1713
|
+
return await apiFetch("/api/enroll", {
|
|
1714
|
+
method: "POST",
|
|
1715
|
+
body: { name: input.name, publicKey: input.publicKey },
|
|
1716
|
+
idp: input.idp
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
async function issueAgentToken(input) {
|
|
1720
|
+
const privateKey = createPrivateKey(input.privateKeyPem);
|
|
1721
|
+
const challengeUrl = await getAgentChallengeEndpoint(input.idp);
|
|
1722
|
+
const challengeResp = await fetch(challengeUrl, {
|
|
1723
|
+
method: "POST",
|
|
1724
|
+
headers: { "Content-Type": "application/json" },
|
|
1725
|
+
body: JSON.stringify({ agent_id: input.agentEmail })
|
|
1726
|
+
});
|
|
1727
|
+
if (!challengeResp.ok) {
|
|
1728
|
+
const text = await challengeResp.text().catch(() => "");
|
|
1729
|
+
throw new Error(`Challenge failed (${challengeResp.status}): ${text}`);
|
|
1730
|
+
}
|
|
1731
|
+
const { challenge } = await challengeResp.json();
|
|
1732
|
+
const signature = sign(null, Buffer2.from(challenge), privateKey).toString("base64");
|
|
1733
|
+
const authenticateUrl = await getAgentAuthenticateEndpoint(input.idp);
|
|
1734
|
+
const authResp = await fetch(authenticateUrl, {
|
|
1735
|
+
method: "POST",
|
|
1736
|
+
headers: { "Content-Type": "application/json" },
|
|
1737
|
+
body: JSON.stringify({ agent_id: input.agentEmail, challenge, signature })
|
|
1738
|
+
});
|
|
1739
|
+
if (!authResp.ok) {
|
|
1740
|
+
const text = await authResp.text().catch(() => "");
|
|
1741
|
+
throw new Error(`Authenticate failed (${authResp.status}): ${text}`);
|
|
1742
|
+
}
|
|
1743
|
+
const result = await authResp.json();
|
|
1744
|
+
return { token: result.token, expiresIn: result.expires_in || 3600 };
|
|
1745
|
+
}
|
|
1746
|
+
var SH_HEREDOC_DELIMITER = "APES_HEREDOC_END";
|
|
1747
|
+
function shHeredoc(content) {
|
|
1748
|
+
if (content.includes(SH_HEREDOC_DELIMITER)) {
|
|
1749
|
+
throw new Error(`Refusing to emit heredoc: content contains ${SH_HEREDOC_DELIMITER}`);
|
|
1750
|
+
}
|
|
1751
|
+
return `<< '${SH_HEREDOC_DELIMITER}'
|
|
1752
|
+
${content}
|
|
1753
|
+
${SH_HEREDOC_DELIMITER}`;
|
|
1754
|
+
}
|
|
1755
|
+
function buildSpawnSetupScript(input) {
|
|
1756
|
+
const { name, homeDir, shellPath } = input;
|
|
1757
|
+
const privatePemForHeredoc = input.privateKeyPem.endsWith("\n") ? input.privateKeyPem : `${input.privateKeyPem}
|
|
1758
|
+
`;
|
|
1759
|
+
const claudeBlock = input.claudeSettingsJson && input.hookScriptSource ? `
|
|
1760
|
+
mkdir -p "$HOME_DIR/.claude/hooks"
|
|
1761
|
+
cat > "$HOME_DIR/.claude/settings.json" ${shHeredoc(input.claudeSettingsJson)}
|
|
1762
|
+
cat > "$HOME_DIR/.claude/hooks/bash-via-ape-shell.sh" ${shHeredoc(input.hookScriptSource)}
|
|
1763
|
+
chmod 755 "$HOME_DIR/.claude/hooks/bash-via-ape-shell.sh"
|
|
1764
|
+
` : "";
|
|
1765
|
+
return `#!/bin/bash
|
|
1766
|
+
set -euo pipefail
|
|
1767
|
+
|
|
1768
|
+
NAME=${shQuote(name)}
|
|
1769
|
+
HOME_DIR=${shQuote(homeDir)}
|
|
1770
|
+
SHELL_PATH=${shQuote(shellPath)}
|
|
1771
|
+
|
|
1772
|
+
if dscl . -read "/Users/$NAME" >/dev/null 2>&1; then
|
|
1773
|
+
echo "User $NAME already exists; refusing to overwrite." >&2
|
|
1774
|
+
exit 1
|
|
1775
|
+
fi
|
|
1776
|
+
|
|
1777
|
+
# Pick the next free UID in the [200, 500) hidden service-account range.
|
|
1778
|
+
# Starts the running max at 199 so an empty range yields 200 after the
|
|
1779
|
+
# floor check; otherwise NEXT_UID = max(existing in-range UIDs) + 1.
|
|
1780
|
+
NEXT_UID=199
|
|
1781
|
+
for uid in $(dscl . -list /Users UniqueID | awk '$2 >= 200 && $2 < 500 {print $2}'); do
|
|
1782
|
+
if [ "$uid" -ge "$NEXT_UID" ]; then
|
|
1783
|
+
NEXT_UID=$((uid + 1))
|
|
1784
|
+
fi
|
|
1785
|
+
done
|
|
1786
|
+
if [ "$NEXT_UID" -lt 200 ]; then
|
|
1787
|
+
NEXT_UID=200
|
|
1788
|
+
fi
|
|
1789
|
+
if [ "$NEXT_UID" -ge 500 ]; then
|
|
1790
|
+
echo "No free UID in [200, 500) \u2014 refusing to clobber a real user." >&2
|
|
1791
|
+
exit 1
|
|
1792
|
+
fi
|
|
1793
|
+
|
|
1794
|
+
dscl . -create "/Users/$NAME"
|
|
1795
|
+
dscl . -create "/Users/$NAME" UserShell "$SHELL_PATH"
|
|
1796
|
+
dscl . -create "/Users/$NAME" RealName "OpenApe Agent $NAME"
|
|
1797
|
+
dscl . -create "/Users/$NAME" UniqueID "$NEXT_UID"
|
|
1798
|
+
dscl . -create "/Users/$NAME" PrimaryGroupID 20
|
|
1799
|
+
dscl . -create "/Users/$NAME" NFSHomeDirectory "$HOME_DIR"
|
|
1800
|
+
dscl . -create "/Users/$NAME" IsHidden 1
|
|
1801
|
+
|
|
1802
|
+
mkdir -p "$HOME_DIR/.ssh" "$HOME_DIR/.config/apes"
|
|
1803
|
+
|
|
1804
|
+
cat > "$HOME_DIR/.ssh/id_ed25519" ${shHeredoc(privatePemForHeredoc.trimEnd())}
|
|
1805
|
+
cat > "$HOME_DIR/.ssh/id_ed25519.pub" ${shHeredoc(`${input.publicKeySshLine}`)}
|
|
1806
|
+
cat > "$HOME_DIR/.config/apes/auth.json" ${shHeredoc(input.authJson)}
|
|
1807
|
+
${claudeBlock}
|
|
1808
|
+
chown -R "$NAME:staff" "$HOME_DIR"
|
|
1809
|
+
chmod 700 "$HOME_DIR/.ssh"
|
|
1810
|
+
chmod 700 "$HOME_DIR/.config"
|
|
1811
|
+
chmod 600 "$HOME_DIR/.ssh/id_ed25519"
|
|
1812
|
+
chmod 644 "$HOME_DIR/.ssh/id_ed25519.pub"
|
|
1813
|
+
chmod 600 "$HOME_DIR/.config/apes/auth.json"
|
|
1814
|
+
|
|
1815
|
+
echo "OK $NAME uid=$NEXT_UID home=$HOME_DIR"
|
|
1816
|
+
`;
|
|
1817
|
+
}
|
|
1818
|
+
function buildDestroyTeardownScript(input) {
|
|
1819
|
+
const { name, homeDir } = input;
|
|
1820
|
+
return `#!/bin/bash
|
|
1821
|
+
# Best-effort teardown. set -u catches typos; we deliberately do NOT use -e
|
|
1822
|
+
# because pkill / launchctl are allowed to fail when the user has no live
|
|
1823
|
+
# sessions, and dscl -delete is allowed to fail when the user is already gone.
|
|
1824
|
+
set -u
|
|
1825
|
+
|
|
1826
|
+
NAME=${shQuote(name)}
|
|
1827
|
+
HOME_DIR=${shQuote(homeDir)}
|
|
1828
|
+
|
|
1829
|
+
UID_OF=$(dscl . -read "/Users/$NAME" UniqueID 2>/dev/null | awk '/UniqueID:/ {print $2}')
|
|
1830
|
+
|
|
1831
|
+
if [ -n "$UID_OF" ]; then
|
|
1832
|
+
launchctl bootout "user/$UID_OF" 2>/dev/null || true
|
|
1833
|
+
pkill -9 -u "$UID_OF" 2>/dev/null || true
|
|
1834
|
+
fi
|
|
1835
|
+
|
|
1836
|
+
if [ -d "$HOME_DIR" ] && [ "$HOME_DIR" != "/" ] && [ "$HOME_DIR" != "" ]; then
|
|
1837
|
+
rm -rf "$HOME_DIR"
|
|
1838
|
+
fi
|
|
1839
|
+
|
|
1840
|
+
dscl . -delete "/Users/$NAME" 2>/dev/null || true
|
|
1841
|
+
|
|
1842
|
+
echo "OK destroyed $NAME"
|
|
1843
|
+
`;
|
|
1844
|
+
}
|
|
1845
|
+
function shQuote(s) {
|
|
1846
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
1847
|
+
}
|
|
1848
|
+
function buildAgentAuthJson(input) {
|
|
1849
|
+
return `${JSON.stringify({
|
|
1850
|
+
idp: input.idp,
|
|
1851
|
+
access_token: input.accessToken,
|
|
1852
|
+
email: input.email,
|
|
1853
|
+
expires_at: input.expiresAt
|
|
1854
|
+
}, null, 2)}
|
|
1855
|
+
`;
|
|
1856
|
+
}
|
|
1857
|
+
var CLAUDE_SETTINGS_JSON = `${JSON.stringify({
|
|
1858
|
+
hooks: {
|
|
1859
|
+
PreToolUse: [
|
|
1860
|
+
{
|
|
1861
|
+
matcher: "Bash",
|
|
1862
|
+
hooks: [
|
|
1863
|
+
{ type: "command", command: "$HOME/.claude/hooks/bash-via-ape-shell.sh" }
|
|
1864
|
+
]
|
|
1865
|
+
}
|
|
1866
|
+
]
|
|
1867
|
+
}
|
|
1868
|
+
}, null, 2)}
|
|
1869
|
+
`;
|
|
1870
|
+
var BASH_VIA_APE_SHELL_HOOK_SOURCE = `#!/bin/bash
|
|
1871
|
+
# PreToolUse hook for the Bash tool: rewrite the tool input so the
|
|
1872
|
+
# original command runs via \`ape-shell -c <cmd>\`. That re-routes every
|
|
1873
|
+
# Bash invocation through the apes grant flow, so the agent cannot
|
|
1874
|
+
# execute shell commands without an approved grant.
|
|
1875
|
+
exec python3 -c '
|
|
1876
|
+
import json, shlex, sys
|
|
1877
|
+
data = json.load(sys.stdin)
|
|
1878
|
+
cmd = data["tool_input"]["command"]
|
|
1879
|
+
wrapped = "ape-shell -c " + shlex.quote(cmd)
|
|
1880
|
+
out = {"hookSpecificOutput": {"hookEventName": "PreToolUse", "updatedToolInput": {"command": wrapped}}}
|
|
1881
|
+
print(json.dumps(out))
|
|
1882
|
+
'
|
|
1883
|
+
`;
|
|
1884
|
+
|
|
1885
|
+
// src/lib/macos-user.ts
|
|
1886
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1887
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1888
|
+
function isDarwin() {
|
|
1889
|
+
return process.platform === "darwin";
|
|
1890
|
+
}
|
|
1891
|
+
function readMacOSUser(name) {
|
|
1892
|
+
let output;
|
|
1893
|
+
try {
|
|
1894
|
+
output = execFileSync2("dscl", [".", "-read", `/Users/${name}`], {
|
|
1895
|
+
encoding: "utf-8",
|
|
1896
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1897
|
+
});
|
|
1898
|
+
} catch {
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
const uidMatch = output.match(/UniqueID:\s*(\d+)/);
|
|
1902
|
+
const shellMatch = output.match(/UserShell:\s*(\S.*)$/m);
|
|
1903
|
+
return {
|
|
1904
|
+
name,
|
|
1905
|
+
uid: uidMatch ? Number.parseInt(uidMatch[1], 10) : null,
|
|
1906
|
+
shell: shellMatch ? shellMatch[1].trim() : null
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
function listMacOSUserNames() {
|
|
1910
|
+
let output;
|
|
1911
|
+
try {
|
|
1912
|
+
output = execFileSync2("dscl", [".", "-list", "/Users"], {
|
|
1913
|
+
encoding: "utf-8",
|
|
1914
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1915
|
+
});
|
|
1916
|
+
} catch {
|
|
1917
|
+
return /* @__PURE__ */ new Set();
|
|
1918
|
+
}
|
|
1919
|
+
return new Set(
|
|
1920
|
+
output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0)
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
function whichBinary(name) {
|
|
1924
|
+
try {
|
|
1925
|
+
const out = execFileSync2("which", [name], {
|
|
1926
|
+
encoding: "utf-8",
|
|
1927
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1928
|
+
}).trim();
|
|
1929
|
+
return out || null;
|
|
1930
|
+
} catch {
|
|
1931
|
+
return null;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
function isShellRegistered(shellPath) {
|
|
1935
|
+
if (!existsSync3("/etc/shells")) return false;
|
|
1936
|
+
const content = readFileSync2("/etc/shells", "utf-8");
|
|
1937
|
+
return content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).includes(shellPath);
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
// src/commands/agents/destroy.ts
|
|
1941
|
+
var destroyAgentCommand = defineCommand20({
|
|
1942
|
+
meta: {
|
|
1943
|
+
name: "destroy",
|
|
1944
|
+
description: "Tear down an agent: remove macOS user, hard-delete IdP agent, drop all SSH keys"
|
|
1945
|
+
},
|
|
1946
|
+
args: {
|
|
1947
|
+
name: {
|
|
1948
|
+
type: "positional",
|
|
1949
|
+
description: "Agent name to destroy",
|
|
1950
|
+
required: true
|
|
1951
|
+
},
|
|
1952
|
+
force: {
|
|
1953
|
+
type: "boolean",
|
|
1954
|
+
description: "Skip the interactive confirmation. Required for CI."
|
|
1955
|
+
},
|
|
1956
|
+
soft: {
|
|
1957
|
+
type: "boolean",
|
|
1958
|
+
description: "Soft deactivate at the IdP (PATCH isActive=false) instead of hard-delete"
|
|
1959
|
+
},
|
|
1960
|
+
"keep-os-user": {
|
|
1961
|
+
type: "boolean",
|
|
1962
|
+
description: "Skip OS-side teardown. Useful for CI where the agent has no OS user."
|
|
1963
|
+
}
|
|
1964
|
+
},
|
|
1965
|
+
async run({ args }) {
|
|
1966
|
+
const name = args.name;
|
|
1967
|
+
if (!AGENT_NAME_REGEX.test(name)) {
|
|
1968
|
+
throw new CliError(
|
|
1969
|
+
`Invalid agent name "${name}". Must match /^[a-z][a-z0-9-]{0,23}$/.`
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
const auth = loadAuth();
|
|
1973
|
+
if (!auth) {
|
|
1974
|
+
throw new CliError("Not authenticated. Run `apes login` first.");
|
|
1975
|
+
}
|
|
1976
|
+
const idp = getIdpUrl();
|
|
1977
|
+
if (!idp) {
|
|
1978
|
+
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
1979
|
+
}
|
|
1980
|
+
const owned = await apiFetch("/api/my-agents", { idp });
|
|
1981
|
+
const idpAgent = owned.find((u) => u.name === name);
|
|
1982
|
+
const idpExists = idpAgent !== void 0;
|
|
1983
|
+
const osUserExists = !args["keep-os-user"] && isDarwin() && readMacOSUser(name) !== null;
|
|
1984
|
+
if (!idpExists && !osUserExists) {
|
|
1985
|
+
consola18.info(`Nothing to destroy: no IdP agent and no OS user named "${name}".`);
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
if (!args.force) {
|
|
1989
|
+
const consequences = [];
|
|
1990
|
+
if (osUserExists) consequences.push(`\u2022 Remove macOS user ${name} and rm -rf /Users/${name}`);
|
|
1991
|
+
if (idpExists) {
|
|
1992
|
+
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`);
|
|
1993
|
+
}
|
|
1994
|
+
consola18.warn(`About to destroy "${name}":
|
|
1995
|
+
${consequences.join("\n")}`);
|
|
1996
|
+
const confirmed = await consola18.prompt("Proceed?", { type: "confirm", initial: false });
|
|
1997
|
+
if (typeof confirmed === "symbol" || !confirmed) {
|
|
1998
|
+
throw new CliExit(0);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
if (osUserExists) {
|
|
2002
|
+
const apes = whichBinary("apes");
|
|
2003
|
+
if (!apes) {
|
|
2004
|
+
throw new CliError("`apes` not found on PATH. Install @openape/apes globally first.");
|
|
2005
|
+
}
|
|
2006
|
+
const escapes = whichBinary("escapes");
|
|
2007
|
+
if (!escapes) {
|
|
2008
|
+
throw new CliError("`escapes` not found on PATH; OS teardown requires escapes.");
|
|
2009
|
+
}
|
|
2010
|
+
const scratch = mkdtempSync(join2(tmpdir(), `apes-destroy-${name}-`));
|
|
2011
|
+
const scriptPath = join2(scratch, "teardown.sh");
|
|
2012
|
+
try {
|
|
2013
|
+
const script = buildDestroyTeardownScript({ name, homeDir: `/Users/${name}` });
|
|
2014
|
+
writeFileSync(scriptPath, script, { mode: 448 });
|
|
2015
|
+
consola18.start("Running teardown as root via `apes run --as root --wait`\u2026");
|
|
2016
|
+
consola18.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
|
|
2017
|
+
execFileSync3(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
|
|
2018
|
+
} finally {
|
|
2019
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
2020
|
+
}
|
|
2021
|
+
} else if (!args["keep-os-user"] && isDarwin()) {
|
|
2022
|
+
consola18.info("No macOS user to remove (skipped).");
|
|
2023
|
+
}
|
|
2024
|
+
if (idpExists) {
|
|
2025
|
+
const id = encodeURIComponent(idpAgent.email);
|
|
2026
|
+
if (args.soft) {
|
|
2027
|
+
await apiFetch(`/api/my-agents/${id}`, { method: "PATCH", body: { isActive: false }, idp });
|
|
2028
|
+
consola18.success(`Deactivated IdP agent ${idpAgent.email}`);
|
|
2029
|
+
} else {
|
|
2030
|
+
await apiFetch(`/api/my-agents/${id}`, { method: "DELETE", idp });
|
|
2031
|
+
consola18.success(`Deleted IdP agent ${idpAgent.email}`);
|
|
2032
|
+
}
|
|
2033
|
+
} else {
|
|
2034
|
+
consola18.info("No IdP agent to remove (skipped).");
|
|
2035
|
+
}
|
|
2036
|
+
consola18.success(`Destroyed ${name}.`);
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
// src/commands/agents/list.ts
|
|
2041
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
2042
|
+
import consola19 from "consola";
|
|
2043
|
+
var listAgentsCommand = defineCommand21({
|
|
2044
|
+
meta: {
|
|
2045
|
+
name: "list",
|
|
2046
|
+
description: "List agents owned by the current user, with local OS-user status"
|
|
2047
|
+
},
|
|
2048
|
+
args: {
|
|
2049
|
+
json: {
|
|
2050
|
+
type: "boolean",
|
|
2051
|
+
description: "Output as JSON"
|
|
2052
|
+
},
|
|
2053
|
+
"include-inactive": {
|
|
2054
|
+
type: "boolean",
|
|
2055
|
+
description: "Include agents whose IdP record is deactivated (default hides them)"
|
|
2056
|
+
}
|
|
2057
|
+
},
|
|
2058
|
+
async run({ args }) {
|
|
2059
|
+
const auth = loadAuth();
|
|
2060
|
+
if (!auth) {
|
|
2061
|
+
throw new CliError("Not authenticated. Run `apes login` first.");
|
|
2062
|
+
}
|
|
2063
|
+
const idp = getIdpUrl();
|
|
2064
|
+
if (!idp) {
|
|
2065
|
+
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
2066
|
+
}
|
|
2067
|
+
const all = await apiFetch("/api/my-agents", { idp });
|
|
2068
|
+
const filtered = args["include-inactive"] ? all : all.filter((u) => u.isActive !== false);
|
|
2069
|
+
const osUsers = isDarwin() ? listMacOSUserNames() : /* @__PURE__ */ new Set();
|
|
2070
|
+
const home = (name) => `/Users/${name}`;
|
|
2071
|
+
const rows = filtered.map((u) => ({
|
|
2072
|
+
name: u.name,
|
|
2073
|
+
email: u.email,
|
|
2074
|
+
isActive: u.isActive !== false,
|
|
2075
|
+
osUser: osUsers.has(u.name),
|
|
2076
|
+
home: osUsers.has(u.name) ? home(u.name) : null
|
|
2077
|
+
}));
|
|
2078
|
+
if (args.json) {
|
|
2079
|
+
process.stdout.write(`${JSON.stringify(rows, null, 2)}
|
|
2080
|
+
`);
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
if (rows.length === 0) {
|
|
2084
|
+
consola19.info(args["include-inactive"] ? "No agents found." : "No active agents found. Use --include-inactive to show deactivated.");
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
const nameW = Math.max(4, ...rows.map((r) => r.name.length));
|
|
2088
|
+
const emailW = Math.max(5, ...rows.map((r) => r.email.length));
|
|
2089
|
+
const header = `${"NAME".padEnd(nameW)} ${"EMAIL".padEnd(emailW)} ACTIVE OS-USER HOME`;
|
|
2090
|
+
console.log(header);
|
|
2091
|
+
console.log("-".repeat(header.length));
|
|
2092
|
+
for (const r of rows) {
|
|
2093
|
+
const active = r.isActive ? "\u2713" : "\u2717";
|
|
2094
|
+
const os = r.osUser ? "\u2713" : "\u2717";
|
|
2095
|
+
const homeCol = r.home ?? (isDarwin() ? "(missing)" : "(non-darwin)");
|
|
2096
|
+
console.log(`${r.name.padEnd(nameW)} ${r.email.padEnd(emailW)} ${active.padEnd(6)} ${os.padEnd(7)} ${homeCol}`);
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
|
|
2101
|
+
// src/commands/agents/register.ts
|
|
2102
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
2103
|
+
import { defineCommand as defineCommand22 } from "citty";
|
|
2104
|
+
import consola20 from "consola";
|
|
2105
|
+
var registerAgentCommand = defineCommand22({
|
|
2106
|
+
meta: {
|
|
2107
|
+
name: "register",
|
|
2108
|
+
description: "Register an agent at the IdP using a supplied public key"
|
|
2109
|
+
},
|
|
2110
|
+
args: {
|
|
2111
|
+
name: {
|
|
2112
|
+
type: "string",
|
|
2113
|
+
description: "Agent name (lowercase, [a-z0-9-], 1\u201324 chars, must start with a letter)",
|
|
2114
|
+
required: true
|
|
2115
|
+
},
|
|
2116
|
+
"public-key": {
|
|
2117
|
+
type: "string",
|
|
2118
|
+
description: 'Full ssh-ed25519 public key line (e.g. "ssh-ed25519 AAAAC3...")'
|
|
2119
|
+
},
|
|
2120
|
+
"public-key-file": {
|
|
2121
|
+
type: "string",
|
|
2122
|
+
description: "Path to a .pub file containing the ssh-ed25519 line"
|
|
2123
|
+
},
|
|
2124
|
+
json: {
|
|
2125
|
+
type: "boolean",
|
|
2126
|
+
description: "Emit machine-readable JSON instead of a human summary"
|
|
2127
|
+
}
|
|
2128
|
+
},
|
|
2129
|
+
async run({ args }) {
|
|
2130
|
+
const auth = loadAuth();
|
|
2131
|
+
if (!auth) {
|
|
2132
|
+
throw new CliError("Not authenticated. Run `apes login` first.");
|
|
2133
|
+
}
|
|
2134
|
+
const idp = getIdpUrl();
|
|
2135
|
+
if (!idp) {
|
|
2136
|
+
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
2137
|
+
}
|
|
2138
|
+
const name = args.name;
|
|
2139
|
+
if (!AGENT_NAME_REGEX.test(name)) {
|
|
2140
|
+
throw new CliError(
|
|
2141
|
+
`Invalid agent name "${name}". Must match /^[a-z][a-z0-9-]{0,23}$/ \u2014 lowercase letters, digits and hyphens, 1\u201324 chars, must start with a letter.`
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
let publicKey = args["public-key"];
|
|
2145
|
+
const keyFile = args["public-key-file"];
|
|
2146
|
+
if (publicKey && keyFile) {
|
|
2147
|
+
throw new CliError("Pass either --public-key or --public-key-file, not both.");
|
|
2148
|
+
}
|
|
2149
|
+
if (!publicKey && keyFile) {
|
|
2150
|
+
if (!existsSync4(keyFile)) {
|
|
2151
|
+
throw new CliError(`Public-key file not found: ${keyFile}`);
|
|
2152
|
+
}
|
|
2153
|
+
publicKey = readFileSync3(keyFile, "utf-8").trim();
|
|
2154
|
+
}
|
|
2155
|
+
if (!publicKey) {
|
|
2156
|
+
throw new CliError('Provide --public-key "<ssh-ed25519 line>" or --public-key-file <path>.');
|
|
2157
|
+
}
|
|
2158
|
+
if (!publicKey.startsWith(SSH_ED25519_PREFIX) || !SSH_ED25519_REGEX.test(publicKey)) {
|
|
2159
|
+
throw new CliError(
|
|
2160
|
+
'Public key must be a full ssh-ed25519 line, e.g. "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [optional comment]".'
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
const result = await registerAgentAtIdp({ name, publicKey, idp });
|
|
2164
|
+
if (args.json) {
|
|
2165
|
+
process.stdout.write(`${JSON.stringify({
|
|
2166
|
+
email: result.email,
|
|
2167
|
+
name: result.name,
|
|
2168
|
+
owner: result.owner,
|
|
2169
|
+
approver: result.approver,
|
|
2170
|
+
idp
|
|
2171
|
+
})}
|
|
2172
|
+
`);
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
consola20.success("Agent registered.");
|
|
2176
|
+
console.log(` Name: ${result.name}`);
|
|
2177
|
+
console.log(` Email: ${result.email}`);
|
|
2178
|
+
console.log(` IdP: ${idp}`);
|
|
2179
|
+
console.log(` Owner: ${result.owner}`);
|
|
2180
|
+
console.log(` Approver: ${result.approver}`);
|
|
2181
|
+
console.log("");
|
|
2182
|
+
console.log("Tell the agent to log in with:");
|
|
2183
|
+
console.log(` apes login --idp ${idp} --email ${result.email} --key <path-to-matching-private-key>`);
|
|
2184
|
+
}
|
|
2185
|
+
});
|
|
2186
|
+
|
|
2187
|
+
// src/commands/agents/spawn.ts
|
|
2188
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
2189
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2190
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2191
|
+
import { join as join3 } from "path";
|
|
2192
|
+
import { defineCommand as defineCommand23 } from "citty";
|
|
2193
|
+
import consola21 from "consola";
|
|
2194
|
+
|
|
2195
|
+
// src/lib/keygen.ts
|
|
2196
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
2197
|
+
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
2198
|
+
import { generateKeyPairSync } from "crypto";
|
|
2199
|
+
import { homedir as homedir4 } from "os";
|
|
2200
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
2201
|
+
function resolveKeyPath(p) {
|
|
2202
|
+
return resolve2(p.replace(/^~/, homedir4()));
|
|
2203
|
+
}
|
|
2204
|
+
function buildSshEd25519Line(rawPub) {
|
|
2205
|
+
const keyTypeStr = "ssh-ed25519";
|
|
2206
|
+
const keyTypeLen = Buffer3.alloc(4);
|
|
2207
|
+
keyTypeLen.writeUInt32BE(keyTypeStr.length);
|
|
2208
|
+
const pubKeyLen = Buffer3.alloc(4);
|
|
2209
|
+
pubKeyLen.writeUInt32BE(rawPub.length);
|
|
2210
|
+
const blob = Buffer3.concat([keyTypeLen, Buffer3.from(keyTypeStr), pubKeyLen, rawPub]);
|
|
2211
|
+
return `ssh-ed25519 ${blob.toString("base64")}`;
|
|
2212
|
+
}
|
|
2213
|
+
function readPublicKey(keyPath) {
|
|
2214
|
+
const pubPath = `${keyPath}.pub`;
|
|
2215
|
+
if (existsSync5(pubPath)) {
|
|
2216
|
+
return readFileSync4(pubPath, "utf-8").trim();
|
|
2217
|
+
}
|
|
2218
|
+
const keyContent = readFileSync4(keyPath, "utf-8");
|
|
2219
|
+
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
2220
|
+
const jwk = privateKey.export({ format: "jwk" });
|
|
2221
|
+
const pubBytes = Buffer3.from(jwk.x, "base64url");
|
|
2222
|
+
return buildSshEd25519Line(pubBytes);
|
|
2223
|
+
}
|
|
2224
|
+
function generateAndSaveKey(keyPath) {
|
|
2225
|
+
const resolved = resolveKeyPath(keyPath);
|
|
2226
|
+
const dir = dirname(resolved);
|
|
2227
|
+
if (!existsSync5(dir)) {
|
|
2228
|
+
mkdirSync(dir, { recursive: true });
|
|
2229
|
+
}
|
|
2230
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
2231
|
+
const privatePem = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
2232
|
+
writeFileSync2(resolved, privatePem, { mode: 384 });
|
|
2233
|
+
const jwk = publicKey.export({ format: "jwk" });
|
|
2234
|
+
const pubBytes = Buffer3.from(jwk.x, "base64url");
|
|
2235
|
+
const pubKeyStr = buildSshEd25519Line(pubBytes);
|
|
2236
|
+
writeFileSync2(`${resolved}.pub`, `${pubKeyStr}
|
|
2237
|
+
`, { mode: 420 });
|
|
2238
|
+
return pubKeyStr;
|
|
2239
|
+
}
|
|
2240
|
+
function generateKeyPairInMemory() {
|
|
2241
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
2242
|
+
const privatePem = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
2243
|
+
const jwk = publicKey.export({ format: "jwk" });
|
|
2244
|
+
const pubBytes = Buffer3.from(jwk.x, "base64url");
|
|
2245
|
+
return {
|
|
2246
|
+
privatePem,
|
|
2247
|
+
publicSshLine: buildSshEd25519Line(pubBytes)
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
// src/commands/agents/spawn.ts
|
|
2252
|
+
var spawnAgentCommand = defineCommand23({
|
|
2253
|
+
meta: {
|
|
2254
|
+
name: "spawn",
|
|
2255
|
+
description: "Provision a local macOS agent end-to-end (OS user, keypair, IdP agent, ape-shell, Claude hook)"
|
|
2256
|
+
},
|
|
2257
|
+
args: {
|
|
2258
|
+
name: {
|
|
2259
|
+
type: "positional",
|
|
2260
|
+
description: "Agent name \u2014 also the macOS short username (lowercase, [a-z0-9-], must start with a letter)",
|
|
2261
|
+
required: true
|
|
2262
|
+
},
|
|
2263
|
+
shell: {
|
|
2264
|
+
type: "string",
|
|
2265
|
+
description: "Override login shell. Default: $(which ape-shell)"
|
|
2266
|
+
},
|
|
2267
|
+
"no-claude-hook": {
|
|
2268
|
+
type: "boolean",
|
|
2269
|
+
description: "Skip writing ~/.claude/settings.json + the Bash-rewrite hook"
|
|
2270
|
+
}
|
|
2271
|
+
},
|
|
2272
|
+
async run({ args }) {
|
|
2273
|
+
const name = args.name;
|
|
2274
|
+
if (!AGENT_NAME_REGEX.test(name)) {
|
|
2275
|
+
throw new CliError(
|
|
2276
|
+
`Invalid agent name "${name}". Must match /^[a-z][a-z0-9-]{0,23}$/ \u2014 lowercase letters, digits and hyphens, 1\u201324 chars, must start with a letter.`
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
if (!isDarwin()) {
|
|
2280
|
+
throw new CliError(
|
|
2281
|
+
`\`apes agents spawn\` is currently macOS-only. Detected platform: ${process.platform}. Linux support is a follow-up; for now, use \`apes agents register\` plus a manually provisioned user.`
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
const auth = loadAuth();
|
|
2285
|
+
if (!auth) {
|
|
2286
|
+
throw new CliError("Not authenticated. Run `apes login` first.");
|
|
2287
|
+
}
|
|
2288
|
+
const idp = getIdpUrl();
|
|
2289
|
+
if (!idp) {
|
|
2290
|
+
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
2291
|
+
}
|
|
2292
|
+
const apeShell = args.shell ?? whichBinary("ape-shell");
|
|
2293
|
+
if (!apeShell) {
|
|
2294
|
+
throw new CliError("`ape-shell` not found on PATH. Install @openape/apes globally first.");
|
|
2295
|
+
}
|
|
2296
|
+
const apes = whichBinary("apes");
|
|
2297
|
+
if (!apes) {
|
|
2298
|
+
throw new CliError("`apes` not found on PATH. Install @openape/apes globally first.");
|
|
2299
|
+
}
|
|
2300
|
+
const escapes = whichBinary("escapes");
|
|
2301
|
+
if (!escapes) {
|
|
2302
|
+
throw new CliError(
|
|
2303
|
+
"`escapes` not found on PATH. spawn delegates the privileged setup phase to escapes; install it before running spawn."
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
if (!isShellRegistered(apeShell)) {
|
|
2307
|
+
throw new CliError(
|
|
2308
|
+
`${apeShell} is not registered in /etc/shells. macOS refuses to set it as a login shell. Run:
|
|
2309
|
+
echo ${apeShell} | sudo tee -a /etc/shells
|
|
2310
|
+
and try again.`
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2313
|
+
const existing = readMacOSUser(name);
|
|
2314
|
+
if (existing) {
|
|
2315
|
+
throw new CliError(`macOS user "${name}" already exists (uid=${existing.uid ?? "?"}). Refusing to overwrite.`);
|
|
2316
|
+
}
|
|
2317
|
+
const homeDir = `/Users/${name}`;
|
|
2318
|
+
const scratch = mkdtempSync2(join3(tmpdir2(), `apes-spawn-${name}-`));
|
|
2319
|
+
const scriptPath = join3(scratch, "setup.sh");
|
|
2320
|
+
try {
|
|
2321
|
+
consola21.start(`Generating keypair for ${name}\u2026`);
|
|
2322
|
+
const { privatePem, publicSshLine } = generateKeyPairInMemory();
|
|
2323
|
+
consola21.start(`Registering agent at ${idp}\u2026`);
|
|
2324
|
+
const registration = await registerAgentAtIdp({ name, publicKey: publicSshLine, idp });
|
|
2325
|
+
consola21.success(`Registered as ${registration.email}`);
|
|
2326
|
+
consola21.start("Issuing agent access token\u2026");
|
|
2327
|
+
const { token, expiresIn } = await issueAgentToken({
|
|
2328
|
+
idp,
|
|
2329
|
+
agentEmail: registration.email,
|
|
2330
|
+
privateKeyPem: privatePem
|
|
2331
|
+
});
|
|
2332
|
+
const authJson = buildAgentAuthJson({
|
|
2333
|
+
idp,
|
|
2334
|
+
accessToken: token,
|
|
2335
|
+
email: registration.email,
|
|
2336
|
+
expiresAt: Math.floor(Date.now() / 1e3) + expiresIn
|
|
2337
|
+
});
|
|
2338
|
+
const includeClaudeHook = !args["no-claude-hook"];
|
|
2339
|
+
const script = buildSpawnSetupScript({
|
|
2340
|
+
name,
|
|
2341
|
+
homeDir,
|
|
2342
|
+
shellPath: apeShell,
|
|
2343
|
+
privateKeyPem: privatePem,
|
|
2344
|
+
publicKeySshLine: publicSshLine,
|
|
2345
|
+
authJson,
|
|
2346
|
+
claudeSettingsJson: includeClaudeHook ? CLAUDE_SETTINGS_JSON : null,
|
|
2347
|
+
hookScriptSource: includeClaudeHook ? BASH_VIA_APE_SHELL_HOOK_SOURCE : null
|
|
2348
|
+
});
|
|
2349
|
+
writeFileSync3(scriptPath, script, { mode: 448 });
|
|
2350
|
+
consola21.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
|
|
2351
|
+
consola21.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
|
|
2352
|
+
execFileSync4(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
|
|
2353
|
+
consola21.success(`Agent ${name} spawned.`);
|
|
2354
|
+
console.log("");
|
|
2355
|
+
console.log("Run as the agent with:");
|
|
2356
|
+
console.log(` apes run --as ${name} -- claude --session-name ${name} --dangerously-skip-permissions`);
|
|
2357
|
+
} finally {
|
|
2358
|
+
rmSync2(scratch, { recursive: true, force: true });
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2362
|
+
|
|
2363
|
+
// src/commands/agents/index.ts
|
|
2364
|
+
var agentsCommand = defineCommand24({
|
|
2365
|
+
meta: {
|
|
2366
|
+
name: "agents",
|
|
2367
|
+
description: "Manage owned agents (register, spawn, list, destroy)"
|
|
2368
|
+
},
|
|
2369
|
+
subCommands: {
|
|
2370
|
+
register: registerAgentCommand,
|
|
2371
|
+
spawn: spawnAgentCommand,
|
|
2372
|
+
list: listAgentsCommand,
|
|
2373
|
+
destroy: destroyAgentCommand
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
|
|
2377
|
+
// src/commands/adapter/index.ts
|
|
2378
|
+
import { defineCommand as defineCommand25 } from "citty";
|
|
2379
|
+
import consola22 from "consola";
|
|
2380
|
+
var adapterCommand = defineCommand25({
|
|
1699
2381
|
meta: {
|
|
1700
2382
|
name: "adapter",
|
|
1701
2383
|
description: "Manage CLI adapters"
|
|
1702
2384
|
},
|
|
1703
2385
|
subCommands: {
|
|
1704
|
-
list:
|
|
2386
|
+
list: defineCommand25({
|
|
1705
2387
|
meta: {
|
|
1706
2388
|
name: "list",
|
|
1707
2389
|
description: "List available adapters"
|
|
@@ -1732,7 +2414,7 @@ var adapterCommand = defineCommand20({
|
|
|
1732
2414
|
`);
|
|
1733
2415
|
return;
|
|
1734
2416
|
}
|
|
1735
|
-
|
|
2417
|
+
consola22.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
|
|
1736
2418
|
for (const a of index2.adapters) {
|
|
1737
2419
|
const installed = isInstalled(a.id, false) ? " [installed]" : "";
|
|
1738
2420
|
console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
|
|
@@ -1754,7 +2436,7 @@ var adapterCommand = defineCommand20({
|
|
|
1754
2436
|
return;
|
|
1755
2437
|
}
|
|
1756
2438
|
if (local.length === 0) {
|
|
1757
|
-
|
|
2439
|
+
consola22.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
|
|
1758
2440
|
return;
|
|
1759
2441
|
}
|
|
1760
2442
|
for (const a of local) {
|
|
@@ -1762,7 +2444,7 @@ var adapterCommand = defineCommand20({
|
|
|
1762
2444
|
}
|
|
1763
2445
|
}
|
|
1764
2446
|
}),
|
|
1765
|
-
install:
|
|
2447
|
+
install: defineCommand25({
|
|
1766
2448
|
meta: {
|
|
1767
2449
|
name: "install",
|
|
1768
2450
|
description: "Install an adapter from the registry"
|
|
@@ -1791,24 +2473,24 @@ var adapterCommand = defineCommand20({
|
|
|
1791
2473
|
for (const id of ids) {
|
|
1792
2474
|
const entry = findAdapter(index, id);
|
|
1793
2475
|
if (!entry) {
|
|
1794
|
-
|
|
2476
|
+
consola22.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
|
|
1795
2477
|
continue;
|
|
1796
2478
|
}
|
|
1797
2479
|
const conflicts = findConflictingAdapters(entry.executable, id);
|
|
1798
2480
|
if (conflicts.length > 0) {
|
|
1799
2481
|
for (const c of conflicts) {
|
|
1800
|
-
|
|
1801
|
-
|
|
2482
|
+
consola22.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
|
|
2483
|
+
consola22.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
|
|
1802
2484
|
}
|
|
1803
2485
|
}
|
|
1804
2486
|
const result = await installAdapter(entry, { local });
|
|
1805
2487
|
const verb = result.updated ? "Updated" : "Installed";
|
|
1806
|
-
|
|
1807
|
-
|
|
2488
|
+
consola22.success(`${verb} ${result.id} \u2192 ${result.path}`);
|
|
2489
|
+
consola22.info(`Digest: ${result.digest}`);
|
|
1808
2490
|
}
|
|
1809
2491
|
}
|
|
1810
2492
|
}),
|
|
1811
|
-
remove:
|
|
2493
|
+
remove: defineCommand25({
|
|
1812
2494
|
meta: {
|
|
1813
2495
|
name: "remove",
|
|
1814
2496
|
description: "Remove an installed adapter"
|
|
@@ -1831,9 +2513,9 @@ var adapterCommand = defineCommand20({
|
|
|
1831
2513
|
let failed = false;
|
|
1832
2514
|
for (const id of ids) {
|
|
1833
2515
|
if (removeAdapter(id, local)) {
|
|
1834
|
-
|
|
2516
|
+
consola22.success(`Removed adapter: ${id}`);
|
|
1835
2517
|
} else {
|
|
1836
|
-
|
|
2518
|
+
consola22.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
|
|
1837
2519
|
failed = true;
|
|
1838
2520
|
}
|
|
1839
2521
|
}
|
|
@@ -1841,7 +2523,7 @@ var adapterCommand = defineCommand20({
|
|
|
1841
2523
|
throw new CliError("Some adapters could not be removed");
|
|
1842
2524
|
}
|
|
1843
2525
|
}),
|
|
1844
|
-
info:
|
|
2526
|
+
info: defineCommand25({
|
|
1845
2527
|
meta: {
|
|
1846
2528
|
name: "info",
|
|
1847
2529
|
description: "Show detailed adapter information"
|
|
@@ -1883,7 +2565,7 @@ var adapterCommand = defineCommand20({
|
|
|
1883
2565
|
}
|
|
1884
2566
|
}
|
|
1885
2567
|
}),
|
|
1886
|
-
search:
|
|
2568
|
+
search: defineCommand25({
|
|
1887
2569
|
meta: {
|
|
1888
2570
|
name: "search",
|
|
1889
2571
|
description: "Search adapters in the registry"
|
|
@@ -1915,7 +2597,7 @@ var adapterCommand = defineCommand20({
|
|
|
1915
2597
|
return;
|
|
1916
2598
|
}
|
|
1917
2599
|
if (results.length === 0) {
|
|
1918
|
-
|
|
2600
|
+
consola22.info(`No adapters matching "${query}"`);
|
|
1919
2601
|
return;
|
|
1920
2602
|
}
|
|
1921
2603
|
for (const a of results) {
|
|
@@ -1924,7 +2606,7 @@ var adapterCommand = defineCommand20({
|
|
|
1924
2606
|
}
|
|
1925
2607
|
}
|
|
1926
2608
|
}),
|
|
1927
|
-
update:
|
|
2609
|
+
update: defineCommand25({
|
|
1928
2610
|
meta: {
|
|
1929
2611
|
name: "update",
|
|
1930
2612
|
description: "Update installed adapters"
|
|
@@ -1950,33 +2632,33 @@ var adapterCommand = defineCommand20({
|
|
|
1950
2632
|
const targetId = args.id ? String(args.id) : void 0;
|
|
1951
2633
|
const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
|
|
1952
2634
|
if (targets.length === 0) {
|
|
1953
|
-
|
|
2635
|
+
consola22.info("No adapters installed to update.");
|
|
1954
2636
|
return;
|
|
1955
2637
|
}
|
|
1956
2638
|
for (const id of targets) {
|
|
1957
2639
|
const entry = findAdapter(index, id);
|
|
1958
2640
|
if (!entry) {
|
|
1959
|
-
|
|
2641
|
+
consola22.warn(`${id}: not found in registry, skipping`);
|
|
1960
2642
|
continue;
|
|
1961
2643
|
}
|
|
1962
2644
|
const localDigest = getInstalledDigest(id, false);
|
|
1963
2645
|
if (localDigest === entry.digest) {
|
|
1964
|
-
|
|
2646
|
+
consola22.info(`${id}: already up to date`);
|
|
1965
2647
|
continue;
|
|
1966
2648
|
}
|
|
1967
2649
|
if (localDigest && !args.yes) {
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
2650
|
+
consola22.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
|
|
2651
|
+
consola22.info(` Old: ${localDigest}`);
|
|
2652
|
+
consola22.info(` New: ${entry.digest}`);
|
|
2653
|
+
consola22.info(" Use --yes to confirm");
|
|
1972
2654
|
continue;
|
|
1973
2655
|
}
|
|
1974
2656
|
const result = await installAdapter(entry);
|
|
1975
|
-
|
|
2657
|
+
consola22.success(`Updated ${result.id} \u2192 ${result.path}`);
|
|
1976
2658
|
}
|
|
1977
2659
|
}
|
|
1978
2660
|
}),
|
|
1979
|
-
verify:
|
|
2661
|
+
verify: defineCommand25({
|
|
1980
2662
|
meta: {
|
|
1981
2663
|
name: "verify",
|
|
1982
2664
|
description: "Verify installed adapter against registry digest"
|
|
@@ -2009,7 +2691,7 @@ var adapterCommand = defineCommand20({
|
|
|
2009
2691
|
if (!localDigest)
|
|
2010
2692
|
throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
|
|
2011
2693
|
if (localDigest === entry.digest) {
|
|
2012
|
-
|
|
2694
|
+
consola22.success(`${id}: digest matches registry`);
|
|
2013
2695
|
} else {
|
|
2014
2696
|
console.log(` Local: ${localDigest}`);
|
|
2015
2697
|
console.log(` Registry: ${entry.digest}`);
|
|
@@ -2021,11 +2703,11 @@ var adapterCommand = defineCommand20({
|
|
|
2021
2703
|
});
|
|
2022
2704
|
|
|
2023
2705
|
// src/commands/run.ts
|
|
2024
|
-
import { execFileSync as
|
|
2706
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
2025
2707
|
import { hostname as hostname3 } from "os";
|
|
2026
2708
|
import { basename } from "path";
|
|
2027
|
-
import { defineCommand as
|
|
2028
|
-
import
|
|
2709
|
+
import { defineCommand as defineCommand26 } from "citty";
|
|
2710
|
+
import consola23 from "consola";
|
|
2029
2711
|
function shouldWaitForGrant(args) {
|
|
2030
2712
|
return args.wait === true || process.env.APE_WAIT === "1";
|
|
2031
2713
|
}
|
|
@@ -2062,7 +2744,7 @@ function printPendingGrantInfo(grant, idp) {
|
|
|
2062
2744
|
const statusCmd = `apes grants status ${grant.id}`;
|
|
2063
2745
|
const executeCmd = `apes grants run ${grant.id}`;
|
|
2064
2746
|
if (mode === "human") {
|
|
2065
|
-
|
|
2747
|
+
consola23.success(`Grant ${grant.id} created \u2014 awaiting your approval`);
|
|
2066
2748
|
console.log(` Approve in browser: ${approveUrl}`);
|
|
2067
2749
|
console.log(` Check status: ${statusCmd}`);
|
|
2068
2750
|
console.log(` Run after approval: ${executeCmd}`);
|
|
@@ -2072,7 +2754,7 @@ function printPendingGrantInfo(grant, idp) {
|
|
|
2072
2754
|
return;
|
|
2073
2755
|
}
|
|
2074
2756
|
const maxMin = getPollMaxMinutes();
|
|
2075
|
-
|
|
2757
|
+
consola23.success(`Grant ${grant.id} created (pending approval)`);
|
|
2076
2758
|
console.log(` Approve: ${approveUrl}`);
|
|
2077
2759
|
console.log(` Status: ${statusCmd} [--json]`);
|
|
2078
2760
|
console.log(` Execute: ${executeCmd} --wait`);
|
|
@@ -2094,7 +2776,7 @@ function printPendingGrantInfo(grant, idp) {
|
|
|
2094
2776
|
console.log(' Tip: Approve as "timed" or "always" in the browser to let this');
|
|
2095
2777
|
console.log(" grant be reused on subsequent invocations without re-approval.");
|
|
2096
2778
|
}
|
|
2097
|
-
var runCommand =
|
|
2779
|
+
var runCommand = defineCommand26({
|
|
2098
2780
|
meta: {
|
|
2099
2781
|
name: "run",
|
|
2100
2782
|
description: "Execute a grant-secured command"
|
|
@@ -2197,7 +2879,7 @@ async function runShellMode(command, args) {
|
|
|
2197
2879
|
}
|
|
2198
2880
|
} catch {
|
|
2199
2881
|
}
|
|
2200
|
-
|
|
2882
|
+
consola23.info(`Requesting ape-shell session grant on ${targetHost}`);
|
|
2201
2883
|
const grant = await apiFetch(grantsUrl, {
|
|
2202
2884
|
method: "POST",
|
|
2203
2885
|
body: {
|
|
@@ -2217,8 +2899,8 @@ async function runShellMode(command, args) {
|
|
|
2217
2899
|
host: targetHost
|
|
2218
2900
|
});
|
|
2219
2901
|
if (shouldWaitForGrant(args)) {
|
|
2220
|
-
|
|
2221
|
-
|
|
2902
|
+
consola23.info(`Grant requested: ${grant.id}`);
|
|
2903
|
+
consola23.info("Waiting for approval...");
|
|
2222
2904
|
const maxWait = 3e5;
|
|
2223
2905
|
const interval = 3e3;
|
|
2224
2906
|
const start = Date.now();
|
|
@@ -2249,13 +2931,13 @@ async function tryAdapterModeFromShell(command, idp, args) {
|
|
|
2249
2931
|
try {
|
|
2250
2932
|
resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv]);
|
|
2251
2933
|
} catch (err) {
|
|
2252
|
-
|
|
2934
|
+
consola23.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
|
|
2253
2935
|
return false;
|
|
2254
2936
|
}
|
|
2255
2937
|
try {
|
|
2256
2938
|
const existingGrantId = await findExistingGrant(resolved, idp);
|
|
2257
2939
|
if (existingGrantId) {
|
|
2258
|
-
|
|
2940
|
+
consola23.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
|
|
2259
2941
|
const token = await fetchGrantToken(idp, existingGrantId);
|
|
2260
2942
|
await verifyAndExecute(token, resolved, existingGrantId);
|
|
2261
2943
|
return true;
|
|
@@ -2263,7 +2945,7 @@ async function tryAdapterModeFromShell(command, idp, args) {
|
|
|
2263
2945
|
} catch {
|
|
2264
2946
|
}
|
|
2265
2947
|
const approval = args.approval ?? "once";
|
|
2266
|
-
|
|
2948
|
+
consola23.info(`Requesting grant for: ${resolved.detail.display}`);
|
|
2267
2949
|
const grant = await createShapesGrant(resolved, {
|
|
2268
2950
|
idp,
|
|
2269
2951
|
approval,
|
|
@@ -2271,8 +2953,8 @@ async function tryAdapterModeFromShell(command, idp, args) {
|
|
|
2271
2953
|
});
|
|
2272
2954
|
if (grant.similar_grants?.similar_grants?.length) {
|
|
2273
2955
|
const n = grant.similar_grants.similar_grants.length;
|
|
2274
|
-
|
|
2275
|
-
|
|
2956
|
+
consola23.info("");
|
|
2957
|
+
consola23.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
|
|
2276
2958
|
}
|
|
2277
2959
|
notifyGrantPending({
|
|
2278
2960
|
grantId: grant.id,
|
|
@@ -2282,8 +2964,8 @@ async function tryAdapterModeFromShell(command, idp, args) {
|
|
|
2282
2964
|
host: args.host || hostname3()
|
|
2283
2965
|
});
|
|
2284
2966
|
if (shouldWaitForGrant(args)) {
|
|
2285
|
-
|
|
2286
|
-
|
|
2967
|
+
consola23.info(`Grant requested: ${grant.id}`);
|
|
2968
|
+
consola23.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
|
|
2287
2969
|
const status = await waitForGrantStatus(idp, grant.id);
|
|
2288
2970
|
if (status !== "approved")
|
|
2289
2971
|
throw new CliError(`Grant ${status}`);
|
|
@@ -2299,7 +2981,7 @@ function execShellCommand(command) {
|
|
|
2299
2981
|
throw new CliError("No command to execute");
|
|
2300
2982
|
try {
|
|
2301
2983
|
const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
|
|
2302
|
-
|
|
2984
|
+
execFileSync5(command[0], command.slice(1), {
|
|
2303
2985
|
stdio: "inherit",
|
|
2304
2986
|
env: inheritedEnv
|
|
2305
2987
|
});
|
|
@@ -2357,7 +3039,7 @@ async function runAdapterMode(command, rawArgs, args) {
|
|
|
2357
3039
|
try {
|
|
2358
3040
|
const existingGrantId = await findExistingGrant(resolved, idp);
|
|
2359
3041
|
if (existingGrantId) {
|
|
2360
|
-
|
|
3042
|
+
consola23.info(`Reusing existing grant: ${existingGrantId}`);
|
|
2361
3043
|
const token = await fetchGrantToken(idp, existingGrantId);
|
|
2362
3044
|
await verifyAndExecute(token, resolved, existingGrantId);
|
|
2363
3045
|
return;
|
|
@@ -2371,17 +3053,17 @@ async function runAdapterMode(command, rawArgs, args) {
|
|
|
2371
3053
|
});
|
|
2372
3054
|
if (grant.similar_grants?.similar_grants?.length) {
|
|
2373
3055
|
const n = grant.similar_grants.similar_grants.length;
|
|
2374
|
-
|
|
2375
|
-
|
|
3056
|
+
consola23.info("");
|
|
3057
|
+
consola23.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
|
|
2376
3058
|
if (grant.similar_grants.widened_details?.length) {
|
|
2377
3059
|
const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
|
|
2378
|
-
|
|
3060
|
+
consola23.info(` Broader scope: ${wider}`);
|
|
2379
3061
|
}
|
|
2380
|
-
|
|
3062
|
+
consola23.info("");
|
|
2381
3063
|
}
|
|
2382
3064
|
if (shouldWaitForGrant(args)) {
|
|
2383
|
-
|
|
2384
|
-
|
|
3065
|
+
consola23.info(`Grant requested: ${grant.id}`);
|
|
3066
|
+
consola23.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
|
|
2385
3067
|
const status = await waitForGrantStatus(idp, grant.id);
|
|
2386
3068
|
if (status !== "approved")
|
|
2387
3069
|
throw new Error(`Grant ${status}`);
|
|
@@ -2401,7 +3083,7 @@ async function runAudienceMode(audience, action, args) {
|
|
|
2401
3083
|
const grantsUrl = await getGrantsEndpoint(idp);
|
|
2402
3084
|
const command = action.split(" ");
|
|
2403
3085
|
const targetHost = args.host || hostname3();
|
|
2404
|
-
|
|
3086
|
+
consola23.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
|
|
2405
3087
|
const grant = await apiFetch(grantsUrl, {
|
|
2406
3088
|
method: "POST",
|
|
2407
3089
|
body: {
|
|
@@ -2418,15 +3100,15 @@ async function runAudienceMode(audience, action, args) {
|
|
|
2418
3100
|
printPendingGrantInfo(grant, idp);
|
|
2419
3101
|
throw new CliExit(getAsyncExitCode());
|
|
2420
3102
|
}
|
|
2421
|
-
|
|
2422
|
-
|
|
3103
|
+
consola23.success(`Grant requested: ${grant.id}`);
|
|
3104
|
+
consola23.info("Waiting for approval...");
|
|
2423
3105
|
const maxWait = 3e5;
|
|
2424
3106
|
const interval = 3e3;
|
|
2425
3107
|
const start = Date.now();
|
|
2426
3108
|
while (Date.now() - start < maxWait) {
|
|
2427
3109
|
const status = await apiFetch(`${grantsUrl}/${grant.id}`);
|
|
2428
3110
|
if (status.status === "approved") {
|
|
2429
|
-
|
|
3111
|
+
consola23.success("Grant approved!");
|
|
2430
3112
|
break;
|
|
2431
3113
|
}
|
|
2432
3114
|
if (status.status === "denied" || status.status === "revoked") {
|
|
@@ -2434,15 +3116,15 @@ async function runAudienceMode(audience, action, args) {
|
|
|
2434
3116
|
}
|
|
2435
3117
|
await new Promise((r) => setTimeout(r, interval));
|
|
2436
3118
|
}
|
|
2437
|
-
|
|
3119
|
+
consola23.info("Fetching grant token...");
|
|
2438
3120
|
const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
|
|
2439
3121
|
method: "POST"
|
|
2440
3122
|
});
|
|
2441
3123
|
if (audience === "escapes") {
|
|
2442
|
-
|
|
3124
|
+
consola23.info(`Executing: ${command.join(" ")}`);
|
|
2443
3125
|
try {
|
|
2444
3126
|
const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
|
|
2445
|
-
|
|
3127
|
+
execFileSync5(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
|
|
2446
3128
|
stdio: "inherit",
|
|
2447
3129
|
env: inheritedEnv
|
|
2448
3130
|
});
|
|
@@ -2457,8 +3139,8 @@ async function runAudienceMode(audience, action, args) {
|
|
|
2457
3139
|
|
|
2458
3140
|
// src/commands/proxy.ts
|
|
2459
3141
|
import { spawn as spawn2 } from "child_process";
|
|
2460
|
-
import { defineCommand as
|
|
2461
|
-
import
|
|
3142
|
+
import { defineCommand as defineCommand27 } from "citty";
|
|
3143
|
+
import consola24 from "consola";
|
|
2462
3144
|
|
|
2463
3145
|
// src/proxy/config.ts
|
|
2464
3146
|
function buildDefaultProxyConfigToml(opts) {
|
|
@@ -2492,10 +3174,10 @@ note = "VPC-internal hostname suffix"
|
|
|
2492
3174
|
|
|
2493
3175
|
// src/proxy/local-proxy.ts
|
|
2494
3176
|
import { spawn } from "child_process";
|
|
2495
|
-
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
3177
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
2496
3178
|
import { createRequire } from "module";
|
|
2497
|
-
import { tmpdir } from "os";
|
|
2498
|
-
import { dirname, join as
|
|
3179
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
3180
|
+
import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
|
|
2499
3181
|
var require2 = createRequire(import.meta.url);
|
|
2500
3182
|
function findProxyBin() {
|
|
2501
3183
|
const pkgPath = require2.resolve("@openape/proxy/package.json");
|
|
@@ -2504,12 +3186,12 @@ function findProxyBin() {
|
|
|
2504
3186
|
if (!binRel) {
|
|
2505
3187
|
throw new Error("@openape/proxy is missing the openape-proxy bin entry");
|
|
2506
3188
|
}
|
|
2507
|
-
return
|
|
3189
|
+
return resolve3(dirname2(pkgPath), binRel);
|
|
2508
3190
|
}
|
|
2509
3191
|
async function startEphemeralProxy(configToml) {
|
|
2510
|
-
const tmpDir =
|
|
2511
|
-
const configPath =
|
|
2512
|
-
|
|
3192
|
+
const tmpDir = mkdtempSync3(join4(tmpdir3(), "openape-proxy-"));
|
|
3193
|
+
const configPath = join4(tmpDir, "config.toml");
|
|
3194
|
+
writeFileSync4(configPath, configToml, { mode: 384 });
|
|
2513
3195
|
const binPath = findProxyBin();
|
|
2514
3196
|
const child = spawn(process.execPath, [binPath, "-c", configPath], {
|
|
2515
3197
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -2517,7 +3199,7 @@ async function startEphemeralProxy(configToml) {
|
|
|
2517
3199
|
});
|
|
2518
3200
|
const cleanupTmp = () => {
|
|
2519
3201
|
try {
|
|
2520
|
-
|
|
3202
|
+
rmSync3(tmpDir, { recursive: true, force: true });
|
|
2521
3203
|
} catch {
|
|
2522
3204
|
}
|
|
2523
3205
|
};
|
|
@@ -2601,10 +3283,10 @@ function resolveProxyConfigOptions() {
|
|
|
2601
3283
|
77
|
|
2602
3284
|
);
|
|
2603
3285
|
}
|
|
2604
|
-
|
|
3286
|
+
consola24.info(`[apes proxy] IdP-mediated mode \u2014 agent=${auth.email}, idp=${auth.idp}`);
|
|
2605
3287
|
return { agentEmail: auth.email, idpUrl: auth.idp, mediated: true };
|
|
2606
3288
|
}
|
|
2607
|
-
var proxyCommand =
|
|
3289
|
+
var proxyCommand = defineCommand27({
|
|
2608
3290
|
meta: {
|
|
2609
3291
|
name: "proxy",
|
|
2610
3292
|
description: "Run a command with HTTPS_PROXY routed through the OpenApe egress proxy."
|
|
@@ -2626,12 +3308,12 @@ var proxyCommand = defineCommand22({
|
|
|
2626
3308
|
let close = null;
|
|
2627
3309
|
if (reuseUrl) {
|
|
2628
3310
|
proxyUrl = reuseUrl;
|
|
2629
|
-
|
|
3311
|
+
consola24.info(`[apes proxy] reusing existing proxy at ${proxyUrl}`);
|
|
2630
3312
|
} else {
|
|
2631
3313
|
const ephemeral = await startEphemeralProxy(buildDefaultProxyConfigToml(resolveProxyConfigOptions()));
|
|
2632
3314
|
proxyUrl = ephemeral.url;
|
|
2633
3315
|
close = ephemeral.close;
|
|
2634
|
-
|
|
3316
|
+
consola24.info(`[apes proxy] started ephemeral proxy at ${proxyUrl}`);
|
|
2635
3317
|
}
|
|
2636
3318
|
const noProxy = process.env.NO_PROXY ?? process.env.no_proxy ?? "127.0.0.1,localhost";
|
|
2637
3319
|
const childEnv = {
|
|
@@ -2663,7 +3345,7 @@ var proxyCommand = defineCommand22({
|
|
|
2663
3345
|
else resolveExit(code ?? 0);
|
|
2664
3346
|
});
|
|
2665
3347
|
child.once("error", (err) => {
|
|
2666
|
-
|
|
3348
|
+
consola24.error(`[apes proxy] failed to spawn '${wrapped[0]}':`, err.message);
|
|
2667
3349
|
resolveExit(127);
|
|
2668
3350
|
});
|
|
2669
3351
|
});
|
|
@@ -2677,8 +3359,8 @@ function signalNumber(signal) {
|
|
|
2677
3359
|
}
|
|
2678
3360
|
|
|
2679
3361
|
// src/commands/explain.ts
|
|
2680
|
-
import { defineCommand as
|
|
2681
|
-
var explainCommand =
|
|
3362
|
+
import { defineCommand as defineCommand28 } from "citty";
|
|
3363
|
+
var explainCommand = defineCommand28({
|
|
2682
3364
|
meta: {
|
|
2683
3365
|
name: "explain",
|
|
2684
3366
|
description: "Show what permission a command would need"
|
|
@@ -2716,9 +3398,9 @@ var explainCommand = defineCommand23({
|
|
|
2716
3398
|
});
|
|
2717
3399
|
|
|
2718
3400
|
// src/commands/config/get.ts
|
|
2719
|
-
import { defineCommand as
|
|
2720
|
-
import
|
|
2721
|
-
var configGetCommand =
|
|
3401
|
+
import { defineCommand as defineCommand29 } from "citty";
|
|
3402
|
+
import consola25 from "consola";
|
|
3403
|
+
var configGetCommand = defineCommand29({
|
|
2722
3404
|
meta: {
|
|
2723
3405
|
name: "get",
|
|
2724
3406
|
description: "Get a configuration value"
|
|
@@ -2738,7 +3420,7 @@ var configGetCommand = defineCommand24({
|
|
|
2738
3420
|
if (idp)
|
|
2739
3421
|
console.log(idp);
|
|
2740
3422
|
else
|
|
2741
|
-
|
|
3423
|
+
consola25.info("No IdP configured.");
|
|
2742
3424
|
break;
|
|
2743
3425
|
}
|
|
2744
3426
|
case "email": {
|
|
@@ -2746,7 +3428,7 @@ var configGetCommand = defineCommand24({
|
|
|
2746
3428
|
if (auth?.email)
|
|
2747
3429
|
console.log(auth.email);
|
|
2748
3430
|
else
|
|
2749
|
-
|
|
3431
|
+
consola25.info("Not logged in.");
|
|
2750
3432
|
break;
|
|
2751
3433
|
}
|
|
2752
3434
|
default: {
|
|
@@ -2759,7 +3441,7 @@ var configGetCommand = defineCommand24({
|
|
|
2759
3441
|
if (sectionObj && field in sectionObj) {
|
|
2760
3442
|
console.log(sectionObj[field]);
|
|
2761
3443
|
} else {
|
|
2762
|
-
|
|
3444
|
+
consola25.info(`Key "${key}" not set.`);
|
|
2763
3445
|
}
|
|
2764
3446
|
} else {
|
|
2765
3447
|
throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
|
|
@@ -2770,9 +3452,9 @@ var configGetCommand = defineCommand24({
|
|
|
2770
3452
|
});
|
|
2771
3453
|
|
|
2772
3454
|
// src/commands/config/set.ts
|
|
2773
|
-
import { defineCommand as
|
|
2774
|
-
import
|
|
2775
|
-
var configSetCommand =
|
|
3455
|
+
import { defineCommand as defineCommand30 } from "citty";
|
|
3456
|
+
import consola26 from "consola";
|
|
3457
|
+
var configSetCommand = defineCommand30({
|
|
2776
3458
|
meta: {
|
|
2777
3459
|
name: "set",
|
|
2778
3460
|
description: "Set a configuration value"
|
|
@@ -2808,12 +3490,12 @@ var configSetCommand = defineCommand25({
|
|
|
2808
3490
|
throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
|
|
2809
3491
|
}
|
|
2810
3492
|
saveConfig(config);
|
|
2811
|
-
|
|
3493
|
+
consola26.success(`Set ${key} = ${value}`);
|
|
2812
3494
|
}
|
|
2813
3495
|
});
|
|
2814
3496
|
|
|
2815
3497
|
// src/commands/fetch/index.ts
|
|
2816
|
-
import { defineCommand as
|
|
3498
|
+
import { defineCommand as defineCommand31 } from "citty";
|
|
2817
3499
|
async function doRequest(method, url, body, contentType, raw, showHeaders) {
|
|
2818
3500
|
const token = getAuthToken();
|
|
2819
3501
|
if (!token) {
|
|
@@ -2849,13 +3531,13 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
|
|
|
2849
3531
|
throw new CliError(`HTTP ${response.status} ${response.statusText}`);
|
|
2850
3532
|
}
|
|
2851
3533
|
}
|
|
2852
|
-
var fetchCommand =
|
|
3534
|
+
var fetchCommand = defineCommand31({
|
|
2853
3535
|
meta: {
|
|
2854
3536
|
name: "fetch",
|
|
2855
3537
|
description: "Make authenticated HTTP requests"
|
|
2856
3538
|
},
|
|
2857
3539
|
subCommands: {
|
|
2858
|
-
get:
|
|
3540
|
+
get: defineCommand31({
|
|
2859
3541
|
meta: {
|
|
2860
3542
|
name: "get",
|
|
2861
3543
|
description: "GET request with auth token"
|
|
@@ -2881,7 +3563,7 @@ var fetchCommand = defineCommand26({
|
|
|
2881
3563
|
await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
|
|
2882
3564
|
}
|
|
2883
3565
|
}),
|
|
2884
|
-
post:
|
|
3566
|
+
post: defineCommand31({
|
|
2885
3567
|
meta: {
|
|
2886
3568
|
name: "post",
|
|
2887
3569
|
description: "POST request with auth token"
|
|
@@ -2920,8 +3602,8 @@ var fetchCommand = defineCommand26({
|
|
|
2920
3602
|
});
|
|
2921
3603
|
|
|
2922
3604
|
// src/commands/mcp/index.ts
|
|
2923
|
-
import { defineCommand as
|
|
2924
|
-
var mcpCommand =
|
|
3605
|
+
import { defineCommand as defineCommand32 } from "citty";
|
|
3606
|
+
var mcpCommand = defineCommand32({
|
|
2925
3607
|
meta: {
|
|
2926
3608
|
name: "mcp",
|
|
2927
3609
|
description: "Start MCP server for AI agents"
|
|
@@ -2944,48 +3626,48 @@ var mcpCommand = defineCommand27({
|
|
|
2944
3626
|
if (transport !== "stdio" && transport !== "sse") {
|
|
2945
3627
|
throw new Error('Transport must be "stdio" or "sse"');
|
|
2946
3628
|
}
|
|
2947
|
-
const { startMcpServer } = await import("./server-
|
|
3629
|
+
const { startMcpServer } = await import("./server-VZ325IPS.js");
|
|
2948
3630
|
await startMcpServer(transport, port);
|
|
2949
3631
|
}
|
|
2950
3632
|
});
|
|
2951
3633
|
|
|
2952
3634
|
// src/commands/init/index.ts
|
|
2953
|
-
import { existsSync as
|
|
3635
|
+
import { existsSync as existsSync6, copyFileSync, writeFileSync as writeFileSync5 } from "fs";
|
|
2954
3636
|
import { randomBytes } from "crypto";
|
|
2955
|
-
import { execFileSync as
|
|
2956
|
-
import { join as
|
|
2957
|
-
import { defineCommand as
|
|
2958
|
-
import
|
|
3637
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
3638
|
+
import { join as join5 } from "path";
|
|
3639
|
+
import { defineCommand as defineCommand33 } from "citty";
|
|
3640
|
+
import consola27 from "consola";
|
|
2959
3641
|
var DEFAULT_IDP_URL = "https://id.openape.at";
|
|
2960
3642
|
async function downloadTemplate(repo, targetDir) {
|
|
2961
3643
|
const { downloadTemplate: gigetDownload } = await import("giget");
|
|
2962
3644
|
await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
|
|
2963
3645
|
}
|
|
2964
3646
|
function installDeps(dir) {
|
|
2965
|
-
const hasLockFile = (name) =>
|
|
3647
|
+
const hasLockFile = (name) => existsSync6(join5(dir, name));
|
|
2966
3648
|
if (hasLockFile("pnpm-lock.yaml")) {
|
|
2967
|
-
|
|
3649
|
+
execFileSync6("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
|
|
2968
3650
|
} else if (hasLockFile("bun.lockb")) {
|
|
2969
|
-
|
|
3651
|
+
execFileSync6("bun", ["install"], { cwd: dir, stdio: "inherit" });
|
|
2970
3652
|
} else {
|
|
2971
|
-
|
|
3653
|
+
execFileSync6("npm", ["install"], { cwd: dir, stdio: "inherit" });
|
|
2972
3654
|
}
|
|
2973
3655
|
}
|
|
2974
3656
|
async function promptChoice(message, choices) {
|
|
2975
|
-
const result = await
|
|
3657
|
+
const result = await consola27.prompt(message, { type: "select", options: choices });
|
|
2976
3658
|
if (typeof result === "symbol") {
|
|
2977
3659
|
throw new CliExit(0);
|
|
2978
3660
|
}
|
|
2979
3661
|
return result;
|
|
2980
3662
|
}
|
|
2981
3663
|
async function promptText(message, defaultValue) {
|
|
2982
|
-
const result = await
|
|
3664
|
+
const result = await consola27.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
|
|
2983
3665
|
if (typeof result === "symbol") {
|
|
2984
3666
|
throw new CliExit(0);
|
|
2985
3667
|
}
|
|
2986
3668
|
return result || defaultValue || "";
|
|
2987
3669
|
}
|
|
2988
|
-
var initCommand =
|
|
3670
|
+
var initCommand = defineCommand33({
|
|
2989
3671
|
meta: {
|
|
2990
3672
|
name: "init",
|
|
2991
3673
|
description: "Scaffold a new OpenApe project"
|
|
@@ -3027,23 +3709,23 @@ var initCommand = defineCommand28({
|
|
|
3027
3709
|
});
|
|
3028
3710
|
async function initSP(targetDir) {
|
|
3029
3711
|
const dir = targetDir || "my-app";
|
|
3030
|
-
if (
|
|
3712
|
+
if (existsSync6(join5(dir, "package.json"))) {
|
|
3031
3713
|
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
3032
3714
|
}
|
|
3033
|
-
|
|
3715
|
+
consola27.start("Scaffolding SP starter...");
|
|
3034
3716
|
await downloadTemplate("openape-ai/openape-sp-starter", dir);
|
|
3035
|
-
|
|
3036
|
-
|
|
3717
|
+
consola27.success("Scaffolded from openape-sp-starter");
|
|
3718
|
+
consola27.start("Installing dependencies...");
|
|
3037
3719
|
installDeps(dir);
|
|
3038
|
-
|
|
3039
|
-
const envExample =
|
|
3040
|
-
const envFile =
|
|
3041
|
-
if (
|
|
3720
|
+
consola27.success("Dependencies installed");
|
|
3721
|
+
const envExample = join5(dir, ".env.example");
|
|
3722
|
+
const envFile = join5(dir, ".env");
|
|
3723
|
+
if (existsSync6(envExample) && !existsSync6(envFile)) {
|
|
3042
3724
|
copyFileSync(envExample, envFile);
|
|
3043
|
-
|
|
3725
|
+
consola27.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
|
|
3044
3726
|
}
|
|
3045
3727
|
console.log("");
|
|
3046
|
-
|
|
3728
|
+
consola27.box([
|
|
3047
3729
|
`cd ${dir}`,
|
|
3048
3730
|
"npm run dev",
|
|
3049
3731
|
"",
|
|
@@ -3052,7 +3734,7 @@ async function initSP(targetDir) {
|
|
|
3052
3734
|
}
|
|
3053
3735
|
async function initIdP(targetDir) {
|
|
3054
3736
|
const dir = targetDir || "my-idp";
|
|
3055
|
-
if (
|
|
3737
|
+
if (existsSync6(join5(dir, "package.json"))) {
|
|
3056
3738
|
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
3057
3739
|
}
|
|
3058
3740
|
const domain = await promptText("Domain for the IdP", "localhost");
|
|
@@ -3062,15 +3744,15 @@ async function initIdP(targetDir) {
|
|
|
3062
3744
|
"s3 (S3-compatible)"
|
|
3063
3745
|
]);
|
|
3064
3746
|
const adminEmail = await promptText("Admin email");
|
|
3065
|
-
|
|
3747
|
+
consola27.start("Scaffolding IdP starter...");
|
|
3066
3748
|
await downloadTemplate("openape-ai/openape-idp-starter", dir);
|
|
3067
|
-
|
|
3068
|
-
|
|
3749
|
+
consola27.success("Scaffolded from openape-idp-starter");
|
|
3750
|
+
consola27.start("Installing dependencies...");
|
|
3069
3751
|
installDeps(dir);
|
|
3070
|
-
|
|
3752
|
+
consola27.success("Dependencies installed");
|
|
3071
3753
|
const sessionSecret = randomBytes(32).toString("hex");
|
|
3072
3754
|
const managementToken = randomBytes(32).toString("hex");
|
|
3073
|
-
|
|
3755
|
+
consola27.success("Secrets generated");
|
|
3074
3756
|
const isLocalhost = domain === "localhost";
|
|
3075
3757
|
const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
|
|
3076
3758
|
const envContent = [
|
|
@@ -3084,11 +3766,11 @@ async function initIdP(targetDir) {
|
|
|
3084
3766
|
`NUXT_OPENAPE_RP_ID=${domain}`,
|
|
3085
3767
|
`NUXT_OPENAPE_RP_ORIGIN=${origin}`
|
|
3086
3768
|
].join("\n");
|
|
3087
|
-
|
|
3769
|
+
writeFileSync5(join5(dir, ".env"), `${envContent}
|
|
3088
3770
|
`, { mode: 384 });
|
|
3089
|
-
|
|
3771
|
+
consola27.success(".env created");
|
|
3090
3772
|
console.log("");
|
|
3091
|
-
|
|
3773
|
+
consola27.box([
|
|
3092
3774
|
`cd ${dir}`,
|
|
3093
3775
|
"npm run dev",
|
|
3094
3776
|
"",
|
|
@@ -3104,68 +3786,24 @@ async function initIdP(targetDir) {
|
|
|
3104
3786
|
}
|
|
3105
3787
|
|
|
3106
3788
|
// src/commands/enroll.ts
|
|
3107
|
-
import { Buffer as
|
|
3108
|
-
import { existsSync as
|
|
3789
|
+
import { Buffer as Buffer4 } from "buffer";
|
|
3790
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
3109
3791
|
import { execFile as execFile2 } from "child_process";
|
|
3110
|
-
import {
|
|
3111
|
-
import {
|
|
3112
|
-
import
|
|
3113
|
-
import { defineCommand as defineCommand29 } from "citty";
|
|
3114
|
-
import consola24 from "consola";
|
|
3792
|
+
import { sign as sign2 } from "crypto";
|
|
3793
|
+
import { defineCommand as defineCommand34 } from "citty";
|
|
3794
|
+
import consola28 from "consola";
|
|
3115
3795
|
var DEFAULT_IDP_URL2 = "https://id.openape.at";
|
|
3116
3796
|
var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
|
|
3117
3797
|
var POLL_INTERVAL = 3e3;
|
|
3118
3798
|
var POLL_TIMEOUT = 3e5;
|
|
3119
|
-
function resolvePath2(p) {
|
|
3120
|
-
return resolve3(p.replace(/^~/, homedir4()));
|
|
3121
|
-
}
|
|
3122
3799
|
function openBrowser2(url) {
|
|
3123
3800
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
3124
3801
|
execFile2(cmd, [url], () => {
|
|
3125
3802
|
});
|
|
3126
3803
|
}
|
|
3127
|
-
function readPublicKey(keyPath) {
|
|
3128
|
-
const pubPath = `${keyPath}.pub`;
|
|
3129
|
-
if (existsSync4(pubPath)) {
|
|
3130
|
-
return readFileSync2(pubPath, "utf-8").trim();
|
|
3131
|
-
}
|
|
3132
|
-
const keyContent = readFileSync2(keyPath, "utf-8");
|
|
3133
|
-
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
3134
|
-
const jwk = privateKey.export({ format: "jwk" });
|
|
3135
|
-
const pubBytes = Buffer2.from(jwk.x, "base64url");
|
|
3136
|
-
const keyTypeStr = "ssh-ed25519";
|
|
3137
|
-
const keyTypeLen = Buffer2.alloc(4);
|
|
3138
|
-
keyTypeLen.writeUInt32BE(keyTypeStr.length);
|
|
3139
|
-
const pubKeyLen = Buffer2.alloc(4);
|
|
3140
|
-
pubKeyLen.writeUInt32BE(pubBytes.length);
|
|
3141
|
-
const blob = Buffer2.concat([keyTypeLen, Buffer2.from(keyTypeStr), pubKeyLen, pubBytes]);
|
|
3142
|
-
return `ssh-ed25519 ${blob.toString("base64")}`;
|
|
3143
|
-
}
|
|
3144
|
-
function generateAndSaveKey(keyPath) {
|
|
3145
|
-
const resolved = resolvePath2(keyPath);
|
|
3146
|
-
const dir = dirname2(resolved);
|
|
3147
|
-
if (!existsSync4(dir)) {
|
|
3148
|
-
mkdirSync(dir, { recursive: true });
|
|
3149
|
-
}
|
|
3150
|
-
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
3151
|
-
const privatePem = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
3152
|
-
writeFileSync3(resolved, privatePem, { mode: 384 });
|
|
3153
|
-
const jwk = publicKey.export({ format: "jwk" });
|
|
3154
|
-
const pubBytes = Buffer2.from(jwk.x, "base64url");
|
|
3155
|
-
const keyTypeStr = "ssh-ed25519";
|
|
3156
|
-
const keyTypeLen = Buffer2.alloc(4);
|
|
3157
|
-
keyTypeLen.writeUInt32BE(keyTypeStr.length);
|
|
3158
|
-
const pubKeyLen = Buffer2.alloc(4);
|
|
3159
|
-
pubKeyLen.writeUInt32BE(pubBytes.length);
|
|
3160
|
-
const blob = Buffer2.concat([keyTypeLen, Buffer2.from(keyTypeStr), pubKeyLen, pubBytes]);
|
|
3161
|
-
const pubKeyStr = `ssh-ed25519 ${blob.toString("base64")}`;
|
|
3162
|
-
writeFileSync3(`${resolved}.pub`, `${pubKeyStr}
|
|
3163
|
-
`, { mode: 420 });
|
|
3164
|
-
return pubKeyStr;
|
|
3165
|
-
}
|
|
3166
3804
|
async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
3167
|
-
const resolvedKey =
|
|
3168
|
-
const keyContent =
|
|
3805
|
+
const resolvedKey = resolveKeyPath(keyPath);
|
|
3806
|
+
const keyContent = readFileSync5(resolvedKey, "utf-8");
|
|
3169
3807
|
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
3170
3808
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
3171
3809
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
@@ -3179,7 +3817,7 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
|
3179
3817
|
});
|
|
3180
3818
|
if (challengeResp.ok) {
|
|
3181
3819
|
const { challenge } = await challengeResp.json();
|
|
3182
|
-
const signature =
|
|
3820
|
+
const signature = sign2(null, Buffer4.from(challenge), privateKey).toString("base64");
|
|
3183
3821
|
const authResp = await fetch(authenticateUrl, {
|
|
3184
3822
|
method: "POST",
|
|
3185
3823
|
headers: { "Content-Type": "application/json" },
|
|
@@ -3196,7 +3834,7 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
|
3196
3834
|
}
|
|
3197
3835
|
throw new Error("Enrollment timed out. Please check the browser and try again.");
|
|
3198
3836
|
}
|
|
3199
|
-
var enrollCommand =
|
|
3837
|
+
var enrollCommand = defineCommand34({
|
|
3200
3838
|
meta: {
|
|
3201
3839
|
name: "enroll",
|
|
3202
3840
|
description: "Enroll an agent with an Identity Provider"
|
|
@@ -3216,38 +3854,38 @@ var enrollCommand = defineCommand29({
|
|
|
3216
3854
|
}
|
|
3217
3855
|
},
|
|
3218
3856
|
async run({ args }) {
|
|
3219
|
-
const idp = args.idp || await
|
|
3857
|
+
const idp = args.idp || await consola28.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
|
|
3220
3858
|
if (typeof r === "symbol") throw new CliExit(0);
|
|
3221
3859
|
return r;
|
|
3222
3860
|
}) || DEFAULT_IDP_URL2;
|
|
3223
|
-
const agentName = args.name || await
|
|
3861
|
+
const agentName = args.name || await consola28.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
|
|
3224
3862
|
if (typeof r === "symbol") throw new CliExit(0);
|
|
3225
3863
|
return r;
|
|
3226
3864
|
});
|
|
3227
3865
|
if (!agentName) {
|
|
3228
3866
|
throw new CliError("Agent name is required.");
|
|
3229
3867
|
}
|
|
3230
|
-
const keyPath = args.key || await
|
|
3868
|
+
const keyPath = args.key || await consola28.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
|
|
3231
3869
|
if (typeof r === "symbol") throw new CliExit(0);
|
|
3232
3870
|
return r;
|
|
3233
3871
|
}) || DEFAULT_KEY_PATH;
|
|
3234
|
-
const resolvedKey =
|
|
3872
|
+
const resolvedKey = resolveKeyPath(keyPath);
|
|
3235
3873
|
let publicKey;
|
|
3236
|
-
if (
|
|
3874
|
+
if (existsSync7(resolvedKey)) {
|
|
3237
3875
|
publicKey = readPublicKey(resolvedKey);
|
|
3238
|
-
|
|
3876
|
+
consola28.success(`Using existing key ${keyPath}`);
|
|
3239
3877
|
} else {
|
|
3240
|
-
|
|
3878
|
+
consola28.start(`Generating Ed25519 key pair at ${keyPath}...`);
|
|
3241
3879
|
publicKey = generateAndSaveKey(keyPath);
|
|
3242
|
-
|
|
3880
|
+
consola28.success(`Key pair generated at ${keyPath}`);
|
|
3243
3881
|
}
|
|
3244
3882
|
const encodedKey = encodeURIComponent(publicKey);
|
|
3245
3883
|
const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
|
|
3246
|
-
|
|
3247
|
-
|
|
3884
|
+
consola28.info("Opening browser for enrollment...");
|
|
3885
|
+
consola28.info(`\u2192 ${idp}/enroll`);
|
|
3248
3886
|
openBrowser2(enrollUrl);
|
|
3249
3887
|
console.log("");
|
|
3250
|
-
const agentEmail = await
|
|
3888
|
+
const agentEmail = await consola28.prompt(
|
|
3251
3889
|
"Agent email (shown in browser after enrollment)",
|
|
3252
3890
|
{ type: "text", placeholder: `agent+${agentName}@...` }
|
|
3253
3891
|
).then((r) => {
|
|
@@ -3257,7 +3895,7 @@ var enrollCommand = defineCommand29({
|
|
|
3257
3895
|
if (!agentEmail) {
|
|
3258
3896
|
throw new CliError("Agent email is required to verify enrollment.");
|
|
3259
3897
|
}
|
|
3260
|
-
|
|
3898
|
+
consola28.start("Verifying enrollment...");
|
|
3261
3899
|
const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
|
|
3262
3900
|
saveAuth({
|
|
3263
3901
|
idp,
|
|
@@ -3269,18 +3907,18 @@ var enrollCommand = defineCommand29({
|
|
|
3269
3907
|
config.defaults = { ...config.defaults, idp };
|
|
3270
3908
|
config.agent = { key: keyPath, email: agentEmail };
|
|
3271
3909
|
saveConfig(config);
|
|
3272
|
-
|
|
3273
|
-
|
|
3910
|
+
consola28.success(`Agent enrolled as ${agentEmail}`);
|
|
3911
|
+
consola28.success("Config saved to ~/.config/apes/");
|
|
3274
3912
|
console.log("");
|
|
3275
|
-
|
|
3913
|
+
consola28.info("Verify with: apes whoami");
|
|
3276
3914
|
}
|
|
3277
3915
|
});
|
|
3278
3916
|
|
|
3279
3917
|
// src/commands/register-user.ts
|
|
3280
|
-
import { existsSync as
|
|
3281
|
-
import { defineCommand as
|
|
3282
|
-
import
|
|
3283
|
-
var registerUserCommand =
|
|
3918
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
3919
|
+
import { defineCommand as defineCommand35 } from "citty";
|
|
3920
|
+
import consola29 from "consola";
|
|
3921
|
+
var registerUserCommand = defineCommand35({
|
|
3284
3922
|
meta: {
|
|
3285
3923
|
name: "register-user",
|
|
3286
3924
|
description: "Register a sub-user with SSH key"
|
|
@@ -3316,8 +3954,8 @@ var registerUserCommand = defineCommand30({
|
|
|
3316
3954
|
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
3317
3955
|
}
|
|
3318
3956
|
let publicKey = args.key;
|
|
3319
|
-
if (
|
|
3320
|
-
publicKey =
|
|
3957
|
+
if (existsSync8(args.key)) {
|
|
3958
|
+
publicKey = readFileSync6(args.key, "utf-8").trim();
|
|
3321
3959
|
}
|
|
3322
3960
|
if (!publicKey.startsWith("ssh-ed25519 ")) {
|
|
3323
3961
|
throw new CliError("Public key must be in ssh-ed25519 format.");
|
|
@@ -3335,15 +3973,15 @@ var registerUserCommand = defineCommand30({
|
|
|
3335
3973
|
...userType ? { type: userType } : {}
|
|
3336
3974
|
}
|
|
3337
3975
|
});
|
|
3338
|
-
|
|
3976
|
+
consola29.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
|
|
3339
3977
|
}
|
|
3340
3978
|
});
|
|
3341
3979
|
|
|
3342
3980
|
// src/commands/dns-check.ts
|
|
3343
|
-
import { defineCommand as
|
|
3344
|
-
import
|
|
3981
|
+
import { defineCommand as defineCommand36 } from "citty";
|
|
3982
|
+
import consola30 from "consola";
|
|
3345
3983
|
import { resolveDDISA as resolveDDISA2 } from "@openape/core";
|
|
3346
|
-
var dnsCheckCommand =
|
|
3984
|
+
var dnsCheckCommand = defineCommand36({
|
|
3347
3985
|
meta: {
|
|
3348
3986
|
name: "dns-check",
|
|
3349
3987
|
description: "Validate DDISA DNS TXT records for a domain"
|
|
@@ -3357,7 +3995,7 @@ var dnsCheckCommand = defineCommand31({
|
|
|
3357
3995
|
},
|
|
3358
3996
|
async run({ args }) {
|
|
3359
3997
|
const domain = args.domain;
|
|
3360
|
-
|
|
3998
|
+
consola30.start(`Checking _ddisa.${domain}...`);
|
|
3361
3999
|
try {
|
|
3362
4000
|
const result = await resolveDDISA2(domain);
|
|
3363
4001
|
if (!result) {
|
|
@@ -3366,7 +4004,7 @@ var dnsCheckCommand = defineCommand31({
|
|
|
3366
4004
|
console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
|
|
3367
4005
|
throw new CliError(`No DDISA record found for ${domain}`);
|
|
3368
4006
|
}
|
|
3369
|
-
|
|
4007
|
+
consola30.success(`_ddisa.${domain} \u2192 ${result.idp}`);
|
|
3370
4008
|
console.log("");
|
|
3371
4009
|
console.log(` Version: ${result.version || "ddisa1"}`);
|
|
3372
4010
|
console.log(` IdP URL: ${result.idp}`);
|
|
@@ -3375,14 +4013,14 @@ var dnsCheckCommand = defineCommand31({
|
|
|
3375
4013
|
if (result.priority !== void 0)
|
|
3376
4014
|
console.log(` Priority: ${result.priority}`);
|
|
3377
4015
|
console.log("");
|
|
3378
|
-
|
|
4016
|
+
consola30.start(`Verifying IdP at ${result.idp}...`);
|
|
3379
4017
|
const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
|
|
3380
4018
|
if (!discoResp.ok) {
|
|
3381
|
-
|
|
4019
|
+
consola30.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
|
|
3382
4020
|
return;
|
|
3383
4021
|
}
|
|
3384
4022
|
const disco = await discoResp.json();
|
|
3385
|
-
|
|
4023
|
+
consola30.success(`IdP is reachable`);
|
|
3386
4024
|
console.log(` Issuer: ${disco.issuer}`);
|
|
3387
4025
|
console.log(` DDISA: v${disco.ddisa_version || "?"}`);
|
|
3388
4026
|
if (disco.ddisa_auth_methods_supported) {
|
|
@@ -3400,7 +4038,7 @@ var dnsCheckCommand = defineCommand31({
|
|
|
3400
4038
|
// src/commands/health.ts
|
|
3401
4039
|
import { exec } from "child_process";
|
|
3402
4040
|
import { promisify } from "util";
|
|
3403
|
-
import { defineCommand as
|
|
4041
|
+
import { defineCommand as defineCommand37 } from "citty";
|
|
3404
4042
|
var execAsync = promisify(exec);
|
|
3405
4043
|
async function resolveApeShellPath() {
|
|
3406
4044
|
try {
|
|
@@ -3436,7 +4074,7 @@ async function bestEffortGrantCount(idp) {
|
|
|
3436
4074
|
}
|
|
3437
4075
|
}
|
|
3438
4076
|
async function runHealth(args) {
|
|
3439
|
-
const version = true ? "0.
|
|
4077
|
+
const version = true ? "0.15.1" : "0.0.0";
|
|
3440
4078
|
const auth = loadAuth();
|
|
3441
4079
|
if (!auth) {
|
|
3442
4080
|
throw new CliError("Not logged in. Run `apes login` first.", 1);
|
|
@@ -3499,7 +4137,7 @@ async function runHealth(args) {
|
|
|
3499
4137
|
throw new CliError(`IdP ${auth.idp} unreachable: ${idpProbe.error}`, 1);
|
|
3500
4138
|
}
|
|
3501
4139
|
}
|
|
3502
|
-
var healthCommand =
|
|
4140
|
+
var healthCommand = defineCommand37({
|
|
3503
4141
|
meta: {
|
|
3504
4142
|
name: "health",
|
|
3505
4143
|
description: "Report CLI diagnostic state (auth, IdP, grants, binaries)"
|
|
@@ -3517,8 +4155,8 @@ var healthCommand = defineCommand32({
|
|
|
3517
4155
|
});
|
|
3518
4156
|
|
|
3519
4157
|
// src/commands/workflows.ts
|
|
3520
|
-
import { defineCommand as
|
|
3521
|
-
import
|
|
4158
|
+
import { defineCommand as defineCommand38 } from "citty";
|
|
4159
|
+
import consola31 from "consola";
|
|
3522
4160
|
|
|
3523
4161
|
// src/guides/index.ts
|
|
3524
4162
|
var guides = [
|
|
@@ -3568,7 +4206,7 @@ var guides = [
|
|
|
3568
4206
|
];
|
|
3569
4207
|
|
|
3570
4208
|
// src/commands/workflows.ts
|
|
3571
|
-
var workflowsCommand =
|
|
4209
|
+
var workflowsCommand = defineCommand38({
|
|
3572
4210
|
meta: {
|
|
3573
4211
|
name: "workflows",
|
|
3574
4212
|
description: "Discover workflow guides"
|
|
@@ -3589,7 +4227,7 @@ var workflowsCommand = defineCommand33({
|
|
|
3589
4227
|
if (args.id) {
|
|
3590
4228
|
const guide = guides.find((g) => g.id === String(args.id));
|
|
3591
4229
|
if (!guide) {
|
|
3592
|
-
|
|
4230
|
+
consola31.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
|
|
3593
4231
|
throw new CliError(`Guide not found: ${args.id}`);
|
|
3594
4232
|
}
|
|
3595
4233
|
if (args.json) {
|
|
@@ -3638,10 +4276,10 @@ if (shellRewrite) {
|
|
|
3638
4276
|
if (shellRewrite.action === "rewrite") {
|
|
3639
4277
|
process.argv = shellRewrite.argv;
|
|
3640
4278
|
} else if (shellRewrite.action === "version") {
|
|
3641
|
-
console.log(`ape-shell ${"0.
|
|
4279
|
+
console.log(`ape-shell ${"0.15.1"} (OpenApe DDISA shell wrapper)`);
|
|
3642
4280
|
process.exit(0);
|
|
3643
4281
|
} else if (shellRewrite.action === "help") {
|
|
3644
|
-
console.log(`ape-shell ${"0.
|
|
4282
|
+
console.log(`ape-shell ${"0.15.1"} \u2014 OpenApe DDISA shell wrapper`);
|
|
3645
4283
|
console.log("");
|
|
3646
4284
|
console.log("Usage:");
|
|
3647
4285
|
console.log(" ape-shell Start interactive grant-mediated REPL");
|
|
@@ -3665,7 +4303,7 @@ if (shellRewrite) {
|
|
|
3665
4303
|
}
|
|
3666
4304
|
}
|
|
3667
4305
|
var debug = process.argv.includes("--debug");
|
|
3668
|
-
var grantsCommand =
|
|
4306
|
+
var grantsCommand = defineCommand39({
|
|
3669
4307
|
meta: {
|
|
3670
4308
|
name: "grants",
|
|
3671
4309
|
description: "Grant management"
|
|
@@ -3686,7 +4324,7 @@ var grantsCommand = defineCommand34({
|
|
|
3686
4324
|
"delegation-revoke": delegationRevokeCommand
|
|
3687
4325
|
}
|
|
3688
4326
|
});
|
|
3689
|
-
var configCommand =
|
|
4327
|
+
var configCommand = defineCommand39({
|
|
3690
4328
|
meta: {
|
|
3691
4329
|
name: "config",
|
|
3692
4330
|
description: "Configuration management"
|
|
@@ -3696,10 +4334,10 @@ var configCommand = defineCommand34({
|
|
|
3696
4334
|
set: configSetCommand
|
|
3697
4335
|
}
|
|
3698
4336
|
});
|
|
3699
|
-
var main =
|
|
4337
|
+
var main = defineCommand39({
|
|
3700
4338
|
meta: {
|
|
3701
4339
|
name: "apes",
|
|
3702
|
-
version: "0.
|
|
4340
|
+
version: "0.15.1",
|
|
3703
4341
|
description: "Unified CLI for OpenApe"
|
|
3704
4342
|
},
|
|
3705
4343
|
subCommands: {
|
|
@@ -3712,6 +4350,7 @@ var main = defineCommand34({
|
|
|
3712
4350
|
whoami: whoamiCommand,
|
|
3713
4351
|
health: healthCommand,
|
|
3714
4352
|
grants: grantsCommand,
|
|
4353
|
+
agents: agentsCommand,
|
|
3715
4354
|
admin: adminCommand,
|
|
3716
4355
|
run: runCommand,
|
|
3717
4356
|
proxy: proxyCommand,
|
|
@@ -3728,13 +4367,13 @@ runMain(main).catch((err) => {
|
|
|
3728
4367
|
process.exit(err.exitCode);
|
|
3729
4368
|
}
|
|
3730
4369
|
if (err instanceof CliError) {
|
|
3731
|
-
|
|
4370
|
+
consola32.error(err.message);
|
|
3732
4371
|
process.exit(err.exitCode);
|
|
3733
4372
|
}
|
|
3734
4373
|
if (debug) {
|
|
3735
|
-
|
|
4374
|
+
consola32.error(err);
|
|
3736
4375
|
} else {
|
|
3737
|
-
|
|
4376
|
+
consola32.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
|
|
3738
4377
|
}
|
|
3739
4378
|
process.exit(1);
|
|
3740
4379
|
});
|