From 940b28dd9b3769fa3befa45d2387287854425124 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 07:15:26 -0400 Subject: [PATCH 01/17] docs: plan terminal sessions, developer setup and agent design system --- docs/archipelago-agent-design-system-spec.md | 268 ++++++++++ docs/terminal-developer-environment-spec.md | 524 +++++++++++++++++++ 2 files changed, 792 insertions(+) create mode 100644 docs/archipelago-agent-design-system-spec.md create mode 100644 docs/terminal-developer-environment-spec.md diff --git a/docs/archipelago-agent-design-system-spec.md b/docs/archipelago-agent-design-system-spec.md new file mode 100644 index 00000000..28aa6494 --- /dev/null +++ b/docs/archipelago-agent-design-system-spec.md @@ -0,0 +1,268 @@ +# Archipelago design system for agent-built apps + +Status: planning draft, 2026-10-09. Companion to the +[terminal and developer environment plan](terminal-developer-environment-spec.md). +No tokens, components, templates or installed skills are changed by this draft. + +An agent asked to build an Archipelago app should produce an app that belongs in +Archipelago by construction. Give it a maintained component kit, runnable +starters, documented screen patterns and visual references. A skill telling it +to “use glassmorphism and orange” is insufficient. + +The same foundation should drive Terminal, setup screens, first-party apps and +newly generated apps. Existing third-party applications keep their own product UI; +their Archipelago entry, launch and integration surfaces follow this contract. + +## Evidence and design authority + +At repository baseline `2cb1bae5`, the dashboard's design is distributed across +`neode-ui/src/style.css`, `neode-ui/tailwind.config.js`, Vue components, and the +short standards section of `docs/developer-guide.md`. AIUI maintains a separate +token/style definition in `aiui/packages/app/src/styles/main.css`. + +| Existing rule | Source observation | Contract to preserve or resolve | +| --- | --- | --- | +| Dark controls | Dashboard sets `color-scheme: dark` and explicit select colors | Dark baseline, including native controls | +| Glass cards | `.glass-card`: black at 0.65 alpha, white border at 0.18, 16px radius | Preserve the established surface, not a generic frosted card | +| Blur | Default card blur 18px; dashboard contexts deliberately suppress backdrop blur to avoid rendering corruption | Context-aware surface variants; never reintroduce blanket blur | +| Buttons | `.glass-button`: 44px minimum, 12px radius; established focus/hover/pressed variants | Reusable buttons with full interaction states | +| Accent | Dashboard focus/action uses `#fb923c`; AIUI accent is `#F7931A` | Named semantic tokens with explicit mapping; do not silently choose one for everything | +| Typography | Dashboard body uses Avenir Next/system fallback, headings have bundled Montserrat; AIUI uses Inter/system fallback | Approved roles, metrics, fallback behavior and licensed assets | +| Spacing | Tailwind extends a 4px grid | Shared spacing scale and layout examples | +| Search | Existing search is 40px on desktop, 52px below the 920px breakpoint; right clear control retains focus | Shared search component and documented responsive behavior | +| Card actions | Existing CSS places full-width actions at the bottom on desktop and mobile | Preserve this layout in generated card screens | +| Modal layout | `BaseModal.vue`: pinned title/footer with independently scrolling content | Shared dialog shell with tested focus and Back behavior | +| Mobile layout | Dynamic viewport, safe-area and audio-player offsets already exist | One documented inset contract; prevent double padding in embedded apps | +| Success | Shared `PaymentSuccessPane.vue` and `IdentitySuccessPane.vue` express branded completion | Reuse the relevant pattern only when its completion condition is actually met | +| Embedded canvas | AIUI embedded mode is transparent; standalone has its own background | Host owns wallpaper; apps choose explicit embedded/standalone mode | + +These observations seed a visual audit; they do not make every legacy style a +rule. Capture representative current screens in the running supported UI before +extracting components. Approve Home, Apps, app detail, Settings, forms/dialogs, +list/detail screens, Terminal and mobile examples as reference baselines. + +The source of truth becomes versioned tokens plus components and pattern docs. +Screenshots illustrate that contract and detect drift; they do not replace it. +Existing dashboard and AIUI differences need explicit migration decisions. +Preserve the current look first and make intentional design improvements visible +in review rather than introducing them incidentally through an app template. + +## Deliverable shape + +Proposed layout, to be finalized against repository packaging conventions: + +```text +packages/archipelago-design/ + tokens/ semantic definitions and generated CSS/JSON + styles/ scoped foundations and non-Vue component classes + assets/ approved fonts, icons and licenses +packages/archipelago-ui/ + components/ Vue components with documented states + patterns/ app shells and composed screen patterns +docs/design-system/ + index.md decision guide and version compatibility + foundations.md typography, color, surfaces, spacing, motion + components.md usage, states, accessibility, stable imports + patterns.md complete screen composition and behavior + examples/ reference captures and their matching source +examples/archipelago-app/ + ... working starter with manifest and tests +skills/archipelago-design/ + SKILL.md concise trigger, routing and workflow + references/ versioned design index and evaluation rubric +``` + +Build an offline component gallery with the UI kit. Every example links to its +source, token usage and applicable app pattern. A developer can run the gallery +without a node session or provider API key. It must show loading, empty, error, +success, disabled, focused, hovered and pressed states, not only the ideal screen. + +Publish packages only as part of an explicitly authorized later implementation +workflow. Until then these names and paths describe proposed deliverables. + +## Token contract + +Use framework-neutral CSS custom properties generated from a single definition. +Vue components and any Tailwind adapters consume the same values. Prefix tokens +with `--archy-`; do not expose arbitrary dashboard-global selectors to apps. + +| Token family | Required coverage | +| --- | --- | +| Color | Canvas, card, inset, overlay, border, text primary/secondary/muted, interactive accent, focus, selected, success, warning, danger and info | +| Typography | Body, heading and monospace families; type scale, weights, line heights and numeric alignment | +| Space and size | 4px-based scale, page gutters, content widths, control heights, touch targets and icon sizes | +| Shape | Card, dialog, input, button and pill radii | +| Elevation | Card/overlay shadows, border strengths, blur-permitted and no-blur surfaces | +| Motion | Durations/easing, permitted hover/press transitions and reduced-motion equivalents | +| Layering | Header, navigation, menu, popover, modal and toast layers with ownership rules | +| Viewport | Host-provided safe areas, keyboard viewport, navigation/audio offsets and embedded mode | +| Terminal | Canvas, foreground, cursor, selection and a readable 16-color ANSI palette | + +Make brand orange and interactive/focus orange distinct roles if preserving both +existing values. Status colors must communicate meaning alongside text/icons. +Name action variants by purpose: the existing orange “warning” class also serves +primary actions, so exporting that name unchanged would teach the wrong semantics. + +Typography must have predictable dimensions on Debian, Android and desktop +browsers. Use only redistributable local font assets. Referencing Avenir or Courier +New in a fallback stack does not license bundling those fonts. Freeze a tested +body/heading/mono choice and line-height matrix in the visual review; do not let +each generated app choose its own fonts or fetch a font CDN. + +Check contrast on actual composited surfaces and the brightest/darkest supported +backgrounds. Opacity values or CSS comments alone do not establish accessibility. +Small text must remain readable, and focus must remain visible where current +global styles suppress outlines. Low-power/no-backdrop-filter fallbacks retain +the same hierarchy and usable contrast. + +## Components and patterns agents can reuse + +Extract reusable behavior from existing code; avoid giving agents a second +lookalike library with slightly different interactions. Package components +without dashboard stores, router assumptions, privileged RPC clients or secrets. +Use adapters for integrations owned by the host. + +| Foundation | First required components | Existing reference | +| --- | --- | --- | +| Structure | AppShell, PageHeader, Section, Card, InsetPanel, ActionRow | Dashboard CSS, app headers, card action rules | +| Controls | Button, IconButton, TextField, PasswordField, Select, Toggle, SearchField, SegmentedControl | `ToggleSwitch.vue`, `PasswordRevealInput.vue`, `AppSearchField.vue` | +| Navigation | Tabs, Breadcrumbs, Back action, contextual menu | Dashboard navigation, `BackButton.vue`, Cloud menus | +| Feedback | StatusBadge, EmptyState, Skeleton, Progress, InlineError, Toast | `EmptyState.vue`, `SkeletonCard.vue`, `ToastStack.vue`, existing progress patterns | +| Dialogs | Dialog, ConfirmDialog, credential handoff | `BaseModal.vue`, `AppConfirmModal.vue`, `AppCredentialInterstitial.vue` | +| Data | ListRow, responsive table/list, detail key/value row, copy action | Existing app lists, `CopyButton.vue` | +| Terminal | TerminalShell, SessionList, SessionRow, ConnectionStatus, mobile key strip | New consumers of the same foundation | + +Each public component documents supported props/events/slots, sizing, keyboard +behavior, accessibility, states and examples. Shared helpers must respect context: +the current modal helper captures Escape and arrow keys, which is unsuitable +for a terminal, editor or other widget that owns those keys. Fix/reuse that +behavior through explicit contracts rather than blindly wrapping Terminal in it. + +Provide composed patterns for: + +- A searchable collection with filters, list/grid view, empty state and bottom + card actions. +- A detail screen with back navigation, metadata, status and primary action. +- Settings grouped by task, with visible validation, save progress and retry. +- A multi-step setup flow with skip/resume, honest progress and recovery. +- First launch and credential handoff, including apps with their own login. +- A destructive confirmation that names its target and data-preservation effect. +- A session picker and terminal that clearly distinguish Close from End session. + +Copywriting is part of the contract: concise task labels, useful error recovery, +explicit pending versus complete states, no raw internal exception as the sole +user message, and appropriate units/time formatting. Technical detail is +available on demand. App-generated progress must reflect actual work. + +## App shells and host integration + +Supply a preferred Vue/TypeScript starter and a small portable HTML/CSS example. +Existing React or other framework applications consume the tokens/styles and +behavior contracts; do not require a framework rewrite to package an app. +Full additional framework bindings follow actual demand and have their own +acceptance, rather than presenting CSS classes as equivalent accessible widgets. + +New starters include local assets, locked dependencies, scripts, manifest, +container definition, health check, persistent-data example, tests and a complete +reference screen. A starter must build and run from its copied location without +monorepo-only aliases. It cannot require fetching current dashboard CSS at runtime. + +The host integration contract defines: + +- Explicit standalone versus embedded appearance. Embedded apps use a transparent + canvas where appropriate; they do not add a duplicate wallpaper or navigation + shell. Standalone mode still has a complete, readable canvas. +- Versioned, non-secret theme/inset messages if runtime synchronization is needed. + Validate sender origin, source window and payload; no wildcard privileged + message bridge. An app must remain usable with no host handshake. +- Exactly one owner for top/bottom safe-area padding and fixed player/navigation + clearance. Test the actual Companion WebView, not just a mobile screenshot. +- Existing launch interfaces and app-gate behavior from the developer docs. + Asset URLs work under their supported base path and HTTP/HTTPS entry points. +- Nostr signer, media controls and credential handoff only through their supported + APIs. Visual kit installation does not give an app host management access. + +Package tokens/components with the app's pinned kit version. Evolve them with +documented compatibility and migrations. Runtime theme values may be forwarded +through the agreed contract; runtime JavaScript/CSS replacement is not the update +mechanism. A platform update must not silently break every installed app. + +## Agent workflow + +The app skill always routes new UI work through `archipelago-design`. The system +skill does the same when a system modification affects a visible screen. A +backend-only request loads no unnecessary design material. + +For relevant tasks the agent: + +1. Reads the design index, supported kit version and matching pattern; identifies + existing components before creating new ones. +2. Inspects the reference screen and its source, then selects the closest starter. +3. Implements the requested behavior using shared components and semantic tokens. +4. Exercises normal, loading, empty, invalid, failed and successful states. +5. Renders standalone and embedded previews at the required viewports; inspects + captures, keyboard behavior and actual component geometry. +6. Runs app checks and supplies the working preview, component/token provenance, + results and any deliberate design exceptions. + +Make that loop easy: `archy app new`, `archy app preview` and `archy app check` +should supply the starter, preview fixtures and relevant checks. Those are +proposed tools in the terminal plan, not currently implemented commands. +Ordinary changes within the requested task proceed through this loop without +requiring approval for every edit. A new shared design primitive or intentional +departure is identified in the review, with its use case and visual evidence. + +The skill should tell agents how to find and use the design system, not make +them recreate it from a long list of adjectives. Keep the entrypoint concise; +conditional references cover forms, layout, terminal interaction, media and +signer flows. Templates and screenshots belong in reusable assets, not prose +that the agent has to transcribe. + +## Design conformance and definition of done + +Combine targeted static checks, interaction tests and visual review. Static +checks cannot prove a coherent design; screenshots cannot prove an accessible +or correct interaction. + +| Gate | Acceptance | +| --- | --- | +| Shared foundation | New UI imports the approved kit/version; bespoke color/font/radius/z-index values need a named exception, with allowances for content such as charts and user imagery | +| Visual hierarchy | Reference-matched page width/gutters, type roles, card surfaces, action placement and density | +| Responsive layout | 320, 390, 768 and 1440px widths plus phone landscape; 200% text zoom; no accidental horizontal page overflow | +| Input | Keyboard-only use, visible focus, sensible tab order, labels, dialog focus containment/restoration and Back behavior; terminal keys remain intact | +| Touch | 44px usable targets for new mobile controls, reachable persistent actions, no essential hover-only interaction | +| States | Loading, empty, offline, denied, validation error, retry and success demonstrated with deterministic fixtures | +| Appearance | Dark native controls, foreground/background contrast, reduced motion, no-blur fallback and locally available fonts | +| Host modes | Embedded/standalone, HTTP/HTTPS where supported, software keyboard, safe areas, audio-player offsets and Companion | +| Runtime truth | Success and readiness only follow actual confirmed results; theme/state changes do not erase form or session work | +| Regression | Shared component changes compared against representative dashboard and app reference captures before release | + +Choose per-component visual-diff tolerances after baseline capture; avoid a +single permissive threshold that conceals layout regressions. Stabilize fonts, +viewport, fixture data and animations. Review intended baseline changes rather +than automatically accepting new screenshots when CI fails. + +Behavioral skill evaluation should use realistic tasks with no hidden design +brief: build a bookmarks app, add a settings form, create an app with first-run +credentials, adapt a non-Vue app, and improve Terminal's session picker. Inspect +the generated artifacts and previews for component reuse, consistent appearance, +correct manifest integration and usable failure states. Repeat on a fresh +environment to ensure success does not rely on this checkout or an agent's +conversation history. + +## Implementation order + +1. Audit representative existing screens; resolve accent/typography/blur and + embedded-mode decisions; record the approved reference set. +2. Extract semantic tokens and the first components while preserving current + dashboard rendering. Migrate a small representative slice to prove parity. +3. Build the offline gallery, app shell and working starter. Use Terminal/setup + as real consumers so the foundation is exercised immediately. +4. Package the app/design skills with matching docs and assets; run realistic + generation tasks against the starter and gallery. +5. Add scoped conformance checks to app validation and qualification; migrate + additional first-party surfaces incrementally. + +App generation cannot be called complete while agents still invent the visual +foundation. Completion means a new agent, a new workspace and an ordinary app +request reliably produce a working, recognizably Archipelago result. diff --git a/docs/terminal-developer-environment-spec.md b/docs/terminal-developer-environment-spec.md new file mode 100644 index 00000000..fa40485c --- /dev/null +++ b/docs/terminal-developer-environment-spec.md @@ -0,0 +1,524 @@ +# Archipelago terminal and developer environment plan + +Status: planning draft, 2026-10-09. No runtime implementation or installation is +part of this change. Repository baseline: `2cb1bae5ce244d387679f90951d62fd030ebf228`. + +Archipelago should let an owner open a real terminal, resume previous work, +configure their system, and ask a preinstalled coding agent to build an app that +looks and behaves like Archipelago. The experience must work from the dashboard, +local console, and SSH, with the same tools and discoverable commands. + +The user requirements are: + +- Bring Omarchy's initial setup and development capabilities to Archipelago. +- Ship Codex ready to launch, with guided personal authentication. +- Make closing and resuming terminal sessions easy and reliable. +- Ship local skills that help agents modify the system and build apps using the + actual developer documentation. +- Make visual and interaction consistency an enforced part of app creation. +- Plan in a separate worktree, without conflicting with current release work. + +The companion [agent design system plan](archipelago-agent-design-system-spec.md) +defines the shared UI kit, agent workflow, app templates, and visual acceptance. +That work is a dependency of the app-building experience, not finishing polish. + +## Product outcome + +On a newly installed node, Terminal opens to a usable shell with a small welcome +panel offering Setup, Resume, Build an app, Work on Archipelago, and Help. The +shell is immediately usable; onboarding is dismissible and resumable. Existing +owners get the same environment through an upgrade that preserves their files, +configuration, credentials, and uninstall decisions. + +A representative first session: + +1. Open Terminal from the dashboard or launch it locally. +2. See the node, workspace, account, and any existing sessions clearly identified. +3. Run the setup guide, select editor/toolchains, and sign in to Codex. +4. Choose Build an app and describe the app. The agent loads Archipelago's app + and design guidance and starts from the maintained starter. +5. Review a working local preview, including mobile and failure states. +6. Close Terminal while a build or agent task runs. +7. Reopen it, select the named session, and continue where it was left. +8. Install the candidate through the existing app lifecycle on a chosen test + node, then contribute through ngit when publication is requested. + +## Current implementation and gaps + +These are source findings, not live-node acceptance results. + +| Surface | Current source | Required change | +| --- | --- | --- | +| Dashboard terminal | `neode-ui/src/components/CLIPopup.vue`: development-only simulated commands; production SSH instructions and a literal default password | Real terminal and session picker; connection information derived from actual setup state | +| Terminal state | `neode-ui/src/stores/cli.ts`: open/close boolean | Server-owned session inventory; client UI state independent of process lifetime | +| Keyboard | `neode-ui/src/App.vue`: global F and other shortcuts; `useModalKeyboard.ts` captures arrows and Escape | A terminal focus boundary so shell, editor and agent keys reach the PTY | +| Console setup | `image-recipe/archipelago-scripts/archipelago-menu.sh`: legacy menu, eager installer-tool installation, direct container setup | Shared command catalog and current orchestration; opening a menu does not install software | +| Shell onboarding | `scripts/welcome-banner.sh`: banner and SSH information, including literal default password; may wait for networking | Short local welcome, accurate access state, fast offline shell startup | +| ISO | `image-recipe/build-debian-iso.sh` invokes a relocated copy of `_archived/build-auto-installer-iso.sh` | Integrate the actual active build path, despite its archived filename | +| Upgrade | `scripts/self-update.sh`, `core/archipelago/src/bootstrap.rs`, runtime assets | One versioned environment payload and repeatable migration shared with ISO | +| Existing development access | Pinned ngit installer and GitWorkshop app already exist | Reuse these; keep ngit the contribution platform | +| App packaging | `docs/app-developer-guide.md`, manifest spec and Rust parser | Turn existing contracts into agent workflows, validated starters and development commands | +| Design | Dashboard CSS/Tailwind, reusable Vue components, separate AIUI CSS | Extract and document a coherent shared contract; see companion plan | + +The backend uses Hyper/Tokio and existing WebSocket handlers; the developer guide +currently calls it Axum. Several contributor documents also show unrestricted +`cargo test`, conflicting with `AGENTS.md`. Correct those documentation examples +before packaging them as agent instructions. On a live node, backend execution +must use `scripts/test-backend-isolated.sh`. + +## Omarchy reference and capability mapping + +Research baseline: the official [Omarchy repository](https://github.com/omacom/omarchy), +formerly reached through `basecamp/omarchy`. Its default branch was `quattro` at +[`fcf9eeb5c454739f3f23cdfd7d57d77b12961025`](https://github.com/omacom/omarchy/tree/fcf9eeb5c454739f3f23cdfd7d57d77b12961025). +The latest published release returned during research was +[v4.0.4](https://github.com/omacom/omarchy/releases/tag/v4.0.4), published September 15, +2026, resolving to `c668141e9c42b13c80c9ca4ea108e11708c5e8a5`. +The inventory below describes the pinned default-branch source; it does not +assert that every item is present in that published release. + +“All setup capabilities” means explicit disposition of the setup surface, not +silently omitting desktop-specific features. The proposed core release covers +the terminal, system setup and development rows below. Hardware/desktop options +remain named follow-on work; completion of the core is not full Omarchy parity. + +| Omarchy capability | Proposed Archipelago equivalent | Delivery | +| --- | --- | --- | +| Owner, keyboard, hostname, timezone, optional Git name/email | Resume existing node onboarding; configure developer identity separately from appliance identity | Core | +| Deferred provisioning for another owner | Leave personal developer credentials unset; first owner completes setup | Core | +| Separate packaged defaults, user finalization and migrations | Versioned system payload, per-user setup receipts, explicit reset with backup | Core | +| First-login welcome, shortcuts, network/update guidance | Welcome and searchable help shared by terminal and dashboard | Core | +| Bash completion, history, prompt, fuzzy finding and directory navigation | Bash, completion, Starship, fzf and zoxide with readable console fallback | Core | +| File/search/system tools | ripgrep, fd, bat, eza, jq, less, man, tldr, btop and fastfetch | Core | +| Terminal selection: Foot, Alacritty, Ghostty, Kitty | Browser terminal plus console/SSH; local emulator adapters compatible with the actual kiosk display stack | Browser/console core; native choices follow-on | +| tmux sessions, panes, developer layouts | Named persistent sessions and editor/agent/shell layouts; same sessions accessible by SSH | Core | +| Neovim and selectable editors | nano available immediately; maintained Neovim profile and editor preference; GUI editors when desktop support exists | Core terminal editors; GUI follow-on | +| Mise development environments | Versioned Node/npm, Rust, Python/uv profiles first; project-local versions | Core | +| Ruby/Rails, Bun, Deno, Go, PHP/Laravel/Symfony, Elixir/Phoenix, Java, Zig, OCaml, .NET, Clojure, Scala | Optional profiles in the same installer catalog, each with architecture and verification metadata | Parity follow-on | +| Git, lazygit, GitHub CLI | Git/lazygit and ngit/GitWorkshop first-class; gh optional for other upstream projects | Core | +| Lazy agent launchers | Codex actually bundled; optional adapters for other agent CLIs | Core Codex; provider expansion follow-on | +| Default-agent selector and starter prompts | Codex selected initially for a fresh setup; preserve existing preference; system/app/design entry prompts | Core | +| Agent account selection and usage panel | Explicit account context and supported login status; manual account profiles before considering usage automation | Follow-on | +| Local model tools | Integrate the existing Ollama app and compatible provider setup; downloads and model resource needs visible | Follow-on | +| Bundled system and app-building skills | Archipelago system, app and design skills with offline references and templates | Core | +| Development databases: MySQL, PostgreSQL, Redis, MongoDB, MariaDB, MSSQL | Scoped rootless Podman development recipes, isolated ports/data and generated credentials | Core PostgreSQL/Redis-compatible recipe; remaining recipes follow-on | +| Container tooling | Existing rootless Podman; documented Compose compatibility where tested | Core | +| DNS, Wi-Fi, network QR, SSH daemon and SSH agent setup | Existing network/SSH controls exposed through shared setup operations, with accurate connection details | Core | +| Fingerprint, FIDO2 and privilege preferences | Hardware-aware account-security setup; no copied Arch PAM configuration | Follow-on | +| Monitors, keyboard bindings, input, XCompose | Kiosk/console equivalents through existing system configuration; desktop-specific adapters separately | Core console basics; desktop follow-on | +| Browser, terminal, editor and dictation defaults | Editor/agent/terminal choices first; browser/dictation surfaced when relevant to the device | Core subset; follow-on adapters | +| Themes, fonts, background and prompt | Shared Archipelago tokens and terminal palette; preserve owner customization | Core | +| Shell plugins and customization hooks | Versioned extension points and documented user overrides; dashboard extensions require a separate supported contract | User overrides core; plugins follow-on | +| Package, TUI, web-app and development installation menus | Curated tool catalog plus existing Archipelago app catalog; distinguish developer tools from managed apps | Core | +| Commercial services, GUI apps, gaming, Windows VM | Individual optional app/desktop integrations, inventoried as a separate parity backlog | Follow-on, not preinstalled on nodes | +| Update, reset, snapshots, direct boot | Integrate Archipelago update/recovery choices; explicit reset scope and backup | Core existing operations; new boot/snapshot features follow-on | +| Hardware detection and vendor fixes | Existing Archipelago hardware configuration, capability detection and separately qualified device fixes | Core detection; device adapters follow-on | +| Crash diagnosis skill | Sanitized diagnostics with an explicit handoff to the chosen agent; preserve source versus live evidence | Follow-on | + +Primary source routes for the inventory: + +- [Provisioning and file layout](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/docs/file-layout.md), + [setup form](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/install/provisioning/setup-form.sh), + [setup and installation menu](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/default/omarchy/omarchy-menu.jsonc). +- [Base packages](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/install/omarchy-base.packages), + [shell initialization](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/default/bash/init), + [terminal and tmux](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/manual/15-terminal.md). +- [Development profiles](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/bin/omarchy-install-dev-env), + [agent setup](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/install/user/mise.sh), + [agent launcher implementation](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/bin/omarchy-mise-install), + [development databases](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/bin/omarchy-install-docker-dbs). +- [System skill](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/default/agents/skills/omarchy/SKILL.md) + and [app skill](https://github.com/omacom/omarchy/blob/fcf9eeb5c454739f3f23cdfd7d57d77b12961025/default/agents/skills/omarchy-app/SKILL.md). + +Adapt the workflows to Debian and Archipelago's manifest-driven runtime. In +particular, Omarchy's app skill produces Qt desktop apps; Archipelago's starter +must produce an Archipelago app. Omarchy's agent launchers download tools on use; +the user's requirement here is stronger: Codex must already be installed. Its +database recipes and permission-changing aliases are not defaults to transplant. +Any reused upstream files need their original license notices and provenance. + +## Terminal experience + +Use a real PTY with a bundled terminal renderer, proposed `@xterm/xterm` with +fit/search support. It must run Bash, nano/Neovim, lazygit, tmux and Codex without +special simulated command handling. Exact dependency versions are selected and +locked during implementation qualification. + +The dashboard entry opens a large resizable terminal on desktop and a full-height +surface on mobile. It provides session name, workspace/node context, connection +status, session switcher, New, Resume, Search, copy/paste, font size, fullscreen, +and Close. End session belongs in a separate menu with a clear running-work warning. +Mobile adds an Esc/Ctrl/Tab/arrows key strip and works with the software keyboard. + +Use the shared Archipelago shell/components around an opaque, readable terminal +canvas. Terminal content uses a packaged monospace font with Unicode support; +the console gets an ASCII-compatible fallback. Apply the design plan's focus, +contrast, reduced-motion and safe-area rules. + +While terminal input has focus, F, Escape, arrows, Ctrl+C, Ctrl+D, Ctrl+R, Tab, +editor keys and tmux prefixes belong to the terminal. Closing uses a visible +control or a documented terminal-specific shortcut. Do not reuse the current +modal keyboard handler unchanged. Ctrl+C interrupts the foreground job; Ctrl+D +has normal shell semantics. Focus can move to toolbar controls and back. + +## Persistent sessions and one-click resume + +This is a core release criterion, including the first usable terminal milestone. + +Session execution lives on the node in a dedicated, supervised user environment. +Use tmux as the initial persistence engine; a browser WebSocket is an attachment, +not the owner of the shell process. Keep the tmux server and worker scope outside +the dashboard/backend service's kill group. Ordinary manager deployment must not +tear down developer sessions. + +Prefer one tmux server/socket and supervised process scope per managed session, +so ending one session cannot kill a shared server containing other work. Keep +those sockets private to the developer account. SSH and local-console resume use +the same session registry and attachment helper rather than guessing tmux names. +Cross-device resume refers to browsers connected to the same node; moving live +processes between different nodes is outside this contract. + +| Event | Required behavior | +| --- | --- | +| Close terminal panel, navigate away, close tab, kill browser | Detach; shell, build and agent continue | +| Reopen Terminal | Show running and recent sessions; Resume most recent available in one action; do not auto-create duplicates | +| Switch session | Detach the old view and attach the selected session | +| Network drop or mobile sleep | Show Reconnecting; reauthenticate if needed, then attach to the existing session | +| Dashboard reload or backend restart | Session survives; rediscover it from node state | +| Second browser/device | Same authenticated owner sees sessions; explicit transfer of input control | +| Logout/session expiry | Revoke browser attachments immediately; background work remains; a fresh owner login is required to reattach | +| Explicit End session | Confirm when work is active, terminate that session's process scope, record ended state; do not remove its files | +| Shell exits | Show ended status and exit information where available; offer a new shell in that workspace | +| Broker crash | tmux/process scope survives if its supervisor survives; reconcile inventory, without replaying input | +| Node reboot/power failure | Processes stop. Keep workspace/session metadata and offer Reopen workspace and Resume Codex conversation; never label this a live process resume | + +The picker shows a user-editable name, workspace, creation/last-attachment time, +Running/Detached/Ended/Interrupted state, and optional pinned status. Scope every +entry to a node and stable owner identity. LocalStorage may remember selection; +it is never the authoritative session registry. + +Persist metadata atomically under the workspace account's private state directory. +Record schema version, opaque session ID, owner ID, node ID, boot ID, tmux target, +workspace ID/path, created/attached timestamps and lifecycle state. Treat tmux and +supervised process state as authority for whether a session is still alive. +After a crash, reconcile orphan sessions and interrupted creates before accepting +another create with the same request ID. + +Place workspaces and agent state on durable storage with an explicit ownership, +quota and backup policy; do not consume a small system partition accidentally. +Expose a familiar `~/Work` entrypoint while recording the actual workspace root. +Reconnecting never requires a fresh Git checkout. A missing or unmounted workspace +is a visible recovery state, not permission to create an empty replacement at the +same path. Deleting session metadata and deleting project files are distinct actions. + +Keep bounded in-memory scrollback and restore the current terminal screen from +the surviving tmux attachment. Do not assume client-side scrollback survived. +Reboot persistence covers metadata and saved files; transcript recording is a +separate opt-in setting because terminal output can include credentials. + +Initial proposed limits: eight sessions per owner, one active input controller +per session, a 10,000-line scrollback ceiling plus a byte ceiling, and bounded +per-connection queues. Do not kill detached work because a browser idle timer +expired. Make resource use visible and allow explicit stop/cleanup. Qualify CPU, +memory and disk limits on the smallest supported node before choosing defaults. + +Input is never automatically replayed after reconnect: a lost acknowledgement +does not establish whether Enter or a command reached the shell. Resize events +are idempotent. A newly attached terminal redraws from the current PTY state. +Revoke the previous writer before granting a new writer lease; read-only views +can be a later addition. User labels and working directories must never be +interpolated into shell command strings. + +## Execution and connection architecture + +Proposed components and responsibility boundaries: + +```text +Dashboard terminal entry / SSH / local console + | + authenticated attachment + | +Terminal session service -- session metadata and ownership + | + supervised developer user + tmux + PTY + | + Bash / editor / Codex / project toolchains + | + archy CLI -- existing typed management operations +``` + +Add a small terminal session service with a local Unix-socket control interface. +The existing management backend validates owner authorization and brokers +short-lived attachments; it does not execute arbitrary shell strings in an RPC +handler. Use typed arguments and fixed executable paths for session creation. +An implementation spike must verify PTY allocation, systemd ownership, tmux +reattachment and Codex rendering before committing to the broker library. + +Prefer a dedicated developer Unix account, separate from the existing +`archipelago` service account, with its own home, rootless container storage and +no mounts of production wallets/secrets. Keep the current service account and +data ownership intact. The inspected ISO builder grants the service account broad +passwordless sudo; using that account for a browser shell would grant equivalent +host authority. A new developer account alone is not proof of isolation: test +actual filesystem permissions, sockets, groups and sudo policy. + +Provide an explicit System administration context for system changes. Routine +supported operations should use the same typed management operations as the UI; +arbitrary host-shell administration needs a distinct authenticated operator +session. These boundaries must still permit authorized agents to configure the +system efficiently. Do not require repeated consent for each step of an already +authorized operation, or treat a skill as an access-control mechanism. + +Initial web access is for the node owner. Public app sessions, guests, peer +identities and iframe signers do not imply terminal authorization. Multi-user +workspace sharing is outside the first release; reject unsupported mappings. + +Prefer a dedicated terminal browser origin with a minimal self-hosted bundle, +no app iframes or external scripts, and narrow authenticated handoff from the +dashboard. A same-origin subpage reduces bundle complexity but does not isolate +it from same-origin scripts. Resolve origin, certificate and Companion handling +in the connection spike before shipping. The +[xterm.js integration guide](https://xtermjs.org/docs/guides/security/) specifically +requires application-level WebSocket authentication/origin handling and careful +treatment of terminal output. + +Remote terminal transport requires verified HTTPS/WSS. Existing plain-HTTP +dashboard users get a working secure-terminal entry and SSH alternative; do not +force a global dashboard HTTPS migration as a side effect. Retain current private +management ingress controls, IPv6 support and explicit proxy trust. Never infer +terminal authorization from a forwarded hostname or a private IP alone. + +The protocol contract must include: + +- Create/list/rename/end session operations with CSRF checks, stable owner + binding and idempotent create/end behavior. +- A single-use, short-lived attachment grant bound to session, owner and exact + terminal origin. No reusable dashboard cookie or API key in a query string. +- An authenticated WebSocket with bounded pre-auth time and no PTY output before + authorization; validate Origin separately from CORS and revalidate on reconnect. +- Input bytes, output bytes, resize, connection state and exit messages; bounded + frame sizes, queue backpressure and slow-reader behavior. +- Immediate attachment revocation on logout/credential revocation. No automatic + input replay, automatic command rerun or unauthenticated reconnect. +- Process-group cleanup only for explicit termination; metadata-only audit logs, + excluding command contents, keystrokes, credentials and terminal output. +- Terminal titles/links treated as untrusted text; external links require a user + gesture; clipboard escape sequences cannot silently read/write the clipboard. + +## Setup and tool distribution + +Provide a searchable `archy` CLI and matching setup TUI; the existing +`archipelago` executable remains the backend daemon. All `archy` commands in +this document are proposed interfaces, not commands available today. + +| Proposed command | Purpose | +| --- | --- | +| `archy setup` | Resume setup, show installed/available/deferred items | +| `archy commands --json` | Agent-readable command descriptions, inputs, privileges and side effects | +| `archy doctor` | Read-only, bounded health checks with actionable findings | +| `archy session list` / `resume ` | Discover and attach to existing work | +| `archy dev setup ` | Install a declared, versioned toolchain profile | +| `archy agent` | Launch selected agent in the selected workspace | +| `archy app new ` | Create from a maintained Archipelago starter | +| `archy app check ` | Manifest, build, integration and design checks | +| `archy app preview ` | Start an isolated local preview and return its URL | +| `archy app install --node ` | Explicit candidate deployment through supported orchestration | +| `archy system ` | Discoverable adapters to supported system operations | +| `archy skills status` | Show installed skill/doc/kit versions and local overrides | + +Use one command catalog for the CLI, setup menu and skill references. Each entry +declares what it reads/changes, required privilege, supported machines, expected +output, progress and rollback/recovery behavior. Structured status is for agents; +the human menu uses clear task names. Missing capability is a visible explanation. + +First-run sequence: verify network/time/access status; create or select a workspace; +set optional Git identity and editor; confirm installed tools; offer toolchain +profiles; sign in to Codex; offer a sample app or system task. Users can skip and +resume individual steps. Authentication is never an ISO build step. Run nothing +interactive in noninteractive shells, SCP/SFTP, remote command execution or CI. + +Ship offline: shell essentials, tmux, Git/ngit, Codex binary, local documentation, +skills and design-kit assets. Developer profiles add the complete Node/frontend, +Rust/backend or Python environment and build prerequisites. Offline capability +must be stated precisely: shell/docs/Codex launch can work offline; model calls, +uncached dependencies and external login require connectivity. Plan a full +offline developer bundle as a separate profile if all build caches are required. + +Use signed/versioned payloads and per-architecture hashes. Debian packages and +user toolchains have separate ownership. Core tools update through qualified +releases; opening a shell or running `codex` must not silently update binaries. +Optional tool installs show download size, source, version and progress. Respect +package-manager locks and existing mise/rustup/nvm installations. Do not prune a +binary version beneath a running session. + +State is versioned per machine and per user with pending/running/done/failed/skipped +steps; write completion only after verification. Concurrent setup is serialized. +Preserve user dotfiles through small managed includes and explicit overrides; +preview changes and back up touched configuration during an explicit reset. +Network interruption, disk exhaustion and reboot leave resumable state. + +## Codex integration + +Bundle a qualified stable Codex release for each supported architecture, verified +against a pinned artifact. The official +[CLI installation documentation](https://learn.chatgpt.com/docs/codex/cli) +documents standalone and npm installation; the release implementation should +resolve exact packaging and pin it rather than executing an unversioned installer +on every node. + +Use normal Codex authentication in the developer user's private environment. +Support browser login and the official device-code option for remote/headless +sessions when available; API-key login is another explicit option. The +[authentication documentation](https://learn.chatgpt.com/docs/auth) describes +these flows. Do not collect credentials in an Archipelago transcript or copy the +node's wallet identity into agent configuration. The UI reports Installed, +Sign-in required, Ready or Error based on actual results. + +Honor the user's Codex configuration, permissions and model choice. A launcher +selects a workspace and skill context; it does not inject unrestricted execution +flags. A local skill is local guidance, not a claim that Codex inference is local. +Explain the selected provider and what workspace content may be sent to it during +setup, alongside any self-hosted alternative. + +During an ordinary disconnect, resume the same running Codex process in tmux. +After an ended session or reboot, offer the official `codex resume` workflow in +the matching workspace. Do not automatically reissue the last task. Preserve +agent history/configuration separately from disposable build caches, and keep +credentials out of ordinary app export/support bundles. + +## Local skills that enable real work + +Ship a small coordinated skill family with one obvious entrypoint. Keep the +instructions actionable and references focused; avoid copying the whole manual +into every prompt. The skill-creator guidance informs this structure: precise +triggers, reusable resources, progressive disclosure and behavioral validation. + +| Skill | Trigger and outcome | Required resources | +| --- | --- | --- | +| `archipelago` | Configure, troubleshoot or change an Archipelago system; route app/UI work to the companion skills | System map, command catalog, config ownership, task recipes and recovery | +| `archipelago-app` | Build, package, test or update an Archipelago app using the developer contract | Developer docs, manifest schema, starter assets, launch/auth/signer examples, lifecycle checks | +| `archipelago-design` | Create or change an Archipelago UI, including apps, setup and Terminal | Shared tokens/components, pattern gallery, app shells, screenshots and visual checks | + +For a system task, the agent should locate the correct config/operation, inspect +current state, make the requested scoped change, validate it and report the result. +Teach recipes for networking, SSH, tool installation, terminal preferences, +service diagnosis and supported app configuration. Prefer maintained management +commands; when direct configuration is necessary, explain owned versus generated +files, the minimal affected service and reversal. Repository changes go into a +separate branch/worktree; installed-node changes target an explicitly identified +node and retain a scoped backup. Planning requests stay planning requests. + +For an app task, the agent reads `docs/app-developer-guide.md` and +`docs/app-manifest-spec.md`, follows the design plan, chooses a starter, implements +the requested behavior, validates it and supplies a runnable preview. Package +with pinned images/build contexts, rootless execution, declared storage/secrets, +health checks and truthful interfaces. Include HTTP/HTTPS, iframe/Companion, +first-run credentials and Nostr signer behavior where relevant. Publication and +live installation are distinct requested steps. + +The skill must explain non-obvious runtime facts: manifests copied only into +`/opt/archipelago/apps` are replaced at backend start; runtime payload promotion +and signed-catalog precedence matter. Installed status is not launch readiness. +Pre-catalog testing must not replace the signed catalog. Test uninstall/reinstall +with data preservation on a disposable target. Use `AGENTS.md` over stale testing +examples. Payments, wallets and uninstall decisions retain their existing +invariants; a generic repair must not reset them. + +Proposed source layout: `skills/archipelago*` plus a versioned reference bundle and +app starter assets. Ship managed copies under a versioned read-only system path +and expose one discoverable link per skill per agent. Current official +[Codex skill guidance](https://learn.chatgpt.com/docs/build-skills) supports +repository `.agents/skills`, user `~/.agents/skills`, administrator +`/etc/codex/skills`, and symlinked skill directories. Qualify discovery with the +pinned CLI, including from outside this repository. Preserve local skills and +overrides; avoid duplicate skill names from multiple discovery paths. + +Each bundle records the source revision, supported Archipelago version, manifest +schema and design-kit version. Installed skills use matching offline docs; +repository development uses that checkout's docs. Version mismatch is visible. +Future command names in this plan must not be taught as available until shipped. + +## App creation and design integration + +The default new app is a Vue/TypeScript app using the shared Archipelago UI kit, +with a pinned container build, manifest, icon, tests and local preview. Supply a +framework-neutral CSS/token starter for existing non-Vue apps; port semantic +behavior deliberately instead of depending on dashboard globals. + +Provide a complete example app with a useful list/detail/settings flow, persistent +data, loading/empty/error/success states, a manifest and lifecycle evidence. +Extend it with optional signer/media examples only when those features are used. +The starter must render correctly both embedded and standalone. A third-party +upstream app can retain its own UI; the consistency contract governs the app's +Archipelago wrapper and newly authored Archipelago screens. + +Local development data and ports are separate from production apps. Bind previews +to loopback by default and provide an authenticated preview path for remote users. +Development databases get unique project-scoped names, persistent volumes and +credentials; they must not attach to live Bitcoin/LND or production app databases. + +## Delivery sequence + +| Phase | Deliverable | Exit evidence | +| --- | --- | --- | +| 0 | Finalize command/session contracts, origin/account choice and design baseline | Reviewed wireframes, reference screens, privilege map and pinned Omarchy inventory | +| 1A | Persistent terminal service and browser/SSH attachment | Real shell + Codex TUI; close/reopen, network loss, manager restart and ownership tests | +| 1B | Shared design foundation, gallery and starter | Dashboard-derived tokens/components; consistent app at desktop/mobile sizes | +| 2 | Core setup/tool payload and Codex onboarding | Fresh/offline/upgrade/retry matrix; correct account state and user override preservation | +| 3 | System/app/design skills and app-development commands | Realistic agent tasks produce correct scoped system changes and a consistent packaged app | +| 4 | Integrated candidate qualification | Real node and Companion resume/design acceptance, resource tests and packaged ISO/OTA checks | +| 5 | Remaining Omarchy parity adapters | Each inventory row supported or explicitly retained with reason and acceptance target | + +1A and 1B can be independent implementation workstreams once phase 0 contracts +are agreed. This planning session has not launched implementation agents. +App-generation functionality is incomplete until 1B and the skill acceptance pass. + +Expected code boundaries: Terminal UI/store/keyboard handling; new terminal +service and typed management routes; shared CLI/setup catalog; versioned tool and +skill packaging; design-kit extraction; app starter/validation; ISO and OTA +integration. Keep each in a focused contribution. Coordinate shared frontend +styles, backend routing and packaging files before implementation starts. + +## Acceptance criteria + +| ID | Required proof | +| --- | --- | +| TERM-01 | Real PTY supports editors, completion, colors, Unicode, signals and Codex; no fake production commands | +| TERM-02 | Start a long-running fixture and edit a file, close Terminal/tab/browser, reopen and resume the exact session and process | +| TERM-03 | Offline/reconnect, mobile sleep, frontend reload, backend restart and attachment-service restart do not duplicate execution | +| TERM-04 | A second device resumes after owner authentication; writer transfer is atomic; another identity cannot list/attach/terminate | +| TERM-05 | End session stops only its process scope; logout revokes access; neither operation deletes workspace files | +| TERM-06 | Reboot retains workspace metadata, labels interrupted sessions accurately and offers Codex conversation resume without rerunning commands | +| TERM-07 | Terminal keyboard ownership, focus, text selection, paste, resize, mobile keyboard and screen-reader mode work | +| AUTH-01 | Missing/expired/replayed grants, cross-origin sockets, forged proxy headers and app/guest credentials fail before shell I/O | +| AUTH-02 | Developer user cannot read production wallets/secrets or control production container sockets; authorized system workflow works | +| SETUP-01 | Fresh install and existing-node upgrade deliver the same core capabilities; Codex version works before network access | +| SETUP-02 | Failed download, package lock, low disk, reboot and concurrent setup recover without false completion or broken existing tools | +| SETUP-03 | Existing dotfiles, editor/agent choices, credentials, app data and uninstall decisions are preserved | +| AGENT-01 | A system-change task uses the correct operation/config, verifies its result and preserves unrelated services | +| AGENT-02 | An app-building task follows actual developer docs and passes manifest/build/launch/lifecycle checks | +| DESIGN-01 | Agent-built apps satisfy the companion design plan using shared assets/components, including failure and mobile states | +| PKG-01 | Exact candidate OTA and ISO contain matching tools, docs, skills and UI-kit versions; restore previous payload without removing user work | + +Backend unit execution uses `scripts/test-backend-isolated.sh`. PTY/process and +account-isolation integration tests run in disposable users/VMs, not a funded +production node. Terminal continuity tests use observable process IDs/output and +file checks, not a mocked “resumed” label. UI tests include 320/390/768/1440 pixel +layouts, landscape, enlarged text and actual Companion input on a device. + +Record source tests, disposable integration, actual-node acceptance and packaged +artifact results separately. Preserve unfinished requirements in +`docs/post-1.8.22-regressions-20261001.md` and the current release acceptance ledger; +this feature plan closes none of them. Later publication follows ngit review and +merge, then identical accepted main/tag objects on both ngit and Gitea, with the +required mirror checks. No release version or publication date is reserved here. + +## Decisions to resolve during design review + +The proposed defaults are a dedicated developer account, tmux persistence, +minimal terminal origin, bundled Codex, Bash, and a Vue starter with portable +design tokens. The implementation review must settle terminal-origin/certificate +handling on every supported ingress, the supported operator-shell model, the +exact first-release tool profile, and the approved visual baseline. Native +terminal emulators and the broader desktop parity backlog need separate device +compatibility decisions. None of these questions prevents reviewing this plan. From 820df9a196411ab75af49b835a56a8c5a98bfc92 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 07:31:06 -0400 Subject: [PATCH 02/17] feat: add Archipelago agent skills and app starter --- .agents/skills/archipelago-app/SKILL.md | 49 ++++++++++++++ .../skills/archipelago-app/agents/openai.yaml | 7 ++ .../references/app-contract.md | 17 +++++ .agents/skills/archipelago-design/SKILL.md | 41 ++++++++++++ .../archipelago-design/agents/openai.yaml | 7 ++ .../references/component-map.md | 15 +++++ .../references/design-contract.md | 18 +++++ .agents/skills/archipelago/SKILL.md | 53 +++++++++++++++ .agents/skills/archipelago/agents/openai.yaml | 7 ++ .../skills/archipelago/references/routing.md | 10 +++ .../archipelago/references/system-map.md | 19 ++++++ examples/archipelago-app-starter/.gitignore | 5 ++ examples/archipelago-app-starter/Dockerfile | 16 +++++ examples/archipelago-app-starter/README.md | 11 +++ examples/archipelago-app-starter/index.html | 13 ++++ examples/archipelago-app-starter/manifest.yml | 48 +++++++++++++ examples/archipelago-app-starter/package.json | 18 +++++ examples/archipelago-app-starter/src/App.vue | 67 +++++++++++++++++++ examples/archipelago-app-starter/src/main.ts | 5 ++ .../archipelago-app-starter/src/styles.css | 54 +++++++++++++++ .../archipelago-app-starter/vite.config.ts | 4 ++ 21 files changed, 484 insertions(+) create mode 100644 .agents/skills/archipelago-app/SKILL.md create mode 100644 .agents/skills/archipelago-app/agents/openai.yaml create mode 100644 .agents/skills/archipelago-app/references/app-contract.md create mode 100644 .agents/skills/archipelago-design/SKILL.md create mode 100644 .agents/skills/archipelago-design/agents/openai.yaml create mode 100644 .agents/skills/archipelago-design/references/component-map.md create mode 100644 .agents/skills/archipelago-design/references/design-contract.md create mode 100644 .agents/skills/archipelago/SKILL.md create mode 100644 .agents/skills/archipelago/agents/openai.yaml create mode 100644 .agents/skills/archipelago/references/routing.md create mode 100644 .agents/skills/archipelago/references/system-map.md create mode 100644 examples/archipelago-app-starter/.gitignore create mode 100644 examples/archipelago-app-starter/Dockerfile create mode 100644 examples/archipelago-app-starter/README.md create mode 100644 examples/archipelago-app-starter/index.html create mode 100644 examples/archipelago-app-starter/manifest.yml create mode 100644 examples/archipelago-app-starter/package.json create mode 100644 examples/archipelago-app-starter/src/App.vue create mode 100644 examples/archipelago-app-starter/src/main.ts create mode 100644 examples/archipelago-app-starter/src/styles.css create mode 100644 examples/archipelago-app-starter/vite.config.ts diff --git a/.agents/skills/archipelago-app/SKILL.md b/.agents/skills/archipelago-app/SKILL.md new file mode 100644 index 00000000..3af28e91 --- /dev/null +++ b/.agents/skills/archipelago-app/SKILL.md @@ -0,0 +1,49 @@ +--- +name: archipelago-app +description: Build, package, test, and update a manifest-driven Archipelago app using the real app developer contract, rootless runtime, and lifecycle acceptance flow. +metadata: + short-description: Build a real Archipelago app +--- + +# Archipelago app development + +Use this skill when creating or changing an Archipelago app, manifest, container +build, app integration, credentials, signer flow, or app preview. Always load +`archipelago-design` for newly authored UI. + +Read [app-contract.md](references/app-contract.md), +`docs/app-developer-guide.md`, and `docs/app-manifest-spec.md` before coding. + +## Required loop + +1. Create a dedicated worktree or project directory and choose the smallest + useful app scope. Prefer the maintained starter and its pinned dependencies. +2. Implement the app as a manifest, rootless container, persistent data path, + truthful health/readiness check, declared interface, and tests. Never add a + per-app Rust installer or rootful/Docker-socket shortcut. +3. Use generated secrets or declared secret files; never put credentials in a + manifest, image, logs, URL, or frontend bundle. Keep production wallets and + app databases outside development fixtures. +4. Validate the manifest and build context, then run the app through install, + start, stop, restart, manager restart, uninstall with data preservation, and + reinstall. Verify the real My Apps/Services launch path, not only a direct + port. +5. For UI, exercise standalone and embedded modes at 320, 390, 768, and 1440px + plus phone landscape, keyboard navigation, loading/empty/error/retry/success, + safe areas, and reduced motion. +6. Report source, disposable-node, actual-node, and release-artifact evidence + separately. Catalog publication is a separate request. + +Important runtime facts: + +- `/opt/archipelago/apps` is rebuilt from the runtime payload at backend start; + staging only there will be lost. Follow the developer guide's payload path. +- Signed catalog entries take precedence over disk manifests for catalog apps. +- Installed does not mean ready or launchable; use health and readiness evidence. +- App interfaces describe the service behind the gate. Do not hard-code a node + IP, scheme, app path, or host frame URL into app code. + +## References + +- [app contract and acceptance](references/app-contract.md) +- [design routing](../archipelago-design/SKILL.md) diff --git a/.agents/skills/archipelago-app/agents/openai.yaml b/.agents/skills/archipelago-app/agents/openai.yaml new file mode 100644 index 00000000..55c93594 --- /dev/null +++ b/.agents/skills/archipelago-app/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Archipelago app" + short_description: "Build a real Archipelago app" + brand_color: "#F7931A" + default_prompt: "Use $archipelago-app to build and validate this Archipelago app." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/archipelago-app/references/app-contract.md b/.agents/skills/archipelago-app/references/app-contract.md new file mode 100644 index 00000000..823235d5 --- /dev/null +++ b/.agents/skills/archipelago-app/references/app-contract.md @@ -0,0 +1,17 @@ +# App contract + +Start with `docs/app-developer-guide.md` and `docs/app-manifest-spec.md`; those +files are authoritative for fields and launch behavior. Validate with: + +```bash +./scripts/validate-app-manifest.sh apps//manifest.yml +python3 scripts/generate-app-catalog.py +python3 scripts/check-app-catalog-drift.py --release --strict +``` + +Use pinned image versions, read-only root, no-new-privileges, minimal +capabilities, rootless Podman, declared persistent data under +`/var/lib/archipelago/`, health checks, generated/declared secrets, and +truthful interfaces. Test install/start/stop/restart/manager-restart/uninstall +with data preservation/reinstall on a disposable node. Do not replace the +signed catalog to make a local test app appear. diff --git a/.agents/skills/archipelago-design/SKILL.md b/.agents/skills/archipelago-design/SKILL.md new file mode 100644 index 00000000..e6926f77 --- /dev/null +++ b/.agents/skills/archipelago-design/SKILL.md @@ -0,0 +1,41 @@ +--- +name: archipelago-design +description: Create or change Archipelago UI using the shared semantic tokens, components, responsive patterns, accessibility states, and embedded/standalone host contract. +metadata: + short-description: Keep Archipelago UI consistent +--- + +# Archipelago design system + +Use this skill for any new or changed Archipelago UI, including app screens, +Terminal, setup flows, dialogs, dashboards, and app wrappers. Read +[design-contract.md](references/design-contract.md) before implementation. + +## Rules + +- Find and reuse the closest shared component and pattern before creating one. +- Use semantic `--archy-*` tokens and the pinned kit version. A bespoke color, + font, radius, shadow, or z-index needs a documented reason. +- Preserve the dark baseline, readable surfaces, 4px spacing rhythm, bottom + action placement, 44px touch targets, visible focus, and safe-area behavior. +- Keep terminal/editor/tmux keys inside the terminal focus boundary; generic + modal Escape/arrow handlers must not consume them. +- Implement loading, empty, offline, denied, validation-error, retry, disabled, + and confirmed-success states. Status color must have text or icon support. +- Test standalone and embedded modes. Embedded apps do not duplicate the host + wallpaper or navigation and must work without a host handshake. + +## Workflow + +1. Read the reference screen and source; choose the matching component/pattern. +2. Build with the shared kit and local assets, not runtime dashboard CSS or CDN + fonts. Keep host RPC, signer, and credential bridges behind typed adapters. +3. Render the gallery/preview at required viewports and inspect screenshots and + keyboard behavior. Check contrast on the real composited surface. +4. Record deliberate exceptions and update the kit only when a reusable need is + demonstrated. Do not silently accept visual-diff changes. + +## References + +- [design contract](references/design-contract.md) +- [existing component map](references/component-map.md) diff --git a/.agents/skills/archipelago-design/agents/openai.yaml b/.agents/skills/archipelago-design/agents/openai.yaml new file mode 100644 index 00000000..4d113931 --- /dev/null +++ b/.agents/skills/archipelago-design/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Archipelago design" + short_description: "Keep Archipelago UI consistent" + brand_color: "#F7931A" + default_prompt: "Use $archipelago-design to implement this UI in Archipelago's design system." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/archipelago-design/references/component-map.md b/.agents/skills/archipelago-design/references/component-map.md new file mode 100644 index 00000000..53e4068f --- /dev/null +++ b/.agents/skills/archipelago-design/references/component-map.md @@ -0,0 +1,15 @@ +# Existing component map + +Reuse these first: + +- Structure: `BaseModal.vue`, `BackButton.vue`, `EmptyState.vue`, + `SkeletonCard.vue`. +- Controls: `ToggleSwitch.vue`, `PasswordRevealInput.vue`, + `AppSearchField.vue`, `CopyButton.vue`. +- Feedback: `ToastStack.vue`, `ContainerStatus.vue`, existing upload/progress + components. +- Completion: `PaymentSuccessPane.vue`, `IdentitySuccessPane.vue`. +- App flows: `AppLauncherOverlay.vue`, `AppCredentialInterstitial.vue`. + +The terminal must have an explicit keyboard ownership boundary and must not be +wrapped in the generic arrow-key modal behavior without adapting it. diff --git a/.agents/skills/archipelago-design/references/design-contract.md b/.agents/skills/archipelago-design/references/design-contract.md new file mode 100644 index 00000000..4809b62f --- /dev/null +++ b/.agents/skills/archipelago-design/references/design-contract.md @@ -0,0 +1,18 @@ +# Design contract + +The current baseline is defined by `neode-ui/src/style.css`, +`neode-ui/tailwind.config.js`, `BaseModal.vue`, `AppSearchField.vue`, +`EmptyState.vue`, `SkeletonCard.vue`, and `PaymentSuccessPane.vue`. Preserve the +dark canvas, glass-card surface, 4px spacing scale, semantic orange accent, +local licensed fonts, bottom card actions, 40px desktop/52px mobile search, +44px touch targets, visible focus, dynamic viewport, safe-area and embedded +canvas rules while the shared kit is extracted. + +Use semantic tokens, not raw values. New controls must document keyboard, +focus, disabled, loading, validation, error, retry, success, responsive, and +reduced-motion behavior. No essential action may be hover-only. Use the existing +dialog footer/content split and restore focus after close. + +Visual acceptance covers 320/390/768/1440px, phone landscape, enlarged text, +standalone/embedded modes, dark native controls, no-blur fallback, and actual +Companion WebView behavior where applicable. diff --git a/.agents/skills/archipelago/SKILL.md b/.agents/skills/archipelago/SKILL.md new file mode 100644 index 00000000..f5ccabba --- /dev/null +++ b/.agents/skills/archipelago/SKILL.md @@ -0,0 +1,53 @@ +--- +name: archipelago +description: Configure, troubleshoot, and safely modify an Archipelago node or its developer environment using the repository's typed operations, ownership rules, and recovery workflow. +metadata: + short-description: Work safely on Archipelago systems +--- + +# Archipelago system work + +Use this skill for node configuration, terminal/developer setup, service +diagnosis, network and SSH work, supported app operations, and system changes. +For app implementation load `archipelago-app`; for any new or changed UI also +load `archipelago-design`. + +Read [system-map.md](references/system-map.md) before acting. Read `AGENTS.md` +and the relevant project documentation in the current checkout. + +## Workflow + +1. Identify the node, repository/worktree, owner, and whether the request is + planning, source work, a disposable test, or a live-node operation. +2. Inspect current state before changing it. Prefer `archy`/Archipelago RPC + operations and existing scripts over direct edits or ad-hoc service commands. +3. Preserve wallets, app data, credentials, user uninstall decisions, and + unrelated services. Back up only the scoped state before a mutation. +4. Make the smallest reversible change. Keep generated state separate from + user overrides; never edit generated or packaged files when an owned source + or managed override exists. +5. Verify the actual result and report source tests, disposable integration, + live-node acceptance, and packaged-artifact checks separately. + +For repository work, use a dedicated worktree and focused branch. For backend +unit tests use `scripts/test-backend-isolated.sh`; do not run unrestricted +`cargo test` on a node with installed apps. Do not publish OTA, ISO, catalog, +or Git mirrors from this skill unless that publication is explicitly requested +and every release gate is satisfied. + +## Terminal and sessions + +Treat terminal close as detach. Resume the existing named session; never create +a duplicate shell or replay a lost command. A reboot can restore workspace and +Codex conversation metadata but cannot restore the old process. Distinguish +running, detached, ended, and interrupted states in user-facing output. + +Keep credentials, wallet material, terminal output, and command contents out of +diagnostics and support bundles. A terminal is a privileged capability: verify +the authenticated owner, origin, attachment grant, and account boundary before +any PTY bytes flow. + +## References + +- [system map and safe recipes](references/system-map.md) +- [app and design routing](references/routing.md) diff --git a/.agents/skills/archipelago/agents/openai.yaml b/.agents/skills/archipelago/agents/openai.yaml new file mode 100644 index 00000000..6bbb6078 --- /dev/null +++ b/.agents/skills/archipelago/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Archipelago system" + short_description: "Work safely on Archipelago systems" + brand_color: "#F7931A" + default_prompt: "Use $archipelago to inspect and safely change this Archipelago node." +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/archipelago/references/routing.md b/.agents/skills/archipelago/references/routing.md new file mode 100644 index 00000000..ef303ea6 --- /dev/null +++ b/.agents/skills/archipelago/references/routing.md @@ -0,0 +1,10 @@ +# Skill routing + +Load `archipelago-app` for manifests, containers, app previews, lifecycle, +credentials, signer integration, or app packaging. Load `archipelago-design` for +any newly authored or changed UI. For backend-only work, use only the system +skill and the relevant project docs. + +Planning stays planning. A source change, node mutation, deployment, or +publication requires that explicit scope from the user. A skill supplies +workflow knowledge; it does not grant additional privileges. diff --git a/.agents/skills/archipelago/references/system-map.md b/.agents/skills/archipelago/references/system-map.md new file mode 100644 index 00000000..c6fc230b --- /dev/null +++ b/.agents/skills/archipelago/references/system-map.md @@ -0,0 +1,19 @@ +# System map and safe recipes + +The native backend is `core/archipelago`; the Vue dashboard is `neode-ui`; ISO +and first-boot material lives under `image-recipe` and `scripts`; app manifests +are under `apps//manifest.yml`. Read `CLAUDE.md`, `AGENTS.md`, and the current +release checklist before release work. + +Use the existing typed RPC and orchestration layers for app lifecycle, network, +wallet, identity, and service operations. Inspect a matching handler before +adding an endpoint. Preserve the existing session cookie, CSRF, Origin, and +app-gate protections. + +For source tests, run frontend checks from `neode-ui`. Backend unit tests run +only through `scripts/test-backend-isolated.sh`; live host checks must be named +and explicit. Do not touch real wallet/payment/channel state for a test fixture. + +Developer changes belong in a dedicated worktree. Keep generated catalog, +runtime payload, release artifacts, and local node state separate until the +request explicitly includes packaging or deployment. diff --git a/examples/archipelago-app-starter/.gitignore b/examples/archipelago-app-starter/.gitignore new file mode 100644 index 00000000..c744b43a --- /dev/null +++ b/examples/archipelago-app-starter/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +.env.* +!.env.example diff --git a/examples/archipelago-app-starter/Dockerfile b/examples/archipelago-app-starter/Dockerfile new file mode 100644 index 00000000..0940ea5f --- /dev/null +++ b/examples/archipelago-app-starter/Dockerfile @@ -0,0 +1,16 @@ +FROM node:22-alpine AS build +WORKDIR /src +COPY package.json vite.config.ts index.html ./ +COPY src ./src +RUN npm install --ignore-scripts && npm run build + +FROM nginx:1.27-alpine +COPY --from=build /src/dist /usr/share/nginx/html +COPY <<'EOF' /etc/nginx/conf.d/default.conf +server { + listen 8080; + root /usr/share/nginx/html; + index index.html; + location / { try_files $uri $uri/ /index.html; } +} +EOF diff --git a/examples/archipelago-app-starter/README.md b/examples/archipelago-app-starter/README.md new file mode 100644 index 00000000..443616f1 --- /dev/null +++ b/examples/archipelago-app-starter/README.md @@ -0,0 +1,11 @@ +# Archipelago app starter + +This is a small standalone Vue/Vite app that demonstrates Archipelago's initial +design contract: semantic `--archy-*` tokens, dark native controls, responsive +cards, bottom actions, visible focus, 44px targets, and loading/empty/error/ +success states. It intentionally has no dashboard dependency or host handshake. + +Run `npm install && npm run dev` from this directory. Before packaging, copy the +source into a project, replace the demo state with a real API, pin dependencies, +and validate the manifest using the repository app developer guide. The Docker +file is a starter build example; it is not a substitute for lifecycle acceptance. diff --git a/examples/archipelago-app-starter/index.html b/examples/archipelago-app-starter/index.html new file mode 100644 index 00000000..5cdc4f16 --- /dev/null +++ b/examples/archipelago-app-starter/index.html @@ -0,0 +1,13 @@ + + + + + + + Archipelago app + + +
+ + + diff --git a/examples/archipelago-app-starter/manifest.yml b/examples/archipelago-app-starter/manifest.yml new file mode 100644 index 00000000..2bce05fa --- /dev/null +++ b/examples/archipelago-app-starter/manifest.yml @@ -0,0 +1,48 @@ +app: + id: archipelago-app-starter + name: Archipelago App Starter + version: 0.1.0 + description: A minimal Archipelago design-system starter with responsive states. + container: + build: + context: . + dockerfile: Dockerfile + tag: localhost/archipelago-app-starter:0.1.0 + resources: + cpu_limit: 1 + memory_limit: 128Mi + disk_limit: 256Mi + security: + capabilities: [] + readonly_root: true + no_new_privileges: true + network_policy: isolated + ports: + - host: 8180 + container: 8080 + protocol: tcp + bind: 127.0.0.1 + auth: gated + volumes: + - type: bind + source: /var/lib/archipelago/archipelago-app-starter + target: /data + options: [rw] + health_check: + type: http + endpoint: http://127.0.0.1:8080 + path: / + interval: 30s + timeout: 5s + retries: 3 + interfaces: + main: + name: Archipelago App Starter + description: Responsive design-system starter + type: ui + port: 8180 + protocol: http + path: / + metadata: + category: development + tier: optional diff --git a/examples/archipelago-app-starter/package.json b/examples/archipelago-app-starter/package.json new file mode 100644 index 00000000..f6b6d955 --- /dev/null +++ b/examples/archipelago-app-starter/package.json @@ -0,0 +1,18 @@ +{ + "name": "archipelago-app-starter", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1", + "build": "vite build", + "preview": "vite preview --host 127.0.0.1" + }, + "dependencies": { + "@vitejs/plugin-vue": "^6.0.1", + "vite": "^7.2.2", + "typescript": "~5.9.3", + "vue": "^3.5.24" + }, + "devDependencies": {} +} diff --git a/examples/archipelago-app-starter/src/App.vue b/examples/archipelago-app-starter/src/App.vue new file mode 100644 index 00000000..949198ee --- /dev/null +++ b/examples/archipelago-app-starter/src/App.vue @@ -0,0 +1,67 @@ + + + diff --git a/examples/archipelago-app-starter/src/main.ts b/examples/archipelago-app-starter/src/main.ts new file mode 100644 index 00000000..fdbdce56 --- /dev/null +++ b/examples/archipelago-app-starter/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './styles.css' + +createApp(App).mount('#app') diff --git a/examples/archipelago-app-starter/src/styles.css b/examples/archipelago-app-starter/src/styles.css new file mode 100644 index 00000000..bca42b4c --- /dev/null +++ b/examples/archipelago-app-starter/src/styles.css @@ -0,0 +1,54 @@ +:root { + color-scheme: dark; + --archy-canvas: #0a0a0a; + --archy-surface: rgba(0, 0, 0, 0.65); + --archy-surface-muted: rgba(255, 255, 255, 0.06); + --archy-border: rgba(255, 255, 255, 0.18); + --archy-text: rgba(255, 255, 255, 0.92); + --archy-muted: rgba(255, 255, 255, 0.58); + --archy-accent: #fb923c; + --archy-success: #4ade80; + --archy-danger: #f87171; + --archy-radius-card: 16px; + --archy-radius-control: 12px; + --archy-shadow: 0 8px 24px rgba(0, 0, 0, 0.45); + --archy-space-1: 4px; + --archy-space-2: 8px; + --archy-space-3: 12px; + --archy-space-4: 16px; + --archy-space-6: 24px; + --archy-space-8: 32px; + font-family: Avenir Next, system-ui, sans-serif; + background: var(--archy-canvas); + color: var(--archy-text); +} + +* { box-sizing: border-box; } +body { margin: 0; min-width: 320px; background: var(--archy-canvas); } +button { font: inherit; } +button:focus-visible { outline: 2px solid var(--archy-accent); outline-offset: 3px; } + +.archy-shell { width: min(100% - 32px, 960px); margin: 0 auto; padding: 40px 0 56px; } +.archy-header, .archy-section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--archy-space-4); } +.archy-header { margin-bottom: var(--archy-space-8); } +.archy-eyebrow { margin: 0 0 var(--archy-space-2); color: var(--archy-accent); font-size: 0.72rem; font-weight: 800; letter-spacing: 0.12em; } +h1, h2 { margin: 0; line-height: 1.15; } +h1 { font-size: clamp(1.8rem, 5vw, 2.8rem); } +h2 { font-size: 1.25rem; } +.archy-muted, .archy-footnote { color: var(--archy-muted); } +.archy-card { padding: var(--archy-space-6); background: var(--archy-surface); border: 1px solid var(--archy-border); border-radius: var(--archy-radius-card); box-shadow: var(--archy-shadow); } +.archy-section-heading { align-items: center; margin-bottom: var(--archy-space-6); } +.archy-button { min-height: 44px; padding: 10px 18px; border: 1px solid rgba(251, 146, 60, 0.35); border-radius: var(--archy-radius-control); background: rgba(251, 146, 60, 0.2); color: #fed7aa; cursor: pointer; } +.archy-button:hover { background: rgba(251, 146, 60, 0.3); } +.archy-button-secondary { border-color: var(--archy-border); background: var(--archy-surface-muted); color: var(--archy-text); } +.archy-state { display: grid; gap: var(--archy-space-3); padding: var(--archy-space-6); border-radius: var(--archy-radius-control); background: var(--archy-surface-muted); color: var(--archy-muted); } +.archy-state strong { color: var(--archy-text); } +.archy-state-error { border: 1px solid rgba(248, 113, 113, 0.35); } +.archy-state-error strong { color: var(--archy-danger); } +.archy-list { display: grid; gap: var(--archy-space-2); padding: 0; margin: 0; list-style: none; } +.archy-list-row { display: flex; align-items: center; justify-content: space-between; gap: var(--archy-space-4); padding: var(--archy-space-4); border-radius: var(--archy-radius-control); background: var(--archy-surface-muted); } +.archy-list-row div { display: grid; gap: var(--archy-space-1); min-width: 0; } +.archy-status { color: var(--archy-success); font-size: 0.8rem; } +.archy-footnote { margin: var(--archy-space-4) 0 0; font-size: 0.8rem; } +@media (max-width: 560px) { .archy-shell { width: min(100% - 24px, 960px); padding-top: 24px; } .archy-header { flex-direction: column; } .archy-header .archy-button { width: 100%; } .archy-card { padding: var(--archy-space-4); } .archy-section-heading { align-items: stretch; flex-direction: column; } .archy-section-heading .archy-button { width: 100%; } } +@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; } } diff --git a/examples/archipelago-app-starter/vite.config.ts b/examples/archipelago-app-starter/vite.config.ts new file mode 100644 index 00000000..2d575c4c --- /dev/null +++ b/examples/archipelago-app-starter/vite.config.ts @@ -0,0 +1,4 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ plugins: [vue()] }) From 16085c723a7ead032d64f74832862a438e6f626b Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 07:32:15 -0400 Subject: [PATCH 03/17] feat: add persistent Archipelago developer sessions --- scripts/archy-session | 118 ++++++++++++++++++++++++++++ scripts/tests/archy-session-test.sh | 14 ++++ 2 files changed, 132 insertions(+) create mode 100755 scripts/archy-session create mode 100755 scripts/tests/archy-session-test.sh diff --git a/scripts/archy-session b/scripts/archy-session new file mode 100755 index 00000000..22563f4f --- /dev/null +++ b/scripts/archy-session @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Persistent Archipelago developer sessions. Terminal attachments are disposable; +# the tmux session owns the process and durable JSON owns its discoverable state. +set -euo pipefail + +STATE_DIR="${ARCHY_SESSION_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/archipelago/sessions}" +mkdir -p "$STATE_DIR" +die() { echo "archy-session: $*" >&2; exit 2; } +need_tmux() { command -v tmux >/dev/null 2>&1 || die "tmux is required"; } +need_python() { command -v python3 >/dev/null 2>&1 || die "python3 is required"; } +valid_id() { [[ "$1" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$ ]]; } +session_file() { printf '%s/%s.json' "$STATE_DIR" "$1"; } + +write_record() { + local id=$1 name=$2 workspace=$3 state=$4 tmux_name=$5 + need_python + python3 - "$STATE_DIR" "$id" "$name" "$workspace" "$state" "$tmux_name" <<'PY' +import json, os, sys, tempfile, time +state_dir, ident, name, workspace, state, tmux_name = sys.argv[1:] +record = {"schema": 1, "id": ident, "name": name, "workspace": workspace, + "state": state, "tmux": tmux_name, "updated_at": int(time.time())} +fd, tmp = tempfile.mkstemp(prefix=f".{ident}.", suffix=".tmp", dir=state_dir) +try: + with os.fdopen(fd, "w", encoding="utf-8") as handle: + json.dump(record, handle, sort_keys=True); handle.write("\n"); handle.flush(); os.fsync(handle.fileno()) + os.replace(tmp, os.path.join(state_dir, ident + ".json")) +finally: + try: os.unlink(tmp) + except FileNotFoundError: pass +PY +} + +new_id() { need_python; python3 - <<'PY' +import secrets +print("s-" + secrets.token_hex(8)) +PY +} + +read_json() { + python3 - "$1" "$2" <<'PY' +import json, sys +print(json.load(open(sys.argv[1], encoding="utf-8"))[sys.argv[2]]) +PY +} + +cmd_list() { + need_tmux; need_python + python3 - "$STATE_DIR" <<'PY' +import json, os, subprocess, sys +state_dir = sys.argv[1] +for filename in sorted(os.listdir(state_dir)): + if not filename.endswith(".json"): continue + try: record = json.load(open(os.path.join(state_dir, filename), encoding="utf-8")) + except (OSError, ValueError, KeyError): continue + live = subprocess.run(["tmux", "has-session", "-t", record["tmux"]], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0 + effective = "detached" if live else ("interrupted" if record.get("state") == "running" else record.get("state", "ended")) + print(f"{record['id']}\t{record['name']}\t{effective}\t{record['workspace']}") +PY +} + +cmd_start() { + need_tmux + local name=${1:-Work} workspace=${2:-$HOME/Work} + [[ -d "$workspace" ]] || die "workspace does not exist: $workspace" + local id tmux_name; id=$(new_id); tmux_name="archy-$id" + tmux new-session -d -s "$tmux_name" -c "$workspace" + write_record "$id" "$name" "$workspace" running "$tmux_name" + printf '%s\n' "$id" +} + +record_for() { + local id=$1 file; valid_id "$id" || die "invalid session id"; file=$(session_file "$id") + [[ -f "$file" ]] || die "session not found: $id"; printf '%s\n' "$file" +} + +cmd_attach() { + need_tmux; local file=$1 tmux_name; tmux_name=$(read_json "$file" tmux) + tmux has-session -t "$tmux_name" 2>/dev/null || die "session is not running; inspect with list" + exec tmux attach-session -t "$tmux_name" +} + +cmd_rename() { + local id=$1 name=$2 file=$3; local workspace tmux_name state=ended + workspace=$(read_json "$file" workspace); tmux_name=$(read_json "$file" tmux); need_tmux + tmux has-session -t "$tmux_name" 2>/dev/null && state=detached + write_record "$id" "$name" "$workspace" "$state" "$tmux_name" +} + +cmd_end() { + local id=$1 file=$2; local tmux_name name workspace + tmux_name=$(read_json "$file" tmux); name=$(read_json "$file" name); workspace=$(read_json "$file" workspace); need_tmux + tmux kill-session -t "$tmux_name" 2>/dev/null || true + write_record "$id" "$name" "$workspace" ended "$tmux_name" +} + +usage() { + cat >&2 <<'EOF' +Usage: + archy-session list + archy-session start [name] [workspace] + archy-session attach + archy-session rename + archy-session end + +Closing an attached terminal detaches it. end is the explicit destructive +operation and never removes the workspace or its files. +EOF +} + +command=${1:-} +case "$command" in +list) cmd_list ;; +start) shift; cmd_start "$@" ;; +attach) shift; [[ $# == 1 ]] || die "attach requires an id"; file=$(record_for "$1"); cmd_attach "$file" ;; +rename) shift; [[ $# == 2 ]] || die "rename requires an id and name"; file=$(record_for "$1"); cmd_rename "$1" "$2" "$file" ;; +end) shift; [[ $# == 1 ]] || die "end requires an id"; file=$(record_for "$1"); cmd_end "$1" "$file" ;; +*) usage; exit 2 ;; +esac diff --git a/scripts/tests/archy-session-test.sh b/scripts/tests/archy-session-test.sh new file mode 100755 index 00000000..95e06b7d --- /dev/null +++ b/scripts/tests/archy-session-test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail +ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT +export ARCHY_SESSION_STATE_DIR="$TMP/state"; mkdir -p "$TMP/work" +if ! command -v tmux >/dev/null 2>&1; then echo "SKIP: tmux is unavailable"; exit 0; fi +id=$("$ROOT/scripts/archy-session" start demo "$TMP/work") +test -f "$TMP/state/$id.json" +grep -q "$id" <("$ROOT/scripts/archy-session" list) +"$ROOT/scripts/archy-session" rename "$id" renamed +grep -q $'renamed\tdetached' <("$ROOT/scripts/archy-session" list) +"$ROOT/scripts/archy-session" end "$id" +grep -q $'renamed\tended' <("$ROOT/scripts/archy-session" list) +test -d "$TMP/work"; echo "archy-session lifecycle passed" From f50521072d1cef5d5a8ff5451a4e8f6f5f3012e3 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 07:32:31 -0400 Subject: [PATCH 04/17] docs: record implemented session execution slice --- docs/terminal-developer-environment-spec.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/terminal-developer-environment-spec.md b/docs/terminal-developer-environment-spec.md index fa40485c..e9258b67 100644 --- a/docs/terminal-developer-environment-spec.md +++ b/docs/terminal-developer-environment-spec.md @@ -1,7 +1,9 @@ # Archipelago terminal and developer environment plan -Status: planning draft, 2026-10-09. No runtime implementation or installation is -part of this change. Repository baseline: `2cb1bae5ce244d387679f90951d62fd030ebf228`. +Status: execution draft, 2026-10-09. The local persistent-session primitive is +implemented in `scripts/archy-session`; dashboard/WebSocket attachment, +developer-account provisioning, and Codex installation remain follow-on slices. +Repository baseline: `2cb1bae5ce244d387679f90951d62fd030ebf228`. Archipelago should let an owner open a real terminal, resume previous work, configure their system, and ask a preinstalled coding agent to build an app that @@ -163,6 +165,19 @@ has normal shell semantics. Focus can move to toolbar controls and back. ## Persistent sessions and one-click resume +### Implemented local primitive + +`scripts/archy-session` provides the first execution slice for named sessions. +It stores atomic JSON metadata under the user state directory and uses one +isolated tmux session per workspace. `list` reconciles metadata with the live +tmux process, `attach` resumes an existing process without creating a duplicate, +`rename` preserves the workspace, and `end` is the only operation that stops a +session. Its lifecycle test is `scripts/tests/archy-session-test.sh`. + +This helper is intentionally not the browser security boundary: the future +session service must enforce owner/node/boot identity, attachment grants, CSRF, +and revocation before exposing it through the dashboard. + This is a core release criterion, including the first usable terminal milestone. Session execution lives on the node in a dedicated, supervised user environment. From 16c84c450ef8bcdf0beaddb6cdbd0ef94264ce88 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 07:59:12 -0400 Subject: [PATCH 05/17] feat: add authenticated resumable terminal --- core/archipelago/src/api/handler/mod.rs | 20 ++ core/archipelago/src/api/handler/terminal.rs | 257 +++++++++++++ docs/terminal-uat-deployment.md | 47 +++ neode-ui/src/components/CLIPopup.vue | 360 +++++-------------- 4 files changed, 414 insertions(+), 270 deletions(-) create mode 100644 core/archipelago/src/api/handler/terminal.rs create mode 100644 docs/terminal-uat-deployment.md diff --git a/core/archipelago/src/api/handler/mod.rs b/core/archipelago/src/api/handler/mod.rs index c05a3baf..908af95d 100644 --- a/core/archipelago/src/api/handler/mod.rs +++ b/core/archipelago/src/api/handler/mod.rs @@ -14,6 +14,7 @@ mod remote_input; mod remote_relay; mod rental_playback; mod routstr_proxy; +mod terminal; mod websocket; use crate::api::rpc::RpcHandler; @@ -426,6 +427,16 @@ impl ApiHandler { .await; } + // Owner terminal attachment — the browser socket is disposable; the + // authenticated tmux session survives reconnects and browser closes. + if method == Method::GET && path == "/ws/terminal" { + if !self.is_authenticated(req.headers()).await { + tracing::warn!("401 WebSocket /ws/terminal — session invalid or missing"); + return Ok(Self::unauthorized()); + } + return Self::handle_terminal_websocket(req).await; + } + // Remote input WebSocket — companion app sends keyboard/mouse events if method == Method::GET && path == "/ws/remote-input" { if !self.is_authenticated(req.headers()).await { @@ -544,6 +555,15 @@ impl ApiHandler { .unwrap()) } + (Method::GET, "/api/terminal/sessions") => { + if !self.is_authenticated(&headers).await { return Ok(Self::unauthorized()); } + terminal::list_response().await + } + (Method::POST, "/api/terminal/sessions") => { + if !self.is_authenticated(&headers).await { return Ok(Self::unauthorized()); } + terminal::create(&body_bytes).await + } + // Node message — P2P endpoint (authenticated by source validation, not cookie) (Method::POST, "/archipelago/node-message") => { Self::handle_node_message(body_bytes).await diff --git a/core/archipelago/src/api/handler/terminal.rs b/core/archipelago/src/api/handler/terminal.rs new file mode 100644 index 00000000..628d953c --- /dev/null +++ b/core/archipelago/src/api/handler/terminal.rs @@ -0,0 +1,257 @@ +//! Owner-authenticated terminal sessions backed by private tmux processes. +//! +//! Browser connections are disposable attachments. The tmux process and its +//! metadata remain on the node so a reconnect resumes the same workspace. + +use anyhow::{anyhow, Result}; +use futures_util::{SinkExt, StreamExt}; +use hyper::{Request, Response, StatusCode}; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; +use tokio::process::Command; +use tokio_tungstenite::tungstenite::Message; +use uuid::Uuid; + +use super::{build_response, ApiHandler}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct SessionRecord { + pub schema: u8, + pub id: String, + pub name: String, + pub workspace: String, + pub state: String, + pub tmux: String, + pub updated_at: i64, +} + +#[derive(Debug, Deserialize)] +struct CreateRequest { + name: Option, + workspace: Option, +} + +#[derive(Debug, Deserialize)] +struct ClientMessage { + #[serde(rename = "type")] + kind: String, + data: Option, + cols: Option, + rows: Option, +} + +pub(crate) fn state_dir() -> PathBuf { + std::env::var_os("ARCHY_SESSION_STATE_DIR") + .map(PathBuf::from) + .unwrap_or_else(|| { + if let Some(xdg) = std::env::var_os("XDG_STATE_HOME") { + PathBuf::from(xdg).join("archipelago/sessions") + } else if let Some(home) = std::env::var_os("HOME") { + let user_dir = PathBuf::from(home).join(".local/state/archipelago/sessions"); + if user_dir.exists() { + user_dir + } else { + PathBuf::from("/var/lib/archipelago/sessions") + } + } else { + PathBuf::from("/var/lib/archipelago/sessions") + } + }) +} + +fn valid_id(id: &str) -> bool { + !id.is_empty() + && id.len() <= 64 + && id + .bytes() + .all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'_' || b == b'-') +} + +async fn read_record(dir: &Path, id: &str) -> Result { + if !valid_id(id) { + return Err(anyhow!("invalid session id")); + } + let bytes = tokio::fs::read(dir.join(format!("{id}.json"))).await?; + Ok(serde_json::from_slice(&bytes)?) +} + +async fn tmux_alive(name: &str) -> bool { + Command::new("tmux") + .args(["has-session", "-t", name]) + .output() + .await + .map(|out| out.status.success()) + .unwrap_or(false) +} + +async fn write_record(dir: &Path, record: &SessionRecord) -> Result<()> { + tokio::fs::create_dir_all(dir).await?; + let tmp = dir.join(format!(".{}.tmp-{}", record.id, Uuid::new_v4())); + let final_path = dir.join(format!("{}.json", record.id)); + tokio::fs::write(&tmp, serde_json::to_vec_pretty(record)?).await?; + tokio::fs::rename(tmp, final_path).await?; + Ok(()) +} + +pub(crate) async fn list(dir: &Path) -> Result> { + let mut out = Vec::new(); + let mut entries = match tokio::fs::read_dir(dir).await { + Ok(entries) => entries, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(out), + Err(error) => return Err(error.into()), + }; + while let Some(entry) = entries.next_entry().await? { + if entry.path().extension().and_then(|s| s.to_str()) != Some("json") { + continue; + } + let Ok(bytes) = tokio::fs::read(entry.path()).await else { + continue; + }; + let Ok(mut record) = serde_json::from_slice::(&bytes) else { + continue; + }; + record.state = if tmux_alive(&record.tmux).await { + "detached".into() + } else if record.state == "running" { + "interrupted".into() + } else { + record.state.clone() + }; + out.push(record); + } + out.sort_by(|a, b| b.updated_at.cmp(&a.updated_at)); + Ok(out) +} + +pub(crate) async fn list_response() -> Result> { + Ok(Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json") + .body(hyper::Body::from(serde_json::to_vec( + &list(&state_dir()).await?, + )?))?) +} + +pub(crate) async fn create(body: &[u8]) -> Result> { + let request: CreateRequest = serde_json::from_slice(body).unwrap_or(CreateRequest { + name: None, + workspace: None, + }); + let workspace_was_requested = request.workspace.is_some(); + let workspace = request.workspace.unwrap_or_else(|| { + std::env::var_os("HOME") + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("/tmp")) + .join("Work") + .to_string_lossy() + .into_owned() + }); + let workspace_path = PathBuf::from(&workspace); + if !workspace_was_requested { + tokio::fs::create_dir_all(&workspace_path).await?; + } + if !workspace_path.is_absolute() || !workspace_path.is_dir() || workspace_path == Path::new("/") + { + return Ok(build_response( + StatusCode::BAD_REQUEST, + "application/json", + hyper::Body::from(r#"{"error":"workspace must be an existing non-root directory"}"#), + )); + } + let id = format!("s-{}", Uuid::new_v4().simple()); + let tmux_name = format!("archy-{id}"); + let output = Command::new("tmux") + .args(["new-session", "-d", "-s", &tmux_name, "-c", &workspace]) + .output() + .await?; + if !output.status.success() { + return Ok(build_response( + StatusCode::SERVICE_UNAVAILABLE, + "application/json", + hyper::Body::from(r#"{"error":"tmux could not start the session"}"#), + )); + } + let name = request + .name + .filter(|n| !n.trim().is_empty()) + .unwrap_or_else(|| "Work".into()); + let record = SessionRecord { + schema: 1, + id, + name, + workspace, + state: "running".into(), + tmux: tmux_name, + updated_at: chrono::Utc::now().timestamp(), + }; + write_record(&state_dir(), &record).await?; + Ok(Response::builder() + .status(StatusCode::CREATED) + .header("Content-Type", "application/json") + .body(hyper::Body::from(serde_json::to_vec(&record)?))?) +} + +pub(crate) async fn websocket(req: Request) -> Result> { + let id = req + .uri() + .query() + .and_then(|query| { + query + .split('&') + .find_map(|part| part.strip_prefix("session=")) + }) + .unwrap_or("") + .to_string(); + let record = read_record(&state_dir(), &id).await?; + if !tmux_alive(&record.tmux).await { + return Ok(build_response( + StatusCode::CONFLICT, + "application/json", + hyper::Body::from(r#"{"error":"session is not running"}"#), + )); + } + let (response, ws_fut) = + hyper_ws_listener::create_ws(req).map_err(|e| anyhow!("WebSocket upgrade failed: {e}"))?; + if let Some(ws_fut) = ws_fut { + tokio::spawn(async move { + let Ok(Ok(stream)) = ws_fut.await else { return }; + let (mut tx, mut rx) = stream.split(); + let mut interval = tokio::time::interval(std::time::Duration::from_millis(150)); + let mut last = String::new(); + loop { + tokio::select! { + _ = interval.tick() => { + let output = Command::new("tmux").args(["capture-pane", "-p", "-e", "-t", &record.tmux, "-S", "-250"]).output().await; + if let Ok(output) = output { + let text = String::from_utf8_lossy(&output.stdout).into_owned(); + if text != last { last = text.clone(); if tx.send(Message::Text(serde_json::json!({"type":"output", "data":text}).to_string())).await.is_err() { break; } } + } else { break; } + } + message = rx.next() => match message { + Some(Ok(Message::Text(text))) => { + let Ok(message) = serde_json::from_str::(&text) else { continue }; + match message.kind.as_str() { + "input" => if let Some(data) = message.data { let mut command = Command::new("tmux"); command.args(["send-keys", "-t", &record.tmux]); if data == "\u{3}" { command.arg("C-c"); } else if data == "\u{4}" { command.arg("C-d"); } else { command.args(["-l", "--", &data]); } let _ = command.output().await; }, + "resize" => if let (Some(cols), Some(rows)) = (message.cols, message.rows) { let _ = Command::new("tmux").args(["resize-window", "-t", &record.tmux, "-x", &cols.to_string(), "-y", &rows.to_string()]).output().await; }, + "ping" => { let _ = tx.send(Message::Text(r#"{"type":"pong"}"#.into())).await; }, + _ => {} + } + } + Some(Ok(Message::Close(_))) | None => break, + Some(Err(_)) => break, + _ => {} + } + } + } + }); + } + Ok(response) +} + +impl ApiHandler { + pub(super) async fn handle_terminal_websocket( + req: Request, + ) -> Result> { + websocket(req).await + } +} diff --git a/docs/terminal-uat-deployment.md b/docs/terminal-uat-deployment.md new file mode 100644 index 00000000..29c691eb --- /dev/null +++ b/docs/terminal-uat-deployment.md @@ -0,0 +1,47 @@ +# Terminal UAT deployment runbook + +Status: prepared, 2026-10-09. This runbook targets the physical Framework node +(`framework-pt`) and does not authorize an OTA, catalog publication, or wallet +mutation. + +## Candidate contents + +Build and record the backend binary, dashboard bundle, and source commit from +the isolated terminal worktree. The backend must have `ARCHY_SESSION_STATE_DIR` +set to the developer account's shared state directory (or use the default +resolution in `api/handler/terminal.rs`). Preserve the existing web root and +service binary before any replacement. + +## Preconditions + +1. Verify the Framework hostname and SSH host key through the operator's + approved connection mechanism. A plain `ssh framework-pt` must not be used + until host-key verification is available. +2. Capture service/container state, boot ID, running binary digest, served UI + digest, and the existing `/var/lib/archipelago/support` layout without + printing credentials, wallet files, or environment contents. +3. Create a timestamped protected rollback directory under + `/var/lib/archipelago/support/terminal-uat-`. + +## Acceptance flow + +- Open the dashboard as the node owner; unauthenticated requests to + `/api/terminal/sessions` and `/ws/terminal` return 401. +- Create a named session, type `printf 'uat\n'`, close the terminal, reopen it, + and resume the same session without a duplicate tmux process. +- Refresh the browser and reconnect after a temporary network interruption. +- Open a second owner browser and verify inventory visibility; verify only one + active attachment sends input at a time before enabling transfer controls. +- Confirm explicit End stops the tmux process but preserves the workspace. +- Reboot acceptance is separate: processes may stop, metadata must remain, and + the UI must call this interrupted rather than a live resume. +- Verify the Omarchy-derived agent skill files and app starter are present in + the candidate source/artifact; do not treat a local npm install failure as a + successful app build. + +## Rollback + +Stop exposing the new dashboard before restoring the previous UI/backend pair. +Restore only from the protected receipt, verify the previous hashes and health, +and leave terminal session metadata/workspaces untouched unless the operator +explicitly requests session cleanup. diff --git a/neode-ui/src/components/CLIPopup.vue b/neode-ui/src/components/CLIPopup.vue index b7e76ae9..039e7542 100644 --- a/neode-ui/src/components/CLIPopup.vue +++ b/neode-ui/src/components/CLIPopup.vue @@ -1,298 +1,118 @@ From 540f581927a85d21f79d59893bbbbc747b83a5ec Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 08:00:06 -0400 Subject: [PATCH 06/17] feat: add idempotent developer environment bootstrap --- scripts/archy-developer-setup | 64 +++++++++++++++++++++ scripts/tests/archy-developer-setup-test.sh | 7 +++ 2 files changed, 71 insertions(+) create mode 100755 scripts/archy-developer-setup create mode 100755 scripts/tests/archy-developer-setup-test.sh diff --git a/scripts/archy-developer-setup b/scripts/archy-developer-setup new file mode 100755 index 00000000..135c2046 --- /dev/null +++ b/scripts/archy-developer-setup @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Idempotent Archipelago developer environment bootstrap. +# Check by default; package installation requires --apply. +set -euo pipefail + +APPLY=0 +CODEX=0 +for arg in "$@"; do + case "$arg" in + --apply) APPLY=1 ;; + --codex) CODEX=1 ;; + --help|-h) printf 'Usage: archy-developer-setup [--check] [--apply] [--codex]\n'; exit 0 ;; + --check) ;; + *) printf 'archy-developer-setup: unknown option: %s\n' "$arg" >&2; exit 2 ;; + esac +done + +tools=(git tmux jq fzf rg fd nvim podman mise direnv) +missing=() +for tool in "${tools[@]}"; do + if command -v "$tool" >/dev/null 2>&1; then + printf 'ok\t%s\t%s\n' "$tool" "$(command -v "$tool")" + else + missing+=("$tool") + printf 'missing\t%s\n' "$tool" + fi +done + +if command -v codex >/dev/null 2>&1; then + printf 'ok\tcodex\t%s\n' "$(command -v codex)" +elif (( CODEX )); then + missing+=(codex) + printf 'missing\tcodex\n' +fi + +if (( ! APPLY )); then + if [ "${#missing[@]}" -gt 0 ]; then + printf '\nMissing: %s\n' "${missing[*]}" + printf 'Re-run with --apply to install missing packages, and --codex to install Codex.\n' + exit 1 + fi + exit 0 +fi + +if ! command -v apt-get >/dev/null 2>&1; then + printf 'No apt-get found; install the missing tools using the node distribution package manager.\n' >&2 + exit 1 +fi + +packages=(git tmux jq fzf ripgrep fd-find neovim podman direnv) +if [ "${#missing[@]}" -gt 0 ]; then + sudo -n apt-get update + sudo -n apt-get install -y "${packages[@]}" +fi + +if (( CODEX )) && ! command -v codex >/dev/null 2>&1; then + command -v npm >/dev/null 2>&1 || { printf 'npm is required for Codex installation.\n' >&2; exit 1; } + npm install --global @openai/codex +fi + +if command -v mise >/dev/null 2>&1; then + mise trust --yes "$PWD" >/dev/null 2>&1 || true +fi +printf 'developer setup complete; authenticate Codex interactively with: codex login\n' diff --git a/scripts/tests/archy-developer-setup-test.sh b/scripts/tests/archy-developer-setup-test.sh new file mode 100755 index 00000000..ba5020b2 --- /dev/null +++ b/scripts/tests/archy-developer-setup-test.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail +ROOT=$(cd "$(dirname "$0")/../.." && pwd) +output=$(PATH=/usr/bin:/bin "$ROOT/scripts/archy-developer-setup" --check 2>&1 || true) +grep -qE '^(ok|missing)[[:space:]]' <<<"$output" +! grep -q 'sudo -n apt-get' <<<"$output" +printf 'archy-developer-setup check is non-mutating\n' From d041f498cfc7e73f12bd4d7f22e6776e0ebdcf28 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 09:03:10 -0400 Subject: [PATCH 07/17] fix: satisfy terminal UI type checking --- neode-ui/src/components/CLIPopup.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neode-ui/src/components/CLIPopup.vue b/neode-ui/src/components/CLIPopup.vue index 039e7542..0a517fd1 100644 --- a/neode-ui/src/components/CLIPopup.vue +++ b/neode-ui/src/components/CLIPopup.vue @@ -91,7 +91,8 @@ function onKeydown(event: KeyboardEvent) { if (event.ctrlKey && event.key.toLowerCase() === 'c') { event.preventDefault(); send('\u0003'); return } if (event.ctrlKey && event.key.toLowerCase() === 'd') { event.preventDefault(); send('\u0004'); return } const special: Record = { Enter: '\n', Backspace: '\u007f', Tab: '\t', ArrowUp: '\u001b[A', ArrowDown: '\u001b[B', ArrowRight: '\u001b[C', ArrowLeft: '\u001b[D' } - if (special[event.key]) { event.preventDefault(); send(special[event.key]); return } + const specialKey = special[event.key] + if (specialKey !== undefined) { event.preventDefault(); send(specialKey); return } if (event.key.length === 1 && !event.metaKey && !event.altKey) { event.preventDefault(); send(event.key) } } function focusInput() { nextTick(() => inputRef.value?.focus()) } From 783d8bfd43073f874b6a6625fbc951597b529f66 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 09:55:32 -0400 Subject: [PATCH 08/17] feat: polish terminal controls and install codex by default --- neode-ui/src/components/CLIPopup.vue | 52 +++++++++++++++++++++------- scripts/archy-developer-setup | 12 ++++--- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/neode-ui/src/components/CLIPopup.vue b/neode-ui/src/components/CLIPopup.vue index 0a517fd1..5bfcca59 100644 --- a/neode-ui/src/components/CLIPopup.vue +++ b/neode-ui/src/components/CLIPopup.vue @@ -1,10 +1,9 @@ diff --git a/scripts/archy-developer-setup b/scripts/archy-developer-setup index 135c2046..88a6ecc1 100755 --- a/scripts/archy-developer-setup +++ b/scripts/archy-developer-setup @@ -4,12 +4,14 @@ set -euo pipefail APPLY=0 -CODEX=0 +# Codex is part of the standard developer environment. Keep --codex as a +# backwards-compatible no-op for existing setup commands. +CODEX=1 for arg in "$@"; do case "$arg" in --apply) APPLY=1 ;; --codex) CODEX=1 ;; - --help|-h) printf 'Usage: archy-developer-setup [--check] [--apply] [--codex]\n'; exit 0 ;; + --help|-h) printf 'Usage: archy-developer-setup [--check] [--apply] [--codex]\n\nCodex is installed by default with --apply; authenticate with: codex login\n'; exit 0 ;; --check) ;; *) printf 'archy-developer-setup: unknown option: %s\n' "$arg" >&2; exit 2 ;; esac @@ -36,7 +38,7 @@ fi if (( ! APPLY )); then if [ "${#missing[@]}" -gt 0 ]; then printf '\nMissing: %s\n' "${missing[*]}" - printf 'Re-run with --apply to install missing packages, and --codex to install Codex.\n' + printf 'Re-run with --apply to install missing packages, including Codex.\n' exit 1 fi exit 0 @@ -47,7 +49,7 @@ if ! command -v apt-get >/dev/null 2>&1; then exit 1 fi -packages=(git tmux jq fzf ripgrep fd-find neovim podman direnv) +packages=(git tmux jq fzf ripgrep fd-find neovim podman direnv nodejs npm) if [ "${#missing[@]}" -gt 0 ]; then sudo -n apt-get update sudo -n apt-get install -y "${packages[@]}" @@ -55,7 +57,7 @@ fi if (( CODEX )) && ! command -v codex >/dev/null 2>&1; then command -v npm >/dev/null 2>&1 || { printf 'npm is required for Codex installation.\n' >&2; exit 1; } - npm install --global @openai/codex + sudo -n npm install --global @openai/codex fi if command -v mise >/dev/null 2>&1; then From 46b38f98009cec9a3fa398ab34975cbf13a103fc Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 09:58:17 -0400 Subject: [PATCH 09/17] docs: capture terminal controls and proxy UAT checks --- docs/terminal-uat-deployment.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/terminal-uat-deployment.md b/docs/terminal-uat-deployment.md index 29c691eb..35524693 100644 --- a/docs/terminal-uat-deployment.md +++ b/docs/terminal-uat-deployment.md @@ -30,6 +30,9 @@ service binary before any replacement. - Create a named session, type `printf 'uat\n'`, close the terminal, reopen it, and resume the same session without a duplicate tmux process. - Refresh the browser and reconnect after a temporary network interruption. +- Verify the terminal panel stays bounded while output grows, keeps an inner + scroll position at the newest line, and supports drag, resize, minimize, + refresh, and fullscreen controls without blocking dashboard navigation. - Open a second owner browser and verify inventory visibility; verify only one active attachment sends input at a time before enabling transfer controls. - Confirm explicit End stops the tmux process but preserves the workspace. @@ -39,6 +42,11 @@ service binary before any replacement. the candidate source/artifact; do not treat a local npm install failure as a successful app build. +The node's reverse proxy must route `/api/terminal/` to the Archipelago daemon +(`127.0.0.1:5678`) in both HTTP and HTTPS server blocks. `/ws` already carries +the terminal WebSocket upgrade. Without the REST location, the SPA fallback +returns `index.html` instead of the authenticated session response. + ## Rollback Stop exposing the new dashboard before restoring the previous UI/backend pair. From 85fe925763dab11c40afcd8dd0faade4d8450a31 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 9 Oct 2026 10:17:33 -0400 Subject: [PATCH 10/17] feat: use xterm for terminal UI and match app controls --- neode-ui/package-lock.json | 17 ++++ neode-ui/package.json | 4 +- neode-ui/src/components/CLIPopup.vue | 121 ++++++++++++++++++--------- 3 files changed, 102 insertions(+), 40 deletions(-) diff --git a/neode-ui/package-lock.json b/neode-ui/package-lock.json index 27ada981..b23faad2 100644 --- a/neode-ui/package-lock.json +++ b/neode-ui/package-lock.json @@ -11,6 +11,8 @@ "@scure/bip39": "^2.2.0", "@types/dompurify": "^3.0.5", "@vue-leaflet/vue-leaflet": "^0.10.1", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "buffer": "^6.0.3", "d3": "^7.9.0", "dompurify": "^3.3.3", @@ -4417,6 +4419,21 @@ } } }, + "node_modules/@xterm/addon-fit": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", + "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT" + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", diff --git a/neode-ui/package.json b/neode-ui/package.json index 4d5d86ba..c3be9af0 100644 --- a/neode-ui/package.json +++ b/neode-ui/package.json @@ -10,7 +10,7 @@ "test:watch": "vitest", "test:mock-parity": "node scripts/mock-rpc-parity.mjs", "dev": "vite", - "dev:mock": "concurrently --raw \"node mock-backend.js\" \"VITE_AIUI_URL=http://localhost:5173 vite\" \"cd ../../AIUI && perl -MPOSIX -e 'POSIX::setsid(); exec @ARGV' -- pnpm dev 2>/dev/null || echo '[AIUI] Not found at ../../AIUI \u2014 chat will show placeholder'\"", + "dev:mock": "concurrently --raw \"node mock-backend.js\" \"VITE_AIUI_URL=http://localhost:5173 vite\" \"cd ../../AIUI && perl -MPOSIX -e 'POSIX::setsid(); exec @ARGV' -- pnpm dev 2>/dev/null || echo '[AIUI] Not found at ../../AIUI — chat will show placeholder'\"", "dev:boot": "VITE_DEV_MODE=boot concurrently --raw \"VITE_DEV_MODE=boot node mock-backend.js\" \"VITE_DEV_MODE=boot vite\"", "dev:real": "echo 'Start backend: cd ../core && cargo run --release' && vite", "backend:mock": "node mock-backend.js", @@ -28,6 +28,8 @@ "@scure/bip39": "^2.2.0", "@types/dompurify": "^3.0.5", "@vue-leaflet/vue-leaflet": "^0.10.1", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "buffer": "^6.0.3", "d3": "^7.9.0", "dompurify": "^3.3.3", diff --git a/neode-ui/src/components/CLIPopup.vue b/neode-ui/src/components/CLIPopup.vue index 5bfcca59..8d69fb20 100644 --- a/neode-ui/src/components/CLIPopup.vue +++ b/neode-ui/src/components/CLIPopup.vue @@ -1,31 +1,34 @@