@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 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":[]}