sella-cli 0.6.0 → 0.6.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 +1 -1
- package/dist/fund.js +2 -2
- package/dist/index.js +36 -22
- package/dist/pairing.js +1 -1
- package/dist/postinstall.js +1 -1
- package/dist/ui.js +75 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# sella-cli
|
|
2
2
|
|
|
3
|
-
Terminal onboarding for [Sella](https://
|
|
3
|
+
Terminal onboarding for [Sella](https://sellag.vercel.app) — the marketplace where agents buy datasets,
|
|
4
4
|
models, and machine-payable APIs.
|
|
5
5
|
|
|
6
6
|
```bash
|
package/dist/fund.js
CHANGED
|
@@ -2,9 +2,9 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { fetchChainRegistry, chainCapability } from './chains.js';
|
|
4
4
|
function platformOrigin(mcpUrl) {
|
|
5
|
-
return mcpUrl.replace(/\/api\/mcp\/?$/, '') || 'https://
|
|
5
|
+
return mcpUrl.replace(/\/api\/mcp\/?$/, '') || 'https://sellag.vercel.app';
|
|
6
6
|
}
|
|
7
|
-
export function getFundingInfo(env, mcpUrl = 'https://
|
|
7
|
+
export function getFundingInfo(env, mcpUrl = 'https://sellag.vercel.app/api/mcp') {
|
|
8
8
|
const fundingUrl = `${platformOrigin(mcpUrl)}/dashboard/funding`;
|
|
9
9
|
const configPath = path.join(env.home, '.sella-agentwallet', 'config.json');
|
|
10
10
|
let config = null;
|
package/dist/index.js
CHANGED
|
@@ -11,8 +11,8 @@ import { getFundingInfo, annotateFunding } from './fund.js';
|
|
|
11
11
|
import { capabilityLabel } from './chains.js';
|
|
12
12
|
import { defaultIo, Printer } from './output.js';
|
|
13
13
|
import { Ui } from './ui.js';
|
|
14
|
-
const DEFAULT_MCP_URL = 'https://
|
|
15
|
-
const VERSION = '0.6.
|
|
14
|
+
const DEFAULT_MCP_URL = 'https://sellag.vercel.app/api/mcp';
|
|
15
|
+
const VERSION = '0.6.2';
|
|
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
|
}
|
|
@@ -206,7 +209,8 @@ function wizardAsk(ui, ctx) {
|
|
|
206
209
|
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
|
-
const origin = ctx.mcpUrl.replace(/\/api\/mcp\/?$/, '') || 'https://
|
|
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,23 @@ 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',
|
|
284
|
-
]
|
|
285
|
-
|
|
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
|
+
const took = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
|
|
299
|
+
ui.outro(flags.dryRun ? 'Dry run complete — nothing was written.' : `Done in ${took}s — your agents can now buy on Sella.`);
|
|
286
300
|
}
|
|
287
301
|
else {
|
|
288
302
|
ui.outro('Pairing incomplete — fix the issue above, then re-run `sella init`.');
|
|
@@ -348,7 +362,7 @@ export async function runCli(argv, io = defaultIo(), env = defaultEnv()) {
|
|
|
348
362
|
catch {
|
|
349
363
|
/* keep full URL */
|
|
350
364
|
}
|
|
351
|
-
ui.
|
|
365
|
+
ui.banner('Pair this machine', `v${VERSION} · ${host}`);
|
|
352
366
|
const choice = await collectPairChoice(ui, flags, ctx);
|
|
353
367
|
if (choice.email)
|
|
354
368
|
ui.step(`Requesting a verification code for ${choice.email}…`);
|
|
@@ -442,7 +456,7 @@ export async function runCli(argv, io = defaultIo(), env = defaultEnv()) {
|
|
|
442
456
|
}
|
|
443
457
|
case 'publish': {
|
|
444
458
|
const sub = flags.positional[1];
|
|
445
|
-
const origin = ctx.mcpUrl.replace(/\/api\/mcp\/?$/, '') || 'https://
|
|
459
|
+
const origin = ctx.mcpUrl.replace(/\/api\/mcp\/?$/, '') || 'https://sellag.vercel.app';
|
|
446
460
|
if (sub === 'init') {
|
|
447
461
|
const csv = flags.positional.slice(2).find((s) => !s.startsWith('--'));
|
|
448
462
|
if (!csv) {
|
package/dist/pairing.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { claimSetupCode, completeEmailOtp, startEmailOtp } from './api.js';
|
|
2
2
|
import { saveCredentials } from './credentials.js';
|
|
3
3
|
const NO_TTY_HELP = 'Non-interactive run without credentials. Pass --setup-code <SELLA-…>, set SELLA_SETUP_CODE, ' +
|
|
4
|
-
'or run interactively. Mint a code at https://
|
|
4
|
+
'or run interactively. Mint a code at https://sellag.vercel.app/onboarding (Connect your agent).';
|
|
5
5
|
export async function pair(opts) {
|
|
6
6
|
const finish = (payload, method) => {
|
|
7
7
|
if (!payload.ok || !payload.apiKey) {
|
package/dist/postinstall.js
CHANGED
|
@@ -24,7 +24,7 @@ function main() {
|
|
|
24
24
|
'',
|
|
25
25
|
` ${bold(cmd)}`,
|
|
26
26
|
'',
|
|
27
|
-
` ${dim('Docs: https://
|
|
27
|
+
` ${dim('Docs: https://sellag.vercel.app · Try without an account: `' + cmd + ' sandbox "web search"`')}`,
|
|
28
28
|
'',
|
|
29
29
|
].join('\n'));
|
|
30
30
|
}
|
package/dist/ui.js
CHANGED
|
@@ -54,6 +54,68 @@ export class Ui {
|
|
|
54
54
|
this.out(`${this.dim('┌')} ${this.paint('1', title)}${meta ? ` ${this.dim(meta)}` : ''}`);
|
|
55
55
|
this.bar();
|
|
56
56
|
}
|
|
57
|
+
/** Branded header panel — the first thing the wizard paints. */
|
|
58
|
+
banner(subtitle, meta) {
|
|
59
|
+
if (this.quiet)
|
|
60
|
+
return;
|
|
61
|
+
if (!this.pretty) {
|
|
62
|
+
this.out(`SELLA — ${subtitle} (${meta})`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const wordmark = 'S E L L A';
|
|
66
|
+
const inner = Math.min(this.width() - 2, Math.max(wordmark.length, subtitle.length, meta.length) + 6);
|
|
67
|
+
const pad = (raw, painted = raw) => `${this.accent('│')} ${painted}${' '.repeat(Math.max(0, inner - raw.length - 2))}${this.accent('│')}`;
|
|
68
|
+
this.out('');
|
|
69
|
+
this.out(this.accent('╭' + '─'.repeat(inner) + '╮'));
|
|
70
|
+
this.out(pad(wordmark, this.paint('1;36', wordmark)));
|
|
71
|
+
this.out(pad(subtitle, this.paint('1', subtitle)));
|
|
72
|
+
this.out(pad(meta, this.dim(meta)));
|
|
73
|
+
this.out(this.accent('╰' + '─'.repeat(inner) + '╯'));
|
|
74
|
+
this.out('');
|
|
75
|
+
}
|
|
76
|
+
/** Inverse-video chip, e.g. ` 1/3 INSTALL `. */
|
|
77
|
+
chipText(label, tone = 'brand') {
|
|
78
|
+
if (!this.color)
|
|
79
|
+
return `[${label}]`;
|
|
80
|
+
const bg = tone === 'ok' ? '42;30' : tone === 'warn' ? '43;30' : '46;30';
|
|
81
|
+
return `\x1b[${bg}m ${label} \x1b[0m`;
|
|
82
|
+
}
|
|
83
|
+
/** Phase header: chip + progress dots + bold title. */
|
|
84
|
+
phase(n, total, label, title) {
|
|
85
|
+
if (this.quiet)
|
|
86
|
+
return;
|
|
87
|
+
if (!this.pretty) {
|
|
88
|
+
this.out(`[${n}/${total} ${label}] ${title}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const dots = Array.from({ length: total }, (_, i) => i < n ? this.accent('●') : this.dim('○')).join(' ');
|
|
92
|
+
this.out(`${this.chipText(`${n}/${total} ${label}`)} ${this.paint('1', title)} ${dots}`);
|
|
93
|
+
}
|
|
94
|
+
/** Aligned two-column rows with a colored left tag, for lists like the marketplace taste. */
|
|
95
|
+
rows(items) {
|
|
96
|
+
if (this.quiet || items.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
const w = Math.max(...items.map((i) => i.tag.length));
|
|
99
|
+
for (const item of items) {
|
|
100
|
+
const padded = item.tag.padEnd(w);
|
|
101
|
+
const tag = item.tone === 'ok' ? this.paint('32', padded) : item.tone === 'dim' ? this.dim(padded) : this.paint('35', padded);
|
|
102
|
+
if (!this.pretty)
|
|
103
|
+
this.out(` ${padded} ${item.text}`);
|
|
104
|
+
else
|
|
105
|
+
this.out(`${this.dim('│')} ${tag} ${item.text}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** One-line ✓/✕ strip summarizing named checks, e.g. the doctor verify. */
|
|
109
|
+
checkStrip(checks) {
|
|
110
|
+
if (this.quiet || checks.length === 0)
|
|
111
|
+
return;
|
|
112
|
+
const parts = checks.map((c) => c.ok ? `${this.paint('32', '✓')} ${this.dim(c.id)}` : `${this.paint('31', '✕')} ${this.paint('31', c.id)}`);
|
|
113
|
+
if (!this.pretty) {
|
|
114
|
+
this.out(' ' + checks.map((c) => `${c.ok ? 'ok' : 'FAIL'}:${c.id}`).join(' '));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.out(`${this.dim('│')} ${parts.join(' ')}`);
|
|
118
|
+
}
|
|
57
119
|
outro(text) {
|
|
58
120
|
if (this.quiet)
|
|
59
121
|
return;
|
|
@@ -94,8 +156,8 @@ export class Ui {
|
|
|
94
156
|
return;
|
|
95
157
|
this.out(this.pretty ? `${this.dim('│')} ${text}` : ` ${text}`);
|
|
96
158
|
}
|
|
97
|
-
/** Boxed panel for the final summary / next steps. */
|
|
98
|
-
note(title, lines) {
|
|
159
|
+
/** Boxed panel for the final summary / next steps. Tone colors the border. */
|
|
160
|
+
note(title, lines, tone = 'plain') {
|
|
99
161
|
if (this.quiet)
|
|
100
162
|
return;
|
|
101
163
|
if (!this.pretty) {
|
|
@@ -105,6 +167,7 @@ export class Ui {
|
|
|
105
167
|
this.out(` ${line}`);
|
|
106
168
|
return;
|
|
107
169
|
}
|
|
170
|
+
const edge = (s) => tone === 'ok' ? this.paint('32', s) : tone === 'warn' ? this.paint('33', s) : this.dim(s);
|
|
108
171
|
const inner = this.width() - 6;
|
|
109
172
|
const wrapped = [];
|
|
110
173
|
for (const line of lines) {
|
|
@@ -124,13 +187,13 @@ export class Ui {
|
|
|
124
187
|
}
|
|
125
188
|
const w = Math.min(this.width() - 2, Math.max(title.length, ...wrapped.map((l) => l.length), 20) + 4);
|
|
126
189
|
this.bar();
|
|
127
|
-
this.out(`${
|
|
190
|
+
this.out(`${edge('╭' + '─'.repeat(w) + '╮')}`);
|
|
128
191
|
// Pad from the raw string length, then paint — ANSI codes must not count toward width.
|
|
129
|
-
const pad = (s, painted = s) => `${
|
|
192
|
+
const pad = (s, painted = s) => `${edge('│')} ${painted}${' '.repeat(Math.max(0, w - s.length - 2))}${edge('│')}`;
|
|
130
193
|
this.out(pad(title, this.paint('1', title)));
|
|
131
194
|
for (const line of wrapped)
|
|
132
195
|
this.out(pad(line));
|
|
133
|
-
this.out(`${
|
|
196
|
+
this.out(`${edge('╰' + '─'.repeat(w) + '╯')}`);
|
|
134
197
|
}
|
|
135
198
|
// ── spinner ────────────────────────────────────────────────────────────────
|
|
136
199
|
spinner(label) {
|
|
@@ -148,10 +211,13 @@ export class Ui {
|
|
|
148
211
|
ensureCursorRestoredOnExit();
|
|
149
212
|
let current = label;
|
|
150
213
|
let i = 0;
|
|
214
|
+
const startedAt = Date.now();
|
|
151
215
|
process.stdout.write('\x1b[?25l');
|
|
152
216
|
const render = () => {
|
|
153
217
|
const frame = this.accent(FRAMES[i % FRAMES.length]);
|
|
154
|
-
|
|
218
|
+
const elapsed = Date.now() - startedAt;
|
|
219
|
+
const suffix = elapsed > 2_000 ? ` ${this.dim(`(${Math.round(elapsed / 1000)}s)`)}` : '';
|
|
220
|
+
process.stdout.write(`\r\x1b[K${frame} ${current}${suffix}`);
|
|
155
221
|
i += 1;
|
|
156
222
|
};
|
|
157
223
|
render();
|
|
@@ -160,7 +226,9 @@ export class Ui {
|
|
|
160
226
|
clearInterval(timer);
|
|
161
227
|
process.stdout.write(`\r\x1b[K`);
|
|
162
228
|
process.stdout.write('\x1b[?25h');
|
|
163
|
-
|
|
229
|
+
const elapsed = Date.now() - startedAt;
|
|
230
|
+
const suffix = elapsed > 2_000 ? ` ${this.dim(`(${(elapsed / 1000).toFixed(1)}s)`)}` : '';
|
|
231
|
+
this.out(`${this.paint(glyphCode, glyph)} ${text || current}${suffix}`);
|
|
164
232
|
};
|
|
165
233
|
return {
|
|
166
234
|
update: (text) => {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sella-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Sella onboarding CLI — install Sella into your agent clients, pair, verify, fund, publish. (`npx sella-cli init`)",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"homepage": "https://
|
|
6
|
+
"homepage": "https://sellag.vercel.app",
|
|
7
7
|
"repository": { "type": "git", "url": "https://github.com/010100100100011101010100/ogsella", "directory": "cli" },
|
|
8
8
|
"type": "module",
|
|
9
9
|
"bin": { "sella": "./dist/index.js" },
|