Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,14 @@ When working with MooseStack data models, ClickHouse schemas, queries, or config
Each rule includes MooseStack TypeScript/Python examples. When reviewing or implementing ClickHouse-related code, read relevant rule files and cite specific rules in your guidance.

To install the skill: `514 agent init`

## Learned User Preferences

- Promote flag-first `moose init` in docs and examples: `moose init --name <name> --template <template>`; treat legacy `moose init <name> [template]` as backward-compatible only, not the primary or help-example style.
- For `moose init --help`, show flag-based examples in the main promoted/after-help text; do not present positional forms as the recommended invocations even when the parser still accepts them.
- For `moose dev` failures, show `moose dev --dockerless` when the runtime is actually unavailable; do not suggest dockerless for unrelated infra failures (avoid error-text matching that fires on timeout or generic troubleshooting copy).
- When improving agent success on init, account for agents copying `moose harness init` patterns (e.g. `--template=...`) onto plain `moose init`—keep CLI, help, and docs aligned on flag syntax and valid template slugs.

## Learned Workspace Facts

- `moose init` is more discoverable at the top level than `moose harness init`, but harness init is the fuller agent setup path; documentation and help should make the two consistent on flags and template discovery (`moose template list`, `moose template list --json`).
2 changes: 1 addition & 1 deletion apps/framework-cli-e2e/test/seed-filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ describe("moose seed clickhouse with seedFilter", function () {
// 1. Init project from play.clickhouse.com (git_clickhouse database - only 3 tables)
testLogger.info("Initializing project from play.clickhouse.com...");
const initResult = await execAsync(
`"${CLI_PATH}" init test-seed-filter typescript-empty --from-remote "${REMOTE_HTTPS_URL}" --location "${testProjectDir}"`,
`"${CLI_PATH}" init --name test-seed-filter --template typescript-empty --from-remote "${REMOTE_HTTPS_URL}" --location "${testProjectDir}"`,
);
testLogger.debug("Init output:", initResult.stdout);

Expand Down
4 changes: 2 additions & 2 deletions apps/framework-cli-e2e/test/utils/project-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const setupTypeScriptProject = async (
log.info(`Initializing TypeScript project with ${templateName} template`);
try {
const result = await execAsync(
`"${cliPath}" init ${appName} ${templateName} --location "${projectDir}"`,
`"${cliPath}" init --name ${appName} --template ${templateName} --location "${projectDir}"`,
{ env },
);
log.debug("CLI init stdout", { stdout: result.stdout });
Expand Down Expand Up @@ -166,7 +166,7 @@ export const setupPythonProject = async (
log.info(`Initializing Python project with ${templateName} template`);
try {
const result = await execAsync(
`"${cliPath}" init ${appName} ${templateName} --location "${projectDir}"`,
`"${cliPath}" init --name ${appName} --template ${templateName} --location "${projectDir}"`,
{ env },
);
log.debug("CLI init stdout", { stdout: result.stdout });
Expand Down
26 changes: 19 additions & 7 deletions apps/framework-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,19 +484,31 @@ pub async fn top_command_handler(
match commands {
Commands::Init {
name,
location,
name_option,
template,
template_option,
location,
no_fail_already_exists,
from_remote,
custom_dockerfile,
} => {
let project_name = name_option.as_deref().or(name.as_deref()).ok_or_else(|| {
RoutineFailure::error(Message {
action: "Init".to_string(),
details:
"Project name is required (use --name <NAME> or the legacy positional)."
.to_string(),
})
})?;
let template_from_args = template_option.as_ref().or(template.as_ref());

info!(
"Running init command with name: {}, location: {:?}, template: {:?}, custom_dockerfile: {}",
name, location, template, custom_dockerfile
project_name, location, template_from_args, custom_dockerfile
);

// Determine template, prompting when needed.
let template = match template {
let template = match template_from_args {
Some(t) => t.to_lowercase(),
None => {
display::show_message_wrapper(
Expand All @@ -510,21 +522,21 @@ pub async fn top_command_handler(
}
};

let dir_path = Path::new(location.as_deref().unwrap_or(name));
let dir_path = Path::new(location.as_deref().unwrap_or(project_name));

let capture_handle = crate::utilities::capture::capture_usage(
ActivityType::InitTemplateCommand,
Some(name.to_string()),
Some(project_name.to_string()),
&settings,
machine_id.clone(),
HashMap::from([("template".to_string(), template.to_string())]),
);

check_project_name(name)?;
check_project_name(project_name)?;

let project_outcome = initialize_project(&ProjectInitOptions {
template: &template,
project_name: name,
project_name,
dir_path,
no_fail_already_exists: *no_fail_already_exists,
custom_dockerfile: *custom_dockerfile,
Expand Down
54 changes: 50 additions & 4 deletions apps/framework-cli/src/cli/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,66 @@

use std::path::PathBuf;

use clap::{Args, Subcommand};
use clap::{ArgGroup, Args, Subcommand};

const MOOSE_INIT_AFTER_LONG_HELP: &str = "Examples (preferred):
moose init --name my-app
moose init --name my-app --template typescript
moose init --name my-app --template=typescript
moose init --name my-app --template typescript --location ./sandbox
moose init --name my-project --template typescript-empty --from-remote <CONNECTION-STRING>
moose init --name my-project --template python-empty --from-remote

Legacy positional name and template arguments are still accepted for backward compatibility but are
hidden from this help output; prefer --name and --template in scripts and agent workflows.

Template catalog:
moose template list
moose template list --json

Arg-driven init is non-interactive when --template is provided, or when stdin is not a terminal:
omitted --template in other cases will prompt to select a template (TTY only).";

#[derive(Subcommand)]
pub enum Commands {
// Initializes the developer environment with all the necessary directories including temporary ones for data storage
/// Initialize a new project
#[command(visible_alias = "i")]
#[command(
visible_alias = "i",
after_long_help = MOOSE_INIT_AFTER_LONG_HELP,
group(
ArgGroup::new("init_project_name")
.id("init_project_name")
.args(["name", "name_option"])
.required(true)
),
group(
ArgGroup::new("init_template_input")
.id("init_template_input")
.args(["template", "template_option"])
)
)]
Init {
/// [Deprecated] Use `--name` instead. Hidden from help; still parsed for compatibility.
#[arg(hide = true)]
name: Option<String>,

/// Name of your app or service
name: String,
#[arg(long = "name", value_name = "NAME", conflicts_with = "name")]
name_option: Option<String>,

/// Template to use for the project
/// [Deprecated] Use `--template` instead. Hidden from help; still parsed for compatibility.
#[arg(hide = true, conflicts_with = "template_option")]
template: Option<String>,

/// Template to use (run `moose template list` to see the catalog). Omit to select interactively (TTY only)
#[arg(
long = "template",
value_name = "TEMPLATE",
conflicts_with = "template"
)]
template_option: Option<String>,

/// Location of your app or service
#[arg(short, long)]
location: Option<String>,
Expand Down
2 changes: 1 addition & 1 deletion apps/framework-cli/src/cli/routines/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ pub async fn get_template_config(
return Err(RoutineFailure::error(Message {
action: "Template".to_string(),
details: format!(
"Template '{}' not found. Available templates:\n{}\n\nLooking for a full example app? Check https://github.com/514-labs/moosestack/tree/main/examples",
"Template '{}' not found. Available templates:\n{}\n\nList templates (including machine-readable JSON for agents and scripts):\n moose template list\n moose template list --json\n\nLooking for a full example app? Check https://github.com/514-labs/moosestack/tree/main/examples",
template_name,
available_templates.join("\n")
),
Expand Down
104 changes: 103 additions & 1 deletion apps/framework-cli/tests/cli_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,109 @@ fn init_help_does_not_list_language_flag() -> Result<(), Box<dyn std::error::Err
.assert()
.success()
.stdout(predicate::str::contains("--language").not())
.stdout(predicate::str::contains("[TEMPLATE]"));
.stdout(predicate::str::contains("[TEMPLATE]").not())
.stdout(predicate::str::contains("Examples (preferred):"))
.stdout(predicate::str::contains(
"--name my-app --template typescript",
));

Ok(())
}

#[test]
#[serial_test::serial(init)]
fn can_run_cli_init_with_flag_name_and_template() -> Result<(), Box<dyn std::error::Error>> {
ensure_test_environment();

let temp = assert_fs::TempDir::new().unwrap();
std::fs::remove_dir(&temp)?;
let dir: &str = temp.path().to_str().unwrap();

let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("moose-cli"));

cmd.arg("init")
.arg("--name")
.arg("flag-app")
.arg("--template")
.arg("typescript")
.arg("-l")
.arg(dir);

cmd.assert().success();

temp.child("package.json").assert(predicate::path::exists());
temp.child("app").assert(predicate::path::exists());
temp.child("moose.config.toml")
.assert(predicate::path::exists());

Ok(())
}

#[test]
#[serial_test::serial(init)]
fn can_run_cli_init_with_equals_style_template_flag() -> Result<(), Box<dyn std::error::Error>> {
ensure_test_environment();

let temp = assert_fs::TempDir::new().unwrap();
std::fs::remove_dir(&temp)?;
let dir: &str = temp.path().to_str().unwrap();

let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("moose-cli"));

cmd.arg("init")
.arg("equals-app")
.arg("--template=typescript")
.arg("-l")
.arg(dir);
Comment thread
okane16 marked this conversation as resolved.

cmd.assert().success();

temp.child("package.json").assert(predicate::path::exists());

Ok(())
Comment thread
okane16 marked this conversation as resolved.
}

#[test]
#[serial_test::serial(init)]
fn init_rejects_positional_and_flag_template_together() -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("moose-cli"));

cmd.arg("init")
.arg("my-app")
.arg("typescript")
.arg("--template")
.arg("python");

cmd.assert()
.failure()
.stderr(predicate::str::contains("cannot be used with"))
.stderr(predicate::str::contains("--template"));

Ok(())
}
Comment thread
okane16 marked this conversation as resolved.

#[test]
#[serial_test::serial(init)]
fn init_with_unknown_template_flag_prints_template_recovery(
) -> Result<(), Box<dyn std::error::Error>> {
ensure_test_environment();

let temp = assert_fs::TempDir::new().unwrap();
std::fs::remove_dir(&temp)?;
let dir: &str = temp.path().to_str().unwrap();

let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("moose-cli"));

cmd.arg("init")
.arg("order-loader")
.arg("--template=simple")
.arg("-l")
.arg(dir);

cmd.assert().failure().stdout(
predicate::str::contains("Template 'simple' not found")
.and(predicate::str::contains("moose template list --json")),
);
Comment thread
okane16 marked this conversation as resolved.

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ bash -i <(curl -fsSL https://fiveonefour.com/install.sh) moose,514
<ConditionalContent whenId="starting-point" whenValue="scratch">

```shell
moose init <project-name> typescript-mcp
moose init --name <project-name> --template typescript-mcp
cd <project-name>
```

Expand Down Expand Up @@ -202,7 +202,7 @@ moose generate hash-token
<ToggleBlock openText="Show instructions to create one" closeText="Hide instructions">
Create one first and add it to your `pnpm-workspace.yaml`:
```shell
moose init moosestack-service typescript --location packages/moosestack-service
moose init --name moosestack-service --template typescript --location packages/moosestack-service
```
```yaml
# pnpm-workspace.yaml
Expand Down
2 changes: 1 addition & 1 deletion apps/framework-docs-v2/content/guides/data-warehouses.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ moose --version
Initialize a new MooseStack project:

```bash
moose init ecommerce-warehouse typescript
moose init --name ecommerce-warehouse --template typescript
cd ecommerce-warehouse
npm install
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ moose --version
### Step 2: Initialize project

```bash
moose init sales-reports typescript-empty
moose init --name sales-reports --template typescript-empty
cd sales-reports
pnpm install
```
Expand Down Expand Up @@ -2166,7 +2166,7 @@ If this returns empty `[]`, verify data was ingested and the date range is corre

**Stopping the dev server:** Press `Ctrl+C` in the `moose dev` terminal. Docker containers stop automatically.

**Starting over completely:** Stop `moose dev`, delete the project directory, and run `moose init` again.
**Starting over completely:** Stop `moose dev`, delete the project directory, and run `moose init` with `--name` and `--template` again.

**Reset data but keep code:** Stop `moose dev`, then remove the project's Docker volumes. **Caution:** `docker volume prune` deletes *all* unused volumes on your machine—not just this project's. Use `docker volume ls` to list volumes, identify yours (typically containing `clickhouse` or `redpanda`), and remove them with `docker volume rm <volume_name>`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ LIMIT 10;
## Shell/Bash Commands

```shell
moose init my-project typescript
moose init --name my-project --template typescript
cd my-project
pnpm install
pnpm dev
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,5 @@ moose build --docker --arm64
You can also enable custom Dockerfile mode during project initialization:

```bash
moose init my-app typescript --custom-dockerfile
moose init --name my-app --template typescript --custom-dockerfile
```
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ npm install @514labs/moose-lib

````markdown
```bash filename="Terminal" animate
moose init my-project
moose init --name my-project
cd my-project
moose dev
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Create a new Moose project:
<LanguageTabs>
<LanguageTabContent value="typescript" label="TypeScript">
```bash
moose init your-project-name ts
moose init --name your-project-name --template typescript
cd your-project-name
```

Expand All @@ -150,7 +150,7 @@ npm install # or yarn install
</LanguageTabContent>
<LanguageTabContent value="python" label="Python">
```bash
moose init your-project-name py
moose init --name your-project-name --template python
cd your-project-name
```
</LanguageTabContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,15 @@ bash -i <(curl -fsSL https://fiveonefour.com/install.sh) moose
Please follow the initialization instructions for your language.

```bash
moose init test-ts typescript
moose init --name test-ts --template typescript
cd test-ts
npm install
```

or

```bash
moose init test-py python
moose init --name test-py --template python
cd test-py
pip install -r requirements.txt
```
Expand Down
Loading
Loading