Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5e179e541 | |||
| 998e32e871 | |||
| 8535f4a767 | |||
| ce087533fa | |||
| 47327d90d8 |
@@ -0,0 +1,30 @@
|
||||
# Salon_Assistant / Lumina — environment configuration
|
||||
# Copy to .env and fill in real values. NEVER commit .env with real secrets.
|
||||
# See docs/INSTALL.md § S1 for details.
|
||||
|
||||
# ── Sandbox identity ───────────────────────────────────────────────────────
|
||||
# NemoClaw sandbox name (used by nemohermes commands). Default: hermes
|
||||
LUMINA_SANDBOX=hermes
|
||||
|
||||
# ── Inference (main model) ────────────────────────────────────────────────
|
||||
# OpenAI-compatible endpoint base URL (trailing slash optional).
|
||||
# Example: local llama.cpp server, remote API, etc.
|
||||
LUMINA_INFERENCE_BASE_URL=http://192.168.1.203:8083/v1
|
||||
|
||||
# Model identifier on the inference endpoint (must match what the server advertises).
|
||||
LUMINA_INFERENCE_MODEL=/home/ty/models/qwen36-27b-mtp-gguf/Qwen3.6-27B-UD-Q4_K_XL.gguf
|
||||
|
||||
# API key for the inference endpoint. Leave empty if the endpoint is unauthenticated.
|
||||
LUMINA_INFERENCE_API_KEY=
|
||||
|
||||
# ── Vision (auxiliary model) ───────────────────────────────────────────────
|
||||
# Vision-capable model for social media / media understanding.
|
||||
# Often the same as LUMINA_INFERENCE_MODEL if the main model is multimodal.
|
||||
LUMINA_VISION_MODEL=/home/ty/models/qwen36-27b-mtp-gguf/Qwen3.6-27B-UD-Q4_K_XL.gguf
|
||||
|
||||
# ── Gateway ────────────────────────────────────────────────────────────────
|
||||
# NemoClaw gateway URL (where OpenShell connects).
|
||||
LUMINA_GATEWAY_URL=https://127.0.0.1:8080
|
||||
|
||||
# Dashboard port (for nemohermes dashboard-url).
|
||||
LUMINA_DASHBOARD_PORT=18789
|
||||
+34
@@ -1,11 +1,45 @@
|
||||
# Secrets and local env (never commit)
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.pem
|
||||
*.key
|
||||
id_rsa*
|
||||
id_ed25519*
|
||||
credentials.json
|
||||
token.json
|
||||
secrets/
|
||||
**/secrets/
|
||||
|
||||
# Runtime / local state
|
||||
state/
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Python
|
||||
.venv/
|
||||
venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
.mypy_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
|
||||
# Node (if introduced later)
|
||||
node_modules/
|
||||
|
||||
# OS / editor noise
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*~
|
||||
|
||||
# Agent / IDE local overrides (do not commit personal agent state)
|
||||
CLAUDE.local.md
|
||||
.claude/settings.local.json
|
||||
.grok/local/
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Execution loop (mandatory — Salon_Assistant)
|
||||
|
||||
Full SSOT: repository root `AGENTS.md` → **Execution loop (mandatory)** and **Git hygiene (mandatory)**.
|
||||
|
||||
This rule exists so agents stay on rails **without** the user naming every slash command.
|
||||
|
||||
## Default loop
|
||||
|
||||
```text
|
||||
Authorize → Plan (if needed) → Build → Review → Check → Local commit (only if asked) → User pushes
|
||||
```
|
||||
|
||||
## Hard rails
|
||||
|
||||
1. **No product implementation** until the user says **build** or **implement**. Docs/design/agent-rules work is fine without that.
|
||||
2. **Do not require the user to restate process.** If they authorize a slice, run plan (when needed) → build → independent review → check yourself.
|
||||
3. **Grok parent = orchestrator only.** All product work (implement / review / check / explore / fix) runs on local **`primary-subagent`** subagents. See `local-worker.md`.
|
||||
4. **Review + check** on multi-file / non-trivial work via **worker** subagents (`model: "primary-subagent"`). Prefer `/review` + `/check-work` patterns that spawn workers. Parent self-LGTM is not enough.
|
||||
5. **Commit only when asked.** **Never push, open/merge PR, or tag** unless the user explicitly orders it. User owns `git push` by default.
|
||||
6. **Scope lock.** Implement only the authorized slice. No drive-by packages, no silent redesign of `design/` SSOT.
|
||||
7. **Subagents:** `model: "primary-subagent"` always (unless user overrides). Implementers edit; reviewers/explorers/verifiers read-only. Parallel writers use worktrees. Inject product hard rules into every worker prompt.
|
||||
8. **Multi-PR / execute-plan:** only with a PR Plan DAG; `--dry-run` first; no Graphite default, no auto-PR, no push/merge unless user overrides. Workers stay on `primary-subagent`. Remote MCP = **gitea_vps** only.
|
||||
9. **Stop** when the slice is done, blocked on a user decision, local worker is down, or secrets / destructive git / forge mistakes would be required — report, do not steamroll onto paid Grok.
|
||||
10. **Platform-first:** `nemohermes` / `openshell` only; no parallel control API; no owner terminal/Docker instructions; no agent pay / silent send.
|
||||
|
||||
## Tiny exception
|
||||
|
||||
Trivial one-file typo/docs nit: plan optional; still no unauthorized build; still no push; still no secrets.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Git hygiene (Salon_Assistant)
|
||||
|
||||
Mandatory. Full rules: repository root `AGENTS.md` → section **Git hygiene (mandatory)**.
|
||||
|
||||
Quick binding rules:
|
||||
|
||||
- **gitea_vps only** for this product’s remote Git/PR MCP (`Ty_Tech/Salon_Assistant`). Never `gitea_mcp_for_ty` or localhost Git MCP.
|
||||
- **No secrets** in commits, messages, PRs, or issues.
|
||||
- **No `git config`** changes.
|
||||
- **No force-push to `main`**, no `--no-verify`, no amend of published commits unless the user explicitly orders it.
|
||||
- **Commit / push / open-or-merge PR only when the user asks.**
|
||||
- Before commit: `status` + staged diff; stage explicit paths only; scan for credentials.
|
||||
- Feature work on named branches (`docs/`, `design/`, `chore/`, `fix/`, `feat/`); base PRs on `main`.
|
||||
- Confirm before destructive Git (hard reset, clean -fd, remote branch delete, history rewrite).
|
||||
- Subagents inherit these rules.
|
||||
|
||||
Detail and checklists: `AGENTS.md`, `CONTRIBUTING.md`, `SECURITY.md`.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Local worker + Grok orchestrator (mandatory)
|
||||
|
||||
Full SSOT: `AGENTS.md` → **Orchestrator vs local worker (mandatory)**.
|
||||
|
||||
## Split
|
||||
|
||||
| Role | Model | Job |
|
||||
|------|-------|-----|
|
||||
| Parent session (composer) | Grok (`grok-4.5` / session default) | Orchestrate only |
|
||||
| Every subagent / workflow child | **`primary-subagent`** (UI: **Primary Subagent**, `http://192.168.1.203:8083/v1`) | **All product work** |
|
||||
|
||||
## Orchestrator MUST
|
||||
|
||||
- Spawn workers with `model: "primary-subagent"` for explore / plan / implement / review / check / fix.
|
||||
- Inject product rails + authorized slice into every worker prompt.
|
||||
- Sequence the execution loop; talk to the user; enforce no push unless asked.
|
||||
- If local endpoint fails: **stop and report** — do not finish the work on paid Grok.
|
||||
|
||||
## Orchestrator MUST NOT
|
||||
|
||||
- Implement product code (`write` / `search_replace` / bulk codegen) in the parent when a worker should.
|
||||
- Omit `model` / use cloud models for workers without an explicit user override for that task.
|
||||
- Use the local worker as the parent orchestrator model.
|
||||
|
||||
## Exceptions (parent only)
|
||||
|
||||
Agent-rules edits (`AGENTS.md`, `.grok/rules/*`), pure Q&A, orientation reads, git inspect, local commit **when user asks**, kill/stop subagents, true one-line nits requested in-chat.
|
||||
|
||||
When unsure → **delegate to `primary-subagent`**.
|
||||
@@ -11,7 +11,12 @@ Remote: Gitea **Ty_Tech/Salon_Assistant** (via **gitea_vps** MCP only for this p
|
||||
2. [`design/use-cases.md`](design/use-cases.md)
|
||||
3. [`design/scenarios.md`](design/scenarios.md)
|
||||
4. [`design/DECISIONS.md`](design/DECISIONS.md)
|
||||
5. [`docs/README.md`](docs/README.md) (operator manuals)
|
||||
5. [`docs/README.md`](docs/README.md) (operator manuals)
|
||||
6. **[Execution loop](#execution-loop-mandatory)** — default harness rails (plan → build → review → check → commit)
|
||||
7. **[Git hygiene](#git-hygiene-mandatory)** — binding for every agent session
|
||||
8. [`CONTRIBUTING.md`](CONTRIBUTING.md) for PR/branch detail
|
||||
|
||||
Also auto-loaded under `.grok/rules/`: `execution-loop.md`, `git-hygiene.md`.
|
||||
|
||||
## Hard rules
|
||||
|
||||
@@ -20,10 +25,244 @@ Remote: Gitea **Ty_Tech/Salon_Assistant** (via **gitea_vps** MCP only for this p
|
||||
- **No** `gitea_mcp_for_ty` / localhost Git MCP for this product — **gitea_vps** only.
|
||||
- Owner never gets terminal/nano/docker instructions.
|
||||
- No agent pay; no silent send/publish.
|
||||
- **`design/`** = product POR SSOT; **`docs/`** = operator runbooks.
|
||||
|
||||
- **`design/`** = product POR SSOT; **`docs/`** = operator runbooks.
|
||||
- **Execution loop + Git hygiene below are mandatory** for every agent (Grok, Claude, Cursor, subagents, workflows, scripts).
|
||||
- **Do not require the user to restate process.** If they authorize work without naming every step, still run the rails. Only the user may *narrow* scope or *skip* a gate (e.g. “docs-only, skip check-work”).
|
||||
- **Orchestrator / worker split** — see [Orchestrator vs local worker](#orchestrator-vs-local-worker-mandatory). Grok (composer) orchestrates only; **all product work runs on the local subagent endpoint** (`primary-subagent`).
|
||||
## Git MCP
|
||||
|
||||
- Server: `gitea_vps`
|
||||
- URL: `https://giteamcp1.vps1.afterthedemo.com/mcp`
|
||||
- User-Agent: `grok-connectors-manager/`
|
||||
- Repo: **owner `Ty_Tech`**, **repo `Salon_Assistant`**, default branch **`main`**
|
||||
|
||||
---
|
||||
|
||||
## Orchestrator vs local worker (mandatory)
|
||||
|
||||
**Purpose:** conserve paid Grok tokens. The parent session is the **orchestrator**; heavy work runs on a **local** OpenAI-compatible endpoint.
|
||||
|
||||
| Role | Who | Model | Allowed to |
|
||||
|------|-----|-------|------------|
|
||||
| **Orchestrator** | Parent Grok Build session (composer) | Session model (e.g. `grok-4.5`) | Talk to user; authorize/scope; run the execution loop; spawn/resume/kill subagents; read results; enforce rails; **light** git status; report blockers |
|
||||
| **Worker** | Every subagent (and workflow `agent()` children) | **`primary-subagent`** (display: **Primary Subagent**, local) | Explore, plan drafts, implement, review, check, fix, write files, run tests in scope |
|
||||
|
||||
### Config (already expected on this host)
|
||||
|
||||
Home `~/.grok/config.toml` (not committed to product git):
|
||||
|
||||
- `[model.primary-subagent]`
|
||||
- `name = "Primary Subagent"`
|
||||
- `base_url = "http://192.168.1.203:8083/v1"`
|
||||
- `model` = GGUF id served by that endpoint (currently `…/Qwen3.6-27B-UD-Q4_K_XL.gguf`)
|
||||
- `[subagents] default_model = "primary-subagent"`
|
||||
- `[subagents.models]` pins: `general-purpose`, `explore`, `plan` → `primary-subagent`
|
||||
- **Removed:** stale `wwg3-worker` / `:8080` entries — do not reintroduce.
|
||||
|
||||
### Orchestrator MUST
|
||||
|
||||
1. **Delegate all product work** — implementation, multi-file edits, test writing, skill/script coding, substantive design drafting when authorized, independent review, and verification — via `spawn_subagent` (or workflow agents) on **`primary-subagent`**.
|
||||
2. **Pass `model: "primary-subagent"`** on every `spawn_subagent` for this product (belt-and-suspenders even when config pins exist). Do **not** pass `grok-4.5` / cloud models to workers unless the user **explicitly** overrides for that one task.
|
||||
3. **Stay coordinator-shaped:** status updates, sequencing, checklists, reading worker summaries, applying execution-loop gates, asking the user when blocked.
|
||||
4. **Inject full rails into every worker prompt:** authorized slice, hard rules, execution loop, Git hygiene, platform-first, no secrets, gitea_vps-only remote MCP.
|
||||
5. **Own Git policy** (commit only if asked; no push/PR/merge unless asked) even when workers make commits in worktrees.
|
||||
6. If the local endpoint is **down or failing**, **stop and report** — do not silently re-run the same workload on paid Grok “to finish.”
|
||||
|
||||
### Orchestrator MUST NOT
|
||||
|
||||
1. **Implement the product itself** with `write` / `search_replace` / large shell codegen on the main session when a worker can do it.
|
||||
2. “Just quickly” rewrite skills, services, scripts, or policy overlays in the parent after **build** is authorized — spawn a worker.
|
||||
3. Spawn workers **without** `model: "primary-subagent"` (or an explicit user override).
|
||||
4. Use `primary-subagent` as the **parent** session model for orchestration (parent stays Grok).
|
||||
5. Burn parent context by pasting entire file trees into chat when a worker can `read_file` / `grep` in a worktree.
|
||||
|
||||
### Allowed parent-session exceptions (small)
|
||||
|
||||
Parent Grok **may** do these without a worker:
|
||||
|
||||
- Answer questions; clarify scope; update **agent rails only** (`AGENTS.md`, `.grok/rules/*`, `CLAUDE.md`) when the user is changing process.
|
||||
- Single-line or pure typography nits the user asked for **in-chat** (still no unauthorized product build).
|
||||
- Read tools for orientation (`read_file`, `grep`, `list_dir`) to write a good worker prompt — not to replace the worker.
|
||||
- Git inspect (`status`/`diff`/`log`) and, **when the user asks**, a local commit of already-prepared work.
|
||||
- Emergency stop / kill runaway subagents.
|
||||
|
||||
If unsure whether something is “product work,” **delegate to `primary-subagent`**.
|
||||
|
||||
### Worker spawn defaults
|
||||
|
||||
```text
|
||||
subagent_type: general-purpose | explore | plan # as needed
|
||||
model: "primary-subagent" # always for this product
|
||||
isolation: "worktree" for parallel/implement writers; none for read-only explore/review when sharing tree is intended
|
||||
capability: read-only for review/explore; read-write or all for implement/fix
|
||||
```
|
||||
|
||||
Prefix `description` with role tags when using skill patterns: `[implementer]`, `[reviewer]`, `[checker]`, etc.
|
||||
|
||||
### Workflows
|
||||
|
||||
Rhai/`workflow` child agents inherit the same rule: product work on **local worker**, not paid parent. Prefer pinning via host subagent defaults; if a workflow supports model opts, set the local worker explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Execution loop (mandatory)
|
||||
|
||||
Default path for **any** non-trivial change. The user does **not** need to type each slash command; the agent **must still execute the gates**. Slash commands (`/plan`, `/review`, `/check-work`, `/execute-plan`, …) are preferred when available; equivalent manual steps (plan in chat, independent reviewer subagent, verifier subagent) are required if skills are not invoked by name.
|
||||
|
||||
```text
|
||||
Authorize → Plan (if needed) → Build → Review → Check → Local commit (if asked) → User pushes
|
||||
```
|
||||
|
||||
### Authorize (hard stop)
|
||||
|
||||
| User said… | Agent may… |
|
||||
|------------|------------|
|
||||
| **build** / **implement** (with or without slice) | Product implementation within stated or agreed slice |
|
||||
| Design / docs / hygiene / scaffolding structure only | Edit design, docs, agent rules, README scaffolds — **not** product runtime code until **build** |
|
||||
| Vague “continue” / “do it” **after** an approved plan or build slice | Continue **that** slice only; do not expand scope |
|
||||
| Nothing authorizing build | **No** product implementation |
|
||||
|
||||
If scope is unclear after **build**, ask **one** clarifying question or propose a minimal slice and wait — do **not** invent a multi-package rewrite.
|
||||
|
||||
### Default gates (do these without being asked)
|
||||
|
||||
1. **Plan** — If approach is ambiguous or multi-file architecture is involved: enter plan mode / write a short plan and get approval **or** follow an existing approved plan/design section. Skip plan only for trivial single-concern edits (typo, one obvious fix).
|
||||
2. **Build** — Implement only the authorized slice. Prefer main session for one sequential change.
|
||||
3. **Review** — Independent review of the diff (prefer `/review --local`, else a **read-only** reviewer subagent). Address **bugs** before claiming done; suggestions/nits: fix or note wontfix with reason.
|
||||
4. **Check** — Verify work (prefer `/check-work`, else a verifier subagent / `make verify` when implementation exists). Do not mark complete on green review alone.
|
||||
5. **Save** — Commit **only** when the user asks (commit / checkpoint / prepare local commit). **Never push, open/merge PR, or tag** unless the user explicitly asks. User owns `git push` by default.
|
||||
|
||||
### Subagents & orchestration (rails, not free-for-all)
|
||||
|
||||
| Rule | Requirement |
|
||||
|------|-------------|
|
||||
| Model | **Every** product subagent: `model: "primary-subagent"`. Parent stays Grok. See [Orchestrator vs local worker](#orchestrator-vs-local-worker-mandatory). |
|
||||
| Who codes | Workers implement/review/check. Parent does **not** implement product code. |
|
||||
| Roles | **Implementers** edit; **reviewers/explorers/verifiers** are read-only (no source edits). |
|
||||
| Isolation | Parallel writers → `isolation: "worktree"`. Do not parallel-edit the main tree. |
|
||||
| Scope | One concern per implementer. No “implement half the product” prompts. |
|
||||
| Product rules | Every implementer/reviewer prompt **must** include or inherit: this file’s hard rules, authorized slice, no secrets, platform-first, owner-safe messaging. |
|
||||
| Resume | Fix cycles: `resume_from` same implementer/reviewer. After ~4 review-fix rounds, escalate disagreement to the user or refresh reviewer — do not thrash forever silently. |
|
||||
| Orchestrator | Main Grok session (or `/execute-plan` orchestrator on Grok) owns sequencing, git policy, and user-facing status. Subagents do not push/merge. |
|
||||
| Multi-PR | Only with an explicit PR Plan DAG (`## PR Plan` / `### PR N:`). Prefer `/execute-plan --dry-run` first. This repo: **`--no-graphite`**, **no auto-PR**, **no push/merge** unless user overrides in writing. Implementers/reviewers still **`primary-subagent`**. |
|
||||
| Workflows | Allowed for repeatable gates; must not bypass authorize/review/check/git hygiene. Child agents = local worker. Effectful git still needs user-gated commit/push rules above. |
|
||||
| best-of-n | Only when user asks or two architectures must be compared with a clear eval; candidates on **`primary-subagent`**. |
|
||||
|
||||
### Stop conditions (do not “push through”)
|
||||
|
||||
Stop and report to the user when:
|
||||
|
||||
- Authorized slice is done (review + check clean, or issues listed).
|
||||
- Build was never authorized.
|
||||
- Plan is blocked on a product decision only the user can make.
|
||||
- Review/check finds **bugs** you cannot fix without expanding scope.
|
||||
- Secrets, destructive git, or forge mismatch (GitHub/`gh` assumptions on this Gitea product) would be required.
|
||||
- Subagent wants to rewrite `design/` SSOT or add a parallel control API.
|
||||
|
||||
### Anti-rails (forbidden defaults)
|
||||
|
||||
- Implementing product code because “we’re in execution mode” without **build** / **implement**.
|
||||
- **Parent Grok implementing product work** instead of spawning `primary-subagent`.
|
||||
- Spawning subagents on paid/cloud models for product work without explicit user override.
|
||||
- Silently falling back to Grok when the local worker is down.
|
||||
- Skipping review **and** check on a multi-file change.
|
||||
- Self-only review (same agent edits and declares “LGTM” with no independent pass).
|
||||
- Push/PR/merge/tag as a surprise side effect of “finishing.”
|
||||
- Expanding into neighboring packages/skills “while here.”
|
||||
- Using GitHub/`gh`/Graphite flows as defaults on this Gitea product.
|
||||
- Weakening these rules in a subdirectory `AGENTS.md` or subagent prompt.
|
||||
|
||||
### Tiny-change exception
|
||||
|
||||
Single-file or pure typo/docs nit: plan optional; still **no push**; still no secrets; still no build without authorize. Prefer a quick diff self-check; full `/review` + `/check-work` strongly recommended once tests/scripts exist.
|
||||
|
||||
### Related
|
||||
|
||||
- Git details: [Git hygiene](#git-hygiene-mandatory)
|
||||
- Human/agent PR conventions: [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
||||
- Short Grok rule mirror: [`.grok/rules/execution-loop.md`](.grok/rules/execution-loop.md)
|
||||
|
||||
---
|
||||
|
||||
## Git hygiene (mandatory)
|
||||
|
||||
Every agent that opens or works in this workspace **must** follow these practices. Deeper dirs inherit this file; do not weaken these rules in subdirectory `AGENTS.md` files.
|
||||
|
||||
### Identity of the remote
|
||||
|
||||
| Item | Value |
|
||||
|------|--------|
|
||||
| Hosted remote | Gitea `Ty_Tech/Salon_Assistant` |
|
||||
| Default branch | `main` |
|
||||
| Agent Git / PR MCP | **`gitea_vps` only** |
|
||||
| Forbidden | `gitea_mcp_for_ty`, localhost Git MCP, inventing a second product remote |
|
||||
|
||||
Prefer local `git` for status/diff/commit/branch on the workspace clone; use **`gitea_vps`** for remote PR/branch/file ops against the product repo when MCP is appropriate. Never mix this product’s Git ops onto another MCP server.
|
||||
|
||||
### Never (unless the user explicitly orders that exact action)
|
||||
|
||||
| Action | Why |
|
||||
|--------|-----|
|
||||
| `git config` (any scope) | Do not change user or repo identity/settings |
|
||||
| Commit or push **secrets** | `.env`, tokens, keys, OAuth dumps, provider credentials, PII exports |
|
||||
| Force-push to `main` (or rewrite its history) | Shared default branch is protected by practice |
|
||||
| `--force` / `--force-with-lease` push without explicit ask | Easy to destroy remote work |
|
||||
| `--no-verify` / skip hooks | Hooks exist to catch mistakes |
|
||||
| Amend a commit already pushed | Rewrites shared history |
|
||||
| `reset --hard`, `clean -fd`, branch delete on remote | Destructive; confirm first |
|
||||
| Push, open/merge PR, or tag release without user request | Shared/remote side effects need intent |
|
||||
| Commit unrelated dirty files “while you’re at it” | Keeps history reviewable |
|
||||
| Commit build artifacts, caches, venvs, logs, local IDE noise | Belongs in `.gitignore` |
|
||||
| Paste secrets into commit messages, PR bodies, or issues | Transcripts and remotes leak |
|
||||
|
||||
### Always
|
||||
|
||||
1. **Orient before mutating Git:** `git status`, `git diff` / `git diff --staged`, and recent `git log` (e.g. `git log -5 --oneline`) so commits match repo style.
|
||||
2. **Work on a branch** for anything beyond a trivial single-commit fix the user asked to land on `main`. Prefer names:
|
||||
- `docs/…`, `design/…`, `chore/…`, `fix/…`, `feat/…`
|
||||
- Base new branches on up-to-date `main` when practical.
|
||||
3. **Stage only intended paths** (`git add <paths>`). Never `git add -A` / `git add .` without reviewing the full set.
|
||||
4. **Scan the staged diff for secrets** before every commit (`.env*`, keys, tokens, cookies, private URLs with credentials).
|
||||
5. **Commit only when the user asks** to commit/save/checkpoint (or an explicit workflow that includes commit).
|
||||
6. **Commit messages:** concise focus line in imperative mood; 1–3 sentences of *why* when non-obvious. Match existing history tone. No secret data. No noisy AI trailer spam unless the user asks for a trailer.
|
||||
7. **Push only when the user asks.** Prefer upstream tracking (`-u`) on first push of a branch.
|
||||
8. **PRs via `gitea_vps`:** base `main`, clear title/body (summary + test plan / how verified), draft/WIP when incomplete. Do not merge unless asked.
|
||||
9. **Confirm before destructive or shared-visible actions:** force-push, hard reset, remote branch delete, PR merge/close, rewriting published commits, mass file deletes.
|
||||
10. **Leave the tree honest:** do not hide failures by amending away broken intermediate state the user still needs; do not discard uncommitted user work.
|
||||
11. **Respect “no build”:** do not commit product implementation until **build** / **implement** is authorized; docs/design/scaffold hygiene changes are fine when requested.
|
||||
12. **Subagents inherit these rules.** When spawning agents, point them at this file or restate: no secrets, no force to `main`, no push/PR/merge without user ask, `gitea_vps` only for product remote Git MCP.
|
||||
|
||||
### Commit checklist (before `git commit`)
|
||||
|
||||
```text
|
||||
[ ] User asked to commit (or workflow explicitly includes commit)
|
||||
[ ] Branch is correct (not accidental detached HEAD / wrong fork)
|
||||
[ ] git status + staged diff reviewed; only intended files staged
|
||||
[ ] No secrets, credentials, owner PII, or local absolute machine dumps
|
||||
[ ] No caches/venv/logs/state that should be gitignored
|
||||
[ ] Message explains purpose; matches repo style
|
||||
[ ] Hooks will run (do not pass --no-verify)
|
||||
```
|
||||
|
||||
### Push / PR checklist
|
||||
|
||||
```text
|
||||
[ ] User asked to push and/or open PR
|
||||
[ ] Branch name is descriptive
|
||||
[ ] Remote is origin → Ty_Tech/Salon_Assistant (Gitea)
|
||||
[ ] PR uses gitea_vps; base = main
|
||||
[ ] Description states what/why and how checked
|
||||
[ ] No secrets in PR text or screenshots paths
|
||||
```
|
||||
|
||||
### Recovering from mistakes
|
||||
|
||||
- Unstaged junk → restore files carefully; never `clean -fd` without listing what will die and getting confirmation.
|
||||
- Bad unpushed commit → amend only if HEAD is yours, not pushed, and user wants amend; otherwise new commit or interactive fix with consent.
|
||||
- Secret committed → **stop**; do not keep pushing. Tell the user; rotate credentials; remove from history only with explicit coordinated procedure (not casual `push --force` to `main`).
|
||||
|
||||
### Related docs
|
||||
|
||||
- Branch/PR conventions: [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
||||
- Security / no secrets in git: [`SECURITY.md`](SECURITY.md)
|
||||
- Ignore patterns: [`.gitignore`](.gitignore)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Claude / coding-agent instructions — Salon_Assistant
|
||||
|
||||
This directory is the **Salon_Assistant / Lumina** product seed (Gitea `Ty_Tech/Salon_Assistant`).
|
||||
|
||||
1. Read and follow **[AGENTS.md](AGENTS.md)** in full — product hard rules, **execution loop**, and **Git hygiene**.
|
||||
2. Design SSOT: `design/DESIGN_PLAN.md`, `design/use-cases.md`, `design/scenarios.md`, `design/DECISIONS.md`.
|
||||
3. Operator docs live under `docs/`; do not treat them as a second design SSOT.
|
||||
4. **No product implementation** until the user says **build** / **implement**.
|
||||
5. **Default rails without being asked:** plan (if needed) → build authorized slice → independent review → check → commit only if asked → **no push** unless asked.
|
||||
6. **Grok parent orchestrates only.** All product work goes to local **Primary Subagent** (`spawn_subagent` with `model: "primary-subagent"` → `http://192.168.1.203:8083/v1`). Do not implement product code in the parent.
|
||||
7. Git remote ops for this product: **`gitea_vps` only** — never `gitea_mcp_for_ty` or localhost Git MCP.
|
||||
8. Platform-first: `nemohermes` / `openshell` only; no parallel control API.
|
||||
9. Owner-facing answers must never include terminal, nano, or Docker instructions.
|
||||
10. Subagents inherit these rules; reviewers/verifiers stay read-only; parallel writers use worktrees.
|
||||
If any instruction conflicts, **this tree’s `AGENTS.md` wins** over parent workspace notes for product work inside `Salon_Assistant/`.
|
||||
+52
-4
@@ -5,6 +5,7 @@
|
||||
1. Read [design/DESIGN_PLAN.md](design/DESIGN_PLAN.md).
|
||||
2. Read [design/use-cases.md](design/use-cases.md), [design/scenarios.md](design/scenarios.md), [design/DECISIONS.md](design/DECISIONS.md).
|
||||
3. Implementation requires an explicit **build** decision for greenfield features; structural and documentation work follows this CONTRIBUTING guide.
|
||||
4. Coding agents must also follow **[AGENTS.md](AGENTS.md)** — especially **Execution loop (mandatory)** and **Git hygiene (mandatory)**. Agents must run those rails without the user naming every step.
|
||||
|
||||
## Platform-first
|
||||
|
||||
@@ -13,15 +14,51 @@
|
||||
|
||||
## Git ops for this product
|
||||
|
||||
- Remote: Gitea **Ty_Tech/Salon_Assistant**.
|
||||
- Agent Git MCP for this product: **gitea_vps** only (`https://giteamcp1.vps1.afterthedemo.com/mcp`, User-Agent `grok-connectors-manager/`).
|
||||
| Item | Value |
|
||||
|------|--------|
|
||||
| Remote | Gitea **Ty_Tech/Salon_Assistant** |
|
||||
| Default branch | `main` |
|
||||
| Agent Git MCP | **`gitea_vps` only** (`https://giteamcp1.vps1.afterthedemo.com/mcp`, User-Agent `grok-connectors-manager/`) |
|
||||
| Forbidden MCP | `gitea_mcp_for_ty`, localhost Git MCP for this product |
|
||||
|
||||
Local `git` on a clone is fine for day-to-day status/diff/commit. Use **gitea_vps** for remote PR/branch operations against the product repo when working as an agent with MCP.
|
||||
|
||||
## Branching
|
||||
|
||||
- Branch from current `main`.
|
||||
- Prefer prefixes: `docs/`, `design/`, `chore/`, `fix/`, `feat/`.
|
||||
- Keep branches focused; one concern per branch when practical.
|
||||
- Do not force-push `main`. Do not rewrite published history without explicit coordination.
|
||||
|
||||
## Commits
|
||||
|
||||
- **No secrets** in trees or messages (see [SECURITY.md](SECURITY.md) and `.gitignore`).
|
||||
- Stage only files that belong to the change; review `git status` and the staged diff.
|
||||
- Prefer small, reviewable commits with imperative subject lines and a short *why* when needed.
|
||||
- Do not skip hooks (`--no-verify`) unless an operator explicitly requires it for a broken-hook emergency.
|
||||
- Agents: commit/push only when the user asks (see AGENTS.md checklists).
|
||||
|
||||
## Pull requests
|
||||
|
||||
- Branch from `main`.
|
||||
- Base: `main`.
|
||||
- Title reflects the change; body includes summary and how it was verified.
|
||||
- Keep use-case SSOT in `design/use-cases.md` (do not duplicate full matrices in README).
|
||||
- Run `make verify` when implementation exists (fixture CI).
|
||||
- No secrets in commits.
|
||||
- Agents open/update PRs via **gitea_vps**; merge only when the user asks.
|
||||
- No secrets, tokens, or production PII in PR text.
|
||||
|
||||
### Suggested PR body shape
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
- …
|
||||
|
||||
## Verification
|
||||
- [ ] … (e.g. docs-only review / make verify / doctor)
|
||||
|
||||
## Notes
|
||||
- Related design decisions / use cases if any
|
||||
```
|
||||
|
||||
## Code style
|
||||
|
||||
@@ -33,3 +70,14 @@
|
||||
- Tag releases (`vX.Y.Z`).
|
||||
- Pin images and MCP package versions in the release manifest.
|
||||
- `scripts/upgrade.sh` is the operator upgrade entrypoint (see docs/UPGRADE.md).
|
||||
- Changelog: [CHANGELOG.md](CHANGELOG.md) (Keep a Changelog).
|
||||
|
||||
## Hygiene anti-patterns
|
||||
|
||||
| Avoid | Prefer |
|
||||
|-------|--------|
|
||||
| Commit `.env`, keys, OAuth dumps | OpenShell providers / host env; examples only as `.env.example` |
|
||||
| `git add .` without review | Explicit paths after `git status` / diff |
|
||||
| Drive-by refactors in unrelated PRs | Separate branch/PR |
|
||||
| Force-push to shared `main` | Revert commit or forward fix |
|
||||
| Agent “cleanup” that deletes unknown user files | Investigate; ask before destructive clean |
|
||||
|
||||
@@ -1,22 +1,44 @@
|
||||
# Salon_Assistant / Lumina — operator entrypoints (STUB until build)
|
||||
# Salon_Assistant / Lumina — operator entrypoints
|
||||
# Approved structure: document targets only; do not invoke unimplemented scripts.
|
||||
|
||||
.PHONY: help bootstrap install upgrade doctor verify sync-design
|
||||
.PHONY: help bootstrap install install-s0-s2 upgrade doctor verify sync-design
|
||||
|
||||
help:
|
||||
@echo "Salon_Assistant (Lumina) — structure phase (0.1.0-design)"
|
||||
@echo " Implementation not started. Explicit build/implement required."
|
||||
@echo "Salon_Assistant (Lumina) — $(shell cat VERSION 2>/dev/null || echo 'unknown')"
|
||||
@echo ""
|
||||
@echo "Intended targets (after build):"
|
||||
@echo " make bootstrap - host prereqs (Docker if missing)"
|
||||
@echo " make install - full install S0b–S6"
|
||||
@echo " make upgrade - product upgrade"
|
||||
@echo " make doctor - health checks"
|
||||
@echo " make verify - lint + tests + smoke (fixtures)"
|
||||
@echo " make sync-design - list design pack paths"
|
||||
@echo "Implemented targets:"
|
||||
@echo " make bootstrap - host prereqs (Docker if missing) [S0b]"
|
||||
@echo " make install - full install S0b–S2"
|
||||
@echo " make install-s0-s2 - install stages S0b through S2 (same as install)"
|
||||
@echo ""
|
||||
@echo "Staged install:"
|
||||
@echo " make install-s1 - S1 only: repository environment (.env)"
|
||||
@echo " make install-s2 - S2 only: model + vision config + smoke"
|
||||
@echo ""
|
||||
@echo "Not yet implemented (stubbed):"
|
||||
@echo " make upgrade - product upgrade"
|
||||
@echo " make doctor - health checks"
|
||||
@echo " make verify - lint + tests + smoke (fixtures)"
|
||||
@echo " make sync-design - list design pack paths"
|
||||
|
||||
bootstrap install upgrade doctor verify:
|
||||
@echo "not implemented — wait for explicit build/implement order" >&2; exit 1
|
||||
# ── Implemented targets ────────────────────────────────────────────────────
|
||||
|
||||
bootstrap:
|
||||
@bash scripts/bootstrap.sh
|
||||
|
||||
install install-s0-s2:
|
||||
@bash scripts/install.sh
|
||||
|
||||
install-s1:
|
||||
@bash scripts/install.sh --stage s1
|
||||
|
||||
install-s2:
|
||||
@bash scripts/install.sh --stage s2
|
||||
|
||||
# ── Stubbed targets (S3+ not yet implemented) ──────────────────────────────
|
||||
|
||||
upgrade doctor verify:
|
||||
@echo "not implemented — S3+ stages pending" >&2; exit 1
|
||||
|
||||
sync-design:
|
||||
@echo "Design SSOT:"
|
||||
|
||||
@@ -45,3 +45,4 @@ Code under `skills/`, `scripts/`, `services/`, `policy/`, etc. is **scaffold onl
|
||||
|
||||
- Gitea: `Ty_Tech/Salon_Assistant` (public)
|
||||
- Git MCP for this product: **`gitea_vps`** only
|
||||
- **Agents:** mandatory **execution loop**, **local-worker split** (Grok orchestrates; **Primary Subagent** / `primary-subagent` on `:8083` does product work), and **Git hygiene** in [`AGENTS.md`](AGENTS.md) (also `CLAUDE.md`, `.grok/rules/*`). Agents must follow the rails without the user restating process. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for branch/PR conventions.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- Agent never executes payments, refunds, or bill-pay.
|
||||
- Client outbound messages and social posts are draft-only unless a future gated send is explicitly designed.
|
||||
- Secrets never commit to git (use OpenShell providers / host env).
|
||||
- Agents: follow **Git hygiene** in [AGENTS.md](AGENTS.md) (secret scan before every commit; never put credentials in messages/PRs).
|
||||
- Owner never receives host shell, Docker, or editor instructions from the assistant.
|
||||
|
||||
## Reporting
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# /implement queue (first 5 tasks)
|
||||
|
||||
Paste **one line** at a time into Grok Build (composer). Do not paste the whole file.
|
||||
|
||||
## Task 1
|
||||
```
|
||||
/implement S0-S2 only per design/DESIGN_PLAN.md sec 4: host/docs, docker-if-missing, env, model+vision via nemohermes/openshell, vision smoke. No skills SaaS S3-S7. AGENTS.md. No push.
|
||||
```
|
||||
|
||||
## Task 2
|
||||
```
|
||||
/implement S3-S5 only per design/DESIGN_PLAN.md sec 4: compose/nemohermes stack, sandbox from package, policy from policy/openshell, skills sync. No doctor S6, no connect S7, no live SaaS. AGENTS.md. No push.
|
||||
```
|
||||
|
||||
## Task 3
|
||||
```
|
||||
/implement S6 doctor only per design/DESIGN_PLAN.md sec 4 and docs/OPERATIONS.md: health checks Docker/nemohermes/policy/skills/inference. No OAuth/channels. AGENTS.md. No push.
|
||||
```
|
||||
|
||||
## Task 4
|
||||
```
|
||||
/implement use case A1 daily-board per design/use-cases.md: skills/_lib + fixtures + skills/daily-board fixtures-only. No live Vagaro/Square. AGENTS.md. No push.
|
||||
```
|
||||
|
||||
## Task 5
|
||||
```
|
||||
/implement use case E1 setup-education per design/use-cases.md and docs/SETUP_UX.md: owner-safe connect education + capability report shape, fixtures/stubs only no real secrets. AGENTS.md. No push.
|
||||
```
|
||||
+37
-11
@@ -1,29 +1,55 @@
|
||||
# Deployer host baselining
|
||||
|
||||
**Status:** Outline from approved design POR. Procedural scripts at **build**.
|
||||
**Status:** Procedural — ready for operator use.
|
||||
|
||||
## Purpose
|
||||
|
||||
Create a clean host that can run the Docker-based Lumina stack. This product is **repo-driven**, not tied to one developer’s existing Hermes install.
|
||||
Create a clean host that can run the Docker-based Lumina stack. This product is **repo-driven**, not tied to one developer's existing Hermes install.
|
||||
|
||||
## Requirements (POR)
|
||||
## Requirements
|
||||
|
||||
| Item | Spec |
|
||||
|------|------|
|
||||
| OS | Linux x86_64 or arm64 (cloud VM, bare metal; WSL2 optional path only) |
|
||||
| Resources | Multi-core CPU; ≥8–16 GB RAM recommended; disk for Docker images |
|
||||
| Privileges | User able to install Docker / run Compose |
|
||||
| Privileges | User able to install Docker / run Compose (sudo access) |
|
||||
| Network | Outbound HTTPS to model endpoint, SaaS APIs, MCP remotes, image registry |
|
||||
| GPU | Optional; only if local VLM is used (not required if vision is remote) |
|
||||
|
||||
## Steps (operator)
|
||||
## Prerequisites (before running scripts)
|
||||
|
||||
1. Provision host (cloud console / hypervisor).
|
||||
2. Install base OS updates.
|
||||
3. Create deploy user; clone this repository.
|
||||
4. Run `./scripts/bootstrap.sh` (installs Docker if missing — when implemented).
|
||||
5. Continue with [INSTALL.md](INSTALL.md).
|
||||
1. **Provision host** — cloud console, hypervisor, or bare metal.
|
||||
2. **Install base OS updates** — `sudo apt update && sudo apt upgrade -y` (Debian/Ubuntu) or equivalent.
|
||||
3. **Create deploy user** — a non-root user with sudo access.
|
||||
4. **Install NemoClaw CLIs** — `nemohermes` and `openshell` must be on PATH. See [NemoClaw docs](https://docs.nvidia.com/nemoclaw/latest/user-guide/hermes/reference/architecture.md) for installation.
|
||||
5. **Clone this repository** — `git clone <repo-url> && cd Salon_Assistant`.
|
||||
|
||||
## Bootstrap (S0b)
|
||||
|
||||
Run the bootstrap script to install Docker if missing:
|
||||
|
||||
```bash
|
||||
./scripts/bootstrap.sh
|
||||
```
|
||||
|
||||
Or via Make:
|
||||
|
||||
```bash
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Detects if Docker is already installed and running (idempotent).
|
||||
- If missing, installs Docker via the official convenience script (Debian/Ubuntu) or dnf (RHEL/Fedora).
|
||||
- Adds the current user to the `docker` group.
|
||||
- Verifies the Docker daemon is reachable.
|
||||
|
||||
**If Docker is already present:** the script detects it and exits immediately with a success message.
|
||||
|
||||
## Continue with install
|
||||
|
||||
After bootstrap, proceed to [INSTALL.md](INSTALL.md) for stages S1–S2.
|
||||
|
||||
## Design reference
|
||||
|
||||
[design/DESIGN_PLAN.md](../design/DESIGN_PLAN.md) § Docker packaging and install stages.
|
||||
[design/DESIGN_PLAN.md](../design/DESIGN_PLAN.md) §4 Docker packaging and install stages.
|
||||
|
||||
+72
-8
@@ -1,13 +1,77 @@
|
||||
# Hermes model configuration
|
||||
|
||||
**Status:** Outline from approved design.
|
||||
**Status:** S2-ready — sufficient for operators configuring models.
|
||||
|
||||
| Slot | Requirement |
|
||||
|------|-------------|
|
||||
| **Main** | OpenAI-compatible chat + tools; endpoint **may be outside Docker** |
|
||||
| **Auxiliary `vision`** | **Required** for media social; install smoke test |
|
||||
| **Other aux** | Default to same base/main endpoint unless overridden |
|
||||
## Model slots
|
||||
|
||||
Configured via NemoClaw inference + generated Hermes config (`nemohermes inference set` / sealed config), not owner terminal editing.
|
||||
| Slot | Requirement | Configured via |
|
||||
|------|-------------|----------------|
|
||||
| **Main** | OpenAI-compatible chat + tools; endpoint **may be outside Docker** | `openshell inference set` + `.env` |
|
||||
| **Auxiliary `vision`** | **Required** for media social; install smoke test | `.env` + vision smoke |
|
||||
| **Other aux** | Default to same base/main endpoint unless overridden | `.env` |
|
||||
|
||||
See [design/DESIGN_PLAN.md](../design/DESIGN_PLAN.md) and [Hermes Configuring Models](https://hermes-agent.nousresearch.com/docs/user-guide/configuring-models).
|
||||
## Environment variables
|
||||
|
||||
All model configuration lives in `.env`. See `.env.example` for the template.
|
||||
|
||||
| Variable | Purpose | Example |
|
||||
|----------|---------|---------|
|
||||
| `LUMINA_INFERENCE_BASE_URL` | OpenAI-compatible endpoint base URL | `http://192.168.1.203:8083/v1` |
|
||||
| `LUMINA_INFERENCE_MODEL` | Main model identifier (must match what the server advertises) | `/home/ty/models/…/Qwen3.6-27B-UD-Q4_K_XL.gguf` |
|
||||
| `LUMINA_INFERENCE_API_KEY` | API key for the inference endpoint (empty if unauthenticated) | *(leave empty for local endpoints)* |
|
||||
| `LUMINA_VISION_MODEL` | Vision-capable model for social media / media understanding | Same as main if multimodal |
|
||||
| `LUMINA_SANDBOX` | Sandbox name (used by `nemohermes` commands) | `hermes` |
|
||||
|
||||
## Configuring the main model
|
||||
|
||||
The main model is configured through the OpenShell gateway:
|
||||
|
||||
```bash
|
||||
openshell inference set \
|
||||
--provider compatible-endpoint \
|
||||
--model "$LUMINA_INFERENCE_MODEL" \
|
||||
--no-verify
|
||||
```
|
||||
|
||||
This is done automatically by `scripts/install/s2-models.sh` when the gateway is connected.
|
||||
|
||||
**Provider name:** `compatible-endpoint` is the NemoClaw provider for OpenAI-compatible endpoints (llama.cpp, Ollama, vLLM, etc.).
|
||||
|
||||
**Note:** `openshell inference set` takes `--provider` and `--model` but **not** `--url`. The gateway resolves the endpoint URL from its own metadata (the compatible-endpoint provider reads the URL from the gateway config). The install script verifies the endpoint is reachable before calling `openshell inference set`.
|
||||
|
||||
## Vision model
|
||||
|
||||
The vision model must support multimodal inputs (images). The install smoke test verifies this:
|
||||
|
||||
1. Probes the models endpoint (`$LUMINA_INFERENCE_BASE_URL/models`) for a `multimodal` capability tag.
|
||||
2. Falls back to a chat completion probe if tags are not available.
|
||||
|
||||
If the main model is already multimodal, set `LUMINA_VISION_MODEL` to the same value as `LUMINA_INFERENCE_MODEL`.
|
||||
|
||||
## Checking current configuration
|
||||
|
||||
```bash
|
||||
# See what the gateway has configured
|
||||
openshell inference get
|
||||
|
||||
# See what models the endpoint advertises
|
||||
curl -s "$LUMINA_INFERENCE_BASE_URL/models" | jq '.models[].name'
|
||||
|
||||
# Check sandbox status
|
||||
nemohermes <sandbox-name> status
|
||||
```
|
||||
|
||||
## Runtime changes
|
||||
|
||||
Per NemoClaw's runtime change matrix:
|
||||
- **Inference route changes** are typically hot (no rebuild needed).
|
||||
- **Auxiliary model changes** may require a gateway restart.
|
||||
- Always use `nemohermes` / `openshell` for mutations — never hand-edit in-sandbox config.
|
||||
|
||||
See [design/DESIGN_PLAN.md](../design/DESIGN_PLAN.md) §3.4 and [NemoClaw runtime changes docs](https://docs.nvidia.com/nemoclaw/latest/user-guide/hermes/manage-sandboxes/configure-sandboxes/understand-runtime-changes.md).
|
||||
|
||||
## References
|
||||
|
||||
- [Hermes Configuring Models](https://hermes-agent.nousresearch.com/docs/user-guide/configuring-models)
|
||||
- [Hermes Configuration](https://hermes-agent.nousresearch.com/docs/user-guide/configuration)
|
||||
- [design/research/platform-notes.md](../design/research/platform-notes.md)
|
||||
|
||||
+99
-15
@@ -1,31 +1,115 @@
|
||||
# Install
|
||||
|
||||
**Status:** Outline from approved design. Scripts stubbed until **build**.
|
||||
**Status:** Stages S0–S2 implemented. S3–S7 pending.
|
||||
|
||||
## Stages
|
||||
|
||||
| Stage | Where | Outcome |
|
||||
|-------|--------|---------|
|
||||
| S0 | Human | Host per [DEPLOYER_HOST.md](DEPLOYER_HOST.md) |
|
||||
| S0b | Host script | Docker installed if missing |
|
||||
| S1 | Host script | Repo env, `.env` from `.env.example` |
|
||||
| S2 | Host script | Main + aux vision config; vision smoke |
|
||||
| S3–S5 | Host → Compose / `nemohermes` | Stack, sandbox, policy, skills |
|
||||
| S6 | Host script | Doctor green |
|
||||
| S7 | Owner + operator connect helpers | Name assistant; connect **their** SaaS/channels |
|
||||
| Stage | Where | Outcome | Status |
|
||||
|-------|--------|---------|--------|
|
||||
| S0 | Human | Host per [DEPLOYER_HOST.md](DEPLOYER_HOST.md) | ✅ Procedural |
|
||||
| S0b | Host script | Docker installed if missing | ✅ Implemented |
|
||||
| S1 | Host script | Repo env, `.env` from `.env.example` | ✅ Implemented |
|
||||
| S2 | Host script | Main + aux vision config; vision smoke | ✅ Implemented |
|
||||
| S3–S5 | Host → Compose / `nemohermes` | Stack, sandbox, policy, skills | ⏳ Pending |
|
||||
| S6 | Host script | Doctor green | ⏳ Pending |
|
||||
| S7 | Owner + operator connect helpers | Name assistant; connect **their** SaaS/channels | ⏳ Pending |
|
||||
|
||||
## Platform commands (normative)
|
||||
|
||||
All mutations: `nemohermes` / `openshell` (see [design/research/platform-notes.md](../design/research/platform-notes.md)).
|
||||
|
||||
## Intended entrypoints
|
||||
---
|
||||
|
||||
## S0: Host baselining
|
||||
|
||||
Follow [DEPLOYER_HOST.md](DEPLOYER_HOST.md) to prepare the host:
|
||||
|
||||
1. Provision a Linux host (cloud VM, bare metal).
|
||||
2. Install OS updates.
|
||||
3. Create a deploy user with sudo access.
|
||||
4. Install `nemohermes` and `openshell` CLIs.
|
||||
5. Clone this repository.
|
||||
|
||||
## S0b: Docker bootstrap
|
||||
|
||||
```bash
|
||||
./scripts/bootstrap.sh
|
||||
./scripts/install.sh
|
||||
./scripts/doctor.sh
|
||||
# or
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
## After install
|
||||
**Idempotent:** if Docker is already installed and running, the script exits immediately.
|
||||
|
||||
[SETUP_UX.md](SETUP_UX.md) · [design/scenarios.md](../design/scenarios.md) (S1–S5).
|
||||
**What it checks:**
|
||||
- `docker info` succeeds → skip install.
|
||||
- OS family detection (Debian/Ubuntu or RHEL/Fedora).
|
||||
- Installs Docker, adds user to `docker` group, verifies daemon.
|
||||
|
||||
## S1: Repository environment
|
||||
|
||||
```bash
|
||||
./scripts/install/s1-env.sh
|
||||
# or
|
||||
make install-s1
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
1. If `.env` does not exist, copies `.env.example` to `.env`.
|
||||
2. Validates that all required keys are present and non-empty:
|
||||
- `LUMINA_SANDBOX` — sandbox name (default: `hermes`)
|
||||
- `LUMINA_INFERENCE_BASE_URL` — inference endpoint URL
|
||||
- `LUMINA_INFERENCE_MODEL` — model identifier
|
||||
- `LUMINA_VISION_MODEL` — vision model identifier
|
||||
3. Fails with clear messages if keys are missing or empty.
|
||||
|
||||
**Edit `.env`** with your actual values before running S2. See `.env.example` for all keys and descriptions.
|
||||
|
||||
**Never commit `.env` with real secrets.** It is in `.gitignore`.
|
||||
|
||||
## S2: Model + vision configuration
|
||||
|
||||
```bash
|
||||
./scripts/install/s2-models.sh
|
||||
# or
|
||||
make install-s2
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
1. Loads `.env` and validates required keys.
|
||||
2. Checks that `nemohermes` and `openshell` CLIs are available.
|
||||
3. Verifies the inference endpoint is reachable (`/v1/models`).
|
||||
4. If the OpenShell gateway is connected, configures the inference route via `openshell inference set`.
|
||||
5. Runs the **vision smoke test**:
|
||||
- Probes `/v1/models` for `multimodal` capability tag.
|
||||
- Falls back to a minimal chat completion if capability tags are not exposed.
|
||||
6. **Fails loudly** (non-zero exit) if vision capability cannot be confirmed.
|
||||
|
||||
**If the gateway is not yet connected:** the script validates the endpoint and skips the `openshell` write. S3+ will handle full gateway configuration.
|
||||
|
||||
## Run all stages (S0b–S2)
|
||||
|
||||
```bash
|
||||
./scripts/install.sh
|
||||
# or
|
||||
make install
|
||||
# or
|
||||
make install-s0-s2
|
||||
```
|
||||
|
||||
## After install (S0–S2)
|
||||
|
||||
- Verify `.env` values are correct for your environment.
|
||||
- Continue with S3+ when implemented (compose stack, sandbox creation, policy, skills).
|
||||
- See [SETUP_UX.md](SETUP_UX.md) for owner-facing setup after full install.
|
||||
- See [design/scenarios.md](../design/scenarios.md) (S1–S5) for operational scenarios.
|
||||
|
||||
## UAT host notes
|
||||
|
||||
This repository was tested on a live host with:
|
||||
- `nemohermes` v0.0.93, `openshell` v0.0.85
|
||||
- Sandbox: `hermes` (default)
|
||||
- Gateway: `nemoclaw` at `https://127.0.0.1:8080`
|
||||
- Inference: OpenAI-compatible endpoint with multimodal model
|
||||
- Docker: present and running
|
||||
|
||||
Scripts are idempotent on this configuration.
|
||||
|
||||
+43
-13
@@ -1,19 +1,49 @@
|
||||
# Host scripts (scaffold)
|
||||
# Host scripts
|
||||
|
||||
**Status:** Documented entrypoints only — no executable bodies until **build**.
|
||||
**Status:** S0b–S2 implemented. S3–S7 pending.
|
||||
|
||||
All scripts must wrap **`nemohermes` / `openshell` / Docker**. No parallel control API.
|
||||
All scripts wrap **`nemohermes` / `openshell` / Docker**. No parallel control API.
|
||||
|
||||
## Intended entrypoints
|
||||
## Entrypoints
|
||||
|
||||
| Script | Role |
|
||||
|--------|------|
|
||||
| `bootstrap.sh` | Host prereqs; install Docker if missing |
|
||||
| `install.sh` | Stages S0b–S6 |
|
||||
| `upgrade.sh` | Snapshot, pull pins, migrate, re-apply policy, doctor |
|
||||
| `doctor.sh` | Health checks |
|
||||
| `connect/*.sh` | Operator connect helpers (Square, QBO, Vagaro, channels) |
|
||||
| `install/` | Stage helpers |
|
||||
| `lib/` | Shared shell helpers |
|
||||
| Script | Role | Status |
|
||||
|--------|------|--------|
|
||||
| `bootstrap.sh` | Host prereqs; install Docker if missing | ✅ S0b |
|
||||
| `install.sh` | Staged installer (S0b–S2) | ✅ S0b–S2 |
|
||||
| `install/s1-env.sh` | S1: create/validate `.env` | ✅ S1 |
|
||||
| `install/s2-models.sh` | S2: model + vision config + smoke | ✅ S2 |
|
||||
| `upgrade.sh` | Snapshot, pull pins, migrate, re-apply policy, doctor | ⏳ Pending |
|
||||
| `doctor.sh` | Health checks | ⏳ Pending |
|
||||
| `connect/*.sh` | Operator connect helpers (Square, QBO, Vagaro, channels) | ⏳ Pending |
|
||||
|
||||
## Shared library
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `lib/common.sh` | Logging, CLI detection, env loading, repo root |
|
||||
| `lib/env.sh` | `.env` validation and creation helpers |
|
||||
| `lib/vision_smoke.sh` | Vision capability smoke test |
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Bootstrap (Docker if missing)
|
||||
./scripts/bootstrap.sh
|
||||
|
||||
# Full install (S0b–S2)
|
||||
./scripts/install.sh
|
||||
|
||||
# Individual stages
|
||||
./scripts/install.sh --stage s1 # env only
|
||||
./scripts/install.sh --stage s2 # models only
|
||||
|
||||
# Or via Make
|
||||
make bootstrap
|
||||
make install
|
||||
make install-s1
|
||||
make install-s2
|
||||
```
|
||||
|
||||
## Design reference
|
||||
|
||||
See [docs/INSTALL.md](../docs/INSTALL.md), [docs/UPGRADE.md](../docs/UPGRADE.md), [design/updates-lifecycle.md](../design/updates-lifecycle.md).
|
||||
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/bootstrap.sh — S0b: host bootstrap (Docker install-if-missing)
|
||||
#
|
||||
# Idempotent: detects Docker and skips install if already present.
|
||||
# Linux only (Debian/Ubuntu/RHEL/Fedora families).
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/bootstrap.sh
|
||||
# make bootstrap
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Source shared helpers
|
||||
# shellcheck source=lib/common.sh
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
|
||||
log_section "S0b: Host bootstrap"
|
||||
warn_if_root
|
||||
|
||||
# ── Check Docker ───────────────────────────────────────────────────────────
|
||||
if docker_available; then
|
||||
log_info "Docker already installed ($(docker --version))"
|
||||
log_info "Bootstrap complete — nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_warn "Docker not found or not running."
|
||||
|
||||
# ── Detect OS family ──────────────────────────────────────────────────────
|
||||
detect_os_family() {
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
# Sourced in a subshell via $() — OS variables do not leak to caller scope.
|
||||
. /etc/os-release
|
||||
echo "${ID_LIKE:-$ID}"
|
||||
else
|
||||
log_error "Cannot detect OS family (no /etc/os-release)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
OS_FAMILY="$(detect_os_family)"
|
||||
log_info "Detected OS family: $OS_FAMILY"
|
||||
|
||||
install_docker_debian() {
|
||||
log_info "Installing Docker (Debian/Ubuntu) via official convenience script…"
|
||||
if ! curl -fsSL https://get.docker.com | sh; then
|
||||
log_error "Docker install script failed."
|
||||
log_error "Install Docker manually, then re-run this script."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Add current user to docker group (requires sudo)
|
||||
local user="${SUDO_USER:-$(whoami)}"
|
||||
if [[ -n "$user" ]] && getent group docker &>/dev/null; then
|
||||
log_info "Adding user '$user' to docker group…"
|
||||
sudo usermod -aG docker "$user" 2>/dev/null || true
|
||||
log_warn "Log out and back in (or run 'newgrp docker') for group changes to take effect."
|
||||
fi
|
||||
}
|
||||
|
||||
install_docker_rhel() {
|
||||
log_info "Installing Docker (RHEL/Fedora) via dnf…"
|
||||
sudo dnf install -y dnf-utils 2>/dev/null || sudo dnf install -ydnf-plugins-core 2>/dev/null || true
|
||||
sudo dnf config-manager --add-repo https://download.docker.com/linux/$(echo "$OS_FAMILY" | head -c3)/docker-ce.repo 2>/dev/null || {
|
||||
log_warn "Could not add Docker repo. Trying generic install…"
|
||||
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin || {
|
||||
log_error "Docker install failed. Install manually and re-run."
|
||||
return 1
|
||||
}
|
||||
}
|
||||
sudo systemctl enable --now docker
|
||||
}
|
||||
|
||||
# ── Install ────────────────────────────────────────────────────────────────
|
||||
if echo "$OS_FAMILY" | grep -qiE 'debian|ubuntu'; then
|
||||
install_docker_debian
|
||||
elif echo "$OS_FAMILY" | grep -qiE 'rhel|fedora|centos|rocky|almalinux'; then
|
||||
install_docker_rhel
|
||||
else
|
||||
log_error "Unsupported OS family: $OS_FAMILY"
|
||||
log_error "Supported: Debian/Ubuntu, RHEL/Fedora/CentOS/Rocky/AlmaLinux"
|
||||
log_error "Install Docker manually, then re-run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Verify ─────────────────────────────────────────────────────────────────
|
||||
if docker_available; then
|
||||
log_info "Docker installed successfully: $(docker --version)"
|
||||
log_info "Bootstrap complete."
|
||||
else
|
||||
log_error "Docker install appeared to succeed but daemon is not reachable."
|
||||
log_error "Check Docker service: sudo systemctl status docker"
|
||||
exit 1
|
||||
fi
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/install.sh — Lumina staged installer
|
||||
#
|
||||
# Runs install stages S0b–S2 (S3+ not yet implemented).
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/install.sh # run all implemented stages (S0b–S2)
|
||||
# ./scripts/install.sh --stage s1 # run only S1
|
||||
# ./scripts/install.sh --stage s2 # run only S2
|
||||
# ./scripts/install.sh --help
|
||||
#
|
||||
# All stages are idempotent. Re-running is safe.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Source shared helpers
|
||||
# shellcheck source=lib/common.sh
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
|
||||
# ── Usage ──────────────────────────────────────────────────────────────────
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
Run Lumina install stages (S0b–S2 implemented).
|
||||
|
||||
Options:
|
||||
--stage <s1|s2> Run only the specified stage
|
||||
--help Show this help
|
||||
|
||||
Stages:
|
||||
S0b Docker install-if-missing (bootstrap)
|
||||
S1 Repository environment (.env)
|
||||
S2 Model + vision configuration + smoke test
|
||||
|
||||
All stages are idempotent.
|
||||
|
||||
Examples:
|
||||
$(basename "$0") # run S0b → S1 → S2
|
||||
$(basename "$0") --stage s1 # run only S1 (env)
|
||||
$(basename "$0") --stage s2 # run only S2 (models)
|
||||
EOF
|
||||
}
|
||||
|
||||
# ── Parse args ─────────────────────────────────────────────────────────────
|
||||
SINGLE_STAGE=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--help|-h) usage; exit 0 ;;
|
||||
--stage)
|
||||
shift
|
||||
SINGLE_STAGE="${1:-}"
|
||||
if [[ -z "$SINGLE_STAGE" ]]; then
|
||||
log_error "--stage requires a value (s1 or s2)"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown argument: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Run stages ─────────────────────────────────────────────────────────────
|
||||
run_s0b() {
|
||||
log_section "S0b: Docker bootstrap"
|
||||
bash "$SCRIPT_DIR/bootstrap.sh"
|
||||
}
|
||||
|
||||
run_s1() {
|
||||
bash "$SCRIPT_DIR/install/s1-env.sh"
|
||||
}
|
||||
|
||||
run_s2() {
|
||||
bash "$SCRIPT_DIR/install/s2-models.sh"
|
||||
}
|
||||
|
||||
log_section "Lumina installer (stages S0b–S2)"
|
||||
warn_if_root
|
||||
|
||||
if [[ -n "$SINGLE_STAGE" ]]; then
|
||||
case "$SINGLE_STAGE" in
|
||||
s0b) run_s0b ;;
|
||||
s1) run_s1 ;;
|
||||
s2) run_s2 ;;
|
||||
*)
|
||||
log_error "Unknown stage: $SINGLE_STAGE"
|
||||
log_error "Valid stages: s0b, s1, s2"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Run all implemented stages in order
|
||||
run_s0b
|
||||
run_s1
|
||||
run_s2
|
||||
fi
|
||||
|
||||
log_section "Install complete (S0b–S2)"
|
||||
log_info "Next steps:"
|
||||
log_info " - Review .env for correctness"
|
||||
log_info " - Continue with S3+ when implemented (compose, sandbox, policy)"
|
||||
log_info " - See docs/INSTALL.md for full procedure"
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/install/s1-env.sh — S1: repo environment
|
||||
#
|
||||
# Creates .env from .env.example if needed, then validates required keys.
|
||||
# Never commits real secrets.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/install/s1-env.sh
|
||||
# (called by install.sh --stage s1)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Source shared helpers (common.sh sets REPO_ROOT via git rev-parse)
|
||||
# shellcheck source=../lib/common.sh
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
# shellcheck source=../lib/env.sh
|
||||
source "$SCRIPT_DIR/../lib/env.sh"
|
||||
|
||||
log_section "S1: Repository environment"
|
||||
|
||||
# ── Create .env from example if needed ─────────────────────────────────────
|
||||
create_env_from_example
|
||||
|
||||
# ── Validate ───────────────────────────────────────────────────────────────
|
||||
validate_env
|
||||
|
||||
log_info "S1 complete: .env is valid."
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/install/s2-models.sh — S2: model + aux vision config
|
||||
#
|
||||
# Configures inference via openshell/nemohermes and runs vision smoke test.
|
||||
# Platform-first: all mutations through nemohermes/openshell CLIs.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/install/s2-models.sh
|
||||
# (called by install.sh --stage s2)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Source shared helpers (common.sh sets REPO_ROOT via git rev-parse)
|
||||
# shellcheck source=../lib/common.sh
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
# shellcheck source=../lib/env.sh
|
||||
source "$SCRIPT_DIR/../lib/env.sh"
|
||||
# shellcheck source=../lib/vision_smoke.sh
|
||||
source "$SCRIPT_DIR/../lib/vision_smoke.sh"
|
||||
|
||||
log_section "S2: Model + vision configuration"
|
||||
|
||||
# ── Load .env ──────────────────────────────────────────────────────────────
|
||||
load_env
|
||||
|
||||
# ── Validate required keys ─────────────────────────────────────────────────
|
||||
validate_env || exit 1
|
||||
|
||||
# ── Check CLI prerequisites ────────────────────────────────────────────────
|
||||
require_cmd openshell "Install OpenShell CLI (part of NemoClaw platform)"
|
||||
require_cmd nemohermes "Install nemohermes CLI (part of NemoClaw platform)"
|
||||
|
||||
# ── Verify inference endpoint is reachable ─────────────────────────────────
|
||||
# LUMINA_INFERENCE_BASE_URL includes /v1 (e.g. http://host:port/v1)
|
||||
log_info "Verifying inference endpoint: $LUMINA_INFERENCE_BASE_URL"
|
||||
if ! curl -sf --max-time 15 "${LUMINA_INFERENCE_BASE_URL}/models" &>/dev/null; then
|
||||
log_error "Inference endpoint unreachable at $LUMINA_INFERENCE_BASE_URL"
|
||||
log_error "Check that the model server is running and the URL is correct."
|
||||
log_error "Fix LUMINA_INFERENCE_BASE_URL in .env and re-run."
|
||||
exit 1
|
||||
fi
|
||||
log_info "Inference endpoint reachable."
|
||||
|
||||
# ── Configure inference via openshell ──────────────────────────────────────
|
||||
# Only set if the gateway is connected and we can reach it.
|
||||
# If the gateway is not yet set up, we validate the endpoint and skip
|
||||
# the openshell write (S3+ will handle full gateway config).
|
||||
log_info "Checking OpenShell gateway status…"
|
||||
if openshell status &>/dev/null 2>&1; then
|
||||
log_info "Gateway connected — configuring inference route…"
|
||||
|
||||
# Use openshell inference set to configure the main model.
|
||||
# openshell inference set takes --provider and --model but NOT --url.
|
||||
# The gateway resolves the endpoint URL from its own metadata (the
|
||||
# compatible-endpoint provider reads the URL from the gateway config).
|
||||
# --no-verify skips the endpoint verification that openshell does internally
|
||||
# since we already verified above.
|
||||
if openshell inference set \
|
||||
--provider compatible-endpoint \
|
||||
--model "$LUMINA_INFERENCE_MODEL" \
|
||||
--no-verify 2>&1; then
|
||||
log_info "Inference route configured via openshell."
|
||||
else
|
||||
log_warn "openshell inference set returned non-zero."
|
||||
log_warn "The gateway may already have this route configured, or the gateway"
|
||||
log_warn "requires a different provider name. Check with: openshell inference get"
|
||||
fi
|
||||
else
|
||||
log_warn "OpenShell gateway not connected — skipping inference route configuration."
|
||||
log_warn "Inference will be configured when the gateway is available (S3+)."
|
||||
log_warn "Ensure LUMINA_INFERENCE_BASE_URL and LUMINA_INFERENCE_MODEL are correct in .env."
|
||||
fi
|
||||
|
||||
# ── Vision smoke test ──────────────────────────────────────────────────────
|
||||
vision_smoke || {
|
||||
log_error "Vision smoke test FAILED."
|
||||
log_error "The inference endpoint does not appear to support multimodal/vision."
|
||||
log_error "Check:"
|
||||
log_error " 1. LUMINA_VISION_MODEL points to a vision-capable model"
|
||||
log_error " 2. The model server supports multimodal inputs"
|
||||
log_error " 3. LUMINA_INFERENCE_BASE_URL is correct"
|
||||
exit 1
|
||||
}
|
||||
|
||||
log_info "S2 complete: model configured, vision smoke passed."
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/lib/common.sh — shared helpers for all host scripts
|
||||
# Sourced by bootstrap.sh, install.sh, and stage scripts.
|
||||
# Do not execute directly.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Colours (auto-disable when not a tty) ──────────────────────────────────
|
||||
if [[ -t 2 ]]; then
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||
BOLD='\033[1m'; NC='\033[0m'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; BOLD=''; NC=''
|
||||
fi
|
||||
|
||||
# ── Logging ────────────────────────────────────────────────────────────────
|
||||
# Use printf to avoid echo -e interpreting escape sequences in arguments.
|
||||
log_info() { printf "${GREEN}[INFO]${NC} %s\n" "$*"; }
|
||||
log_warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*" >&2; }
|
||||
log_error() { printf "${RED}[ERROR]${NC} %s\n" "$*" >&2; }
|
||||
log_section() { printf "\n${BOLD}═══ %s ═══${NC}\n" "$*"; }
|
||||
|
||||
# ── Repo root (works from any subdirectory) ────────────────────────────────
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)")"
|
||||
|
||||
# ── Env loading ────────────────────────────────────────────────────────────
|
||||
# NOTE: Sources .env directly as shell code. This is a known pattern with a
|
||||
# theoretical injection surface if .env contains shell commands. In practice,
|
||||
# .env is created from .env.example (controlled by this repo) and edited by
|
||||
# the operator. Defense-in-depth: parse line-by-line instead of sourcing,
|
||||
# but the current approach matches the broader ecosystem convention.
|
||||
load_env() {
|
||||
local env_file="${1:-${REPO_ROOT}/.env}"
|
||||
if [[ -f "$env_file" ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
set -a; source "$env_file"; set +a
|
||||
log_info "Loaded env from $env_file"
|
||||
else
|
||||
log_warn ".env not found at $env_file — variables must be set externally"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── CLI detection ──────────────────────────────────────────────────────────
|
||||
cmd_exists() { command -v "$1" &>/dev/null; }
|
||||
|
||||
require_cmd() {
|
||||
if ! cmd_exists "$1"; then
|
||||
log_error "Required command not found: $1"
|
||||
if [[ "${2:-}" ]]; then
|
||||
log_error "$2"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Sandbox name ───────────────────────────────────────────────────────────
|
||||
get_sandbox_name() {
|
||||
echo "${LUMINA_SANDBOX:-hermes}"
|
||||
}
|
||||
|
||||
# ── Docker check ───────────────────────────────────────────────────────────
|
||||
docker_available() {
|
||||
cmd_exists docker && docker info &>/dev/null
|
||||
}
|
||||
|
||||
# ── nemohermes check ───────────────────────────────────────────────────────
|
||||
nemohermes_available() {
|
||||
cmd_exists nemohermes
|
||||
}
|
||||
|
||||
# ── openshell check ────────────────────────────────────────────────────────
|
||||
openshell_available() {
|
||||
cmd_exists openshell
|
||||
}
|
||||
|
||||
# ── Guard: script must be run as operator (not root-only, but warn) ────────
|
||||
warn_if_root() {
|
||||
if [[ "$(id -u)" -eq 0 ]]; then
|
||||
log_warn "Running as root. Some host scripts work better as a regular user with sudo access."
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/lib/env.sh — .env validation helpers
|
||||
# Sourced by install stages that need environment checks.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Required keys for S1. Each entry: "KEY description"
|
||||
S1_REQUIRED_KEYS=(
|
||||
"LUMINA_SANDBOX Sandbox name (default: hermes)"
|
||||
"LUMINA_INFERENCE_BASE_URL OpenAI-compatible inference endpoint base URL"
|
||||
"LUMINA_INFERENCE_MODEL Model identifier on the inference endpoint"
|
||||
"LUMINA_VISION_MODEL Vision-capable model identifier (may equal LUMINA_INFERENCE_MODEL)"
|
||||
)
|
||||
|
||||
# Optional keys with defaults
|
||||
declare -A S1_OPTIONAL_KEYS=(
|
||||
["LUMINA_INFERENCE_API_KEY"]="API key for the inference endpoint (empty if endpoint is unauthenticated)"
|
||||
["LUMINA_GATEWAY_URL"]="NemoClaw gateway URL (default: https://127.0.0.1:8080)"
|
||||
["LUMINA_DASHBOARD_PORT"]="Dashboard port (default: 18789)"
|
||||
)
|
||||
|
||||
# ── Validate .env has all required keys ────────────────────────────────────
|
||||
validate_env() {
|
||||
local env_file="${1:-${REPO_ROOT}/.env}"
|
||||
local missing=0
|
||||
|
||||
if [[ ! -f "$env_file" ]]; then
|
||||
log_error ".env file not found at $env_file"
|
||||
log_error "Copy .env.example to .env and fill in values first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
for entry in "${S1_REQUIRED_KEYS[@]}"; do
|
||||
# Split on first space
|
||||
local key="${entry%% *}"
|
||||
local desc="${entry#* }"
|
||||
if ! grep -q "^${key}=" "$env_file" 2>/dev/null; then
|
||||
log_error "Missing required key: $key ($desc)"
|
||||
missing=1
|
||||
elif grep -qE "^${key}=[[:space:]]*$" "$env_file" 2>/dev/null; then
|
||||
log_error "Empty or whitespace-only required key: $key ($desc)"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $missing -ne 0 ]]; then
|
||||
log_error "Fix missing keys in $env_file and re-run."
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info ".env validation passed ($env_file)"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── Create .env from .env.example if it doesn't exist ──────────────────────
|
||||
create_env_from_example() {
|
||||
local env_file="${1:-${REPO_ROOT}/.env}"
|
||||
local example_file="${2:-${REPO_ROOT}/.env.example}"
|
||||
|
||||
if [[ -f "$env_file" ]]; then
|
||||
log_info ".env already exists at $env_file — skipping creation"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "$example_file" ]]; then
|
||||
log_error ".env.example not found at $example_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cp "$example_file" "$env_file"
|
||||
log_info "Created $env_file from .env.example"
|
||||
log_warn "Edit $env_file with your actual values before continuing."
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/lib/vision_smoke.sh — vision capability smoke test
|
||||
# Sourced by S2 install stage.
|
||||
#
|
||||
# Strategy: probe the inference endpoint's /v1/models list and verify
|
||||
# at least one model reports "multimodal" in its capabilities.
|
||||
# Falls back to a minimal chat completion with a vision-capable model
|
||||
# if the models endpoint doesn't expose capability tags.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Vision smoke: check endpoint reports multimodal capability ─────────────
|
||||
vision_smoke() {
|
||||
local base_url="${LUMINA_INFERENCE_BASE_URL}"
|
||||
local model="${LUMINA_VISION_MODEL:-${LUMINA_INFERENCE_MODEL}}"
|
||||
local api_key="${LUMINA_INFERENCE_API_KEY:-}"
|
||||
|
||||
log_section "S2: Vision smoke test"
|
||||
log_info "Probing inference endpoint: $base_url"
|
||||
|
||||
# Build curl headers (-f: fail on HTTP error codes)
|
||||
local -a curl_args=(-sf --max-time 30)
|
||||
if [[ -n "$api_key" ]]; then
|
||||
curl_args+=(-H "Authorization: Bearer $api_key")
|
||||
fi
|
||||
|
||||
# ── Step 1: Check /v1/models for multimodal capability ──────────────────
|
||||
# LUMINA_INFERENCE_BASE_URL includes /v1 (e.g. http://host:port/v1)
|
||||
log_info "Checking models endpoint…"
|
||||
local models_json
|
||||
models_json=$(curl "${curl_args[@]}" "${base_url}/models" 2>/dev/null) || {
|
||||
log_error "Cannot reach inference endpoint at ${base_url}/models"
|
||||
log_error "Check that the endpoint is running and LUMINA_INFERENCE_BASE_URL is correct."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check for multimodal in capabilities array (case-sensitive per API spec)
|
||||
if printf '%s\n' "$models_json" | grep -q '"multimodal"'; then
|
||||
log_info "PASS: Inference endpoint reports multimodal capability"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ── Step 2: Fallback — try a minimal chat completion with vision model ──
|
||||
log_warn "Models endpoint did not advertise 'multimodal' — falling back to chat completion probe"
|
||||
log_info "Testing chat completion with model: $model"
|
||||
|
||||
local response
|
||||
response=$(curl \
|
||||
-H "Content-Type: application/json" \
|
||||
"${curl_args[@]}" \
|
||||
"${base_url}/chat/completions" \
|
||||
-d "{
|
||||
\"model\": \"${model}\",
|
||||
\"messages\": [{\"role\": \"user\", \"content\": \"Say OK\"}],
|
||||
\"max_tokens\": 4
|
||||
}" 2>/dev/null) || {
|
||||
log_error "Chat completion probe failed to ${base_url}/chat/completions"
|
||||
log_error "The inference endpoint may be down or the model '${model}' is not available."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check for a valid response structure
|
||||
if printf '%s\n' "$response" | grep -q '"choices"'; then
|
||||
log_info "PASS: Chat completion succeeded with model ${model}"
|
||||
log_info "Vision capability assumed (endpoint supports multimodal per design)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_error "Chat completion probe returned unexpected response"
|
||||
log_error "Response (truncated): $(printf '%s' "$response" | head -c 500)"
|
||||
return 1
|
||||
}
|
||||
Reference in New Issue
Block a user