use serde_json::{json, Value};
const PROTOCOL_VERSION: &str = "2025-06-18";
pub fn run() {
use std::io::{BufRead, Write};
let stdin = std::io::stdin();
let mut out = std::io::stdout().lock();
for line in stdin.lock().lines() {
let line = match line {
Ok(l) => l,
Err(_) => break,
};
if line.trim().is_empty() {
continue;
}
let msg: Value = match serde_json::from_str(&line) {
Ok(v) => v,
Err(_) => continue, };
if let Some(resp) = handle(&msg) {
if writeln!(out, "{resp}").is_err() {
break; }
out.flush().ok();
}
}
}
pub fn handle(msg: &Value) -> Option<Value> {
let method = msg.get("method")?.as_str()?;
let id = msg.get("id")?;
let result = match method {
"initialize" => Ok(json!({
"protocolVersion": msg
.pointer("/params/protocolVersion")
.and_then(|v| v.as_str())
.unwrap_or(PROTOCOL_VERSION),
"capabilities": { "tools": {} },
"serverInfo": { "name": "cmduse", "version": env!("CARGO_PKG_VERSION") },
})),
"ping" => Ok(json!({})),
"tools/list" => Ok(json!({ "tools": tools() })),
"tools/call" => tool_call(msg),
_ => Err((-32601, format!("method not found: {method}"))),
};
Some(match result {
Ok(v) => json!({ "jsonrpc": "2.0", "id": id, "result": v }),
Err((code, message)) => json!({
"jsonrpc": "2.0", "id": id,
"error": { "code": code, "message": message },
}),
})
}
fn tool_call(msg: &Value) -> Result<Value, (i64, String)> {
let name = msg
.pointer("/params/name")
.and_then(|v| v.as_str())
.unwrap_or_default();
let args = msg
.pointer("/params/arguments")
.cloned()
.unwrap_or(json!({}));
let get = |k: &str| args.get(k);
let opt_usize = |k: &str| get(k).and_then(|v| v.as_u64()).map(|n| n as usize);
let opt_bool = |k: &str| get(k).and_then(|v| v.as_bool()).unwrap_or(false);
let opt_tz = |k: &str| -> Result<i64, (i64, String)> {
match get(k) {
None => Ok(0),
Some(v) => match v.as_str().and_then(crate::cli::parse_tz) {
Some(secs) => Ok(secs),
None => Err((-32602, format!("{k} needs an offset like +05:30 or -08:00"))),
},
}
};
let text = match name {
"usage" => Ok(crate::usage_output()),
"plans" => Ok(crate::plans_output(false)),
"models" => crate::models_output(true),
"daily" => {
let tz = opt_tz("tz")?;
crate::daily_output(
opt_usize("days"),
tz,
opt_bool("local"),
crate::report_render::Fmt::Table,
)
}
"hourly" => {
let tz = opt_tz("tz")?;
crate::hourly_output(
opt_usize("hours"),
tz,
opt_bool("local"),
crate::report_render::Fmt::Table,
)
}
"session" => Ok(crate::session_output(
get("since").and_then(|v| v.as_str()),
opt_usize("days"),
opt_tz("tz")?,
crate::report_render::Fmt::Table,
false,
usize::MAX,
)),
"model" => Ok(crate::model_output(
get("since").and_then(|v| v.as_str()),
opt_usize("days"),
opt_tz("tz")?,
crate::report_render::Fmt::Table,
false,
usize::MAX,
)),
_ => {
return Ok(json!({
"content": [{ "type": "text", "text": format!("unknown tool: {name}") }],
"isError": true,
}))
}
};
Ok(match text {
Ok(t) => json!({ "content": [{ "type": "text", "text": t }] }),
Err(e) => json!({
"content": [{ "type": "text", "text": format!("error: {e}") }],
"isError": true,
}),
})
}
fn tools() -> Value {
json!([
{
"name": "usage",
"description": "Command Code usage dashboard: plan, monthly credits remaining, 5-hour and weekly spend windows, billing-period totals.",
"inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
},
{
"name": "plans",
"description": "Command Code plan comparison table (price, monthly credits, window caps), with the account's current plan marked.",
"inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
},
{
"name": "models",
"description": "Live Command Code model list from the API, filtered to what the current plan allows.",
"inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
},
{
"name": "daily",
"description": "Command Code usage by day (all harnesses). Falls back to local CLI logs when no API key is set or local=true.",
"inputSchema": {
"type": "object",
"properties": {
"days": { "type": "integer", "description": "Days back (default 7, max 365)" },
"tz": { "type": "string", "description": "UTC offset for day buckets, e.g. +05:30" },
"local": { "type": "boolean", "description": "Use local CLI logs only (skip the account API)" },
},
"additionalProperties": false,
},
},
{
"name": "hourly",
"description": "Command Code usage by hour (all harnesses, or local CLI sessions with local=true).",
"inputSchema": {
"type": "object",
"properties": {
"hours": { "type": "integer", "description": "Hours back (default 24, max 168)" },
"tz": { "type": "string", "description": "UTC offset for hour buckets, e.g. -08:00" },
"local": { "type": "boolean", "description": "Use local CLI logs only (skip the account API)" },
},
"additionalProperties": false,
},
},
{
"name": "session",
"description": "Command Code local usage by project/session from the CLI logs in ~/.commandcode/projects, windowed by since or days.",
"inputSchema": {
"type": "object",
"properties": {
"since": { "type": "string", "description": "ISO-8601 instant to count from, e.g. 2026-08-27T00:00:00Z" },
"days": { "type": "integer", "description": "Days back (used when since is absent)" },
"tz": { "type": "string", "description": "UTC offset for the local buckets, e.g. +05:30" },
},
"additionalProperties": false,
},
},
{
"name": "model",
"description": "Command Code local usage by model from the CLI logs in ~/.commandcode/projects, windowed by since or days.",
"inputSchema": {
"type": "object",
"properties": {
"since": { "type": "string", "description": "ISO-8601 instant to count from, e.g. 2026-08-27T00:00:00Z" },
"days": { "type": "integer", "description": "Days back (used when since is absent)" },
"tz": { "type": "string", "description": "UTC offset for the local buckets, e.g. -08:00" },
},
"additionalProperties": false,
},
},
])
}