Skip to content

Commit e8977e8

Browse files
committed
Add agent-session auth to CLI
1 parent bf2d13a commit e8977e8

10 files changed

Lines changed: 1072 additions & 53 deletions

File tree

README.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ Analytics your AI agent can actually use — track, analyze, experiment, optimiz
77
## Quick Start
88

99
```bash
10-
# 1. Get your API key from https://app.agentanalytics.sh (sign in with GitHub or Google)
10+
# 1. Start agent login or signup in the browser
11+
npx @agent-analytics/cli login
1112

12-
# 2. Save your key
13-
npx @agent-analytics/cli login --token aak_your_key
14-
15-
# 3. Create a project
13+
# 2. Create a project
1614
npx @agent-analytics/cli create my-site --domain https://mysite.com
1715

18-
# 4. Watch it live
16+
# 3. Watch it live
1917
npx @agent-analytics/cli live
2018

19+
# Optional fallbacks
20+
npx @agent-analytics/cli login --detached
21+
npx @agent-analytics/cli login --token aak_your_key # advanced/manual fallback
22+
2123
# Optional: clear your saved local auth later
2224
npx @agent-analytics/cli logout
2325
```
@@ -26,8 +28,10 @@ npx @agent-analytics/cli logout
2628

2729
```bash
2830
# Setup
29-
login --token <key> Save your API key
30-
logout Clear your saved API key
31+
login Browser approval flow for signup/login
32+
login --detached Detached approval flow with poll/manual exchange
33+
login --token <key> Advanced fallback: save a raw API key
34+
logout Clear your saved local auth
3135
create <name> --domain <url> Create a project and get your tracking snippet
3236
projects List all your projects
3337

@@ -57,10 +61,12 @@ experiments complete <id> Ship the winner
5761
# Account
5862
whoami Show current account & tier
5963
feedback --message "..." Send product/process feedback
60-
logout Clear saved local auth (does not revoke your key)
64+
logout Clear saved local auth (does not revoke remote sessions)
6165
revoke-key Revoke and regenerate API key
6266
```
6367

68+
The CLI is agent-session-first. It stores a renewable Agent Analytics session locally after browser approval and uses that bearer auth for API calls. Raw `aak_*` API keys still work, but only as an advanced/manual fallback for direct HTTP-style usage.
69+
6470
Bounce metrics (`insights`, `pages`, `sessions`) treat a session as a bounce when it has only non-interactive events:
6571
`page_view`, `$impression`, `$scroll_depth`, `$error`, `$time_on_page`, `$performance`, `$web_vitals`.
6672

@@ -92,6 +98,8 @@ Claude Code, OpenClaw, Cursor, Codex — any AI agent that can run `npx`. Or add
9298
claude mcp add agent-analytics --transport http https://mcp.agentanalytics.sh/mcp
9399
```
94100

101+
For managed or remote runtimes that cannot receive a localhost callback, use `npx @agent-analytics/cli login --detached` and complete approval in the browser or with manual exchange.
102+
95103
## Agent Skill
96104

97105
The installable Agent Skill lives in the canonical public repo:
@@ -106,7 +114,7 @@ Do not install the skill from this CLI repo. This package is the runtime CLI; th
106114

107115
| Variable | Description |
108116
|----------|-------------|
109-
| `AGENT_ANALYTICS_API_KEY` | API key (overrides config file) |
117+
| `AGENT_ANALYTICS_API_KEY` | Advanced fallback API key (overrides config file) |
110118
| `AGENT_ANALYTICS_URL` | Custom API URL (for self-hosted) |
111119

112120
## Links

bin/cli.mjs

Lines changed: 146 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
* agent-analytics CLI
55
*
66
* Usage:
7-
* npx @agent-analytics/cli login --token <key> — Save your API key
8-
* npx @agent-analytics/cli logout — Clear your saved API key
7+
* npx @agent-analytics/cli login — Start browser-based agent session login
8+
* npx @agent-analytics/cli login --detached — Detached/browser approval flow
9+
* npx @agent-analytics/cli login --token <key> — Advanced fallback: save a raw API key
10+
* npx @agent-analytics/cli logout — Clear saved local auth
911
* npx @agent-analytics/cli create <name> — Create a project and get your snippet
1012
* npx @agent-analytics/cli projects — List your projects
1113
* npx @agent-analytics/cli all-sites — Historical summary across all projects
@@ -42,14 +44,15 @@
4244
*/
4345

4446
import { AgentAnalyticsAPI } from '../lib/api.mjs';
47+
import { finishManualExchange, loginDetached, loginInteractive } from '../lib/auth-flow.mjs';
4548
import {
4649
clearStoredAuth,
47-
getApiKey,
4850
getBaseUrl,
49-
getConfig,
5051
getConfigFile,
51-
saveConfig,
52+
getStoredAuth,
53+
setAgentSession,
5254
setApiKey,
55+
updateStoredAccount,
5356
} from '../lib/config.mjs';
5457

5558
const BOLD = '\x1b[1m';
@@ -68,17 +71,67 @@ function warn(msg) { log(`${YELLOW}⚠${RESET} ${msg}`); }
6871
function error(msg) { log(`${RED}${RESET} ${msg}`); process.exit(1); }
6972
function heading(msg) { log(`\n${BOLD}${msg}${RESET}`); }
7073

71-
function requireKey() {
72-
const key = getApiKey();
73-
if (!key) {
74+
function startWaitingIndicator(message, { interruptMessage = 'Stopped waiting for browser approval.' } = {}) {
75+
if (!process.stdout.isTTY) {
76+
log(`${DIM}${message}${RESET}`);
77+
return () => {};
78+
}
79+
80+
const frames = ['-', '\\', '|', '/'];
81+
let index = 0;
82+
let stopped = false;
83+
const HIDE_CURSOR = '\x1b[?25l';
84+
const SHOW_CURSOR = '\x1b[?25h';
85+
const clearLine = () => process.stdout.write(`\r\x1b[2K${SHOW_CURSOR}`);
86+
const render = () => {
87+
if (stopped) return;
88+
process.stdout.write(`\r${HIDE_CURSOR}${DIM}${frames[index]} ${message}${RESET}`);
89+
};
90+
const stop = (finalMessage = '') => {
91+
if (stopped) return;
92+
stopped = true;
93+
clearInterval(timer);
94+
process.removeListener('SIGINT', handleSigint);
95+
clearLine();
96+
if (finalMessage) {
97+
log(finalMessage);
98+
}
99+
};
100+
const handleSigint = () => {
101+
stop(`${DIM}${interruptMessage}${RESET}`);
102+
process.exit(130);
103+
};
104+
105+
render();
106+
const timer = setInterval(() => {
107+
if (stopped) return;
108+
index = (index + 1) % frames.length;
109+
render();
110+
}, 120);
111+
process.once('SIGINT', handleSigint);
112+
113+
return stop;
114+
}
115+
116+
function createApiClient(auth = getStoredAuth()) {
117+
return new AgentAnalyticsAPI(auth, getBaseUrl(), {
118+
onAuthUpdate(nextAuth) {
119+
setAgentSession(nextAuth);
120+
},
121+
});
122+
}
123+
124+
function requireClient() {
125+
const auth = getStoredAuth();
126+
if (!auth) {
74127
error('Not logged in. Run: npx @agent-analytics/cli login');
75128
}
76-
return new AgentAnalyticsAPI(key, getBaseUrl());
129+
return createApiClient(auth);
77130
}
78131

79132
function withApi(fn) {
80133
return async (...args) => {
81-
const api = requireKey();
134+
const api = requireClient();
82135
try {
83136
return await fn(api, ...args);
84137
} catch (err) {
@@ -94,30 +147,85 @@ function ifEmpty(arr, label) {
94147

95148
// ==================== COMMANDS ====================
96149

97-
async function cmdLogin(token) {
150+
async function cmdLogin({ token, detached, exchangeCode, authRequestId }) {
98151
if (!token) {
152+
const api = createApiClient(null);
153+
154+
if (exchangeCode) {
155+
if (!authRequestId) {
156+
error('Manual exchange requires --auth-request <id> together with --exchange-code <code>');
157+
}
158+
const result = await finishManualExchange(api, authRequestId, exchangeCode);
159+
setAgentSession(result.agent_session);
160+
updateStoredAccount(result.account);
161+
success(`Connected as ${BOLD}${result.account.github_login || result.account.google_name || result.account.email}${RESET} (${result.account.tier})`);
162+
log(`${DIM}Agent session saved to ${getConfigFile()}${RESET}`);
163+
return;
164+
}
165+
166+
if (detached) {
167+
heading('Agent Analytics — Detached Login');
168+
let stopWaiting = () => {};
169+
try {
170+
const { started, exchanged } = await loginDetached(api, {
171+
onPending(started) {
172+
log(`Approval URL: ${CYAN}${started.authorize_url}${RESET}`);
173+
log(`Approval code: ${YELLOW}${started.approval_code}${RESET}`);
174+
log(`${DIM}Approve in the browser. If polling is blocked, finish manually with:${RESET}`);
175+
log(` ${CYAN}npx @agent-analytics/cli login --auth-request ${started.auth_request_id} --exchange-code <code>${RESET}`);
176+
log('');
177+
stopWaiting = startWaitingIndicator('Waiting for browser approval...');
178+
},
179+
});
180+
stopWaiting(`${DIM}Browser approval received.${RESET}`);
181+
setAgentSession(exchanged.agent_session);
182+
updateStoredAccount(exchanged.account);
183+
success(`Connected as ${BOLD}${exchanged.account.github_login || exchanged.account.google_name || exchanged.account.email}${RESET} (${exchanged.account.tier})`);
184+
log(`${DIM}Detached request ${started.auth_request_id} approved and saved to ${getConfigFile()}${RESET}`);
185+
return;
186+
} catch (err) {
187+
stopWaiting(`${DIM}Stopped waiting for browser approval.${RESET}`);
188+
throw err;
189+
}
190+
}
191+
99192
heading('Agent Analytics — Login');
100193
log('');
101-
log('Pass your API key from the dashboard:');
102-
log(` ${CYAN}npx @agent-analytics/cli login --token aak_your_key_here${RESET}`);
103-
log('');
104-
log('Or set it as an environment variable:');
105-
log(` ${CYAN}export AGENT_ANALYTICS_API_KEY=aak_your_key_here${RESET}`);
106-
log('');
107-
log(`Get your API key at: ${CYAN}https://app.agentanalytics.sh${RESET}`);
108-
log(`${DIM}Sign in with GitHub or Google — your API key is on the settings page.${RESET}`);
109-
return;
194+
let stopWaiting = () => {};
195+
try {
196+
const result = await loginInteractive(api, {
197+
onPending(started) {
198+
log(`Approval URL: ${CYAN}${started.authorize_url}${RESET}`);
199+
log(`Approval code: ${YELLOW}${started.approval_code}${RESET}`);
200+
log(`${DIM}The browser should open automatically. If it does not, open the URL above.${RESET}`);
201+
log('');
202+
stopWaiting = startWaitingIndicator('Waiting for browser approval...');
203+
},
204+
});
205+
stopWaiting(`${DIM}Browser approval received.${RESET}`);
206+
setAgentSession(result.agent_session);
207+
updateStoredAccount(result.account);
208+
success(`Connected as ${BOLD}${result.account.github_login || result.account.google_name || result.account.email}${RESET} (${result.account.tier})`);
209+
log(`${DIM}Agent session saved to ${getConfigFile()}${RESET}`);
210+
log('');
211+
log(`Fallbacks:`);
212+
log(` ${CYAN}npx @agent-analytics/cli login --detached${RESET}`);
213+
log(` ${CYAN}npx @agent-analytics/cli login --token aak_your_key_here${RESET} ${DIM}advanced/manual fallback${RESET}`);
214+
return;
215+
} catch (err) {
216+
stopWaiting(`${DIM}Stopped waiting for browser approval.${RESET}`);
217+
warn(`Interactive login failed: ${err.message}`);
218+
log(`Retry with detached approval: ${CYAN}npx @agent-analytics/cli login --detached${RESET}`);
219+
return;
220+
}
110221
}
111222

112-
// Validate the token works
113-
const api = new AgentAnalyticsAPI(token, getBaseUrl());
223+
// Advanced/manual fallback: API key login
224+
const api = createApiClient({ api_key: token });
114225
try {
115226
const account = await api.getAccount();
116227
setApiKey(token);
117-
const config = getConfig();
118-
config.email = account.email;
119-
config.github_login = account.github_login;
120-
saveConfig(config);
228+
updateStoredAccount(account);
121229

122230
success(`Logged in as ${BOLD}${account.github_login || account.email}${RESET} (${account.tier})`);
123231
log(`${DIM}API key saved to ${getConfigFile()}${RESET}`);
@@ -1065,8 +1173,10 @@ ${BOLD}USAGE${RESET}
10651173
npx @agent-analytics/cli <command> [options]
10661174
10671175
${BOLD}SETUP${RESET}
1068-
${CYAN}login${RESET} --token <key> Save your API key
1069-
${CYAN}logout${RESET} Clear your saved API key
1176+
${CYAN}login${RESET} Browser-based agent session login
1177+
${CYAN}login${RESET} --detached Detached approval flow for remote/headless runtimes
1178+
${CYAN}login${RESET} --token <key> Advanced/manual API key fallback
1179+
${CYAN}logout${RESET} Clear saved local auth
10701180
${CYAN}create${RESET} <name> Create a project and get your tracking snippet
10711181
${CYAN}projects${RESET} List all your projects
10721182
@@ -1117,8 +1227,8 @@ ${BOLD}KEY OPTIONS${RESET}
11171227
--window <N> Live view time window in seconds (default: 60)
11181228
11191229
${BOLD}QUICK START${RESET}
1120-
${DIM}# 1. Save your API key${RESET}
1121-
npx @agent-analytics/cli login --token aak_your_key
1230+
${DIM}# 1. Start agent login${RESET}
1231+
npx @agent-analytics/cli login
11221232
11231233
${DIM}# 2. Create a project${RESET}
11241234
npx @agent-analytics/cli create my-site --domain https://mysite.com
@@ -1151,7 +1261,12 @@ function getArg(flag) {
11511261
try {
11521262
switch (command) {
11531263
case 'login':
1154-
await cmdLogin(getArg('--token'));
1264+
await cmdLogin({
1265+
token: getArg('--token'),
1266+
detached: args.includes('--detached'),
1267+
exchangeCode: getArg('--exchange-code'),
1268+
authRequestId: getArg('--auth-request'),
1269+
});
11551270
break;
11561271
case 'logout':
11571272
cmdLogout();

0 commit comments

Comments
 (0)