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
10 changes: 5 additions & 5 deletions .runseal/wrappers/cloudflare.seal
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ load_manage_redirect_rules() {
}

print_manage_redirect_plan() {
pretty_sh=$(runseal @tool json pretty "$rule_sh")
pretty_ps1=$(runseal @tool json pretty "$rule_ps1")
pretty_sh=$(runseal @tool json pretty value "$rule_sh")
pretty_ps1=$(runseal @tool json pretty value "$rule_ps1")
print "manage redirect plan"
print "zone: $zone_name"
if [ "$1" = with-zone-id ]; then
Expand Down Expand Up @@ -107,11 +107,11 @@ case "$1" in
ruleset_count=$(runseal @tool json len "$rulesets")
zones_payload=$(runseal @tool cloudflare api request GET /zones --query "account.id=$account_id" --query per_page=50)
zones=$(runseal @tool json get "$zones_payload" .result)
zones_pretty=$(runseal @tool json pretty "$zones")
zones_pretty=$(runseal @tool json pretty value "$zones")
account=$(runseal @tool cloudflare account get --account-id "$account_id")
account_name=$(runseal @tool json get "$account" .name)
buckets=$(runseal @tool cloudflare account r2 bucket list --account-id "$account_id")
buckets_pretty=$(runseal @tool json pretty "$buckets")
buckets_pretty=$(runseal @tool json pretty value "$buckets")
print "cloudflare check: ok"
print "account id: $account_id"
print "account name: $account_name"
Expand Down Expand Up @@ -151,7 +151,7 @@ case "$1" in
print "manage inspect: no manage redirect rules found"
exit 0
fi
pretty=$(runseal @tool json pretty "$matched")
pretty=$(runseal @tool json pretty value "$matched")
print "manage rules:"
print "$pretty"
;;
Expand Down
176 changes: 138 additions & 38 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,130 @@
# AGENTS

## Core Principle
## 1. AGENTS.md Meta Constraints

Small CLI. Explicit profile. No hidden orchestration.
This top-level `AGENTS.md` is the repository navigation and policy layer.

- Keep this file focused on shared constraints, navigation, and recurring
operating guidance.
- Push local implementation detail downward into child `AGENTS.md` files when a
directory starts carrying its own stable rules.
- Do not duplicate large bodies of module-specific instruction here once a child
`AGENTS.md` exists.
- Treat this file as the default contract for the whole repository unless a
deeper `AGENTS.md` overrides a narrower scope.
- Keep `.task/` out of git by default. Use it only for long-running work and
update it as live task state, not as archival prose.

Core product stance:

- Small CLI. Explicit profile. No hidden orchestration.
- Keep the Rust core thin and concrete.
- Support only `env`, `symlink`, fixed-prefix `argv`, explicit `:wrapper` command resolution, and read-only `@internal` introspection unless a new product decision explicitly expands the surface.
- Use mature CLI parsing through `clap`; do not hand-roll argument parsing.
- Support only `env`, `symlink`, fixed-prefix `argv`, explicit `:wrapper`
resolution, direct `.seal` execution, and read-only `@internal`
introspection unless a new product decision explicitly expands the surface.
- Use `clap` for CLI parsing. Do not hand-roll argument parsing.
- Preserve command lifecycle semantics: load profile, register symlinks, export
env, run command, clean up symlinks.
- Keep command namespaces explicit: `<cmd>` is external, `:<cmd>` is a profile
wrapper, `@<cmd>` is runseal internal.

Runtime path rules:

- Treat `RUNSEAL_HOME` as the runseal configuration root.
- Treat `RUNSEAL_PROFILE_HOME` as the profile directory, defaulting to `<RUNSEAL_HOME>/profiles`.
- Treat `RUNSEAL_PROFILE_HOME` as the profile directory, defaulting to
`<RUNSEAL_HOME>/profiles`.
- Resolve one concrete `RUNSEAL_PROFILE_PATH` during app initialization.

Tooling rules:

- Treat `runseal` and `flavor` as installed developer infrastructure, at the
same level as `git`, `gh`, and `cargo`; this repository does not bootstrap
them.
- Preserve command lifecycle semantics: load profile, register symlinks, export env, run command, cleanup symlinks.
- Keep command namespaces explicit: `<cmd>` is external, `:<cmd>` is profile wrapper, `@<cmd>` is runseal internal.
- Treat `.runseal/wrappers/*.seal` as first-class wrappers executed directly by
runseal. `@transpile` is an isomorphic debug/export tool, not the normal
wrapper execution path.

## Directory Conventions
## 2. Directory Conventions

Direct child directories with their own `AGENTS.md`:

- None yet.

Direct child directories that are likely future candidates for a child
`AGENTS.md` once their local rules become stable:

- `app/`: Rust application code, tests, and core runtime behavior.
- `.runseal/`: repo-local wrappers and operator-facing workflow glue.
- `.github/`: CI, release automation, and workflow support scripts.
- `docs/`: durable operator or contributor documentation, if this area starts
carrying rules distinct from code.

When a direct child directory gains its own stable constraints, add an
`AGENTS.md` there and link it from this section.

## 3. Core File Index

There are no child `AGENTS.md` targets yet, so this index currently points to
the repository-owned canonical files directly.

- `app/src/bin/runseal.rs`: CLI entrypoint.
- `app/src/core/config.rs`: app configuration and profile discovery.
- `app/src/core/profile.rs`: profile format loading and normalization.
- `app/src/core/runtime.rs`: command execution lifecycle.
- `app/src/core/transpile/runner.rs`: direct Seal wrapper runtime.
- `app/src/core/injections/`: `env` and `symlink` implementations.
- `app/tests/`: integration tests and focused unit tests.
- `runseal.toml`: repo-local operator profile.
- `app/src/core/tool/`: built-in atomic `@tool` surface.
- `app/tests/`: integration tests and focused behavioral coverage.
- `.runseal/wrappers/`: repo-local `:wrapper` entrypoints. Prefer `.seal`
wrappers; platform scripts exist only while a wrapper has not migrated.
- `manage.sh` and `manage.ps1`: public install/uninstall managers.
- `.task/`: branch-bound task state, ignored by git.
- `runseal.toml`: repo-local operator profile.
- `manage.sh` and `manage.ps1`: public install and uninstall managers.

Once child `AGENTS.md` files exist, this section should prefer links to those
local guides over repeating their detail here.

## 4. Daily Iteration Workflow And Commands

Normal workflow:

1. Work on a feature branch.
2. Keep changes scoped to the current product boundary.
3. Validate locally before PR.
4. Use repo wrappers for recurring operator flows when they already encode the
intended path.

Common validation commands:

```bash
cargo fmt --check
cargo test --locked --workspace
flavor check
```

Common repo workflow commands:

```bash
runseal :init
runseal :cloudflare
runseal :pr
runseal :release beta
```

## Profile Discovery
Manager install/update path:

Priority order:
```bash
./manage.sh install --channel beta
```

Release and distribution rules:

- Release and manager downloads use R2 metadata and artifacts as the source of
truth.
- Public install and uninstall entrypoints are `manage.sh` and `manage.ps1`.
- Release and smoke flows should reference those root files.
- Cloudflare manager redirects are exact-path rules for
`runseal.perish.uk/manage.sh` and `runseal.perish.uk/manage.ps1`, pointing to
`releases.runseal.perish.uk/manage.sh` and
`releases.runseal.perish.uk/manage.ps1`.

Profile discovery order:

1. `--profile <path>`
2. From `<cwd>` upward to filesystem root, at each directory:
Expand All @@ -52,30 +140,42 @@ Priority order:
Format priority is TOML, YAML, then JSON within each searched directory.
Successful profile and wrapper paths are normalized absolute paths.

## Development Workflow
## 5. FAQ

1. Work on a feature branch.
2. Use `runseal :init`, `runseal :cloudflare`, `runseal :pr`, and
`runseal :release` for repo management.
3. Keep changes scoped to the reduced CLI surface.
4. Run:
### Why keep the CLI surface small?

```bash
cargo fmt --check
cargo test
```
Because this repository is building explicit runtime glue, not a hidden
orchestrator. New behavior should be added only when it fits the existing
surface cleanly.

### When should behavior become Seal syntax?

Only when bash and PowerShell share an elegant, stable semantic shape that is
worth making first-class.

### When should behavior become `@tool`?

When native CLI coverage is insufficient for an atomic, reusable operation and
the result still fits the small explicit model.

### When should logic stay outside runseal?

When the behavior cannot be described cleanly as shared shell-shape syntax or a
small atomic tool, keep it in Python, Ruby, JavaScript, or another external
script.

### Should `.seal` wrappers be treated as first-class runtime entrypoints?

Yes. Treat `.runseal/wrappers/*.seal` as first-class wrappers executed directly
by runseal. `@transpile` is a debug/export tool, not the normal wrapper
execution path.

Release and manager downloads use R2 metadata and artifacts as the source of
truth. Public install/uninstall entrypoints are `manage.sh` and `manage.ps1`;
release and smoke scripts should reference those root files.
### What should never be committed?

Cloudflare manager redirects are exact-path rules for
`runseal.perish.uk/manage.sh` and `runseal.perish.uk/manage.ps1`, pointing to
`releases.runseal.perish.uk/manage.sh` and
`releases.runseal.perish.uk/manage.ps1`.
- `.task/`
- accidental broad surface expansions that were not backed by an explicit
product decision

## Commit Rules
### What is the commit style?

- Prefer small focused commits.
- Do not commit `.task/`.
- Do not reintroduce broader command surfaces without an explicit product decision.
Prefer small focused commits.
78 changes: 0 additions & 78 deletions app/src/core/tool/help/basic.rs
Original file line number Diff line number Diff line change
@@ -1,83 +1,5 @@
use super::{Entry, Section};

pub const JSON: Entry = Entry {
key: "json",
usage: "runseal @tool json <command> [args]",
about: None,
sections: &[Section {
title: "JSON helpers",
items: &[
("get <json> <path>", "print one JSON value"),
("empty <json>", "print true when JSON length is zero"),
("len <json>", "print JSON array/object/string length"),
("pretty <json>", "print formatted JSON"),
(
"find <array> <field> <value>",
"print first object with field=value",
),
(
"filter <array> <field> <value>...",
"print objects with field matching values",
),
],
}],
examples: &[],
};

pub const JSON_GET: Entry = Entry {
key: "json.get",
usage: "runseal @tool json get <json> <path>",
about: Some("Print one JSON value selected by the path expression."),
sections: &[Section {
title: "Arguments",
items: &[
("<json>", "input JSON text"),
("<path>", "path expression such as `.[0].databaseId`"),
],
}],
examples: &["runseal @tool json get '[{\"databaseId\":123}]' '.[0].databaseId'"],
};

pub const JSON_EMPTY: Entry = Entry {
key: "json.empty",
usage: "runseal @tool json empty <json>",
about: Some("Print `true` when the JSON array, object, or string length is zero."),
sections: &[],
examples: &["runseal @tool json empty '[]'"],
};

pub const JSON_LEN: Entry = Entry {
key: "json.len",
usage: "runseal @tool json len <json>",
about: Some("Print the JSON array, object, or string length as an integer."),
sections: &[],
examples: &["runseal @tool json len '[1,2,3]'"],
};

pub const JSON_PRETTY: Entry = Entry {
key: "json.pretty",
usage: "runseal @tool json pretty <json>",
about: Some("Print formatted JSON with indentation."),
sections: &[],
examples: &["runseal @tool json pretty '{\"a\":1}'"],
};

pub const JSON_FIND: Entry = Entry {
key: "json.find",
usage: "runseal @tool json find <array> <field> <value>",
about: Some("Print the first object in the JSON array with `<field> == <value>`."),
sections: &[],
examples: &["runseal @tool json find '[{\"id\":1}]' id 1"],
};

pub const JSON_FILTER: Entry = Entry {
key: "json.filter",
usage: "runseal @tool json filter <array> <field> <value>...",
about: Some("Print objects in the JSON array whose `<field>` matches any provided value."),
sections: &[],
examples: &["runseal @tool json filter '[{\"env\":\"dev\"},{\"env\":\"prod\"}]' env dev prod"],
};

pub const STRING: Entry = Entry {
key: "string",
usage: "runseal @tool string <command> [args]",
Expand Down
Loading
Loading