Skip to main content

prompter/
cli.rs

1//! CLI argument types and mode resolution.
2use crate::profile::FamilyName;
3use clap::{Parser, Subcommand};
4use std::path::PathBuf;
5pub use tftio_lib::MetaCommand;
6use tftio_lib::agent::AgentSubcommand;
7
8#[derive(Parser, Debug)]
9#[command(name = "prompter")]
10#[command(about = "Compose reusable prompt snippets from profile definitions")]
11#[command(long_about = None)]
12/// CLI argument struct for the prompter tool.
13#[command(version)]
14pub struct Cli {
15    /// Subcommand to execute
16    #[command(subcommand)]
17    pub command: Commands,
18
19    /// Override configuration file path
20    #[arg(short = 'c', long, value_name = "FILE", global = true)]
21    pub config: Option<PathBuf>,
22
23    /// Output in JSON format
24    #[arg(short = 'j', long, global = true)]
25    pub json: bool,
26}
27
28/// Available subcommands for the prompter CLI.
29///
30/// Each variant represents a different operation mode of the tool.
31#[derive(Subcommand, Debug)]
32pub enum Commands {
33    /// Shared metadata commands.
34    Meta {
35        /// Shared metadata command to execute.
36        #[command(subcommand)]
37        command: MetaCommand,
38    },
39    /// Initialize default config and library
40    Init,
41    /// List available profiles
42    List,
43    /// Show dependency tree for profiles
44    Tree,
45    /// Validate configuration and library references
46    Validate,
47    /// Render one or more profiles (concatenated file contents with deduplication)
48    Run {
49        /// Profile name(s) to render
50        #[arg(required = true)]
51        profiles: Vec<String>,
52        /// Model family used to select fragment variants
53        #[arg(long, value_name = "NAME")]
54        family: Option<FamilyName>,
55        /// Separator between files
56        #[arg(short, long)]
57        separator: Option<String>,
58        /// Pre-prompt text to inject at the beginning
59        #[arg(short = 'p', long)]
60        pre_prompt: Option<String>,
61        /// Post-prompt text to inject at the end
62        #[arg(short = 'P', long)]
63        post_prompt: Option<String>,
64        /// Emit only the deduplicated fragments, suppressing the auto-injected
65        /// default pre-prompt, the date/system context, and the post-prompt
66        /// (an explicit --pre-prompt/--post-prompt still applies)
67        #[arg(short = 'b', long)]
68        bare: bool,
69    },
70    /// Render the always-on invariant base (the agent system prompt).
71    ///
72    /// Emits the `core.base` profile, optionally followed by additional
73    /// profiles. Intended for harness/launcher startup: stamp the output into
74    /// each agent's system-prompt site (Codex AGENTS.md, Claude
75    /// --append-system-prompt, pi SYSTEM.md). Skills inject task context with
76    /// `run` and do NOT re-emit this base.
77    System {
78        /// Additional profile(s) to append after the system base
79        profiles: Vec<String>,
80        /// Separator between files
81        #[arg(short, long)]
82        separator: Option<String>,
83        /// Pre-prompt text to inject at the beginning
84        #[arg(short = 'p', long)]
85        pre_prompt: Option<String>,
86        /// Post-prompt text to inject at the end
87        #[arg(short = 'P', long)]
88        post_prompt: Option<String>,
89        /// Emit only the deduplicated fragments, suppressing the auto-injected
90        /// default pre-prompt, the date/system context, and the post-prompt
91        /// (an explicit --pre-prompt/--post-prompt still applies)
92        #[arg(short = 'b', long)]
93        bare: bool,
94    },
95}
96
97/// Whether rendered output carries its auto-injected framing context.
98///
99/// `Full` wraps the deduplicated fragment bodies in the pre-prompt, the
100/// date/system prefix, and the post-prompt. `Bare` suppresses everything
101/// auto-injected — the default pre-prompt, the date/system stamp, and the
102/// default/config post-prompt — leaving only the fragment bodies, for callers
103/// that want just the composed prompt (e.g. a token-frugal local model that
104/// caches the whole system prompt and is defeated by the per-call date line).
105///
106/// In both modes an explicit `--pre-prompt`/`--post-prompt` is honored: `Bare`
107/// drops defaults, not text the user asked for. The date/system stamp is never
108/// user-supplied, so `Bare` always omits it.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum Framing {
111    /// Surround fragments with the pre-prompt, system prefix, and post-prompt.
112    Full,
113    /// Emit only the deduplicated fragment bodies.
114    Bare,
115}
116
117impl Framing {
118    /// Convert a `--bare` flag into a framing mode.
119    #[must_use]
120    pub const fn from_bare_flag(bare: bool) -> Self {
121        if bare { Self::Bare } else { Self::Full }
122    }
123
124    /// Whether this mode emits the surrounding framing context.
125    #[must_use]
126    pub const fn is_full(self) -> bool {
127        matches!(self, Self::Full)
128    }
129}
130
131/// Application execution modes after parsing command-line arguments.
132///
133/// This enum represents the resolved execution mode after processing
134/// both subcommands and direct profile arguments.
135#[derive(Debug)]
136pub enum AppMode {
137    /// Render one or more profiles with optional separator and pre-prompt
138    Run {
139        /// Profile name(s) to render
140        profiles: Vec<String>,
141        /// Optional model family used to select fragment variants
142        family: Option<FamilyName>,
143        /// Optional separator between concatenated files
144        separator: Option<String>,
145        /// Optional custom pre-prompt text
146        pre_prompt: Option<String>,
147        /// Optional custom post-prompt text
148        post_prompt: Option<String>,
149        /// Whether to wrap fragments in framing context or emit bare bodies
150        framing: Framing,
151        /// Optional configuration file override
152        config: Option<PathBuf>,
153        /// Output in JSON format
154        json: bool,
155    },
156    /// List all available profiles using an optional config override
157    List {
158        /// Optional configuration file override
159        config: Option<PathBuf>,
160        /// Output in JSON format
161        json: bool,
162    },
163    /// Show dependency tree for profiles
164    Tree {
165        /// Optional configuration file override
166        config: Option<PathBuf>,
167        /// Output in JSON format
168        json: bool,
169    },
170    /// Validate configuration and library references with an optional config override
171    Validate {
172        /// Optional configuration file override
173        config: Option<PathBuf>,
174        /// Output in JSON format
175        json: bool,
176    },
177    /// Initialize default configuration and library
178    Init,
179    /// Show version information
180    Version {
181        /// Output in JSON format
182        json: bool,
183    },
184    /// Show license information
185    License,
186    /// Show help information
187    Help,
188    /// Generate shell completion scripts
189    Completions {
190        /// Shell to generate completions for
191        shell: clap_complete::Shell,
192    },
193    /// Check health and configuration status
194    Doctor {
195        /// Output in JSON format
196        json: bool,
197    },
198    /// Agent-skill artifact subcommand (routed through cli-common).
199    Agent {
200        /// Subcommand to dispatch.
201        command: AgentSubcommand,
202    },
203}