use clap::{Args, Parser, Subcommand, ValueEnum}; use core_protocol::{InvocationKind, ToolExecutionMode}; use mnote_cli::{ execute_output, plan_block_embed, plan_block_insert, plan_block_move, plan_block_patch, plan_mindmap_get, plan_mindmap_op, plan_mindmap_put, plan_page_create, plan_page_delete, plan_page_get, plan_page_move, plan_page_restore, plan_page_save, plan_page_title, plan_search_blocks, plan_search_documents, plan_sidebar_dataset, plan_tool_run, render_plain_output, BlockEmbedArgs, BlockMoveArgs, CliContext, CliError, CliJsonOutput, PageCreateArgs, PageDeleteArgs, PageMoveArgs, PageRestoreArgs, }; #[derive(Parser, Debug)] #[command(name = "mnote-cli")] #[command(about = "Phase 8 最小真实执行 CLI", long_about = None)] struct Cli { #[command(flatten)] global: GlobalArgs, #[command(subcommand)] command: Commands, } #[derive(Args, Debug, Clone)] struct GlobalArgs { #[arg(long)] json: bool, #[arg(long)] execute: bool, #[arg(long, default_value = "cli_user")] actor_id: String, #[arg(long, default_value = "human")] actor_type: String, #[arg(long)] session_id: Option, #[arg(long)] reason: Option, #[arg(long)] idempotency_key: Option, #[arg(long)] validate_only: bool, #[arg(long)] dry_run: bool, } #[derive(Subcommand, Debug)] enum Commands { Page(PageCommand), Block(BlockCommand), Mindmap(MindmapCommand), Search(SearchCommand), Sidebar(SidebarCommand), Tool(ToolCommand), } #[derive(Args, Debug)] struct PageCommand { #[command(subcommand)] action: PageAction, } #[derive(Subcommand, Debug)] enum PageAction { Get(PageGetArgs), Title(PageTitleArgs), Save(PageSaveArgs), Create(PageCreateCliArgs), Move(PageMoveCliArgs), Delete(PageDeleteCliArgs), Restore(PageRestoreCliArgs), } #[derive(Args, Debug)] struct PageGetArgs { #[arg(long)] page_id: String, #[arg(long)] workspace_id: Option, } #[derive(Args, Debug)] struct PageTitleArgs { #[arg(long)] page_id: String, #[arg(long)] workspace_id: Option, #[arg(long)] title: String, } #[derive(Args, Debug)] struct PageSaveArgs { #[arg(long)] page_id: String, #[arg(long)] workspace_id: Option, #[arg(long)] revision: Option, #[arg(long)] content_json: String, #[arg(long)] conflict_detection_key: Option, } #[derive(Args, Debug)] struct PageCreateCliArgs { #[arg(long)] page_id: String, #[arg(long)] workspace_id: String, #[arg(long)] parent_id: Option, #[arg(long)] title: String, #[arg(long, default_value = "private")] access_scope: String, #[arg(long, default_value = "[]")] content_json: String, } #[derive(Args, Debug)] struct PageMoveCliArgs { #[arg(long)] page_id: String, #[arg(long)] workspace_id: Option, #[arg(long)] parent_id: Option, #[arg(long)] sort_order: i64, } #[derive(Args, Debug)] struct PageDeleteCliArgs { #[arg(long)] page_id: String, #[arg(long)] workspace_id: Option, } #[derive(Args, Debug)] struct PageRestoreCliArgs { #[arg(long)] page_id: String, #[arg(long)] workspace_id: Option, } #[derive(Args, Debug)] struct BlockCommand { #[command(subcommand)] action: BlockAction, } #[derive(Subcommand, Debug)] enum BlockAction { Insert(BlockInsertArgs), Patch(BlockPatchArgs), Move(BlockMoveCliArgs), Embed(BlockEmbedCliArgs), } #[derive(Args, Debug)] struct BlockInsertArgs { #[arg(long)] workspace_id: String, #[arg(long)] page_id: String, #[arg(long)] content: String, #[arg(long, default_value = "paragraph")] block_type: String, #[arg(long)] parent_block_id: Option, #[arg(long)] prev_block_id: Option, } #[derive(Args, Debug)] struct BlockPatchArgs { #[arg(long)] page_id: String, #[arg(long)] block_id: String, #[arg(long)] workspace_id: Option, #[arg(long)] revision: Option, #[arg(long)] snapshot_json: String, #[arg(long)] conflict_detection_key: Option, } #[derive(Args, Debug)] struct BlockMoveCliArgs { #[arg(long)] source_document_id: String, #[arg(long)] block_id: String, #[arg(long)] target_document_id: String, } #[derive(Args, Debug)] struct BlockEmbedCliArgs { #[arg(long)] source_document_id: String, #[arg(long)] block_id: String, #[arg(long)] target_document_id: String, #[arg(long)] target_block_id: Option, } #[derive(Args, Debug)] struct MindmapCommand { #[command(subcommand)] action: MindmapAction, } #[derive(Subcommand, Debug)] enum MindmapAction { Get(MindmapGetArgs), Put(MindmapPutArgs), Op(MindmapOpArgs), } #[derive(Args, Debug)] struct MindmapGetArgs { #[arg(long)] document_id: String, #[arg(long)] mindmap_id: String, #[arg(long)] workspace_id: Option, } #[derive(Args, Debug)] struct MindmapPutArgs { #[arg(long)] document_id: String, #[arg(long)] mindmap_id: String, #[arg(long)] workspace_id: Option, #[arg(long)] data_json: String, #[arg(long, default_value_t = false)] create_only: bool, } #[derive(Args, Debug)] struct MindmapOpArgs { #[arg(long)] document_id: String, #[arg(long)] mindmap_id: String, #[arg(long)] workspace_id: Option, #[arg(long)] ops_json: String, } #[derive(Args, Debug)] struct SearchCommand { #[command(subcommand)] action: SearchAction, } #[derive(Subcommand, Debug)] enum SearchAction { Documents(SearchDocumentsArgs), Blocks(SearchBlocksArgs), } #[derive(Args, Debug)] struct SearchDocumentsArgs { #[arg(long)] query: String, #[arg(long)] workspace_id: Option, #[arg(long, default_value_t = 20)] limit: u32, #[arg(long)] cursor: Option, } #[derive(Args, Debug)] struct SearchBlocksArgs { #[arg(long)] query: String, #[arg(long)] page_id: Option, #[arg(long, default_value_t = 20)] limit: u32, #[arg(long)] cursor: Option, } #[derive(Args, Debug)] struct SidebarCommand { #[command(subcommand)] action: SidebarAction, } #[derive(Subcommand, Debug)] enum SidebarAction { Dataset(SidebarDatasetArgs), } #[derive(Args, Debug)] struct SidebarDatasetArgs { #[arg(long)] workspace_id: String, } #[derive(Args, Debug)] struct ToolCommand { #[command(subcommand)] action: ToolAction, } #[derive(Subcommand, Debug)] enum ToolAction { Run(ToolRunArgs), } #[derive(Args, Debug)] struct ToolRunArgs { #[arg(long)] tool_name: String, #[arg(long, value_enum, default_value_t = ToolKindArg::Command)] kind: ToolKindArg, #[arg(long, value_enum, default_value_t = ToolModeArg::Plan)] mode: ToolModeArg, #[arg(long)] args_json: String, } #[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)] enum ToolKindArg { Command, Query, Job, } #[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)] enum ToolModeArg { Plan, Result, ExplainPlan, } fn main() { let cli = Cli::parse(); let context = CliContext { actor_id: cli.global.actor_id, actor_type: cli.global.actor_type, session_id: cli.global.session_id, reason: cli.global.reason, idempotency_key: cli.global.idempotency_key, validate_only: cli.global.validate_only, dry_run: cli.global.dry_run, }; let result = match cli.command { Commands::Page(command) => match command.action { PageAction::Get(args) => { plan_page_get(&context, &args.page_id, args.workspace_id.as_deref()) } PageAction::Title(args) => plan_page_title( &context, &args.page_id, args.workspace_id.as_deref(), &args.title, ), PageAction::Save(args) => plan_page_save( &context, &args.page_id, args.workspace_id.as_deref(), args.revision, &args.content_json, args.conflict_detection_key.as_deref(), ), PageAction::Create(args) => plan_page_create( &context, &PageCreateArgs { page_id: &args.page_id, workspace_id: &args.workspace_id, parent_id: args.parent_id.as_deref(), title: &args.title, access_scope: &args.access_scope, content_json: &args.content_json, }, ), PageAction::Move(args) => plan_page_move( &context, &PageMoveArgs { page_id: &args.page_id, workspace_id: args.workspace_id.as_deref(), parent_id: args.parent_id.as_deref(), sort_order: args.sort_order, }, ), PageAction::Delete(args) => plan_page_delete( &context, &PageDeleteArgs { page_id: &args.page_id, workspace_id: args.workspace_id.as_deref(), }, ), PageAction::Restore(args) => plan_page_restore( &context, &PageRestoreArgs { page_id: &args.page_id, workspace_id: args.workspace_id.as_deref(), }, ), }, Commands::Block(command) => match command.action { BlockAction::Insert(args) => plan_block_insert( &context, &args.workspace_id, &args.page_id, &args.content, &args.block_type, args.parent_block_id.as_deref(), args.prev_block_id.as_deref(), ), BlockAction::Patch(args) => plan_block_patch( &context, args.workspace_id.as_deref(), &args.page_id, &args.block_id, args.revision, &args.snapshot_json, args.conflict_detection_key.as_deref(), ), BlockAction::Move(args) => plan_block_move( &context, &BlockMoveArgs { source_document_id: &args.source_document_id, block_id: &args.block_id, target_document_id: &args.target_document_id, }, ), BlockAction::Embed(args) => plan_block_embed( &context, &BlockEmbedArgs { source_document_id: &args.source_document_id, block_id: &args.block_id, target_document_id: &args.target_document_id, target_block_id: args.target_block_id.as_deref(), }, ), }, Commands::Mindmap(command) => match command.action { MindmapAction::Get(args) => plan_mindmap_get( &context, args.workspace_id.as_deref(), &args.document_id, &args.mindmap_id, ), MindmapAction::Put(args) => plan_mindmap_put( &context, args.workspace_id.as_deref(), &args.document_id, &args.mindmap_id, &args.data_json, args.create_only, ), MindmapAction::Op(args) => plan_mindmap_op( &context, args.workspace_id.as_deref(), &args.document_id, &args.mindmap_id, &args.ops_json, ), }, Commands::Search(command) => match command.action { SearchAction::Documents(args) => plan_search_documents( &context, &args.query, args.workspace_id.as_deref(), args.limit, args.cursor.as_deref(), ), SearchAction::Blocks(args) => plan_search_blocks( &context, &args.query, args.page_id.as_deref(), args.limit, args.cursor.as_deref(), ), }, Commands::Sidebar(command) => match command.action { SidebarAction::Dataset(args) => plan_sidebar_dataset(&context, &args.workspace_id), }, Commands::Tool(command) => match command.action { ToolAction::Run(args) => plan_tool_run( &context, &args.tool_name, to_invocation_kind(args.kind), to_execution_mode(args.mode), &args.args_json, ), }, } .and_then(|output| { if cli.global.execute { execute_output(&output, &context) } else { Ok(output) } }); match result { Ok(output) => emit_success(&output, cli.global.json), Err(error) => emit_error(&error, cli.global.json), } } fn emit_success(output: &CliJsonOutput, json_mode: bool) { if json_mode { println!( "{}", serde_json::to_string_pretty(output).expect("CLI JSON 输出必须可序列化") ); } else { println!("{}", render_plain_output(output)); } } fn emit_error(error: &CliError, json_mode: bool) -> ! { if json_mode { println!( "{}", serde_json::to_string_pretty(&serde_json::json!({ "ok": false, "entrypoint": "mnote-cli", "error": { "code": error.code, "message": error.message, } })) .expect("CLI 错误 JSON 输出必须可序列化") ); } else { eprintln!("{}: {}", error.code, error.message); } std::process::exit(1); } fn to_invocation_kind(kind: ToolKindArg) -> InvocationKind { match kind { ToolKindArg::Command => InvocationKind::Command, ToolKindArg::Query => InvocationKind::Query, ToolKindArg::Job => InvocationKind::Job, } } fn to_execution_mode(mode: ToolModeArg) -> ToolExecutionMode { match mode { ToolModeArg::Plan => ToolExecutionMode::Plan, ToolModeArg::Result => ToolExecutionMode::Result, ToolModeArg::ExplainPlan => ToolExecutionMode::ExplainPlan, } }