1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! Data-plane CLI subcommands.
//!
//! These mirror the daemon's `/api/v1` REST routes (and the MCP tools) so most actions are
//! reachable from the command line too — routine flags and per-routine snooze are REST/MCP-only
//! for now (#1516 tracks closing that gap). The global routine lock (`lock`/`unlock`/
//! `lock-status`) has full CLI parity below. Each subcommand is a thin client: it serializes its flags into the
//! same JSON the REST API expects, sends it to the running server over the loopback HTTP client in
//! [`crate::cli`], and prints the server's response. The daemon must already be running
//! (`moadim` / `moadim -i`); when it is not, these commands report that and exit
//! [`crate::cli::EXIT_NOT_RUNNING`].
use clap::{Parser, Subcommand};
use serde_json::{Map, Value};
#[allow(
clippy::missing_docs_in_private_items,
reason = "split-out module keeps the file under the linecheck limit"
)]
#[path = "routine_cmd.rs"]
mod routine_cmd;
pub(crate) use routine_cmd::*;
#[allow(
clippy::missing_docs_in_private_items,
reason = "split-out module keeps the file under the linecheck limit"
)]
#[path = "dispatch_routine.rs"]
mod dispatch_routine;
pub(crate) use dispatch_routine::*;
/// Top-level parser for the data-plane subcommands, parsed from argv with the leading `moadim`
/// binary name already stripped (`no_binary_name`), so the first token is the subcommand keyword.
#[derive(Parser)]
#[command(
name = "moadim",
version,
no_binary_name = true,
about = "moadim data commands"
)]
struct DataCli {
/// The selected data subcommand.
#[command(subcommand)]
command: DataCommand,
}
/// The data subcommand groups: routines and agents.
#[derive(Subcommand)]
pub(crate) enum DataCommand {
/// Manage routines (create/list/get/update/replace/delete/trigger/logs/ical).
///
/// Boxed because `RoutineCmd` (the largest variant by far now that the cron-job
/// subcommand is gone) would otherwise blow up the size of every `DataCommand`
/// value, including the trivial `Agents`/`Schedule` ones (`clippy::large_enum_variant`).
#[command(subcommand, visible_alias = "routine")]
Routines(Box<RoutineCmd>),
/// Trigger a routine on its schedule by ID (invoked by the generated crontab line).
#[command(subcommand, visible_alias = "sched")]
Schedule(ScheduleCmd),
/// Enable a routine (set `enabled = true`) by id or slug.
Enable {
/// Routine id or slug to enable.
routine: String,
/// Emit a machine-readable `{"routine","enabled"}` object instead of a status line.
#[arg(long)]
json: bool,
},
/// Disable a routine (set `enabled = false`) by id or slug.
Disable {
/// Routine id or slug to disable.
routine: String,
/// Optional reason to persist into disabled.json.
#[arg(long)]
reason: Option<String>,
/// Emit a machine-readable `{"routine","enabled"}` object instead of a status line.
#[arg(long)]
json: bool,
},
/// List the available agent registry keys.
Agents,
/// Export every tracked routine, agent, and global config file as one portable JSON bundle.
///
/// Runs offline against the config directory — no daemon needed. Gitignored runtime files
/// (`*.local.*`, `*.pid`, `*.log`, compiled outputs) are excluded, so the bundle is safe to
/// share or commit.
Export {
/// Write the bundle to this file (atomically) instead of stdout.
#[arg(long)]
out: Option<std::path::PathBuf>,
},
/// Restore a bundle produced by `moadim export` into the config directory.
///
/// Runs offline; existing files are skipped unless `--force` is given. Run `moadim restart`
/// afterwards so the imported routines are resynced into the scheduler.
Import {
/// Path of the bundle file to import.
file: std::path::PathBuf,
/// Print what would be created/overwritten/skipped without writing anything.
#[arg(long)]
dry_run: bool,
/// Overwrite files that already exist (default: skip them).
#[arg(long)]
force: bool,
},
/// Create a lock sentinel, halting all routine scheduling and triggers without touching any
/// individual routine's `enabled` state (the "pause everything" escape hatch, #683).
Lock {
/// Which sentinel to create: `shared` (committed `.lock`, shareable via git) or `local`
/// (gitignored `.local.lock`, machine-local).
#[arg(long, default_value = "shared")]
scope: String,
},
/// Remove lock sentinel(s), restoring prior routine scheduling.
Unlock {
/// Which sentinel(s) to remove: `shared`, `local`, or `all` (both, the default).
#[arg(long, default_value = "all")]
scope: String,
},
/// Show the current global lock status (whether the shared and/or local sentinel is present).
LockStatus,
}
/// Schedule operations driven by the OS crontab, keyed only by ID.
#[derive(Subcommand)]
pub(crate) enum ScheduleCmd {
/// Run a routine on its schedule by ID.
///
/// This is what the generated crontab line invokes at each fire time. It records a *scheduled*
/// trigger (not a manual one), so it maps to the routine's `scheduled-trigger` route rather than
/// the manual `trigger` route.
Trigger {
/// UUID of the routine to trigger.
id: String,
},
}
/// Build the full create/replace JSON body for a routine, validating optional `repositories` as a
/// JSON array. Returns the serialized body, or an exit code (`2`) when `repositories` is invalid.
#[allow(
clippy::too_many_arguments,
reason = "all parameters map to distinct CLI flags with no natural grouping"
)]
#[path = "commands_http.rs"]
mod commands_http;
use commands_http::{insert_json_opt, insert_opt, request, tags_value, to_body};
#[path = "commands_routine_actions.rs"]
mod commands_routine_actions;
use commands_routine_actions::set_routine_enabled;
#[path = "routes/move_routine/cli.rs"]
mod move_routine_cli;
use move_routine_cli::move_routine;
#[cfg(test)]
#[path = "commands_tests.rs"]
mod commands_tests;