@useswarm/cli 0.1.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.
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const listSwarmsCommand: Command;
@@ -0,0 +1,37 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { ApiClient } from "../lib/api-client.js";
5
+ import { requireAuth } from "../lib/config.js";
6
+ export const listSwarmsCommand = new Command("list-swarms")
7
+ .description("List your saved persona swarms")
8
+ .action(async () => {
9
+ const config = requireAuth();
10
+ const client = new ApiClient(config.apiUrl, config.apiKey);
11
+ const spinner = ora("Fetching swarms...").start();
12
+ let res;
13
+ try {
14
+ res = await client.listSwarms();
15
+ }
16
+ catch (err) {
17
+ spinner.fail("Failed to fetch swarms");
18
+ console.error(chalk.red(err.message));
19
+ process.exit(1);
20
+ }
21
+ spinner.stop();
22
+ if (res.swarms.length === 0) {
23
+ console.log(chalk.yellow("No swarms found. Create one on the dashboard first."));
24
+ return;
25
+ }
26
+ console.log(chalk.bold(`\n ${res.swarms.length} swarm(s) found:\n`));
27
+ for (const swarm of res.swarms) {
28
+ const personaCount = Array.isArray(swarm.personas) ? swarm.personas.length : 0;
29
+ console.log(` ${chalk.cyan(swarm.id)} ${chalk.bold(swarm.name)} (${personaCount} personas)`);
30
+ if (swarm.description) {
31
+ console.log(` ${chalk.dim(swarm.description)}`);
32
+ }
33
+ }
34
+ console.log();
35
+ console.log(chalk.dim(" Use a swarm ID with: swarm test --swarm <id>"));
36
+ console.log();
37
+ });
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const loginCommand: Command;
3
+ export declare const logoutCommand: Command;
@@ -0,0 +1,91 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import open from "open";
5
+ import { ApiClient } from "../lib/api-client.js";
6
+ import { saveConfig, getApiUrl, getConfig, clearConfig } from "../lib/config.js";
7
+ export const loginCommand = new Command("login")
8
+ .description("Authenticate with Swarm via browser")
9
+ .option("--api-url <url>", "Override API URL")
10
+ .action(async (opts) => {
11
+ const apiUrl = opts.apiUrl ?? getApiUrl();
12
+ const client = new ApiClient(apiUrl);
13
+ const existing = getConfig();
14
+ if (existing) {
15
+ console.log(chalk.yellow(`Already logged in as ${existing.user.name} (${existing.organization.name}).`));
16
+ console.log(chalk.dim("Run `swarm logout` to switch accounts.\n"));
17
+ }
18
+ const spinner = ora("Starting authentication...").start();
19
+ let initRes;
20
+ try {
21
+ initRes = await client.initiateAuth();
22
+ }
23
+ catch (err) {
24
+ spinner.fail("Failed to start authentication");
25
+ console.error(chalk.red(err.message));
26
+ process.exit(1);
27
+ }
28
+ spinner.stop();
29
+ console.log();
30
+ console.log(chalk.bold(" Enter this code in your browser:"));
31
+ console.log();
32
+ console.log(chalk.cyan.bold(` ${initRes.userCode}`));
33
+ console.log();
34
+ console.log(chalk.dim(` Opening ${initRes.verificationUrl} ...`));
35
+ console.log();
36
+ await open(initRes.verificationUrl).catch(() => {
37
+ console.log(chalk.yellow(` Could not open browser. Visit: ${initRes.verificationUrl}`));
38
+ });
39
+ const pollSpinner = ora("Waiting for browser authorization...").start();
40
+ const deadline = Date.now() + initRes.expiresIn * 1000;
41
+ while (Date.now() < deadline) {
42
+ await sleep(2500);
43
+ try {
44
+ const poll = await client.pollAuth(initRes.deviceCode);
45
+ if (poll.status === "complete" && poll.apiKey && poll.user && poll.organization) {
46
+ pollSpinner.stop();
47
+ const authedClient = new ApiClient(apiUrl, poll.apiKey);
48
+ let ngrokToken;
49
+ try {
50
+ const tokenRes = await authedClient.getTunnelToken();
51
+ ngrokToken = tokenRes.ngrokAuthToken;
52
+ }
53
+ catch {
54
+ // Non-fatal
55
+ }
56
+ saveConfig({
57
+ apiKey: poll.apiKey,
58
+ apiUrl,
59
+ ngrokToken,
60
+ user: poll.user,
61
+ organization: poll.organization,
62
+ });
63
+ console.log(chalk.green("✓ Authenticated successfully!"));
64
+ console.log();
65
+ console.log(` ${chalk.bold("User:")} ${poll.user.name} (${poll.user.email})`);
66
+ console.log(` ${chalk.bold("Org:")} ${poll.organization.name} (${poll.organization.planType} plan)`);
67
+ console.log();
68
+ console.log(chalk.dim(" Run `swarm test` to start a UX simulation."));
69
+ return;
70
+ }
71
+ if (poll.status === "expired") {
72
+ pollSpinner.fail("Authentication expired. Run `swarm login` again.");
73
+ process.exit(1);
74
+ }
75
+ }
76
+ catch {
77
+ // Network error — keep polling
78
+ }
79
+ }
80
+ pollSpinner.fail("Authentication timed out. Run `swarm login` again.");
81
+ process.exit(1);
82
+ });
83
+ export const logoutCommand = new Command("logout")
84
+ .description("Remove stored credentials")
85
+ .action(() => {
86
+ clearConfig();
87
+ console.log(chalk.green("✓ Logged out successfully."));
88
+ });
89
+ function sleep(ms) {
90
+ return new Promise((resolve) => setTimeout(resolve, ms));
91
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const testCommand: Command;
@@ -0,0 +1,167 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { ApiClient } from "../lib/api-client.js";
5
+ import { requireAuth } from "../lib/config.js";
6
+ import { openTunnel } from "../lib/tunnel.js";
7
+ import { renderStep, renderRunComplete, renderSynthesis, renderJudge, renderDone } from "../lib/renderer.js";
8
+ import { EventSourceParserStream } from "eventsource-parser/stream";
9
+ export const testCommand = new Command("test")
10
+ .description("Run a UX simulation against a URL")
11
+ .requiredOption("--url <url>", "Target URL (use localhost:PORT for local dev)")
12
+ .requiredOption("--goal <goal>", "What the user should try to do")
13
+ .option("--description <desc>", "Describe your target audience", "general web users")
14
+ .option("--swarm <id>", "Use an existing swarm's personas")
15
+ .option("--agents <count>", "Number of AI agents to generate", "3")
16
+ .option("--audience <desc>", "Audience description for persona generation")
17
+ .option("--no-tunnel", "Skip ngrok tunnel (URL must be publicly accessible)")
18
+ .action(async (opts) => {
19
+ const config = requireAuth();
20
+ const client = new ApiClient(config.apiUrl, config.apiKey);
21
+ let targetUrl = opts.url;
22
+ let tunnel;
23
+ const isLocalhost = targetUrl.includes("localhost") ||
24
+ targetUrl.includes("127.0.0.1") ||
25
+ targetUrl.match(/:\d+/) !== null;
26
+ if (isLocalhost && opts.tunnel !== false) {
27
+ const portMatch = targetUrl.match(/:(\d+)/);
28
+ const port = portMatch ? parseInt(portMatch[1], 10) : 3000;
29
+ if (!config.ngrokToken) {
30
+ console.error(chalk.red("No tunnel token available. Run `swarm login` again or use --no-tunnel."));
31
+ process.exit(1);
32
+ }
33
+ const tunnelSpinner = ora(`Opening tunnel to localhost:${port}...`).start();
34
+ try {
35
+ tunnel = await openTunnel(port, config.ngrokToken);
36
+ targetUrl = tunnel.url;
37
+ tunnelSpinner.succeed(`Tunnel open: ${chalk.underline(tunnel.url)}`);
38
+ }
39
+ catch (err) {
40
+ tunnelSpinner.fail("Failed to open tunnel");
41
+ console.error(chalk.red(err.message));
42
+ process.exit(1);
43
+ }
44
+ }
45
+ const body = {
46
+ targetUrl,
47
+ goal: opts.goal,
48
+ userDescription: opts.description,
49
+ };
50
+ if (opts.swarm) {
51
+ body.swarmId = opts.swarm;
52
+ }
53
+ else {
54
+ body.generatePersonas = {
55
+ agentCount: parseInt(opts.agents, 10),
56
+ audienceDescription: opts.audience ?? opts.description,
57
+ };
58
+ }
59
+ const createSpinner = ora("Creating test run...").start();
60
+ let createRes;
61
+ try {
62
+ createRes = await client.createTest(body);
63
+ }
64
+ catch (err) {
65
+ createSpinner.fail("Failed to create test");
66
+ console.error(chalk.red(err.message));
67
+ await tunnel?.close();
68
+ process.exit(1);
69
+ }
70
+ createSpinner.succeed(`Test started with ${createRes.totalRuns} agent(s) — ${chalk.dim(createRes.batchId)}`);
71
+ console.log();
72
+ for (const p of createRes.personas) {
73
+ console.log(` ${chalk.cyan("•")} ${p.name}`);
74
+ }
75
+ console.log();
76
+ try {
77
+ await streamResults(client, createRes.batchId, config.apiKey, createRes.dashboardUrl);
78
+ }
79
+ catch (err) {
80
+ console.error(chalk.red(`\nStream error: ${err.message}`));
81
+ console.log(chalk.dim(`\nPoll results: swarm status ${createRes.batchId}`));
82
+ }
83
+ if (tunnel) {
84
+ await tunnel.close().catch(() => { });
85
+ }
86
+ });
87
+ async function streamResults(client, batchId, apiKey, dashboardUrl) {
88
+ const url = client.streamUrl(batchId);
89
+ const response = await fetch(url, {
90
+ headers: {
91
+ "X-API-Key": apiKey,
92
+ Accept: "text/event-stream",
93
+ },
94
+ });
95
+ if (!response.ok || !response.body) {
96
+ console.log(chalk.dim("SSE not available, falling back to polling..."));
97
+ await pollResults(client, batchId, dashboardUrl);
98
+ return;
99
+ }
100
+ const stream = response.body
101
+ .pipeThrough(new TextDecoderStream())
102
+ .pipeThrough(new EventSourceParserStream());
103
+ for await (const event of stream) {
104
+ const eventType = event.event;
105
+ if (!eventType || eventType === "keepalive" || !event.data)
106
+ continue;
107
+ try {
108
+ const data = JSON.parse(event.data);
109
+ switch (data.type) {
110
+ case "step":
111
+ renderStep(data);
112
+ break;
113
+ case "run_complete":
114
+ renderRunComplete(data);
115
+ break;
116
+ case "synthesis":
117
+ renderSynthesis(data);
118
+ break;
119
+ case "judge":
120
+ renderJudge(data);
121
+ break;
122
+ case "done":
123
+ renderDone(dashboardUrl);
124
+ return;
125
+ }
126
+ }
127
+ catch {
128
+ // Skip malformed events
129
+ }
130
+ }
131
+ renderDone(dashboardUrl);
132
+ }
133
+ async function pollResults(client, batchId, dashboardUrl) {
134
+ const spinner = ora("Waiting for results...").start();
135
+ while (true) {
136
+ await new Promise((r) => setTimeout(r, 5000));
137
+ try {
138
+ const status = await client.getBatchStatus(batchId);
139
+ spinner.text = `Running... ${status.completedRuns}/${status.totalRuns} agents complete`;
140
+ if (status.status === "completed" || status.status === "failed") {
141
+ spinner.stop();
142
+ if (status.synthesis) {
143
+ renderSynthesis({
144
+ type: "synthesis",
145
+ executiveSummary: status.synthesis.executiveSummary,
146
+ tickets: status.synthesis.tickets,
147
+ positives: status.synthesis.positives,
148
+ });
149
+ }
150
+ if (status.judge) {
151
+ renderJudge({
152
+ type: "judge",
153
+ verdict: status.judge.verdict,
154
+ confidence: status.judge.confidence,
155
+ evidenceBlocks: status.judge.evidenceBlocks,
156
+ recommendedActions: status.judge.recommendedActions,
157
+ });
158
+ }
159
+ renderDone(dashboardUrl);
160
+ return;
161
+ }
162
+ }
163
+ catch {
164
+ // Keep polling on error
165
+ }
166
+ }
167
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { loginCommand, logoutCommand } from "./commands/login.js";
4
+ import { testCommand } from "./commands/test.js";
5
+ import { listSwarmsCommand } from "./commands/list-swarms.js";
6
+ const program = new Command();
7
+ program
8
+ .name("swarm")
9
+ .description("Swarm CLI — run AI-powered UX simulations from the terminal")
10
+ .version("0.0.1");
11
+ program.addCommand(loginCommand);
12
+ program.addCommand(logoutCommand);
13
+ program.addCommand(testCommand);
14
+ program.addCommand(listSwarmsCommand);
15
+ program.parse();
@@ -0,0 +1,15 @@
1
+ import type { InitiateResponse, PollResponse, TunnelTokenResponse, SwarmsResponse, CreateTestRequest, CreateTestResponse, BatchStatusResponse } from "../types.js";
2
+ export declare class ApiClient {
3
+ private baseUrl;
4
+ private apiKey?;
5
+ constructor(baseUrl: string, apiKey?: string | undefined);
6
+ private headers;
7
+ private request;
8
+ initiateAuth(): Promise<InitiateResponse>;
9
+ pollAuth(deviceCode: string): Promise<PollResponse>;
10
+ getTunnelToken(): Promise<TunnelTokenResponse>;
11
+ listSwarms(): Promise<SwarmsResponse>;
12
+ createTest(req: CreateTestRequest): Promise<CreateTestResponse>;
13
+ getBatchStatus(batchId: string): Promise<BatchStatusResponse>;
14
+ streamUrl(batchId: string): string;
15
+ }
@@ -0,0 +1,48 @@
1
+ export class ApiClient {
2
+ baseUrl;
3
+ apiKey;
4
+ constructor(baseUrl, apiKey) {
5
+ this.baseUrl = baseUrl;
6
+ this.apiKey = apiKey;
7
+ }
8
+ headers() {
9
+ const h = { "Content-Type": "application/json" };
10
+ if (this.apiKey)
11
+ h["X-API-Key"] = this.apiKey;
12
+ return h;
13
+ }
14
+ async request(method, path, body) {
15
+ const url = `${this.baseUrl}/api/cli${path}`;
16
+ const res = await fetch(url, {
17
+ method,
18
+ headers: this.headers(),
19
+ body: body ? JSON.stringify(body) : undefined,
20
+ });
21
+ if (!res.ok) {
22
+ const err = await res.json().catch(() => ({ error: res.statusText }));
23
+ throw new Error(err.error ?? `HTTP ${res.status}`);
24
+ }
25
+ return res.json();
26
+ }
27
+ async initiateAuth() {
28
+ return this.request("POST", "/auth/initiate");
29
+ }
30
+ async pollAuth(deviceCode) {
31
+ return this.request("GET", `/auth/poll?device_code=${encodeURIComponent(deviceCode)}`);
32
+ }
33
+ async getTunnelToken() {
34
+ return this.request("GET", "/tunnel-token");
35
+ }
36
+ async listSwarms() {
37
+ return this.request("GET", "/swarms");
38
+ }
39
+ async createTest(req) {
40
+ return this.request("POST", "/tests", req);
41
+ }
42
+ async getBatchStatus(batchId) {
43
+ return this.request("GET", `/tests/${batchId}`);
44
+ }
45
+ streamUrl(batchId) {
46
+ return `${this.baseUrl}/api/cli/tests/${batchId}/stream`;
47
+ }
48
+ }
@@ -0,0 +1,6 @@
1
+ import type { SwarmConfig } from "../types.js";
2
+ export declare function getConfig(): SwarmConfig | null;
3
+ export declare function saveConfig(config: SwarmConfig): void;
4
+ export declare function clearConfig(): void;
5
+ export declare function getApiUrl(): string;
6
+ export declare function requireAuth(): SwarmConfig;
@@ -0,0 +1,49 @@
1
+ import Conf from "conf";
2
+ const DEFAULT_API_URL = "https://staging-api.useswarm.co";
3
+ const store = new Conf({
4
+ projectName: "swarm",
5
+ schema: {
6
+ auth: {
7
+ type: "object",
8
+ properties: {
9
+ apiKey: { type: "string" },
10
+ apiUrl: { type: "string" },
11
+ ngrokToken: { type: "string" },
12
+ user: {
13
+ type: "object",
14
+ properties: {
15
+ name: { type: "string" },
16
+ email: { type: "string" },
17
+ },
18
+ },
19
+ organization: {
20
+ type: "object",
21
+ properties: {
22
+ name: { type: "string" },
23
+ planType: { type: "string" },
24
+ },
25
+ },
26
+ },
27
+ },
28
+ },
29
+ });
30
+ export function getConfig() {
31
+ return store.get("auth") ?? null;
32
+ }
33
+ export function saveConfig(config) {
34
+ store.set("auth", config);
35
+ }
36
+ export function clearConfig() {
37
+ store.delete("auth");
38
+ }
39
+ export function getApiUrl() {
40
+ return getConfig()?.apiUrl ?? process.env.SWARM_API_URL ?? DEFAULT_API_URL;
41
+ }
42
+ export function requireAuth() {
43
+ const config = getConfig();
44
+ if (!config) {
45
+ console.error("Not logged in. Run `swarm login` first.");
46
+ process.exit(1);
47
+ }
48
+ return config;
49
+ }
@@ -0,0 +1,6 @@
1
+ import type { StepEvent, RunCompleteEvent, SynthesisEvent, JudgeEvent } from "../types.js";
2
+ export declare function renderStep(event: StepEvent): void;
3
+ export declare function renderRunComplete(event: RunCompleteEvent): void;
4
+ export declare function renderSynthesis(event: SynthesisEvent): void;
5
+ export declare function renderJudge(event: JudgeEvent): void;
6
+ export declare function renderDone(dashboardUrl: string): void;
@@ -0,0 +1,71 @@
1
+ import chalk from "chalk";
2
+ const SENTIMENT_COLORS = {
3
+ positive: chalk.green,
4
+ neutral: chalk.gray,
5
+ negative: chalk.yellow,
6
+ critical: chalk.red,
7
+ };
8
+ export function renderStep(event) {
9
+ const persona = chalk.cyan(event.personaName.padEnd(18));
10
+ const stepNum = chalk.dim(`[step ${String(event.step).padStart(2)}]`);
11
+ if (event.thought?.summary) {
12
+ const color = SENTIMENT_COLORS[event.thought.sentiment ?? "neutral"] ?? chalk.gray;
13
+ console.log(` ${persona} ${stepNum} ${color(event.thought.summary)}`);
14
+ }
15
+ if (event.action) {
16
+ const action = chalk.dim(`→ ${event.action.toolName}`);
17
+ console.log(` ${" ".repeat(18)} ${" ".repeat(10)} ${action}`);
18
+ }
19
+ }
20
+ export function renderRunComplete(event) {
21
+ const persona = chalk.cyan(event.personaName.padEnd(18));
22
+ const status = event.status === "completed"
23
+ ? chalk.green("✓ completed")
24
+ : event.status === "abandoned"
25
+ ? chalk.yellow("⚠ abandoned")
26
+ : event.status === "blocked"
27
+ ? chalk.red("✕ blocked")
28
+ : chalk.red(`✕ ${event.status}`);
29
+ const score = event.uxScore != null ? chalk.bold(` UX: ${event.uxScore}/100`) : "";
30
+ console.log(`\n ${persona} ${status}${score}\n`);
31
+ }
32
+ export function renderSynthesis(event) {
33
+ console.log(chalk.bold("\n ── Synthesis ──────────────────────────────────────\n"));
34
+ console.log(` ${event.executiveSummary}\n`);
35
+ if (event.positives?.length > 0) {
36
+ console.log(chalk.green.bold(" Positives:"));
37
+ for (const p of event.positives) {
38
+ const text = typeof p === "string" ? p : p.title ?? JSON.stringify(p);
39
+ console.log(chalk.green(` ✓ ${text}`));
40
+ }
41
+ console.log();
42
+ }
43
+ if (event.tickets?.length > 0) {
44
+ console.log(chalk.yellow.bold(" Issues Found:"));
45
+ for (const t of event.tickets) {
46
+ const title = typeof t === "string" ? t : t.title ?? JSON.stringify(t);
47
+ const severity = t.severity ? chalk.dim(` [${t.severity}]`) : "";
48
+ console.log(chalk.yellow(` • ${title}${severity}`));
49
+ }
50
+ console.log();
51
+ }
52
+ }
53
+ export function renderJudge(event) {
54
+ console.log(chalk.bold("\n ── Judge Verdict ─────────────────────────────────\n"));
55
+ const verdictColor = event.verdict === "pass" ? chalk.green : event.verdict === "fail" ? chalk.red : chalk.yellow;
56
+ console.log(` Verdict: ${verdictColor.bold(event.verdict.toUpperCase())}`);
57
+ console.log(` Confidence: ${chalk.bold(event.confidence)}`);
58
+ if (event.recommendedActions?.length > 0) {
59
+ console.log(chalk.bold("\n Recommended Actions:"));
60
+ for (const action of event.recommendedActions) {
61
+ const text = typeof action === "string" ? action : action.action ?? JSON.stringify(action);
62
+ console.log(` → ${text}`);
63
+ }
64
+ }
65
+ console.log();
66
+ }
67
+ export function renderDone(dashboardUrl) {
68
+ console.log(chalk.bold(" ── Complete ──────────────────────────────────────\n"));
69
+ console.log(` ${chalk.dim("Full results:")} ${chalk.underline(dashboardUrl)}`);
70
+ console.log();
71
+ }
@@ -0,0 +1,5 @@
1
+ export interface TunnelInfo {
2
+ url: string;
3
+ close: () => Promise<void>;
4
+ }
5
+ export declare function openTunnel(localPort: number, authToken: string): Promise<TunnelInfo>;
@@ -0,0 +1,17 @@
1
+ import ngrok from "@ngrok/ngrok";
2
+ export async function openTunnel(localPort, authToken) {
3
+ const listener = await ngrok.forward({
4
+ addr: localPort,
5
+ authtoken: authToken,
6
+ });
7
+ const url = listener.url();
8
+ if (!url) {
9
+ throw new Error("ngrok tunnel created but no URL returned");
10
+ }
11
+ return {
12
+ url,
13
+ close: async () => {
14
+ await listener.close();
15
+ },
16
+ };
17
+ }
@@ -0,0 +1,131 @@
1
+ export interface InitiateResponse {
2
+ deviceCode: string;
3
+ userCode: string;
4
+ verificationUrl: string;
5
+ expiresIn: number;
6
+ }
7
+ export interface PollResponse {
8
+ status: "pending" | "expired" | "consumed" | "complete";
9
+ apiKey?: string;
10
+ user?: {
11
+ name: string;
12
+ email: string;
13
+ };
14
+ organization?: {
15
+ name: string;
16
+ planType: string;
17
+ };
18
+ error?: string;
19
+ }
20
+ export interface TunnelTokenResponse {
21
+ ngrokAuthToken: string;
22
+ }
23
+ export interface Swarm {
24
+ id: string;
25
+ name: string;
26
+ description: string | null;
27
+ personas: any[];
28
+ createdAt: string;
29
+ }
30
+ export interface SwarmsResponse {
31
+ swarms: Swarm[];
32
+ }
33
+ export interface CreateTestRequest {
34
+ targetUrl: string;
35
+ goal: string;
36
+ userDescription: string;
37
+ swarmId?: string;
38
+ generatePersonas?: {
39
+ agentCount: number;
40
+ audienceDescription: string;
41
+ };
42
+ }
43
+ export interface CreateTestResponse {
44
+ batchId: string;
45
+ personas: Array<{
46
+ index: number;
47
+ name: string;
48
+ }>;
49
+ totalRuns: number;
50
+ dashboardUrl: string;
51
+ }
52
+ export interface BatchStatusResponse {
53
+ batchId: string;
54
+ status: string;
55
+ overallUxScore: number | null;
56
+ totalRuns: number;
57
+ completedRuns: number;
58
+ failedRuns: number;
59
+ runs: Array<{
60
+ id: string;
61
+ personaIndex: number;
62
+ personaName: string | null;
63
+ status: string;
64
+ uxScore: number | null;
65
+ stepsTaken: number | null;
66
+ }>;
67
+ synthesis: {
68
+ executiveSummary: string;
69
+ tickets: any[];
70
+ positives: any[];
71
+ } | null;
72
+ judge: {
73
+ verdict: string;
74
+ confidence: string;
75
+ evidenceBlocks: any[];
76
+ recommendedActions: any[];
77
+ } | null;
78
+ dashboardUrl: string;
79
+ }
80
+ export interface StepEvent {
81
+ type: "step";
82
+ runId: string;
83
+ personaName: string;
84
+ step: number;
85
+ thought?: {
86
+ kind: string;
87
+ summary?: string;
88
+ sentiment?: string;
89
+ };
90
+ action?: {
91
+ toolName: string;
92
+ input: Record<string, unknown>;
93
+ };
94
+ }
95
+ export interface RunCompleteEvent {
96
+ type: "run_complete";
97
+ runId: string;
98
+ personaName: string;
99
+ status: string;
100
+ uxScore?: number;
101
+ }
102
+ export interface SynthesisEvent {
103
+ type: "synthesis";
104
+ executiveSummary: string;
105
+ tickets: any[];
106
+ positives: any[];
107
+ }
108
+ export interface JudgeEvent {
109
+ type: "judge";
110
+ verdict: string;
111
+ confidence: string;
112
+ evidenceBlocks: any[];
113
+ recommendedActions: any[];
114
+ }
115
+ export interface DoneEvent {
116
+ type: "done";
117
+ }
118
+ export type SSEEvent = StepEvent | RunCompleteEvent | SynthesisEvent | JudgeEvent | DoneEvent;
119
+ export interface SwarmConfig {
120
+ apiKey: string;
121
+ apiUrl: string;
122
+ ngrokToken?: string;
123
+ user: {
124
+ name: string;
125
+ email: string;
126
+ };
127
+ organization: {
128
+ name: string;
129
+ planType: string;
130
+ };
131
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@useswarm/cli",
3
+ "version": "0.1.0",
4
+ "description": "Swarm CLI — AI-powered UX testing from your terminal",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "swarm": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "dev": "tsx src/index.ts",
15
+ "build": "tsc",
16
+ "start": "node dist/index.js",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": ["ux", "testing", "ai", "swarm", "cli"],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/aaladaruncc/my-stagehand-app",
23
+ "directory": "packages/cli"
24
+ },
25
+ "homepage": "https://useswarm.co",
26
+ "dependencies": {
27
+ "commander": "^13.0.0",
28
+ "chalk": "^5.4.0",
29
+ "ora": "^8.2.0",
30
+ "open": "^10.1.0",
31
+ "conf": "^13.0.0",
32
+ "@ngrok/ngrok": "^1.4.1",
33
+ "eventsource-parser": "^3.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "tsx": "^4.0.0",
37
+ "typescript": "^5.0.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }