@addai/node 0.8.2 → 0.9.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 +96 -96
- package/dist/autostart-mac.js +30 -30
- package/dist/autostart-win.js +50 -50
- package/dist/cli.js +24 -21
- package/dist/config.d.ts +1 -0
- package/dist/config.js +18 -3
- package/dist/heartbeat.js +46 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +7 -7
- package/dist/open-browser.d.ts +1 -0
- package/dist/open-browser.js +42 -0
- package/dist/pair-destination.d.ts +20 -0
- package/dist/pair-destination.js +59 -0
- package/dist/pairing.d.ts +4 -2
- package/dist/pairing.js +64 -16
- package/dist/request-pump.d.ts +1 -0
- package/dist/request-pump.js +9 -0
- package/dist/tui/pair.d.ts +33 -0
- package/dist/tui/pair.js +178 -0
- package/package.json +60 -60
- package/scripts/fix-pty-helper.js +28 -28
- package/scripts/precompact-capture.js +292 -292
- package/scripts/probe-tui.mjs +122 -122
- package/scripts/smoke-test.sh +74 -74
package/scripts/probe-tui.mjs
CHANGED
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Drive the console UI in a real pty and dump the frames a user would see.
|
|
3
|
-
//
|
|
4
|
-
// Unit tests assert on the pure render functions; this drives the actual
|
|
5
|
-
// binary — alt screen, raw keys, live data, the lot. Every defect in the
|
|
6
|
-
// 2026-07-30 console pass was found with this and none of them were visible
|
|
7
|
-
// from the code.
|
|
8
|
-
//
|
|
9
|
-
// node scripts/probe-tui.mjs "w4000,shome,ka,w2500,sactivity"
|
|
10
|
-
//
|
|
11
|
-
// Script steps, comma separated:
|
|
12
|
-
// w<ms> wait
|
|
13
|
-
// k<key> send a key: a literal, or CR UP DOWN LEFT RIGHT ESC TAB
|
|
14
|
-
// s<label> snapshot the screen under that label
|
|
15
|
-
//
|
|
16
|
-
// Frames are painted with cursor addressing, so the raw pty stream is
|
|
17
|
-
// replayed through a tiny terminal model to reconstruct what is on screen.
|
|
18
|
-
|
|
19
|
-
import { spawn } from 'node:child_process';
|
|
20
|
-
import { createRequire } from 'node:module';
|
|
21
|
-
import * as path from 'node:path';
|
|
22
|
-
import * as url from 'node:url';
|
|
23
|
-
|
|
24
|
-
const here = path.dirname(url.fileURLToPath(import.meta.url));
|
|
25
|
-
const root = path.resolve(here, '..');
|
|
26
|
-
const require = createRequire(import.meta.url);
|
|
27
|
-
|
|
28
|
-
let pty;
|
|
29
|
-
try {
|
|
30
|
-
pty = require('node-pty');
|
|
31
|
-
} catch {
|
|
32
|
-
console.error('node-pty is not installed — run npm install first');
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const COLS = Number(process.env.PROBE_COLS ?? 120);
|
|
37
|
-
const ROWS = Number(process.env.PROBE_ROWS ?? 40);
|
|
38
|
-
const steps = (process.argv[2] ?? 'w4000,shome').split(',').filter(Boolean);
|
|
39
|
-
|
|
40
|
-
const KEYS = {
|
|
41
|
-
CR: '\r', ESC: '\x1b', TAB: '\t',
|
|
42
|
-
UP: '\x1b[A', DOWN: '\x1b[B', RIGHT: '\x1b[C', LEFT: '\x1b[D',
|
|
43
|
-
PGUP: '\x1b[5~', PGDN: '\x1b[6~',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/** Just enough terminal to reconstruct a screen from cursor-addressed writes. */
|
|
47
|
-
function createScreen(rows, cols) {
|
|
48
|
-
let grid = Array.from({ length: rows }, () => '');
|
|
49
|
-
let row = 0;
|
|
50
|
-
let col = 0;
|
|
51
|
-
|
|
52
|
-
const put = text => {
|
|
53
|
-
if (!text) return;
|
|
54
|
-
const line = grid[row] ?? '';
|
|
55
|
-
grid[row] = (line.padEnd(col, ' ')).slice(0, col) + text;
|
|
56
|
-
col += text.length;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// A pty delivers bytes, not frames: an escape sequence is routinely split
|
|
60
|
-
// across two chunks. Anything that looks like the start of one is held
|
|
61
|
-
// back until the rest arrives, or it lands in the output as literal text.
|
|
62
|
-
let pending = '';
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
write(raw) {
|
|
66
|
-
const chunk = pending + raw;
|
|
67
|
-
pending = '';
|
|
68
|
-
const dangling = chunk.match(/\x1b(\[[0-9;]*|\][^\x07]*|[()]?)?$/);
|
|
69
|
-
const body = dangling ? chunk.slice(0, dangling.index) : chunk;
|
|
70
|
-
if (dangling) pending = dangling[0];
|
|
71
|
-
|
|
72
|
-
// Split into escape sequences and printable runs.
|
|
73
|
-
const re = /\x1b\[([0-9;]*)([A-Za-z])|\x1b\][^\x07]*\x07|\x1b[()][A-Za-z0-9]|[^\x1b]+/g;
|
|
74
|
-
let m;
|
|
75
|
-
while ((m = re.exec(body)) !== null) {
|
|
76
|
-
const [seq, params, cmd] = m;
|
|
77
|
-
if (cmd === undefined) {
|
|
78
|
-
if (seq.startsWith('\x1b')) continue; // OSC / charset — ignore
|
|
79
|
-
for (const part of seq.split(/(\r\n|\n|\r)/)) {
|
|
80
|
-
if (part === '\n' || part === '\r\n') { row = Math.min(rows - 1, row + 1); col = 0; }
|
|
81
|
-
else if (part === '\r') { col = 0; }
|
|
82
|
-
else put(part);
|
|
83
|
-
}
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
const n = params.split(';').map(x => Number(x || 0));
|
|
87
|
-
if (cmd === 'H' || cmd === 'f') { row = Math.max(0, (n[0] || 1) - 1); col = Math.max(0, (n[1] || 1) - 1); }
|
|
88
|
-
else if (cmd === 'J') { if ((n[0] ?? 0) === 2) { grid = Array.from({ length: rows }, () => ''); row = 0; col = 0; } }
|
|
89
|
-
else if (cmd === 'K') { grid[row] = (grid[row] ?? '').slice(0, col); }
|
|
90
|
-
else if (cmd === 'A') { row = Math.max(0, row - (n[0] || 1)); }
|
|
91
|
-
else if (cmd === 'B') { row = Math.min(rows - 1, row + (n[0] || 1)); }
|
|
92
|
-
// m (colour), h/l (modes) do not move the cursor or change content
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
snapshot() {
|
|
96
|
-
return grid.map(l => l.replace(/\s+$/, '')).join('\n').replace(/\n{3,}$/, '\n');
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const screen = createScreen(ROWS, COLS);
|
|
102
|
-
const child = pty.spawn(process.execPath, ['dist/cli.js'], {
|
|
103
|
-
name: 'xterm-256color', cols: COLS, rows: ROWS, cwd: root,
|
|
104
|
-
env: { ...process.env, TERM: 'xterm-256color' },
|
|
105
|
-
});
|
|
106
|
-
child.onData(d => screen.write(d));
|
|
107
|
-
|
|
108
|
-
const wait = ms => new Promise(r => setTimeout(r, ms));
|
|
109
|
-
const strip = s => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
110
|
-
|
|
111
|
-
for (const step of steps) {
|
|
112
|
-
const kind = step[0];
|
|
113
|
-
const arg = step.slice(1);
|
|
114
|
-
if (kind === 'w') await wait(Number(arg));
|
|
115
|
-
else if (kind === 'k') child.write(KEYS[arg] ?? arg);
|
|
116
|
-
else if (kind === 's') {
|
|
117
|
-
process.stdout.write(`\n===== ${arg} =====\n${strip(screen.snapshot())}\n`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
child.kill();
|
|
122
|
-
process.exit(0);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Drive the console UI in a real pty and dump the frames a user would see.
|
|
3
|
+
//
|
|
4
|
+
// Unit tests assert on the pure render functions; this drives the actual
|
|
5
|
+
// binary — alt screen, raw keys, live data, the lot. Every defect in the
|
|
6
|
+
// 2026-07-30 console pass was found with this and none of them were visible
|
|
7
|
+
// from the code.
|
|
8
|
+
//
|
|
9
|
+
// node scripts/probe-tui.mjs "w4000,shome,ka,w2500,sactivity"
|
|
10
|
+
//
|
|
11
|
+
// Script steps, comma separated:
|
|
12
|
+
// w<ms> wait
|
|
13
|
+
// k<key> send a key: a literal, or CR UP DOWN LEFT RIGHT ESC TAB
|
|
14
|
+
// s<label> snapshot the screen under that label
|
|
15
|
+
//
|
|
16
|
+
// Frames are painted with cursor addressing, so the raw pty stream is
|
|
17
|
+
// replayed through a tiny terminal model to reconstruct what is on screen.
|
|
18
|
+
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
import * as url from 'node:url';
|
|
23
|
+
|
|
24
|
+
const here = path.dirname(url.fileURLToPath(import.meta.url));
|
|
25
|
+
const root = path.resolve(here, '..');
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
|
|
28
|
+
let pty;
|
|
29
|
+
try {
|
|
30
|
+
pty = require('node-pty');
|
|
31
|
+
} catch {
|
|
32
|
+
console.error('node-pty is not installed — run npm install first');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const COLS = Number(process.env.PROBE_COLS ?? 120);
|
|
37
|
+
const ROWS = Number(process.env.PROBE_ROWS ?? 40);
|
|
38
|
+
const steps = (process.argv[2] ?? 'w4000,shome').split(',').filter(Boolean);
|
|
39
|
+
|
|
40
|
+
const KEYS = {
|
|
41
|
+
CR: '\r', ESC: '\x1b', TAB: '\t',
|
|
42
|
+
UP: '\x1b[A', DOWN: '\x1b[B', RIGHT: '\x1b[C', LEFT: '\x1b[D',
|
|
43
|
+
PGUP: '\x1b[5~', PGDN: '\x1b[6~',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Just enough terminal to reconstruct a screen from cursor-addressed writes. */
|
|
47
|
+
function createScreen(rows, cols) {
|
|
48
|
+
let grid = Array.from({ length: rows }, () => '');
|
|
49
|
+
let row = 0;
|
|
50
|
+
let col = 0;
|
|
51
|
+
|
|
52
|
+
const put = text => {
|
|
53
|
+
if (!text) return;
|
|
54
|
+
const line = grid[row] ?? '';
|
|
55
|
+
grid[row] = (line.padEnd(col, ' ')).slice(0, col) + text;
|
|
56
|
+
col += text.length;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// A pty delivers bytes, not frames: an escape sequence is routinely split
|
|
60
|
+
// across two chunks. Anything that looks like the start of one is held
|
|
61
|
+
// back until the rest arrives, or it lands in the output as literal text.
|
|
62
|
+
let pending = '';
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
write(raw) {
|
|
66
|
+
const chunk = pending + raw;
|
|
67
|
+
pending = '';
|
|
68
|
+
const dangling = chunk.match(/\x1b(\[[0-9;]*|\][^\x07]*|[()]?)?$/);
|
|
69
|
+
const body = dangling ? chunk.slice(0, dangling.index) : chunk;
|
|
70
|
+
if (dangling) pending = dangling[0];
|
|
71
|
+
|
|
72
|
+
// Split into escape sequences and printable runs.
|
|
73
|
+
const re = /\x1b\[([0-9;]*)([A-Za-z])|\x1b\][^\x07]*\x07|\x1b[()][A-Za-z0-9]|[^\x1b]+/g;
|
|
74
|
+
let m;
|
|
75
|
+
while ((m = re.exec(body)) !== null) {
|
|
76
|
+
const [seq, params, cmd] = m;
|
|
77
|
+
if (cmd === undefined) {
|
|
78
|
+
if (seq.startsWith('\x1b')) continue; // OSC / charset — ignore
|
|
79
|
+
for (const part of seq.split(/(\r\n|\n|\r)/)) {
|
|
80
|
+
if (part === '\n' || part === '\r\n') { row = Math.min(rows - 1, row + 1); col = 0; }
|
|
81
|
+
else if (part === '\r') { col = 0; }
|
|
82
|
+
else put(part);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const n = params.split(';').map(x => Number(x || 0));
|
|
87
|
+
if (cmd === 'H' || cmd === 'f') { row = Math.max(0, (n[0] || 1) - 1); col = Math.max(0, (n[1] || 1) - 1); }
|
|
88
|
+
else if (cmd === 'J') { if ((n[0] ?? 0) === 2) { grid = Array.from({ length: rows }, () => ''); row = 0; col = 0; } }
|
|
89
|
+
else if (cmd === 'K') { grid[row] = (grid[row] ?? '').slice(0, col); }
|
|
90
|
+
else if (cmd === 'A') { row = Math.max(0, row - (n[0] || 1)); }
|
|
91
|
+
else if (cmd === 'B') { row = Math.min(rows - 1, row + (n[0] || 1)); }
|
|
92
|
+
// m (colour), h/l (modes) do not move the cursor or change content
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
snapshot() {
|
|
96
|
+
return grid.map(l => l.replace(/\s+$/, '')).join('\n').replace(/\n{3,}$/, '\n');
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const screen = createScreen(ROWS, COLS);
|
|
102
|
+
const child = pty.spawn(process.execPath, ['dist/cli.js'], {
|
|
103
|
+
name: 'xterm-256color', cols: COLS, rows: ROWS, cwd: root,
|
|
104
|
+
env: { ...process.env, TERM: 'xterm-256color' },
|
|
105
|
+
});
|
|
106
|
+
child.onData(d => screen.write(d));
|
|
107
|
+
|
|
108
|
+
const wait = ms => new Promise(r => setTimeout(r, ms));
|
|
109
|
+
const strip = s => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
110
|
+
|
|
111
|
+
for (const step of steps) {
|
|
112
|
+
const kind = step[0];
|
|
113
|
+
const arg = step.slice(1);
|
|
114
|
+
if (kind === 'w') await wait(Number(arg));
|
|
115
|
+
else if (kind === 'k') child.write(KEYS[arg] ?? arg);
|
|
116
|
+
else if (kind === 's') {
|
|
117
|
+
process.stdout.write(`\n===== ${arg} =====\n${strip(screen.snapshot())}\n`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
child.kill();
|
|
122
|
+
process.exit(0);
|
package/scripts/smoke-test.sh
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Daemon smoke test — verifies the running daemon's health + each agent's
|
|
3
|
-
# binary + auth state. Run AFTER upgrading the daemon to 0.2.14+.
|
|
4
|
-
#
|
|
5
|
-
# Usage: bash scripts/smoke-test.sh
|
|
6
|
-
|
|
7
|
-
set -uo pipefail
|
|
8
|
-
LOCKFILE="${HOME}/.entities-runtime/runtime.json"
|
|
9
|
-
|
|
10
|
-
if [ ! -f "$LOCKFILE" ]; then
|
|
11
|
-
echo "✗ No daemon lockfile at $LOCKFILE — daemon not running"
|
|
12
|
-
exit 1
|
|
13
|
-
fi
|
|
14
|
-
|
|
15
|
-
PORT=$(python3 -c "import json; print(json.load(open('${LOCKFILE}'))['port'])")
|
|
16
|
-
echo "Daemon lockfile port: $PORT"
|
|
17
|
-
|
|
18
|
-
probe() {
|
|
19
|
-
local ep="$1"
|
|
20
|
-
local expect_field="$2"
|
|
21
|
-
local body
|
|
22
|
-
body=$(curl -s --max-time 3 "http://127.0.0.1:${PORT}${ep}")
|
|
23
|
-
if [ -z "$body" ]; then
|
|
24
|
-
echo " ✗ $ep — no response"
|
|
25
|
-
return 1
|
|
26
|
-
fi
|
|
27
|
-
if [ -n "$expect_field" ]; then
|
|
28
|
-
if echo "$body" | python3 -c "import json,sys; d=json.load(sys.stdin); assert '$expect_field' in d or any('$expect_field' in str(v) for v in d.values())" 2>/dev/null; then
|
|
29
|
-
echo " ✓ $ep — $(echo "$body" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin), separators=(',',':'))[:120])")"
|
|
30
|
-
else
|
|
31
|
-
echo " ⚠ $ep — missing field '$expect_field': $body" | head -c 200
|
|
32
|
-
echo
|
|
33
|
-
fi
|
|
34
|
-
else
|
|
35
|
-
echo " ✓ $ep — $(echo "$body" | head -c 100)"
|
|
36
|
-
fi
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
echo
|
|
40
|
-
echo "=== Daemon control-server endpoints ==="
|
|
41
|
-
probe /status "version"
|
|
42
|
-
probe /self "runtime"
|
|
43
|
-
probe /stats "inflight" # inflight field added in 0.2.11
|
|
44
|
-
probe /requests "requests"
|
|
45
|
-
probe /projects "projects"
|
|
46
|
-
|
|
47
|
-
echo
|
|
48
|
-
echo "=== Agent binary + auth checks ==="
|
|
49
|
-
check_agent() {
|
|
50
|
-
local name="$1" bin="$2" auth_file="$3" env_var="$4"
|
|
51
|
-
printf " %s: " "$name"
|
|
52
|
-
if ! command -v "$bin" >/dev/null 2>&1; then
|
|
53
|
-
echo "✗ binary missing"
|
|
54
|
-
return
|
|
55
|
-
fi
|
|
56
|
-
local ver
|
|
57
|
-
ver=$("$bin" --version 2>&1 | head -1 | tr -d '\n')
|
|
58
|
-
if [ -n "$env_var" ] && [ -n "${!env_var:-}" ]; then
|
|
59
|
-
echo "✓ $ver — authed via \$$env_var"
|
|
60
|
-
return
|
|
61
|
-
fi
|
|
62
|
-
if [ -f "$HOME/$auth_file" ] && [ "$(stat -f %z "$HOME/$auth_file" 2>/dev/null || stat -c %s "$HOME/$auth_file" 2>/dev/null)" -gt 16 ]; then
|
|
63
|
-
echo "✓ $ver — authed via ~/$auth_file"
|
|
64
|
-
else
|
|
65
|
-
echo "⚠ $ver — NO AUTH (set $env_var or login)"
|
|
66
|
-
fi
|
|
67
|
-
}
|
|
68
|
-
check_agent "claude" claude .claude/.credentials.json ANTHROPIC_API_KEY
|
|
69
|
-
check_agent "codex" codex .codex/auth.json OPENAI_API_KEY
|
|
70
|
-
check_agent "kimi" kimi .kimi/auth.toml KIMI_API_KEY
|
|
71
|
-
check_agent "gemini" gemini .gemini/oauth_creds.json GEMINI_API_KEY
|
|
72
|
-
|
|
73
|
-
echo
|
|
74
|
-
echo "=== Done ==="
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Daemon smoke test — verifies the running daemon's health + each agent's
|
|
3
|
+
# binary + auth state. Run AFTER upgrading the daemon to 0.2.14+.
|
|
4
|
+
#
|
|
5
|
+
# Usage: bash scripts/smoke-test.sh
|
|
6
|
+
|
|
7
|
+
set -uo pipefail
|
|
8
|
+
LOCKFILE="${HOME}/.entities-runtime/runtime.json"
|
|
9
|
+
|
|
10
|
+
if [ ! -f "$LOCKFILE" ]; then
|
|
11
|
+
echo "✗ No daemon lockfile at $LOCKFILE — daemon not running"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
PORT=$(python3 -c "import json; print(json.load(open('${LOCKFILE}'))['port'])")
|
|
16
|
+
echo "Daemon lockfile port: $PORT"
|
|
17
|
+
|
|
18
|
+
probe() {
|
|
19
|
+
local ep="$1"
|
|
20
|
+
local expect_field="$2"
|
|
21
|
+
local body
|
|
22
|
+
body=$(curl -s --max-time 3 "http://127.0.0.1:${PORT}${ep}")
|
|
23
|
+
if [ -z "$body" ]; then
|
|
24
|
+
echo " ✗ $ep — no response"
|
|
25
|
+
return 1
|
|
26
|
+
fi
|
|
27
|
+
if [ -n "$expect_field" ]; then
|
|
28
|
+
if echo "$body" | python3 -c "import json,sys; d=json.load(sys.stdin); assert '$expect_field' in d or any('$expect_field' in str(v) for v in d.values())" 2>/dev/null; then
|
|
29
|
+
echo " ✓ $ep — $(echo "$body" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin), separators=(',',':'))[:120])")"
|
|
30
|
+
else
|
|
31
|
+
echo " ⚠ $ep — missing field '$expect_field': $body" | head -c 200
|
|
32
|
+
echo
|
|
33
|
+
fi
|
|
34
|
+
else
|
|
35
|
+
echo " ✓ $ep — $(echo "$body" | head -c 100)"
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
echo
|
|
40
|
+
echo "=== Daemon control-server endpoints ==="
|
|
41
|
+
probe /status "version"
|
|
42
|
+
probe /self "runtime"
|
|
43
|
+
probe /stats "inflight" # inflight field added in 0.2.11
|
|
44
|
+
probe /requests "requests"
|
|
45
|
+
probe /projects "projects"
|
|
46
|
+
|
|
47
|
+
echo
|
|
48
|
+
echo "=== Agent binary + auth checks ==="
|
|
49
|
+
check_agent() {
|
|
50
|
+
local name="$1" bin="$2" auth_file="$3" env_var="$4"
|
|
51
|
+
printf " %s: " "$name"
|
|
52
|
+
if ! command -v "$bin" >/dev/null 2>&1; then
|
|
53
|
+
echo "✗ binary missing"
|
|
54
|
+
return
|
|
55
|
+
fi
|
|
56
|
+
local ver
|
|
57
|
+
ver=$("$bin" --version 2>&1 | head -1 | tr -d '\n')
|
|
58
|
+
if [ -n "$env_var" ] && [ -n "${!env_var:-}" ]; then
|
|
59
|
+
echo "✓ $ver — authed via \$$env_var"
|
|
60
|
+
return
|
|
61
|
+
fi
|
|
62
|
+
if [ -f "$HOME/$auth_file" ] && [ "$(stat -f %z "$HOME/$auth_file" 2>/dev/null || stat -c %s "$HOME/$auth_file" 2>/dev/null)" -gt 16 ]; then
|
|
63
|
+
echo "✓ $ver — authed via ~/$auth_file"
|
|
64
|
+
else
|
|
65
|
+
echo "⚠ $ver — NO AUTH (set $env_var or login)"
|
|
66
|
+
fi
|
|
67
|
+
}
|
|
68
|
+
check_agent "claude" claude .claude/.credentials.json ANTHROPIC_API_KEY
|
|
69
|
+
check_agent "codex" codex .codex/auth.json OPENAI_API_KEY
|
|
70
|
+
check_agent "kimi" kimi .kimi/auth.toml KIMI_API_KEY
|
|
71
|
+
check_agent "gemini" gemini .gemini/oauth_creds.json GEMINI_API_KEY
|
|
72
|
+
|
|
73
|
+
echo
|
|
74
|
+
echo "=== Done ==="
|