diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..4924a53 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,18 @@ +name: Build + +on: + workflow_dispatch: + pull_request: + paths-ignore: ['*.md'] + branches: ['main'] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.event.pull_request.draft == false + uses: Start9Labs/start-technologies/.github/workflows/build.yml@master + secrets: + DEV_KEY: ${{ secrets.DEV_KEY }} diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index 893f485..c601b98 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -28,6 +28,11 @@ on: push: tags: - "v*" + # StartOS packaging tags carry the packaging revision as `_` + # (v5.7.0_0) and are pushed by tagAndRelease.yml, not by release.sh. + # They are not desktop releases, and the gates job below would reject + # them anyway for not matching package.json's version. + - "!v*_*" workflow_dispatch: permissions: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a4041c6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Release + +on: + push: + tags: + # StartOS release tags carry the packaging revision as `_` (v5.7.0_0), + # written by tagAndRelease. The app's own desktop lane tags plain `v5.7.0`, + # which `v*.*` would also match — so this filter names the underscore. + - 'v*_*' + +jobs: + release: + uses: Start9Labs/start-technologies/.github/workflows/release.yml@master + with: + RELEASE_REGISTRY: ${{ vars.RELEASE_REGISTRY }} + S3_S9PKS_BASE_URL: ${{ vars.S3_S9PKS_BASE_URL }} + secrets: + DEV_KEY: ${{ secrets.DEV_KEY }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + permissions: + contents: write diff --git a/.github/workflows/tagAndRelease.yml b/.github/workflows/tagAndRelease.yml new file mode 100644 index 0000000..c4031d3 --- /dev/null +++ b/.github/workflows/tagAndRelease.yml @@ -0,0 +1,24 @@ +name: Tag and Release + +on: + push: + branches: ['main'] + paths-ignore: ['*.md'] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + tag: + uses: Start9Labs/start-technologies/.github/workflows/tagAndRelease.yml@master + with: + REFERENCE_REGISTRY: ${{ vars.REFERENCE_REGISTRY }} + RELEASE_REGISTRY: ${{ vars.RELEASE_REGISTRY }} + S3_S9PKS_BASE_URL: ${{ vars.S3_S9PKS_BASE_URL }} + secrets: + DEV_KEY: ${{ secrets.DEV_KEY }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + permissions: + contents: write diff --git a/.gitignore b/.gitignore index 6e4dd52..980e4d6 100644 --- a/.gitignore +++ b/.gitignore @@ -67,7 +67,8 @@ chama-pulse-log.md # (restored 2026-07-13 after the "clean public repository" cleanup removed them; # PHILOSOPHY/DECISIONS/INVARIANTS/BACKLOG restored 2026-07-25 from 7e4162c). # Keep on disk for Claude/CC continuity; push only if explicitly needed. -CLAUDE.md +# CLAUDE.md is the exception: as a StartOS package repo this tree ships the +# tracked one-line `@AGENTS.md` import every package carries. Codex.md PHILOSOPHY.md DECISIONS.md @@ -77,3 +78,6 @@ design/mockups/ # Local StartOS package artifacts (rebuild; do not commit) *.s9pk +startos/*.js +docker-images +ncc-cache diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bfbec35 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,68 @@ +# AGENTS.md + +This repository is **both** the Chama application source and a StartOS service-package +repository — it builds a `.s9pk` for StartOS out of the very source it ships. Everything +under `startos/` plus `Dockerfile`, `Makefile`, `icon.png`, `LICENSE`, `instructions.md` +and `.github/workflows/{build,release,tagAndRelease}.yml` is the packaging layer; +everything else is the application. + +Develop the packaging layer inside a StartOS packaging workspace created by +`start-cli s9pk init-workspace`, which provides the packaging guide and agent context one +level up. If you're reading this in a bare clone with no workspace, the full guide is at +. + +**Start every packaging task at the recipe index** — +`../start-technologies/projects/start-sdk/docs/src/recipes.md` (or +). It maps an intent to the constructs, +the reference pages, and a named production package to copy. Find the recipe before you +read a neighbouring package: one you reach by grepping may be non-conformant, and the +recipe outranks it. + +Work `TODO.md` from top to bottom. Keep `README.md` (architecture, for developers and +LLMs) and `instructions.md` (end-user docs) in sync with your changes. + +## This repo + +- **Package id is `chama`.** One image built from this repo's own `Dockerfile`, one `main` + volume, no dependencies, no actions. The image serves three independent Chama web + origins (ports 8080/8081/8082), each proxying `/bridge/` to its own + `chama-fedimint-bridge` process on loopback (8787/8788/8789) with its own wallet + directory under `/data/client-`. +- **`packageRepo` is this fork** (`Start9-Community/chama`); `upstreamRepo` is the + application's home (`jesuspirate/chama`). Packaging changes land here. + +## Packaging gotchas specific to this repo + +- **Use the `startos:*` npm scripts, never `check` / `build`.** `npm run build` is the + Vite web build the `Dockerfile` calls; `npm run typecheck` is the app's. The StartOS + bundle is `startos:check` → `startos:lint` → `startos:build`. `Makefile` overrides + `s9pk.mk`'s stock `javascript/index.js` recipe for exactly this reason — make prints a + "overriding recipe" warning on every run, which is expected. +- **The StartOS tsconfig is `startos/tsconfig.json`, not the root one.** The root + `tsconfig.json` belongs to the React app and includes only `src`. Keeping the packaging + tsconfig inside `startos/` is also what lets the SDK's ESLint runner (`projectService`) + resolve a project for `startos/**/*.ts`. +- **`javascript/index.js` is emitted as ESM, not CJS.** `ncc` follows the root + `package.json`'s `"type": "module"`, and writes a matching `javascript/package.json`. + StartOS's container runtime `require()`s the bundle, which works because Node ≥ 20.19 + supports `require(esm)`. Do not "fix" this by dropping `"type": "module"` — the app + needs it — and do not add a `startos/package.json` to force CJS: webpack then emits an + empty export table and the package loads with no `manifest`/`main`/`init`. +- **`icon.png` is a 512×512 downscale of `src-tauri/icons/icon.png`.** The 1024×1024 + original is ~713 KB, and a package icon is embedded as a base64 data URL in every + registry index. Regenerate with: + `convert src-tauri/icons/icon.png -filter Lanczos -resize 512x512 -strip -quality 95 icon.png` +- **Two release lanes share this repo's tags.** `scripts/release.sh` tags `vX.Y.Z` for the + desktop/Zapstore lane; StartOS's `tagAndRelease.yml` tags `vX.Y.Z_`. The tag + filters in `release.yml` and `desktop-release.yml` keep them apart — keep them in sync + if either changes. +- **The Docker build context is the whole repo**, filtered by `.dockerignore`. Anything + the web build or the Rust bridge needs must stay out of that ignore list. + +## Inspecting a running install + +To run a command inside the service's container (check a bridge, read nginx logs), use +`start-cli package attach chama -n chama-sub -- `. Select the subcontainer by **name** +with `-n` (the name passed to `SubContainer.of` in `startos/main.ts` — here `chama-sub`) or +by image with `-i`. Note: `-s/--subcontainer` matches the internal **Guid**, not the name, +so passing a name to `-s` fails with "no matching subcontainers". diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/Makefile b/Makefile index 1844472..a90c0b1 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,11 @@ ARCHES := x86 arm +# overrides to s9pk.mk must precede the include statement +include node_modules/@start9labs/start-sdk/s9pk.mk -# Published npm builds of the SDK (e.g. 1.5.3) do not always include s9pk.mk. -# Prefer the package-local file; else a common Start9 SDK checkout; else fail loud. -# Override anytime: -# make START_SDK_MK=/path/to/start-sdk/s9pk.mk x86 -START_SDK_MK ?= node_modules/@start9labs/start-sdk/s9pk.mk -ifeq ($(wildcard $(START_SDK_MK)),) - ifneq ($(wildcard $(HOME)/start9-workspace/start-technologies/projects/start-sdk/s9pk.mk),) - START_SDK_MK := $(HOME)/start9-workspace/start-technologies/projects/start-sdk/s9pk.mk - endif -endif -ifeq ($(wildcard $(START_SDK_MK)),) -$(error s9pk.mk missing at $(START_SDK_MK). Use an SDK that ships s9pk.mk (≥2.0.6), or: make START_SDK_MK=/path/to/start-sdk/s9pk.mk x86) -endif -include $(START_SDK_MK) - -# StartOS packages discover their catalog artwork from a root-level `icon.*`. -# Always derive it from the same high-resolution canonical mark shipped by -# Tauri so package builds cannot drift to a placeholder or approximation. -icon.png: src-tauri/icons/icon.png - cp $< $@ - -ingredients: icon.png - -# Chama's existing `build` script builds the web application. Override the SDK's -# conventional package target so the StartOS runtime bundle uses its own scripts. -javascript/index.js: $(shell find startos -type f) tsconfig.startos.json node_modules +# The SDK's stock recipe runs `npm run check` / `npm run build`; in this repo +# those script names belong to the Chama web app, so the StartOS bundle is +# driven by the `startos:*` scripts. A recipe override must follow the include. +javascript/index.js: $(shell find startos -type f) node_modules npm run startos:check + npm run startos:lint npm run startos:build diff --git a/README.md b/README.md index 4b163cf..c8db841 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,207 @@ -# Chama +

+ Chama Logo +

+ +# Chama on StartOS + +> **Upstream project and docs:** +> +> Everything not listed in this document should behave the same as upstream +> Chama. If a feature, setting, or behavior is not mentioned here, the upstream +> documentation is accurate and fully applicable. + +Chama is a Nostr-native peer-to-peer marketplace with non-custodial Fedimint +ecash escrow. There is no Chama account server and no custody layer: clients +coordinate encrypted trade events over Nostr and talk directly to a Fedimint +federation the user picks. This package serves the Chama web client from your +own server, so the code you load is the code you host. + +This repository is both the upstream application source and the StartOS package +that wraps it — the packaging layer is `startos/`, `Dockerfile`, `Makefile`, +`icon.png`, `LICENSE`, `instructions.md`, and the `build` / `release` / +`tagAndRelease` workflows. + +--- + +## Table of Contents + +- [Image and Container Runtime](#image-and-container-runtime) +- [Volume and Data Layout](#volume-and-data-layout) +- [Installation and First-Run Flow](#installation-and-first-run-flow) +- [Configuration Management](#configuration-management) +- [Network Access and Interfaces](#network-access-and-interfaces) +- [Actions](#actions) +- [Backups and Restore](#backups-and-restore) +- [Health Checks](#health-checks) +- [Dependencies](#dependencies) +- [Limitations and Differences](#limitations-and-differences) +- [What Is Unchanged from Upstream](#what-is-unchanged-from-upstream) +- [Contributing](#contributing) +- [Quick Reference for AI Consumers](#quick-reference-for-ai-consumers) + +--- + +## Image and Container Runtime + +| What | Detail | +| ------------- | ------------------------------------------------------------------------- | +| Image source | Built from this repository's own `Dockerfile` — there is no published tag | +| Architectures | `x86_64`, `aarch64` | +| Entrypoint | Custom — `startos/entrypoint.sh`, installed as `chama-startos-entrypoint` | + +The build has three stages: a Rust stage compiles the `chama-fedimint-bridge` +binary from `native/fedimint-bridge/`, a Node stage runs the app's own web build +with the native bridge pinned on (`VITE_CHAMA_NATIVE_BRIDGE_REQUIRED`, +`VITE_CHAMA_NATIVE_BRIDGE_URL=/bridge`), and an nginx stage assembles the two +with `startos/nginx.conf`. -[![CI](https://github.com/jesuspirate/chama/actions/workflows/ci.yml/badge.svg)](https://github.com/jesuspirate/chama/actions/workflows/ci.yml) - -Chama is a Nostr-native peer-to-peer marketplace with non-custodial Fedimint ecash escrow. There is no central Chama account server or custody layer: clients coordinate encrypted trade events over Nostr and interact directly with a selected Fedimint federation. - -## Run locally - -Requirements: Node.js 22+, npm, and a NIP-07 signer extension such as Alby or nos2x. - -```bash -npm install -npm run dev -``` - -The development server runs at `http://localhost:3000`. - -### StartOS package (lab — 3 clients) - -This package is a **local lab**: three co-located Chama UIs (ports 8080–8082), each with its own native Fedimint bridge under `/data/client-*`. It is **not** the friend-wallet / remote-bridge host. - -Requirements: Docker, `start-cli`, `jq`, Node 22+, and a packaging workspace (this repo’s `.startos` link). The npm `@start9labs/start-sdk@1.5.3` pin may not ship `s9pk.mk`; the Makefile falls back to `$HOME/start9-workspace/.../s9pk.mk` or accepts: - -```bash -make START_SDK_MK=/path/to/start-sdk/s9pk.mk x86 # or: make arm -npm run startos:check # package TypeScript only -make install # sideload on a StartOS box matching the arch -make publish # personal registry -``` - -Package icon must be `icon.svg` or `icon.png` ≤ 40 KiB (StartOS marketplace limit). - -Before opening a pull request: - -```bash -npm run predeploy -npm run build +The entrypoint starts one `chama-fedimint-bridge` per client on loopback, then +nginx in the foreground, and supervises all four. If any child dies — or is left +as an unreaped zombie — it exits non-zero so StartOS restarts the whole service +rather than leaving a healthy-looking page whose wallet calls all fail. + +## Volume and Data Layout + +One volume, `main`, mounted at `/data`: + +| Path | Contents | +| ---------------- | --------------------------------------------- | +| `/data/client-1` | Fedimint client database for **Client One** | +| `/data/client-2` | Fedimint client database for **Client Two** | +| `/data/client-3` | Fedimint client database for **Client Three** | + +Each directory is created by the entrypoint on first start and is used +exclusively by that client's bridge process. There is no shared database, no +StartOS `store.json`, and no generated config file — the package writes nothing +else to the volume. + +Everything else Chama holds — the Nostr identity, trade chains, drafts, and +local caches — lives in **browser storage**, keyed to the origin the client was +loaded from. It is not on this volume. + +## Installation and First-Run Flow + +There is no setup wizard, no generated credential, and no configuration step. +After install each of the three interfaces serves an empty Chama client. A +client becomes usable once the user, inside it, chooses or imports a Nostr +identity and joins a Fedimint federation with an invite code. Both are in-app +decisions the package neither makes nor stores. + +## Configuration Management + +| StartOS-Managed | Upstream-Managed | +| ------------------------------------------------ | -------------------------------------------------------------------- | +| Which addresses each client interface answers on | Nostr identity, relays, federation, arbiter roster, every app option | + +The package sets no environment variables at runtime. The two `VITE_*` values +above are build-time constants baked into the web bundle; they cannot be changed +without rebuilding the image. + +## Network Access and Interfaces + +Three `ui` interfaces, one per client, each on its own host so the browser gives +each its own origin — which is what keeps their identities, storage, and wallets +separate: + +| Interface id | Name | Internal port | Protocol | +| -------------- | ------------ | ------------- | -------- | +| `client-one` | Client One | 8080 | HTTP | +| `client-two` | Client Two | 8081 | HTTP | +| `client-three` | Client Three | 8082 | HTTP | + +Each client's nginx server block proxies `/bridge/` to that client's own +`chama-fedimint-bridge` on loopback (8787, 8788, 8789 respectively). The bridges +bind loopback only and are **not** exported as interfaces; they are reachable +solely through their own client's `/bridge/` path. That location carries a +one-hour proxy read timeout because `/await-invoice` is a long poll held open +until a human actually pays. + +Where these interfaces are reachable from is the user's decision, made per +address on the service's Interfaces tab. + +## Actions + +| Action | Id | Visibility | Availability | Input | Output | +| ------------------------ | --------------- | ---------- | ------------ | ----- | ----------------------------------------- | +| **Wallet Bridge Status** | `wallet-status` | Enabled | Only running | None | Per-client federation and discovery state | + +Queries each client's bridge and reports whether it answered, whether that +client has joined a federation, and the state of its relay-discovery probe +(reachable / degraded / still probing / not configured). Read-only. + +## Backups and Restore + +`Backups.ofVolumes('main')` — the three Fedimint client databases. StartOS stops +the service before running the backup, so the databases are quiesced, and +`restoreInit` puts them back on restore. + +**Not backed up:** all browser-side state — Nostr keys, trade chains, drafts — +because it never leaves the browser profile that loaded the client. Anything the +user needs to survive a lost browser has to be exported through Chama's own +in-app recovery material. + +## Health Checks + +One daemon health check, displayed as **Web Clients**, with a 30-second grace +period. For each client it requires both the web port and that client's wallet +bridge port to be listening; a client whose page is up but whose bridge is not +would serve the UI and then fail every `/bridge/` call, so it is not reported +ready. + +## Dependencies + +None. + +## Limitations and Differences + +1. **Neither the clients nor the bridges authenticate anyone.** The bridge + refuses to serve a non-loopback bind without a token, but here it is reached + through nginx on the same origin as the page — so anyone who can load a + client interface can spend that client's ecash. Enable only addresses you + trust, and treat each interface URL as a credential. +2. **The client count is fixed at three** and is not configurable. It is a + property of the image: three nginx server blocks, three bridge processes, + three data directories. +3. **Browser state is not backed up and not portable.** Clearing site data, + switching browsers, or opening a client in a private window yields a fresh + local client and can lose access to browser-only state. +4. **Web client only.** The desktop (Tauri) and Android (Capacitor) shells that + also live in this repository are not built or shipped by this package. +5. **No `riscv64` build.** The Rust bridge and the base images have not been + confirmed to build there. + +## What Is Unchanged from Upstream + +The Chama application itself: the escrow state machine and its Nostr event +kinds, encrypted trade coordination, relay discovery and replay, Fedimint ecash +and Lightning operations, on-chain commitment bonds and their chain +verification, arbiter rosters and selection, and the entire user interface. All +of it behaves exactly as upstream documents — including that relay and +federation selection remain in-app choices. + +## Contributing + +See [AGENTS.md](./AGENTS.md). + +--- + +## Quick Reference for AI Consumers + +```yaml +package_id: chama +architectures: [x86_64, aarch64] +volumes: + main: /data +ports: + client-one: 8080 + client-two: 8081 + client-three: 8082 +internal_only_ports: + client-one-bridge: 8787 + client-two-bridge: 8788 + client-three-bridge: 8789 +dependencies: none +startos_managed_env_vars: [] +actions: + - wallet-status ``` - -`predeploy` checks repository hygiene, TypeScript, and the escrow-engine test suite. - -## Architecture - -| Area | Responsibility | -| --- | --- | -| `src/escrow-engine/` | Deterministic escrow state machine, event validation, encrypted Nostr coordination, relay discovery, and replay | -| `src/fedimint/` | Federation selection, ecash operations, recovery, and browser/native bridge adapters | -| `src/bond-multisig/` | On-chain single-key CLTV commitment bonds and chain verification | -| `src/arbiters/` | Arbiter rosters, bonded-arbiter selection, exposure, premiums, and earnings | -| `src/ui/` | React application and platform-neutral product UI | -| `native/fedimint-bridge/` | Rust Fedimint client used by native platforms | -| `android/` | Capacitor Android application project and native bridge packaging | -| `src-tauri/` | Tauri desktop shell and sidecar configuration | -| `public/` | Production web assets copied into web, Android, and desktop builds | - -The core trade chain uses Nostr kinds `38100`–`38113` (with retired kinds reserved). Governance, roster, and chain-verifiable bond announcements use separate kinds. Sensitive trade content and ecash material are encrypted for participants. - -### Platform builds - -```bash -npm run android:sync # build web assets and sync the Android project -npm run tauri:build # build the desktop application -``` - -Android builds require the Android SDK/NDK; see [docs/ANDROID_BUILD.md](./docs/ANDROID_BUILD.md). Desktop builds require Rust and the platform dependencies expected by Tauri. The native bridge build scripts under `scripts/` are invoked by these platform builds. - -## Repository boundaries - -This repository contains product source, tests, platform projects, public assets, and reproducible build/release automation. Draft designs, generated media, migration records, private infrastructure notes, agent memory, and social-content logs do not belong here. Keep temporary work under the ignored `outputs/` or `tmp/` directories. - -The Zapstore screenshots are retained because they are referenced by `zapstore.yaml` and are part of the public Android store listing. GitHub workflows are retained because they run CI and build desktop release artifacts. - -For a visual technical introduction, see [chama-technical-overview.pdf](./chama-technical-overview.pdf). Relay operators can consult [docs/RELAY_OPERATIONS.md](./docs/RELAY_OPERATIONS.md). - -## License - -[MIT](./LICENSE) diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..a0f7a26 --- /dev/null +++ b/TODO.md @@ -0,0 +1,10 @@ +# TODO + +- [ ] The three client origins have no authentication of their own: anyone who can load + one of the interfaces can spend that client's ecash, because nginx proxies + `/bridge/` to a loopback bridge that skips its own auth-token requirement for + loopback binds. Documented as a limitation for now. A real fix needs the + application to gate the UI (or the bridge to require a token the UI holds), not a + packaging workaround. +- [ ] `riscv64` is not built. The Rust bridge and the base images would need to be + confirmed to build there before adding it to `arch` and `ARCHES`. diff --git a/UPDATING.md b/UPDATING.md new file mode 100644 index 0000000..b004ace --- /dev/null +++ b/UPDATING.md @@ -0,0 +1,32 @@ +# Updating the upstream version + +Chama is packaged from **this repository's own source** — the `Dockerfile` builds the web +client and the Rust Fedimint bridge from the tree it is checked out in. There is no +external image tag or submodule to move, so "upstream" here means the application version +this repo is at. + +## Determining the upstream version + +The application version is `version` in the root `package.json`, bumped by +`scripts/release.sh` and mirrored into the Tauri bundle via `src-tauri/tauri.conf.json`. + +```sh +node -p "require('./package.json').version" +``` + +The packaged version lives in `startos/versions/current.ts` as `:`. +The two are in sync when the upstream half of that string equals the value above. + +## Applying the bump + +1. Set `version` in `startos/versions/current.ts` to `:0`. +2. Rewrite `releaseNotes` in that file for all five locales (`en_US`, `es_ES`, `de_DE`, + `pl_PL`, `fr_FR`), describing what the release changes **for someone running it on + StartOS** — not the full app changelog. +3. If only the packaging changed and the application version did not, leave the upstream + half alone and increment the revision instead (`5.7.0:0` → `5.7.0:1`). + +A migration is only needed when the on-disk layout under `/data` changes — the bridge's +per-client wallet directories (`/data/client-1` … `/data/client-3`). A plain application +or packaging bump keeps `migrations.up` empty and stays in `current.ts`; do not spin off a +version file for it. diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/ABOUT.md b/assets/ABOUT.md deleted file mode 100644 index 9020afd..0000000 --- a/assets/ABOUT.md +++ /dev/null @@ -1,3 +0,0 @@ -# Chama on StartOS - -This package builds Chama directly from the source in this repository and serves three isolated browser origins for multi-party testing. diff --git a/icon.png b/icon.png index 368c7bb..9b7bd1f 100644 Binary files a/icon.png and b/icon.png differ diff --git a/instructions.md b/instructions.md index a9ac571..364185d 100644 --- a/instructions.md +++ b/instructions.md @@ -1,17 +1,57 @@ # Chama -## Open isolated test clients +## Documentation -The service exposes **Client One**, **Client Two**, and **Client Three** on its dashboard. Open each interface in a separate browser tab to act as different marketplace participants. Each interface has a distinct origin and a dedicated native Fedimint wallet, so Chama keeps its identity, wallet, and local state separate from the other two clients. +- [Chama technical overview](https://github.com/jesuspirate/chama/blob/main/chama-technical-overview.pdf) — how the escrow state machine, the Nostr event kinds, and Fedimint settlement fit together. +- [Relay operations](https://github.com/jesuspirate/chama/blob/main/docs/RELAY_OPERATIONS.md) — for anyone running a Nostr relay that carries Chama traffic. -Use different Nostr identities for buyer, seller, and arbiter scenarios. All three clients use the relays and Fedimint federation selected inside Chama; the StartOS package itself has no account server, database, or custody role. +## What you get on StartOS -## Data and backups +Three separate Chama clients — **Client One**, **Client Two**, and **Client +Three** — each with its own address. They are fully independent: a Nostr +identity, browser storage, and a Fedimint ecash wallet per client, with nothing +shared between them. Use one for yourself, or use several to hold separate +trading identities, or to play buyer, seller, and arbiter through a trade +end to end. -Chama stores identity and trade state in each browser profile, while each native Fedimint wallet is stored in the StartOS service volume. StartOS backups cover the native wallet volume but do not include browser storage. Back up any secrets or recovery material shown by Chama using the in-app guidance. +The Fedimint wallet of each client is the one part that lives on your server +rather than in your browser, so it is what StartOS backs up. -Clearing site data, changing browsers, or opening an interface in a private window creates a fresh local client and can remove access to browser-only state. +## Getting set up -## Learn more +Do this once per client you intend to use: -- [Chama source and documentation](https://github.com/jesuspirate/chama) +1. Open the client from the **Interfaces** tab and let the page load. +2. Choose or import a Nostr identity when Chama asks for one. This is the + identity your counterparties will see, so use a different one in each client + if you want the clients to be unrelated. +3. Join a Fedimint federation with an invite code. Until you do, the client can + browse but cannot hold ecash or fund an escrow. +4. Save whatever recovery material Chama shows you, somewhere off this server. + +## Using Chama + +### Web interfaces + +Each client opens on the Chama marketplace: browse offers, publish your own, +negotiate, and settle. Bookmark the address of the client you use — the identity +and history you built up belong to that address, and opening a different client +gets you a different, empty one. + +### Actions + +**Wallet Bridge Status** reports, for each client, whether its wallet is +answering, whether it has joined a federation yet, and whether Chama can reach +the relay-discovery service it uses to find federation nodes. Run it when a +client will not load its balance or a payment seems stuck. + +## Limitations + +- **A Chama client has no login.** Anyone who can open one of these addresses + can spend that client's ecash. Enable only the addresses you trust, and treat + each one like a password. +- **Backups cover the wallets, not your history.** Your identity, trades, and + drafts live in the browser you opened the client with. Clearing site data, + switching browsers, or using a private window gives you a fresh empty client + and can lose access to the old one — export your recovery material instead of + relying on the backup. diff --git a/package-lock.json b/package-lock.json index 7be14cf..c8cec8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "shamir-secret-sharing": "^0.0.4" }, "devDependencies": { - "@start9labs/start-sdk": "1.5.3", + "@start9labs/start-sdk": "2.0.9", "@tauri-apps/cli": "^2.11.2", "@types/node": "^22.0.0", "@types/react": "^18.3.0", @@ -1677,26 +1677,301 @@ } }, "node_modules/@start9labs/start-sdk": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-1.5.3.tgz", - "integrity": "sha512-OyHe9J6hMvyA5ZavcLkxdVQvZcuTH9J9kagV6NDI83eAG/YpJFIq62gP/n/2PPNdHWwNSXVQmSwnsvsV8Gyg+A==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@start9labs/start-sdk/-/start-sdk-2.0.9.tgz", + "integrity": "sha512-HuSYrS10Bb+f6OyAMlCzd5Qyr+rIauiI0dF8MVh5Ny+eU0gF8/DeFAXlHFgL+9wkSv4UAP5q5yOzlBpXzsNbWQ==", + "bundleDependencies": [ + "@start9labs/start-core", + "eslint", + "typescript-eslint" + ], "dev": true, "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0", + "@start9labs/start-core": "file:./node_modules/@start9labs/start-core", "@types/ini": "^4.1.1", "deep-equality-data-structures": "^2.0.0", + "eslint": "^9.39.4", "fast-xml-parser": "~5.7.0", "ini": "^5.0.0", "isomorphic-fetch": "^3.0.0", "mime": "^4.1.0", + "typescript-eslint": "^8.61.0", "yaml": "^2.8.3", - "zod": "4.3.6", + "zod": "4.4.3", "zod-deep-partial": "^1.2.0" } }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/config-array": { + "version": "0.21.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.15", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/core": { + "version": "0.17.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "Python-2.0" + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.15", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "inBundle": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/js": { + "version": "9.39.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/object-schema": { + "version": "2.1.7", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanfs/core": { + "version": "0.19.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanfs/node": { + "version": "0.16.8", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanfs/types": { + "version": "0.15.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@start9labs/start-sdk/node_modules/@iarna/toml": { "version": "3.0.0", "dev": true, @@ -1727,31 +2002,81 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@start9labs/start-sdk/node_modules/ini": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", - "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core": { "dev": true, - "license": "ISC", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@iarna/toml": "^3.0.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0", + "deep-equality-data-structures": "^2.0.0", + "isomorphic-fetch": "^3.0.0", + "mime": "^4.1.0", + "yaml": "^2.8.3", + "zod": "4.4.3", + "zod-deep-partial": "^1.4.4" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/@iarna/toml": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/@noble/curves": { + "version": "1.9.7", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@start9labs/start-sdk/node_modules/isomorphic-fetch": { + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/@noble/hashes": { + "version": "1.8.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/deep-equality-data-structures": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "object-hash": "^3.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/isomorphic-fetch": { "version": "3.0.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { "node-fetch": "^2.6.1", "whatwg-fetch": "^3.4.1" } }, - "node_modules/@start9labs/start-sdk/node_modules/mime": { + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/mime": { "version": "4.1.0", "dev": true, "funding": [ "https://github.com/sponsors/broofa" ], + "inBundle": true, "license": "MIT", "bin": { "mime": "bin/cli.js" @@ -1760,9 +2085,10 @@ "node": ">=16" } }, - "node_modules/@start9labs/start-sdk/node_modules/node-fetch": { + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/node-fetch": { "version": "2.7.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -1779,33 +2105,47 @@ } } }, - "node_modules/@start9labs/start-sdk/node_modules/tr46": { + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/object-hash": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/tr46": { "version": "0.0.3", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/@start9labs/start-sdk/node_modules/webidl-conversions": { + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/webidl-conversions": { "version": "3.0.1", "dev": true, + "inBundle": true, "license": "BSD-2-Clause" }, - "node_modules/@start9labs/start-sdk/node_modules/whatwg-fetch": { + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/whatwg-fetch": { "version": "3.6.20", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/@start9labs/start-sdk/node_modules/whatwg-url": { + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/whatwg-url": { "version": "5.0.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, - "node_modules/@start9labs/start-sdk/node_modules/yaml": { + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/yaml": { "version": "2.9.0", "dev": true, + "inBundle": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -1817,10 +2157,1251 @@ "url": "https://github.com/sponsors/eemeli" } }, - "node_modules/@start9labs/start-sdk/node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/zod": { + "version": "4.4.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@start9labs/start-core/node_modules/zod-deep-partial": { + "version": "1.4.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "peerDependencies": { + "zod": "^4.1.13" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@types/estree": { + "version": "1.0.9", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/type-utils": "8.61.0", + "@typescript-eslint/utils": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.61.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/parser": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/project-service": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.61.0", + "@typescript-eslint/types": "^8.61.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/scope-manager": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/type-utils": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/types": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.61.0", + "@typescript-eslint/tsconfig-utils": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/utils": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/acorn": { + "version": "8.16.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/ajv": { + "version": "6.15.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@start9labs/start-sdk/node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint": { + "version": "9.39.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint-scope": { + "version": "8.4.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.15", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/espree": { + "version": "10.4.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/esquery": { + "version": "1.7.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/@start9labs/start-sdk/node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/flatted": { + "version": "3.4.2", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/@start9labs/start-sdk/node_modules/globals": { + "version": "14.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/@start9labs/start-sdk/node_modules/isomorphic-fetch": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/levn": { + "version": "0.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/mime": { + "version": "4.1.0", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/node-fetch": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@start9labs/start-sdk/node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/tinyglobby": { + "version": "0.2.17", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/tr46": { + "version": "0.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/ts-api-utils": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/typescript-eslint": { + "version": "8.61.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/webidl-conversions": { + "version": "3.0.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@start9labs/start-sdk/node_modules/whatwg-fetch": { + "version": "3.6.20", + "dev": true, + "license": "MIT" + }, + "node_modules/@start9labs/start-sdk/node_modules/whatwg-url": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/yaml": { + "version": "2.9.0", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@start9labs/start-sdk/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index 49db4d7..68dff40 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "tauri:build": "tauri build", "tauri:dev": "tauri dev", "tauri:sidecar": "./scripts/build-tauri-fedimint-bridge.sh", - "startos:check": "tsc --noEmit -p tsconfig.startos.json", + "startos:check": "tsc --noEmit -p startos/tsconfig.json", + "startos:lint": "node node_modules/@start9labs/start-sdk/lint.mjs", "startos:build": "rm -rf javascript && ncc build startos/index.ts -o javascript --transpile-only", "profile:start9-memory": "tsx scripts/profile-start9-memory.ts" }, @@ -51,7 +52,7 @@ "shamir-secret-sharing": "^0.0.4" }, "devDependencies": { - "@start9labs/start-sdk": "1.5.3", + "@start9labs/start-sdk": "2.0.9", "@tauri-apps/cli": "^2.11.2", "@types/node": "^22.0.0", "@types/react": "^18.3.0", diff --git a/startos/.prettierrc b/startos/.prettierrc new file mode 100644 index 0000000..d50a919 --- /dev/null +++ b/startos/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/startos/actions/index.ts b/startos/actions/index.ts index c82cb95..f24aeb1 100644 --- a/startos/actions/index.ts +++ b/startos/actions/index.ts @@ -1,3 +1,4 @@ import { sdk } from '../sdk' +import { walletStatus } from './walletStatus' -export const actions = sdk.Actions.of() +export const actions = sdk.Actions.of().addAction(walletStatus) diff --git a/startos/actions/walletStatus.ts b/startos/actions/walletStatus.ts new file mode 100644 index 0000000..3aa601d --- /dev/null +++ b/startos/actions/walletStatus.ts @@ -0,0 +1,99 @@ +import { i18n } from '../i18n' +import { sdk } from '../sdk' +import { clients } from '../utils' + +// The bridge reports one of "probing" | "reachable" | "degraded" | "skipped" +// under `discovery.status`; anything else means a bridge newer than this +// package knows about. +const DISCOVERY = { + reachable: 'Reachable', + degraded: 'Degraded', + probing: 'Still probing', + skipped: 'Not configured', +} as const + +async function readHealth(port: number) { + try { + const res = await fetch(`http://127.0.0.1:${port}/health`, { + signal: AbortSignal.timeout(3_000), + }) + if (!res.ok) return null + return (await res.json()) as { + joined?: boolean + discovery?: { status?: string } + } + } catch { + return null + } +} + +export const walletStatus = sdk.Action.withoutInput( + 'wallet-status', + + async ({ effects }) => ({ + name: i18n('Wallet Bridge Status'), + description: i18n( + "Report the federation and relay discovery state of each client's Fedimint wallet bridge", + ), + warning: null, + allowedStatuses: 'only-running', + group: null, + visibility: 'enabled', + }), + + async ({ effects }) => ({ + version: '1' as const, + title: i18n('Wallet Bridge Status'), + message: i18n( + 'A client holds ecash only once you have joined a federation from inside Chama.', + ), + result: { + type: 'group' as const, + value: await Promise.all( + clients.map(async ({ name, bridgePort }) => { + const health = await readHealth(bridgePort) + if (!health) + return { + type: 'single' as const, + name: i18n(name), + description: null, + value: i18n('Not responding'), + masked: false, + copyable: false, + qr: false, + } + const status = health.discovery?.status ?? '' + return { + type: 'group' as const, + name: i18n(name), + description: null, + value: [ + { + type: 'single' as const, + name: i18n('Federation'), + description: null, + value: health.joined ? i18n('Joined') : i18n('Not joined yet'), + masked: false, + copyable: false, + qr: false, + }, + { + type: 'single' as const, + name: i18n('Relay discovery'), + description: null, + value: i18n( + status in DISCOVERY + ? DISCOVERY[status as keyof typeof DISCOVERY] + : 'Unknown', + ), + masked: false, + copyable: false, + qr: false, + }, + ], + } + }), + ), + }, + }), +) diff --git a/startos/backups.ts b/startos/backups.ts index 0b97d7f..0a90b1e 100644 --- a/startos/backups.ts +++ b/startos/backups.ts @@ -1,5 +1,5 @@ import { sdk } from './sdk' -export const { createBackup, restoreInit } = sdk.setupBackups(async () => - sdk.Backups.ofVolumes('main'), +export const { createBackup, restoreInit } = sdk.setupBackups( + async ({ effects }) => sdk.Backups.ofVolumes('main'), ) diff --git a/startos/dependencies.ts b/startos/dependencies.ts index f77d698..7221c4b 100644 --- a/startos/dependencies.ts +++ b/startos/dependencies.ts @@ -1,3 +1,5 @@ import { sdk } from './sdk' -export const setDependencies = sdk.setupDependencies(async () => ({})) +export const setDependencies = sdk.setupDependencies( + async ({ effects }) => ({}), +) diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts index 7ade9ad..7a836f5 100644 --- a/startos/i18n/dictionaries/default.ts +++ b/startos/i18n/dictionaries/default.ts @@ -1,15 +1,38 @@ export const DEFAULT_LANG = 'en_US' const dict = { + // main.ts 'Starting Chama': 0, - 'Web clients': 1, - 'The Chama web clients are ready': 2, - 'The Chama web clients are not ready': 3, - 'Client One': 4, - 'Client Two': 5, - 'Client Three': 6, - 'An isolated Chama testing client': 7, + 'Web Clients': 1, + 'All three Chama clients are ready': 2, + '${client} is not ready yet': 3, + "${client}'s wallet bridge is not ready yet": 4, + + // interfaces.ts + 'Client One': 5, + 'Client Two': 6, + 'Client Three': 7, + 'A self-contained Chama client with its own identity, browser storage and Fedimint wallet': 8, + + // actions/walletStatus.ts + 'Wallet Bridge Status': 9, + "Report the federation and relay discovery state of each client's Fedimint wallet bridge": 10, + 'A client holds ecash only once you have joined a federation from inside Chama.': 11, + 'Not responding': 12, + Federation: 13, + Joined: 14, + 'Not joined yet': 15, + 'Relay discovery': 16, + Reachable: 17, + Degraded: 18, + 'Still probing': 19, + 'Not configured': 20, + Unknown: 21, } as const -export type LangDict = Record<(typeof dict)[keyof typeof dict], string> +/** + * Plumbing. DO NOT EDIT. + */ +export type I18nKey = keyof typeof dict +export type LangDict = Record<(typeof dict)[I18nKey], string> export default dict diff --git a/startos/i18n/dictionaries/translations.ts b/startos/i18n/dictionaries/translations.ts index 98f9348..82e12f1 100644 --- a/startos/i18n/dictionaries/translations.ts +++ b/startos/i18n/dictionaries/translations.ts @@ -1,3 +1,100 @@ import { LangDict } from './default' -export default {} satisfies Record +export default { + es_ES: { + 0: 'Iniciando Chama', + 1: 'Clientes web', + 2: 'Los tres clientes de Chama están listos', + 3: '${client} aún no está listo', + 4: 'El puente de monedero de ${client} aún no está listo', + 5: 'Cliente Uno', + 6: 'Cliente Dos', + 7: 'Cliente Tres', + 8: 'Un cliente de Chama independiente, con su propia identidad, almacenamiento del navegador y monedero Fedimint', + 9: 'Estado del puente de monedero', + 10: 'Muestra el estado de federación y de descubrimiento de relés del puente de monedero Fedimint de cada cliente', + 11: 'Un cliente solo guarda ecash una vez que te has unido a una federación desde Chama.', + 12: 'Sin respuesta', + 13: 'Federación', + 14: 'Unido', + 15: 'Aún sin unirse', + 16: 'Descubrimiento de relés', + 17: 'Accesible', + 18: 'Degradado', + 19: 'Comprobando todavía', + 20: 'Sin configurar', + 21: 'Desconocido', + }, + de_DE: { + 0: 'Chama wird gestartet', + 1: 'Web-Clients', + 2: 'Alle drei Chama-Clients sind bereit', + 3: '${client} ist noch nicht bereit', + 4: 'Die Wallet-Bridge von ${client} ist noch nicht bereit', + 5: 'Client Eins', + 6: 'Client Zwei', + 7: 'Client Drei', + 8: 'Ein eigenständiger Chama-Client mit eigener Identität, eigenem Browser-Speicher und eigener Fedimint-Wallet', + 9: 'Status der Wallet-Bridge', + 10: 'Zeigt den Föderations- und Relay-Discovery-Status der Fedimint-Wallet-Bridge jedes Clients', + 11: 'Ein Client hält erst dann E-Cash, wenn Sie in Chama einer Föderation beigetreten sind.', + 12: 'Keine Antwort', + 13: 'Föderation', + 14: 'Beigetreten', + 15: 'Noch nicht beigetreten', + 16: 'Relay-Discovery', + 17: 'Erreichbar', + 18: 'Eingeschränkt', + 19: 'Wird noch geprüft', + 20: 'Nicht konfiguriert', + 21: 'Unbekannt', + }, + pl_PL: { + 0: 'Uruchamianie Chama', + 1: 'Klienci webowi', + 2: 'Wszyscy trzej klienci Chama są gotowi', + 3: '${client} nie jest jeszcze gotowy', + 4: 'Mostek portfela klienta ${client} nie jest jeszcze gotowy', + 5: 'Klient Pierwszy', + 6: 'Klient Drugi', + 7: 'Klient Trzeci', + 8: 'Samodzielny klient Chama z własną tożsamością, pamięcią przeglądarki i portfelem Fedimint', + 9: 'Stan mostka portfela', + 10: 'Pokazuje stan federacji i wykrywania przekaźników mostka portfela Fedimint każdego klienta', + 11: 'Klient przechowuje ecash dopiero po dołączeniu do federacji w aplikacji Chama.', + 12: 'Brak odpowiedzi', + 13: 'Federacja', + 14: 'Dołączono', + 15: 'Jeszcze nie dołączono', + 16: 'Wykrywanie przekaźników', + 17: 'Osiągalne', + 18: 'Ograniczone', + 19: 'Trwa sprawdzanie', + 20: 'Nieskonfigurowane', + 21: 'Nieznane', + }, + fr_FR: { + 0: 'Démarrage de Chama', + 1: 'Clients web', + 2: 'Les trois clients Chama sont prêts', + 3: "${client} n'est pas encore prêt", + 4: "La passerelle de portefeuille de ${client} n'est pas encore prête", + 5: 'Client Un', + 6: 'Client Deux', + 7: 'Client Trois', + 8: 'Un client Chama autonome, avec sa propre identité, son propre stockage de navigateur et son propre portefeuille Fedimint', + 9: 'État de la passerelle de portefeuille', + 10: "Indique l'état de fédération et de découverte des relais de la passerelle de portefeuille Fedimint de chaque client", + 11: "Un client ne détient des ecash qu'une fois que vous avez rejoint une fédération depuis Chama.", + 12: 'Aucune réponse', + 13: 'Fédération', + 14: 'Rejointe', + 15: 'Pas encore rejointe', + 16: 'Découverte des relais', + 17: 'Accessible', + 18: 'Dégradée', + 19: 'Vérification en cours', + 20: 'Non configurée', + 21: 'Inconnue', + }, +} satisfies Record diff --git a/startos/i18n/index.ts b/startos/i18n/index.ts index 1849d6d..04cea20 100644 --- a/startos/i18n/index.ts +++ b/startos/i18n/index.ts @@ -1,3 +1,6 @@ +/** + * Plumbing. DO NOT EDIT this file. + */ import { setupI18n } from '@start9labs/start-sdk' import defaultDict, { DEFAULT_LANG } from './dictionaries/default' import translations from './dictionaries/translations' diff --git a/startos/index.ts b/startos/index.ts index 3e27bc9..7af589b 100644 --- a/startos/index.ts +++ b/startos/index.ts @@ -1,3 +1,6 @@ +/** + * Plumbing. DO NOT EDIT. + */ export { createBackup } from './backups' export { main } from './main' export { init, uninit } from './init' @@ -5,5 +8,4 @@ export { actions } from './actions' import { buildManifest } from '@start9labs/start-sdk' import { manifest as sdkManifest } from './manifest' import { versionGraph } from './versions' - export const manifest = buildManifest(versionGraph, sdkManifest) diff --git a/startos/interfaces.ts b/startos/interfaces.ts index 2b6b769..f9e99d9 100644 --- a/startos/interfaces.ts +++ b/startos/interfaces.ts @@ -1,32 +1,35 @@ import { i18n } from './i18n' import { sdk } from './sdk' -import { clientOnePort, clientThreePort, clientTwoPort } from './utils' +import { clients } from './utils' export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => { - const clientOneHost = sdk.MultiHost.of(effects, 'client-one-host') - const clientTwoHost = sdk.MultiHost.of(effects, 'client-two-host') - const clientThreeHost = sdk.MultiHost.of(effects, 'client-three-host') + const receipts = [] - const clientOneOrigin = await clientOneHost.bindPort(clientOnePort, { protocol: 'http' }) - const clientTwoOrigin = await clientTwoHost.bindPort(clientTwoPort, { protocol: 'http' }) - const clientThreeOrigin = await clientThreeHost.bindPort(clientThreePort, { protocol: 'http' }) + // A separate host per client, so each lands on its own origin and the + // browser keeps their identities, storage and wallets apart. + for (const { id, name, uiPort } of clients) { + const origin = await sdk.MultiHost.of(effects, `${id}-host`).bindPort( + uiPort, + { protocol: 'http' }, + ) + receipts.push( + await origin.export([ + sdk.createInterface(effects, { + name: i18n(name), + id, + description: i18n( + 'A self-contained Chama client with its own identity, browser storage and Fedimint wallet', + ), + type: 'ui', + masked: false, + schemeOverride: null, + username: null, + path: '', + query: {}, + }), + ]), + ) + } - const makeClient = (id: string, name: 'Client One' | 'Client Two' | 'Client Three') => - sdk.createInterface(effects, { - name: i18n(name), - id, - description: i18n('An isolated Chama testing client'), - type: 'ui', - masked: false, - schemeOverride: null, - username: null, - path: '', - query: {}, - }) - - return [ - await clientOneOrigin.export([makeClient('client-one', 'Client One')]), - await clientTwoOrigin.export([makeClient('client-two', 'Client Two')]), - await clientThreeOrigin.export([makeClient('client-three', 'Client Three')]), - ] + return receipts }) diff --git a/startos/main.ts b/startos/main.ts index 9f74b75..120cc0e 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -1,40 +1,52 @@ import { i18n } from './i18n' import { sdk } from './sdk' -import { clientOnePort, clientThreePort, clientTwoPort } from './utils' +import { clients } from './utils' export const main = sdk.setupMain(async ({ effects }) => { console.info(i18n('Starting Chama')) - const chamaSubcontainer = await sdk.SubContainer.of( - effects, - { imageId: 'chama' }, - sdk.Mounts.of().mountVolume({ - volumeId: 'main', - subpath: null, - mountpoint: '/data', - readonly: false, - }), - 'chama-sub', - ) - return sdk.Daemons.of(effects).addDaemon('primary', { - subcontainer: chamaSubcontainer, + subcontainer: sdk.SubContainer.of( + effects, + { imageId: 'chama' }, + sdk.Mounts.of().mountVolume({ + volumeId: 'main', + subpath: null, + mountpoint: '/data', + readonly: false, + }), + 'chama-sub', + ), exec: { command: ['/usr/local/bin/chama-startos-entrypoint'] }, ready: { - display: i18n('Web clients'), + display: i18n('Web Clients'), gracePeriod: 30_000, fn: async () => { - const ports = [clientOnePort, clientTwoPort, clientThreePort] - for (const port of ports) { - const result = await sdk.healthCheck.checkPortListening(effects, port, { - successMessage: i18n('The Chama web clients are ready'), - errorMessage: i18n('The Chama web clients are not ready'), - }) - if (result.result !== 'success') return result + for (const { name, uiPort, bridgePort } of clients) { + const client = i18n(name) + // A client whose UI is up but whose wallet bridge is not serves the + // page and then 502s every /bridge/ call, so both ports gate ready. + for (const [port, errorMessage] of [ + [uiPort, i18n('${client} is not ready yet', { client })], + [ + bridgePort, + i18n("${client}'s wallet bridge is not ready yet", { client }), + ], + ] as const) { + const result = await sdk.healthCheck.checkPortListening( + effects, + port, + { + successMessage: i18n('All three Chama clients are ready'), + errorMessage, + }, + ) + if (result.result !== 'success') return result + } } return { result: 'success' as const, - message: i18n('The Chama web clients are ready'), + message: i18n('All three Chama clients are ready'), } }, }, diff --git a/startos/manifest/i18n.ts b/startos/manifest/i18n.ts index 3451edd..a9cff39 100644 --- a/startos/manifest/i18n.ts +++ b/startos/manifest/i18n.ts @@ -1,9 +1,25 @@ export const short = { en_US: 'A sovereign marketplace for trading anything, anywhere, with Nostr and private ecash escrow', + es_ES: + 'Un mercado soberano para comerciar con cualquier cosa, en cualquier lugar, con Nostr y depósito en garantía de ecash privado', + de_DE: + 'Ein souveräner Marktplatz, um überall alles zu handeln — mit Nostr und privater E-Cash-Treuhand', + pl_PL: + 'Suwerenny rynek do handlu wszystkim i wszędzie, z Nostr i prywatnym depozytem ecash', + fr_FR: + 'Une place de marché souveraine pour échanger tout, partout, avec Nostr et un séquestre en ecash privé', } export const long = { en_US: 'Chama is a borderless peer-to-peer marketplace for real-world trade without platform accounts, custodial middlemen, or permission. Publish offers over Nostr, negotiate directly, coordinate buyer-seller-arbiter escrow, and settle with Fedimint ecash and Lightning.', + es_ES: + 'Chama es un mercado entre pares sin fronteras para el comercio real, sin cuentas de plataforma, sin intermediarios que custodien tus fondos y sin pedir permiso. Publica ofertas por Nostr, negocia directamente, coordina un depósito en garantía entre comprador, vendedor y árbitro, y liquida con ecash de Fedimint y Lightning.', + de_DE: + 'Chama ist ein grenzenloser Peer-to-Peer-Marktplatz für echten Handel — ohne Plattformkonten, ohne verwahrende Mittelsleute und ohne Erlaubnis. Veröffentliche Angebote über Nostr, verhandle direkt, koordiniere eine Treuhand aus Käufer, Verkäufer und Schiedsperson, und begleiche mit Fedimint-E-Cash und Lightning.', + pl_PL: + 'Chama to pozbawiony granic rynek peer-to-peer do prawdziwego handlu — bez kont na platformie, bez powierniczych pośredników i bez pytania o pozwolenie. Publikuj oferty przez Nostr, negocjuj bezpośrednio, koordynuj depozyt między kupującym, sprzedającym i arbitrem oraz rozliczaj się ecashem Fedimint i przez Lightning.', + fr_FR: + 'Chama est une place de marché pair-à-pair sans frontières pour le commerce réel, sans compte de plateforme, sans intermédiaire dépositaire et sans permission. Publiez des offres via Nostr, négociez directement, coordonnez un séquestre entre acheteur, vendeur et arbitre, puis réglez en ecash Fedimint et via Lightning.', } diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index 4519ca0..cb79d84 100644 --- a/startos/manifest/index.ts +++ b/startos/manifest/index.ts @@ -5,7 +5,7 @@ export const manifest = setupManifest({ id: 'chama', title: 'Chama', license: 'MIT', - packageRepo: 'https://github.com/jesuspirate/chama', + packageRepo: 'https://github.com/Start9-Community/chama', upstreamRepo: 'https://github.com/jesuspirate/chama', marketingUrl: 'https://chama.community/', donationUrl: null, diff --git a/startos/nginx.conf b/startos/nginx.conf index 8eb27cb..ed33f05 100644 --- a/startos/nginx.conf +++ b/startos/nginx.conf @@ -5,6 +5,12 @@ pid /tmp/nginx.pid; events { worker_connections 1024; } +# One server block per Chama client, each proxying /bridge/ to that client's own +# chama-fedimint-bridge on loopback. /await-invoice is a long poll held open +# until the payer actually pays, so a read timeout there is a clock on the human, +# not on the bridge: at nginx's default it hung up during an ordinary +# scan-the-QR pause and answered with its own 504 HTML, which the client read as +# a rejected payment. The bridge stays reachable for a full invoice lifetime. http { include /etc/nginx/mime.types; default_type application/octet-stream; @@ -21,11 +27,6 @@ http { location /bridge/ { proxy_pass http://127.0.0.1:8787/; proxy_connect_timeout 10s; - # /await-invoice is a long poll held open until the payer actually pays, - # so a read timeout here is a clock on the human, not on the bridge. At - # 110s nginx hung up during an ordinary scan-the-QR pause and answered - # with its own 504 HTML, which the client read as a rejected payment. - # The bridge stays reachable for a full invoice lifetime instead. proxy_send_timeout 3600s; proxy_read_timeout 3600s; } @@ -36,11 +37,6 @@ http { location /bridge/ { proxy_pass http://127.0.0.1:8788/; proxy_connect_timeout 10s; - # /await-invoice is a long poll held open until the payer actually pays, - # so a read timeout here is a clock on the human, not on the bridge. At - # 110s nginx hung up during an ordinary scan-the-QR pause and answered - # with its own 504 HTML, which the client read as a rejected payment. - # The bridge stays reachable for a full invoice lifetime instead. proxy_send_timeout 3600s; proxy_read_timeout 3600s; } @@ -51,11 +47,6 @@ http { location /bridge/ { proxy_pass http://127.0.0.1:8789/; proxy_connect_timeout 10s; - # /await-invoice is a long poll held open until the payer actually pays, - # so a read timeout here is a clock on the human, not on the bridge. At - # 110s nginx hung up during an ordinary scan-the-QR pause and answered - # with its own 504 HTML, which the client read as a rejected payment. - # The bridge stays reachable for a full invoice lifetime instead. proxy_send_timeout 3600s; proxy_read_timeout 3600s; } diff --git a/startos/sdk.ts b/startos/sdk.ts index 4bf7c2c..04ae4b1 100644 --- a/startos/sdk.ts +++ b/startos/sdk.ts @@ -1,4 +1,9 @@ import { StartSdk } from '@start9labs/start-sdk' import { manifest } from './manifest' +/** + * Plumbing. DO NOT EDIT. + * + * The exported "sdk" const is used throughout this package codebase. + */ export const sdk = StartSdk.of().withManifest(manifest).build(true) diff --git a/startos/tsconfig.json b/startos/tsconfig.json index 794f976..cbe6f2f 100644 --- a/startos/tsconfig.json +++ b/startos/tsconfig.json @@ -1,11 +1,4 @@ { - "include": ["./**/*.ts", "../node_modules/**/startos"], - "compilerOptions": { - "target": "ES2018", - "module": "CommonJS", - "moduleResolution": "node", - "esModuleInterop": true, - "strict": true, - "skipLibCheck": true - } + "extends": "@start9labs/start-sdk/tsconfig.base.json", + "include": ["./**/*.ts", "../node_modules/**/startos"] } diff --git a/startos/utils.ts b/startos/utils.ts index c22c297..368a56c 100644 --- a/startos/utils.ts +++ b/startos/utils.ts @@ -1,3 +1,9 @@ -export const clientOnePort = 8080 -export const clientTwoPort = 8081 -export const clientThreePort = 8082 +// One Chama client per entry. Each gets its own nginx server block (uiPort) +// and its own chama-fedimint-bridge process on loopback (bridgePort) writing +// to its own wallet directory under /data — startos/nginx.conf and +// startos/entrypoint.sh derive the same three from these numbers. +export const clients = [ + { id: 'client-one', name: 'Client One', uiPort: 8080, bridgePort: 8787 }, + { id: 'client-two', name: 'Client Two', uiPort: 8081, bridgePort: 8788 }, + { id: 'client-three', name: 'Client Three', uiPort: 8082, bridgePort: 8789 }, +] as const diff --git a/startos/versions/current.ts b/startos/versions/current.ts index edd808d..539560b 100644 --- a/startos/versions/current.ts +++ b/startos/versions/current.ts @@ -1,13 +1,21 @@ import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' export const current = VersionInfo.of({ - version: '5.5.0:14', + version: '5.7.0:0', releaseNotes: { en_US: - 'Fixes the relay-storm memory runaway that could freeze or crash a client: redelivered events are recognised before they are decrypted, repeat chain reloads of one trade back off instead of re-arming, listing hydration is queued behind a concurrency cap, and the in-memory event cache is bounded. Also fixes funding: waiting for a Lightning payment no longer reports a false rejection when the proxy in front of the wallet bridge closes the connection — the bridge now bounds its own wait and the client re-asks, so an unpaid invoice stays honestly pending. Carries forward the lab health checks, fail-closed entrypoint, and Work offers (NIP-99 + worker résumé), plus in-progress Guided buy work from this build.', + 'First StartOS release. Runs three self-contained Chama clients, each on its own interface with its own identity, browser storage and Fedimint wallet. Adds a Wallet Bridge Status action reporting each wallet bridge, and holds a client back from healthy until its wallet bridge is up, not just its web page.', + es_ES: + 'Primera versión para StartOS. Ejecuta tres clientes de Chama independientes, cada uno en su propia interfaz y con su propia identidad, almacenamiento del navegador y monedero Fedimint. Añade una acción de Estado del puente de monedero e impide que un cliente aparezca como correcto hasta que su puente de monedero esté activo, no solo su página web.', + de_DE: + 'Erste StartOS-Veröffentlichung. Betreibt drei eigenständige Chama-Clients, jeder mit eigener Schnittstelle, eigener Identität, eigenem Browser-Speicher und eigener Fedimint-Wallet. Ergänzt eine Aktion „Status der Wallet-Bridge“ und meldet einen Client erst als fehlerfrei, wenn auch seine Wallet-Bridge läuft, nicht nur seine Webseite.', + pl_PL: + 'Pierwsze wydanie dla StartOS. Uruchamia trzech samodzielnych klientów Chama, każdego na własnym interfejsie i z własną tożsamością, pamięcią przeglądarki oraz portfelem Fedimint. Dodaje akcję Stan mostka portfela i uznaje klienta za sprawnego dopiero wtedy, gdy działa także jego mostek portfela, a nie tylko strona internetowa.', + fr_FR: + "Première version pour StartOS. Exécute trois clients Chama autonomes, chacun sur sa propre interface avec sa propre identité, son propre stockage de navigateur et son propre portefeuille Fedimint. Ajoute une action « État de la passerelle de portefeuille » et ne considère un client comme sain qu'une fois sa passerelle de portefeuille active, et pas seulement sa page web.", }, migrations: { - up: async () => {}, + up: async ({ effects }) => {}, down: IMPOSSIBLE, }, }) diff --git a/tsconfig.startos.json b/tsconfig.startos.json deleted file mode 100644 index a2945a5..0000000 --- a/tsconfig.startos.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "include": ["startos/**/*.ts", "node_modules/**/startos"], - "compilerOptions": { - "target": "ES2018", - "module": "CommonJS", - "moduleResolution": "node", - "esModuleInterop": true, - "strict": true, - "skipLibCheck": true - } -}