@openape/cli 0.1.3 → 0.1.4

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,6 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { writeFileSync } from 'node:fs';
4
+ import { parseArgs } from 'node:util';
4
5
  import { ask, closePrompt, isMacOS } from '../utils.js';
5
6
  const CONFIG_DIR = '/etc/openape-proxy';
6
7
  const CONFIG_PATH = `${CONFIG_DIR}/config.toml`;
@@ -8,7 +9,6 @@ const INSTALL_DIR = '/opt/openape-proxy';
8
9
  const LAUNCHD_LABEL = 'ai.openape.proxy';
9
10
  const LAUNCHD_PLIST = `/Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`;
10
11
  function apes(reason, cmd) {
11
- // Find apes key — prefer user's SSH key
12
12
  const keyPaths = [
13
13
  `${process.env.HOME}/.ssh/id_ed25519`,
14
14
  `${process.env.HOME}/.ssh/id_ecdsa`,
@@ -31,16 +31,43 @@ export async function installProxyApes() {
31
31
  catch {
32
32
  throw new Error('apes is not installed. Run: sudo npx @openape/cli install-sudo');
33
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'];
34
48
  console.log('\n🐾 OpenApe Proxy Installer (via apes)\n');
35
- console.log('This installer uses apes for privilege elevation.');
36
49
  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();
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
+ }
44
71
  // Generate config locally in /tmp
45
72
  const config = `# OpenApe Proxy Configuration
46
73
  # Generated by: openape install-proxy --via-apes
@@ -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,48 @@ 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`)) {
69
+ // Clone/update proxy source
70
+ ensureDir(INSTALL_DIR, 0o755);
71
+ if (existsSync(`${INSTALL_DIR}/package.json`)) {
80
72
  console.log('Updating proxy source...');
81
- execSync(`cd ${installDir} && git pull`, { stdio: 'inherit' });
73
+ execSync(`cd ${INSTALL_DIR} && git pull`, { stdio: 'inherit' });
82
74
  }
83
75
  else {
84
76
  console.log('Downloading proxy source...');
85
- execSync(`git clone https://github.com/openape-ai/proxy.git ${installDir}`, { stdio: 'inherit' });
77
+ execSync(`git clone https://github.com/openape-ai/proxy.git ${INSTALL_DIR}`, { stdio: 'inherit' });
86
78
  }
87
- // Install dependencies
88
79
  console.log('Installing dependencies...');
89
- execSync(`cd ${installDir} && bun install`, { stdio: 'inherit' });
90
- // Set up system service
80
+ execSync(`cd ${INSTALL_DIR} && bun install`, { stdio: 'inherit' });
81
+ // System service
91
82
  if (isMacOS()) {
92
- await setupLaunchd(installDir);
83
+ setupLaunchd();
93
84
  }
94
85
  else if (isLinux()) {
95
- await setupSystemd(installDir);
86
+ setupSystemd();
96
87
  }
97
88
  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}`);
89
+ console.log(`\n⚠️ Manual start: cd ${INSTALL_DIR} && bun run src/index.ts --config ${CONFIG_PATH}`);
100
90
  }
101
- closePrompt();
102
91
  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
- }
92
+ console.log(` Config: ${CONFIG_PATH}`);
93
+ console.log(` Source: ${INSTALL_DIR}`);
94
+ console.log(` Listen: ${listen}`);
95
+ console.log(` Audit: ${auditLog}`);
96
+ console.log(`\n Edit ${CONFIG_PATH} to configure rules, then restart the service.\n`);
119
97
  }
120
- async function setupLaunchd(installDir) {
98
+ function setupLaunchd() {
121
99
  const bunPath = run('which bun');
122
100
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
123
101
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -129,7 +107,7 @@ async function setupLaunchd(installDir) {
129
107
  <array>
130
108
  <string>${bunPath}</string>
131
109
  <string>run</string>
132
- <string>${installDir}/src/index.ts</string>
110
+ <string>${INSTALL_DIR}/src/index.ts</string>
133
111
  <string>--config</string>
134
112
  <string>${CONFIG_PATH}</string>
135
113
  </array>
@@ -142,20 +120,18 @@ async function setupLaunchd(installDir) {
142
120
  <key>StandardErrorPath</key>
143
121
  <string>/var/log/openape-proxy/stderr.log</string>
144
122
  <key>WorkingDirectory</key>
145
- <string>${installDir}</string>
123
+ <string>${INSTALL_DIR}</string>
146
124
  </dict>
147
125
  </plist>`;
148
126
  writeSecureFile(LAUNCHD_PLIST, plist, 0o644);
149
- console.log(`✅ launchd service created: ${LAUNCHD_LABEL}`);
150
- // Stop if already running
151
127
  try {
152
128
  execSync(`launchctl unload ${LAUNCHD_PLIST}`, { stdio: 'ignore' });
153
129
  }
154
130
  catch { }
155
131
  execSync(`launchctl load ${LAUNCHD_PLIST}`);
156
- console.log('✅ Service started');
132
+ console.log(`✅ launchd service: ${LAUNCHD_LABEL}`);
157
133
  }
158
- async function setupSystemd(installDir) {
134
+ function setupSystemd() {
159
135
  const bunPath = run('which bun');
160
136
  const unit = `[Unit]
161
137
  Description=OpenApe HTTP Proxy
@@ -163,18 +139,13 @@ After=network.target
163
139
 
164
140
  [Service]
165
141
  Type=simple
166
- ExecStart=${bunPath} run ${installDir}/src/index.ts --config ${CONFIG_PATH}
167
- WorkingDirectory=${installDir}
142
+ ExecStart=${bunPath} run ${INSTALL_DIR}/src/index.ts --config ${CONFIG_PATH}
143
+ WorkingDirectory=${INSTALL_DIR}
168
144
  Restart=on-failure
169
145
  RestartSec=5
170
- StandardOutput=journal
171
- StandardError=journal
172
-
173
- # Security hardening
174
146
  NoNewPrivileges=true
175
147
  ProtectSystem=strict
176
148
  ProtectHome=true
177
- ReadOnlyPaths=/
178
149
  ReadWritePaths=/var/log/openape-proxy
179
150
 
180
151
  [Install]
@@ -184,5 +155,5 @@ WantedBy=multi-user.target
184
155
  execSync('systemctl daemon-reload');
185
156
  execSync('systemctl enable openape-proxy');
186
157
  execSync('systemctl start openape-proxy');
187
- console.log('✅ systemd service created and started: openape-proxy');
158
+ console.log('✅ systemd service: openape-proxy');
188
159
  }
@@ -1,31 +1,26 @@
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 { requireRoot, ensureDir, writeSecureFile, run, parseFlags, requireFlag } from '../utils.js';
4
4
  const CONFIG_DIR = '/etc/apes';
5
5
  const CONFIG_PATH = `${CONFIG_DIR}/config.toml`;
6
6
  const BIN_PATH = '/usr/local/bin/apes';
7
- export async function installSudo() {
7
+ export async function installSudo(args) {
8
8
  requireRoot();
9
+ const flags = parseFlags(args);
10
+ const idpUrl = requireFlag(flags, 'idp-url');
11
+ const pollTimeout = flags['poll-timeout'] || '300';
12
+ const pollInterval = flags['poll-interval'] || '2';
13
+ const force = flags['force'] === true;
9
14
  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
- }
15
+ // Check existing binary
16
+ if (existsSync(BIN_PATH) && !force) {
17
+ throw new Error(`apes already installed at ${BIN_PATH}. Use --force to reinstall.`);
18
18
  }
19
- // Check for Rust toolchain
20
- let hasCargo = false;
19
+ // Check Rust toolchain
21
20
  try {
22
21
  run('cargo --version');
23
- hasCargo = true;
24
22
  }
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
23
+ catch {
29
24
  throw new Error('Rust toolchain required to build apes. Install it: https://rustup.rs\n' +
30
25
  'Pre-built binaries will be available in a future release.');
31
26
  }
@@ -46,41 +41,33 @@ export async function installSudo() {
46
41
  execSync(`cp ${buildDir}/target/release/apes ${BIN_PATH}`);
47
42
  execSync(`chown root:wheel ${BIN_PATH}`);
48
43
  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
- }
44
+ console.log(`✅ Binary: ${BIN_PATH} (setuid root)`);
45
+ // Config
46
+ if (existsSync(CONFIG_PATH) && !force) {
47
+ console.log(`ℹ️ Config preserved: ${CONFIG_PATH}`);
58
48
  }
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
49
+ else {
50
+ const config = `# OpenApe Sudo (apes) Configuration
63
51
  # Generated by: openape install-sudo
64
52
 
65
- server_url = "${serverUrl}"
53
+ server_url = "${idpUrl}"
66
54
 
67
55
  # Agent identity is determined by the key passed via --key flag.
68
56
  # Each user provides their own key: apes --key ~/.ssh/id_ed25519 -- <command>
69
57
 
70
58
  [poll]
71
- interval_secs = ${intervalSecs}
72
- timeout_secs = ${timeoutSecs}
59
+ interval_secs = ${pollInterval}
60
+ timeout_secs = ${pollTimeout}
73
61
  `;
74
- ensureDir(CONFIG_DIR, 0o700);
75
- writeSecureFile(CONFIG_PATH, config);
76
- console.log(`✅ Config written to ${CONFIG_PATH}`);
77
- closePrompt();
62
+ ensureDir(CONFIG_DIR, 0o700);
63
+ writeSecureFile(CONFIG_PATH, config);
64
+ console.log(`✅ Config: ${CONFIG_PATH}`);
65
+ }
78
66
  console.log('\n🎉 apes installed!\n');
79
67
  console.log(` Binary: ${BIN_PATH} (setuid root)`);
80
68
  console.log(` Config: ${CONFIG_PATH}`);
81
69
  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');
70
+ console.log(` 1. Enroll: apes enroll --server ${idpUrl} --agent-email <email> --agent-name <name> --key <path>`);
71
+ console.log(' 2. Admin approves enrollment');
72
+ console.log(' 3. Use: apes --key ~/.ssh/id_ed25519 --reason "why" -- <command>\n');
86
73
  }
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.1.4",
4
4
  "description": "OpenApe CLI — install and manage OpenApe components (proxy, sudo, auth)",
5
5
  "type": "module",
6
6
  "bin": {