* chore: rebrand user-facing copy to OpenClaude Replace lingering Claude Code branding in CLI, tips, and runtime UI with OpenClaude/openclaude, including the startup tip Gitlawb mention. Co-Authored-By: Claude GPT-5.4 <noreply@openclaude.dev> * chore: address branding-sweep review feedback - PermissionRequest.tsx: rebrand the two remaining "Claude needs your approval/permission" notifications to OpenClaude (review-artifact and generic tool permission paths). - main.tsx, teleport.tsx, session.tsx, WebFetchTool/utils.ts, skills/bundled/{debug,updateConfig}.ts: replace leftover `claude --…` CLI hints and "Claude Code" labels missed by the original sweep. - main.tsx: drop the inline gitlawb.com marketing copy from the stale-prompt tip; keep it a pure rebrand. - auth.ts: finish the half-rename so both `claude setup-token` and `claude auth login` references in the same error block now read `openclaude …`. - mcp/client.ts: keep `name: 'claude-code'` for MCP server allowlist compatibility (now explicit via comment) and replace the "Anthropic's agentic coding tool" description with an OpenClaude one. - MCPSettings.tsx: point the empty-server-list hint at https://github.com/Gitlawb/openclaude instead of code.claude.com. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: replace help link with OpenClaude repo URL Replace https://code.claude.com/docs/en/overview with https://github.com/Gitlawb/openclaude in the help screen. Co-Authored-By: OpenClaude <openclaude@gitlawb.com> --------- Co-authored-by: Claude GPT-5.4 <noreply@openclaude.dev> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: OpenClaude <openclaude@gitlawb.com>
244 lines
8.2 KiB
TypeScript
244 lines
8.2 KiB
TypeScript
import { c as _c } from "react-compiler-runtime";
|
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from 'src/services/analytics/index.js';
|
|
import { setupTerminal, shouldOfferTerminalSetup } from '../commands/terminalSetup/terminalSetup.js';
|
|
import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js';
|
|
import { Box, Link, Newline, Text, useTheme } from '../ink.js';
|
|
import { useKeybindings } from '../keybindings/useKeybinding.js';
|
|
import { isAnthropicAuthEnabled } from '../utils/auth.js';
|
|
import { normalizeApiKeyForConfig } from '../utils/authPortable.js';
|
|
import { getCustomApiKeyStatus } from '../utils/config.js';
|
|
import { env } from '../utils/env.js';
|
|
import { isRunningOnHomespace } from '../utils/envUtils.js';
|
|
import { PreflightStep } from '../utils/preflightChecks.js';
|
|
import type { ThemeSetting } from '../utils/theme.js';
|
|
import { ApproveApiKey } from './ApproveApiKey.js';
|
|
import { ConsoleOAuthFlow } from './ConsoleOAuthFlow.js';
|
|
import { Select } from './CustomSelect/select.js';
|
|
import { WelcomeV2 } from './LogoV2/WelcomeV2.js';
|
|
import { PressEnterToContinue } from './PressEnterToContinue.js';
|
|
import { ThemePicker } from './ThemePicker.js';
|
|
import { OrderedList } from './ui/OrderedList.js';
|
|
type StepId = 'preflight' | 'theme' | 'oauth' | 'api-key' | 'security' | 'terminal-setup';
|
|
interface OnboardingStep {
|
|
id: StepId;
|
|
component: React.ReactNode;
|
|
}
|
|
type Props = {
|
|
onDone(): void;
|
|
};
|
|
export function Onboarding({
|
|
onDone
|
|
}: Props): React.ReactNode {
|
|
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
|
const [skipOAuth, setSkipOAuth] = useState(false);
|
|
const [oauthEnabled] = useState(() => isAnthropicAuthEnabled());
|
|
const [theme, setTheme] = useTheme();
|
|
useEffect(() => {
|
|
logEvent('tengu_began_setup', {
|
|
oauthEnabled
|
|
});
|
|
}, [oauthEnabled]);
|
|
function goToNextStep() {
|
|
if (currentStepIndex < steps.length - 1) {
|
|
const nextIndex = currentStepIndex + 1;
|
|
setCurrentStepIndex(nextIndex);
|
|
logEvent('tengu_onboarding_step', {
|
|
oauthEnabled,
|
|
stepId: steps[nextIndex]?.id as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
|
});
|
|
} else {
|
|
onDone();
|
|
}
|
|
}
|
|
function handleThemeSelection(newTheme: ThemeSetting) {
|
|
setTheme(newTheme);
|
|
goToNextStep();
|
|
}
|
|
const exitState = useExitOnCtrlCDWithKeybindings();
|
|
|
|
// Define all onboarding steps
|
|
const themeStep = <Box marginX={1}>
|
|
<ThemePicker onThemeSelect={handleThemeSelection} showIntroText={true} helpText="To change this later, run /theme" hideEscToCancel={true} skipExitHandling={true} // Skip exit handling as Onboarding already handles it
|
|
/>
|
|
</Box>;
|
|
const securityStep = <Box flexDirection="column" gap={1} paddingLeft={1}>
|
|
<Text bold>Security notes:</Text>
|
|
<Box flexDirection="column" width={70}>
|
|
{/**
|
|
* OrderedList misnumbers items when rendering conditionally,
|
|
* so put all items in the if/else
|
|
*/}
|
|
<OrderedList>
|
|
<OrderedList.Item>
|
|
<Text>Claude can make mistakes</Text>
|
|
<Text dimColor wrap="wrap">
|
|
You should always review Claude's responses, especially when
|
|
<Newline />
|
|
running code.
|
|
<Newline />
|
|
</Text>
|
|
</OrderedList.Item>
|
|
<OrderedList.Item>
|
|
<Text>
|
|
Due to prompt injection risks, only use it with code you trust
|
|
</Text>
|
|
<Text dimColor wrap="wrap">
|
|
For more details see:
|
|
<Newline />
|
|
<Link url="https://code.claude.com/docs/en/security" />
|
|
</Text>
|
|
</OrderedList.Item>
|
|
</OrderedList>
|
|
</Box>
|
|
<PressEnterToContinue />
|
|
</Box>;
|
|
const preflightStep = <PreflightStep onSuccess={goToNextStep} />;
|
|
// Create the steps array - determine which steps to include based on reAuth and oauthEnabled
|
|
const apiKeyNeedingApproval = useMemo(() => {
|
|
// Add API key step if needed
|
|
// On homespace, ANTHROPIC_API_KEY is preserved in process.env for child
|
|
// processes but ignored by Claude Code itself (see auth.ts).
|
|
if (!process.env.ANTHROPIC_API_KEY || isRunningOnHomespace() || !isAnthropicAuthEnabled()) {
|
|
return '';
|
|
}
|
|
const customApiKeyTruncated = normalizeApiKeyForConfig(process.env.ANTHROPIC_API_KEY);
|
|
if (getCustomApiKeyStatus(customApiKeyTruncated) === 'new') {
|
|
return customApiKeyTruncated;
|
|
}
|
|
}, []);
|
|
function handleApiKeyDone(approved: boolean) {
|
|
if (approved) {
|
|
setSkipOAuth(true);
|
|
}
|
|
goToNextStep();
|
|
}
|
|
const steps: OnboardingStep[] = [];
|
|
if (oauthEnabled) {
|
|
steps.push({
|
|
id: 'preflight',
|
|
component: preflightStep
|
|
});
|
|
}
|
|
steps.push({
|
|
id: 'theme',
|
|
component: themeStep
|
|
});
|
|
if (apiKeyNeedingApproval) {
|
|
steps.push({
|
|
id: 'api-key',
|
|
component: <ApproveApiKey customApiKeyTruncated={apiKeyNeedingApproval} onDone={handleApiKeyDone} />
|
|
});
|
|
}
|
|
if (oauthEnabled) {
|
|
steps.push({
|
|
id: 'oauth',
|
|
component: <SkippableStep skip={skipOAuth} onSkip={goToNextStep}>
|
|
<ConsoleOAuthFlow onDone={goToNextStep} />
|
|
</SkippableStep>
|
|
});
|
|
}
|
|
steps.push({
|
|
id: 'security',
|
|
component: securityStep
|
|
});
|
|
if (shouldOfferTerminalSetup()) {
|
|
steps.push({
|
|
id: 'terminal-setup',
|
|
component: <Box flexDirection="column" gap={1} paddingLeft={1}>
|
|
<Text bold>Use OpenClaude's terminal setup?</Text>
|
|
<Box flexDirection="column" width={70} gap={1}>
|
|
<Text>
|
|
For the optimal coding experience, enable the recommended settings
|
|
<Newline />
|
|
for your terminal:{' '}
|
|
{env.terminal === 'Apple_Terminal' ? 'Option+Enter for newlines and visual bell' : 'Shift+Enter for newlines'}
|
|
</Text>
|
|
<Select options={[{
|
|
label: 'Yes, use recommended settings',
|
|
value: 'install'
|
|
}, {
|
|
label: 'No, maybe later with /terminal-setup',
|
|
value: 'no'
|
|
}]} onChange={value => {
|
|
if (value === 'install') {
|
|
// Errors already logged in setupTerminal, just swallow and proceed
|
|
void setupTerminal(theme).catch(() => {}).finally(goToNextStep);
|
|
} else {
|
|
goToNextStep();
|
|
}
|
|
}} onCancel={() => goToNextStep()} />
|
|
<Text dimColor>
|
|
{exitState.pending ? <>Press {exitState.keyName} again to exit</> : <>Enter to confirm · Esc to skip</>}
|
|
</Text>
|
|
</Box>
|
|
</Box>
|
|
});
|
|
}
|
|
const currentStep = steps[currentStepIndex];
|
|
|
|
// Handle Enter on security step and Escape on terminal-setup step
|
|
// Dependencies match what goToNextStep uses internally
|
|
const handleSecurityContinue = useCallback(() => {
|
|
if (currentStepIndex === steps.length - 1) {
|
|
onDone();
|
|
} else {
|
|
goToNextStep();
|
|
}
|
|
}, [currentStepIndex, steps.length, oauthEnabled, onDone]);
|
|
const handleTerminalSetupSkip = useCallback(() => {
|
|
goToNextStep();
|
|
}, [currentStepIndex, steps.length, oauthEnabled, onDone]);
|
|
useKeybindings({
|
|
'confirm:yes': handleSecurityContinue
|
|
}, {
|
|
context: 'Confirmation',
|
|
isActive: currentStep?.id === 'security'
|
|
});
|
|
useKeybindings({
|
|
'confirm:no': handleTerminalSetupSkip
|
|
}, {
|
|
context: 'Confirmation',
|
|
isActive: currentStep?.id === 'terminal-setup'
|
|
});
|
|
return <Box flexDirection="column">
|
|
<WelcomeV2 />
|
|
<Box flexDirection="column" marginTop={1}>
|
|
{currentStep?.component}
|
|
{exitState.pending && <Box padding={1}>
|
|
<Text dimColor>Press {exitState.keyName} again to exit</Text>
|
|
</Box>}
|
|
</Box>
|
|
</Box>;
|
|
}
|
|
export function SkippableStep(t0) {
|
|
const $ = _c(4);
|
|
const {
|
|
skip,
|
|
onSkip,
|
|
children
|
|
} = t0;
|
|
let t1;
|
|
let t2;
|
|
if ($[0] !== onSkip || $[1] !== skip) {
|
|
t1 = () => {
|
|
if (skip) {
|
|
onSkip();
|
|
}
|
|
};
|
|
t2 = [skip, onSkip];
|
|
$[0] = onSkip;
|
|
$[1] = skip;
|
|
$[2] = t1;
|
|
$[3] = t2;
|
|
} else {
|
|
t1 = $[2];
|
|
t2 = $[3];
|
|
}
|
|
useEffect(t1, t2);
|
|
if (skip) {
|
|
return null;
|
|
}
|
|
return children;
|
|
}
|