sella-cli 0.6.1 → 0.6.4
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/dist/index.js +42 -18
- package/dist/ui.js +99 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { capabilityLabel } from './chains.js';
|
|
|
12
12
|
import { defaultIo, Printer } from './output.js';
|
|
13
13
|
import { Ui } from './ui.js';
|
|
14
14
|
const DEFAULT_MCP_URL = 'https://sellag.vercel.app/api/mcp';
|
|
15
|
-
const VERSION = '0.6.
|
|
15
|
+
const VERSION = '0.6.4';
|
|
16
16
|
function parseFlags(argv) {
|
|
17
17
|
const flags = {
|
|
18
18
|
json: false, yes: false, noColor: false, dryRun: false, noKeychain: false,
|
|
@@ -162,6 +162,9 @@ async function runPair(ctx, flags, emit, extra = {}) {
|
|
|
162
162
|
keyStorage: outcome.save.keyStorage,
|
|
163
163
|
files: outcome.save.files,
|
|
164
164
|
agentWallet: outcome.payload.agentWallet?.status || 'none',
|
|
165
|
+
...(outcome.payload.agentWallet?.status === 'unavailable' && outcome.payload.agentWallet?.reason
|
|
166
|
+
? { agentWalletReason: outcome.payload.agentWallet.reason }
|
|
167
|
+
: {}),
|
|
165
168
|
},
|
|
166
169
|
};
|
|
167
170
|
}
|
|
@@ -207,6 +210,7 @@ async function cmdInit(ctx, flags, printer) {
|
|
|
207
210
|
const ui = new Ui(ctx.io, { json: flags.json, noColor: flags.noColor, yes: flags.yes });
|
|
208
211
|
const emit = ui.pretty ? uiEmitter(ui, printer) : printer;
|
|
209
212
|
const origin = ctx.mcpUrl.replace(/\/api\/mcp\/?$/, '') || 'https://sellag.vercel.app';
|
|
213
|
+
const startedAt = Date.now();
|
|
210
214
|
let host = ctx.mcpUrl;
|
|
211
215
|
try {
|
|
212
216
|
host = new URL(ctx.mcpUrl).host;
|
|
@@ -214,24 +218,27 @@ async function cmdInit(ctx, flags, printer) {
|
|
|
214
218
|
catch {
|
|
215
219
|
/* keep full URL */
|
|
216
220
|
}
|
|
217
|
-
ui.
|
|
221
|
+
ui.banner('Connect your agents to the marketplace', `v${VERSION} · ${host}`);
|
|
218
222
|
// A taste of the marketplace (free sandbox, best-effort — never blocks onboarding).
|
|
219
223
|
const taste = ui.spinner('Peeking at the marketplace (free, no signup)…');
|
|
220
224
|
try {
|
|
221
225
|
const result = await sandboxSearch(ctx.mcpUrl, 'data');
|
|
222
226
|
const picks = [
|
|
223
|
-
...result.datasets.slice(0, 2).map((d) =>
|
|
224
|
-
|
|
227
|
+
...result.datasets.slice(0, 2).map((d) => ({
|
|
228
|
+
tag: 'dataset',
|
|
229
|
+
text: `${d.title || d.id}${d.priceUSDC ? ` ${String(d.priceUSDC)} USDC` : ''}`,
|
|
230
|
+
tone: 'brand',
|
|
231
|
+
})),
|
|
232
|
+
...result.apis.slice(0, 2).map((a) => ({ tag: 'api', text: a.name || '', tone: 'ok' })),
|
|
225
233
|
];
|
|
226
234
|
taste.succeed(picks.length ? 'A taste of what your agent can buy here:' : 'Marketplace preview is empty right now.');
|
|
227
|
-
|
|
228
|
-
ui.detail(p);
|
|
235
|
+
ui.rows(picks);
|
|
229
236
|
}
|
|
230
237
|
catch {
|
|
231
238
|
taste.succeed('Marketplace preview skipped.');
|
|
232
239
|
}
|
|
233
240
|
ui.bar();
|
|
234
|
-
//
|
|
241
|
+
// Phase 1 — install
|
|
235
242
|
const detected = detectClients(ctx.env);
|
|
236
243
|
const targets = selectClientIds(flags, detected);
|
|
237
244
|
if (targets.length === 0) {
|
|
@@ -240,30 +247,31 @@ async function cmdInit(ctx, flags, printer) {
|
|
|
240
247
|
ui.outro('Nothing to do.');
|
|
241
248
|
return 2;
|
|
242
249
|
}
|
|
243
|
-
ui.
|
|
250
|
+
ui.phase(1, 3, 'INSTALL', `Sella MCP server → ${targets.join(', ')}`);
|
|
244
251
|
const install = installAll(ctx, flags, emit);
|
|
245
252
|
ui.bar();
|
|
246
|
-
//
|
|
247
|
-
ui.
|
|
253
|
+
// Phase 2 — pair
|
|
254
|
+
ui.phase(2, 3, 'PAIR', 'Link this machine to your Sella account');
|
|
248
255
|
const choice = await collectPairChoice(ui, flags, ctx);
|
|
249
256
|
if (choice.email)
|
|
250
257
|
ui.step(`Requesting a verification code for ${choice.email}…`);
|
|
251
258
|
const paired = await runPair(ctx, flags, emit, { ...choice, ask: wizardAsk(ui, ctx) });
|
|
252
259
|
let reinstalled = [];
|
|
253
260
|
if (paired.code === 0 && paired.apiKey && !flags.dryRun) {
|
|
254
|
-
ui.bar();
|
|
255
261
|
ui.step('Wiring your API key into the client configs');
|
|
256
262
|
reinstalled = installAll(ctx, flags, emit, paired.apiKey).results;
|
|
257
263
|
}
|
|
258
|
-
//
|
|
264
|
+
// Phase 3 — verify (report-only: exit codes stay install/pair-driven so automation is stable)
|
|
259
265
|
let verifySummary;
|
|
260
266
|
if (paired.code === 0 && !flags.dryRun) {
|
|
261
267
|
ui.bar();
|
|
262
|
-
|
|
268
|
+
ui.phase(3, 3, 'VERIFY', 'Prove the install works (sella doctor)');
|
|
269
|
+
const sp = ui.spinner('Checking endpoint, credentials, auth, wallets, pay-quote…');
|
|
263
270
|
try {
|
|
264
271
|
const verify = await runDoctor({ mcpUrl: ctx.mcpUrl, env: ctx.env });
|
|
265
272
|
const passed = verify.checks.filter((c) => c.ok).length;
|
|
266
|
-
sp.succeed
|
|
273
|
+
(verify.ok ? sp.succeed : sp.succeed)(`${passed}/${verify.checks.length} checks passed`);
|
|
274
|
+
ui.checkStrip(verify.checks.map((c) => ({ id: c.id, ok: c.ok })));
|
|
267
275
|
for (const check of verify.checks.filter((c) => !c.ok)) {
|
|
268
276
|
ui.warn(`${check.label} — ${check.detail}`);
|
|
269
277
|
if (check.fix)
|
|
@@ -272,17 +280,33 @@ async function cmdInit(ctx, flags, printer) {
|
|
|
272
280
|
verifySummary = { ok: verify.ok, passed, total: verify.checks.length };
|
|
273
281
|
}
|
|
274
282
|
catch (err) {
|
|
275
|
-
sp.fail('
|
|
283
|
+
sp.fail('Verify could not run (network?) — try `sella doctor` later.');
|
|
276
284
|
verifySummary = { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
277
285
|
}
|
|
278
286
|
}
|
|
279
287
|
if (paired.code === 0) {
|
|
280
|
-
|
|
288
|
+
const awStatus = String(paired.summary?.agentWallet || 'none');
|
|
289
|
+
const lines = [
|
|
281
290
|
`Fund your agent wallet: ${origin}/dashboard/funding`,
|
|
282
291
|
'Try it free: sella sandbox "web search"',
|
|
283
292
|
'Health check: sella doctor',
|
|
293
|
+
];
|
|
294
|
+
if (awStatus === 'unavailable') {
|
|
295
|
+
lines.push('AgentWallet pending (backend not configured) — re-run `sella pair` later.');
|
|
296
|
+
}
|
|
297
|
+
ui.note('Sella is connected', lines, flags.dryRun ? 'plain' : awStatus === 'unavailable' ? 'warn' : 'ok');
|
|
298
|
+
// RFI: ready-to-send prompts for real agent tasks — the "now what?" answer.
|
|
299
|
+
const rfi = `${origin}/rfi`;
|
|
300
|
+
ui.bar();
|
|
301
|
+
ui.step('Ideas to try with your agent (each is a ready-to-send prompt):');
|
|
302
|
+
ui.rows([
|
|
303
|
+
{ tag: 'invest', text: `Validate a startup idea for $25 — ${rfi}?uc=twenty-five-dollar-study`, tone: 'brand' },
|
|
304
|
+
{ tag: 'daily', text: `Get a daily morning brief — ${rfi}?uc=morning-brief`, tone: 'ok' },
|
|
305
|
+
{ tag: 'free', text: `Test Sella without spending — ${rfi}?uc=zero-dollar-demo`, tone: 'dim' },
|
|
284
306
|
]);
|
|
285
|
-
ui.
|
|
307
|
+
ui.detail(`Browse all ideas: ${rfi}`);
|
|
308
|
+
const took = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
|
|
309
|
+
ui.outro(flags.dryRun ? 'Dry run complete — nothing was written.' : `Done in ${took}s — your agents can now buy on Sella.`);
|
|
286
310
|
}
|
|
287
311
|
else {
|
|
288
312
|
ui.outro('Pairing incomplete — fix the issue above, then re-run `sella init`.');
|
|
@@ -348,7 +372,7 @@ export async function runCli(argv, io = defaultIo(), env = defaultEnv()) {
|
|
|
348
372
|
catch {
|
|
349
373
|
/* keep full URL */
|
|
350
374
|
}
|
|
351
|
-
ui.
|
|
375
|
+
ui.banner('Pair this machine', `v${VERSION} · ${host}`);
|
|
352
376
|
const choice = await collectPairChoice(ui, flags, ctx);
|
|
353
377
|
if (choice.email)
|
|
354
378
|
ui.step(`Requesting a verification code for ${choice.email}…`);
|
package/dist/ui.js
CHANGED
|
@@ -54,6 +54,92 @@ export class Ui {
|
|
|
54
54
|
this.out(`${this.dim('┌')} ${this.paint('1', title)}${meta ? ` ${this.dim(meta)}` : ''}`);
|
|
55
55
|
this.bar();
|
|
56
56
|
}
|
|
57
|
+
/** Slant-style SELLA wordmark (slashes + underscores, matrix-green fade) — wide TTYs only. */
|
|
58
|
+
wordmarkArt() {
|
|
59
|
+
if (!this.pretty || (process.stdout.columns || 80) < 48)
|
|
60
|
+
return null;
|
|
61
|
+
const glyphs = {
|
|
62
|
+
S: [' _____', ' / ___/', ' \\__ \\ ', ' ___/ / ', '/____/ '],
|
|
63
|
+
E: [' ______', ' / ____/', ' / __/ ', ' / /___ ', '/_____/ '],
|
|
64
|
+
L: [' __ ', ' / / ', ' / / ', ' / /___ ', '/_____/ '],
|
|
65
|
+
A: [' ___ ', ' / |', ' / /| |', ' / ___ |', '/_/ |_|'],
|
|
66
|
+
};
|
|
67
|
+
const rows = [];
|
|
68
|
+
for (let r = 0; r < 5; r += 1) {
|
|
69
|
+
const raw = ['S', 'E', 'L', 'L', 'A'].map((ch) => glyphs[ch][r]).join('');
|
|
70
|
+
// Matrix fade: bright green on top, deep green at the base — decoration only.
|
|
71
|
+
const code = r < 3 ? '92' : '32';
|
|
72
|
+
rows.push(' ' + this.paint(code, raw));
|
|
73
|
+
}
|
|
74
|
+
return rows;
|
|
75
|
+
}
|
|
76
|
+
/** Branded header: line-art wordmark + info panel — the first thing the wizard paints. */
|
|
77
|
+
banner(subtitle, meta) {
|
|
78
|
+
if (this.quiet)
|
|
79
|
+
return;
|
|
80
|
+
if (!this.pretty) {
|
|
81
|
+
this.out(`SELLA — ${subtitle} (${meta})`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.out('');
|
|
85
|
+
const art = this.wordmarkArt();
|
|
86
|
+
if (art)
|
|
87
|
+
for (const row of art)
|
|
88
|
+
this.out(row);
|
|
89
|
+
const wordmark = art ? '' : 'S E L L A';
|
|
90
|
+
const inner = Math.min(this.width() - 2, Math.max(wordmark.length, subtitle.length, meta.length) + 6);
|
|
91
|
+
const pad = (raw, painted = raw) => `${this.accent('│')} ${painted}${' '.repeat(Math.max(0, inner - raw.length - 2))}${this.accent('│')}`;
|
|
92
|
+
this.out(this.accent('╭' + '─'.repeat(inner) + '╮'));
|
|
93
|
+
if (wordmark)
|
|
94
|
+
this.out(pad(wordmark, this.paint('1;36', wordmark)));
|
|
95
|
+
this.out(pad(subtitle, this.paint('1', subtitle)));
|
|
96
|
+
this.out(pad(meta, this.dim(meta)));
|
|
97
|
+
this.out(this.accent('╰' + '─'.repeat(inner) + '╯'));
|
|
98
|
+
this.out('');
|
|
99
|
+
}
|
|
100
|
+
/** Inverse-video chip, e.g. ` 1/3 INSTALL `. */
|
|
101
|
+
chipText(label, tone = 'brand') {
|
|
102
|
+
if (!this.color)
|
|
103
|
+
return `[${label}]`;
|
|
104
|
+
const bg = tone === 'ok' ? '42;30' : tone === 'warn' ? '43;30' : '46;30';
|
|
105
|
+
return `\x1b[${bg}m ${label} \x1b[0m`;
|
|
106
|
+
}
|
|
107
|
+
/** Phase header: chip + progress dots + bold title. */
|
|
108
|
+
phase(n, total, label, title) {
|
|
109
|
+
if (this.quiet)
|
|
110
|
+
return;
|
|
111
|
+
if (!this.pretty) {
|
|
112
|
+
this.out(`[${n}/${total} ${label}] ${title}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const dots = Array.from({ length: total }, (_, i) => i < n ? this.accent('●') : this.dim('○')).join(' ');
|
|
116
|
+
this.out(`${this.chipText(`${n}/${total} ${label}`)} ${this.paint('1', title)} ${dots}`);
|
|
117
|
+
}
|
|
118
|
+
/** Aligned two-column rows with a colored left tag, for lists like the marketplace taste. */
|
|
119
|
+
rows(items) {
|
|
120
|
+
if (this.quiet || items.length === 0)
|
|
121
|
+
return;
|
|
122
|
+
const w = Math.max(...items.map((i) => i.tag.length));
|
|
123
|
+
for (const item of items) {
|
|
124
|
+
const padded = item.tag.padEnd(w);
|
|
125
|
+
const tag = item.tone === 'ok' ? this.paint('32', padded) : item.tone === 'dim' ? this.dim(padded) : this.paint('35', padded);
|
|
126
|
+
if (!this.pretty)
|
|
127
|
+
this.out(` ${padded} ${item.text}`);
|
|
128
|
+
else
|
|
129
|
+
this.out(`${this.dim('│')} ${tag} ${item.text}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** One-line ✓/✕ strip summarizing named checks, e.g. the doctor verify. */
|
|
133
|
+
checkStrip(checks) {
|
|
134
|
+
if (this.quiet || checks.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
const parts = checks.map((c) => c.ok ? `${this.paint('32', '✓')} ${this.dim(c.id)}` : `${this.paint('31', '✕')} ${this.paint('31', c.id)}`);
|
|
137
|
+
if (!this.pretty) {
|
|
138
|
+
this.out(' ' + checks.map((c) => `${c.ok ? 'ok' : 'FAIL'}:${c.id}`).join(' '));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.out(`${this.dim('│')} ${parts.join(' ')}`);
|
|
142
|
+
}
|
|
57
143
|
outro(text) {
|
|
58
144
|
if (this.quiet)
|
|
59
145
|
return;
|
|
@@ -94,8 +180,8 @@ export class Ui {
|
|
|
94
180
|
return;
|
|
95
181
|
this.out(this.pretty ? `${this.dim('│')} ${text}` : ` ${text}`);
|
|
96
182
|
}
|
|
97
|
-
/** Boxed panel for the final summary / next steps. */
|
|
98
|
-
note(title, lines) {
|
|
183
|
+
/** Boxed panel for the final summary / next steps. Tone colors the border. */
|
|
184
|
+
note(title, lines, tone = 'plain') {
|
|
99
185
|
if (this.quiet)
|
|
100
186
|
return;
|
|
101
187
|
if (!this.pretty) {
|
|
@@ -105,6 +191,7 @@ export class Ui {
|
|
|
105
191
|
this.out(` ${line}`);
|
|
106
192
|
return;
|
|
107
193
|
}
|
|
194
|
+
const edge = (s) => tone === 'ok' ? this.paint('32', s) : tone === 'warn' ? this.paint('33', s) : this.dim(s);
|
|
108
195
|
const inner = this.width() - 6;
|
|
109
196
|
const wrapped = [];
|
|
110
197
|
for (const line of lines) {
|
|
@@ -124,13 +211,13 @@ export class Ui {
|
|
|
124
211
|
}
|
|
125
212
|
const w = Math.min(this.width() - 2, Math.max(title.length, ...wrapped.map((l) => l.length), 20) + 4);
|
|
126
213
|
this.bar();
|
|
127
|
-
this.out(`${
|
|
214
|
+
this.out(`${edge('╭' + '─'.repeat(w) + '╮')}`);
|
|
128
215
|
// Pad from the raw string length, then paint — ANSI codes must not count toward width.
|
|
129
|
-
const pad = (s, painted = s) => `${
|
|
216
|
+
const pad = (s, painted = s) => `${edge('│')} ${painted}${' '.repeat(Math.max(0, w - s.length - 2))}${edge('│')}`;
|
|
130
217
|
this.out(pad(title, this.paint('1', title)));
|
|
131
218
|
for (const line of wrapped)
|
|
132
219
|
this.out(pad(line));
|
|
133
|
-
this.out(`${
|
|
220
|
+
this.out(`${edge('╰' + '─'.repeat(w) + '╯')}`);
|
|
134
221
|
}
|
|
135
222
|
// ── spinner ────────────────────────────────────────────────────────────────
|
|
136
223
|
spinner(label) {
|
|
@@ -148,10 +235,13 @@ export class Ui {
|
|
|
148
235
|
ensureCursorRestoredOnExit();
|
|
149
236
|
let current = label;
|
|
150
237
|
let i = 0;
|
|
238
|
+
const startedAt = Date.now();
|
|
151
239
|
process.stdout.write('\x1b[?25l');
|
|
152
240
|
const render = () => {
|
|
153
241
|
const frame = this.accent(FRAMES[i % FRAMES.length]);
|
|
154
|
-
|
|
242
|
+
const elapsed = Date.now() - startedAt;
|
|
243
|
+
const suffix = elapsed > 2_000 ? ` ${this.dim(`(${Math.round(elapsed / 1000)}s)`)}` : '';
|
|
244
|
+
process.stdout.write(`\r\x1b[K${frame} ${current}${suffix}`);
|
|
155
245
|
i += 1;
|
|
156
246
|
};
|
|
157
247
|
render();
|
|
@@ -160,7 +250,9 @@ export class Ui {
|
|
|
160
250
|
clearInterval(timer);
|
|
161
251
|
process.stdout.write(`\r\x1b[K`);
|
|
162
252
|
process.stdout.write('\x1b[?25h');
|
|
163
|
-
|
|
253
|
+
const elapsed = Date.now() - startedAt;
|
|
254
|
+
const suffix = elapsed > 2_000 ? ` ${this.dim(`(${(elapsed / 1000).toFixed(1)}s)`)}` : '';
|
|
255
|
+
this.out(`${this.paint(glyphCode, glyph)} ${text || current}${suffix}`);
|
|
164
256
|
};
|
|
165
257
|
return {
|
|
166
258
|
update: (text) => {
|
package/package.json
CHANGED