Skip to content
Closed
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
4 changes: 2 additions & 2 deletions packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({
text: rawText,
defaultColor,
}) => {
const text = stripUnsafeCharacters(rawText);
const ansiText = parseMarkdownToANSI(text, defaultColor);
const sanitizedText = stripUnsafeCharacters(rawText);
const ansiText = parseMarkdownToANSI(sanitizedText, defaultColor);

return <Text>{ansiText}</Text>;
};
Expand Down
48 changes: 47 additions & 1 deletion packages/cli/src/ui/utils/markdownParsingUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe('parsingUtils', () => {
const input = 'Visit https://google.com now';
const output = parseMarkdownToANSI(input);
expect(output).toBe(
`${primary('Visit ')}${link('https://google.com')}${primary(' now')}`,
`${primary('Visit ')}\x1b]8;;https://google.com\x1b\\${link('https://google.com')}\x1b]8;;\x1b\\${primary(' now')}`,
);
});

Expand Down Expand Up @@ -269,5 +269,51 @@ describe('parsingUtils', () => {
expect(output).toBe(primary('It costs $5.99 total'));
});
});

describe('OSC 8 terminal hyperlinks for bare URLs', () => {
const OSC = '\x1b]8;;';
const ST = '\x1b\\';

it('wraps bare https URLs in an OSC 8 hyperlink sequence', () => {
const url =
'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=x';
const result = parseMarkdownToANSI(url);
// Must begin with the OSC 8 opening sequence containing the full URL
expect(result).toContain(`${OSC}${url}${ST}`);
// Must close with the OSC 8 terminator
expect(result).toContain(`${OSC}${ST}`);
// The full URL string must appear as visible text
expect(result).toContain(url);
});

it('does not drop OAuth query parameters', () => {
const url =
'https://example.com/auth?redirect_uri=http%3A%2F%2F127.0.0.1%3A1234%2Foauth2callback&response_type=code';
const result = parseMarkdownToANSI(url);
expect(result).toContain('response_type=code');
expect(result).toContain('redirect_uri=http%3A%2F%2F127.0.0.1');
});

it('updates the existing bare-URL test to expect OSC 8 wrapping', () => {
const url = 'https://google.com';
const result = parseMarkdownToANSI(`Visit ${url} now`);
expect(result).toContain(`${OSC}${url}${ST}`);
});

it('does not emit OSC 8 when chalk.level is 0 (NO_COLOR / non-TTY)', () => {
const originalLevel = chalk.level;
try {
chalk.level = 0;
const url = 'https://example.com/path?q=1';
const result = parseMarkdownToANSI(url);
// Must not contain the OSC 8 opening sequence
expect(result).not.toContain('\x1b]8;;');
// The URL text itself must still be present
expect(result).toContain(url);
} finally {
chalk.level = originalLevel;
}
});
});
});
});
11 changes: 10 additions & 1 deletion packages/cli/src/ui/utils/markdownParsingUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,16 @@ export const parseMarkdownToANSI = (
),
);
} else if (fullMatch.match(/^https?:\/\//)) {
styledPart = ansiColorize(fullMatch, theme.text.link);
const colored = ansiColorize(fullMatch, theme.text.link);
// Wrap in an OSC 8 terminal hyperlink so the full URL remains the
// clickable target even when the terminal visually word-wraps the text.
// OSC 8 is an escape sequence: only emit it when chalk is outputting
// ANSI codes. chalk.level === 0 when NO_COLOR is set, stdout is
// non-TTY, or FORCE_COLOR/TERM=dumb suppresses output.
styledPart =
chalk.level > 0
? `\x1b]8;;${fullMatch}\x1b\\${colored}\x1b]8;;\x1b\\`
: colored;
}
} catch (e) {
debugLogger.warn('Error parsing inline markdown part:', fullMatch, e);
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/code_assist/oauth2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,11 @@ async function authWithUserCode(client: OAuth2Client): Promise<boolean> {
code_challenge: codeVerifier.codeChallenge,
state,
});
const osc8Link = (url: string) =>
`\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\`;
writeToStdout(
'Please visit the following URL to authorize the application:\n\n' +
authUrl +
osc8Link(authUrl) +
'\n\n',
);
Comment thread
anshumancodes marked this conversation as resolved.

Expand Down
Loading