@openape/cli 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,9 +1,11 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
+ import { arch, platform } from 'node:os';
3
4
  import { requireRoot, ensureDir, writeSecureFile, run, parseFlags, requireFlag } 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);
@@ -16,32 +18,12 @@ export async function installSudo(args) {
16
18
  if (existsSync(BIN_PATH) && !force) {
17
19
  throw new Error(`apes already installed at ${BIN_PATH}. Use --force to reinstall.`);
18
20
  }
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' });
21
+ // Try GitHub Release first, fall back to source build
22
+ const installed = await tryGitHubRelease();
23
+ if (!installed) {
24
+ console.log('No pre-built binary available. Building from source...');
25
+ buildFromSource();
37
26
  }
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
27
  // Config
46
28
  if (existsSync(CONFIG_PATH) && !force) {
47
29
  console.log(`ℹ️ Config preserved: ${CONFIG_PATH}`);
@@ -71,3 +53,77 @@ timeout_secs = ${pollTimeout}
71
53
  console.log(' 2. Admin approves enrollment');
72
54
  console.log(' 3. Use: apes --key ~/.ssh/id_ed25519 --reason "why" -- <command>\n');
73
55
  }
56
+ function getAssetName() {
57
+ const os = platform();
58
+ const cpu = arch();
59
+ const osMap = { darwin: 'apple-darwin', linux: 'unknown-linux-gnu' };
60
+ const archMap = { arm64: 'aarch64', x64: 'x86_64' };
61
+ const osStr = osMap[os];
62
+ const archStr = archMap[cpu];
63
+ if (!osStr || !archStr) {
64
+ throw new Error(`Unsupported platform: ${os}-${cpu}`);
65
+ }
66
+ return `apes-${archStr}-${osStr}`;
67
+ }
68
+ async function tryGitHubRelease() {
69
+ try {
70
+ // Check if gh CLI is available
71
+ run('which gh');
72
+ }
73
+ catch {
74
+ console.log('GitHub CLI (gh) not found — skipping release download.');
75
+ return false;
76
+ }
77
+ try {
78
+ const assetName = getAssetName();
79
+ console.log(`Looking for pre-built binary: ${assetName}...`);
80
+ // Download latest release asset
81
+ const tmpBin = '/tmp/apes-download';
82
+ execSync(`gh release download --repo ${REPO} --pattern "${assetName}" --output ${tmpBin} --clobber`, {
83
+ stdio: 'pipe',
84
+ timeout: 30000,
85
+ });
86
+ if (!existsSync(tmpBin)) {
87
+ return false;
88
+ }
89
+ // Install
90
+ execSync(`cp ${tmpBin} ${BIN_PATH}`);
91
+ execSync(`chown root:wheel ${BIN_PATH}`);
92
+ execSync(`chmod u+s ${BIN_PATH}`);
93
+ execSync(`rm -f ${tmpBin}`);
94
+ console.log(`✅ Binary (pre-built): ${BIN_PATH} (setuid root)`);
95
+ return true;
96
+ }
97
+ catch {
98
+ console.log('No pre-built binary found for this platform.');
99
+ return false;
100
+ }
101
+ }
102
+ function buildFromSource() {
103
+ // Check Rust toolchain
104
+ try {
105
+ run('cargo --version');
106
+ }
107
+ catch {
108
+ throw new Error('Rust toolchain required to build apes. Install it: https://rustup.rs\n' +
109
+ 'Or create a GitHub Release with pre-built binaries.');
110
+ }
111
+ const buildDir = '/tmp/openape-sudo-build';
112
+ if (existsSync(`${buildDir}/.git`)) {
113
+ console.log('Updating source...');
114
+ execSync(`git config --global --add safe.directory ${buildDir}`, { stdio: 'ignore' });
115
+ execSync(`cd ${buildDir} && git pull`, { stdio: 'inherit' });
116
+ }
117
+ else {
118
+ console.log('Downloading source...');
119
+ execSync(`rm -rf ${buildDir}`, { stdio: 'ignore' });
120
+ execSync(`git clone https://github.com/${REPO}.git ${buildDir}`, { stdio: 'inherit' });
121
+ }
122
+ console.log('Building apes (release mode)...');
123
+ execSync(`cd ${buildDir} && cargo build --release`, { stdio: 'inherit' });
124
+ // Install binary with setuid
125
+ execSync(`cp ${buildDir}/target/release/apes ${BIN_PATH}`);
126
+ execSync(`chown root:wheel ${BIN_PATH}`);
127
+ execSync(`chmod u+s ${BIN_PATH}`);
128
+ console.log(`✅ Binary (built): ${BIN_PATH} (setuid root)`);
129
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openape/cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
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
- }