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
230 changes: 145 additions & 85 deletions apps/web/src/routes/app-overview/app-overview-install.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,64 @@
import { Check, KeyRound } from "lucide-react";
import { MousePointerClick, SquareTerminal } from "lucide-react";
import type { ReactElement } from "react";
import { useState } from "react";
import { Link } from "react-router-dom";

import { cn } from "@/shared/lib/class-names";
import { writeClipboardText } from "@/shared/lib/clipboard";
import { RuntimeIcon } from "@/shared/ui/brand-icons";
import { Button } from "@/shared/ui/button";
import { CopyCheckIcon } from "@/shared/ui/copy-check-icon";

const INSTALL_COMMAND = "curl -fsSL https://install.mosoo.ai/install.sh | bash";
const API_TOKENS_PATH = "/settings/access-tokens";
const READY_STEPS = [
"Installs mosoo CLI",
"Installs the @mosoo skill",
"Signs in to cloud and runs doctor",
] as const;
const CODING_AGENT_HARNESSES = [
{ label: "Codex", runtimeId: "codex" },
{ label: "Claude Code", runtimeId: "claude-code" },
{ label: "OpenCode", runtimeId: "opencode" },
{ label: "Cursor", runtimeId: "cursor" },
{ label: "Cline", runtimeId: "cline" },
] as const;
import { INSTALL_COMMAND } from "./onboarding-setup-prompt";
import { DocsAction, OnboardingActions, OnboardingSteps } from "./onboarding-steps";

export function AppOverviewInstallGuide(): ReactElement {
type SetupLane = "cli" | "console";

const SETUP_LANE_STORAGE_KEY = "mosoo_overview_setup_lane";

function readStoredSetupLane(): SetupLane | null {
try {
const value = globalThis.localStorage.getItem(SETUP_LANE_STORAGE_KEY);
return value === "cli" || value === "console" ? value : null;
} catch {
return null;
}
}

function storeSetupLane(lane: SetupLane): void {
try {
globalThis.localStorage.setItem(SETUP_LANE_STORAGE_KEY, lane);
} catch {
// Storage can be unavailable in restricted browser contexts.
}
}

function SetupLaneTab({
active,
icon,
label,
onSelect,
}: {
active: boolean;
icon: ReactElement;
label: string;
onSelect: () => void;
}): ReactElement {
return (
<button
type="button"
aria-pressed={active}
onClick={onSelect}
className={cn(
"flex items-center gap-2 rounded-md px-4 py-2 text-sm font-semibold transition-colors",
active ? "bg-card text-fg-1 shadow-xs" : "text-fg-3 hover:text-fg-2",
)}
>
{icon}
{label}
</button>
);
}

function CodingAgentLane(): ReactElement {
const [copied, setCopied] = useState(false);
const [copyFailed, setCopyFailed] = useState(false);

Expand All @@ -43,84 +78,109 @@ export function AppOverviewInstallGuide(): ReactElement {
}, 1500);
}

return (
<>
<div className="border-border bg-bg-sunken mt-7 flex w-full flex-col items-stretch gap-3 rounded-lg border px-4 py-3 sm:flex-row sm:items-center">
<code className="text-fg-1 min-w-0 flex-1 truncate text-left font-mono text-[13px] sm:text-base">
<span className="text-fg-3 select-none">$ </span>
{INSTALL_COMMAND}
</code>
<Button
onClick={() => {
void copyInstallCommand();
}}
className="w-full bg-[rgb(111_211_4)] text-black hover:bg-[rgb(111_211_4)] sm:w-auto"
size="default"
variant="accent"
>
<CopyCheckIcon copied={copied} />
{copied ? "Copied" : "Copy"}
</Button>
</div>

{copyFailed ? (
<div className="mt-2 w-full text-left">
<input
aria-label="mosoo install command"
readOnly
value={INSTALL_COMMAND}
onFocus={(event) => {
event.currentTarget.select();
}}
className="border-border bg-bg-sunken text-fg-1 w-full rounded-md border px-3 py-2 font-mono text-xs"
/>
<p className="text-fg-3 mt-1 text-xs">Copy failed. Select and copy the command above.</p>
</div>
) : null}

<p className="text-fg-3 mt-3 max-w-2xl text-[13px] leading-5">
One command installs the mosoo CLI and the @mosoo skill, signs in to try.mosoo.ai, and
checks cloud readiness. Sign-in creates your API token automatically; you will be asked for
a provider key before sessions can run.
</p>

<OnboardingActions />
</>
);
}

function ConsoleLane(): ReactElement {
return (
<>
<OnboardingSteps />
<div className="mt-3 w-full">
<DocsAction />
</div>
</>
);
}

/**
* The pre-deploy Overview hero, split into two explicit setup lanes: "In your
* coding agent" (install command, auto-minted CLI token, copyable setup
* prompt) and "In the console" (the three-step checklist). Both lanes read
* the same account state, so progress counts once wherever a step happens.
* The last chosen lane is remembered locally; first visits land on the
* coding-agent lane.
*/
export function AppOverviewInstallGuide(): ReactElement {
const [lane, setLane] = useState<SetupLane>(() => readStoredSetupLane() ?? "cli");

function selectLane(nextLane: SetupLane): void {
setLane(nextLane);
storeSetupLane(nextLane);
}

return (
<section className="py-8 sm:py-10">
<div className="mx-auto flex max-w-3xl flex-col items-center text-center">
<h2 className="text-foreground text-3xl font-semibold tracking-tight sm:text-4xl">
Build agent app with <span className="text-[rgb(111_211_4)]">mosoo</span> in your coding
agent
Build agent app with <span className="text-[rgb(111_211_4)]">mosoo</span>
</h2>
<p className="text-muted-foreground mt-3 max-w-2xl text-base leading-7">
One command installs mosoo CLI and the @mosoo skill, signs in to try.mosoo.ai, and checks
cloud readiness.
Pick how you want to set up. Progress counts the same either way.
</p>

<div className="border-border bg-bg-sunken mt-8 flex w-full flex-col items-stretch gap-3 rounded-lg border px-4 py-3 sm:flex-row sm:items-center">
<code className="text-fg-1 min-w-0 flex-1 truncate text-left font-mono text-[13px] sm:text-base">
<span className="text-fg-3 select-none">$ </span>
{INSTALL_COMMAND}
</code>
<Button
onClick={() => {
void copyInstallCommand();
<div className="border-border bg-bg-sunken mt-7 inline-flex rounded-lg border p-1">
<SetupLaneTab
active={lane === "cli"}
icon={<SquareTerminal className="size-4" />}
label="In your coding agent"
onSelect={() => {
selectLane("cli");
}}
className="w-full bg-[rgb(111_211_4)] text-black hover:bg-[rgb(111_211_4)] sm:w-auto"
size="default"
variant="accent"
>
<CopyCheckIcon copied={copied} />
{copied ? "Copied" : "Copy"}
</Button>
</div>

{copyFailed ? (
<div className="mt-2 w-full text-left">
<input
aria-label="mosoo install command"
readOnly
value={INSTALL_COMMAND}
onFocus={(event) => {
event.currentTarget.select();
}}
className="border-border bg-bg-sunken text-fg-1 w-full rounded-md border px-3 py-2 font-mono text-xs"
/>
<p className="text-fg-3 mt-1 text-xs">
Copy failed. Select and copy the command above.
</p>
</div>
) : null}

<div className="text-fg-2 mt-7 grid w-full max-w-3xl grid-cols-1 gap-3 text-sm leading-6 font-medium sm:grid-cols-3 sm:gap-4 sm:text-base">
{READY_STEPS.map((label) => (
<span className="inline-flex min-w-0 items-center justify-center gap-2" key={label}>
<Check className="size-4 shrink-0 text-green-600" />
{label}
</span>
))}
/>
<SetupLaneTab
active={lane === "console"}
icon={<MousePointerClick className="size-4" />}
label="In the console"
onSelect={() => {
selectLane("console");
}}
/>
</div>

<Button asChild className="mt-6" size="default" variant="outline">
<Link to={API_TOKENS_PATH}>
<KeyRound className="size-4" />
Create API token
</Link>
</Button>

<div
aria-label="Supported coding agent harnesses"
className="mt-8 flex w-full max-w-2xl flex-wrap items-center justify-center gap-3"
>
{CODING_AGENT_HARNESSES.map((harness) => (
<span
className="border-border bg-card inline-flex size-12 items-center justify-center rounded-md border shadow-xs"
key={harness.runtimeId}
title={harness.label}
>
<RuntimeIcon className="size-7" runtimeId={harness.runtimeId} />
<span className="sr-only">{harness.label}</span>
</span>
))}
</div>
{lane === "cli" ? <CodingAgentLane /> : <ConsoleLane />}
</div>
</section>
);
Expand Down
80 changes: 80 additions & 0 deletions apps/web/src/routes/app-overview/onboarding-setup-prompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { MOSOO_CONSOLE_ORIGIN } from "@mosoo/contracts/origin";

import { HELP_DOCS_BASE_URL } from "@/shared/config/help-docs";

export const INSTALL_COMMAND = "curl -fsSL https://install.mosoo.ai/install.sh | bash";

function currentOrigin(): string {
return globalThis.window !== undefined ? globalThis.location.origin : MOSOO_CONSOLE_ORIGIN;
}

/**
* Copy-ready onboarding instruction for any coding agent (Codex, Claude Code,
* OpenCode, Cursor, Cline, and others). It mirrors the console checklist on
* the App Overview: install and sign in, provider key, API token, first agent
* and session. The provider key is the one value an agent cannot mint itself,
* so the prompt tells the agent to ask the user for it; CLI sign-in creates
* its own token through the browser login callback.
*/
export function buildOnboardingSetupPrompt(origin: string = currentOrigin()): string {
return `# Set up mosoo

Use this instruction with any coding agent. You are helping the user finish
onboarding to mosoo, an open-source agent runtime: configure an Agent, publish
it, and call it through the Thread API. Work through the steps in order and
skip any step that is already done. Ask the user for values only they know.
Never print or commit secrets.

## Generated variables

\`\`\`text
MOSOO_CONSOLE_URL=${origin}
MOSOO_DOCS_URL=${HELP_DOCS_BASE_URL}
\`\`\`

## 1. Install the mosoo CLI and sign in

\`\`\`bash
${INSTALL_COMMAND}
\`\`\`

The installer adds the mosoo CLI and the @mosoo skill, then opens a browser
sign-in and checks cloud readiness (doctor). The login callback authorizes the
CLI and creates a personal API token for it automatically. If no browser can
open, show the user the sign-in URL printed in the terminal and wait for them
to finish it.

## 2. Add a provider key (required before runs)

Session runs need a model provider credential, for example OpenAI or
Anthropic. This is the one step you cannot complete alone: ask the user which
provider they want and have them add the key at MOSOO_CONSOLE_URL/providers.
Do not start the first run until a provider key is configured.

## 3. Create an API token (optional)

The CLI sign-in from step 1 already covers CLI usage. If this project calls
the Thread API from a backend or script, create a token at
MOSOO_CONSOLE_URL/settings/access-tokens and store it in the project
environment as MOSOO_API_TOKEN. Keep it out of source control.

## 4. Create an agent and run a session

Create an agent at MOSOO_CONSOLE_URL/agent?create=1, then start a first
session from the agent page or MOSOO_CONSOLE_URL/threads?compose=1. The API
flow (create a thread, stream events, continue runs) is documented at
MOSOO_DOCS_URL/coding-agents and MOSOO_DOCS_URL/api-reference.

## 5. Deploy an App (optional)

The App Overview at MOSOO_CONSOLE_URL/ can deploy an App from a public GitHub
repo and bind published agents to it.

## Done when

- The CLI is signed in and doctor reports the cloud is reachable.
- A provider key is configured for the active App.
- An agent exists and at least one Thread run has started under
MOSOO_CONSOLE_URL/threads.
`;
}
Loading
Loading