Skip to content

Commit 66e80b7

Browse files
committed
Merge branch 'development'
2 parents a550297 + 804d3e6 commit 66e80b7

205 files changed

Lines changed: 15269 additions & 2636 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/AGENTS.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
# Public, open-source repository
2+
3+
`smbcloud-cli` is published on GitHub as **open source**. Everything you commit here — code, comments, docs, and `.agents/skills/*` — is **world-readable and permanent** (git history outlives any later deletion). Before writing anything, ask: "is this safe on a public repo forever?"
4+
5+
Do **not** add internal smbCloud infrastructure detail or secrets:
6+
7+
- server hostnames/IPs and operational endpoints beyond what the CLI already targets in source — e.g. account-scoped SSH key names (`id_<n>@smbcloud`), `api-1.smbcloud.xyz`, internal health ports
8+
- account/user IDs and which user or tenant owns which project
9+
- real customer/app domains, PM2 process names, the production port → app → domain registry
10+
- workspace/project IDs and `frontend_app_id` / `deploy_repo_id` values
11+
- secrets/config: API keys, tokens, `.env` values, connection strings, real auth/CORS origins
12+
- commands that read local credentials to enumerate the API (e.g. `cat ~/.smb/token | curl …`)
13+
- incident logs or examples that name real apps, tenants, customers, or dated rollouts
14+
15+
Keep docs and skills **generic** — describe how the tool behaves, using placeholders (`example.com`, `<app>`, `<source>`, `<port>`, `<n>`). The base API host (`api.smbcloud.xyz`) is already in the source, so it is not a new leak; the items above are. Fleet-specific operational detail and the internal deploy reference live only in the private `smbcloud` repo.
16+
17+
---
18+
119
# Rust coding guidelines
220

321
- Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified.
@@ -25,3 +43,71 @@
2543
}
2644
});
2745
```
46+
47+
---
48+
49+
# Build, test, and run
50+
51+
This is a Cargo workspace pinned to stable Rust (`rust-toolchain.toml`). The CLI binary is named **`smb`** (crate `smbcloud-cli`, in `crates/cli`).
52+
53+
```sh
54+
cargo build # build everything (debug)
55+
cargo run -p smbcloud-cli -- <args> # run the CLI, e.g. -- me, -- deploy, -- --help
56+
cargo run -p smbcloud-cli -- -e dev me # talk to a local API (Environment::Dev -> http://localhost:8088)
57+
```
58+
59+
CI gate (mirror it locally before pushing — see `.github/workflows/ci.yml`):
60+
61+
```sh
62+
cargo fmt --all -- --check
63+
cargo clippy --workspace --exclude smbcloud-auth-sdk-wasm --tests -- -D warnings # warnings are denied
64+
cargo test --workspace --exclude smbcloud-auth-sdk-wasm
65+
cargo test -p smbcloud-model app_auth::tests::some_test # a single test
66+
```
67+
68+
`smbcloud-auth-sdk-wasm` targets `wasm32-unknown-unknown` and cannot build or test on the host — it is always excluded from workspace clippy/test and checked separately: `cargo check -p smbcloud-auth-sdk-wasm --target wasm32-unknown-unknown`.
69+
70+
`CLI_CLIENT_SECRET` (see `.env.example`) is the OAuth client-credentials secret read at runtime; tests and `cargo check` don't need it, but login flows against a real API do.
71+
72+
## Releases
73+
74+
Releases are prepared on the `development` branch with a clean tree via the Makefile, which drives `cargo workspaces` to bump every crate in lockstep, then syncs the package-manager manifests (npm/nuget/pypi) and regenerates the SDK lockfiles:
75+
76+
```sh
77+
make patch | make minor | make major | make custom VERSION=0.x.y
78+
```
79+
80+
It commits `Release <version>` and tags `v<version>` locally; pushing is manual. Crate publishing is separate and manual (`cargo workspaces publish`, see `docs/development.md`). The per-target release workflows (`release-*.yml`) build the distributable artifacts.
81+
82+
---
83+
84+
# Architecture
85+
86+
`smb` is a thin async (`tokio`) `clap` front-end over a set of `smbcloud-*` library crates that talk to the smbCloud REST API. The product purpose is **deploying apps** (Node.js/Next.js, Ruby/Rails, Rust, Swift/Vapor, static/Vite) from the terminal.
87+
88+
## Request flow
89+
90+
`crates/cli/src/main.rs``cli/mod.rs` (the `Cli`/`Commands` clap tree) → a `process_*` handler under `account/`, `project/`, `deploy/`, or `mail/`. Each handler returns a `CommandResult` (a spinner + final symbol/message); `main` prints it and sets the exit code. The top-level `--environment` (`dev`/`production`, default production) threads through every handler and selects the API host and the on-disk state dir (`.smb` vs `.smb-dev`, see `smbcloud-network/src/environment.rs`).
91+
92+
Two cross-cutting globals are resolved once in `main` before any handler runs:
93+
- **CI mode** (`crates/cli/src/ci.rs`): `--ci` / `SMB_CI=1` / conventional `CI` env. Stored in a process-global `AtomicBool` (`ci::is_ci()`) rather than threaded through signatures, so deep prompt code can fail fast instead of blocking on a missing TTY. New interactive prompts must check `is_ci()` and use `ci::interactive_message(...)`.
94+
- **Logging**: bunyan JSON to `~/<smb_dir>/smbcloud-cli.log` (not stdout); user-facing output is `console`/spinner styling.
95+
96+
## Crate layout (workspace)
97+
98+
- `smbcloud-model` — shared serde types (`Project`, `User`, `Runner`, deploy config, `ErrorCode`/`ErrorResponse`). `crate-type = ["cdylib","rlib"]` because the SDK crates re-export it. Most unit tests live here.
99+
- `smbcloud-network``Environment` enum (host/protocol/state-dir resolution) and connectivity checks.
100+
- `smbcloud-networking``SmbClient` (the OAuth client identity) and the low-level HTTP client/constants.
101+
- `smbcloud-networking-project` — typed REST calls for projects, deploy repos, frontend apps, deploy config, and deployments (the `crud_*` files).
102+
- `smbcloud-auth` / `smbcloud-auth-sdk` — auth flows (login, signup, OIDC, Apple, client-credentials, reset/verify). `auth` is the in-CLI implementation; `auth-sdk` is the embeddable client.
103+
- `smbcloud-auth-sdk-wasm` / `-py` (+ `sdk/`) — language bindings: WASM/`wasm-bindgen` for npm, PyO3 `cdylib` for Python, and a separate `rb_sys` Cargo workspace under `sdk/gems` for the Ruby gem. These wrap `smbcloud-auth-sdk`.
104+
- `smbcloud-mail`, `smbcloud-gresiq`(`-sdk`), `smbcloud-s6n` — clients for the Mail, GresIQ (serverless Postgres), and S6n (S3-compatible storage) products.
105+
- `smbcloud-utils``.smb/config.toml` (`Config`) parsing and SSH key-path resolution.
106+
107+
## Deploy subsystem (`crates/cli/src/deploy/`)
108+
109+
The most involved area. `process_deploy.rs` loads `.smb/config.toml` (running interactive `setup_project` if missing), overlays server-side config, validates project access, then **dispatches by `config.project.kind`** to a per-stack path: `vite-spa`, `nextjs-ssr`, `rails`, `rust`, `swift` each have their own `process_deploy_*.rs`. If `kind` is unset it falls back to `deployment_method`: `Rsync` (build locally, upload over rsync, restart over SSH) or `Git` (push to a remote git hook), with `detect_runner.rs` sniffing the framework from `package.json`/`Gemfile`/`Package.swift`/`Cargo.toml`.
110+
111+
**Monorepo**: `runner = Monorepo` with a `[[projects]]` array in config; `smb deploy --project <name>` (or an interactive picker) swaps `config.project` to the named sub-project before the dispatch above. `migrate.rs`/`smb migrate` pushes local deploy fields up to the server.
112+
113+
When editing a deploy path, keep the public-repo policy above in mind: SSH key names, hosts, ports, app/tenant identifiers, and PM2 process names must stay as placeholders — concrete fleet detail belongs only in the private `smbcloud` repo.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Plan: decouple build/deploy from the CLI into `smbcloud-deploy`
2+
3+
Status: in progress. Stages 0 to 3 (vite_spa slice) shipped. Remaining work below
4+
is the **next milestone**.
5+
6+
## Goal
7+
8+
Move the deploy logic out of `crates/cli` into a reusable `crates/smbcloud-deploy`
9+
engine so the CLI, the CI action, and (later) the server-side git receiver all
10+
drive the same code. They differ only in how they report progress and how they
11+
authenticate.
12+
13+
Two inversions make it reusable:
14+
- `Reporter` replaces direct `spinners` / `dialoguer` / `println!`, so the engine
15+
never owns the terminal. The CLI supplies `SpinnerReporter`.
16+
- Auth is passed in (a token / credentials); the engine never reads `~/.smb` or
17+
prompts for login.
18+
19+
Decisions already made:
20+
- Networking crates (`smbcloud-networking*`) stay direct deps of the engine.
21+
Only reporting and auth are inverted.
22+
- `git.rs` (SSH-git remote setup) stays in the CLI for now. It is the SSH-git
23+
path slated for replacement by git-smart-HTTP, so decoupling it is wasted work.
24+
- Interactive setup (`setup*.rs`, `process_migrate.rs`) stays in the CLI. It is
25+
UX, not engine.
26+
27+
## Done
28+
29+
- **Stage 0** engine crate: `error::DeployError`, `report::{Reporter, NoopReporter}`,
30+
`runner::detect_runner`.
31+
- **Stage 1** CLI depends on the engine; `ui/reporter.rs::SpinnerReporter`;
32+
`detect_runner` rewired; old `deploy/detect_runner.rs` removed.
33+
- **Stage 2** transport: `transport::{Transport, RsyncTransport}` + moved
34+
`known_hosts`. CLI helper `deploy::rsync_transport(config, runner, user_id)`
35+
resolves the local `~/.ssh` identity and remote path.
36+
- **Stage 3 (vite_spa slice)** build split: `build::{BuildStrategy, BuildArtifact,
37+
ViteSpaBuild}`. `process_deploy_vite_spa.rs` now calls the engine for the build
38+
and reuses one `SpinnerReporter` for build + transport.
39+
40+
## Next milestone
41+
42+
### 1. Shared remote-command step
43+
44+
Several strategies do the same "SSH in and restart the service" step, inlined
45+
per file. Extract it into the engine first (e.g. `transport::RemoteCommand` or a
46+
`remote` module) so the strategies can share it. Model it behind the same pinned
47+
host-key SSH used by `RsyncTransport`.
48+
49+
### 2. Remaining `BuildStrategy` extractions (one per pass, compile-green each)
50+
51+
Do smallest first. Each: lift build mechanics into a `BuildStrategy`, swap
52+
`spinners` + `stop_and_persist` for `Reporter`, take auth as a param, route the
53+
remote-restart step through step 1.
54+
55+
| File | Lines | Shape |
56+
| --- | --- | --- |
57+
| `process_deploy_rails.rs` | ~395 | rsync shared lib, SSH compile native gem, git force-push |
58+
| `process_deploy_rust.rs` | ~641 | cross-build Linux binary, rsync, SSH restart |
59+
| `process_deploy_nextjs_ssr.rs` | ~700 | install, build, upload `.next/standalone`, SSH restart |
60+
| `process_deploy_swift.rs` | ~832 | Docker Linux build, rsync binary + Resources, SSH restart |
61+
62+
Follow `ViteSpaBuild` as the template. Keep the deployment-record API calls
63+
(`create_deployment` / `update`) in the CLI router, not the engine.
64+
65+
### 3. Stage 4: router + retire the CommandResult wart
66+
67+
- Flip `process_deploy.rs`'s router to call an `engine::deploy(...)` orchestrator.
68+
- Retire `CommandResult`'s `spinner: Spinner` field (the "return a live spinner,
69+
caller stops it" pattern) now that the engine reports the whole flow through
70+
`Reporter`. `CommandResult` has ~50 construction sites, so this is its own pass:
71+
either make `spinner` optional or drop it and have commands return plain data.
72+
73+
## Reference
74+
75+
- Spec: `smbcloud` repo `.agents/specs/smbcloud-deploy.md` (product direction),
76+
`smbcloud-actions.md` (CI wrapper). Direction is git-smart-HTTP, local-first
77+
build, drop SSH transport later.
78+
- Engine crate: `crates/smbcloud-deploy/`.

.agents/skills/ci-cd/SKILL.md

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -260,16 +260,16 @@ The PyPI workflow is also exempt because maturin is already pointed at the right
260260

261261
### Current runners and targets
262262

263-
| Platform | Runner | Target | Notes |
264-
| ------------- | ------------------ | --------------------------- | ------------------------- |
265-
| Linux x86_64 | `ubuntu-latest` | `x86_64-unknown-linux-gnu` | Native |
266-
| Linux arm64 | `ubuntu-24.04-arm` | `aarch64-unknown-linux-gnu` | Native — do not use QEMU |
267-
| Windows x64 | `windows-2022` | `x86_64-pc-windows-msvc` | Native |
268-
| Windows arm64 | `windows-2022` | `aarch64-pc-windows-msvc` | Cross-compile on x64 host |
269-
| macOS x64 | `macos-15-intel` | `x86_64-apple-darwin` | Native Intel runner |
270-
| macOS arm64 | `macos-latest` | `aarch64-apple-darwin` | Native Apple Silicon |
263+
| Platform | Runner | Target | Notes |
264+
| ------------- | ------------------ | --------------------------- | -------------------------- |
265+
| Linux x86_64 | `ubuntu-latest` | `x86_64-unknown-linux-gnu` | Native |
266+
| Linux arm64 | `ubuntu-24.04-arm` | `aarch64-unknown-linux-gnu` | Native — do not use QEMU |
267+
| Windows x64 | `windows-2022` | `x86_64-pc-windows-msvc` | Native |
268+
| Windows arm64 | `windows-2022` | `aarch64-pc-windows-msvc` | Do not use for PyPI wheels |
269+
| macOS x64 | `macos-15-intel` | `x86_64-apple-darwin` | Native Intel runner |
270+
| macOS arm64 | `macos-latest` | `aarch64-apple-darwin` | Native Apple Silicon |
271271

272-
For npm and GitHub Release, use native runners. For PyPI (maturin), the macOS x64 build uses `macos-latest` with cross-compilation because maturin handles it transparently inside its container.
272+
For npm and GitHub Release, use native runners. For PyPI (maturin), the macOS x64 build uses `macos-latest` with cross-compilation because maturin handles it transparently inside its container. Windows arm64 is the exception: GitHub's hosted Windows runners only provide x64 Python, so maturin will refuse to build an `aarch64-pc-windows-msvc` wheel when the interpreter reports `win-amd64`.
273273

274274
### Do not use QEMU for arm64 Linux
275275

@@ -370,6 +370,15 @@ permissions:
370370
371371
No `PYPI_TOKEN` secret needed. The action exchanges the GitHub OIDC JWT for a short-lived PyPI token automatically.
372372

373+
**Each PyPI package × workflow combination needs its own trusted publisher.** The CLI package (`smbcloud-cli`) is configured for `release-pypi.yml`, but the SDK package (`smbcloud-sdk-auth`) needs a separate trusted publisher pointing at `release-sdk-pypi.yml`. Configure it at `https://pypi.org/manage/project/<package>/settings/publishing/` with:
374+
375+
- Owner: `smbcloudXYZ`
376+
- Repository: `smbcloud-cli`
377+
- Workflow name: the exact `.yml` filename that publishes that package
378+
- Environment: leave blank
379+
380+
If the trusted publisher is missing, the publish step fails with `invalid-publisher: valid token, but no corresponding publisher`.
381+
373382
### npm — does NOT support trusted publishing natively
374383

375384
npm has no OIDC-based trusted publishing equivalent to PyPI. The `id-token: write` permission in the npm workflow is unused for auth.
@@ -690,6 +699,24 @@ One practical warning: the Ruby gem release depends on crates.io propagation. If
690699

691700
## Common mistakes
692701

702+
**SDK npm version out of sync with Rust crate version**
703+
`sdk/npm/smbcloud-auth/package.json` must have the same version as `crates/smbcloud-auth-sdk-wasm/Cargo.toml`. The `prepare-package.mjs` script compares them at build time and throws a hard error on mismatch. When bumping workspace crate versions for a release, always update the npm package version in the same commit. Forgetting this causes the `check-npm-sdk` CI job and the `release-sdk-npm` workflow to fail.
704+
705+
**SDK Ruby gem versions out of sync with workspace crate versions**
706+
Three files must be bumped together for each gem in `sdk/gems/`:
707+
708+
- `lib/<gem>/version.rb` — the gem version (`Auth::VERSION`, `Model::VERSION`)
709+
- `ext/<gem>/Cargo.toml` — the native extension crate version
710+
- `ext/<gem>/Cargo.toml` dependencies — the `smbcloud-*` version constraints (e.g. `"0.3"` → `"0.4"`)
711+
712+
After bumping, regenerate lockfiles with `cargo generate-lockfile` and `bundle lock` inside each gem directory. The `release-sdk-gem` workflow checks `Auth::VERSION` against the tag and fails on mismatch.
713+
714+
**Tagging a release on a feature branch instead of `development`**
715+
Always tag on `development` (the mainline branch). Tagging on a feature branch leaves `development` without the release commit, making git history confusing and future releases error-prone. Merge the feature branch into `development` first, then tag. If a tag was already placed on the wrong commit, move it with `git tag -f v<version>` and `git push origin v<version> --force`.
716+
717+
**"Re-run all jobs" on an old workflow run rebuilds from the old commit**
718+
GitHub Actions workflow re-runs always use the commit the run was originally dispatched with. If you moved a tag after the run was created, the re-run still checks out the old commit. Use "Re-run failed jobs" only when the code on that commit is correct and only an external issue (e.g. missing trusted publisher) caused the failure. If the code itself was fixed, dispatch a fresh run instead: `gh workflow run <workflow>.yml -f tag=v<version>`.
719+
693720
**Building the full workspace in release workflows**
694721
Always pass `--package smbcloud-cli` to `cargo build` and `cargo publish`. Omitting it builds `smbcloud-auth-sdk-py` (PyO3 cdylib) which fails on platforms without a matching Python interpreter.
695722

@@ -708,6 +735,9 @@ This reads whatever version is currently in `Cargo.toml` on the checked-out bran
708735
**QEMU for arm64 Linux**
709736
Using `ubuntu-latest` (x86_64) to build `aarch64-unknown-linux-gnu` via QEMU inside manylinux is slow and causes container mismatch errors. Use `ubuntu-24.04-arm`.
710737

738+
**Windows arm64 PyPI wheels on hosted runners**
739+
Maturin needs a matching Python interpreter for wheel generation. On GitHub-hosted Windows runners, the available interpreter reports `win-amd64`, so an `aarch64-pc-windows-msvc` wheel build gets skipped and then fails. Unless you have an arm64 Windows runner with arm64 Python installed, leave Windows arm64 out of the PyPI wheel matrix.
740+
711741
**`yum` vs `apt` in manylinux**
712742
manylinux2014 is CentOS 7. Use `yum`, not `apt`. For manylinux_2_28 (AlmaLinux 8) use `dnf`.
713743

.agents/skills/smbcloud-auth/SKILL.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ Use this skill when work touches any part of the smbCloud auth stack:
99

1010
- `smbcloud-api` Rails auth service
1111
- `smbcloud-web-console` Next.js admin UI
12-
- `smbcloud-cli/crates/smbcloud-auth` Rust SDK
13-
- `smbcloud-cli/crates/smbcloud-auth-sdk-wasm` browser SDK
12+
- `smbcloud-cli/crates/smbcloud-auth-sdk` canonical client SDK core (wrapped by the `-py` and `-wasm` bindings; the Apple SDK is the native Swift package `smbcloud-auth-swift`, not a UniFFI binding)
13+
- `smbcloud-cli/crates/smbcloud-auth-sdk-wasm` browser SDK (binding over the core)
14+
- `smbcloud-cli/crates/smbcloud-auth` the smbcloud-cli's own internal auth client — NOT the published SDK
1415
- Tauri apps such as Rumi Learn Persian and PBJ Komplit
1516
- web apps that use `@smbcloud/sdk-auth`
1617

@@ -135,10 +136,18 @@ Do not claim email confirmation exists unless the mailer and route are actually
135136

136137
## Rust SDK conventions
137138

138-
The Rust client surface lives in:
139+
The canonical Rust client surface lives in:
139140

140-
- `smbcloud-cli/crates/smbcloud-auth`
141-
- `smbcloud-cli/crates/smbcloud-auth-sdk-wasm`
141+
- `smbcloud-cli/crates/smbcloud-auth-sdk` — the shared SDK core; the published
142+
bindings (`smbcloud-auth-sdk-py`, `smbcloud-auth-sdk-wasm`) depend on it.
143+
Change the client contract here.
144+
- `smbcloud-cli/crates/smbcloud-auth-sdk-wasm` — the browser binding (`@smbcloud/sdk-auth`).
145+
- Apple platforms are served by the native Swift port in `smbcloud-auth-swift`
146+
(`AuthCore`), pinned to this crate by a conformance suite — there is no UniFFI
147+
Apple binding.
148+
149+
`smbcloud-cli/crates/smbcloud-auth` is the CLI's own internal auth client (only
150+
the `cli` crate depends on it); don't treat it as the SDK.
142151

143152
When changing tenant app auth:
144153

@@ -277,7 +286,7 @@ Use the smallest relevant checks.
277286

278287
### Rust SDK
279288

280-
- `cargo check -p smbcloud-auth`
289+
- `cargo check -p smbcloud-auth-sdk` (the core; also rebuilds the bindings as needed)
281290
- `cargo check -p smbcloud-auth-sdk-wasm`
282291

283292
### Tauri apps

0 commit comments

Comments
 (0)