cli.rs
Rust · 283 lines · 9.4 KB
//! The command line: every subcommand and its flags.
use crate::index::Filter;
use crate::{setup, store, upgrade};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
// Clap strips these colour codes itself when output is piped or NO_COLOR is set.
const EXAMPLES: &str = "\x1b[1;33mExamples:\x1b[0m
mind search \"retry webhooks with backoff\" find how it was done before
mind search \"deploy\" --kind runbook -p api narrow by kind and project
mind get <id> read an entry and open its files
mind template system > e.md start a new entry, then: mind record --file e.md
mind stale entries whose code changed since they were written
mind who <project> where a project lives and how to reach its agent
mind setup configure: name, AI provider, agent tools, projects
mind upgrade install the latest version from your clone's source
Full guide: https://megamind.coinopolis.io/source/f/docs/GUIDE.md.html (docs/GUIDE.md in the source).";
pub fn styles() -> clap::builder::Styles {
use clap::builder::styling::{AnsiColor, Effects};
clap::builder::Styles::styled()
.header(AnsiColor::Yellow.on_default() | Effects::BOLD)
.usage(AnsiColor::Yellow.on_default() | Effects::BOLD)
.literal(AnsiColor::Cyan.on_default() | Effects::BOLD)
.placeholder(AnsiColor::Cyan.on_default())
}
#[derive(Parser)]
#[command(
name = "mind",
version,
about = "Mega Mind: shared memory for your AI agents — how things were built, \
workflows, fixes, gotchas, decisions, runbooks. Searchable by meaning.",
after_help = EXAMPLES,
styles = styles()
)]
pub struct Cli {
/// Who is writing (default: $MIND_AGENT, else the project of the current directory)
#[arg(long = "as", global = true)]
pub agent: Option<String>,
#[command(subcommand)]
pub cmd: Option<Cmd>,
}
#[derive(clap::Args, Default)]
pub struct FilterArgs {
/// Only this project (substring of project or owner)
#[arg(long, short)]
pub project: Option<String>,
/// Only this kind (system, workflow, fix, gotcha, decision, runbook, overview)
#[arg(long, short)]
pub kind: Option<String>,
/// Comma-separated tags the entry must all have
#[arg(long, short)]
pub tags: Option<String>,
/// Engine/stack substring, e.g. unity, next.js, spring
#[arg(long, short)]
pub engine: Option<String>,
/// Genre substring, e.g. survival
#[arg(long)]
pub genre: Option<String>,
/// Platform substring, e.g. quest, android, web
#[arg(long)]
pub platform: Option<String>,
/// Include retired and superseded entries (hidden by default)
#[arg(long)]
pub all: bool,
}
impl FilterArgs {
pub fn filter(&self) -> Filter {
Filter {
project: self.project.clone().unwrap_or_default(),
kind: self.kind.clone().unwrap_or_default(),
tags: self
.tags
.as_deref()
.unwrap_or("")
.split(',')
.map(store::norm_tag)
.filter(|t| !t.is_empty())
.collect(),
engine: self.engine.clone().unwrap_or_default(),
genre: self.genre.clone().unwrap_or_default(),
platform: self.platform.clone().unwrap_or_default(),
exclude: Default::default(),
}
}
}
#[derive(Subcommand)]
pub enum Cmd {
/// Search by meaning and keywords
Search {
/// What you're trying to do or the problem, in plain words
query: String,
#[command(flatten)]
f: FilterArgs,
/// How many results
#[arg(long, short = 'n', default_value_t = 8)]
limit: usize,
/// Also search the librarian's rephrasings of the query (needs a librarian provider)
#[arg(long)]
deep: bool,
/// One line per result (id, kind, project, title): scan them all, then `mind get` the ones that fit
#[arg(long, short = 'b', conflicts_with = "json")]
brief: bool,
/// Machine-readable output
#[arg(long)]
json: bool,
},
/// Show an entry, the state of its refs, librarian notes and related entries
Get {
id: String,
/// Print only the raw markdown (to edit and pass to `mind update`)
#[arg(long)]
raw: bool,
/// Also show the librarian's free-text notes (AI output, untrusted)
#[arg(long)]
notes: bool,
},
/// Entries most similar to this one
Related { id: String },
/// List entries, newest first
List {
#[command(flatten)]
f: FilterArgs,
#[arg(long, short = 'n', default_value_t = 50)]
limit: usize,
/// Machine-readable output
#[arg(long)]
json: bool,
},
/// Record a new entry (markdown with frontmatter, from stdin or --file)
Record {
#[arg(long)]
file: Option<PathBuf>,
/// Record even though a very similar entry exists
#[arg(long)]
new: bool,
},
/// Replace an entry with new content (stdin or --file); history stays in git
Update {
id: String,
#[arg(long)]
file: Option<PathBuf>,
},
/// Open an entry in $EDITOR, then validate and save it
Edit { id: String },
/// Mark an entry's refs as checked: still accurate after the files changed
Ack { id: String },
/// Delete an entry (it stays in git history)
Rm {
id: String,
#[arg(long)]
reason: String,
/// Needed for runbooks and for entries other entries link to
#[arg(long)]
yes: bool,
},
/// Retire an entry that is no longer true: it drops out of search and lists but stays readable
Retire {
id: String,
/// Why it no longer applies
#[arg(long)]
why: String,
/// The entry that replaces it
#[arg(long)]
by: Option<String>,
},
/// Nightly upkeep: index, stale entries, librarian reviews, broken links, duplicates, contradictions
Maintain {
/// At most this many librarian reviews and contradiction checks per run
#[arg(long, default_value_t = 40)]
limit: usize,
/// Skip everything that needs the librarian (reviews, contradiction checks)
#[arg(long)]
no_librarian: bool,
},
/// Entries whose referenced files changed or vanished since they were written
Stale {
#[arg(long, short)]
project: Option<String>,
/// All projects, not just the current directory's
#[arg(long)]
all: bool,
},
/// Have the librarian check stale entries against the code changes (LM Studio is started and restored as needed)
Review {
#[arg(long, short)]
project: Option<String>,
#[arg(long, short = 'n', default_value_t = 20)]
limit: usize,
/// Re-review even if a current review exists
#[arg(long)]
force: bool,
},
/// A project: where it lives, its stack, its entries, how to reach its agent
Who { project: String },
/// Which projects and entries use a technology, e.g. `mind uses postgres`
Uses { term: String },
/// List known projects
Projects,
/// Manage projects
Project {
#[command(subcommand)]
cmd: ProjectCmd,
},
/// Runbook change proposals awaiting review
Proposals {
#[command(subcommand)]
cmd: Option<ProposalCmd>,
},
/// Print the template for a kind of entry
Template { kind: String },
/// Describe the kinds of entries
Kinds,
/// Validate every entry and ref
Check,
/// Recent changes to the mind
Log {
#[arg(short = 'n', default_value_t = 25)]
n: usize,
},
/// Rebuild the search index
Reindex,
/// Update Mega Mind itself: fetch the source it was built from, show what's new, rebuild
Upgrade(upgrade::UpgradeArgs),
/// Check that everything is wired up: settings, AI providers, index, agent tools
Doctor,
/// Overview of what's in the mind (also what plain `mind` shows)
Stats,
/// What agents recorded recently, grouped by project
Digest {
/// How far back: 24h, 3d, 2w, or a date (YYYY-MM-DD)
#[arg(long, default_value = "24h")]
since: String,
},
/// Create the mind's data directory
Init,
/// One-time (re-runnable) setup: your name, AI providers, agent tools, projects
Setup(setup::SetupArgs),
/// Claude Code hook entry points (session-start, prompt, stop)
#[command(hide = true)]
Hook { name: String },
}
#[derive(Subcommand)]
pub enum ProjectCmd {
/// Add or update a project
Add {
name: String,
path: String,
#[arg(long, default_value = "")]
description: String,
/// Comma-separated stack, e.g. "rust 1.90, axum, postgres"
#[arg(long, default_value = "")]
stack: String,
/// Its agent's session name prefix, e.g. payments-api
#[arg(long, default_value = "")]
session: String,
},
}
#[derive(Subcommand)]
pub enum ProposalCmd {
/// Show a proposal as a diff against the current entry
Show { id: String },
/// Apply a proposal
Accept {
id: String,
#[arg(long)]
yes: bool,
},
/// Discard a proposal
Reject {
id: String,
#[arg(long)]
reason: String,
},
}