Unlucky Launcher v0.2.1
Single-commit source snapshot of the v0.2.1 release.
This commit is contained in:
commit
d517f28140
1092 files changed
+211068
No files matched your search
@@ -0,0 +1,135 @@
|
||||
//! `ull agent`:对外 MCP server 的 headless 入口(02 文档 §1 的第二形态)。
|
||||
//!
|
||||
//! 两个子命令:
|
||||
//! - `agent serve`:长驻服务(`--stdio` 或 Streamable HTTP on 127.0.0.1);
|
||||
//! - `agent config`:打印 Claude Code / Cursor 的配置样例(含当前端口与令牌状态)。
|
||||
//!
|
||||
//! 为什么 `serve` 不能走 `commands::run` 的「返回 Report 再打印」套路:stdio 形态下
|
||||
//! **stdout 就是 JSON-RPC 流**,任何一行多余输出都会让客户端解析失败。所以 runner
|
||||
//! 里对 `Command::AgentServe` 提前分流,不走 `output::print_report`。
|
||||
|
||||
pub mod ports;
|
||||
pub mod serve;
|
||||
|
||||
use serde_json::{json, Value};
|
||||
use unlucky_agent::mcp::config_samples;
|
||||
use unlucky_core::setting::keys::launcher_schema;
|
||||
use unlucky_core::setting::store::SettingStore;
|
||||
|
||||
use crate::cli::AgentServeArgs;
|
||||
use crate::context::Ctx;
|
||||
use crate::error::CliError;
|
||||
use crate::output::Report;
|
||||
|
||||
/// `ull agent serve`:起服务(阻塞)。
|
||||
///
|
||||
/// # Errors
|
||||
/// 见 [`serve::run`]。
|
||||
pub fn run_serve(ctx: &Ctx, args: &AgentServeArgs) -> Result<i32, CliError> {
|
||||
serve::run(ctx, args)
|
||||
}
|
||||
|
||||
/// runner 的分流入口:解析数据根 → 起服务。错误由 runner 统一打印。
|
||||
///
|
||||
/// # Errors
|
||||
/// 数据根缺失、平台不达基线、端口占用、非回环监听等返回用户错误。
|
||||
pub fn run_serve_with(
|
||||
globals: &crate::cli::Globals,
|
||||
args: &AgentServeArgs,
|
||||
err: &mut dyn std::io::Write,
|
||||
) -> Result<i32, CliError> {
|
||||
let ctx = Ctx::resolve(globals)?;
|
||||
ctx.ensure_supported()?;
|
||||
for note in ctx.notes() {
|
||||
let _ = writeln!(err, "提示: {note}");
|
||||
}
|
||||
run_serve(&ctx, args)
|
||||
}
|
||||
|
||||
/// `ull agent config`:输出两份可粘贴的客户端配置 + 当前设置状态。
|
||||
///
|
||||
/// # Errors
|
||||
/// 配置不可读时返回用户错误(schema 不认识的键不影响)。
|
||||
pub fn config_report(ctx: &Ctx, port: Option<u16>) -> Result<Report, CliError> {
|
||||
let store = SettingStore::load(ctx.launcher_config(), launcher_schema());
|
||||
let doc = store.doc();
|
||||
let enabled = doc
|
||||
.effective("agent.mcp.enabled")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let allow_write = doc
|
||||
.effective("agent.mcp.allow_write")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let yolo = doc
|
||||
.effective("ai.yolo")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let token = doc
|
||||
.effective_str("agent.mcp.token")
|
||||
.map(str::trim)
|
||||
.filter(|text| !text.is_empty())
|
||||
.map(str::to_string);
|
||||
let configured_port = doc
|
||||
.effective("agent.mcp.port")
|
||||
.and_then(Value::as_u64)
|
||||
.and_then(|value| u16::try_from(value).ok())
|
||||
.unwrap_or(47823);
|
||||
let port = port.unwrap_or(configured_port);
|
||||
let url = format!("http://127.0.0.1:{port}/mcp");
|
||||
let samples = config_samples(&url, token.as_deref());
|
||||
let registry = unlucky_agent::tools::build_v1_registry(&ports::build(ctx))
|
||||
.map_err(|error| CliError::internal("agent_registry", error.to_string()))?;
|
||||
let read_only_tools = registry.read_only_definitions().len();
|
||||
let all_tools = registry.len();
|
||||
|
||||
let data = json!({
|
||||
"url": url,
|
||||
"enabled": enabled,
|
||||
"allow_write": allow_write,
|
||||
"yolo": yolo,
|
||||
"token_configured": token.is_some(),
|
||||
"read_only_tools": read_only_tools,
|
||||
"all_tools": all_tools,
|
||||
"claude_code": samples.claude_code,
|
||||
"cursor": samples.cursor,
|
||||
});
|
||||
|
||||
let mut human = vec![
|
||||
format!("MCP 端点:{url}"),
|
||||
format!(
|
||||
"开关:{} · 写权限:{} · YOLO:{} · 令牌:{}",
|
||||
if enabled {
|
||||
"已启用"
|
||||
} else {
|
||||
"未启用(serve 仍可手动起)"
|
||||
},
|
||||
if allow_write {
|
||||
"开"
|
||||
} else {
|
||||
"关(只读)"
|
||||
},
|
||||
if yolo { "开" } else { "关" },
|
||||
if token.is_some() {
|
||||
"已设置"
|
||||
} else {
|
||||
"未设置"
|
||||
}
|
||||
),
|
||||
"Claude Code(~/.claude.json 或 `claude mcp add --json`):".to_string(),
|
||||
];
|
||||
human.extend(pretty_lines(&samples.claude_code));
|
||||
human.push("Cursor(.cursor/mcp.json):".to_string());
|
||||
human.extend(pretty_lines(&samples.cursor));
|
||||
human.push(
|
||||
"启动:%prog% agent serve(默认 Streamable HTTP) / %prog% agent serve --stdio".to_string(),
|
||||
);
|
||||
Ok(Report::new(data, human))
|
||||
}
|
||||
|
||||
fn pretty_lines(value: &Value) -> Vec<String> {
|
||||
match serde_json::to_string_pretty(value) {
|
||||
Ok(text) => text.lines().map(|line| format!(" {line}")).collect(),
|
||||
Err(_) => vec![" (序列化失败)".to_string()],
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user