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
6 changes: 3 additions & 3 deletions .github/workflows/codra-cli-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
publish:
description: Publish @codra/cli to npm (requires NPM_TOKEN secret)
description: Publish @talocode/codra to npm (requires NPM_TOKEN secret)
type: boolean
default: false
include_darwin_x64:
Expand Down Expand Up @@ -108,7 +108,7 @@ jobs:
if-no-files-found: error

package-npm:
name: Package @codra/cli npm tarball
name: Package @talocode/codra npm tarball
needs: [resolve-matrix, build-binaries]
runs-on: ubuntu-latest

Expand Down Expand Up @@ -172,7 +172,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: codra-cli-npm-tarball
path: packages/codra-npm-cli/codra-cli-*.tgz
path: packages/codra-npm-cli/talocode-codra-*.tgz
if-no-files-found: error

- name: Publish to npm (guarded)
Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Changelog

## 0.1.4

### Fixed

- Fixed npm executable metadata so npm preserves the `codra` binary mapping during publish.
- Removed reliance on stale globally installed binaries during validation.

## 0.1.3

### Fixed

- Fixed npm `bin` metadata so `npx @talocode/codra` executes the packaged Codra binary.
- Prevented npx from falling back to stale globally installed binaries.

## 0.1.2

### Fixed

- Fixed the npm package runtime version mismatch so `codra --version` matches the published package version.
- Added validation to prevent stale bundled native binaries from being published.

## 0.1.1

- Added `codra understand` repo scanning and local knowledge-graph output.
- Wrote `.codra/graph/knowledge-graph.json` and `.codra/graph/summary.md`.
- Added repository signal detection for package manager, languages, frameworks, scripts, docs, tests, and routes.
- Documented the canonical npm package path as `packages/codra-npm-cli`.
- Moved the stale duplicate `packages/codra-cli` out of the workspace before the release work.

## 0.1.0

- First terminal release of Codra as `@talocode/codra`.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ See [crates/codra-cli/README.md](crates/codra-cli/README.md).

## Installable CLI roadmap

The `@codra/cli` package is publish-ready work in progress and is not published to npm yet. Once maintainers verify the release tarball includes every selected platform binary, users will be able to install Codra globally:
The `@talocode/codra` package is publish-ready work in progress and is not published to npm yet. Once maintainers verify the release tarball includes every selected platform binary, users will be able to install Codra globally:

```bash
pnpm add -g @codra/cli
npm install -g @codra/cli
pnpm add -g @talocode/codra
npm install -g @talocode/codra
codra --help
codra
```
Expand All @@ -135,7 +135,7 @@ cargo run -p codra-cli -- init
cargo run -p codra-cli -- run --task summarize-context --jsonl
```

The [`@codra/cli`](packages/codra-npm-cli/) package is a thin Node wrapper that spawns the native `codra` binary built from `codra-cli`. Multi-platform npm distribution targets Linux, macOS, and Windows; the manual [release workflow](.github/workflows/codra-cli-release.yml) packages platform binaries before guarded publish. npm publish remains off by default and must not run until tarball contents are verified (see [packages/codra-npm-cli/README.md](packages/codra-npm-cli/README.md)).
The [`@talocode/codra`](packages/codra-npm-cli/) package is a thin Node wrapper that spawns the native `codra` binary built from `codra-cli`. Multi-platform npm distribution targets Linux, macOS, and Windows; the manual [release workflow](.github/workflows/codra-cli-release.yml) packages platform binaries before guarded publish. npm publish remains off by default and must not run until tarball contents are verified (see [packages/codra-npm-cli/README.md](packages/codra-npm-cli/README.md)).

## Roadmap

Expand Down
4 changes: 3 additions & 1 deletion crates/codra-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "codra-cli"
version = "0.1.0"
version = "0.1.4"
edition = "2021"

[lib]
Expand All @@ -23,3 +23,5 @@ codra-memory = { path = "../codra-memory" }
chrono = { workspace = true }
reqwest = { workspace = true, features = ["blocking"] }
tokio = { workspace = true }
crossterm = "0.28"
ratatui = "0.29"
Comment on lines +26 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Commit Cargo.lock for new CLI dependencies

These dependencies were added without updating Cargo.lock; the reviewed lockfile still has no crossterm or ratatui package entries. Since cargo build --help defines --locked as asserting that Cargo.lock remains unchanged, any locked/reproducible build of this repo will fail before compilation until the regenerated lockfile is committed with the manifest change.

Useful? React with 👍 / 👎.

4 changes: 4 additions & 0 deletions crates/codra-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ fn main() {
let result = match command {
"" => welcome(),
"--help" | "-h" | "help" => help(),
"--version" | "-V" | "version" => {
println!("codra {}", env!("CARGO_PKG_VERSION"));
Ok(())
},
"init" => {
args.remove(0);
execute_init(&args)
Expand Down
47 changes: 47 additions & 0 deletions crates/codra-cli/tests/deploy_command_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,53 @@ fn cli_verify_requires_url() {
assert!(result.unwrap_err().contains("URL is required"));
}

#[test]
fn cli_status_returns_empty_registry_for_workspace_without_deployments() {
let dir = temp_dir();

let result = execute_deploy_command(&[
"status".to_string(),
"--workspace".to_string(),
dir.display().to_string(),
"--json".to_string(),
]);

assert!(result.is_ok());
}

#[test]
fn cli_status_lists_saved_registry_services() {
let dir = temp_dir();
let timestamp = "2026-06-11T00:00:00Z".to_string();
let mut registry = codra_deploy::empty_services_registry();
registry.updated_at = timestamp.clone();
registry.services.push(codra_deploy::ServiceRecord {
service_name: "web".to_string(),
project: "launchpix".to_string(),
container_name: "codra-launchpix-web".to_string(),
image: "codra-launchpix-web:latest".to_string(),
host_port: Some(3000),
internal_port: Some(3000),
current_deploy_id: Some("deploy_test_001".to_string()),
status: codra_deploy::DeploymentServiceStatus::Running,
health_check_path: Some("/api/health".to_string()),
health_check_url: Some("http://localhost:3000/api/health".to_string()),
created_at: timestamp.clone(),
updated_at: timestamp,
});
codra_deploy::save_services_registry(&dir, &registry).expect("seed registry");

let result = execute_deploy_command(&[
"status".to_string(),
"--workspace".to_string(),
dir.display().to_string(),
"--service".to_string(),
"web".to_string(),
]);

assert!(result.is_ok());
}

#[test]
fn cli_logs_resolves_expected_container_name() {
let dir = temp_dir();
Expand Down
4 changes: 4 additions & 0 deletions crates/codra-deploy/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod logs;
mod plan;
mod status;
mod up;
pub mod verify;

Expand All @@ -14,6 +15,7 @@ pub fn execute_deploy(args: &[String]) -> Result<(), String> {
"plan" => plan::execute_plan(&args[1..]),
"up" => up::execute_up(&args[1..]),
"logs" => logs::execute_logs(&args[1..]),
"status" => status::execute_status(&args[1..]),
"verify" => verify::execute_verify(&args[1..]),
"" | "--help" | "-h" => {
print_deploy_help();
Expand All @@ -34,12 +36,14 @@ fn print_deploy_help() {
println!(" plan Validate and render a safe deployment plan");
println!(" up Prepare or execute a local Docker deployment");
println!(" logs Show logs for a deployed service container");
println!(" status Show deployed services from the local deployment registry");
println!(" verify Run Agent Browser smoke check against a deployed URL");
println!();
println!("Examples:");
println!(" codra deploy plan --config codra.deploy.json");
println!(" codra deploy up --dry-run");
println!(" CODRA_DEPLOY_ENABLE_EXECUTE=1 codra deploy up --execute --service web");
println!(" codra deploy logs --service web --tail 100");
println!(" codra deploy status --workspace . --json");
println!(" codra deploy verify https://example.com --json");
}
141 changes: 141 additions & 0 deletions crates/codra-deploy/src/cli/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use crate::registry::{list_services, ServiceRecord};
use crate::cli::DeployOutputFormat;
use serde::Serialize;
use std::env;
use std::path::PathBuf;

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct DeployStatusOutput {
workspace: String,
registry_exists: bool,
services: Vec<ServiceRecord>,
}

pub fn execute_status(args: &[String]) -> Result<(), String> {
if args.iter().any(|arg| arg == "--help" || arg == "-h") {
print_status_help();
return Ok(());
}

let (format, workspace, service_filter) = parse_args(args)?;
let registry_path = crate::registry::services_registry_path(&workspace);
let registry_exists = registry_path.exists();
let mut services = list_services(&workspace).map_err(|err| err.to_string())?;

if let Some(service_name) = service_filter {
services.retain(|service| service.service_name == service_name);
}

services.sort_by(|left, right| {
left.project
.cmp(&right.project)
.then_with(|| left.service_name.cmp(&right.service_name))
});

match format {
DeployOutputFormat::Human => print_human(&workspace, registry_exists, &services),
DeployOutputFormat::Json => {
let body = serde_json::to_string_pretty(&DeployStatusOutput {
workspace: workspace.display().to_string(),
registry_exists,
services,
})
.map_err(|err| err.to_string())?;
println!("{body}");
}
}

Ok(())
}

fn parse_args(args: &[String]) -> Result<(DeployOutputFormat, PathBuf, Option<String>), String> {
let mut format = DeployOutputFormat::Human;
let mut workspace = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let mut service_filter = None;
let mut iter = args.iter().peekable();

while let Some(arg) = iter.next() {
match arg.as_str() {
"--json" => format = DeployOutputFormat::Json,
"--workspace" => {
let value = iter
.next()
.ok_or_else(|| "missing value for --workspace".to_string())?;
workspace = PathBuf::from(value);
}
"--service" => {
let value = iter
.next()
.ok_or_else(|| "missing value for --service".to_string())?;
service_filter = Some(value.to_string());
}
flag if flag.starts_with("--") => return Err(format!("unknown flag: {flag}")),
other => return Err(format!("unexpected argument: {other}")),
}
}

Ok((format, workspace, service_filter))
}

fn print_human(workspace: &PathBuf, registry_exists: bool, services: &[ServiceRecord]) {
println!("Codra Deploy Status");
println!();
println!("Workspace: {}", workspace.display());
println!("Registry: {}", crate::registry::services_registry_path(workspace).display());
println!("Registry exists: {}", if registry_exists { "yes" } else { "no" });
println!("Services: {}", services.len());
println!();

if services.is_empty() {
println!("No deployed services recorded yet.");
println!("Run `codra deploy up --execute` to create the first deployment record in a later PR.");
return;
}

for service in services {
println!("{} ({})", service.service_name, service.project);
println!(" Status: {:?}", service.status);
println!(" Container: {}", service.container_name);
println!(" Image: {}", service.image);
if let Some(host_port) = service.host_port {
println!(" Host port: {host_port}");
}
if let Some(deploy_id) = &service.current_deploy_id {
println!(" Current deploy: {deploy_id}");
}
if let Some(url) = &service.health_check_url {
println!(" Health URL: {url}");
} else if let Some(path) = &service.health_check_path {
println!(" Health path: {path}");
}
println!(" Updated: {}", service.updated_at);
println!();
}
}

fn print_status_help() {
println!("codra deploy status [--workspace <path>] [--service <name>] [--json]");
println!(" Show deployed services from the local .codra/deployments registry.");
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse_status_args_reads_workspace_service_and_json() {
let parsed = parse_args(&[
"--workspace".to_string(),
"/tmp/workspace".to_string(),
"--service".to_string(),
"web".to_string(),
"--json".to_string(),
])
.expect("parse status args");

assert_eq!(parsed.0, DeployOutputFormat::Json);
assert_eq!(parsed.1, PathBuf::from("/tmp/workspace"));
assert_eq!(parsed.2, Some("web".to_string()));
}
}
8 changes: 8 additions & 0 deletions crates/codra-deploy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod docker_availability;
mod docker_exec;
mod naming;
mod plan;
pub mod registry;
mod runner;
mod validator;

Expand Down Expand Up @@ -37,6 +38,13 @@ pub use naming::{
pub use plan::{
generate_plan, DeployPlan, DeployPlanService, DeployPlanUnsupportedFeature, DeployPlanWarning,
};
pub use registry::{
deploy_record_path, deployment_registry_root, empty_services_registry, list_services,
load_deploy_record, load_services_registry, save_deploy_record, save_services_registry,
services_registry_path, upsert_service_record, DeployRecord, DeployRecordStatus,
DeploymentServiceStatus, RegistryError, ServiceRecord, ServicesRegistry, DEPLOYMENTS_DIR,
REGISTRY_VERSION,
};
pub use runner::{
ExecuteResult, LocalDockerRunner, LogsPlan, LogsStatus, RuntimeMode, RuntimePlan,
RuntimeServicePlan, RuntimeStatus, RuntimeWarning, SkippedService,
Expand Down
Loading
Loading