feat: add explicit workspace init
This commit is contained in:
parent
5002be498f
commit
da631029cb
|
|
@ -1,9 +1,11 @@
|
|||
---
|
||||
title: 'Workspace初期化をinitコマンドに切り出しserveの副作用をなくす'
|
||||
state: 'planning'
|
||||
state: 'closed'
|
||||
created_at: '2026-07-02T07:02:02Z'
|
||||
updated_at: '2026-07-02T08:31:22Z'
|
||||
updated_at: '2026-07-02T09:20:26Z'
|
||||
assignee: null
|
||||
queued_by: 'yoi ticket'
|
||||
queued_at: '2026-07-02T09:03:56Z'
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
|
|
|||
16
.yoi/tickets/00001KWGT4WYB/resolution.md
Normal file
16
.yoi/tickets/00001KWGT4WYB/resolution.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
Implemented explicit Workspace initialization command and removed implicit initialization from serve.
|
||||
|
||||
Delivered:
|
||||
- `yoi workspace init [--workspace <PATH>]`.
|
||||
- `yoi-workspace-server init [--workspace <PATH>]`.
|
||||
- `init` creates `.yoi/workspace.toml` and `.yoi/workspace-backend.default.toml` only.
|
||||
- `serve` requires `.yoi/workspace.toml` to already exist and fails with an init diagnostic otherwise.
|
||||
- `serve` no longer creates workspace identity or default config template.
|
||||
- Help text and focused tests updated.
|
||||
|
||||
Validation:
|
||||
- `cargo test -p yoi-workspace-server`
|
||||
- `cargo test -p yoi`
|
||||
- `cargo check -p yoi`
|
||||
- `git diff --check`
|
||||
- `nix build .#yoi --no-link`
|
||||
|
|
@ -38,4 +38,110 @@ Workspace init Ticket に storage / 正本境界を追記した。
|
|||
- 将来 ProjectRecordBackend / TicketBackend / ObjectiveBackend 相当の provider を差し替える余地を残す。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: intake_summary author: hare at: 2026-07-02T09:03:56Z -->
|
||||
|
||||
## Intake summary
|
||||
|
||||
Marked ready by `yoi ticket state`.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-07-02T09:03:56Z from: planning to: ready reason: cli_state field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Marked ready by `yoi ticket state`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-07-02T09:03:56Z from: ready to: queued reason: queued field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket を `yoi ticket` が queued にしました。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-07-02T09:03:56Z from: queued to: inprogress reason: cli_state field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
State changed to `inprogress`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: hare at: 2026-07-02T09:20:10Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Implemented explicit Workspace init and removed serve-time initialization side effects.
|
||||
|
||||
Changes:
|
||||
- Added `yoi workspace init [--workspace <PATH>]` passthrough to `yoi-workspace-server init`.
|
||||
- Added `yoi-workspace-server init [--workspace <PATH>]`.
|
||||
- `init` creates only `.yoi/workspace.toml` and `.yoi/workspace-backend.default.toml`.
|
||||
- `init` does not create `.local` config, SQLite DB, embedded Runtime store, logs, Worker data, or project record layout.
|
||||
- Added `WorkspaceIdentity::load_required(...)` for serve-time load-only behavior.
|
||||
- `serve` now requires an initialized workspace and no longer calls `load_or_init` or copies the default config template.
|
||||
- Uninitialized `serve` fails with a diagnostic telling the user to run `yoi workspace init --workspace <path>`.
|
||||
- Updated help text for `yoi workspace` and `yoi-workspace-server`.
|
||||
- Added focused tests for init parsing, init output files, load_required no-create behavior, and yoi CLI passthrough.
|
||||
|
||||
Validation:
|
||||
- `cargo test -p yoi-workspace-server`
|
||||
- `cargo test -p yoi`
|
||||
- `cargo check -p yoi`
|
||||
- `git diff --check`
|
||||
- `nix build .#yoi --no-link`
|
||||
- manual smoke: uninitialized `yoi-workspace-server serve` fails without creating identity.
|
||||
- manual smoke: `yoi-workspace-server init` creates only workspace identity and default template.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-07-02T09:20:10Z from: inprogress to: done reason: cli_state field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
State changed to `done`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: hare at: 2026-07-02T09:20:26Z from: done to: closed reason: closed field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket を closed にしました。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: close author: hare at: 2026-07-02T09:20:26Z status: closed -->
|
||||
|
||||
## 完了
|
||||
|
||||
Implemented explicit Workspace initialization command and removed implicit initialization from serve.
|
||||
|
||||
Delivered:
|
||||
- `yoi workspace init [--workspace <PATH>]`.
|
||||
- `yoi-workspace-server init [--workspace <PATH>]`.
|
||||
- `init` creates `.yoi/workspace.toml` and `.yoi/workspace-backend.default.toml` only.
|
||||
- `serve` requires `.yoi/workspace.toml` to already exist and fails with an init diagnostic otherwise.
|
||||
- `serve` no longer creates workspace identity or default config template.
|
||||
- Help text and focused tests updated.
|
||||
|
||||
Validation:
|
||||
- `cargo test -p yoi-workspace-server`
|
||||
- `cargo test -p yoi`
|
||||
- `cargo check -p yoi`
|
||||
- `git diff --check`
|
||||
- `nix build .#yoi --no-link`
|
||||
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -38,6 +38,21 @@ impl WorkspaceIdentity {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn load_required(workspace_root: impl AsRef<Path>) -> Result<Self> {
|
||||
let path = Self::path(workspace_root.as_ref());
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(raw) => Self::parse_str(&raw, &path),
|
||||
Err(error) if error.kind() == ErrorKind::NotFound => {
|
||||
Err(Error::WorkspaceIdentity(format!(
|
||||
"workspace is not initialized at {}; run `yoi workspace init --workspace {}` first",
|
||||
workspace_root.as_ref().display(),
|
||||
workspace_root.as_ref().display()
|
||||
)))
|
||||
}
|
||||
Err(error) => Err(Error::Io(error)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(workspace_root: impl AsRef<Path>) -> PathBuf {
|
||||
workspace_root
|
||||
.as_ref()
|
||||
|
|
@ -192,6 +207,21 @@ mod tests {
|
|||
const FIXED_WORKSPACE_ID: &str = "0192f0e8-4d84-7d6e-a000-000000000001";
|
||||
const FIXED_CREATED_AT: &str = "2026-06-23T06:43:28Z";
|
||||
|
||||
#[test]
|
||||
fn load_required_rejects_uninitialized_workspace_without_creating_identity() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let workspace_root = temp.path().join("uninitialized-workspace");
|
||||
fs::create_dir_all(&workspace_root).unwrap();
|
||||
|
||||
let error = WorkspaceIdentity::load_required(&workspace_root).unwrap_err();
|
||||
|
||||
assert!(
|
||||
error.to_string().contains("workspace is not initialized"),
|
||||
"unexpected error: {error}"
|
||||
);
|
||||
assert!(!WorkspaceIdentity::path(&workspace_root).exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_identity_file_is_created_with_safe_fields() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ use yoi_workspace_server::{
|
|||
SqliteWorkspaceStore, WorkspaceBackendConfigFile, WorkspaceIdentity, serve,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Command {
|
||||
Serve(ServeOptions),
|
||||
Init(InitOptions),
|
||||
Help,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ServeOptions {
|
||||
workspace: PathBuf,
|
||||
|
|
@ -16,6 +23,11 @@ struct ServeOptions {
|
|||
listen: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InitOptions {
|
||||
workspace: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CliError(String);
|
||||
|
||||
|
|
@ -40,34 +52,57 @@ async fn main() -> ExitCode {
|
|||
|
||||
async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = std::env::args().skip(1).collect::<Vec<_>>();
|
||||
let Some((command, rest)) = args.split_first() else {
|
||||
print_help();
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match command.as_str() {
|
||||
"serve" => {
|
||||
if rest.iter().any(|arg| arg == "--help" || arg == "-h") {
|
||||
print_serve_help();
|
||||
return Ok(());
|
||||
}
|
||||
let options = parse_serve_options(rest)?;
|
||||
run_serve(options).await?;
|
||||
Ok(())
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
print_help();
|
||||
Ok(())
|
||||
}
|
||||
other => Err(Box::new(CliError(format!(
|
||||
"unknown command `{other}`; expected `serve`"
|
||||
)))),
|
||||
match parse_command(&args)? {
|
||||
Command::Serve(options) => run_serve(options).await,
|
||||
Command::Init(options) => run_init(options),
|
||||
Command::Help => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_serve(options: ServeOptions) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn parse_command(args: &[String]) -> Result<Command, CliError> {
|
||||
let Some((command, rest)) = args.split_first() else {
|
||||
print_help();
|
||||
return Ok(Command::Help);
|
||||
};
|
||||
|
||||
match command.as_str() {
|
||||
"init" => {
|
||||
if rest.iter().any(|arg| arg == "--help" || arg == "-h") {
|
||||
print_init_help();
|
||||
return Ok(Command::Help);
|
||||
}
|
||||
Ok(Command::Init(parse_init_options(rest)?))
|
||||
}
|
||||
"serve" => {
|
||||
if rest.iter().any(|arg| arg == "--help" || arg == "-h") {
|
||||
print_serve_help();
|
||||
return Ok(Command::Help);
|
||||
}
|
||||
Ok(Command::Serve(parse_serve_options(rest)?))
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
print_help();
|
||||
Ok(Command::Help)
|
||||
}
|
||||
other => Err(CliError(format!(
|
||||
"unknown command `{other}`; expected `init` or `serve`"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_init(options: InitOptions) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let identity = WorkspaceIdentity::load_or_init(&options.workspace)?;
|
||||
WorkspaceBackendConfigFile::ensure_default_template_for_workspace(&options.workspace)?;
|
||||
eprintln!(
|
||||
"yoi-workspace-server: initialized workspace `{}` ({})",
|
||||
options.workspace.display(),
|
||||
identity.workspace_id
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_serve(options: ServeOptions) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let identity = WorkspaceIdentity::load_required(&options.workspace)?;
|
||||
let config_file = WorkspaceBackendConfigFile::load_for_workspace(&options.workspace)?;
|
||||
let mut resolved = config_file.resolve(&options.workspace, identity)?;
|
||||
if let Some(db) = options.db {
|
||||
|
|
@ -95,6 +130,31 @@ async fn run_serve(options: ServeOptions) -> Result<(), Box<dyn std::error::Erro
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_init_options(args: &[String]) -> Result<InitOptions, CliError> {
|
||||
let mut workspace = std::env::current_dir()
|
||||
.map_err(|error| CliError(format!("failed to read current dir: {error}")))?;
|
||||
let mut iter = args.iter();
|
||||
while let Some(arg) = iter.next() {
|
||||
match arg.as_str() {
|
||||
"--workspace" => {
|
||||
let value = iter
|
||||
.next()
|
||||
.ok_or_else(|| CliError("--workspace requires a path".to_string()))?;
|
||||
workspace = PathBuf::from(value);
|
||||
}
|
||||
value if value.starts_with("--workspace=") => {
|
||||
workspace = PathBuf::from(value_after_equals(arg, "--workspace")?);
|
||||
}
|
||||
other => return Err(CliError(format!("unknown init option `{other}`"))),
|
||||
}
|
||||
}
|
||||
|
||||
let workspace = workspace
|
||||
.canonicalize()
|
||||
.map_err(|error| CliError(format!("failed to canonicalize workspace: {error}")))?;
|
||||
Ok(InitOptions { workspace })
|
||||
}
|
||||
|
||||
fn parse_serve_options(args: &[String]) -> Result<ServeOptions, CliError> {
|
||||
let mut workspace = std::env::current_dir()
|
||||
.map_err(|error| CliError(format!("failed to resolve current directory: {error}")))?;
|
||||
|
|
@ -192,12 +252,58 @@ fn parse_listen(value: &str) -> Result<SocketAddr, CliError> {
|
|||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"yoi-workspace-server\n\nUsage:\n yoi-workspace-server serve [OPTIONS]\n\nOptions:\n -h, --help Print help"
|
||||
"yoi-workspace-server\n\nUsage:\n yoi-workspace-server init [OPTIONS]\n yoi-workspace-server serve [OPTIONS]\n\nOptions:\n -h, --help Print help"
|
||||
);
|
||||
}
|
||||
|
||||
fn print_init_help() {
|
||||
println!(
|
||||
"yoi-workspace-server init\n\nUsage:\n yoi-workspace-server init [OPTIONS]\n\nDescription:\n Initializes a Workspace identity and copies the default Backend config template. Does not create Backend data stores.\n\nOptions:\n --workspace <PATH> Workspace root to initialize (defaults to cwd)\n -h, --help Print help"
|
||||
);
|
||||
}
|
||||
|
||||
fn print_serve_help() {
|
||||
println!(
|
||||
"yoi-workspace-server serve\n\nUsage:\n yoi-workspace-server serve [OPTIONS]\n\nOptions:\n --workspace <PATH> Workspace root containing .yoi project records (defaults to cwd)\n --db <PATH> SQLite database path (defaults to <workspace>/.yoi/workspace.db)\n --frontend <PATH> Static SPA build directory to serve\n --listen <ADDR> Listen address (defaults to 127.0.0.1:8787)\n -h, --help Print help"
|
||||
"yoi-workspace-server serve\n\nUsage:\n yoi-workspace-server serve [OPTIONS]\n\nDescription:\n Serves an already initialized Workspace. Run `yoi workspace init` first.\n\nOptions:\n --workspace <PATH> Workspace root containing .yoi project records (defaults to cwd)\n --db <PATH> SQLite database path (legacy dev override)\n --frontend <PATH> Static SPA build directory to serve (legacy dev override)\n --listen <ADDR> Listen address (legacy dev override; default 127.0.0.1:8787)\n -h, --help Print help"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use yoi_workspace_server::{
|
||||
WORKSPACE_BACKEND_DEFAULT_CONFIG_RELATIVE_PATH, WORKSPACE_IDENTITY_RELATIVE_PATH,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn parse_init_defaults_workspace_to_cwd_or_flag() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let args = vec!["--workspace".to_string(), temp.path().display().to_string()];
|
||||
let options = parse_init_options(&args).unwrap();
|
||||
assert_eq!(options.workspace, temp.path().canonicalize().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_creates_identity_and_default_template_only() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
run_init(InitOptions {
|
||||
workspace: temp.path().canonicalize().unwrap(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(temp.path().join(WORKSPACE_IDENTITY_RELATIVE_PATH).exists());
|
||||
assert!(
|
||||
temp.path()
|
||||
.join(WORKSPACE_BACKEND_DEFAULT_CONFIG_RELATIVE_PATH)
|
||||
.exists()
|
||||
);
|
||||
assert!(
|
||||
!temp
|
||||
.path()
|
||||
.join(".yoi/workspace-backend.local.toml")
|
||||
.exists()
|
||||
);
|
||||
assert!(!temp.path().join(".yoi/workspace.db").exists());
|
||||
assert!(!temp.path().join(".yoi/embedded-runtime").exists());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ enum Mode {
|
|||
WorkerCleanup(worker_cleanup_cli::WorkerCleanupCli),
|
||||
Ticket(ticket_cli::TicketCli),
|
||||
WorkspaceHelp,
|
||||
WorkspaceServe(Vec<String>),
|
||||
WorkspaceServer {
|
||||
subcommand: String,
|
||||
args: Vec<String>,
|
||||
},
|
||||
WorkerRuntime(Vec<String>),
|
||||
Keys,
|
||||
SetupModel,
|
||||
|
|
@ -78,7 +81,7 @@ async fn main() -> ExitCode {
|
|||
print_workspace_help();
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Mode::WorkspaceServe(args) => run_workspace_server(args),
|
||||
Mode::WorkspaceServer { subcommand, args } => run_workspace_server(&subcommand, args),
|
||||
Mode::MemoryLint(options) => match memory_lint::run(&options) {
|
||||
Ok(LintStatus::Clean) => ExitCode::SUCCESS,
|
||||
Ok(LintStatus::Failed) => ExitCode::FAILURE,
|
||||
|
|
@ -607,24 +610,36 @@ fn current_dir() -> Result<PathBuf, ParseError> {
|
|||
fn parse_workspace_args(args: &[String]) -> Result<Mode, ParseError> {
|
||||
let Some((subcommand, rest)) = args.split_first() else {
|
||||
return Err(ParseError(
|
||||
"yoi workspace requires `serve` (try `yoi workspace --help`)".to_string(),
|
||||
"yoi workspace requires `init` or `serve` (try `yoi workspace --help`)".to_string(),
|
||||
));
|
||||
};
|
||||
match subcommand.as_str() {
|
||||
"init" => {
|
||||
if rest.iter().any(|arg| arg == "--help" || arg == "-h") {
|
||||
return Ok(Mode::WorkspaceHelp);
|
||||
}
|
||||
Ok(Mode::WorkspaceServer {
|
||||
subcommand: "init".to_string(),
|
||||
args: rest.to_vec(),
|
||||
})
|
||||
}
|
||||
"serve" => {
|
||||
if rest.iter().any(|arg| arg == "--help" || arg == "-h") {
|
||||
return Ok(Mode::WorkspaceHelp);
|
||||
}
|
||||
Ok(Mode::WorkspaceServe(rest.to_vec()))
|
||||
Ok(Mode::WorkspaceServer {
|
||||
subcommand: "serve".to_string(),
|
||||
args: rest.to_vec(),
|
||||
})
|
||||
}
|
||||
"--help" | "-h" => Ok(Mode::WorkspaceHelp),
|
||||
other => Err(ParseError(format!(
|
||||
"unknown yoi workspace subcommand `{other}`"
|
||||
"unknown yoi workspace subcommand `{other}`; expected `init` or `serve`"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_workspace_server(args: Vec<String>) -> ExitCode {
|
||||
fn run_workspace_server(subcommand: &str, args: Vec<String>) -> ExitCode {
|
||||
let command = match resolve_workspace_server_command() {
|
||||
Ok(command) => command,
|
||||
Err(error) => {
|
||||
|
|
@ -634,7 +649,7 @@ fn run_workspace_server(args: Vec<String>) -> ExitCode {
|
|||
};
|
||||
|
||||
let mut child = Command::new(&command);
|
||||
child.arg("serve");
|
||||
child.arg(subcommand);
|
||||
child.args(args);
|
||||
match child.status() {
|
||||
Ok(status) if status.success() => ExitCode::SUCCESS,
|
||||
|
|
@ -999,13 +1014,14 @@ fn parse_session_id(value: &str) -> Result<SegmentId, ParseError> {
|
|||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"yoi\n\nUsage:\n yoi [OPTIONS]\n yoi resume [--workspace <PATH>] [--all]\n yoi panel [--workspace <PATH>]\n yoi keys\n yoi setup-model\n yoi worker [WORKER_OPTIONS]\n yoi worker delete <NAME> [--force] [--dry-run]\n yoi worker prune --older-than <DURATION> [--force] [--dry-run]\n yoi objective <COMMAND> [OPTIONS]\n yoi session analyze <SESSION_JSONL_PATH> --json\n yoi session prune --unreferenced [--older-than <DURATION>] [--force] [--dry-run]\n yoi ticket <COMMAND> [OPTIONS]\n yoi workspace serve [OPTIONS]\n yoi plugin new <rust-component-tool|rust-component-service> <PATH> [--json]\n yoi plugin check <PATH_OR_PACKAGE> [--json]\n yoi plugin pack <PATH> [--output <FILE>] [--json]\n yoi plugin list [--workspace <PATH>] [--profile <REF>] [--json]\n yoi plugin show <REF> [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp list [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp show <SERVER> [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp tools|resources|prompts [SERVER] [--workspace <PATH>] [--profile <REF>] [--json]\n yoi memory lint [OPTIONS]\n\nSurfaces:\n Console Single-Worker chat/client surface (default, --worker, yoi resume, Backend Runtime target)\n Dashboard Workspace cockpit/action surface (yoi panel)\n TUI Terminal UI implementation umbrella for Console and Dashboard\n\nOptions:\n --workspace <PATH> Runtime workspace root for default Console/--worker (defaults to cwd)\n --worker <NAME> Open the Worker Console by name (attach/restore/create)\n --socket <PATH> Attach a Worker Console to a specific socket with --worker\n --session <UUID> Resume a specific session segment in the Worker Console\n --profile <REF> Select a reusable Profile recipe\n -h, --help Print help\n"
|
||||
"yoi\n\nUsage:\n yoi [OPTIONS]\n yoi resume [--workspace <PATH>] [--all]\n yoi panel [--workspace <PATH>]\n yoi keys\n yoi setup-model\n yoi worker [WORKER_OPTIONS]\n yoi worker delete <NAME> [--force] [--dry-run]\n yoi worker prune --older-than <DURATION> [--force] [--dry-run]\n yoi objective <COMMAND> [OPTIONS]\n yoi session analyze <SESSION_JSONL_PATH> --json\n yoi session prune --unreferenced [--older-than <DURATION>] [--force] [--dry-run]\n yoi ticket <COMMAND> [OPTIONS]\n yoi workspace init [OPTIONS]
|
||||
yoi workspace serve [OPTIONS]\n yoi plugin new <rust-component-tool|rust-component-service> <PATH> [--json]\n yoi plugin check <PATH_OR_PACKAGE> [--json]\n yoi plugin pack <PATH> [--output <FILE>] [--json]\n yoi plugin list [--workspace <PATH>] [--profile <REF>] [--json]\n yoi plugin show <REF> [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp list [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp show <SERVER> [--workspace <PATH>] [--profile <REF>] [--json]\n yoi mcp tools|resources|prompts [SERVER] [--workspace <PATH>] [--profile <REF>] [--json]\n yoi memory lint [OPTIONS]\n\nSurfaces:\n Console Single-Worker chat/client surface (default, --worker, yoi resume, Backend Runtime target)\n Dashboard Workspace cockpit/action surface (yoi panel)\n TUI Terminal UI implementation umbrella for Console and Dashboard\n\nOptions:\n --workspace <PATH> Runtime workspace root for default Console/--worker (defaults to cwd)\n --worker <NAME> Open the Worker Console by name (attach/restore/create)\n --socket <PATH> Attach a Worker Console to a specific socket with --worker\n --session <UUID> Resume a specific session segment in the Worker Console\n --profile <REF> Select a reusable Profile recipe\n -h, --help Print help\n"
|
||||
);
|
||||
}
|
||||
|
||||
fn print_workspace_help() {
|
||||
println!(
|
||||
"yoi workspace\n\nUsage:\n yoi workspace serve [OPTIONS]\n\nDescription:\n Launches the separate yoi-workspace-server executable. The yoi binary does not link the workspace server crate.\n\nOptions forwarded to yoi-workspace-server serve:\n --workspace <PATH> Workspace root containing .yoi project records (defaults to cwd)\n --db <PATH> SQLite database path (defaults to <workspace>/.yoi/workspace.db)\n --frontend <PATH> Static SPA build directory to serve\n --listen <ADDR> Listen address (defaults to 127.0.0.1:8787)\n -h, --help Print help\n\nEnvironment:\n YOI_WORKSPACE_SERVER_COMMAND Path to yoi-workspace-server executable override\n"
|
||||
"yoi workspace\n\nUsage:\n yoi workspace init [OPTIONS]\n yoi workspace serve [OPTIONS]\n\nDescription:\n Launches the separate yoi-workspace-server executable. The yoi binary does not link the workspace server crate.\n\nSubcommands:\n init Initialize .yoi/workspace.toml and the default Backend config template\n serve Serve an already initialized Workspace\n\nOptions forwarded to init/serve:\n --workspace <PATH> Workspace root (defaults to cwd)\n\nLegacy dev options forwarded to serve:\n --db <PATH> SQLite database path override\n --frontend <PATH> Static SPA build directory to serve\n --listen <ADDR> Listen address override\n -h, --help Print help\n\nEnvironment:\n YOI_WORKSPACE_SERVER_COMMAND Path to yoi-workspace-server executable override\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1209,7 +1225,21 @@ mod tests {
|
|||
#[test]
|
||||
fn parse_workspace_serve_passthrough() {
|
||||
match parse_args_from(["workspace", "serve", "--listen", "127.0.0.1:0"]).unwrap() {
|
||||
Mode::WorkspaceServe(args) => assert_eq!(args, vec!["--listen", "127.0.0.1:0"]),
|
||||
Mode::WorkspaceServer { subcommand, args } => {
|
||||
assert_eq!(subcommand, "serve");
|
||||
assert_eq!(args, vec!["--listen", "127.0.0.1:0"]);
|
||||
}
|
||||
other => panic!("unexpected mode: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_workspace_init_passthrough() {
|
||||
match parse_args_from(["workspace", "init", "--workspace", "/tmp/ws"]).unwrap() {
|
||||
Mode::WorkspaceServer { subcommand, args } => {
|
||||
assert_eq!(subcommand, "init");
|
||||
assert_eq!(args, vec!["--workspace", "/tmp/ws"]);
|
||||
}
|
||||
other => panic!("unexpected mode: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user