@openape/cli 0.1.3 → 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.
package/README.md CHANGED
@@ -1,52 +1,61 @@
1
1
  # @openape/cli
2
2
 
3
- Install and manage OpenApe components.
3
+ Install and manage OpenApe components — proxy, sudo (`apes`), and more.
4
4
 
5
5
  ## Usage
6
6
 
7
+ ### With traditional sudo
8
+
7
9
  ```bash
8
- # Install the HTTP proxy (grant-controlled outbound traffic)
9
- sudo npx @openape/cli install-proxy
10
+ sudo npx @openape/cli install-proxy \
11
+ --idp-url https://id.openape.at \
12
+ --agent-email bot@example.com
10
13
 
11
- # Install apes (privilege elevation via grants)
12
- sudo npx @openape/cli install-sudo
14
+ sudo npx @openape/cli install-sudo \
15
+ --idp-url https://id.openape.at
13
16
  ```
14
17
 
15
- ## Commands
18
+ ### With apes (agent-friendly, grant-approved)
16
19
 
17
- ### `install-proxy`
20
+ ```bash
21
+ apes -- npx @openape/cli install-proxy \
22
+ --idp-url https://id.openape.at \
23
+ --agent-email bot@example.com
24
+ ```
18
25
 
19
- Installs the OpenApe HTTP proxy as a system service:
26
+ The CLI itself has no knowledge of `apes` — it just writes files and sets up services. `apes` wraps it for privilege elevation with human-approved grants.
20
27
 
21
- - Creates `/etc/openape-proxy/config.toml` (root-only, `600`)
22
- - Downloads proxy source to `/opt/openape-proxy`
23
- - Sets up launchd (macOS) or systemd (Linux) service
24
- - Starts the proxy
28
+ ## Commands
25
29
 
26
- The proxy runs as a system service. Agents send HTTP requests through it.
27
- The config is **not accessible to agents** — only root can modify it.
30
+ ### `install-proxy`
28
31
 
29
- ### `install-sudo`
32
+ Installs the [OpenApe HTTP Proxy](https://github.com/openape-ai/proxy).
30
33
 
31
- Builds and installs `apes` (OpenApe Sudo):
34
+ | Flag | Required | Default | Description |
35
+ |------|----------|---------|-------------|
36
+ | `--idp-url` | ✅ | — | Identity Provider URL |
37
+ | `--agent-email` | ✅ | — | Agent email identity |
38
+ | `--listen` | | `127.0.0.1:9090` | Listen address |
39
+ | `--default-action` | | `block` | `block`, `request`, or `request-async` |
40
+ | `--audit-log` | | `/var/log/openape-proxy/audit.log` | Audit log path |
41
+ | `--force` | | | Overwrite existing config |
32
42
 
33
- - Builds from source via Cargo (Rust toolchain required)
34
- - Installs to `/usr/local/bin/apes` with setuid bit
35
- - Creates `/etc/apes/config.toml` (root-only, `600`)
43
+ ### `install-sudo`
36
44
 
37
- After installation, register the agent on the IdP with `apes enroll`.
45
+ Installs [apes](https://github.com/openape-ai/sudo) (OpenApe privilege elevation). Requires Rust toolchain.
38
46
 
39
- ## Security
47
+ | Flag | Required | Default | Description |
48
+ |------|----------|---------|-------------|
49
+ | `--idp-url` | ✅ | — | Identity Provider URL |
50
+ | `--poll-timeout` | | `300` | Grant poll timeout (seconds) |
51
+ | `--poll-interval` | | `2` | Grant poll interval (seconds) |
52
+ | `--force` | | | Overwrite existing binary/config |
40
53
 
41
- Both installers create root-owned config files that agents cannot modify.
42
- This is by design — the security boundary between agent and config must be
43
- enforced by the OS file permission system.
54
+ ## Requirements
44
55
 
45
- | Path | Owner | Mode | Purpose |
46
- |------|-------|------|---------|
47
- | `/etc/openape-proxy/config.toml` | root | `600` | Proxy rules |
48
- | `/etc/apes/config.toml` | root | `600` | apes config |
49
- | `/usr/local/bin/apes` | root | `4755` (setuid) | Privilege elevation |
56
+ - **install-proxy**: [Bun](https://bun.sh) runtime
57
+ - **install-sudo**: [Rust](https://rustup.rs) toolchain
58
+ - Root privileges (via `sudo` or `apes`)
50
59
 
51
60
  ## License
52
61
 
@@ -1,42 +1,36 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
- import { ask, closePrompt, requireRoot, ensureDir, writeSecureFile, isMacOS, isLinux, run } from '../utils.js';
3
+ import { requireRoot, ensureDir, writeSecureFile, isMacOS, isLinux, run, parseFlags, requireFlag } from '../utils.js';
4
4
  const CONFIG_DIR = '/etc/openape-proxy';
5
5
  const CONFIG_PATH = `${CONFIG_DIR}/config.toml`;
6
+ const INSTALL_DIR = '/opt/openape-proxy';
6
7
  const LAUNCHD_LABEL = 'ai.openape.proxy';
7
8
  const LAUNCHD_PLIST = `/Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`;
8
9
  const SYSTEMD_UNIT = '/etc/systemd/system/openape-proxy.service';
9
- export async function installProxy() {
10
+ export async function installProxy(args) {
10
11
  requireRoot();
12
+ const flags = parseFlags(args);
13
+ const idpUrl = requireFlag(flags, 'idp-url');
14
+ const agentEmail = requireFlag(flags, 'agent-email');
15
+ const listen = flags['listen'] || '127.0.0.1:9090';
16
+ const defaultAction = flags['default-action'] || 'block';
17
+ const auditLog = flags['audit-log'] || '/var/log/openape-proxy/audit.log';
18
+ const force = flags['force'] === true;
11
19
  console.log('\n🐾 OpenApe Proxy Installer\n');
12
- // Check if bun is available
20
+ // Check bun
13
21
  try {
14
22
  run('which bun');
15
23
  }
16
24
  catch {
17
25
  throw new Error('Bun is required but not found. Install it: https://bun.sh');
18
26
  }
19
- // Check if already installed
20
- if (existsSync(CONFIG_PATH)) {
21
- const overwrite = await ask('Config already exists. Overwrite?', 'n');
22
- if (overwrite.toLowerCase() !== 'y') {
23
- console.log('Aborted.');
24
- closePrompt();
25
- return;
26
- }
27
+ // Check existing config
28
+ if (existsSync(CONFIG_PATH) && !force) {
29
+ throw new Error(`Config already exists at ${CONFIG_PATH}. Use --force to overwrite.`);
27
30
  }
28
- // Gather config
29
- const idpUrl = await ask('IdP URL', 'https://id.test.openape.at');
30
- const agentEmail = await ask('Agent email');
31
- const listen = await ask('Listen address', '127.0.0.1:9090');
32
- const defaultAction = await ask('Default action (block/request/request-async)', 'block');
33
- const auditLog = await ask('Audit log path', '/var/log/openape-proxy/audit.log');
34
- console.log('\nConfiguring rules...');
35
- console.log('(You can edit the config later at ' + CONFIG_PATH + ')\n');
36
31
  // Generate config
37
32
  const config = `# OpenApe Proxy Configuration
38
33
  # Generated by: openape install-proxy
39
- # Edit this file to add/remove rules. Restart the service after changes.
40
34
 
41
35
  [proxy]
42
36
  listen = "${listen}"
@@ -45,7 +39,7 @@ agent_email = "${agentEmail}"
45
39
  default_action = "${defaultAction}"
46
40
  audit_log = "${auditLog}"
47
41
 
48
- # Example rules — customize these for your use case:
42
+ # Rules — customize for your use case:
49
43
 
50
44
  # [[allow]]
51
45
  # domain = "api.github.com"
@@ -60,64 +54,44 @@ audit_log = "${auditLog}"
60
54
  # domain = "api.github.com"
61
55
  # methods = ["POST", "PUT", "DELETE"]
62
56
  # grant_type = "allow_once"
63
- # note = "GitHub API write operations need approval"
57
+ # note = "GitHub API writes need approval"
64
58
  `;
65
59
  // Write config
66
60
  ensureDir(CONFIG_DIR, 0o700);
67
61
  writeSecureFile(CONFIG_PATH, config);
68
- console.log(`✅ Config written to ${CONFIG_PATH}`);
69
- // Create audit log directory
62
+ console.log(`✅ Config: ${CONFIG_PATH}`);
63
+ // Audit log dir
70
64
  const auditDir = auditLog.substring(0, auditLog.lastIndexOf('/'));
71
65
  if (auditDir) {
72
66
  ensureDir(auditDir, 0o755);
73
- console.log(`✅ Audit log directory created: ${auditDir}`);
67
+ console.log(`✅ Audit log dir: ${auditDir}`);
74
68
  }
75
- // Install proxy source
76
- const installDir = '/opt/openape-proxy';
77
- ensureDir(installDir, 0o755);
78
- // Clone or update proxy source
79
- if (existsSync(`${installDir}/package.json`)) {
80
- console.log('Updating proxy source...');
81
- execSync(`cd ${installDir} && git pull`, { stdio: 'inherit' });
69
+ // Install proxy via npm/bun (no git needed)
70
+ ensureDir(INSTALL_DIR, 0o755);
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' });
82
74
  }
83
- else {
84
- console.log('Downloading proxy source...');
85
- execSync(`git clone https://github.com/openape-ai/proxy.git ${installDir}`, { stdio: 'inherit' });
86
- }
87
- // Install dependencies
88
- console.log('Installing dependencies...');
89
- execSync(`cd ${installDir} && bun install`, { stdio: 'inherit' });
90
- // Set up system service
75
+ execSync(`cd ${INSTALL_DIR} && bun add @openape/proxy`, { stdio: 'inherit' });
76
+ console.log(`✅ Proxy installed: ${INSTALL_DIR}`);
77
+ // System service
91
78
  if (isMacOS()) {
92
- await setupLaunchd(installDir);
79
+ setupLaunchd();
93
80
  }
94
81
  else if (isLinux()) {
95
- await setupSystemd(installDir);
82
+ setupSystemd();
96
83
  }
97
84
  else {
98
- console.log('\n⚠️ Unsupported platform for service setup. Start manually:');
99
- console.log(` cd ${installDir} && bun run src/index.ts --config ${CONFIG_PATH}`);
85
+ console.log(`\n⚠️ Manual start: cd ${INSTALL_DIR} && bunx openape-proxy --config ${CONFIG_PATH}`);
100
86
  }
101
- closePrompt();
102
87
  console.log('\n🎉 OpenApe Proxy installed!\n');
103
- console.log(` Config: ${CONFIG_PATH}`);
104
- console.log(` Proxy: ${installDir}`);
105
- console.log(` Audit log: ${auditLog}`);
106
- console.log(` Listening: ${listen}`);
107
- console.log(`\n Edit ${CONFIG_PATH} to configure rules.`);
108
- console.log(` The proxy must be restarted after config changes.\n`);
109
- // Quick test
110
- try {
111
- const port = listen.split(':')[1] || '9090';
112
- const host = listen.split(':')[0] || '127.0.0.1';
113
- const res = execSync(`curl -s http://${host}:${port}/healthz`, { timeout: 3000, encoding: 'utf-8' });
114
- console.log(` Health check: ${res}`);
115
- }
116
- catch {
117
- console.log(' ⚠️ Health check failed — service may still be starting.');
118
- }
88
+ console.log(` Config: ${CONFIG_PATH}`);
89
+ console.log(` Source: ${INSTALL_DIR}`);
90
+ console.log(` Listen: ${listen}`);
91
+ console.log(` Audit: ${auditLog}`);
92
+ console.log(`\n Edit ${CONFIG_PATH} to configure rules, then restart the service.\n`);
119
93
  }
120
- async function setupLaunchd(installDir) {
94
+ function setupLaunchd() {
121
95
  const bunPath = run('which bun');
122
96
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
123
97
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -129,7 +103,7 @@ async function setupLaunchd(installDir) {
129
103
  <array>
130
104
  <string>${bunPath}</string>
131
105
  <string>run</string>
132
- <string>${installDir}/src/index.ts</string>
106
+ <string>${INSTALL_DIR}/node_modules/@openape/proxy/src/index.ts</string>
133
107
  <string>--config</string>
134
108
  <string>${CONFIG_PATH}</string>
135
109
  </array>
@@ -142,20 +116,18 @@ async function setupLaunchd(installDir) {
142
116
  <key>StandardErrorPath</key>
143
117
  <string>/var/log/openape-proxy/stderr.log</string>
144
118
  <key>WorkingDirectory</key>
145
- <string>${installDir}</string>
119
+ <string>${INSTALL_DIR}</string>
146
120
  </dict>
147
121
  </plist>`;
148
122
  writeSecureFile(LAUNCHD_PLIST, plist, 0o644);
149
- console.log(`✅ launchd service created: ${LAUNCHD_LABEL}`);
150
- // Stop if already running
151
123
  try {
152
124
  execSync(`launchctl unload ${LAUNCHD_PLIST}`, { stdio: 'ignore' });
153
125
  }
154
126
  catch { }
155
127
  execSync(`launchctl load ${LAUNCHD_PLIST}`);
156
- console.log('✅ Service started');
128
+ console.log(`✅ launchd service: ${LAUNCHD_LABEL}`);
157
129
  }
158
- async function setupSystemd(installDir) {
130
+ function setupSystemd() {
159
131
  const bunPath = run('which bun');
160
132
  const unit = `[Unit]
161
133
  Description=OpenApe HTTP Proxy
@@ -163,18 +135,13 @@ After=network.target
163
135
 
164
136
  [Service]
165
137
  Type=simple
166
- ExecStart=${bunPath} run ${installDir}/src/index.ts --config ${CONFIG_PATH}
167
- WorkingDirectory=${installDir}
138
+ ExecStart=${bunPath} run ${INSTALL_DIR}/node_modules/@openape/proxy/src/index.ts --config ${CONFIG_PATH}
139
+ WorkingDirectory=${INSTALL_DIR}
168
140
  Restart=on-failure
169
141
  RestartSec=5
170
- StandardOutput=journal
171
- StandardError=journal
172
-
173
- # Security hardening
174
142
  NoNewPrivileges=true
175
143
  ProtectSystem=strict
176
144
  ProtectHome=true
177
- ReadOnlyPaths=/
178
145
  ReadWritePaths=/var/log/openape-proxy
179
146
 
180
147
  [Install]
@@ -184,5 +151,5 @@ WantedBy=multi-user.target
184
151
  execSync('systemctl daemon-reload');
185
152
  execSync('systemctl enable openape-proxy');
186
153
  execSync('systemctl start openape-proxy');
187
- console.log('✅ systemd service created and started: openape-proxy');
154
+ console.log('✅ systemd service: openape-proxy');
188
155
  }
@@ -1,44 +1,123 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
- import { ask, closePrompt, requireRoot, ensureDir, writeSecureFile, run } from '../utils.js';
3
+ import { arch, platform } from 'node:os';
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';
7
- export async function installSudo() {
8
+ const REPO = 'openape-ai/sudo';
9
+ export async function installSudo(args) {
8
10
  requireRoot();
11
+ const flags = parseFlags(args);
12
+ const idpUrl = requireFlag(flags, 'idp-url');
13
+ const pollTimeout = flags['poll-timeout'] || '300';
14
+ const pollInterval = flags['poll-interval'] || '2';
15
+ const force = flags['force'] === true;
9
16
  console.log('\n🐾 OpenApe Sudo (apes) Installer\n');
10
- // Check if already installed
11
- if (existsSync(BIN_PATH)) {
12
- const overwrite = await ask('apes is already installed. Reinstall?', 'n');
13
- if (overwrite.toLowerCase() !== 'y') {
14
- console.log('Aborted.');
15
- closePrompt();
16
- return;
17
+ // Check existing binary
18
+ if (existsSync(BIN_PATH) && !force) {
19
+ throw new Error(`apes already installed at ${BIN_PATH}. Use --force to reinstall.`);
20
+ }
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();
26
+ }
27
+ // Config
28
+ if (existsSync(CONFIG_PATH) && !force) {
29
+ console.log(`ℹ️ Config preserved: ${CONFIG_PATH}`);
30
+ }
31
+ else {
32
+ const config = `# OpenApe Sudo (apes) Configuration
33
+ # Generated by: openape install-sudo
34
+
35
+ server_url = "${idpUrl}"
36
+
37
+ # Agent identity is determined by the key passed via --key flag.
38
+ # Each user provides their own key: apes --key ~/.ssh/id_ed25519 -- <command>
39
+
40
+ [poll]
41
+ interval_secs = ${pollInterval}
42
+ timeout_secs = ${pollTimeout}
43
+ `;
44
+ ensureDir(CONFIG_DIR, 0o700);
45
+ writeSecureFile(CONFIG_PATH, config);
46
+ console.log(`✅ Config: ${CONFIG_PATH}`);
47
+ }
48
+ console.log('\n🎉 apes installed!\n');
49
+ console.log(` Binary: ${BIN_PATH} (setuid root)`);
50
+ console.log(` Config: ${CONFIG_PATH}`);
51
+ console.log('\n Next steps:');
52
+ console.log(` 1. Enroll: apes enroll --server ${idpUrl} --agent-email <email> --agent-name <name> --key <path>`);
53
+ console.log(' 2. Admin approves enrollment');
54
+ console.log(' 3. Use: apes --key ~/.ssh/id_ed25519 --reason "why" -- <command>\n');
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;
17
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;
18
100
  }
19
- // Check for Rust toolchain
20
- let hasCargo = false;
101
+ }
102
+ function buildFromSource() {
103
+ // Check Rust toolchain
21
104
  try {
22
105
  run('cargo --version');
23
- hasCargo = true;
24
106
  }
25
- catch { }
26
- if (!hasCargo) {
27
- console.log('⚠️ Rust/Cargo not found. Checking for pre-built binary...');
28
- // TODO: download pre-built binary from GitHub releases
107
+ catch {
29
108
  throw new Error('Rust toolchain required to build apes. Install it: https://rustup.rs\n' +
30
- 'Pre-built binaries will be available in a future release.');
109
+ 'Or create a GitHub Release with pre-built binaries.');
31
110
  }
32
- // Build from source
33
111
  const buildDir = '/tmp/openape-sudo-build';
34
112
  if (existsSync(`${buildDir}/.git`)) {
35
113
  console.log('Updating source...');
114
+ execSync(`git config --global --add safe.directory ${buildDir}`, { stdio: 'ignore' });
36
115
  execSync(`cd ${buildDir} && git pull`, { stdio: 'inherit' });
37
116
  }
38
117
  else {
39
118
  console.log('Downloading source...');
40
119
  execSync(`rm -rf ${buildDir}`, { stdio: 'ignore' });
41
- execSync(`git clone https://github.com/openape-ai/sudo.git ${buildDir}`, { stdio: 'inherit' });
120
+ execSync(`git clone https://github.com/${REPO}.git ${buildDir}`, { stdio: 'inherit' });
42
121
  }
43
122
  console.log('Building apes (release mode)...');
44
123
  execSync(`cd ${buildDir} && cargo build --release`, { stdio: 'inherit' });
@@ -46,41 +125,5 @@ export async function installSudo() {
46
125
  execSync(`cp ${buildDir}/target/release/apes ${BIN_PATH}`);
47
126
  execSync(`chown root:wheel ${BIN_PATH}`);
48
127
  execSync(`chmod u+s ${BIN_PATH}`);
49
- console.log(`✅ apes installed at ${BIN_PATH} (setuid root)`);
50
- // Config setup
51
- if (existsSync(CONFIG_PATH)) {
52
- const overwriteConfig = await ask('Config already exists. Overwrite?', 'n');
53
- if (overwriteConfig.toLowerCase() !== 'y') {
54
- closePrompt();
55
- console.log('\n🎉 apes binary updated! Existing config preserved.\n');
56
- return;
57
- }
58
- }
59
- const serverUrl = await ask('IdP URL', 'https://id.test.openape.at');
60
- const timeoutSecs = await ask('Poll timeout (seconds)', '300');
61
- const intervalSecs = await ask('Poll interval (seconds)', '2');
62
- const config = `# OpenApe Sudo (apes) Configuration
63
- # Generated by: openape install-sudo
64
-
65
- server_url = "${serverUrl}"
66
-
67
- # Agent identity is determined by the key passed via --key flag.
68
- # Each user provides their own key: apes --key ~/.ssh/id_ed25519 -- <command>
69
-
70
- [poll]
71
- interval_secs = ${intervalSecs}
72
- timeout_secs = ${timeoutSecs}
73
- `;
74
- ensureDir(CONFIG_DIR, 0o700);
75
- writeSecureFile(CONFIG_PATH, config);
76
- console.log(`✅ Config written to ${CONFIG_PATH}`);
77
- closePrompt();
78
- console.log('\n🎉 apes installed!\n');
79
- console.log(` Binary: ${BIN_PATH} (setuid root)`);
80
- console.log(` Config: ${CONFIG_PATH}`);
81
- console.log('\n Next steps:');
82
- console.log(' 1. Register this agent on the IdP:');
83
- console.log(` sudo apes enroll --server ${serverUrl} --agent-email <email> --agent-name <name> --key <path>`);
84
- console.log(' 2. Ask your admin to approve the enrollment');
85
- console.log(' 3. Use it: apes --key ~/.ssh/id_ed25519 --reason "why" -- <command>\n');
128
+ console.log(`✅ Binary (built): ${BIN_PATH} (setuid root)`);
86
129
  }
package/dist/index.js CHANGED
@@ -1,36 +1,47 @@
1
1
  #!/usr/bin/env node
2
2
  import { installProxy } from './commands/install-proxy.js';
3
- import { installProxyApes } from './commands/install-proxy-apes.js';
4
3
  import { installSudo } from './commands/install-sudo.js';
5
4
  const command = process.argv[2];
5
+ const args = process.argv.slice(3);
6
6
  const HELP = `
7
7
  OpenApe CLI — install and manage OpenApe components
8
8
 
9
- Usage: openape <command> [options]
9
+ Usage: openape <command> [flags]
10
10
 
11
11
  Commands:
12
- install-proxy Install the OpenApe HTTP proxy (requires sudo)
13
- install-proxy --via-apes Install the proxy using apes for elevation (no sudo needed!)
14
- install-sudo Install apes privilege elevation (requires sudo)
15
- help Show this help message
12
+ install-proxy Install the OpenApe HTTP proxy
13
+ install-sudo Install apes privilege elevation binary
14
+
15
+ Elevation:
16
+ sudo openape install-proxy ... # traditional sudo
17
+ apes -- npx @openape/cli install-proxy ... # agent-friendly, grant-approved
18
+
19
+ install-proxy flags:
20
+ --idp-url <url> IdP URL (required)
21
+ --agent-email <email> Agent email (required)
22
+ --listen <addr> Listen address (default: 127.0.0.1:9090)
23
+ --default-action <act> Default action: block|request|request-async (default: block)
24
+ --audit-log <path> Audit log path (default: /var/log/openape-proxy/audit.log)
25
+ --force Overwrite existing config
26
+
27
+ install-sudo flags:
28
+ --idp-url <url> IdP URL (required)
29
+ --poll-timeout <secs> Poll timeout in seconds (default: 300)
30
+ --poll-interval <secs> Poll interval in seconds (default: 2)
31
+ --force Overwrite existing config/binary
16
32
 
17
33
  Examples:
18
- sudo openape install-proxy # traditional sudo
19
- openape install-proxy --via-apes # agent-friendly, each step approved
20
- sudo openape install-sudo
34
+ sudo openape install-proxy --idp-url https://id.openape.at --agent-email bot@example.com
35
+ 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
21
37
  `;
22
38
  async function main() {
23
39
  switch (command) {
24
40
  case 'install-proxy':
25
- if (process.argv.includes('--via-apes')) {
26
- await installProxyApes();
27
- }
28
- else {
29
- await installProxy();
30
- }
41
+ await installProxy(args);
31
42
  break;
32
43
  case 'install-sudo':
33
- await installSudo();
44
+ await installSudo(args);
34
45
  break;
35
46
  case 'help':
36
47
  case '--help':
package/dist/utils.js CHANGED
@@ -1,22 +1,9 @@
1
- import { createInterface } from 'node:readline';
2
1
  import { execSync } from 'node:child_process';
3
2
  import { existsSync, mkdirSync, writeFileSync, chmodSync, chownSync } from 'node:fs';
4
3
  import { platform } from 'node:os';
5
- const rl = createInterface({ input: process.stdin, output: process.stdout });
6
- export function ask(question, defaultValue) {
7
- const suffix = defaultValue ? ` [${defaultValue}]` : '';
8
- return new Promise((resolve) => {
9
- rl.question(`${question}${suffix}: `, (answer) => {
10
- resolve(answer.trim() || defaultValue || '');
11
- });
12
- });
13
- }
14
- export function closePrompt() {
15
- rl.close();
16
- }
17
4
  export function requireRoot() {
18
5
  if (process.getuid?.() !== 0) {
19
- throw new Error('This command must be run as root (use sudo)');
6
+ throw new Error('This command must be run as root. Use: sudo npx @openape/cli ... or: apes -- npx @openape/cli ...');
20
7
  }
21
8
  }
22
9
  export function ensureDir(path, mode = 0o755) {
@@ -28,7 +15,6 @@ export function ensureDir(path, mode = 0o755) {
28
15
  export function writeSecureFile(path, content, mode = 0o600) {
29
16
  writeFileSync(path, content, 'utf-8');
30
17
  chmodSync(path, mode);
31
- // Ensure root ownership
32
18
  try {
33
19
  chownSync(path, 0, 0);
34
20
  }
@@ -43,15 +29,28 @@ export function isMacOS() {
43
29
  export function isLinux() {
44
30
  return platform() === 'linux';
45
31
  }
46
- export function serviceExists(name) {
47
- if (isMacOS()) {
48
- return existsSync(`/Library/LaunchDaemons/${name}.plist`);
49
- }
50
- try {
51
- execSync(`systemctl cat ${name}`, { stdio: 'ignore' });
52
- return true;
32
+ export function parseFlags(args) {
33
+ const flags = {};
34
+ for (let i = 0; i < args.length; i++) {
35
+ const arg = args[i];
36
+ if (arg.startsWith('--')) {
37
+ const key = arg.slice(2);
38
+ const next = args[i + 1];
39
+ if (next && !next.startsWith('--')) {
40
+ flags[key] = next;
41
+ i++;
42
+ }
43
+ else {
44
+ flags[key] = true;
45
+ }
46
+ }
53
47
  }
54
- catch {
55
- return false;
48
+ return flags;
49
+ }
50
+ export function requireFlag(flags, name) {
51
+ const val = flags[name];
52
+ if (!val || val === true) {
53
+ throw new Error(`Missing required flag: --${name}`);
56
54
  }
55
+ return val;
57
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openape/cli",
3
- "version": "0.1.3",
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,135 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { execSync } from 'node:child_process';
3
- import { writeFileSync } from 'node:fs';
4
- import { ask, closePrompt, isMacOS } from '../utils.js';
5
- const CONFIG_DIR = '/etc/openape-proxy';
6
- const CONFIG_PATH = `${CONFIG_DIR}/config.toml`;
7
- const INSTALL_DIR = '/opt/openape-proxy';
8
- const LAUNCHD_LABEL = 'ai.openape.proxy';
9
- const LAUNCHD_PLIST = `/Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`;
10
- function apes(reason, cmd) {
11
- // Find apes key — prefer user's SSH key
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
- console.log('\n🐾 OpenApe Proxy Installer (via apes)\n');
35
- console.log('This installer uses apes for privilege elevation.');
36
- console.log('Each privileged step requires approval from your admin.\n');
37
- // Gather config interactively (no root needed)
38
- const idpUrl = await ask('IdP URL', 'https://id.test.openape.at');
39
- const agentEmail = await ask('Agent email');
40
- const listen = await ask('Listen address', '127.0.0.1:9090');
41
- const defaultAction = await ask('Default action (block/request/request-async)', 'block');
42
- const auditLog = await ask('Audit log path', '/var/log/openape-proxy/audit.log');
43
- closePrompt();
44
- // Generate config locally in /tmp
45
- const config = `# OpenApe Proxy Configuration
46
- # Generated by: openape install-proxy --via-apes
47
-
48
- [proxy]
49
- listen = "${listen}"
50
- idp_url = "${idpUrl}"
51
- agent_email = "${agentEmail}"
52
- default_action = "${defaultAction}"
53
- audit_log = "${auditLog}"
54
-
55
- # Add your rules below:
56
-
57
- # [[allow]]
58
- # domain = "api.github.com"
59
- # methods = ["GET"]
60
- # note = "GitHub read-only"
61
-
62
- # [[deny]]
63
- # domain = "*.malware.example.com"
64
- # note = "Blocked"
65
-
66
- # [[grant_required]]
67
- # domain = "api.github.com"
68
- # methods = ["POST", "PUT", "DELETE"]
69
- # grant_type = "allow_once"
70
- # note = "Needs approval"
71
- `;
72
- const tmpConfig = '/tmp/openape-proxy-config.toml';
73
- writeFileSync(tmpConfig, config);
74
- console.log(`\n📝 Config prepared at ${tmpConfig}`);
75
- // Step 1: Create config directory
76
- apes('Create OpenApe Proxy config directory', `mkdir -p ${CONFIG_DIR}`);
77
- apes('Set permissions on config directory', `chmod 700 ${CONFIG_DIR}`);
78
- // Step 2: Copy config
79
- apes('Install proxy config', `cp ${tmpConfig} ${CONFIG_PATH}`);
80
- apes('Secure proxy config (root-only)', `chmod 600 ${CONFIG_PATH}`);
81
- // Step 3: Create audit log directory
82
- const auditDir = auditLog.substring(0, auditLog.lastIndexOf('/'));
83
- if (auditDir) {
84
- apes('Create audit log directory', `mkdir -p ${auditDir}`);
85
- }
86
- // Step 4: Clone/update proxy source
87
- if (existsSync(`${INSTALL_DIR}/package.json`)) {
88
- apes('Update proxy source', `git -C ${INSTALL_DIR} pull`);
89
- }
90
- else {
91
- apes('Download proxy source', `git clone https://github.com/openape-ai/proxy.git ${INSTALL_DIR}`);
92
- }
93
- // Step 5: Install dependencies
94
- const bunPath = execSync('which bun', { encoding: 'utf-8' }).trim();
95
- apes('Install proxy dependencies', `${bunPath} install --cwd ${INSTALL_DIR}`);
96
- // Step 6: Set up system service
97
- if (isMacOS()) {
98
- const plist = `<?xml version="1.0" encoding="UTF-8"?>
99
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
100
- <plist version="1.0">
101
- <dict>
102
- <key>Label</key>
103
- <string>${LAUNCHD_LABEL}</string>
104
- <key>ProgramArguments</key>
105
- <array>
106
- <string>${bunPath}</string>
107
- <string>run</string>
108
- <string>${INSTALL_DIR}/src/index.ts</string>
109
- <string>--config</string>
110
- <string>${CONFIG_PATH}</string>
111
- </array>
112
- <key>RunAtLoad</key>
113
- <true/>
114
- <key>KeepAlive</key>
115
- <true/>
116
- <key>StandardOutPath</key>
117
- <string>/var/log/openape-proxy/stdout.log</string>
118
- <key>StandardErrorPath</key>
119
- <string>/var/log/openape-proxy/stderr.log</string>
120
- <key>WorkingDirectory</key>
121
- <string>${INSTALL_DIR}</string>
122
- </dict>
123
- </plist>`;
124
- const tmpPlist = '/tmp/openape-proxy-launchd.plist';
125
- writeFileSync(tmpPlist, plist);
126
- apes('Install launchd service', `cp ${tmpPlist} ${LAUNCHD_PLIST}`);
127
- apes('Load proxy service', `launchctl load ${LAUNCHD_PLIST}`);
128
- }
129
- console.log('\n🎉 OpenApe Proxy installed via apes!\n');
130
- console.log(` Config: ${CONFIG_PATH}`);
131
- console.log(` Proxy: ${INSTALL_DIR}`);
132
- console.log(` Audit log: ${auditLog}`);
133
- console.log(` Listening: ${listen}`);
134
- console.log('\n Every step was approved by your admin. 🔐\n');
135
- }