Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ERRORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ Every command inherits these. Specific messages are subject to change, but the *
| Scenario | Typical message |
|---|---|
| No credentials | `Not signed in.` with a hint listing `auth login` variants |
| HTTP 401 | `Not authenticated` |
| HTTP 401 | `Not signed in.` |
| HTTP 403 | `Permission denied` |
| OAuth refresh failed | `Token refresh failed` with a hint to re-authenticate |
| OAuth refresh failed | `Couldn't refresh your session (<status>)` with a hint to sign in again |
| WebSocket upgrade rejected (for streaming commands) | `WebSocket upgrade rejected (<status>)` |

### Rate limit / plan (exit `4`)
Expand All @@ -78,7 +78,7 @@ Every command inherits these. Specific messages are subject to change, but the *

| Scenario | Typical message |
|---|---|
| Unknown command | `Unknown command: polylane <path>` with a hint pointing at `polylane --help` |
| Unknown command | `Unknown command: polylane <path>`, plus `Closest match: polylane <command>` when one is close, with a hint pointing at `polylane --help` |
| Unknown flag | `Unknown flag: <flag>` |
| Flag requires a value | `Flag <flag> requires a value` |
| Flag expects a number | `Flag <flag> expects a number, got "<value>"` |
Expand Down
36 changes: 25 additions & 11 deletions src/auth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ async function fetchOIDCConfig(domain: string): Promise<OIDCConfig> {
const res = await fetch(url);
if (!res.ok) {
throw new CLIError(
`Failed to fetch OIDC config from ${url}: ${res.status}`,
ExitCode.NETWORK
`Couldn't fetch the sign-in configuration from ${url} (${res.status})`,
ExitCode.NETWORK,
'Check your network and the --domain flag, then try again'
);
}
return (await res.json()) as OIDCConfig;
Expand Down Expand Up @@ -246,11 +247,11 @@ function renderErrorHtml(error: string): string {
return map[c] ?? c;
});
return renderShell(
'Authentication failed',
'Sign-in did not complete',
// Red-500 from Tailwind default — high-contrast destructive accent
'#ef4444',
'Authentication failed',
safe,
'Sign-in did not complete',
`Close this tab and run <code>polylane auth login</code> again from your terminal. (${safe})`,
ALERT_ICON
);
}
Expand Down Expand Up @@ -307,12 +308,20 @@ async function startCallbackServer(expectedState: string, timeoutMs: number): Pr
});

server.on('error', (err) => {
reject(new CLIError(`Failed to start callback server: ${err.message}`, ExitCode.GENERAL));
reject(
new CLIError(
`Couldn't start the local sign-in listener: ${err.message}`,
ExitCode.GENERAL,
`Check that port ${CALLBACK_PORT} is free, then run \`polylane auth login\` again`
)
);
});

setTimeout(() => {
server.close();
reject(new CLIError('OAuth flow timed out', ExitCode.TIMEOUT));
reject(
new CLIError('Sign-in timed out', ExitCode.TIMEOUT, 'Run `polylane auth login` to try again')
);
}, timeoutMs);
});
}
Expand Down Expand Up @@ -391,7 +400,11 @@ export async function oauthBrowserFlow(

if (!tokenRes.ok) {
const body = await tokenRes.text();
throw new CLIError(`Token exchange failed: ${tokenRes.status} ${body}`, ExitCode.AUTH);
throw new CLIError(
`Sign-in did not complete: the token exchange returned ${tokenRes.status} ${body}`,
ExitCode.AUTH,
'Run `polylane auth login` to try again'
);
}
return (await tokenRes.json()) as OAuthTokenResponse;
}
Expand Down Expand Up @@ -463,12 +476,13 @@ export async function oauthDeviceCodeFlow(config: Config): Promise<OAuthTokenRes
throw new CLIError('Device code expired', ExitCode.AUTH, 'Run `polylane auth login` again');
}
throw new CLIError(
`Device code flow failed: ${errBody.error ?? 'unknown error'}`,
ExitCode.AUTH
`Device code sign-in did not complete (${errBody.error ?? 'no error detail'})`,
ExitCode.AUTH,
'Run `polylane auth login` to try again'
);
}

throw new CLIError('Device code timed out', ExitCode.TIMEOUT);
throw new CLIError('Device code timed out', ExitCode.TIMEOUT, 'Run `polylane auth login` to try again');
}

export async function revokeToken(config: Config, token: string): Promise<void> {
Expand Down
4 changes: 2 additions & 2 deletions src/auth/refresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export async function refreshToken(

if (!res.ok) {
throw new CLIError(
`Token refresh failed: ${res.status}`,
`Couldn't refresh your session (${res.status})`,
ExitCode.AUTH,
'Run `polylane auth login` to re-authenticate'
'Run `polylane auth login` to sign in again'
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/auth/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ export async function resolveCredential(config: Config): Promise<Credential> {
}

throw new CLIError(
'Not authenticated',
'Not signed in.',
ExitCode.AUTH,
'Run `polylane auth login` to authenticate'
'Run `polylane auth login`'
);
}

Expand Down
8 changes: 4 additions & 4 deletions src/auth/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ export async function ensureAuth(config: Config): Promise<void> {

if (!isInteractive(config.nonInteractive)) {
throw new CLIError(
'Not authenticated',
'Not signed in.',
ExitCode.AUTH,
'Run `polylane auth login` to authenticate'
'Run `polylane auth login`'
);
}

throw new CLIError(
'Not authenticated',
'Not signed in.',
ExitCode.AUTH,
'Run `polylane auth login` to authenticate'
'Run `polylane auth login`'
);
}
2 changes: 1 addition & 1 deletion src/commands/artifact/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const artifactDeleteCommand: Command = {
false
);
if (!ok) {
process.stderr.write('Cancelled\n');
process.stderr.write('Cancelled. Nothing changed.\n');
return;
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/commands/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface WorkspaceItem {
}

async function validateApiKey(config: Config, key: string): Promise<WhoamiResult> {
const spinner = new Spinner('Validating API key…');
const spinner = new Spinner('Checking the API key…');
spinner.start();
try {
const res = await request(
Expand All @@ -41,7 +41,11 @@ async function validateApiKey(config: Config, key: string): Promise<WhoamiResult
);
if (!res.ok) {
spinner.stop();
throw new CLIError(`API key validation failed (${res.status})`, ExitCode.AUTH);
throw new CLIError(
`The API key was not accepted (${res.status})`,
ExitCode.AUTH,
'Check the key, or create a new one in the console'
);
}
const json = (await res.json()) as {
success: boolean;
Expand All @@ -64,7 +68,7 @@ async function validateApiKey(config: Config, key: string): Promise<WhoamiResult
}

export async function selectWorkspace(config: Config, user: WhoamiResult): Promise<string | undefined> {
const spinner = new Spinner('Fetching workspaces…');
const spinner = new Spinner('Finding your workspaces…');
spinner.start();
try {
const list = await requestJson<{ items: WorkspaceItem[]; count: number }>(
Expand Down
2 changes: 1 addition & 1 deletion src/commands/auth/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const authLogoutCommand: Command = {
true
);
if (!ok) {
process.stderr.write('Cancelled\n');
process.stderr.write('Cancelled. Nothing changed.\n');
return;
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/commands/auth/signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,15 @@ function workspaceStep(landing?: Landing): string[] {
case 'created':
return [
landing.workspaceSlug
? ` 1. Your first workspace ("${landing.workspaceSlug}") was created set it as the default`
: ` 1. Your first workspace was created set it as the default`,
? ` 1. Your first workspace ("${landing.workspaceSlug}") was created: set it as the default`
: ` 1. Your first workspace was created: set it as the default`,
...setDefault,
];
case 'joined':
return [
landing.workspaceSlug
? ` 1. You joined the "${landing.workspaceSlug}" workspace set it as the default`
: ` 1. You joined an existing workspace set it as the default`,
? ` 1. You joined the "${landing.workspaceSlug}" workspace: set it as the default`
: ` 1. You joined an existing workspace: set it as the default`,
...setDefault,
];
case 'existing':
Expand Down Expand Up @@ -159,7 +159,7 @@ async function verifyEmail(config: Config, email: string, code: string): Promise
const json = (await res.json()) as VerifyEmailEnvelope;
if (res.status === 400) return null;
if (!res.ok || !json.success) {
throw new CLIError(json.error?.detail ?? json.error?.message ?? 'Email verification failed', ExitCode.GENERAL);
throw new CLIError(json.error?.detail ?? json.error?.message ?? 'Email verification did not complete', ExitCode.GENERAL);
}
const expiresAt =
parseSessionExpiresAt(res.headers.get('set-cookie')) ??
Expand Down Expand Up @@ -233,7 +233,7 @@ async function emailSignup(config: Config, args: Record<string, unknown>): Promi
});
const json = (await res.json()) as SignupEnvelope;
if (!res.ok || !json.success) {
throw new CLIError(json.error?.detail ?? json.error?.message ?? 'Signup failed', ExitCode.GENERAL);
throw new CLIError(json.error?.detail ?? json.error?.message ?? 'Signup did not complete', ExitCode.GENERAL);
}
const { user, token } = json.result;
if (!user) {
Expand Down Expand Up @@ -300,7 +300,7 @@ async function emailSignup(config: Config, args: Record<string, unknown>): Promi
}
}
throw new CLIError(
'Email verification failed',
'Email verification did not complete',
ExitCode.GENERAL,
`Re-run \`polylane auth signup\` for a fresh code, or finish later with: polylane auth signup --email ${email} --code <code>`
);
Expand Down
4 changes: 2 additions & 2 deletions src/commands/cloud/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async function confirmBrowserConnect(
process.stderr.write(`✓ Connected: ${account.alias || account.account}${detail ? ` (${detail})` : ''}\n`);
}
} else {
process.stderr.write('Not seeing the connection yet — check with `polylane cloud list`.\n');
process.stderr.write('Not seeing the connection yet. Check with `polylane cloud list`.\n');
}
}

Expand All @@ -131,7 +131,7 @@ function printConnectSuccess(config: Config, result: ConnectResult): void {
process.stderr.write(`✓ Connected: ${account.alias || account.account}${detail ? ` (${detail})` : ''}\n`);
}
for (const failure of result.failures) {
process.stderr.write(`Failed to connect ${failure.account}: ${failure.message}\n`);
process.stderr.write(`Couldn't connect ${failure.account}: ${failure.message}\n`);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/commands/cloud/disconnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const cloudDisconnectCommand: Command = {
false
);
if (!confirmed) {
process.stderr.write('Cancelled\n');
process.stderr.write('Cancelled. Nothing changed.\n');
return;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/help.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Command } from '../command';
import type { Config } from '../config/schema';
import { registry, renderHelp } from '../registry';
import { registry, renderHelp, unknownCommandMessage } from '../registry';
import { CLIError } from '../errors/base';
import { ExitCode } from '../errors/codes';

Expand All @@ -16,7 +16,7 @@ export const helpCommand: Command = {
const resolved = registry.resolve(positional);
if (!resolved) {
throw new CLIError(
`Unknown command: polylane ${positional.join(' ')}`,
unknownCommandMessage(positional, registry.suggestCommand(positional)),
ExitCode.USAGE,
'Run `polylane --help` to list commands'
);
Expand Down
2 changes: 1 addition & 1 deletion src/commands/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export async function waitForBrowserCompletion<T>(
}
const spinner = new Spinner(`Waiting for ${opts.waitingFor}… (Ctrl+C to stop waiting)`);
const onSigint = (): void => {
spinner.stop(`Stopped waiting — the browser setup continues on its own. ${opts.interruptHint}`);
spinner.stop(`Stopped waiting. The browser setup continues on its own. ${opts.interruptHint}`);
process.exit(0);
};
spinner.start();
Expand Down
16 changes: 8 additions & 8 deletions src/commands/integration/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ const TYPE_OPTIONS: Array<{ value: ConnectableType; label: string; hint: string
{ value: 'honeycomb', label: 'Honeycomb', hint: 'configuration API key' },
{ value: 'axiom', label: 'Axiom', hint: 'API token' },
{ value: 'betterstack', label: 'Better Stack', hint: 'global, Uptime and Telemetry tokens' },
{ value: 'devin', label: 'Devin', hint: 'API key coding agent' },
{ value: 'cursor', label: 'Cursor', hint: 'API key coding agent' },
{ value: 'factory', label: 'Factory', hint: 'API key coding agent' },
{ value: 'devin', label: 'Devin', hint: 'API key · coding agent' },
{ value: 'cursor', label: 'Cursor', hint: 'API key · coding agent' },
{ value: 'factory', label: 'Factory', hint: 'API key · coding agent' },
{ value: 'mcp', label: 'MCP server', hint: 'any MCP server by URL' },
];

Expand Down Expand Up @@ -134,7 +134,7 @@ async function confirmBrowserConnect(
if (found) {
process.stderr.write(`✓ ${label} connected: ${found.name}\n`);
} else {
process.stderr.write('Not seeing the connection yet — check with `polylane integration list`.\n');
process.stderr.write('Not seeing the connection yet. Check with `polylane integration list`.\n');
}
}

Expand Down Expand Up @@ -277,7 +277,7 @@ async function connectWithCredentials(
let apiKey = '';
let appKey = '';
const ok = await runSteps([
choiceStep(config, args, 'site', '--site', 'Datadog site the one in your Datadog URL', DATADOG_SITES, (v) => {
choiceStep(config, args, 'site', '--site', 'Datadog site: the one in your Datadog URL', DATADOG_SITES, (v) => {
site = v;
}),
secretStep(
Expand Down Expand Up @@ -324,7 +324,7 @@ async function connectWithCredentials(
args,
'region',
'--region',
'Honeycomb region the one in your Honeycomb URL',
'Honeycomb region: the one in your Honeycomb URL',
[
{ value: 'us', label: 'US (api.honeycomb.io)' },
{ value: 'eu', label: 'EU (api.eu1.honeycomb.io)' },
Expand Down Expand Up @@ -362,7 +362,7 @@ async function connectWithCredentials(
args,
'region',
'--region',
'Axiom edge deployment region see your organization settings (https://app.axiom.co/settings/org)',
'Axiom edge deployment region: see your organization settings (https://app.axiom.co/settings/org)',
[
{ value: 'us-east-1', label: 'US East 1' },
{ value: 'eu-central-1', label: 'EU Central 1' },
Expand Down Expand Up @@ -475,7 +475,7 @@ async function connectWithCredentials(
}
const answer = await promptConfirmOrBack(
{ nonInteractive: config.nonInteractive },
`Use ${agent.name} for all autofixes? (instead of the Polylane executor you can change this later)`,
`Use ${agent.name} for all autofixes? (instead of the Polylane executor; you can change this later)`,
true
);
if (answer === BACK) return BACK;
Expand Down
2 changes: 1 addition & 1 deletion src/commands/integration/disconnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const integrationDisconnectCommand: Command = {
false
);
if (!confirmed) {
process.stderr.write('Cancelled\n');
process.stderr.write('Cancelled. Nothing changed.\n');
return;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/memory/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const memoryDeleteCommand: Command = {
false
);
if (!ok) {
process.stderr.write('Cancelled\n');
process.stderr.write('Cancelled. Nothing changed.\n');
return;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/note/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const noteDeleteCommand: Command = {
false
);
if (!ok) {
process.stderr.write('Cancelled\n');
process.stderr.write('Cancelled. Nothing changed.\n');
return;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/note/global-clear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const noteGlobalClearCommand: Command = {
false
);
if (!ok) {
process.stderr.write('Cancelled\n');
process.stderr.write('Cancelled. Nothing changed.\n');
return;
}
}
Expand Down
Loading
Loading