agentbundle-cli 0.2.1 → 0.2.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 +5 -5
- package/bin/agent-bundle.js +2 -2
- package/lib/agentcard-api.js +44 -11
- package/lib/steps/agentcard.js +66 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,12 +10,12 @@ npx agentbundle-cli
|
|
|
10
10
|
|
|
11
11
|
## What it does
|
|
12
12
|
|
|
13
|
-
It asks for **one email up front** and reuses it for all three sign-ups (
|
|
14
|
-
|
|
13
|
+
It asks for **one email up front** and reuses it for all three sign-ups (each product emails a
|
|
14
|
+
verification code), then runs a guided, three-step onboarding:
|
|
15
15
|
|
|
16
|
-
1. **Agentcard** —
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
1. **Agentcard** — signs you up (you type in the emailed verification code), registers the
|
|
17
|
+
hosted HTTP MCP server (OAuth sign-in on first tool use), and offers to switch to
|
|
18
|
+
production (real cards).
|
|
19
19
|
2. **AgentMail** — fully automated: signs you up via the API (no console visit), provisions an
|
|
20
20
|
inbox, you type in the verification code, and it registers the hosted HTTP MCP server
|
|
21
21
|
(`mcp.agentmail.to`). Sign-ups are tagged with the `agentbundle` referrer.
|
package/bin/agent-bundle.js
CHANGED
|
@@ -59,7 +59,7 @@ function icon(kind) {
|
|
|
59
59
|
return kind === 'ok' ? ui.c.green('✓') : kind === 'warn' ? ui.c.yellow('!') : ui.c.red('✗');
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// One email drives all three sign-ups (Agentcard
|
|
62
|
+
// One email drives all three sign-ups (Agentcard, AgentMail + AgentPhone verification codes). Collect
|
|
63
63
|
// it once up front so each product reuses it instead of asking three times. Stores it on ctx.email.
|
|
64
64
|
async function collectEmail(ctx) {
|
|
65
65
|
if (ctx.email && ac.isValidEmail(ctx.email)) return ctx.email; // from --email / AGENT_BUNDLE_EMAIL
|
|
@@ -101,7 +101,7 @@ async function main() {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// Step one: collect the single email FIRST — before the confirm or any product — so every
|
|
104
|
-
// sign-up reuses it (Agentcard
|
|
104
|
+
// sign-up reuses it (Agentcard + AgentMail + AgentPhone verification codes) and no step ever asks
|
|
105
105
|
// for it again. A pre-set --email / AGENT_BUNDLE_EMAIL skips the prompt entirely.
|
|
106
106
|
// Only prompt when a selected product actually needs signup; if everything is already
|
|
107
107
|
// configured the prompt is skipped and the run stays a clean no-op.
|
package/lib/agentcard-api.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
// Minimal Agentcard auth client used to complete signup from inside agent-bundle, so the whole
|
|
3
|
-
// flow (ask email →
|
|
3
|
+
// flow (ask email → enter the emailed code → verify) happens here AND the signup is tagged with
|
|
4
|
+
// our source.
|
|
4
5
|
//
|
|
5
|
-
// Mirrors the agent-cards CLI: same API base, same ~/.agent-cards/config.json (
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// Mirrors the agent-cards CLI: same API base, same ~/.agent-cards/config.json (mode 0600), same
|
|
7
|
+
// endpoints. Current backends answer POST /auth/signup with { mode: 'workos' } and email a
|
|
8
|
+
// numeric code, completed via POST /auth/verify-code → { accessToken, refreshToken, email }.
|
|
9
|
+
// Older backends return { sessionId } and email a magic link, completed by polling GET /auth/me
|
|
10
|
+
// with X-Magic-Token — both flows are supported. The one difference from the agent-cards CLI:
|
|
11
|
+
// we send `x-platform: agentbundle` so the backend records these signups as coming from the
|
|
12
|
+
// bundle.
|
|
9
13
|
|
|
10
14
|
const fs = require('node:fs');
|
|
11
15
|
const path = require('node:path');
|
|
@@ -76,16 +80,36 @@ async function getJson(pathname, extraHeaders) {
|
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
//
|
|
80
|
-
|
|
83
|
+
// Start a signup/login. Returns { mode: 'workos' } (a numeric code was emailed; finish with
|
|
84
|
+
// verifyCode) or { mode: 'legacy', sessionId } (a magic link was emailed; finish with
|
|
85
|
+
// waitForVerification). Throws with a readable message on failure.
|
|
86
|
+
async function startSignup(email) {
|
|
81
87
|
const r = await postJson('/auth/signup', { email });
|
|
82
|
-
if (!r.ok
|
|
88
|
+
if (!r.ok) {
|
|
83
89
|
throw new Error(r.data.error || r.data.message || `signup failed (HTTP ${r.status})`);
|
|
84
90
|
}
|
|
85
|
-
|
|
91
|
+
if (r.data.mode === 'workos' || !r.data.sessionId) {
|
|
92
|
+
return { mode: 'workos' };
|
|
93
|
+
}
|
|
94
|
+
if (r.data._emailError) {
|
|
95
|
+
throw new Error('Email delivery failed on our side. Try again in a minute — if it keeps happening, email support@agentcard.sh.');
|
|
96
|
+
}
|
|
97
|
+
return { mode: 'legacy', sessionId: r.data.sessionId };
|
|
86
98
|
}
|
|
87
99
|
|
|
88
|
-
//
|
|
100
|
+
// Complete a code-based (WorkOS) login. Returns { accessToken, refreshToken, email }.
|
|
101
|
+
// Throws with err.status set (401 = wrong/expired code → worth re-prompting, 429 = throttled).
|
|
102
|
+
async function verifyCode(email, code) {
|
|
103
|
+
const r = await postJson('/auth/verify-code', { email, code });
|
|
104
|
+
if (r.ok && r.data && (r.data.accessToken || r.data.jwt)) {
|
|
105
|
+
return r.data;
|
|
106
|
+
}
|
|
107
|
+
const err = new Error(r.data.error || r.data.message || `verification failed (HTTP ${r.status})`);
|
|
108
|
+
err.status = r.status;
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Legacy flow only: poll until the user clicks the magic link. Returns { jwt, email, mode }.
|
|
89
113
|
async function waitForVerification(sessionId, { intervalMs = 2500, timeoutMs = 10 * 60 * 1000 } = {}) {
|
|
90
114
|
const deadline = Date.now() + timeoutMs;
|
|
91
115
|
// Date.now is fine at runtime; this module is never run inside the workflow sandbox.
|
|
@@ -103,12 +127,21 @@ function isValidEmail(s) {
|
|
|
103
127
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
|
|
104
128
|
}
|
|
105
129
|
|
|
130
|
+
// A stored session in ~/.agent-cards/config.json: either a WorkOS token pair
|
|
131
|
+
// (current) or a legacy magic-link JWT.
|
|
132
|
+
function hasSession(cfg) {
|
|
133
|
+
const c = cfg || readConfig();
|
|
134
|
+
return !!(c.accessToken || c.jwt);
|
|
135
|
+
}
|
|
136
|
+
|
|
106
137
|
module.exports = {
|
|
107
138
|
SOURCE,
|
|
108
139
|
API_URL,
|
|
109
140
|
readConfig,
|
|
110
141
|
writeConfig,
|
|
111
|
-
|
|
142
|
+
hasSession,
|
|
143
|
+
startSignup,
|
|
144
|
+
verifyCode,
|
|
112
145
|
waitForVerification,
|
|
113
146
|
isValidEmail,
|
|
114
147
|
};
|
package/lib/steps/agentcard.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
// [1/3] Agentcard — virtual cards. agent-bundle drives the full signup (ask email →
|
|
3
|
-
// verify), tagging the signup source as `agentbundle`, then registers the MCP
|
|
2
|
+
// [1/3] Agentcard — virtual cards. agent-bundle drives the full signup (ask email → enter the
|
|
3
|
+
// emailed code → verify), tagging the signup source as `agentbundle`, then registers the MCP
|
|
4
|
+
// server. Registration uses OAuth (no static Authorization header): Claude opens a browser
|
|
5
|
+
// sign-in the first time a tool runs. A baked bearer would be a short-lived WorkOS access token
|
|
6
|
+
// that silently starts 401ing within hours — same reason `agent-cards setup-mcp` dropped it.
|
|
4
7
|
|
|
5
8
|
const { spawnSync } = require('node:child_process');
|
|
6
9
|
const ui = require('../ui');
|
|
@@ -21,37 +24,73 @@ function run(cmd, args) {
|
|
|
21
24
|
return !r.error && r.status === 0;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
const CODE_MAX_ENTRIES = 3;
|
|
28
|
+
|
|
29
|
+
// Sign up (or in): current backends email a numeric code we verify; legacy backends email a
|
|
30
|
+
// magic link we poll for. Reuses the shared email collected up front (ctx.email); only asks if
|
|
31
|
+
// one wasn't provided. Returns true when a session is stored in ~/.agent-cards/config.json.
|
|
26
32
|
async function completeSignup(ctx) {
|
|
27
33
|
let email = ctx && ctx.email && ac.isValidEmail(ctx.email) ? ctx.email : '';
|
|
28
34
|
for (let tries = 0; tries < 3 && !email; tries++) {
|
|
29
35
|
const entered = await ui.ask('What email should your Agentcard account use?');
|
|
30
|
-
if (!entered) return
|
|
36
|
+
if (!entered) return false; // blank = skip
|
|
31
37
|
if (ac.isValidEmail(entered)) email = entered;
|
|
32
38
|
else ui.warn('That doesn\'t look like an email — try again.');
|
|
33
39
|
}
|
|
34
|
-
if (!email) return
|
|
40
|
+
if (!email) return false;
|
|
35
41
|
|
|
36
|
-
let
|
|
42
|
+
let start;
|
|
37
43
|
try {
|
|
38
|
-
ui.step(`
|
|
39
|
-
|
|
44
|
+
ui.step(`Starting Agentcard sign-in for ${email}…`);
|
|
45
|
+
start = await ac.startSignup(email);
|
|
40
46
|
} catch (e) {
|
|
41
47
|
ui.fail('Could not start signup: ' + e.message);
|
|
42
|
-
return
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (start.mode === 'workos') {
|
|
52
|
+
ui.ok(`We emailed a sign-in code to ${ui.c.bold(email)}.`);
|
|
53
|
+
for (let attempt = 1; attempt <= CODE_MAX_ENTRIES; attempt++) {
|
|
54
|
+
const code = (await ui.ask('Enter the code from your email:')).trim();
|
|
55
|
+
if (!code) {
|
|
56
|
+
ui.warn('No code entered — skipping Agentcard signup.');
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const verified = await ac.verifyCode(email, code);
|
|
61
|
+
// Replace the whole session: a stale legacy jwt left in place would shadow
|
|
62
|
+
// the fresh token pair for anything that still reads it.
|
|
63
|
+
ac.writeConfig({
|
|
64
|
+
email: verified.email || email,
|
|
65
|
+
accessToken: verified.accessToken,
|
|
66
|
+
refreshToken: verified.refreshToken,
|
|
67
|
+
jwt: undefined,
|
|
68
|
+
});
|
|
69
|
+
ui.ok(`Signed in as ${ui.c.bold(verified.email || email)}`);
|
|
70
|
+
return true;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (e.status === 401 && attempt < CODE_MAX_ENTRIES) {
|
|
73
|
+
ui.warn(`That code didn't work (attempt ${attempt}/${CODE_MAX_ENTRIES}) — check the newest email to ${email}.`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
ui.fail('Signup not completed: ' + e.message);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
43
81
|
}
|
|
44
82
|
|
|
83
|
+
// Legacy magic-link backend.
|
|
45
84
|
ui.ok(`Magic link sent to ${ui.c.bold(email)}.`);
|
|
46
85
|
ui.info('Open your inbox and click the link — ' + ui.c.dim('waiting for you…'));
|
|
47
86
|
try {
|
|
48
|
-
const { jwt, email: verifiedEmail, mode } = await ac.waitForVerification(sessionId);
|
|
49
|
-
ac.writeConfig({ email: verifiedEmail || email, jwt, mode });
|
|
87
|
+
const { jwt, email: verifiedEmail, mode } = await ac.waitForVerification(start.sessionId);
|
|
88
|
+
ac.writeConfig({ email: verifiedEmail || email, jwt, mode, accessToken: undefined, refreshToken: undefined });
|
|
50
89
|
ui.ok(`Signed in as ${ui.c.bold(verifiedEmail || email)}` + (mode ? ` (${mode} mode)` : ''));
|
|
51
|
-
return
|
|
90
|
+
return true;
|
|
52
91
|
} catch (e) {
|
|
53
92
|
ui.fail('Signup not completed: ' + e.message);
|
|
54
|
-
return
|
|
93
|
+
return false;
|
|
55
94
|
}
|
|
56
95
|
}
|
|
57
96
|
|
|
@@ -64,35 +103,34 @@ async function step(ctx) {
|
|
|
64
103
|
return { product: 'Agentcard', icon: 'ok', note: 'already configured' };
|
|
65
104
|
}
|
|
66
105
|
|
|
67
|
-
// 1. Get an authenticated session (
|
|
68
|
-
|
|
106
|
+
// 1. Get an authenticated session (so the signup is attributed to the bundle and
|
|
107
|
+
// go-live has an account to work with). The MCP server itself authenticates via
|
|
108
|
+
// OAuth, so no token is threaded into the registration below.
|
|
69
109
|
if (ctx.dryRun) {
|
|
70
|
-
ui.step('would use the shared email, POST /auth/signup (x-platform: agentbundle), and
|
|
71
|
-
ui.dim(`would: claude mcp add --transport http ${MCP_NAME} ${MCP_URL}
|
|
110
|
+
ui.step('would use the shared email, POST /auth/signup (x-platform: agentbundle), and verify the emailed code');
|
|
111
|
+
ui.dim(`would: claude mcp add --transport http ${MCP_NAME} ${MCP_URL} (OAuth — no static header)`);
|
|
72
112
|
return { product: 'Agentcard', icon: 'ok', note: 'configured (dry-run)' };
|
|
73
113
|
} else if (ctx.sandbox) {
|
|
74
|
-
ui.step('[sandbox] simulating email →
|
|
75
|
-
|
|
76
|
-
} else if (ac.readConfig().jwt) {
|
|
114
|
+
ui.step('[sandbox] simulating email → code → verified');
|
|
115
|
+
} else if (ac.hasSession()) {
|
|
77
116
|
const cfg = ac.readConfig();
|
|
78
117
|
ui.ok(`Already signed in to Agentcard${cfg.email ? ' as ' + ui.c.bold(cfg.email) : ''} — skipping signup.`);
|
|
79
|
-
jwt = cfg.jwt;
|
|
80
118
|
} else if (!process.stdin.isTTY) {
|
|
81
119
|
ui.warn('Not signed in to Agentcard, and no interactive terminal to complete signup.');
|
|
82
|
-
ui.info('Re-run this in a terminal so it can collect your email and the
|
|
120
|
+
ui.info('Re-run this in a terminal so it can collect your email and the emailed sign-in code.');
|
|
83
121
|
return { product: 'Agentcard', icon: 'warn', note: 'needs interactive signup' };
|
|
84
122
|
} else {
|
|
85
|
-
|
|
86
|
-
if (!
|
|
123
|
+
const signedIn = await completeSignup(ctx);
|
|
124
|
+
if (!signedIn) {
|
|
87
125
|
ui.warn('Skipped Agentcard signup — re-run with --only agentcard when ready.');
|
|
88
126
|
return { product: 'Agentcard', icon: 'warn', note: 'signup not completed' };
|
|
89
127
|
}
|
|
90
128
|
}
|
|
91
129
|
|
|
92
|
-
// 2. Register the MCP server
|
|
130
|
+
// 2. Register the MCP server (OAuth: a browser sign-in appears the first time a tool runs).
|
|
93
131
|
ui.step('Registering the Agentcard MCP server…');
|
|
94
132
|
const res = mcp.addServer(
|
|
95
|
-
{ name: MCP_NAME, transport: 'http', url: MCP_URL
|
|
133
|
+
{ name: MCP_NAME, transport: 'http', url: MCP_URL },
|
|
96
134
|
{ dryRun: ctx.dryRun }
|
|
97
135
|
);
|
|
98
136
|
if (res.error) {
|
|
@@ -126,7 +164,7 @@ async function step(ctx) {
|
|
|
126
164
|
// Whether a real run would need the shared email for this product (i.e. it isn't already set up).
|
|
127
165
|
// Lets the launcher skip the up-front email prompt when nothing needs signup.
|
|
128
166
|
function needsSetup() {
|
|
129
|
-
return !mcp.exists(MCP_NAME) && !ac.
|
|
167
|
+
return !mcp.exists(MCP_NAME) && !ac.hasSession();
|
|
130
168
|
}
|
|
131
169
|
|
|
132
170
|
module.exports = { step, MCP_NAME, needsSetup };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentbundle-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "One command to give your AI agent a card, an inbox, and a phone — installs Agentcard, AgentMail, and AgentPhone via a guided onboarding flow.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentbundle-cli": "bin/agent-bundle.js",
|