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
8 changes: 4 additions & 4 deletions .claude/skills/controller-skill/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ Claude: "You don't have an active session. Would you like me to set one up?"
```
You: "Set up a session for STRK token transfers"

Claude: [Uses controller_generate_keypair]
Claude: [Uses controller_generate]
Claude: [Creates policy file]
Claude: [Uses controller_register_session]
Claude: [Uses controller_register]
Claude: "Please open this URL to authorize: https://x.cartridge.gg/session?..."

You: [Opens URL and authorizes]
Expand Down Expand Up @@ -107,9 +107,9 @@ You can create custom policy files for your specific contracts and methods.

## Tools Available

1. **controller_generate_keypair** - Generate session keypair
1. **controller_generate** - Generate session keypair
2. **controller_status** - Check session status
3. **controller_register_session** - Register new session (requires browser auth)
3. **controller_register** - Register new session (requires browser auth)
4. **controller_execute** - Execute transactions (positional args: contract, entrypoint, calldata)
5. **controller_call** - Read-only contract calls (positional args: contract, entrypoint, calldata)
6. **controller_transaction** - Get transaction status and details
Expand Down
12 changes: 6 additions & 6 deletions .claude/skills/controller-skill/skill.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"version": "0.1.0",
"tools": [
{
"name": "controller_generate_keypair",
"name": "controller_generate",
"description": "Generate a new session keypair for signing Starknet transactions. First step in session setup.",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
},
"command": "controller generate-keypair --json"
"command": "controller generate --json"
},
{
"name": "controller_status",
Expand All @@ -24,7 +24,7 @@
"command": "controller status --json"
},
{
"name": "controller_register_session",
"name": "controller_register",
"description": "Register a new session with specific contract/method policies. IMPORTANT: Requires human to authorize via browser. Display the authorization URL to the user and wait for them to authorize.",
"inputSchema": {
"type": "object",
Expand All @@ -36,7 +36,7 @@
},
"required": ["policy_file"]
},
"command": "controller register-session {policy_file} --json"
"command": "controller register --file {policy_file} --json"
},
{
"name": "controller_execute",
Expand Down Expand Up @@ -124,12 +124,12 @@
},
{
"title": "Generate Keypair",
"tool": "controller_generate_keypair",
"tool": "controller_generate",
"input": {}
},
{
"title": "Register Session",
"tool": "controller_register_session",
"tool": "controller_register",
"input": {
"policy_file": "policy.json"
}
Expand Down
22 changes: 11 additions & 11 deletions .claude/skills/controller-skill/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Use this skill when the user wants to:

## Tools

### controller_generate_keypair
### controller_generate

Generate a new session keypair for signing transactions.

Expand All @@ -40,7 +40,7 @@ Generate a new session keypair for signing transactions.

**Example:**
```bash
controller generate-keypair --json
controller generate --json
```

---
Expand Down Expand Up @@ -68,7 +68,7 @@ controller status --json

---

### controller_register_session
### controller_register

Register a new session with specific contract/method policies. Requires human to authorize via browser.

Expand All @@ -92,7 +92,7 @@ Register a new session with specific contract/method policies. Requires human to

**Example:**
```bash
controller register-session policy.json --json
controller register --file policy.json --json
```

**Policy file format:**
Expand Down Expand Up @@ -375,9 +375,9 @@ controller clear --yes
### First-Time Setup

1. Check status: `controller_status`
2. If no keypair: `controller_generate_keypair`
2. If no keypair: `controller_generate`
3. Create policy file with desired contracts/methods
4. Register session: `controller_register_session` (user must authorize in browser)
4. Register session: `controller_register` (user must authorize in browser)
5. Execute transactions: `controller_execute`

### Transfer Tokens
Expand All @@ -398,19 +398,19 @@ controller execute \

If status shows expired:
1. Create/update policy file if needed
2. Run `controller_register_session policy.json --json`
2. Run `controller_register policy.json --json`
3. User authorizes in browser
4. Retry the transaction

## Error Handling

### NoSession
- **Cause:** No keypair found
- **Fix:** Run `controller_generate_keypair`
- **Fix:** Run `controller_generate`

### SessionExpired
- **Cause:** Session expired
- **Fix:** Run `controller_register_session policy.json` (user must re-authorize)
- **Fix:** Run `controller_register policy.json` (user must re-authorize)

### ManualExecutionRequired
- **Cause:** No authorized session for this transaction
Expand Down Expand Up @@ -453,14 +453,14 @@ Agent: [Checks status]
> Result: {"status": "no_session"}

Agent: "I need to set up a session first. Let me generate a keypair..."
> controller generate-keypair --json
> controller generate --json
> Result: {"public_key": "0x123..."}

Agent: [Creates policy file for STRK transfers]
> Creates policy.json with STRK contract and transfer method

Agent: "Now I need you to authorize this session. Please open this URL:"
> controller register-session policy.json --json
> controller register --file policy.json --json
> Result: {"authorization_url": "https://x.cartridge.gg/session?..."}

Agent: "Please open the URL above and authorize the session. I'll wait..."
Expand Down
16 changes: 8 additions & 8 deletions LLM_USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ git clone https://github.com/cartridge-gg/controller-cli.git /tmp/controller-cli
```

Once installed, 8 tools become available:
- `controller_generate_keypair` - Generate session keypair
- `controller_generate` - Generate session keypair
- `controller_status` - Check session status
- `controller_register_session` - Register session (requires human auth)
- `controller_register` - Register session (requires human auth)
- `controller_execute` - Execute transactions
- `controller_call` - Read-only contract calls
- `controller_transaction` - Get transaction status
Expand All @@ -51,7 +51,7 @@ Once installed, 8 tools become available:
### 1. Generate Keypair

```bash
controller generate-keypair --json
controller generate --json
```

Output:
Expand Down Expand Up @@ -104,7 +104,7 @@ Active session output:
For popular games/apps, use a preset from [cartridge-gg/presets](https://github.com/cartridge-gg/presets/tree/main/configs):

```bash
controller register-session \
controller register \
--preset loot-survivor \
--chain-id SN_MAIN \
--json
Expand Down Expand Up @@ -133,7 +133,7 @@ Create `policy.json`:
```

```bash
controller register-session \
controller register \
--file policy.json \
--rpc-url https://api.cartridge.gg/x/starknet/sepolia \
--json
Expand Down Expand Up @@ -365,10 +365,10 @@ All errors return JSON:

| Error Code | Cause | Recovery |
|------------|-------|----------|
| `NoSession` | No keypair found | Run `controller generate-keypair --json` |
| `SessionExpired` | Session past expiry | Run `controller register-session` again |
| `NoSession` | No keypair found | Run `controller generate --json` |
| `SessionExpired` | Session past expiry | Run `controller register` again |
| `ManualExecutionRequired` | No authorized session for this transaction | Register session with appropriate policies |
| `CallbackTimeout` | User didn't authorize within 360s | Retry `register-session`, ask user to authorize faster |
| `CallbackTimeout` | User didn't authorize within 360s | Retry `register`, ask user to authorize faster |
| `InvalidInput` (UnsupportedChainId) | Bad chain ID | Use `SN_MAIN` or `SN_SEPOLIA`, or `--rpc-url` for custom chains |
| `InvalidInput` (PresetNotFound) | Unknown preset name | Check [available presets](https://github.com/cartridge-gg/presets/tree/main/configs) |
| `InvalidInput` (PresetChainNotSupported) | Preset doesn't support requested chain | Use a supported chain or create a custom policy file |
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ run:

# Generate keypair (example)
example-keygen:
cargo run -- generate-keypair
cargo run -- generate

# Check status (example)
example-status:
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,21 @@ cargo install --git https://github.com/cartridge-gg/controller-cli
### 1. Generate a Keypair

```bash
controller generate-keypair
controller generate
```

Creates and stores a new session keypair. The private key is stored locally — even if compromised, the session is scoped to only the authorized contracts, methods, and time window.

### 2. Register a Session

```bash
controller register-session --file policies.json --chain-id SN_MAIN
controller register --file policies.json --chain-id SN_MAIN
```

Or use a preset for popular games/apps:

```bash
controller register-session --preset loot-survivor --chain-id SN_MAIN
controller register --preset loot-survivor --chain-id SN_MAIN
```

The CLI generates an authorization URL, displays it, then automatically polls until you authorize in the browser and stores the session.
Expand Down
18 changes: 9 additions & 9 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub async fn shorten_url(api_url: &str, long_url: &str) -> Result<String> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.map_err(|e| CliError::ApiError(format!("Failed to build HTTP client: {}", e)))?;
.map_err(|e| CliError::ApiError(format!("Failed to build HTTP client: {e}")))?;

#[derive(Serialize)]
struct ShortenRequest<'a> {
Expand All @@ -26,11 +26,11 @@ pub async fn shorten_url(api_url: &str, long_url: &str) -> Result<String> {
}

let response = client
.post(format!("{}/s", api_base))
.post(format!("{api_base}/s"))
.json(&ShortenRequest { url: long_url })
.send()
.await
.map_err(|e| CliError::ApiError(format!("Failed to shorten URL: {}", e)))?;
.map_err(|e| CliError::ApiError(format!("Failed to shorten URL: {e}")))?;

if !response.status().is_success() {
return Err(CliError::ApiError(format!(
Expand All @@ -42,7 +42,7 @@ pub async fn shorten_url(api_url: &str, long_url: &str) -> Result<String> {
let shorten_response: ShortenResponse = response
.json()
.await
.map_err(|e| CliError::ApiError(format!("Failed to parse shortener response: {}", e)))?;
.map_err(|e| CliError::ApiError(format!("Failed to parse shortener response: {e}")))?;

Ok(shorten_response.url)
}
Expand Down Expand Up @@ -79,7 +79,7 @@ pub async fn query_session_info(
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(130)) // Slightly longer than backend's 2min timeout
.build()
.map_err(|e| CliError::ApiError(format!("Failed to build HTTP client: {}", e)))?;
.map_err(|e| CliError::ApiError(format!("Failed to build HTTP client: {e}")))?;

// This is a QUERY (not subscription) despite the name
let query = r#"
Expand Down Expand Up @@ -142,7 +142,7 @@ pub async fn query_session_info(
.json(&request)
.send()
.await
.map_err(|e| CliError::ApiError(format!("Failed to query session info: {}", e)))?;
.map_err(|e| CliError::ApiError(format!("Failed to query session info: {e}")))?;

if !response.status().is_success() {
return Err(CliError::ApiError(format!(
Expand All @@ -154,7 +154,7 @@ pub async fn query_session_info(
let graphql_response: GraphQLResponse = response
.json()
.await
.map_err(|e| CliError::ApiError(format!("Failed to parse API response: {}", e)))?;
.map_err(|e| CliError::ApiError(format!("Failed to parse API response: {e}")))?;

if let Some(errors) = graphql_response.errors {
let error_messages: Vec<String> = errors.iter().map(|e| e.message.clone()).collect();
Expand All @@ -176,7 +176,7 @@ impl SessionInfo {
.iter()
.map(|hex| {
Felt::from_hex(hex).map_err(|e| {
CliError::InvalidSessionData(format!("Invalid authorization hex: {}", e))
CliError::InvalidSessionData(format!("Invalid authorization hex: {e}"))
})
})
.collect()
Expand All @@ -185,7 +185,7 @@ impl SessionInfo {
/// Convert address string to Felt
pub fn address_as_felt(&self) -> Result<Felt> {
Felt::from_hex(&self.controller.address)
.map_err(|e| CliError::InvalidSessionData(format!("Invalid address hex: {}", e)))
.map_err(|e| CliError::InvalidSessionData(format!("Invalid address hex: {e}")))
}

/// Convert chain_id string to Felt
Expand Down
Loading