Skip to content
Open
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
63 changes: 62 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,74 @@ console.log(response); // "Hello World!"

## Authentication

### Option 1: Claude CLI Authentication (Recommended)

This SDK delegates all authentication to the Claude CLI:

```bash
# One-time setup - login with your Claude account
claude login
```

The SDK does not handle authentication directly. If you see authentication errors, authenticate using the Claude CLI first.
### Option 2: Programmatic Authentication (New!)

For programmatic authentication without the CLI, use the built-in OAuth flow:

```javascript
import { startAuth, isAuthenticated, getAccessToken } from '@instantlyeasy/claude-code-sdk-ts';

// Check if already authenticated
if (await isAuthenticated()) {
console.log('Already authenticated!');
const token = await getAccessToken();
} else {
// Start OAuth flow
const { url, waitForCode } = startAuth();

console.log('Open this URL in your browser:');
console.log(url);

// User opens URL, authorizes, and copies the code
const code = '...'; // Get from user input
await waitForCode(code);

console.log('Authentication successful!');
}
```

Run the complete example:

```bash
node examples/auth-example.js
```

The programmatic auth flow:
1. Generates a secure OAuth URL with PKCE
2. Stores credentials in `~/.claude/credentials.json`
3. Automatically handles token refresh
4. Provides convenient helper functions

### Authentication Classes

For advanced use cases, use the authentication classes directly:

```javascript
import { ClaudeAuth, AnthropicAuth } from '@instantlyeasy/claude-code-sdk-ts';

const auth = new ClaudeAuth();

// Start auth flow
const { url, waitForCode } = auth.startAuthFlow();

// Check authentication status
const isAuth = await auth.isAuthenticated();

// Get access token (refreshes if needed)
const token = await auth.getAccessToken();

// Logout
await auth.logout();
```

## Core Features

Expand Down Expand Up @@ -419,6 +479,7 @@ Comprehensive examples are available in the [examples directory](./examples):
- **[fluent-api-demo.js](./examples/fluent-api-demo.js)** - Complete fluent API showcase
- **[sessions.js](./examples/sessions.js)** - Session management patterns
- **[yaml-config-demo.js](./examples/yaml-config-demo.js)** - Configuration examples
- **[auth-example.js](./examples/auth-example.js)** - 🔐 **Authentication flow example**

### **Advanced Features** ([new-features directory](./examples/fluent-api/new-features/))
- **[interactive-streaming.js](./examples/fluent-api/new-features/interactive-streaming.js)** - 🎬 **Interactive chat with visual streaming**
Expand Down
706 changes: 706 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

132 changes: 132 additions & 0 deletions examples/auth-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import {
AuthAnthropic,
OAuthCredentials,
validateCredentials,
} from "@instantlyeasy/claude-code-sdk-ts";
import { createInterface } from "readline";
import * as fs from "fs/promises";
import * as path from "path";

const CREDENTIALS_FILE = "./credentials.json";

async function main() {
console.log("Claude Code SDK - Authentication Example\n");

// Helper function to load credentials from file
async function loadCredentials(): Promise<Record<string, OAuthCredentials>> {
try {
const data = await fs.readFile(CREDENTIALS_FILE, "utf-8");
return JSON.parse(data);
} catch {
return {};
}
}

// Helper function to save credentials to file
async function saveCredentials(providerID: string, credentials: OAuthCredentials) {
const allCreds = await loadCredentials();
allCreds[providerID] = credentials;
await fs.writeFile(CREDENTIALS_FILE, JSON.stringify(allCreds, null, 2));
await fs.chmod(CREDENTIALS_FILE, 0o600);
}

// Helper function to get valid access token
async function getAccessToken() {
const allCreds = await loadCredentials();
const info = allCreds["anthropic"];

if (!info || info.type !== "oauth") return null;

// Check if access token is still valid
if (info.access && info.expires > Date.now()) {
return info.access;
}

// Refresh the token
try {
const credentials = await AuthAnthropic.refresh(info.refresh);
const validatedCreds: OAuthCredentials = {
type: "oauth",
refresh: credentials.refresh,
access: credentials.access,
expires: credentials.expires,
};
await saveCredentials("anthropic", validatedCreds);
return credentials.access;
} catch (error) {
console.error("❌ Failed to refresh token:", error.message);
return null;
}
}

// Check if already authenticated
const existingToken = await getAccessToken();
if (existingToken) {
console.log("✅ Already authenticated!");
console.log(`🔑 Access token: ${existingToken.substring(0, 20)}...`);
console.log("✅ Authentication working correctly!");
return;
}

console.log("🔐 Starting authentication flow...\n");

// Start the auth flow
const { url, verifier } = await AuthAnthropic.authorize("max");

console.log("📋 Please follow these steps:");
console.log("1. Open this URL in your browser:");
console.log(` ${url}\n`);
console.log("2. Sign in to your Anthropic account");
console.log("3. Authorize the application");
console.log("4. Copy the authorization code from the callback page\n");

let code;
while (true) {
code = await askQuestion("📝 Paste the authorization code here: ");
if (!code) {
console.log("❌ No code provided");
continue;
}
break;
}

try {
console.log("🔄 Exchanging code for tokens...");
const credentials = await AuthAnthropic.exchange(code.trim(), verifier);

const validatedCreds: OAuthCredentials = {
type: "oauth",
refresh: credentials.refresh,
access: credentials.access,
expires: credentials.expires,
};

await saveCredentials("anthropic", validatedCreds);

console.log("✅ Authentication successful!");
console.log(`🔑 Credentials stored in ${path.resolve(CREDENTIALS_FILE)}`);

// Verify by getting access token
const token = await getAccessToken();
console.log(`🎉 Access token obtained: ${token?.substring(0, 20)}...`);
} catch (error) {
console.error("❌ Authentication failed:", error.message);
process.exit(1);
}
}

function askQuestion(question) {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});

return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}

main().catch(console.error);
75 changes: 75 additions & 0 deletions examples/auth-memory-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { AuthAnthropic } from "@instantlyeasy/claude-code-sdk-ts";
import { createInterface } from "readline";

async function main() {
console.log("Claude Code SDK - In-Memory Authentication Example\n");
console.log("This example stores credentials in memory only (no files).\n");

console.log("🔐 Starting authentication flow...\n");

// Start the auth flow
const { url, verifier } = await AuthAnthropic.authorize("max");

console.log("📋 Please follow these steps:");
console.log("1. Open this URL in your browser:");
console.log(` ${url}\n`);
console.log("2. Sign in to your Anthropic account");
console.log("3. Authorize the application");
console.log("4. Copy the authorization code from the callback page\n");

let code;
while (true) {
code = await askQuestion("📝 Paste the authorization code here: ");
if (!code) {
console.log("❌ No code provided");
continue;
}
break;
}

try {
console.log("🔄 Exchanging code for tokens...");
const credentials = await AuthAnthropic.exchange(code.trim(), verifier);

console.log("\n✅ Authentication successful!");
console.log("\n📋 Your credentials (save these securely):");
console.log("=" .repeat(60));
console.log(JSON.stringify({
type: "oauth",
refresh: credentials.refresh,
access: credentials.access,
expires: credentials.expires,
expiresAt: new Date(credentials.expires).toISOString()
}, null, 2));
console.log("=" .repeat(60));
console.log("\n⚠️ These credentials are not saved to disk!");
console.log("💡 To use them later, save the JSON above to a secure location.");

// Example of how to refresh the token
console.log("\n🔄 Testing token refresh...");
const refreshedCredentials = await AuthAnthropic.refresh(credentials.refresh);
console.log("✅ Token refresh successful!");
console.log(`🔑 New access token: ${refreshedCredentials.access.substring(0, 20)}...`);
console.log(`📅 New expiry: ${new Date(refreshedCredentials.expires).toISOString()}`);

} catch (error) {
console.error("❌ Authentication failed:", error.message);
process.exit(1);
}
}

function askQuestion(question: string): Promise<string> {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});

return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer);
});
});
}

main().catch(console.error);
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,14 @@
"node": ">=18.0.0"
},
"dependencies": {
"@openauthjs/openauth": "^0.4.3",
"execa": "^8.0.1",
"js-yaml": "^4.1.0",
"which": "^4.0.0"
"which": "^4.0.0",
"zod": "^4.0.17"
},
"devDependencies": {
"@types/bun": "^1.2.20",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.10.0",
"@types/which": "^3.0.3",
Expand Down
8 changes: 8 additions & 0 deletions src/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## Taken from opencode:

https://github.com/sst/opencode/blob/dev/packages/opencode/src/auth/anthropic.ts

(now moved)

see original at https://github.com/sst/opencode/blob/c93d50e8c7af7911c41ba445b431a5428aaf15b8/packages/opencode/src/auth/anthropic.ts

Loading