@openape/apes 0.6.0 → 0.7.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/README.md +206 -0
- package/dist/auth-lock-SRUFWJC3.js +41 -0
- package/dist/auth-lock-SRUFWJC3.js.map +1 -0
- package/dist/chunk-B32ZQP5K.js +1280 -0
- package/dist/chunk-B32ZQP5K.js.map +1 -0
- package/dist/{chunk-KVBHBOED.js → chunk-ION3CWD5.js} +14 -2
- package/dist/chunk-ION3CWD5.js.map +1 -0
- package/dist/chunk-TBYYREL6.js +133 -0
- package/dist/chunk-TBYYREL6.js.map +1 -0
- package/dist/cli.js +927 -246
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +216 -2
- package/dist/index.js +32 -24
- package/dist/index.js.map +1 -1
- package/dist/orchestrator-JAMWD6DD.js +637 -0
- package/dist/orchestrator-JAMWD6DD.js.map +1 -0
- package/dist/{server-IYR5LM63.js → server-UTCZSPCU.js} +13 -14
- package/dist/server-UTCZSPCU.js.map +1 -0
- package/dist/ssh-key-YBNNG5K5.js +10 -0
- package/package.json +15 -9
- package/dist/chunk-KVBHBOED.js.map +0 -1
- package/dist/chunk-KXESKY4X.js +0 -278
- package/dist/chunk-KXESKY4X.js.map +0 -1
- package/dist/server-IYR5LM63.js.map +0 -1
- package/dist/ssh-key-Q7KG4K25.js +0 -8
- /package/dist/{ssh-key-Q7KG4K25.js.map → ssh-key-YBNNG5K5.js.map} +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# @openape/apes
|
|
2
|
+
|
|
3
|
+
The unified OpenApe CLI for interacting with a DDISA Identity Provider — handles authentication, grants, delegations, adapter-based command authorization, and MCP server integration.
|
|
4
|
+
|
|
5
|
+
Ships three binaries:
|
|
6
|
+
- **`apes`** — main CLI (login, grants, run, admin, etc.)
|
|
7
|
+
- **`ape-shell`** — grant-secured shell wrapper (drop-in replacement for `bash -c`)
|
|
8
|
+
- MCP server mode via `apes mcp`
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add -g @openape/apes
|
|
14
|
+
# or: npm install -g @openape/apes
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
After installation you have `apes` and `ape-shell` in your PATH.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Login to an IdP (opens browser for PKCE flow)
|
|
23
|
+
apes login --idp https://id.example.com
|
|
24
|
+
|
|
25
|
+
# 2. Check who you are
|
|
26
|
+
apes whoami
|
|
27
|
+
|
|
28
|
+
# 3. Request a grant and run a command
|
|
29
|
+
apes run -- git status
|
|
30
|
+
# → creates a grant, waits for approval, executes
|
|
31
|
+
|
|
32
|
+
# 4. List your grants
|
|
33
|
+
apes grants list
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## ape-shell: Grant-Secured Shell Wrapper
|
|
37
|
+
|
|
38
|
+
`ape-shell` is a drop-in shell that routes every command through a DDISA grant. It has two modes:
|
|
39
|
+
|
|
40
|
+
1. **One-shot mode** (`ape-shell -c "<command>"`) — the historical wrapper. Runs a single command through the grant flow and exits. Used by `$SHELL -c` patterns (e.g. `openclaw tui`, `xargs`, git hooks, sshd non-interactive sessions, etc.).
|
|
41
|
+
2. **Interactive mode** (`ape-shell` with no args, or as a login shell) — a full interactive REPL with a persistent bash backend. Every line the user types is routed through the grant flow **before** bash sees it, and executed in bash's persistent state (so `cd`, `export`, aliases, functions, pipes, TUI apps like `vim`/`less`/`top` all work natively).
|
|
42
|
+
|
|
43
|
+
### How the one-shot mode works
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
$SHELL -c "git status"
|
|
47
|
+
↓
|
|
48
|
+
ape-shell -c "git status"
|
|
49
|
+
↓
|
|
50
|
+
apes run --shell -- bash -c "git status"
|
|
51
|
+
↓
|
|
52
|
+
1. Find existing ape-shell session grant (timed/always)
|
|
53
|
+
2. Grant found → execute immediately
|
|
54
|
+
3. No grant → request + wait for human approval → execute
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### How the interactive mode works
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
ape-shell
|
|
61
|
+
↓
|
|
62
|
+
┌─ PROMPT ─────────────────────────────────────────┐
|
|
63
|
+
│ apes$ <user types here> │
|
|
64
|
+
│ → multi-line detection via `bash -n` dry-parse │
|
|
65
|
+
│ → grant dispatch (adapter or ape-shell session)│
|
|
66
|
+
│ → on approval: write line to persistent bash │
|
|
67
|
+
│ → stream output, detect prompt marker │
|
|
68
|
+
└──────────────────────────────────────────────────┘
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every line is audited in `~/.config/apes/audit.jsonl` (session id, line, grant id, exit code).
|
|
72
|
+
|
|
73
|
+
### Setup for an AI agent session (one-shot mode)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Point the agent's SHELL at ape-shell — each spawned command
|
|
77
|
+
# goes through the one-shot grant flow.
|
|
78
|
+
SHELL=$(which ape-shell) openclaw tui
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The first command requests a session grant. After the human approves it (with `grant_type: timed, duration: 8h`), all subsequent commands reuse the same grant without interaction.
|
|
82
|
+
|
|
83
|
+
### Setup as a login shell (interactive mode)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# 1. Register ape-shell as a valid login shell (once per host)
|
|
87
|
+
echo "$(which ape-shell)" | sudo tee -a /etc/shells
|
|
88
|
+
|
|
89
|
+
# 2. Set it as the login shell for a user (e.g. openclaw)
|
|
90
|
+
sudo chsh -s "$(which ape-shell)" openclaw
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
After this:
|
|
94
|
+
|
|
95
|
+
- `ssh openclaw@host` — sshd starts ape-shell as an interactive REPL (sshd passes the login shell with a `-` prefix on argv[0], which ape-shell detects)
|
|
96
|
+
- `ssh openclaw@host "ls"` — sshd invokes `ape-shell -c "ls"`, which still flows through the **one-shot** path (no regression)
|
|
97
|
+
- `su - openclaw` — drops into the interactive REPL
|
|
98
|
+
- Terminal / console login — same as SSH interactive
|
|
99
|
+
|
|
100
|
+
### Example (one-shot)
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
$ apes login
|
|
104
|
+
$ ape-shell -c "git status"
|
|
105
|
+
ℹ Requesting ape-shell session grant on my-host
|
|
106
|
+
ℹ Grant requested: grant_abc123
|
|
107
|
+
ℹ Waiting for approval...
|
|
108
|
+
# Human approves in browser → command executes
|
|
109
|
+
On branch main
|
|
110
|
+
|
|
111
|
+
$ ape-shell -c "git log --oneline -5"
|
|
112
|
+
# Grant is reused automatically — no approval prompt
|
|
113
|
+
abc123 Latest commit
|
|
114
|
+
def456 Previous commit
|
|
115
|
+
...
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Example (interactive)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
$ ape-shell
|
|
122
|
+
apes interactive shell
|
|
123
|
+
Ctrl-D to exit.
|
|
124
|
+
|
|
125
|
+
apes$ cd /tmp
|
|
126
|
+
# (grant approved, reused for free)
|
|
127
|
+
|
|
128
|
+
apes$ ls
|
|
129
|
+
# structured adapter grant for `ls` → approve → output
|
|
130
|
+
|
|
131
|
+
apes$ for i in 1 2 3; do
|
|
132
|
+
> echo $i
|
|
133
|
+
> done
|
|
134
|
+
# single grant for the whole compound, bash runs it natively
|
|
135
|
+
|
|
136
|
+
apes$ vim notes.md
|
|
137
|
+
# grant approved → full TUI vim, raw-mode passthrough
|
|
138
|
+
|
|
139
|
+
apes$ ^D
|
|
140
|
+
Goodbye.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Commands
|
|
144
|
+
|
|
145
|
+
### Authentication
|
|
146
|
+
|
|
147
|
+
| Command | Description |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `apes login` | PKCE browser login or ed25519 key-based agent login |
|
|
150
|
+
| `apes logout` | Clear stored auth |
|
|
151
|
+
| `apes whoami` | Show current identity |
|
|
152
|
+
| `apes enroll` | Enroll an agent at the IdP |
|
|
153
|
+
| `apes register-user` | Register a new human user |
|
|
154
|
+
|
|
155
|
+
### Grants
|
|
156
|
+
|
|
157
|
+
| Command | Description |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `apes grants list` | List all grants |
|
|
160
|
+
| `apes grants inbox` | Show pending approval requests |
|
|
161
|
+
| `apes grants request` | Request a new grant |
|
|
162
|
+
| `apes grants approve <id>` | Approve a grant |
|
|
163
|
+
| `apes grants deny <id>` | Deny a grant |
|
|
164
|
+
| `apes grants revoke <id>` | Revoke an active grant |
|
|
165
|
+
| `apes grants token <id>` | Get the JWT for an approved grant |
|
|
166
|
+
| `apes grants delegate` | Create a delegation grant |
|
|
167
|
+
|
|
168
|
+
### Execution
|
|
169
|
+
|
|
170
|
+
| Command | Description |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `apes run -- <cmd>` | Run a command via a shapes adapter grant |
|
|
173
|
+
| `apes run --shell -- bash -c <cmd>` | Shell mode (used by `ape-shell`) |
|
|
174
|
+
| `apes run --as root -- <cmd>` | Elevate via `escapes` (separate binary) |
|
|
175
|
+
| `apes explain -- <cmd>` | Explain what grant a command would need |
|
|
176
|
+
|
|
177
|
+
### Configuration
|
|
178
|
+
|
|
179
|
+
Auth and config are stored in `~/.config/apes/`:
|
|
180
|
+
- `auth.json` — access token, email, IdP URL
|
|
181
|
+
- `config.toml` — defaults (idp, agent key path, etc.)
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
apes config get defaults.idp
|
|
185
|
+
apes config set defaults.idp https://id.example.com
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## MCP Server
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
apes mcp --transport stdio
|
|
192
|
+
# or
|
|
193
|
+
apes mcp --transport sse --port 3001
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Exposes all grant operations as MCP tools so AI agents (Claude Desktop, Cursor, etc.) can request and use grants directly.
|
|
197
|
+
|
|
198
|
+
## See Also
|
|
199
|
+
|
|
200
|
+
- [DDISA Protocol](https://github.com/openape-ai/protocol) — the underlying identity and authorization protocol
|
|
201
|
+
- [OpenApe Docs](https://docs.openape.at) — full platform documentation
|
|
202
|
+
- [`escapes`](https://github.com/openape-ai/escapes) — Rust binary for privilege escalation (`apes run --as root`)
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT © Patrick Hofmann — [Delta Mind GmbH](https://delta-mind.at)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CONFIG_DIR
|
|
4
|
+
} from "./chunk-TBYYREL6.js";
|
|
5
|
+
|
|
6
|
+
// src/auth-lock.ts
|
|
7
|
+
import { open, rm, stat } from "fs/promises";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
var LOCK_FILE = join(CONFIG_DIR, "auth.json.lock");
|
|
10
|
+
async function acquireAuthLock(opts = {}) {
|
|
11
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 5e3);
|
|
12
|
+
while (Date.now() < deadline) {
|
|
13
|
+
try {
|
|
14
|
+
const handle = await open(LOCK_FILE, "wx");
|
|
15
|
+
return { handle };
|
|
16
|
+
} catch (err) {
|
|
17
|
+
if (err.code !== "EEXIST")
|
|
18
|
+
throw err;
|
|
19
|
+
try {
|
|
20
|
+
const s = await stat(LOCK_FILE);
|
|
21
|
+
if (Date.now() - s.mtimeMs > 3e4)
|
|
22
|
+
await rm(LOCK_FILE, { force: true });
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
async function releaseAuthLock(lock) {
|
|
31
|
+
try {
|
|
32
|
+
await lock.handle.close();
|
|
33
|
+
} finally {
|
|
34
|
+
await rm(LOCK_FILE, { force: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
acquireAuthLock,
|
|
39
|
+
releaseAuthLock
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=auth-lock-SRUFWJC3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth-lock.ts"],"sourcesContent":["import type { FileHandle } from 'node:fs/promises'\nimport { open, rm, stat } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { CONFIG_DIR } from './config'\n\nconst LOCK_FILE = join(CONFIG_DIR, 'auth.json.lock')\n\nexport interface AuthLock {\n handle: FileHandle\n}\n\n/**\n * Best-effort exclusive file lock to serialize concurrent token refreshes\n * between parallel apes / ape-shell invocations. Uses O_CREAT|O_EXCL which\n * is atomic on POSIX. Returns null on timeout so the caller can fall back\n * to \"just re-read auth.json\" (the assumption being that another process\n * successfully refreshed in the meantime).\n *\n * A stale lock older than 30s is considered abandoned (from a crashed\n * process) and is removed so the next acquire can proceed.\n */\nexport async function acquireAuthLock(\n opts: { timeoutMs?: number } = {},\n): Promise<AuthLock | null> {\n const deadline = Date.now() + (opts.timeoutMs ?? 5000)\n while (Date.now() < deadline) {\n try {\n const handle = await open(LOCK_FILE, 'wx')\n return { handle }\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST')\n throw err\n // Stale lock? If the file is older than 30s, remove it and retry.\n try {\n const s = await stat(LOCK_FILE)\n if (Date.now() - s.mtimeMs > 30_000)\n await rm(LOCK_FILE, { force: true })\n }\n catch {\n // File gone between stat and rm — next iteration will retry the open.\n }\n await new Promise(r => setTimeout(r, 100))\n }\n }\n return null\n}\n\nexport async function releaseAuthLock(lock: AuthLock): Promise<void> {\n try {\n await lock.handle.close()\n }\n finally {\n await rm(LOCK_FILE, { force: true })\n }\n}\n"],"mappings":";;;;;;AACA,SAAS,MAAM,IAAI,YAAY;AAC/B,SAAS,YAAY;AAGrB,IAAM,YAAY,KAAK,YAAY,gBAAgB;AAgBnD,eAAsB,gBACpB,OAA+B,CAAC,GACN;AAC1B,QAAM,WAAW,KAAK,IAAI,KAAK,KAAK,aAAa;AACjD,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,WAAW,IAAI;AACzC,aAAO,EAAE,OAAO;AAAA,IAClB,SACO,KAAK;AACV,UAAK,IAA8B,SAAS;AAC1C,cAAM;AAER,UAAI;AACF,cAAM,IAAI,MAAM,KAAK,SAAS;AAC9B,YAAI,KAAK,IAAI,IAAI,EAAE,UAAU;AAC3B,gBAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;AAAA,MACvC,QACM;AAAA,MAEN;AACA,YAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,GAAG,CAAC;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,gBAAgB,MAA+B;AACnE,MAAI;AACF,UAAM,KAAK,OAAO,MAAM;AAAA,EAC1B,UACA;AACE,UAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;AAAA,EACrC;AACF;","names":[]}
|