agentbundle-cli 0.1.1 → 0.2.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 +13 -9
- package/bin/agent-bundle.js +23 -9
- package/lib/agentcard-api.js +11 -1
- package/lib/mcp.js +1 -2
- package/lib/steps/agentcard.js +15 -34
- package/lib/steps/agentmail.js +0 -3
- package/lib/steps/agentphone.js +0 -6
- package/lib/steps/bundle.js +136 -0
- package/lib/ui.js +3 -1
- package/package.json +2 -10
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ One command to give your AI agent the three things it needs to act online — a
|
|
|
5
5
|
you through installing all three, one at a time, and registers each as an MCP server.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx
|
|
8
|
+
npx agent-bundle
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## What it does
|
|
@@ -19,6 +19,13 @@ A guided, three-step onboarding:
|
|
|
19
19
|
hosted HTTP MCP server (`mcp.agentmail.to`).
|
|
20
20
|
3. **AgentPhone** — fully automated: requests a verification code by email, you type it in, it
|
|
21
21
|
provisions a real US number, and registers the stdio MCP server (`npx -y agentphone-mcp`). No browser.
|
|
22
|
+
4. **Agent Bundle** — with all three installed, it offers the bundle subscription: **$20/mo** for
|
|
23
|
+
Agentcard + AgentMail + AgentPhone together (about half of buying them standalone). It opens a
|
|
24
|
+
Stripe-hosted checkout on the bundle server (theagentbundle.com), keyed by your email — no charge
|
|
25
|
+
until you enter a card and confirm on Stripe's page. Re-run with `--only bundle` anytime.
|
|
26
|
+
|
|
27
|
+
Every account is created in **production** — real inboxes, real numbers, and live Agentcard cards.
|
|
28
|
+
There is no test or sandbox mode.
|
|
22
29
|
|
|
23
30
|
All MCP servers are registered at **user scope** (available in every project), via `claude mcp add`.
|
|
24
31
|
|
|
@@ -28,8 +35,7 @@ It's **idempotent** — anything already configured is skipped, so it's safe to
|
|
|
28
35
|
|
|
29
36
|
| Flag | Effect |
|
|
30
37
|
|---|---|
|
|
31
|
-
| `--only <agentcard\|agentmail\|agentphone>` | Run just one product. |
|
|
32
|
-
| `--sandbox` | Walk the whole flow with stub signups + a throwaway config — no real accounts, no charges. |
|
|
38
|
+
| `--only <agentcard\|agentmail\|agentphone\|bundle>` | Run just one product, or `bundle` for the $20/mo bundle checkout. |
|
|
33
39
|
| `--dry-run` | Print what it would do, change nothing. |
|
|
34
40
|
| `--yes`, `-y` | Accept defaults, skip confirmations. |
|
|
35
41
|
| `--no-color` | Plain output. |
|
|
@@ -40,17 +46,15 @@ It's **idempotent** — anything already configured is skipped, so it's safe to
|
|
|
40
46
|
```bash
|
|
41
47
|
export AGENTMAIL_API_KEY=am_...
|
|
42
48
|
export AGENTPHONE_API_KEY=sk_live_...
|
|
43
|
-
npx
|
|
49
|
+
npx agent-bundle
|
|
44
50
|
```
|
|
45
51
|
|
|
46
|
-
##
|
|
52
|
+
## Preview without changing anything
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
config is never touched and no money is spent:
|
|
54
|
+
Use `--dry-run` to print every step it would take without touching your config or creating accounts:
|
|
50
55
|
|
|
51
56
|
```bash
|
|
52
|
-
|
|
53
|
-
cat /tmp/bundle-test.json
|
|
57
|
+
npx agent-bundle --dry-run
|
|
54
58
|
```
|
|
55
59
|
|
|
56
60
|
## How config is written
|
package/bin/agent-bundle.js
CHANGED
|
@@ -7,6 +7,7 @@ const mcp = require('../lib/mcp');
|
|
|
7
7
|
const agentcard = require('../lib/steps/agentcard');
|
|
8
8
|
const agentmail = require('../lib/steps/agentmail');
|
|
9
9
|
const agentphone = require('../lib/steps/agentphone');
|
|
10
|
+
const bundle = require('../lib/steps/bundle');
|
|
10
11
|
|
|
11
12
|
const STEPS = {
|
|
12
13
|
agentcard: agentcard.step,
|
|
@@ -14,12 +15,14 @@ const STEPS = {
|
|
|
14
15
|
agentphone: agentphone.step,
|
|
15
16
|
};
|
|
16
17
|
|
|
18
|
+
// The bundle subscription runs after the three products, not as one of them.
|
|
19
|
+
const ONLY_TARGETS = new Set([...Object.keys(STEPS), 'bundle']);
|
|
20
|
+
|
|
17
21
|
function parseArgs(argv) {
|
|
18
|
-
const ctx = { dryRun: false,
|
|
22
|
+
const ctx = { dryRun: false, yes: false, only: null, help: false };
|
|
19
23
|
for (let i = 0; i < argv.length; i++) {
|
|
20
24
|
const a = argv[i];
|
|
21
25
|
if (a === '--dry-run') ctx.dryRun = true;
|
|
22
|
-
else if (a === '--sandbox') ctx.sandbox = true;
|
|
23
26
|
else if (a === '--yes' || a === '-y') ctx.yes = true;
|
|
24
27
|
else if (a === '--no-color') {/* handled in ui */}
|
|
25
28
|
else if (a === '--help' || a === '-h') ctx.help = true;
|
|
@@ -34,15 +37,13 @@ function help() {
|
|
|
34
37
|
ui.info('Guided setup for your agent\'s card, inbox, and phone.\n');
|
|
35
38
|
ui.info(ui.c.bold('Usage:') + ' npx agentbundle-cli [options]\n');
|
|
36
39
|
ui.info(ui.c.bold('Options:'));
|
|
37
|
-
ui.dim('--only <agentcard|agentmail|agentphone> run just one product');
|
|
38
|
-
ui.dim('--sandbox walk the whole flow with stub signups + a throwaway config (no money, no real accounts)');
|
|
40
|
+
ui.dim('--only <agentcard|agentmail|agentphone|bundle> run just one product (or the $20/mo bundle checkout)');
|
|
39
41
|
ui.dim('--dry-run print what it would do, change nothing');
|
|
40
42
|
ui.dim('--yes, -y accept defaults, skip confirmations');
|
|
41
43
|
ui.dim('--no-color disable colored output');
|
|
42
44
|
ui.dim('--help, -h show this help');
|
|
43
45
|
ui.blank();
|
|
44
46
|
ui.info(ui.c.bold('Pre-set keys to skip prompts:') + ' AGENTMAIL_API_KEY, AGENTPHONE_API_KEY');
|
|
45
|
-
ui.info(ui.c.bold('Test safely:') + ' AGENT_BUNDLE_MCP_CONFIG=/tmp/x.json npx agentbundle-cli --sandbox');
|
|
46
47
|
ui.blank();
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -56,7 +57,6 @@ async function main() {
|
|
|
56
57
|
|
|
57
58
|
ui.banner();
|
|
58
59
|
if (ctx.dryRun) ui.warn('DRY RUN — nothing will be changed.');
|
|
59
|
-
if (ctx.sandbox) ui.warn('SANDBOX — stub signups, throwaway config, no real accounts or charges.');
|
|
60
60
|
|
|
61
61
|
// Preflight
|
|
62
62
|
ui.heading(0, 3, 'Preflight');
|
|
@@ -66,13 +66,16 @@ async function main() {
|
|
|
66
66
|
|
|
67
67
|
let order = ['agentcard', 'agentmail', 'agentphone'];
|
|
68
68
|
if (ctx.only) {
|
|
69
|
-
if (!
|
|
70
|
-
ui.fail(`Unknown --only target: ${ctx.only} (use agentcard | agentmail | agentphone)`);
|
|
69
|
+
if (!ONLY_TARGETS.has(ctx.only)) {
|
|
70
|
+
ui.fail(`Unknown --only target: ${ctx.only} (use agentcard | agentmail | agentphone | bundle)`);
|
|
71
71
|
process.exit(2);
|
|
72
72
|
}
|
|
73
|
-
order = [ctx.only];
|
|
73
|
+
order = ctx.only === 'bundle' ? [] : [ctx.only];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// The $20/mo bundle is offered after a full run, or when explicitly requested.
|
|
77
|
+
const runBundle = !ctx.only || ctx.only === 'bundle';
|
|
78
|
+
|
|
76
79
|
if (!ctx.yes && !ctx.dryRun) {
|
|
77
80
|
ui.blank();
|
|
78
81
|
const go = await ui.confirm('Ready to set up ' + (ctx.only ? ctx.only : 'all three') + '?', true);
|
|
@@ -94,6 +97,17 @@ async function main() {
|
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
99
|
|
|
100
|
+
// Offer the $20/mo bundle once the products are in place.
|
|
101
|
+
if (runBundle) {
|
|
102
|
+
ui.blank();
|
|
103
|
+
try {
|
|
104
|
+
results.push(await bundle.step(ctx));
|
|
105
|
+
} catch (e) {
|
|
106
|
+
ui.fail(`bundle crashed: ${e.message}`);
|
|
107
|
+
results.push({ product: 'Bundle', icon: 'fail', note: e.message });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
// Summary
|
|
98
112
|
ui.blank();
|
|
99
113
|
ui.info(ui.c.bold('── Summary ──────────────────────────────────────────'));
|
package/lib/agentcard-api.js
CHANGED
|
@@ -88,7 +88,6 @@ async function requestMagicLink(email) {
|
|
|
88
88
|
// Poll until the user clicks the magic link. Returns { jwt, email, mode }.
|
|
89
89
|
async function waitForVerification(sessionId, { intervalMs = 2500, timeoutMs = 10 * 60 * 1000 } = {}) {
|
|
90
90
|
const deadline = Date.now() + timeoutMs;
|
|
91
|
-
// Date.now is fine at runtime; this module is never run inside the workflow sandbox.
|
|
92
91
|
for (;;) {
|
|
93
92
|
const r = await getJson('/auth/me', { 'X-Magic-Token': sessionId });
|
|
94
93
|
if (r.ok && r.data && r.data.status === 'verified' && r.data.jwt) {
|
|
@@ -103,6 +102,16 @@ function isValidEmail(s) {
|
|
|
103
102
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
|
|
104
103
|
}
|
|
105
104
|
|
|
105
|
+
// Put the signed-in account into production mode (real cards). New accounts are
|
|
106
|
+
// created live, so we call this right after signup. Returns the resulting mode.
|
|
107
|
+
async function setMode(jwt, mode) {
|
|
108
|
+
const r = await postJson('/users/me/mode', { mode }, { Authorization: `Bearer ${jwt}` });
|
|
109
|
+
if (!r.ok) throw new Error(r.data.error || r.data.message || `mode change failed (HTTP ${r.status})`);
|
|
110
|
+
// Keep the local config in sync with the account's mode.
|
|
111
|
+
writeConfig({ mode });
|
|
112
|
+
return r.data.mode || mode;
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
module.exports = {
|
|
107
116
|
SOURCE,
|
|
108
117
|
API_URL,
|
|
@@ -111,4 +120,5 @@ module.exports = {
|
|
|
111
120
|
requestMagicLink,
|
|
112
121
|
waitForVerification,
|
|
113
122
|
isValidEmail,
|
|
123
|
+
setMode,
|
|
114
124
|
};
|
package/lib/mcp.js
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
// Two backends, auto-selected:
|
|
5
5
|
// * "claude": uses the `claude` CLI (`claude mcp get` / `claude mcp add`). Default in real use.
|
|
6
6
|
// * "file": merges into a JSON config file. Used when AGENT_BUNDLE_MCP_CONFIG is set, or when
|
|
7
|
-
// `claude` isn't on PATH
|
|
8
|
-
// user's real ~/.claude config.
|
|
7
|
+
// `claude` isn't on PATH — writes to that file instead of the user's ~/.claude config.
|
|
9
8
|
|
|
10
9
|
const { spawnSync } = require('node:child_process');
|
|
11
10
|
const fs = require('node:fs');
|
package/lib/steps/agentcard.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
// [1/3] Agentcard — virtual cards. agent-bundle drives the full signup (ask email → magic link →
|
|
3
|
-
// verify), tagging the signup source as `agentbundle`, then registers the MCP server with the JWT
|
|
3
|
+
// verify), tagging the signup source as `agentbundle`, then registers the MCP server with the JWT
|
|
4
|
+
// and puts the account into production (real cards) — there's no test mode in this flow.
|
|
4
5
|
|
|
5
|
-
const { spawnSync } = require('node:child_process');
|
|
6
6
|
const ui = require('../ui');
|
|
7
7
|
const mcp = require('../mcp');
|
|
8
8
|
const ac = require('../agentcard-api');
|
|
@@ -10,17 +10,6 @@ const ac = require('../agentcard-api');
|
|
|
10
10
|
const MCP_NAME = 'agent-cards';
|
|
11
11
|
const MCP_URL = 'https://mcp.agentcard.sh/mcp';
|
|
12
12
|
|
|
13
|
-
function have(cmd) {
|
|
14
|
-
const r = spawnSync(cmd, ['--version'], { stdio: 'ignore' });
|
|
15
|
-
return !r.error && r.status === 0;
|
|
16
|
-
}
|
|
17
|
-
function run(cmd, args) {
|
|
18
|
-
ui.pause();
|
|
19
|
-
const r = spawnSync(cmd, args, { stdio: 'inherit' });
|
|
20
|
-
ui.resume();
|
|
21
|
-
return !r.error && r.status === 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
13
|
// Ask for the email, send the magic link, and wait for the click. Returns the JWT, or null.
|
|
25
14
|
async function completeSignup() {
|
|
26
15
|
let email = '';
|
|
@@ -46,7 +35,7 @@ async function completeSignup() {
|
|
|
46
35
|
try {
|
|
47
36
|
const { jwt, email: verifiedEmail, mode } = await ac.waitForVerification(sessionId);
|
|
48
37
|
ac.writeConfig({ email: verifiedEmail || email, jwt, mode });
|
|
49
|
-
ui.ok(`Signed in as ${ui.c.bold(verifiedEmail || email)}`
|
|
38
|
+
ui.ok(`Signed in as ${ui.c.bold(verifiedEmail || email)}`);
|
|
50
39
|
return jwt;
|
|
51
40
|
} catch (e) {
|
|
52
41
|
ui.fail('Signup not completed: ' + e.message);
|
|
@@ -68,10 +57,8 @@ async function step(ctx) {
|
|
|
68
57
|
if (ctx.dryRun) {
|
|
69
58
|
ui.step('would ask for your email, POST /auth/signup (x-platform: agentbundle), and wait for the magic link');
|
|
70
59
|
ui.dim(`would: claude mcp add --transport http ${MCP_NAME} ${MCP_URL} --header "Authorization: Bearer <jwt>"`);
|
|
60
|
+
ui.dim('would: POST /users/me/mode { mode: "prod" } to put the account into production');
|
|
71
61
|
return { product: 'Agentcard', icon: 'ok', note: 'configured (dry-run)' };
|
|
72
|
-
} else if (ctx.sandbox) {
|
|
73
|
-
ui.step('[sandbox] simulating email → magic link → verified');
|
|
74
|
-
jwt = 'sandbox_placeholder_jwt';
|
|
75
62
|
} else if (ac.readConfig().jwt) {
|
|
76
63
|
const cfg = ac.readConfig();
|
|
77
64
|
ui.ok(`Already signed in to Agentcard${cfg.email ? ' as ' + ui.c.bold(cfg.email) : ''} — skipping signup.`);
|
|
@@ -100,23 +87,17 @@ async function step(ctx) {
|
|
|
100
87
|
}
|
|
101
88
|
ui.ok('Agentcard MCP server registered.');
|
|
102
89
|
|
|
103
|
-
// 3.
|
|
104
|
-
let note = 'configured (
|
|
105
|
-
|
|
106
|
-
ui.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
ui.info('Install the CLI (' + ui.c.bold('npm i -g agent-cards') + ') then run ' + ui.c.bold('agent-cards mode prod') + ' to go live.');
|
|
115
|
-
note = 'configured (TEST mode — install CLI to go live)';
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
ui.info('Staying in ' + ui.c.bold('TEST') + ' mode (sandbox cards). Go live anytime with ' + ui.c.bold('agent-cards mode prod') + '.');
|
|
119
|
-
}
|
|
90
|
+
// 3. Put the account into PRODUCTION (real cards). New accounts go live by default.
|
|
91
|
+
let note = 'configured (production)';
|
|
92
|
+
try {
|
|
93
|
+
ui.step('Setting your account to production…');
|
|
94
|
+
await ac.setMode(jwt, 'prod');
|
|
95
|
+
ui.ok('Agentcard account is in ' + ui.c.bold('production') + ' mode.');
|
|
96
|
+
ui.dim('Add a payment method to fund real cards: ' + ui.c.bold('agent-cards payment-method') + ' (or in the dashboard).');
|
|
97
|
+
} catch (e) {
|
|
98
|
+
ui.warn('Could not set production mode automatically: ' + e.message);
|
|
99
|
+
ui.dim('Set it from the dashboard, or run ' + ui.c.bold('agent-cards mode prod') + '.');
|
|
100
|
+
note = 'configured (set production in the dashboard)';
|
|
120
101
|
}
|
|
121
102
|
|
|
122
103
|
return { product: 'Agentcard', icon: 'ok', note };
|
package/lib/steps/agentmail.js
CHANGED
|
@@ -21,9 +21,6 @@ async function step(ctx) {
|
|
|
21
21
|
let key = process.env.AGENTMAIL_API_KEY || '';
|
|
22
22
|
if (key) {
|
|
23
23
|
ui.ok(`Using AGENTMAIL_API_KEY from your environment (${ui.mask(key)}).`);
|
|
24
|
-
} else if (ctx.sandbox) {
|
|
25
|
-
key = 'am_sandbox_placeholder_key';
|
|
26
|
-
ui.step('[sandbox] using a placeholder AgentMail key.');
|
|
27
24
|
} else if (ctx.dryRun) {
|
|
28
25
|
ui.step('would prompt for an AgentMail API key (am_…)');
|
|
29
26
|
ui.dim(`would: claude mcp add --transport http ${MCP_NAME} ${MCP_URL} --header "x-api-key: …"`);
|
package/lib/steps/agentphone.js
CHANGED
|
@@ -65,12 +65,6 @@ async function step(ctx) {
|
|
|
65
65
|
return { product: 'AgentPhone', icon: 'ok', note: 'configured (dry-run)' };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
if (ctx.sandbox) {
|
|
69
|
-
ui.step('[sandbox] simulating sign-up → OTP → verify…');
|
|
70
|
-
ui.ok('[sandbox] provisioned number +1 (555) 010-0042');
|
|
71
|
-
return register('sk_live_sandbox_placeholder_key', ctx, 'configured (sandbox number)');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
68
|
// Real flow.
|
|
75
69
|
const email = await ui.ask('What email should AgentPhone send your verification code to?');
|
|
76
70
|
if (!email) {
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Final step — the $20/mo bundle subscription. After all three MCP servers are
|
|
3
|
+
// installed, this offers to subscribe to the Agent Bundle: one $20/mo charge for
|
|
4
|
+
// Agentcard + AgentMail + AgentPhone (vs ~$43 buying them standalone).
|
|
5
|
+
//
|
|
6
|
+
// Billing runs on the Agent Bundle's OWN server (theagentbundle.com), on the
|
|
7
|
+
// Agentcard Corporation Stripe account — separate from Agentcard's product
|
|
8
|
+
// backend. The customer is keyed by email; we just open Stripe's hosted
|
|
9
|
+
// checkout, so no charge happens until they enter a card and confirm there.
|
|
10
|
+
|
|
11
|
+
const { spawn } = require('node:child_process');
|
|
12
|
+
const ui = require('../ui');
|
|
13
|
+
const ac = require('../agentcard-api');
|
|
14
|
+
|
|
15
|
+
const PRICE_LABEL = '$20/mo';
|
|
16
|
+
// The bundle server. Override for local/staging with AGENT_BUNDLE_API_URL.
|
|
17
|
+
const BUNDLE_API_URL = (process.env.AGENT_BUNDLE_API_URL || 'https://theagentbundle.com').replace(/\/$/, '');
|
|
18
|
+
|
|
19
|
+
async function fetchJson(method, path, body) {
|
|
20
|
+
const ctrl = new AbortController();
|
|
21
|
+
const t = setTimeout(() => ctrl.abort(), 30000);
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch(BUNDLE_API_URL + path, {
|
|
24
|
+
method,
|
|
25
|
+
headers: { 'Content-Type': 'application/json', 'x-source': 'agent-bundle-cli' },
|
|
26
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
27
|
+
signal: ctrl.signal,
|
|
28
|
+
});
|
|
29
|
+
const text = await res.text();
|
|
30
|
+
let data;
|
|
31
|
+
try { data = JSON.parse(text); } catch { data = { raw: text }; }
|
|
32
|
+
return { ok: res.ok, status: res.status, data };
|
|
33
|
+
} finally {
|
|
34
|
+
clearTimeout(t);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Open a URL in the user's default browser, best-effort and non-blocking.
|
|
39
|
+
function openInBrowser(url) {
|
|
40
|
+
const cmd = process.platform === 'darwin' ? 'open'
|
|
41
|
+
: process.platform === 'win32' ? 'cmd'
|
|
42
|
+
: 'xdg-open';
|
|
43
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
44
|
+
try {
|
|
45
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
46
|
+
child.on('error', () => {});
|
|
47
|
+
child.unref();
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Use the email from the Agentcard signup; fall back to asking.
|
|
55
|
+
async function resolveEmail(ctx) {
|
|
56
|
+
const saved = ac.readConfig().email;
|
|
57
|
+
if (saved && ac.isValidEmail(saved)) return saved;
|
|
58
|
+
if (ctx.yes || !process.stdin.isTTY) return null;
|
|
59
|
+
for (let tries = 0; tries < 3; tries++) {
|
|
60
|
+
const entered = await ui.ask('What email should your bundle subscription use?');
|
|
61
|
+
if (!entered) return null;
|
|
62
|
+
if (ac.isValidEmail(entered)) return entered;
|
|
63
|
+
ui.warn('That doesn\'t look like an email — try again.');
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function step(ctx) {
|
|
69
|
+
ui.heading(0, 0, 'Agent Bundle — one subscription for all three');
|
|
70
|
+
ui.dim(`Agentcard + AgentMail + AgentPhone together for ${PRICE_LABEL} (about half of buying them separately).`);
|
|
71
|
+
|
|
72
|
+
if (ctx.dryRun) {
|
|
73
|
+
ui.step(`would: POST ${BUNDLE_API_URL}/api/checkout {email} → open Stripe checkout for ${PRICE_LABEL}`);
|
|
74
|
+
return { product: 'Bundle', icon: 'ok', note: `offer ${PRICE_LABEL} (dry-run)` };
|
|
75
|
+
}
|
|
76
|
+
const email = await resolveEmail(ctx);
|
|
77
|
+
if (!email) {
|
|
78
|
+
ui.warn('No email for the bundle — skipping. Re-run with ' + ui.c.bold('--only bundle') + ' to subscribe.');
|
|
79
|
+
return { product: 'Bundle', icon: 'warn', note: 'needs email' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Is the bundle live, and is this email already subscribed?
|
|
83
|
+
try {
|
|
84
|
+
const r = await fetchJson('GET', `/api/status?email=${encodeURIComponent(email)}`);
|
|
85
|
+
if (r.ok && r.data && r.data.configured === false) {
|
|
86
|
+
ui.info('The bundle isn\'t available yet — your three MCP servers are installed and ready.');
|
|
87
|
+
return { product: 'Bundle', icon: 'ok', note: 'not offered (coming soon)' };
|
|
88
|
+
}
|
|
89
|
+
if (r.ok && r.data && r.data.active) {
|
|
90
|
+
ui.ok('This email already has an active Agent Bundle — nothing to do.');
|
|
91
|
+
return { product: 'Bundle', icon: 'ok', note: 'already subscribed' };
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
ui.dim('(could not check bundle status — offering anyway)');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!ctx.yes) {
|
|
98
|
+
const go = await ui.confirm(`Subscribe to the Agent Bundle for ${PRICE_LABEL} now?`, true);
|
|
99
|
+
if (!go) {
|
|
100
|
+
ui.info('No problem — subscribe anytime by re-running with ' + ui.c.bold('--only bundle') + '.');
|
|
101
|
+
return { product: 'Bundle', icon: 'warn', note: 'declined' };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let checkoutUrl;
|
|
106
|
+
try {
|
|
107
|
+
ui.step('Creating your secure Stripe checkout…');
|
|
108
|
+
const r = await fetchJson('POST', '/api/checkout', { email });
|
|
109
|
+
if (r.status === 503) {
|
|
110
|
+
ui.info('The bundle isn\'t available yet — your three MCP servers are installed and ready.');
|
|
111
|
+
return { product: 'Bundle', icon: 'ok', note: 'not offered (coming soon)' };
|
|
112
|
+
}
|
|
113
|
+
if (r.status === 409) {
|
|
114
|
+
ui.ok('This email already has an active Agent Bundle — nothing to do.');
|
|
115
|
+
return { product: 'Bundle', icon: 'ok', note: 'already subscribed' };
|
|
116
|
+
}
|
|
117
|
+
if (!r.ok || !r.data || !r.data.checkoutUrl) {
|
|
118
|
+
throw new Error((r.data && r.data.error) || `HTTP ${r.status}`);
|
|
119
|
+
}
|
|
120
|
+
checkoutUrl = r.data.checkoutUrl;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
ui.fail('Could not start checkout: ' + e.message);
|
|
123
|
+
return { product: 'Bundle', icon: 'fail', note: 'checkout failed' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const opened = openInBrowser(checkoutUrl);
|
|
127
|
+
if (opened) ui.ok('Opened Stripe checkout in your browser.');
|
|
128
|
+
else ui.warn('Could not open your browser automatically.');
|
|
129
|
+
ui.info('Complete the ' + ui.c.bold(PRICE_LABEL) + ' subscription here:');
|
|
130
|
+
ui.info(' ' + ui.c.cyan(checkoutUrl));
|
|
131
|
+
ui.dim('No charge happens until you enter a card and confirm on Stripe\'s page.');
|
|
132
|
+
|
|
133
|
+
return { product: 'Bundle', icon: 'ok', note: `checkout opened (${PRICE_LABEL})` };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { step };
|
package/lib/ui.js
CHANGED
|
@@ -36,7 +36,9 @@ function banner() {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function heading(n, total, title) {
|
|
39
|
-
|
|
39
|
+
// total falsy → a plain section heading with no [n/total] counter (e.g. the bundle step).
|
|
40
|
+
const prefix = total ? `[${n}/${total}] ` : '';
|
|
41
|
+
stdout.write('\n' + c.bold(c.blue(`${prefix}${title}`)) + '\n');
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
const info = (m) => stdout.write(' ' + m + '\n');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentbundle-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One command to give your AI agent a card, an inbox, and a phone — installs
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
7
7
|
"agentbundle": "bin/agent-bundle.js",
|
|
@@ -25,13 +25,5 @@
|
|
|
25
25
|
"onboarding",
|
|
26
26
|
"cli"
|
|
27
27
|
],
|
|
28
|
-
"publishConfig": {
|
|
29
|
-
"access": "public"
|
|
30
|
-
},
|
|
31
|
-
"repository": {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "git+https://github.com/tiny-agent-company/tiny-agent-company.git",
|
|
34
|
-
"directory": "apps/agent-bundle-cli"
|
|
35
|
-
},
|
|
36
28
|
"license": "MIT"
|
|
37
29
|
}
|