@openape/cli 0.1.4 → 0.2.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.
@@ -66,18 +66,14 @@ audit_log = "${auditLog}"
66
66
  ensureDir(auditDir, 0o755);
67
67
  console.log(`✅ Audit log dir: ${auditDir}`);
68
68
  }
69
- // Clone/update proxy source
69
+ // Install proxy via npm/bun (no git needed)
70
70
  ensureDir(INSTALL_DIR, 0o755);
71
- if (existsSync(`${INSTALL_DIR}/package.json`)) {
72
- console.log('Updating proxy source...');
73
- execSync(`cd ${INSTALL_DIR} && git pull`, { stdio: 'inherit' });
71
+ console.log('Installing @openape/proxy via bun...');
72
+ if (!existsSync(`${INSTALL_DIR}/package.json`)) {
73
+ execSync(`cd ${INSTALL_DIR} && bun init -y`, { stdio: 'pipe' });
74
74
  }
75
- else {
76
- console.log('Downloading proxy source...');
77
- execSync(`git clone https://github.com/openape-ai/proxy.git ${INSTALL_DIR}`, { stdio: 'inherit' });
78
- }
79
- console.log('Installing dependencies...');
80
- execSync(`cd ${INSTALL_DIR} && bun install`, { stdio: 'inherit' });
75
+ execSync(`cd ${INSTALL_DIR} && bun add @openape/proxy`, { stdio: 'inherit' });
76
+ console.log(`✅ Proxy installed: ${INSTALL_DIR}`);
81
77
  // System service
82
78
  if (isMacOS()) {
83
79
  setupLaunchd();
@@ -86,7 +82,7 @@ audit_log = "${auditLog}"
86
82
  setupSystemd();
87
83
  }
88
84
  else {
89
- console.log(`\n⚠️ Manual start: cd ${INSTALL_DIR} && bun run src/index.ts --config ${CONFIG_PATH}`);
85
+ console.log(`\n⚠️ Manual start: cd ${INSTALL_DIR} && bunx openape-proxy --config ${CONFIG_PATH}`);
90
86
  }
91
87
  console.log('\n🎉 OpenApe Proxy installed!\n');
92
88
  console.log(` Config: ${CONFIG_PATH}`);
@@ -107,7 +103,7 @@ function setupLaunchd() {
107
103
  <array>
108
104
  <string>${bunPath}</string>
109
105
  <string>run</string>
110
- <string>${INSTALL_DIR}/src/index.ts</string>
106
+ <string>${INSTALL_DIR}/node_modules/@openape/proxy/src/index.ts</string>
111
107
  <string>--config</string>
112
108
  <string>${CONFIG_PATH}</string>
113
109
  </array>
@@ -139,7 +135,7 @@ After=network.target
139
135
 
140
136
  [Service]
141
137
  Type=simple
142
- ExecStart=${bunPath} run ${INSTALL_DIR}/src/index.ts --config ${CONFIG_PATH}
138
+ ExecStart=${bunPath} run ${INSTALL_DIR}/node_modules/@openape/proxy/src/index.ts --config ${CONFIG_PATH}
143
139
  WorkingDirectory=${INSTALL_DIR}
144
140
  Restart=on-failure
145
141
  RestartSec=5
@@ -1,13 +1,14 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
- import { requireRoot, ensureDir, writeSecureFile, run, parseFlags, requireFlag } from '../utils.js';
3
+ import { arch, platform } from 'node:os';
4
+ import { requireRoot, ensureDir, writeSecureFile, run, parseFlags } from '../utils.js';
4
5
  const CONFIG_DIR = '/etc/apes';
5
6
  const CONFIG_PATH = `${CONFIG_DIR}/config.toml`;
6
7
  const BIN_PATH = '/usr/local/bin/apes';
8
+ const REPO = 'openape-ai/sudo';
7
9
  export async function installSudo(args) {
8
10
  requireRoot();
9
11
  const flags = parseFlags(args);
10
- const idpUrl = requireFlag(flags, 'idp-url');
11
12
  const pollTimeout = flags['poll-timeout'] || '300';
12
13
  const pollInterval = flags['poll-interval'] || '2';
13
14
  const force = flags['force'] === true;
@@ -16,32 +17,12 @@ export async function installSudo(args) {
16
17
  if (existsSync(BIN_PATH) && !force) {
17
18
  throw new Error(`apes already installed at ${BIN_PATH}. Use --force to reinstall.`);
18
19
  }
19
- // Check Rust toolchain
20
- try {
21
- run('cargo --version');
22
- }
23
- catch {
24
- throw new Error('Rust toolchain required to build apes. Install it: https://rustup.rs\n' +
25
- 'Pre-built binaries will be available in a future release.');
26
- }
27
- // Build from source
28
- const buildDir = '/tmp/openape-sudo-build';
29
- if (existsSync(`${buildDir}/.git`)) {
30
- console.log('Updating source...');
31
- execSync(`cd ${buildDir} && git pull`, { stdio: 'inherit' });
32
- }
33
- else {
34
- console.log('Downloading source...');
35
- execSync(`rm -rf ${buildDir}`, { stdio: 'ignore' });
36
- execSync(`git clone https://github.com/openape-ai/sudo.git ${buildDir}`, { stdio: 'inherit' });
20
+ // Try GitHub Release first, fall back to source build
21
+ const installed = await tryGitHubRelease();
22
+ if (!installed) {
23
+ console.log('No pre-built binary available. Building from source...');
24
+ buildFromSource();
37
25
  }
38
- console.log('Building apes (release mode)...');
39
- execSync(`cd ${buildDir} && cargo build --release`, { stdio: 'inherit' });
40
- // Install binary with setuid
41
- execSync(`cp ${buildDir}/target/release/apes ${BIN_PATH}`);
42
- execSync(`chown root:wheel ${BIN_PATH}`);
43
- execSync(`chmod u+s ${BIN_PATH}`);
44
- console.log(`✅ Binary: ${BIN_PATH} (setuid root)`);
45
26
  // Config
46
27
  if (existsSync(CONFIG_PATH) && !force) {
47
28
  console.log(`ℹ️ Config preserved: ${CONFIG_PATH}`);
@@ -50,14 +31,19 @@ export async function installSudo(args) {
50
31
  const config = `# OpenApe Sudo (apes) Configuration
51
32
  # Generated by: openape install-sudo
52
33
 
53
- server_url = "${idpUrl}"
54
-
55
34
  # Agent identity is determined by the key passed via --key flag.
56
- # Each user provides their own key: apes --key ~/.ssh/id_ed25519 -- <command>
35
+ # Agents are registered via: apes enroll --server <idp-url> --agent-email <email> --agent-name <name> --key <path>
36
+ # Each enrolled agent gets a [[agents]] entry below automatically.
57
37
 
58
38
  [poll]
59
39
  interval_secs = ${pollInterval}
60
40
  timeout_secs = ${pollTimeout}
41
+
42
+ # Agents are added here by 'apes enroll':
43
+ # [[agents]]
44
+ # name = "my-agent"
45
+ # public_key = "ssh-ed25519 AAAA..."
46
+ # server_url = "https://id.example.com"
61
47
  `;
62
48
  ensureDir(CONFIG_DIR, 0o700);
63
49
  writeSecureFile(CONFIG_PATH, config);
@@ -67,7 +53,81 @@ timeout_secs = ${pollTimeout}
67
53
  console.log(` Binary: ${BIN_PATH} (setuid root)`);
68
54
  console.log(` Config: ${CONFIG_PATH}`);
69
55
  console.log('\n Next steps:');
70
- console.log(` 1. Enroll: apes enroll --server ${idpUrl} --agent-email <email> --agent-name <name> --key <path>`);
56
+ console.log(' 1. Enroll: apes enroll --server <idp-url> --agent-email <email> --agent-name <name> --key <path>');
71
57
  console.log(' 2. Admin approves enrollment');
72
58
  console.log(' 3. Use: apes --key ~/.ssh/id_ed25519 --reason "why" -- <command>\n');
73
59
  }
60
+ function getAssetName() {
61
+ const os = platform();
62
+ const cpu = arch();
63
+ const osMap = { darwin: 'apple-darwin', linux: 'unknown-linux-gnu' };
64
+ const archMap = { arm64: 'aarch64', x64: 'x86_64' };
65
+ const osStr = osMap[os];
66
+ const archStr = archMap[cpu];
67
+ if (!osStr || !archStr) {
68
+ throw new Error(`Unsupported platform: ${os}-${cpu}`);
69
+ }
70
+ return `apes-${archStr}-${osStr}`;
71
+ }
72
+ async function tryGitHubRelease() {
73
+ try {
74
+ // Check if gh CLI is available
75
+ run('which gh');
76
+ }
77
+ catch {
78
+ console.log('GitHub CLI (gh) not found — skipping release download.');
79
+ return false;
80
+ }
81
+ try {
82
+ const assetName = getAssetName();
83
+ console.log(`Looking for pre-built binary: ${assetName}...`);
84
+ // Download latest release asset
85
+ const tmpBin = '/tmp/apes-download';
86
+ execSync(`gh release download --repo ${REPO} --pattern "${assetName}" --output ${tmpBin} --clobber`, {
87
+ stdio: 'pipe',
88
+ timeout: 30000,
89
+ });
90
+ if (!existsSync(tmpBin)) {
91
+ return false;
92
+ }
93
+ // Install
94
+ execSync(`cp ${tmpBin} ${BIN_PATH}`);
95
+ execSync(`chown root:wheel ${BIN_PATH}`);
96
+ execSync(`chmod u+s ${BIN_PATH}`);
97
+ execSync(`rm -f ${tmpBin}`);
98
+ console.log(`✅ Binary (pre-built): ${BIN_PATH} (setuid root)`);
99
+ return true;
100
+ }
101
+ catch {
102
+ console.log('No pre-built binary found for this platform.');
103
+ return false;
104
+ }
105
+ }
106
+ function buildFromSource() {
107
+ // Check Rust toolchain
108
+ try {
109
+ run('cargo --version');
110
+ }
111
+ catch {
112
+ throw new Error('Rust toolchain required to build apes. Install it: https://rustup.rs\n' +
113
+ 'Or create a GitHub Release with pre-built binaries.');
114
+ }
115
+ const buildDir = '/tmp/openape-sudo-build';
116
+ if (existsSync(`${buildDir}/.git`)) {
117
+ console.log('Updating source...');
118
+ execSync(`git config --global --add safe.directory ${buildDir}`, { stdio: 'ignore' });
119
+ execSync(`cd ${buildDir} && git pull`, { stdio: 'inherit' });
120
+ }
121
+ else {
122
+ console.log('Downloading source...');
123
+ execSync(`rm -rf ${buildDir}`, { stdio: 'ignore' });
124
+ execSync(`git clone https://github.com/${REPO}.git ${buildDir}`, { stdio: 'inherit' });
125
+ }
126
+ console.log('Building apes (release mode)...');
127
+ execSync(`cd ${buildDir} && cargo build --release`, { stdio: 'inherit' });
128
+ // Install binary with setuid
129
+ execSync(`cp ${buildDir}/target/release/apes ${BIN_PATH}`);
130
+ execSync(`chown root:wheel ${BIN_PATH}`);
131
+ execSync(`chmod u+s ${BIN_PATH}`);
132
+ console.log(`✅ Binary (built): ${BIN_PATH} (setuid root)`);
133
+ }
package/dist/index.js CHANGED
@@ -25,15 +25,15 @@ install-proxy flags:
25
25
  --force Overwrite existing config
26
26
 
27
27
  install-sudo flags:
28
- --idp-url <url> IdP URL (required)
29
28
  --poll-timeout <secs> Poll timeout in seconds (default: 300)
30
29
  --poll-interval <secs> Poll interval in seconds (default: 2)
31
- --force Overwrite existing config/binary
30
+ --force Overwrite existing binary/config
32
31
 
33
32
  Examples:
34
33
  sudo openape install-proxy --idp-url https://id.openape.at --agent-email bot@example.com
35
34
  apes -- npx @openape/cli install-proxy --idp-url https://id.openape.at --agent-email bot@example.com
36
- sudo openape install-sudo --idp-url https://id.openape.at
35
+ sudo openape install-sudo
36
+ # Then enroll: apes enroll --server https://id.openape.at --agent-email bot@example.com --agent-name "My Agent" --key ~/.ssh/id_ed25519
37
37
  `;
38
38
  async function main() {
39
39
  switch (command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openape/cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "OpenApe CLI — install and manage OpenApe components (proxy, sudo, auth)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,162 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { execSync } from 'node:child_process';
3
- import { writeFileSync } from 'node:fs';
4
- import { parseArgs } from 'node:util';
5
- import { ask, closePrompt, isMacOS } from '../utils.js';
6
- const CONFIG_DIR = '/etc/openape-proxy';
7
- const CONFIG_PATH = `${CONFIG_DIR}/config.toml`;
8
- const INSTALL_DIR = '/opt/openape-proxy';
9
- const LAUNCHD_LABEL = 'ai.openape.proxy';
10
- const LAUNCHD_PLIST = `/Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`;
11
- function apes(reason, cmd) {
12
- const keyPaths = [
13
- `${process.env.HOME}/.ssh/id_ed25519`,
14
- `${process.env.HOME}/.ssh/id_ecdsa`,
15
- `${process.env.HOME}/.ssh/id_rsa`,
16
- ];
17
- const keyPath = keyPaths.find(p => existsSync(p));
18
- if (!keyPath) {
19
- throw new Error('No SSH key found. apes needs --key to authenticate.');
20
- }
21
- console.log(`\n🔐 Requesting grant: ${reason}`);
22
- execSync(`apes --key ${keyPath} --reason "${reason}" -- ${cmd}`, {
23
- stdio: 'inherit',
24
- });
25
- }
26
- export async function installProxyApes() {
27
- // Check apes is installed
28
- try {
29
- execSync('which apes', { stdio: 'ignore' });
30
- }
31
- catch {
32
- throw new Error('apes is not installed. Run: sudo npx @openape/cli install-sudo');
33
- }
34
- // Parse flags for non-interactive mode
35
- const { values: flags } = parseArgs({
36
- args: process.argv.slice(3),
37
- options: {
38
- 'idp-url': { type: 'string' },
39
- 'agent-email': { type: 'string' },
40
- 'listen': { type: 'string' },
41
- 'default-action': { type: 'string' },
42
- 'audit-log': { type: 'string' },
43
- 'via-apes': { type: 'boolean' },
44
- },
45
- strict: false,
46
- });
47
- const interactive = !flags['agent-email'];
48
- console.log('\n🐾 OpenApe Proxy Installer (via apes)\n');
49
- console.log('Each privileged step requires approval from your admin.\n');
50
- let idpUrl, agentEmail, listen, defaultAction, auditLog;
51
- if (interactive) {
52
- idpUrl = await ask('IdP URL', 'https://id.test.openape.at');
53
- agentEmail = await ask('Agent email');
54
- listen = await ask('Listen address', '127.0.0.1:9090');
55
- defaultAction = await ask('Default action (block/request/request-async)', 'block');
56
- auditLog = await ask('Audit log path', '/var/log/openape-proxy/audit.log');
57
- closePrompt();
58
- }
59
- else {
60
- idpUrl = flags['idp-url'] || 'https://id.test.openape.at';
61
- agentEmail = flags['agent-email'];
62
- listen = flags['listen'] || '127.0.0.1:9090';
63
- defaultAction = flags['default-action'] || 'block';
64
- auditLog = flags['audit-log'] || '/var/log/openape-proxy/audit.log';
65
- console.log(` IdP: ${idpUrl}`);
66
- console.log(` Agent: ${agentEmail}`);
67
- console.log(` Listen: ${listen}`);
68
- console.log(` Default: ${defaultAction}`);
69
- console.log(` Audit: ${auditLog}`);
70
- }
71
- // Generate config locally in /tmp
72
- const config = `# OpenApe Proxy Configuration
73
- # Generated by: openape install-proxy --via-apes
74
-
75
- [proxy]
76
- listen = "${listen}"
77
- idp_url = "${idpUrl}"
78
- agent_email = "${agentEmail}"
79
- default_action = "${defaultAction}"
80
- audit_log = "${auditLog}"
81
-
82
- # Add your rules below:
83
-
84
- # [[allow]]
85
- # domain = "api.github.com"
86
- # methods = ["GET"]
87
- # note = "GitHub read-only"
88
-
89
- # [[deny]]
90
- # domain = "*.malware.example.com"
91
- # note = "Blocked"
92
-
93
- # [[grant_required]]
94
- # domain = "api.github.com"
95
- # methods = ["POST", "PUT", "DELETE"]
96
- # grant_type = "allow_once"
97
- # note = "Needs approval"
98
- `;
99
- const tmpConfig = '/tmp/openape-proxy-config.toml';
100
- writeFileSync(tmpConfig, config);
101
- console.log(`\n📝 Config prepared at ${tmpConfig}`);
102
- // Step 1: Create config directory
103
- apes('Create OpenApe Proxy config directory', `mkdir -p ${CONFIG_DIR}`);
104
- apes('Set permissions on config directory', `chmod 700 ${CONFIG_DIR}`);
105
- // Step 2: Copy config
106
- apes('Install proxy config', `cp ${tmpConfig} ${CONFIG_PATH}`);
107
- apes('Secure proxy config (root-only)', `chmod 600 ${CONFIG_PATH}`);
108
- // Step 3: Create audit log directory
109
- const auditDir = auditLog.substring(0, auditLog.lastIndexOf('/'));
110
- if (auditDir) {
111
- apes('Create audit log directory', `mkdir -p ${auditDir}`);
112
- }
113
- // Step 4: Clone/update proxy source
114
- if (existsSync(`${INSTALL_DIR}/package.json`)) {
115
- apes('Update proxy source', `git -C ${INSTALL_DIR} pull`);
116
- }
117
- else {
118
- apes('Download proxy source', `git clone https://github.com/openape-ai/proxy.git ${INSTALL_DIR}`);
119
- }
120
- // Step 5: Install dependencies
121
- const bunPath = execSync('which bun', { encoding: 'utf-8' }).trim();
122
- apes('Install proxy dependencies', `${bunPath} install --cwd ${INSTALL_DIR}`);
123
- // Step 6: Set up system service
124
- if (isMacOS()) {
125
- const plist = `<?xml version="1.0" encoding="UTF-8"?>
126
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
127
- <plist version="1.0">
128
- <dict>
129
- <key>Label</key>
130
- <string>${LAUNCHD_LABEL}</string>
131
- <key>ProgramArguments</key>
132
- <array>
133
- <string>${bunPath}</string>
134
- <string>run</string>
135
- <string>${INSTALL_DIR}/src/index.ts</string>
136
- <string>--config</string>
137
- <string>${CONFIG_PATH}</string>
138
- </array>
139
- <key>RunAtLoad</key>
140
- <true/>
141
- <key>KeepAlive</key>
142
- <true/>
143
- <key>StandardOutPath</key>
144
- <string>/var/log/openape-proxy/stdout.log</string>
145
- <key>StandardErrorPath</key>
146
- <string>/var/log/openape-proxy/stderr.log</string>
147
- <key>WorkingDirectory</key>
148
- <string>${INSTALL_DIR}</string>
149
- </dict>
150
- </plist>`;
151
- const tmpPlist = '/tmp/openape-proxy-launchd.plist';
152
- writeFileSync(tmpPlist, plist);
153
- apes('Install launchd service', `cp ${tmpPlist} ${LAUNCHD_PLIST}`);
154
- apes('Load proxy service', `launchctl load ${LAUNCHD_PLIST}`);
155
- }
156
- console.log('\n🎉 OpenApe Proxy installed via apes!\n');
157
- console.log(` Config: ${CONFIG_PATH}`);
158
- console.log(` Proxy: ${INSTALL_DIR}`);
159
- console.log(` Audit log: ${auditLog}`);
160
- console.log(` Listening: ${listen}`);
161
- console.log('\n Every step was approved by your admin. 🔐\n');
162
- }