Merge pull request #54 from kevincodex1/feature/updated-branding
Feature/updated branding
This commit is contained in:
@@ -34,41 +34,41 @@ type Segments = {
|
||||
const POSES: Record<ClawdPose, Segments> = {
|
||||
default: {
|
||||
r1L: ' ╭',
|
||||
r1E: '○ ○ ',
|
||||
r1E: '◌ ◌ ',
|
||||
r1R: '╮',
|
||||
r2L: ' │',
|
||||
r2R: '│ '
|
||||
r2L: ' ┆',
|
||||
r2R: '┆ '
|
||||
},
|
||||
'look-left': {
|
||||
r1L: ' ╭',
|
||||
r1E: '◔ ○ ',
|
||||
r1E: '◔ ◌ ',
|
||||
r1R: '╮',
|
||||
r2L: ' │',
|
||||
r2R: '│ '
|
||||
r2L: ' ┆',
|
||||
r2R: '┆ '
|
||||
},
|
||||
'look-right': {
|
||||
r1L: ' ╭',
|
||||
r1E: '○ ◔ ',
|
||||
r1E: '◌ ◔ ',
|
||||
r1R: '╮',
|
||||
r2L: ' │',
|
||||
r2R: '│ '
|
||||
r2L: ' ┆',
|
||||
r2R: '┆ '
|
||||
},
|
||||
'arms-up': {
|
||||
r1L: '\\╭',
|
||||
r1E: '○ ○ ',
|
||||
r1E: '◌ ◌ ',
|
||||
r1R: '╮/',
|
||||
r2L: ' │',
|
||||
r2R: '│ '
|
||||
r2L: ' ┆',
|
||||
r2R: '┆ '
|
||||
}
|
||||
};
|
||||
|
||||
// Apple Terminal uses a bg-fill trick (see below), so only eye poses make
|
||||
// sense. Arm poses fall back to default.
|
||||
const APPLE_EYES: Record<ClawdPose, string> = {
|
||||
default: ' ○ ○ ',
|
||||
'look-left': ' ◔ ○ ',
|
||||
'look-right': ' ○ ◔ ',
|
||||
'arms-up': ' ○ ○ '
|
||||
default: ' ◌ ◌ ',
|
||||
'look-left': ' ◔ ◌ ',
|
||||
'look-right': ' ◌ ◔ ',
|
||||
'arms-up': ' ◌ ◌ '
|
||||
};
|
||||
export function Clawd(t0) {
|
||||
const $ = _c(26);
|
||||
@@ -98,7 +98,7 @@ export function Clawd(t0) {
|
||||
const p = POSES[pose];
|
||||
let t3;
|
||||
if ($[4] !== p.r1L) {
|
||||
t3 = <Text color="clawd_body">{p.r1L}</Text>;
|
||||
t3 = <Text color="text">{p.r1L}</Text>;
|
||||
$[4] = p.r1L;
|
||||
$[5] = t3;
|
||||
} else {
|
||||
@@ -106,7 +106,7 @@ export function Clawd(t0) {
|
||||
}
|
||||
let t4;
|
||||
if ($[6] !== p.r1E) {
|
||||
t4 = <Text color="clawd_body" backgroundColor="clawd_background">{p.r1E}</Text>;
|
||||
t4 = <Text color="text">{p.r1E}</Text>;
|
||||
$[6] = p.r1E;
|
||||
$[7] = t4;
|
||||
} else {
|
||||
@@ -114,7 +114,7 @@ export function Clawd(t0) {
|
||||
}
|
||||
let t5;
|
||||
if ($[8] !== p.r1R) {
|
||||
t5 = <Text color="clawd_body">{p.r1R}</Text>;
|
||||
t5 = <Text color="text">{p.r1R}</Text>;
|
||||
$[8] = p.r1R;
|
||||
$[9] = t5;
|
||||
} else {
|
||||
@@ -132,7 +132,7 @@ export function Clawd(t0) {
|
||||
}
|
||||
let t7;
|
||||
if ($[14] !== p.r2L) {
|
||||
t7 = <Text color="clawd_body">{p.r2L}</Text>;
|
||||
t7 = <Text color="text">{p.r2L}</Text>;
|
||||
$[14] = p.r2L;
|
||||
$[15] = t7;
|
||||
} else {
|
||||
@@ -140,14 +140,14 @@ export function Clawd(t0) {
|
||||
}
|
||||
let t8;
|
||||
if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t8 = <Text color="clawd_body" backgroundColor="clawd_background">OPEN </Text>;
|
||||
t8 = <Text color="text"> OC </Text>;
|
||||
$[16] = t8;
|
||||
} else {
|
||||
t8 = $[16];
|
||||
}
|
||||
let t9;
|
||||
if ($[17] !== p.r2R) {
|
||||
t9 = <Text color="clawd_body">{p.r2R}</Text>;
|
||||
t9 = <Text color="text">{p.r2R}</Text>;
|
||||
$[17] = p.r2R;
|
||||
$[18] = t9;
|
||||
} else {
|
||||
@@ -164,7 +164,7 @@ export function Clawd(t0) {
|
||||
}
|
||||
let t11;
|
||||
if ($[22] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t11 = <Text color="clawd_body">{" "}╰─◡─╯{" "}</Text>;
|
||||
t11 = <Text color="inactive">{" "}╰─◠─╯{" "}</Text>;
|
||||
$[22] = t11;
|
||||
} else {
|
||||
t11 = $[22];
|
||||
@@ -187,7 +187,7 @@ function AppleTerminalClawd(t0) {
|
||||
} = t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = <Text color="clawd_body">▗</Text>;
|
||||
t1 = <Text color="text">▗</Text>;
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
@@ -195,7 +195,7 @@ function AppleTerminalClawd(t0) {
|
||||
const t2 = APPLE_EYES[pose];
|
||||
let t3;
|
||||
if ($[1] !== t2) {
|
||||
t3 = <Text color="clawd_background" backgroundColor="clawd_body">{t2}</Text>;
|
||||
t3 = <Text color="text">{t2}</Text>;
|
||||
$[1] = t2;
|
||||
$[2] = t3;
|
||||
} else {
|
||||
@@ -203,7 +203,7 @@ function AppleTerminalClawd(t0) {
|
||||
}
|
||||
let t4;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = <Text color="clawd_body">▖</Text>;
|
||||
t4 = <Text color="text">▖</Text>;
|
||||
$[3] = t4;
|
||||
} else {
|
||||
t4 = $[3];
|
||||
|
||||
@@ -88,22 +88,23 @@ export function CondensedLogo() {
|
||||
}
|
||||
let t5;
|
||||
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t5 = <Text bold={true}>Open Claude</Text>;
|
||||
t5 = <Text bold={true}>OPEN CLAUDE</Text>;
|
||||
$[8] = t5;
|
||||
} else {
|
||||
t5 = $[8];
|
||||
}
|
||||
let t6;
|
||||
if ($[9] !== truncatedVersion) {
|
||||
t6 = <Text>{t5}{" "}<Text dimColor={true}>v{truncatedVersion}</Text></Text>;
|
||||
t6 = <Text>{t5} <Text dimColor={true}>v{truncatedVersion}</Text></Text>;
|
||||
$[9] = truncatedVersion;
|
||||
$[10] = t6;
|
||||
} else {
|
||||
t6 = $[10];
|
||||
}
|
||||
const t6a = 'Open terminal for any LLM';
|
||||
let t7;
|
||||
if ($[11] !== shouldSplit || $[12] !== truncatedBilling || $[13] !== truncatedModel) {
|
||||
t7 = shouldSplit ? <><Text dimColor={true}>{truncatedModel}</Text><Text dimColor={true}>{truncatedBilling}</Text></> : <Text dimColor={true}>{truncatedModel} · {truncatedBilling}</Text>;
|
||||
t7 = shouldSplit ? <><Text><Text color="inactive">Model</Text><Text dimColor={true}> {truncatedModel}</Text></Text><Text><Text color="inactive">Mode</Text><Text dimColor={true}> {truncatedBilling}</Text></Text></> : <Text><Text color="inactive">Model</Text><Text dimColor={true}> {truncatedModel} · {truncatedBilling}</Text></Text>;
|
||||
$[11] = shouldSplit;
|
||||
$[12] = truncatedBilling;
|
||||
$[13] = truncatedModel;
|
||||
@@ -114,7 +115,7 @@ export function CondensedLogo() {
|
||||
const t8 = agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd;
|
||||
let t9;
|
||||
if ($[15] !== t8) {
|
||||
t9 = <Text dimColor={true}>{t8}</Text>;
|
||||
t9 = <Text><Text color="inactive">Path</Text><Text dimColor={true}> {t8}</Text></Text>;
|
||||
$[15] = t8;
|
||||
$[16] = t9;
|
||||
} else {
|
||||
@@ -140,7 +141,7 @@ export function CondensedLogo() {
|
||||
}
|
||||
let t12;
|
||||
if ($[23] !== t10 || $[24] !== t11 || $[25] !== t6 || $[26] !== t7 || $[27] !== t9) {
|
||||
t12 = <OffscreenFreeze><Box flexDirection="row" gap={2} alignItems="center">{t4}<Box flexDirection="column">{t6}{t7}{t9}{t10}{t11}</Box></Box></OffscreenFreeze>;
|
||||
t12 = <OffscreenFreeze><Box borderStyle="round" borderColor="inactive" paddingX={2} paddingY={0} flexDirection="row" gap={2} alignItems="center"><Box flexDirection="column" alignItems="center"><Text color="inactive">•</Text>{t4}<Text color="inactive">•</Text></Box><Box flexDirection="column"><Text bold={true}>OPEN CLAUDE</Text><Text dimColor={true}>{t6a}</Text><Box marginTop={1}>{t6}</Box>{t7}{t9}{t10}{t11}</Box></Box></OffscreenFreeze>;
|
||||
$[23] = t10;
|
||||
$[24] = t11;
|
||||
$[25] = t6;
|
||||
|
||||
@@ -250,8 +250,8 @@ export function LogoV2() {
|
||||
}
|
||||
const layoutMode = getLayoutMode(columns);
|
||||
const userTheme = resolveThemeSetting(getGlobalConfig().theme);
|
||||
const borderTitle = ` ${color("claude", userTheme)("Claude Code")} ${color("inactive", userTheme)(`v${version}`)} `;
|
||||
const compactBorderTitle = color("claude", userTheme)(" Claude Code ");
|
||||
const borderTitle = ` ${color("text", userTheme)("Open Claude")} ${color("inactive", userTheme)(`v${version}`)} `;
|
||||
const compactBorderTitle = color("text", userTheme)(" Open Claude ");
|
||||
if (layoutMode === "compact") {
|
||||
let welcomeMessage = formatWelcomeMessage(username);
|
||||
if (stringWidth(welcomeMessage) > columns - 4) {
|
||||
@@ -328,7 +328,7 @@ export function LogoV2() {
|
||||
t18 = $[42];
|
||||
t19 = $[43];
|
||||
}
|
||||
return <><OffscreenFreeze><Box flexDirection="column" borderStyle="round" borderColor="claude" borderText={t11} paddingX={1} paddingY={1} alignItems="center" width={columns}><Text bold={true}>{welcomeMessage}</Text>{t12}{t13}<Text dimColor={true}>{billingType}</Text><Text dimColor={true}>{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}</Text></Box></OffscreenFreeze>{t14}{t15}{t16}{t17}{t18}{t19}</>;
|
||||
return <><OffscreenFreeze><Box flexDirection="column" borderStyle="round" borderColor="inactive" borderText={t11} paddingX={1} paddingY={1} alignItems="center" width={columns}><Text bold={true}>{welcomeMessage}</Text>{t12}{t13}<Text dimColor={true}>{billingType}</Text><Text dimColor={true}>{agentName ? `@${agentName} · ${truncatedCwd}` : truncatedCwd}</Text></Box></OffscreenFreeze>{t14}{t15}{t16}{t17}{t18}{t19}</>;
|
||||
}
|
||||
const welcomeMessage_0 = formatWelcomeMessage(username);
|
||||
const modelLine = showAccountIdentity && !process.env.IS_DEMO && config.oauthAccount?.organizationName ? `${modelDisplayName} · ${billingType} · ${config.oauthAccount.organizationName}` : `${modelDisplayName} · ${billingType}`;
|
||||
@@ -344,7 +344,7 @@ export function LogoV2() {
|
||||
const T1 = Box;
|
||||
const t11 = "column";
|
||||
const t12 = "round";
|
||||
const t13 = "claude";
|
||||
const t13 = "inactive";
|
||||
let t14;
|
||||
if ($[44] !== borderTitle) {
|
||||
t14 = {
|
||||
@@ -359,12 +359,12 @@ export function LogoV2() {
|
||||
t14 = $[45];
|
||||
}
|
||||
const T2 = Box;
|
||||
const t15 = layoutMode === "horizontal" ? "row" : "column";
|
||||
const t16 = 1;
|
||||
const t15 = "column";
|
||||
const t16 = 0;
|
||||
const t17 = 1;
|
||||
let t18;
|
||||
if ($[46] !== welcomeMessage_0) {
|
||||
t18 = <Box marginTop={1}><Text bold={true}>{welcomeMessage_0}</Text></Box>;
|
||||
t18 = <Box marginTop={1} flexDirection="column" alignItems="center"><Text bold={true}>OPEN CLAUDE</Text><Text dimColor={true}>open terminal for any LLM</Text><Text color="inactive">•</Text><Text bold={true}>{welcomeMessage_0}</Text></Box>;
|
||||
$[46] = welcomeMessage_0;
|
||||
$[47] = t18;
|
||||
} else {
|
||||
@@ -379,7 +379,7 @@ export function LogoV2() {
|
||||
}
|
||||
let t20;
|
||||
if ($[49] !== modelLine) {
|
||||
t20 = <Text dimColor={true}>{modelLine}</Text>;
|
||||
t20 = <Text><Text color="inactive">Model</Text><Text dimColor={true}> {modelLine}</Text></Text>;
|
||||
$[49] = modelLine;
|
||||
$[50] = t20;
|
||||
} else {
|
||||
@@ -387,7 +387,7 @@ export function LogoV2() {
|
||||
}
|
||||
let t21;
|
||||
if ($[51] !== cwdLine) {
|
||||
t21 = <Text dimColor={true}>{cwdLine}</Text>;
|
||||
t21 = <Text><Text color="inactive">Path</Text><Text dimColor={true}> {cwdLine}</Text></Text>;
|
||||
$[51] = cwdLine;
|
||||
$[52] = t21;
|
||||
} else {
|
||||
@@ -395,7 +395,7 @@ export function LogoV2() {
|
||||
}
|
||||
let t22;
|
||||
if ($[53] !== t20 || $[54] !== t21) {
|
||||
t22 = <Box flexDirection="column" alignItems="center">{t20}{t21}</Box>;
|
||||
t22 = <Box flexDirection="column" alignItems="center"><Text dimColor={true}>────────────</Text>{t20}{t21}</Box>;
|
||||
$[53] = t20;
|
||||
$[54] = t21;
|
||||
$[55] = t22;
|
||||
@@ -403,9 +403,9 @@ export function LogoV2() {
|
||||
t22 = $[55];
|
||||
}
|
||||
let t23;
|
||||
if ($[56] !== leftWidth || $[57] !== t18 || $[58] !== t22) {
|
||||
t23 = <Box flexDirection="column" width={leftWidth} justifyContent="space-between" alignItems="center" minHeight={9}>{t18}{t19}{t22}</Box>;
|
||||
$[56] = leftWidth;
|
||||
if ($[56] !== columns || $[57] !== t18 || $[58] !== t22) {
|
||||
t23 = <Box flexDirection="column" width="100%" justifyContent="center" alignItems="center" minHeight={11}><Box borderStyle="round" borderColor="inactive" paddingX={3} paddingY={1} width="100%" justifyContent="center" alignItems="center" minHeight={11}><Box flexDirection="column" alignItems="center" justifyContent="center">{t18}<Box marginY={1}>{t19}</Box>{t22}</Box></Box></Box>;
|
||||
$[56] = columns;
|
||||
$[57] = t18;
|
||||
$[58] = t22;
|
||||
$[59] = t23;
|
||||
@@ -414,13 +414,13 @@ export function LogoV2() {
|
||||
}
|
||||
let t24;
|
||||
if ($[60] !== layoutMode) {
|
||||
t24 = layoutMode === "horizontal" && <Box height="100%" borderStyle="single" borderColor="claude" borderDimColor={true} borderTop={false} borderBottom={false} borderLeft={false} />;
|
||||
t24 = false;
|
||||
$[60] = layoutMode;
|
||||
$[61] = t24;
|
||||
} else {
|
||||
t24 = $[61];
|
||||
}
|
||||
const t25 = layoutMode === "horizontal" && <FeedColumn feeds={showOnboarding ? [createProjectOnboardingFeed(getSteps()), createRecentActivityFeed(activities)] : showGuestPassesUpsell ? [createRecentActivityFeed(activities), createGuestPassesFeed()] : showOverageCreditUpsell ? [createRecentActivityFeed(activities), createOverageCreditFeed()] : [createRecentActivityFeed(activities), createWhatsNewFeed(changelog)]} maxWidth={rightWidth} />;
|
||||
const t25 = <FeedColumn feeds={showOnboarding ? [createProjectOnboardingFeed(getSteps()), createRecentActivityFeed(activities)] : showGuestPassesUpsell ? [createRecentActivityFeed(activities), createGuestPassesFeed()] : showOverageCreditUpsell ? [createRecentActivityFeed(activities), createOverageCreditFeed()] : [createRecentActivityFeed(activities), createWhatsNewFeed(changelog)]} maxWidth={columns - 4} />;
|
||||
let t26;
|
||||
if ($[62] !== T2 || $[63] !== t15 || $[64] !== t23 || $[65] !== t24 || $[66] !== t25) {
|
||||
t26 = <T2 flexDirection={t15} paddingX={t16} gap={t17}>{t23}{t24}{t25}</T2>;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,6 +6,7 @@ import {
|
||||
codexStreamToAnthropic,
|
||||
convertAnthropicMessagesToResponsesInput,
|
||||
convertCodexResponseToAnthropicMessage,
|
||||
convertToolsToResponsesTools,
|
||||
} from './codexShim.js'
|
||||
import {
|
||||
resolveCodexApiCredentials,
|
||||
@@ -71,6 +72,77 @@ describe('Codex provider config', () => {
|
||||
})
|
||||
|
||||
describe('Codex request translation', () => {
|
||||
test('disables strict mode for tools with optional parameters', () => {
|
||||
const tools = convertToolsToResponsesTools([
|
||||
{
|
||||
name: 'Agent',
|
||||
description: 'Spawn a sub-agent',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
description: { type: 'string' },
|
||||
prompt: { type: 'string' },
|
||||
subagent_type: { type: 'string' },
|
||||
},
|
||||
required: ['description', 'prompt'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(tools).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
name: 'Agent',
|
||||
description: 'Spawn a sub-agent',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
description: { type: 'string' },
|
||||
prompt: { type: 'string' },
|
||||
subagent_type: { type: 'string' },
|
||||
},
|
||||
required: ['description', 'prompt'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('keeps strict mode for tools whose schema already matches Responses requirements', () => {
|
||||
const tools = convertToolsToResponsesTools([
|
||||
{
|
||||
name: 'Ping',
|
||||
description: 'Ping tool',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string' },
|
||||
},
|
||||
required: ['value'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(tools).toEqual([
|
||||
{
|
||||
type: 'function',
|
||||
name: 'Ping',
|
||||
description: 'Ping tool',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string' },
|
||||
},
|
||||
required: ['value'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
strict: true,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('converts assistant tool use and user tool result into Responses items', () => {
|
||||
const items = convertAnthropicMessagesToResponsesInput([
|
||||
{
|
||||
|
||||
@@ -300,13 +300,75 @@ export function convertToolsToResponsesTools(
|
||||
): ResponsesTool[] {
|
||||
return tools
|
||||
.filter(tool => tool.name && tool.name !== 'ToolSearchTool')
|
||||
.map(tool => ({
|
||||
type: 'function',
|
||||
name: tool.name ?? 'tool',
|
||||
description: tool.description ?? '',
|
||||
parameters: tool.input_schema ?? { type: 'object', properties: {} },
|
||||
strict: true,
|
||||
}))
|
||||
.map(tool => {
|
||||
const parameters = tool.input_schema ?? { type: 'object', properties: {} }
|
||||
|
||||
return {
|
||||
type: 'function',
|
||||
name: tool.name ?? 'tool',
|
||||
description: tool.description ?? '',
|
||||
parameters,
|
||||
...(isStrictResponsesSchema(parameters) ? { strict: true } : {}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function isStrictResponsesSchema(schema: unknown): boolean {
|
||||
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
|
||||
return true
|
||||
}
|
||||
|
||||
const record = schema as Record<string, unknown>
|
||||
const type = record.type
|
||||
|
||||
if (type === 'object') {
|
||||
const properties =
|
||||
record.properties && typeof record.properties === 'object' && !Array.isArray(record.properties)
|
||||
? (record.properties as Record<string, unknown>)
|
||||
: {}
|
||||
|
||||
const propertyKeys = Object.keys(properties)
|
||||
const required = Array.isArray(record.required)
|
||||
? record.required.filter((value): value is string => typeof value === 'string')
|
||||
: null
|
||||
|
||||
if (propertyKeys.length > 0) {
|
||||
if (!required) return false
|
||||
|
||||
const requiredSet = new Set(required)
|
||||
for (const key of propertyKeys) {
|
||||
if (!requiredSet.has(key)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const child of Object.values(properties)) {
|
||||
if (!isStrictResponsesSchema(child)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const combinators = ['anyOf', 'oneOf', 'allOf'] as const
|
||||
for (const key of combinators) {
|
||||
if (key in record) {
|
||||
const value = record[key]
|
||||
if (!Array.isArray(value) || value.some(item => !isStrictResponsesSchema(item))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ('items' in record) {
|
||||
const items = record.items
|
||||
if (Array.isArray(items)) {
|
||||
return items.every(item => isStrictResponsesSchema(item))
|
||||
}
|
||||
return isStrictResponsesSchema(items)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function convertToolChoice(toolChoice: unknown): unknown {
|
||||
|
||||
@@ -96,9 +96,9 @@ export function calculateOptimalLeftWidth(
|
||||
*/
|
||||
export function formatWelcomeMessage(username: string | null): string {
|
||||
if (!username || username.length > MAX_USERNAME_LENGTH) {
|
||||
return 'Welcome back!'
|
||||
return 'Welcome to Open Claude'
|
||||
}
|
||||
return `Welcome back ${username}!`
|
||||
return `Welcome back, ${username}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user