@openape/apes 0.6.1 → 0.7.2
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 +68 -6
- package/dist/auth-lock-SRUFWJC3.js +41 -0
- package/dist/auth-lock-SRUFWJC3.js.map +1 -0
- package/dist/{chunk-G3Q2TMAI.js → chunk-B32ZQP5K.js} +139 -190
- 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 +349 -213
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +11 -9
- package/dist/orchestrator-JAMWD6DD.js +637 -0
- package/dist/orchestrator-JAMWD6DD.js.map +1 -0
- package/dist/{server-FR6GFS3S.js → server-GPETT3ON.js} +8 -6
- package/dist/{server-FR6GFS3S.js.map → server-GPETT3ON.js.map} +1 -1
- package/dist/ssh-key-YBNNG5K5.js +10 -0
- package/package.json +5 -2
- package/scripts/ape-shell-wrapper.sh +103 -0
- package/scripts/fix-node-pty-perms.mjs +39 -0
- package/dist/chunk-G3Q2TMAI.js.map +0 -1
- package/dist/chunk-KVBHBOED.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
CHANGED
|
@@ -35,9 +35,12 @@ apes grants list
|
|
|
35
35
|
|
|
36
36
|
## ape-shell: Grant-Secured Shell Wrapper
|
|
37
37
|
|
|
38
|
-
`ape-shell` is a drop-in shell
|
|
38
|
+
`ape-shell` is a drop-in shell that routes every command through a DDISA grant. It has two modes:
|
|
39
39
|
|
|
40
|
-
|
|
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
|
|
41
44
|
|
|
42
45
|
```
|
|
43
46
|
$SHELL -c "git status"
|
|
@@ -51,16 +54,50 @@ apes run --shell -- bash -c "git status"
|
|
|
51
54
|
3. No grant → request + wait for human approval → execute
|
|
52
55
|
```
|
|
53
56
|
|
|
54
|
-
###
|
|
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)
|
|
55
74
|
|
|
56
75
|
```bash
|
|
57
|
-
# Point the agent's SHELL at ape-shell
|
|
58
|
-
|
|
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
|
|
59
79
|
```
|
|
60
80
|
|
|
61
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.
|
|
62
82
|
|
|
63
|
-
###
|
|
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)
|
|
64
101
|
|
|
65
102
|
```bash
|
|
66
103
|
$ apes login
|
|
@@ -78,6 +115,31 @@ def456 Previous commit
|
|
|
78
115
|
...
|
|
79
116
|
```
|
|
80
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
|
+
|
|
81
143
|
## Commands
|
|
82
144
|
|
|
83
145
|
### Authentication
|
|
@@ -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":[]}
|