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
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,17 @@ echo/
- **Echo TS SDK** (`packages/sdk/ts`): Foundation for all framework-specific SDKs
- **Echo React SDK** (`packages/sdk/react`): React hooks and components
- **Echo Next SDK** (`packages/sdk/next`): Next.js App Router integration
- **Echo CLI** (`templates/echo-cli`): Command-line AI agent with authentication options

### Authentication Methods in Echo

Echo supports multiple authentication and payment methods:

1. **Echo API Keys**: Managed accounts with centralized billing
2. **WalletConnect**: Connect mobile wallets for decentralized payments
3. **Local Wallet (Self-Custody)**: Generate and manage private keys locally for full custody

All methods integrate with the X402 payment protocol for USDC-based AI payments.

## Coding Standards

Expand All @@ -197,6 +208,9 @@ echo/
- Prefer explicit types over `any`
- Use interfaces for public APIs, types for internal structures
- Enable strict mode in `tsconfig.json`
- Import from correct module paths:
- `privateKeyToAccount` from `viem/accounts`, not `viem`
- Use relative paths or absolute imports with `@` alias

### Imports

Expand Down Expand Up @@ -308,21 +322,30 @@ The scope indicates which package is affected:
- `react-sdk`: React SDK
- `next-sdk`: Next.js SDK
- `ts-sdk`: TypeScript SDK
- `cli`: Echo CLI template
- `templates`: Starter templates
- `docs`: Documentation

When working on the Echo CLI or templates, use `cli` or `templates` scope.

### Examples

```bash
feat(react-sdk): add support for streaming responses

feat(cli): add local wallet self-custody authentication

fix(server): correct token counting for Claude models

docs(templates): add environment setup guide for next-chat

docs(cli): update readme with local wallet instructions

refactor(control): simplify balance calculation logic

test(ts-sdk): add integration tests for provider initialization

test(cli): add tests for wallet generation and signing
```

## Pull Request Process
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,14 @@ Available templates:

- **[next](./templates/next)** - Next.js application with Echo
- **[react](./templates/react)** - Vite React application with Echo
- **[nextjsChatbot](./templates/nextjs-chatbot)** - Next.js with Echo and Vercel AI SDK
- **[assistantUi](./templates/assistant-ui)** - Next.js with Echo and Assistant UI
- **[next-chat](./templates/next-chat)** - Next.js chatbot with Echo and Vercel AI SDK
- **[assistant-ui](./templates/assistant-ui)** - Next.js with Echo and Assistant UI
- **[echo-cli](./templates/echo-cli)** - CLI tool for AI chat with Echo (API keys + crypto wallets)

Or run `npx echo-start my-app` to choose interactively.

**Note:** The CLI template (`echo-cli`) requires manual installation from the repository as it's a command-line tool rather than a web application. See the [templates README](./templates/README.md) for details.

# Development

Fill out `packages/app/control/.env` and `packages/app/server/.env`. Then...
Expand Down
82 changes: 81 additions & 1 deletion packages/app/control/docs/getting-started/templates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -416,4 +416,84 @@ Next.js application demonstrating Echo as an Auth.js provider for authentication
</Tab>
</Tabs>
</Step>
</Steps>
</Steps>

## Echo CLI (Echodex)

A command-line interface for AI chat powered by Echo with support for both API key and crypto wallet payments via the X402 protocol.

[View on GitHub](https://github.com/Merit-Systems/echo/tree/master/templates/echo-cli)

<Callout type="info">
**Note:** Unlike web-based templates, the CLI tool requires manual installation from the repository as it's not available through `echo-start`.
</Callout>

### Features

- **Dual Authentication**: Echo API keys or WalletConnect for flexible payment options
- **Multi-Model Support**: GPT-4o, GPT-5, GPT-5 Mini, GPT-5 Nano
- **X402 Protocol**: Pay-per-use with crypto wallets
- **Conversation Management**: Resume and export chat history
- **Secure Storage**: OS keychain integration
- **Real-time Usage Tracking**: View balance and costs

<Steps>
<Step>
### Create an Echo App
Go to [echo.merit.systems/new](https://echo.merit.systems/new) to get an `app_id`.
</Step>

<Step>
### Clone and Install
The CLI template is available in the Echo repository:

```sh lineNumbers
git clone https://github.com/Merit-Systems/echo.git
cd echo/templates/echo-cli
pnpm install
pnpm build
```
</Step>

<Step>
### Authenticate
Start by logging in to Echo:

```sh lineNumbers
echodex login
```

Choose between:
- **Echo API Key**: Opens your browser to create an API key
- **WalletConnect**: Displays a QR code for mobile wallet authentication
</Step>

<Step>
### Start Chatting
Once authenticated, start a chat session:

```sh lineNumbers
echodex
```

Or use other commands:
```sh lineNumbers
echodex model # Select AI model
echodex resume # Resume last conversation
echodex history # View conversation history
echodex export # Export as JSON
echodex profile # View profile and balance
```
</Step>
</Steps>

### Global Installation (Optional)

To use `echodex` globally on your system:

```sh lineNumbers
cd echo/templates/echo-cli
pnpm link --global
```

For more details, see the [CLI template README](https://github.com/Merit-Systems/echo/tree/master/templates/echo-cli#readme).
1 change: 1 addition & 0 deletions packages/app/control/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ We provide a variety of ready-to-use templates to help you get started quickly w
- **Auth.js (NextAuth)** — Next.js app with Echo as an Auth.js provider
- **React Chat** — Chat interface for React apps
- **React Image** — Image generation for React apps
- **CLI** — Command-line tool for AI chat with Echo (API keys + crypto wallets)

All templates are available through `echo-start` or in the [GitHub repository](https://github.com/Merit-Systems/echo/tree/master/templates). Visit our [templates documentation](/docs/getting-started/templates) for detailed setup instructions.

Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ export const Connection: React.FC<Props> = ({ appId }) => {
refetchInterval: shouldRefetch ? 2500 : undefined,
}
);
const [numApiKeys] = api.user.apiKeys.count.useSuspenseQuery({ appId });
const [numApiKeys] = api.user.apiKeys.count.useSuspenseQuery(
{ appId },
{
refetchInterval: shouldRefetch ? 2500 : undefined,
}
);

const isConnected = useMemo(() => {
return numTokens > 0 || numApiKeys > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,10 @@ export const useAppConnectionSetup = (appId: string) => {
refetchInterval: shouldRefetchConnection ? 2500 : undefined,
}
);
const [transactionsCount] = api.apps.app.transactions.count.useSuspenseQuery(
{
appId,
},
{
refetchInterval: shouldRefetchTransactions ? 2500 : undefined,
}
);

const isConnected = useMemo(() => {
return numTokens > 0 || numApiKeys > 0 || transactionsCount > 0;
}, [numTokens, numApiKeys, transactionsCount]);
return numTokens > 0 || numApiKeys > 0;
}, [numTokens, numApiKeys]);

useEffect(() => {
setShouldRefetchConnection(!isConnected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface Transaction {
user?: {
id: string | null;
name: string | null;
email: string | null;
image: string | null;
};
date: Date;
Expand Down Expand Up @@ -111,7 +112,10 @@ const TransactionRow = ({ transaction }: { transaction: Transaction }) => {
<div className="flex flex-col items-start">
<p className="text-sm leading-tight">
<span className="font-medium">
{transaction.user?.name ?? 'x402 Users'}
{transaction.user?.name ??
(transaction.user?.email
? `${transaction.user.id}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a transaction has no name but has an email, the code tries to display the user's ID. However, the ID can be null according to the type definition, which will display the string "null" to users instead of a proper fallback.

View Details
📝 Patch Details
diff --git a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx
index 976abde3..846ca909 100644
--- a/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx
+++ b/packages/app/control/src/app/(app)/app/[id]/transactions/_components/transactions.tsx
@@ -114,7 +114,7 @@ const TransactionRow = ({ transaction }: { transaction: Transaction }) => {
               <span className="font-medium">
                 {transaction.user?.name ??
                   (transaction.user?.email
-                    ? `${transaction.user.id}`
+                    ? transaction.user.id ?? 'x402 Users'
                     : 'Unknown User')}
               </span>{' '}
               made {transaction.callCount} requests
diff --git a/packages/app/control/src/app/(app)/app/[id]/users/_components/users.tsx b/packages/app/control/src/app/(app)/app/[id]/users/_components/users.tsx
index 96a7ea86..9f340423 100644
--- a/packages/app/control/src/app/(app)/app/[id]/users/_components/users.tsx
+++ b/packages/app/control/src/app/(app)/app/[id]/users/_components/users.tsx
@@ -188,7 +188,7 @@ const UserRow = ({ user, showEmail }: { user: User; showEmail: boolean }) => {
         <div className="flex flex-row items-center gap-2">
           <UserAvatar src={user.image} className="size-6" />
           <p className="text-sm font-medium">
-            {user.name ?? (user.email ? `${user.id}` : 'Unknown User')}
+            {user.name ?? (user.email ? user.id ?? 'Unknown User' : 'Unknown User')}
           </p>
         </div>
       </TableCell>

Analysis

Null user.id displays "null" string in transaction and user lists

What fails: TransactionRow component in transactions.tsx (line 116) and UserRow component in users.tsx (line 191) display the literal string "null" when user.id is null but user.email exists

How to reproduce:

// Transaction with null userId but valid email triggers the bug
const transaction = {
  user: { id: null, name: null, email: 'user@example.com' }
};
// Template literal ` 

Result: Users see "null made 5 requests" instead of a proper fallback like "x402 Users made 5 requests"

Expected: Should show meaningful fallback text per database schema (Transaction.userId is nullable String?) and backend service pattern (sets name to 'x402 Users' when userId is null)

: 'Unknown User')}
</span>{' '}
made {transaction.callCount} requests
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const UsersTable: React.FC<Props> = ({ appId }) => {
...rows.map(row =>
[
`"${row.name ?? ''}"`,
`"${row.email || ''}"`,
`"${row.email ?? ''}"`,
row.usage.totalTransactions,
row.usage.rawCost,
row.usage.markupProfit,
Expand Down Expand Up @@ -187,7 +187,9 @@ const UserRow = ({ user, showEmail }: { user: User; showEmail: boolean }) => {
<TableCell className="pl-4">
<div className="flex flex-row items-center gap-2">
<UserAvatar src={user.image} className="size-6" />
<p className="text-sm font-medium">{user.name}</p>
<p className="text-sm font-medium">
{user.name ?? (user.email ? `${user.id}` : 'Unknown User')}
</p>
</div>
</TableCell>
{showEmail && (
Expand Down
3 changes: 3 additions & 0 deletions packages/app/control/src/services/db/apps/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const listAppTransactions = async (
select: {
id: true,
name: true,
email: true,
image: true,
},
},
Expand All @@ -69,6 +70,7 @@ export const listAppTransactions = async (
user: {
id: string | null;
name: string | null;
email: string | null;
image: string | null;
};
date: Date;
Expand All @@ -93,6 +95,7 @@ export const listAppTransactions = async (
id: transaction.id,
user: {
id: transaction.userId,
email: transaction.user?.email ?? null,
name:
transaction.user?.name ??
(transaction.userId === null ? 'x402 Users' : null),
Expand Down
40 changes: 40 additions & 0 deletions templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,46 @@ npx echo-start@latest --template assistant-ui

---

### CLI Templates

#### Echo CLI (`echo-cli`)

A command-line interface for AI chat powered by Echo with support for both API key and crypto wallet payments.

**Note:** This template is different from web-based templates. Install it directly from the repository:

```bash
git clone https://github.com/Merit-Systems/echo.git
cd echo/templates/echo-cli
pnpm install
pnpm build
```

**Features:**

- Dual authentication: Echo API keys or WalletConnect
- Multi-model support (GPT-4o, GPT-5, etc.)
- X402 protocol for crypto payments
- Conversation history and resume
- Secure OS keychain credential storage
- Export conversations as JSON
- Profile and usage management

**Usage:**

```bash
echodex login # Authenticate
echodex # Start chat
echodex model # Select AI model
echodex resume # Resume conversation
echodex history # View history
echodex export # Export as JSON
echodex profile # View profile
echodex logout # Sign out
```

---

### Feature-Specific Templates

#### Next.js Chat (`next-chat`)
Expand Down
2 changes: 2 additions & 0 deletions templates/echo-cli/.env.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ECHO_APP_ID="74d9c979-e036-4e43-904f-32d214b361fc"
NEXT_PUBLIC_ECHO_APP_ID="74d9c979-e036-4e43-904f-32d214b361fc"
5 changes: 5 additions & 0 deletions templates/echo-cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
.env*
!.env.local
.pnpm-store
Loading
Loading