Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# CipherSlate — Development Guide
# Sigstack — Development Guide

## Product direction

Expand Down
17 changes: 11 additions & 6 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,29 @@ jobs:
- run: npm ci
- run: npm run build
env:
BASE_PATH: /sigstack-bot/cypherslate
BASE_PATH: /sigstack-bot/sigstack
PUBLIC_SIGNAL_USERNAME_LINK: ${{ vars.PUBLIC_SIGNAL_USERNAME_LINK }}
PUBLIC_STRIPE_PORTAL_URL: ${{ vars.PUBLIC_STRIPE_PORTAL_URL }}
- name: Prepare Pages artifact for /sigstack-bot/cypherslate
- name: Prepare Pages artifact for /sigstack-bot/sigstack
shell: bash
run: |
set -euo pipefail

# The GitHub Pages project site mounts the artifact at `/<repo>/`.
# Nest the SvelteKit output under `cypherslate/` so /sigstack-bot/cypherslate/
# Nest the SvelteKit output under `sigstack/` so /sigstack-bot/sigstack/
# resolves. Move the whole build dir (avoids overlapping hidden-file globs).
tmp="$(mktemp -d)"
mv build "$tmp/cypherslate"
mv build "$tmp/sigstack"
mkdir -p build
mv "$tmp/cypherslate" build/cypherslate
mv "$tmp/sigstack" build/sigstack
printf '%s\n' '<!doctype html>' \
'<meta http-equiv="refresh" content="0; url=/sigstack-bot/cypherslate/">' \
'<meta http-equiv="refresh" content="0; url=/sigstack-bot/sigstack/">' \
> build/index.html
# Keep old /cypherslate/ bookmarks working.
mkdir -p build/cypherslate
printf '%s\n' '<!doctype html>' \
'<meta http-equiv="refresh" content="0; url=/sigstack-bot/sigstack/">' \
> build/cypherslate/index.html
touch build/.nojekyll
- uses: actions/upload-pages-artifact@v3
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ jobs:
- run: npm run test:e2e
env:
CI: true
BASE_PATH: /sigstack-bot/cypherslate
BASE_PATH: /sigstack-bot/sigstack
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# CipherSlate
# Sigstack

TEE-hosted Signal bot for **voice transcription** and **group translation**, designed as an interoperable product suite.

Expand All @@ -23,7 +23,7 @@ Details: [docs/one-cvm-architecture.md](docs/one-cvm-architecture.md) · [docs/v

| Product | Command | Effect |
|---------|---------|--------|
| Hub | `!help` / `!info` | CipherSlate hub menus |
| Hub | `!help` / `!info` | Sigstack hub menus |
| Hub | `!privacy` | Privacy, TEE, and `!verify` (one reply) |
| Hub | `!translation-threads` | Language Threads menu |
| Hub | `!translation-in-chat` | In-chat translation menu |
Expand Down
6 changes: 3 additions & 3 deletions crates/signal-bot/src/commands/menu_locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ PER MSG QUOTE REPLY:

!help-transcription"#;

const HELP_HUB: &str = r#"--CipherSlate--
const HELP_HUB: &str = r#"--Sigstack--

MENUS:
!translation-threads
Expand All @@ -188,7 +188,7 @@ const HELP_THREAD: &str = r#"Language Thread
!info
!commands"#;

const INFO_HUB: &str = r#"--CipherSlate--
const INFO_HUB: &str = r#"--Sigstack--

!translation-threads
Language Threads — multilingual main chat + language sidecars
Expand Down Expand Up @@ -353,7 +353,7 @@ const PRIVACY_MENU: &str = r#"Privacy & TEE
example:
!verify "write something unique here"

CipherSlate is one Signal number in one Phala TEE/CVM.
Sigstack is one Signal number in one Phala TEE/CVM.

Translation: Signal text is processed in this TEE and translated via NEAR AI private inference.

Expand Down
4 changes: 2 additions & 2 deletions crates/signal-bot/src/commands/translate_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ mod tests {
let msg = BotMessage {
source: "+15550001111".into(),
source_number: Some("+15550001111".into()),
source_name: Some("CipherSlate".into()),
source_name: Some("Sigstack".into()),
text: "📝 Transcript:\nHola amigos".into(),
timestamp: 0,
message_timestamp: 0,
Expand Down Expand Up @@ -879,7 +879,7 @@ mod tests {
let msg = BotMessage {
source: "+15550001111".into(),
source_number: Some("+15550001111".into()),
source_name: Some("CipherSlate".into()),
source_name: Some("Sigstack".into()),
text: "Hello friends".into(),
timestamp: 0,
message_timestamp: 0,
Expand Down
6 changes: 3 additions & 3 deletions crates/signal-bot/src/commands/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl VerifyHandler {

pub(crate) fn prefixed_challenge(&self, raw: Option<String>) -> String {
let user_part = raw.unwrap_or_else(|| "no-challenge-provided".into());
format!("CipherSlate: {user_part}")
format!("Sigstack: {user_part}")
}

/// Parse the challenge nonce from the message text.
Expand Down Expand Up @@ -316,11 +316,11 @@ mod tests {
let handler = create_test_handler();
assert_eq!(
handler.prefixed_challenge(Some("hello".into())),
"CipherSlate: hello"
"Sigstack: hello"
);
assert_eq!(
handler.prefixed_challenge(None),
"CipherSlate: no-challenge-provided"
"Sigstack: no-challenge-provided"
);
}

Expand Down
4 changes: 2 additions & 2 deletions crates/signal-bot/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub struct NearAiConfig {

#[derive(Debug, Clone, Deserialize)]
pub struct BotConfig {
/// Signal username nickname (e.g. `cipherslate`; Signal adds `.NN`).
/// Signal username nickname (e.g. `sigstack`; Signal adds `.NN`).
/// Empty string disables startup username ensure.
#[serde(default = "default_signal_username_opt")]
pub signal_username: Option<String>,
Expand Down Expand Up @@ -286,7 +286,7 @@ fn default_log_level() -> String {
}

fn default_signal_username() -> String {
"cipherslate".into()
"sigstack".into()
}

fn default_signal_username_opt() -> Option<String> {
Expand Down
8 changes: 4 additions & 4 deletions crates/signal-bot/src/ensure_username.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,16 @@ mod tests {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/v1/accounts/%2B15555555555/username"))
.and(body_json(serde_json::json!({ "username": "cipherslate" })))
.and(body_json(serde_json::json!({ "username": "sigstack" })))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"username": "cipherslate.01",
"username": "sigstack.01",
"username_link": "https://signal.me/#eu/x"
})))
.mount(&mock_server)
.await;

let client = SignalClient::new(mock_server.uri()).unwrap();
ensure_signal_username(&client, "+15555555555", "cipherslate.99").await;
ensure_signal_username(&client, "+15555555555", "sigstack.99").await;
}

#[tokio::test]
Expand All @@ -90,6 +90,6 @@ mod tests {
.await;

let client = SignalClient::new(mock_server.uri()).unwrap();
ensure_signal_username(&client, "+15555555555", "cipherslate").await;
ensure_signal_username(&client, "+15555555555", "sigstack").await;
}
}
6 changes: 1 addition & 5 deletions crates/signal-bot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,7 @@ async fn main() -> AppResult<()> {
.as_deref()
.filter(|p| !p.trim().is_empty())
{
let nickname = config
.bot
.signal_username
.as_deref()
.unwrap_or("cipherslate");
let nickname = config.bot.signal_username.as_deref().unwrap_or("sigstack");
ensure_signal_username(&signal, phone, nickname).await;
} else {
warn!(
Expand Down
4 changes: 2 additions & 2 deletions crates/signal-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,8 @@ impl SignalClient {

/// Set (or refresh) a Signal username for an account.
///
/// Pass a nickname (e.g. `cipherslate`); Signal assigns a discriminator
/// (`cipherslate.54`) and returns a shareable `username_link`.
/// Pass a nickname (e.g. `sigstack`); Signal assigns a discriminator
/// (`sigstack.54`) and returns a shareable `username_link`.
#[instrument(skip(self))]
pub async fn set_username(
&self,
Expand Down
10 changes: 5 additions & 5 deletions crates/signal-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,20 +655,20 @@ mod tests {

Mock::given(method("POST"))
.and(path("/v1/accounts/%2B15555555555/username"))
.and(body_json(serde_json::json!({ "username": "cipherslate" })))
.and(body_json(serde_json::json!({ "username": "sigstack" })))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"username": "cipherslate.54",
"username": "sigstack.54",
"username_link": "https://signal.me/#eu/abc"
})))
.mount(&mock_server)
.await;

let client = create_test_client(&mock_server).await;
let info = client
.set_username("+15555555555", "cipherslate")
.set_username("+15555555555", "sigstack")
.await
.unwrap();
assert_eq!(info.username.as_deref(), Some("cipherslate.54"));
assert_eq!(info.username.as_deref(), Some("sigstack.54"));
assert_eq!(
info.username_link.as_deref(),
Some("https://signal.me/#eu/abc")
Expand All @@ -687,7 +687,7 @@ mod tests {

let client = create_test_client(&mock_server).await;
let err = client
.set_username("+15555555555", "cipherslate")
.set_username("+15555555555", "sigstack")
.await
.unwrap_err();
assert!(err.to_string().contains("taken"));
Expand Down
4 changes: 2 additions & 2 deletions docker/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copy to docker/.env and fill in.
# Unified CipherSlate (hub + voice + translation).
# Unified Sigstack (hub + voice + translation).

SIGNAL_PHONE=+1YYYYYYYYYY
NEAR_AI_API_KEY=sk-your-api-key-here
Expand All @@ -14,6 +14,6 @@ LOG_LEVEL=info
GROUP_PREFERENCES_PERSIST=true
ENTITLEMENTS_PERSIST=true
BOT_GITHUB_REPO=https://github.com/BreadchainCoop/sigstack-bot
BOT_SIGNAL_USERNAME=cipherslate
BOT_SIGNAL_USERNAME=sigstack
RATE_LIMIT_GLOBAL_PER_MINUTE=10
RATE_LIMIT_PER_NUMBER_PER_HOUR=3
2 changes: 1 addition & 1 deletion docker/.phala.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ TRANSLATE_ALL_MAX_MESSAGES_PER_MINUTE=30
# ENTITLEMENTS_LEGACY_COMPOSE_HASH=
LOG_LEVEL=info
BOT_GITHUB_REPO=https://github.com/BreadchainCoop/sigstack-bot
BOT_SIGNAL_USERNAME=cipherslate
BOT_SIGNAL_USERNAME=sigstack
RATE_LIMIT_GLOBAL_PER_MINUTE=10
RATE_LIMIT_PER_NUMBER_PER_HOUR=3
# Optional: inject an ops SSH pubkey so `phala ssh -i …` works.
Expand Down
2 changes: 1 addition & 1 deletion docker/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ services:
- TRANSLATE_ALL__ENABLED=${TRANSLATE_ALL_ENABLED:-true}
- TRANSLATE_ALL__MAX_MESSAGES_PER_MINUTE=${TRANSLATE_ALL_MAX_MESSAGES_PER_MINUTE:-30}
- BOT__LOG_LEVEL=${LOG_LEVEL:-info}
- BOT__SIGNAL_USERNAME=${BOT_SIGNAL_USERNAME:-cipherslate}
- BOT__SIGNAL_USERNAME=${BOT_SIGNAL_USERNAME:-sigstack}
- BOT__GITHUB_REPO=${BOT_GITHUB_REPO:-https://github.com/BreadchainCoop/sigstack-bot}
- DSTACK__SOCKET_PATH=/var/run/dstack.sock
- GROUP_PREFERENCES__PERSIST=${GROUP_PREFERENCES_PERSIST:-true}
Expand Down
2 changes: 1 addition & 1 deletion docker/phala.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ services:
- TRANSLATE_ALL__ENABLED=${TRANSLATE_ALL_ENABLED:-true}
- TRANSLATE_ALL__MAX_MESSAGES_PER_MINUTE=${TRANSLATE_ALL_MAX_MESSAGES_PER_MINUTE:-30}
- BOT__LOG_LEVEL=${LOG_LEVEL:-info}
- BOT__SIGNAL_USERNAME=${BOT_SIGNAL_USERNAME:-cipherslate}
- BOT__SIGNAL_USERNAME=${BOT_SIGNAL_USERNAME:-sigstack}
- BOT__GITHUB_REPO=${BOT_GITHUB_REPO:-https://github.com/BreadchainCoop/sigstack-bot}
- DSTACK__SOCKET_PATH=/var/run/dstack.sock
- GROUP_PREFERENCES__PERSIST=true
Expand Down
2 changes: 1 addition & 1 deletion docs/in-chat-translation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# In-chat (group) translation

Status: **MVP implemented** on the unified CipherSlate (hub menus, in-chat, Language Threads, and voice).
Status: **MVP implemented** on the unified Sigstack (hub menus, in-chat, Language Threads, and voice).

One **bilingual** Signal group (e.g. English + Spanish). The bot detects which side of the pair a message is on and quote-replies with the other language in the **same** main thread.

Expand Down
2 changes: 1 addition & 1 deletion docs/language-threads.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ N=1 (one sidecar) uses the same relay rules as N=3 — add another language late
| `!commands` | Sidecar only | Compact Language Thread command list |
| `!list-langs` | Any | Full Language Threads / manual `!translate` catalog (includes Basque, Swahili, and 30 other codes) |
| `!help-threads` | Any | How Language Threads works (use case + flow) |
| `!help` / `!privacy` | Any | Hub menus (`!help` is always the CipherSlate hub; `!privacy` and `!verify` on this bot) |
| `!help` / `!privacy` | Any | Hub menus (`!help` is always the Sigstack hub; `!privacy` and `!verify` on this bot) |

Menus: `!help` → `!translation-threads`. English-only for now (multi-language UI deferred).

Expand Down
2 changes: 1 addition & 1 deletion docs/one-cvm-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ See also: the architecture learning [CPU TEE Whisper does not scale](solutions/a

| Bot | Phone | Duty |
|-----|-------|------|
| **CipherSlate** | Registered number on this CVM | Hub menus (`!help`, `!info`, `!privacy`), Language Threads, Bilingual Threads, in-chat translation, voice (`!transcription` menu, `!transcribe*`), `!verify` (one reply) |
| **Sigstack** | Registered number on this CVM | Hub menus (`!help`, `!info`, `!privacy`), Language Threads, Bilingual Threads, in-chat translation, voice (`!transcription` menu, `!transcribe*`), `!verify` (one reply) |

Voice crate [`crates/signal-bot-voice`](../crates/signal-bot-voice); pairing / `PEER_PHONE` / a second Signal number do not exist.

Expand Down
2 changes: 1 addition & 1 deletion docs/plans/2026-08-14-001-feat-localize-bot-menus-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ A Spanish speaker in a mixed group still gets English `!help`. The leftover `Men
- R10. In a Language Thread sidecar, if the user has no explicit locale, menus default to that sidecar's language code when it is in `ALL_LANGUAGES`.
- R11. An explicit user locale wins over the sidecar default, including `en`.
- R12. Localized command lists keep the stacked layout in `docs/solutions/signal-mobile-menus.md`.
- R13. Hub `!help` from a sidecar still returns the CipherSlate hub menu (localized), not the thread compact list.
- R13. Hub `!help` from a sidecar still returns the Sigstack hub menu (localized), not the thread compact list.

**Coverage and persistence**

Expand Down
6 changes: 3 additions & 3 deletions docs/solutions/signal-mobile-menus.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ Menus are **English-only** for now; multi-language UI is deferred.
2. **Section header** (optional) + optional one-line blurb.
3. **Each command** on its own line. No `cmd — desc` on one line.
4. Prefer plain `!command` lines over `- !command` bullets.
5. **Hub / nav lists** (e.g. main `!help` on CipherSlate): commands only — skip indented descriptions when they would just restate the command name.
5. **Hub / nav lists** (e.g. main `!help` on Sigstack): commands only — skip indented descriptions when they would just restate the command name.
6. **Product / how-to lists** (e.g. `!translation-in-chat`, transcription toggles): put a short description on the next line, indented with two spaces (aim ≤~40 chars).
7. **`!help` footer** is the bare command — no “Main menu” / “Show this menu” line (`!help` is implicit).
8. Prose blocks (privacy explanations, invite/status messages) stay paragraphs; only **command lists** use the forms above.

Hub shape:

```text
CipherSlate
Sigstack

!translation-threads
!translation-in-chat
Expand Down Expand Up @@ -52,7 +52,7 @@ Optional one-line blurb.
!help
```

Language Thread sidecars use `!commands` for the thread menu (rename / leave / info). Hub `!help` always returns the CipherSlate menu, including when sent from a sidecar.
Language Thread sidecars use `!commands` for the thread menu (rename / leave / info). Hub `!help` always returns the Sigstack menu, including when sent from a sidecar.

Voice transcription uses `!transcription` for its product menu (not hub `!help`). Hub `!help` / `!info` / `!privacy` and `!transcription` all live on the same bot. See [one-cvm-architecture.md](../one-cvm-architecture.md).

Expand Down
2 changes: 1 addition & 1 deletion docs/voice-transcription.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Voice transcription

Status: **implemented** on the unified CipherSlate on the surviving Phala CVM.
Status: **implemented** on the unified Sigstack on the surviving Phala CVM.

Speech → text via **NEAR AI Whisper Large V3** (GPU TEE). Audio is decrypted in this CVM, stripped of Signal metadata, and uploaded as a generic file. See [one-CVM architecture](one-cvm-architecture.md) and [CPU TEE Whisper does not scale](solutions/architecture-patterns/2026-08-13-cpu-tee-whisper-does-not-scale.md).

Expand Down
14 changes: 7 additions & 7 deletions site/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# CipherSlate site
# Sigstack site

Educational / marketing site for CipherSlate.
Educational / marketing site for Sigstack.

**Stack:** SvelteKit (static) + Paraglide (en/es/fr via cookie) + Vitest + Playwright
**Host:** GitHub Pages at `https://breadchaincoop.github.io/sigstack-bot/cypherslate/`
**Host:** GitHub Pages at `https://breadchaincoop.github.io/sigstack-bot/sigstack/`
**Design:** Small token system inspired by [Octant](https://octant.build/) accent language and [Golem Foundation](https://golem.foundation/)-like typography (DM Sans). Not affiliated with either. Products live on one `/products` page; the nav Products dropdown pins section anchors (`#language-threads`, `#in-chat`, `#transcription`).

## Local
Expand All @@ -14,7 +14,7 @@ npm install
npm run dev
```

Production base path defaults to `/sigstack-bot/cypherslate`. For root-local preview:
Production base path defaults to `/sigstack-bot/sigstack`. For root-local preview:

```bash
BASE_PATH= npm run build && BASE_PATH= npm run preview
Expand Down Expand Up @@ -42,9 +42,9 @@ Optional public env (GitHub Pages-safe; never put secret keys here):
| Variable | Effect |
| -------- | ------ |
| `PUBLIC_STRIPE_PORTAL_URL` | Success page “Manage billing” link; if unset, shows “coming soon” stub |
| `PUBLIC_SIGNAL_USERNAME_LINK` | Alpha / checkout success “Message CipherSlate” button (`signal.me/#eu/…` username share link; never put the bot E.164 here) |
| `PUBLIC_SIGNAL_USERNAME_LINK` | Alpha / checkout success “Message Sigstack” button (`signal.me/#eu/…` username share link; never put the bot E.164 here) |

After the bot claims its Signal username (startup `BOT__SIGNAL_USERNAME=cipherslate`), copy the logged `username_link` into the GitHub Actions variable `PUBLIC_SIGNAL_USERNAME_LINK` and redeploy Pages.
After the bot claims its Signal username (startup `BOT__SIGNAL_USERNAME=sigstack`), copy the logged `username_link` into the GitHub Actions variable `PUBLIC_SIGNAL_USERNAME_LINK` and redeploy Pages.

## Scripts

Expand All @@ -59,6 +59,6 @@ After the bot claims its Signal username (startup `BOT__SIGNAL_USERNAME=ciphersl

1. Repo **Settings → Pages → Build and deployment → Source: GitHub Actions**
2. Merge to `main` (or run **Pages** workflow manually)
3. Site URL: https://breadchaincoop.github.io/sigstack-bot/cypherslate/
3. Site URL: https://breadchaincoop.github.io/sigstack-bot/sigstack/

Legacy `web/` (Private AI) is unrelated — do not use it as the product storefront.
Loading
Loading