Skip to content
Open
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
5 changes: 5 additions & 0 deletions .agents/skills/debug-inference/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ Check:
- Required credential key exists
- `*_BASE_URL` override is correct when using a self-hosted endpoint

An `OPENAI_BASE_URL` or `ANTHROPIC_BASE_URL` override is route-only. It does not
authorize the fixed public-vendor endpoint or expose the alternate-upstream key
for substitution there. Use an imported endpoint-bearing custom profile when a
sandbox needs direct access to the alternate upstream.

Fix examples:

```bash
Expand Down
24 changes: 9 additions & 15 deletions .agents/skills/openshell-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,8 @@ The `--from-existing` flag discovers credentials from local state (e.g., `gh aut
### Create a provider with explicit credentials

```bash
openshell provider create --name my-api --type generic \
--credential API_KEY \
--config base_url=https://api.example.com
openshell provider create --name my-openai --type openai \
--credential OPENAI_API_KEY
```

Bare `KEY` reads the value from the environment variable of that name and avoids placing the secret in shell history. Use `KEY=VALUE` only when the user explicitly accepts that exposure.
Expand All @@ -137,14 +136,9 @@ must match. For a tunnel to `api.example.com:8443`, send
port and is rejected. An absolute-form request target must use the same
authority.

Profile-backed provider policy composition is controlled by the gateway-global
`providers_v2_enabled` setting. Static credential endpoint binding remains
active even when policy composition is disabled:

```bash
openshell settings get --global
openshell settings set --global --key providers_v2_enabled --value true
```
Profile-backed providers always contribute policy unless a gateway-global
policy is active. Static credential endpoint binding remains independently
enforced.

### Inspect and manage provider profiles

Expand All @@ -171,13 +165,13 @@ openshell provider delete my-github
Use refresh commands only when the provider profile and gateway support refreshable credentials:

```bash
openshell provider refresh status my-outlook
openshell provider refresh configure my-outlook \
openshell provider refresh status my-provider
openshell provider refresh configure my-provider \
--credential-key MS_GRAPH_ACCESS_TOKEN \
--strategy oauth2-refresh-token \
--secret-material-env REFRESH_TOKEN=MS_GRAPH_REFRESH_TOKEN \
--credential-expires-at 2026-07-16T00:00:00Z
openshell provider refresh rotate my-outlook --credential-key MS_GRAPH_ACCESS_TOKEN
openshell provider refresh rotate my-provider --credential-key ACCESS_TOKEN
```

Prefer `--secret-material-env KEY[=ENVVAR]` for secret refresh material. `--material KEY=VALUE` is for non-secret material; `--secret-material-key` marks supplied material keys as secret.
Expand Down Expand Up @@ -698,7 +692,7 @@ openshell settings set work-session --key ocsf_json_enabled --value true
openshell settings delete work-session --key ocsf_json_enabled

openshell settings get --global --json
openshell settings set --global --key providers_v2_enabled --value true
openshell settings set --global --key ocsf_json_enabled --value true
```

Global mutations prompt for confirmation. Use `--yes` only in reviewed automation.
Expand Down
4 changes: 3 additions & 1 deletion .agents/skills/openshell-cli/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,9 @@ Create a provider configuration.
| `--runtime-credentials` | Resolve required credentials at runtime in the gateway or sandbox |
| `--config KEY=VALUE` | Config key/value pair. Repeatable. |

Exactly one credential source is required. Credential-source flags conflict with one another.
Credential-source flags conflict with one another when supplied. A credential
source is optional when the selected profile declares no required static
credentials or can resolve its required credentials at runtime.

### `openshell provider get <name>`

Expand Down
21 changes: 8 additions & 13 deletions .agents/skills/tui-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ Gateway (discovered via openshell_bootstrap::list_gateways())

- **Gateways** are discovered from on-disk config via `openshell_bootstrap::list_gateways()`. Each gateway has a name, endpoint, local/remote flag, and source label.
- **Workspaces** are fetched via `ListWorkspaces`. The user cycles through workspaces with `[w]`, or views all workspaces at once. The current workspace scopes provider and sandbox lists.
- **Provider Profiles** are fetched per-workspace via `ListProviderProfiles` when `providers_v2_enabled` is true. Profiles are cached in a `ProviderProfileCache` keyed by `(workspace, profile_id)` and matched to providers by type. They provide category, credential metadata, endpoint/binary counts, and inference capability.
- **Providers** are fetched via `ListProviders` scoped to the current workspace. Each `ProviderListEntry` pairs a provider with its optional cached profile. When `providers_v2_enabled` is true, CRUD operations are read-only in the TUI; when false, the TUI supports create/update/delete.
- **Provider Profiles** are fetched per-workspace via `ListProviderProfiles`. Profiles are cached in a `ProviderProfileCache` keyed by `(workspace, profile_id)` and matched to providers by type. They provide category, credential metadata, endpoint/binary counts, and inference capability.
- **Providers** are fetched via `ListProviders` scoped to the current workspace. Each `ProviderListEntry` pairs a provider with its optional cached profile. The TUI supports profile-backed create, update, and delete operations.
- **Global Settings** are fetched via `GetGatewayConfig` and displayed in a tabbed pane alongside providers on the dashboard. Each setting is a registered key with a typed value (bool/int/string). Platform-admin access is required; `PermissionDenied` disables the pane.
- **Sandboxes** belong to the active gateway and workspace. Fetched via `ListSandboxes` with a periodic tick refresh.
- **Sandbox Settings** are effective settings returned by `GetSandboxConfig`, each with a scope (sandbox, global, or unset). Globally-managed settings are blocked from sandbox-level edits.
Expand Down Expand Up @@ -170,7 +170,7 @@ Phase 2: WatchSandbox(follow_logs: true) → live tail → send via Event::L

**Sandboxes**: Fetched via `ListSandboxes` on a 2-second tick, scoped to the current workspace (or all workspaces).

**Providers**: Fetched via `ListProviders` on each tick. When `providers_v2_enabled` is true, provider profiles are also fetched per-workspace via `ListProviderProfiles` and cached in a `ProviderProfileCache` keyed by `(workspace, profile_id)`.
**Providers**: Fetched via `ListProviders` on each tick. Provider profiles are fetched per-workspace via `ListProviderProfiles` and cached in a `ProviderProfileCache` keyed by `(workspace, profile_id)`.

**Settings**: Global settings are fetched via `GetGatewayConfig` on each tick. Sandbox settings are fetched alongside the sandbox policy via `GetSandboxConfig` and refreshed on each tick when viewing a sandbox.

Expand Down Expand Up @@ -321,7 +321,7 @@ TUI actions should parallel `openshell` CLI commands so users have familiar ment
| `openshell sandbox connect` | `[s]` on sandbox policy view to launch SSH shell |
| `openshell logs <name>` | `[l]` on sandbox detail to open log viewer |
| `openshell provider list` | Provider table on Dashboard (middle pane) |
| `openshell provider create` | `[c]` on provider panel (when not providers_v2) |
| `openshell provider create` | `[c]` on provider panel |
| `openshell status` | Status in title bar + gateway list |

When adding new TUI features, check what the CLI offers and maintain consistency.
Expand Down Expand Up @@ -383,10 +383,7 @@ All actions are accessible via keyboard shortcuts displayed in the nav bar. The
**Dashboard (Gateways focus):**
`[Tab] Switch Panel [Enter] Select [j/k] Navigate │ [:] Command [q] Quit`

**Dashboard (Providers focus, providers_v2):**
`[Tab] Switch Panel [h/l] Switch Tab [j/k] Navigate [Enter] Detail read-only │ [:] Command [q] Quit`

**Dashboard (Providers focus, legacy):**
**Dashboard (Providers focus):**
`[Tab] Switch Panel [h/l] Switch Tab [j/k] Navigate [Enter] Detail [c] Create [u] Update [d] Delete │ [:] Command [q] Quit`

**Dashboard (Global Settings focus):**
Expand All @@ -412,7 +409,7 @@ All actions are accessible via keyboard shortcuts displayed in the nav bar. The
| File | Purpose |
| --- | --- |
| `crates/openshell-tui/Cargo.toml` | Crate manifest — dependencies on `openshell-core`, `openshell-bootstrap`, `ratatui`, `crossterm`, `tonic`, `tokio` |
| `crates/openshell-tui/src/lib.rs` | Entry point. Event loop, gRPC calls (`refresh_data`, `refresh_providers`, `refresh_global_settings`, `refresh_workspaces`, `refresh_sandboxes`, `spawn_log_stream`, `handle_sandbox_delete`, `fetch_providers_v2_setting`), gateway switching, mTLS channel building, provider CRUD spawners, settings CRUD spawners, draft approval spawners |
| `crates/openshell-tui/src/lib.rs` | Entry point. Event loop, gRPC calls (`refresh_data`, `refresh_providers`, `refresh_global_settings`, `refresh_workspaces`, `refresh_sandboxes`, `spawn_log_stream`, `handle_sandbox_delete`), gateway switching, mTLS channel building, provider CRUD spawners, settings CRUD spawners, draft approval spawners |
| `crates/openshell-tui/src/app.rs` | `App` state struct, `Screen`/`Focus`/`InputMode`/`LogSourceFilter`/`MiddlePaneTab`/`SandboxPolicyTab` enums, `LogLine`/`GatewayEntry`/`GlobalSettingEntry`/`SandboxSettingEntry`/`ProviderListEntry`/`ProviderDetailView` structs, create sandbox/provider form state, all key handling logic |
| `crates/openshell-tui/src/event.rs` | `Event` enum (`Key`, `Mouse`, `Tick`, `Redraw`, `Resize`, `LogLines`, `CreateResult`, `ProviderCreateResult`, `ProviderDetailFetched`, `ProviderUpdateResult`, `ProviderDeleteResult`, `DraftActionResult`, `GlobalSettingsFetched`, `GlobalSettingSetResult`, `GlobalSettingDeleteResult`, `SandboxSettingSetResult`, `SandboxSettingDeleteResult`, `ForwardWarnings`), `EventHandler` with mpsc channels and crossterm polling |
| `crates/openshell-tui/src/theme.rs` | `colors` module (NVIDIA_GREEN, EVERGLADE, BG, FG) and `styles` module (all `Style` constants) |
Expand Down Expand Up @@ -526,17 +523,15 @@ The connect timeout for gateway switching is 10 seconds with HTTP/2 keepalive at
4. On success:
- `app.client` is replaced with a new intercepted client
- `reset_sandbox_state()` clears all sandbox/log/draft/policy data
- `fetch_providers_v2_setting()` probes the new gateway's `GetGatewayConfig` to determine whether providers_v2 mode is enabled, so provider CRUD controls render correctly
- `refresh_data()` runs the full capability refresh sequence: `refresh_health` → `refresh_global_settings` → `refresh_workspaces` → `refresh_providers` → `refresh_sandboxes`
5. On failure: `status_text` shows the error

### Initial startup lifecycle

On launch, before the event loop starts:

1. `fetch_providers_v2_setting()` — probe gateway capability
2. `refresh_gateway_list()` — discover gateways from disk
3. `refresh_data()` — full refresh (health, global settings, workspaces, providers, sandboxes)
1. `refresh_gateway_list()` — discover gateways from disk
2. `refresh_data()` — full refresh (health, global settings, workspaces, providers, sandboxes)

### Workspace switching lifecycle

Expand Down
2 changes: 1 addition & 1 deletion architecture/google-vertex-ai-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ The `discovery` section lists `[service_account_token, gcloud_adc_token]` as the
credential sources the gateway will scan during `--from-existing`.

The `endpoints` section enumerates all Vertex AI API hosts that sandbox network
policies must permit when `providers_v2_enabled=true`:
policies permit through provider profile composition:

- `*-aiplatform.googleapis.com:443` (regional endpoints)
- `aiplatform.googleapis.com:443` (global endpoint)
Expand Down
25 changes: 18 additions & 7 deletions crates/openshell-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,9 @@ const POLICY_EXAMPLES: &str = "\x1b[1mALIAS\x1b[0m
const SETTINGS_EXAMPLES: &str = "\x1b[1mEXAMPLES\x1b[0m
$ openshell settings get my-sandbox
$ openshell settings get --global
$ openshell settings set --global --key providers_v2_enabled --value true
$ openshell settings set my-sandbox --key ocsf_json_enabled --value true
$ openshell settings set --global --key ocsf_json_enabled --value true
$ openshell settings delete --global --key providers_v2_enabled
$ openshell settings delete --global --key ocsf_json_enabled
";

const PROVIDER_EXAMPLES: &str = "\x1b[1mEXAMPLES\x1b[0m
Expand Down Expand Up @@ -811,7 +810,7 @@ impl From<CliEditor> for openshell_cli::ssh::Editor {
#[derive(Subcommand, Debug)]
enum ProviderCommands {
/// Create a provider config.
#[command(group = clap::ArgGroup::new("cred_source").required(true).multiple(true).args(["from_existing", "credentials", "from_gcloud_adc", "runtime_credentials", "from_oidc_token"]), help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")]
#[command(group = clap::ArgGroup::new("cred_source").required(false).multiple(true).args(["from_existing", "credentials", "from_gcloud_adc", "runtime_credentials", "from_oidc_token"]), help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")]
Create {
/// Provider name.
#[arg(long)]
Expand Down Expand Up @@ -4714,8 +4713,8 @@ mod tests {
}

#[test]
fn provider_create_requires_credential_source() {
let err = Cli::try_parse_from([
fn provider_create_accepts_no_credential_source() {
let cli = Cli::try_parse_from([
"openshell",
"provider",
"create",
Expand All @@ -4724,9 +4723,21 @@ mod tests {
"--type",
"spiffe-token-demo",
])
.expect_err("provider create should require a credential source");
.expect("provider create should allow profiles without static credentials");

assert!(err.to_string().contains("--runtime-credentials"));
assert!(matches!(
cli.command,
Some(Commands::Provider {
command: Some(ProviderCommands::Create {
from_existing: false,
credentials,
from_gcloud_adc: false,
from_oidc_token: false,
runtime_credentials: false,
..
})
}) if credentials.is_empty()
));
}

#[test]
Expand Down
Loading
Loading