Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,12 @@ Pin a version or change the directory with `CONSTRUCT_VERSION=v0.2.0` /
### 3. Start the daemon

```sh
constructd
construct daemon run
```

Leave this running. It owns sessions, persists state, and exposes the local IPC
socket used by clients.
socket used by clients. (`constructd` is a back-compat alias for the same
daemon — `constructd run` and `construct daemon run` are equivalent.)

### 4. Open the fleet TUI

Expand Down Expand Up @@ -114,8 +115,8 @@ cargo build --workspace

Debug binaries land in `target/debug/`:

- `target/debug/constructd` — daemon / session supervisor
- `target/debug/construct` — TUI and control CLI
- `target/debug/construct` — TUI, control CLI, **and the daemon** (`construct daemon run`)
- `target/debug/constructd` — standalone daemon binary; a thin back-compat alias for `construct daemon run`
- `target/debug/construct-mcp` — MCP bridge for agents
- `target/debug/construct-adapter-*` — harness adapters

Expand Down
4 changes: 4 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ path = "src/main.rs"
tempfile = "3"
agentd-protocol = { workspace = true }
agentd-client = { workspace = true }
# The daemon, as a library, so the unified `construct` binary can run it
# directly via the `daemon` subcommand (the standalone `constructd` binary is
# a thin shim over the same crate, kept as a back-compat alias).
agentd = { path = "../daemon" }
tokio = { workspace = true, features = ["full"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
Expand Down
54 changes: 51 additions & 3 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use agentd_protocol::paths::Paths;
#[derive(Debug, Parser)]
#[command(
name = "construct",
about = "construct: TUI client for constructd",
about = "construct: TUI client and daemon for the agent fleet",
version
)]
struct Cli {
Expand All @@ -32,6 +32,16 @@ struct Cli {
enum Command {
/// Launch the TUI (default).
Tui,
/// Run the construct daemon (session supervisor + IPC server).
///
/// This is the same daemon shipped as the standalone `constructd`
/// binary — `construct daemon run` and `constructd run` are
/// equivalent. One installed `construct` binary can run both the
/// client and the daemon.
Daemon {
#[command(subcommand)]
command: Option<DaemonCommand>,
},
/// Print resolved paths.
Paths,
/// Ping the daemon.
Expand Down Expand Up @@ -113,6 +123,18 @@ enum Command {
},
}

/// Subcommands of `construct daemon`. Mirrors the standalone `constructd`
/// binary's CLI so the two entry points behave identically.
#[derive(Debug, Subcommand)]
enum DaemonCommand {
/// Run the daemon in the foreground (default).
Run,
/// Print resolved paths and exit.
Paths,
/// Print the embedded default config and exit.
DefaultConfig,
}

fn init_tracing() {
use tracing_subscriber::{fmt, EnvFilter};
let filter = EnvFilter::try_from_default_env()
Expand All @@ -123,12 +145,38 @@ fn init_tracing() {

#[tokio::main]
async fn main() -> Result<()> {
init_tracing();
let cli = Cli::parse();
let command = cli.command.unwrap_or(Command::Tui);

// Daemon mode runs the supervisor in-process via the shared `agentd`
// library — the same code path as the standalone `constructd` binary.
// Handled before the client tracing init so the daemon's verbose filter
// applies, and before socket discovery (the daemon *owns* the socket
// rather than connecting to it). The daemon's restart self-`exec()`
// replays this argv (`construct daemon run …`) verbatim, so picking up an
// upgraded binary keeps working.
if let Command::Daemon { command: daemon_cmd } = command {
agentd::init_tracing();
return match daemon_cmd.unwrap_or(DaemonCommand::Run) {
DaemonCommand::Run => agentd::run(cli.socket).await,
DaemonCommand::Paths => {
agentd::print_paths();
Ok(())
}
DaemonCommand::DefaultConfig => {
println!("{}", agentd::DEFAULT_CONFIG_TOML);
Ok(())
}
};
}

init_tracing();
let socket = cli.socket.unwrap_or_else(|| Paths::discover().socket());

match cli.command.unwrap_or(Command::Tui) {
match command {
Command::Tui => run_tui(socket).await,
// Handled above (early return); listed for match exhaustiveness.
Command::Daemon { .. } => unreachable!("daemon mode handled before this match"),
Command::Paths => {
let p = Paths::discover();
println!("config: {}", p.config_dir.display());
Expand Down
Loading
Loading