This minimalist plan focuses on building the Social State Machine first. We will implement the OAuth + Keyring authentication and the binary thread-tip logic while keeping the agent in a "Read-Only" mode.
The bot is written in pure JavaScript and node.js (no TypeScript or complex build steps).
The bot will use OAuth 2.0 with PKCE. This ensures the orchestrator never touches your master password.
- Storage: We will use
keytar(or a similar library) to interface with the system keyring. - Flow: 1. Orchestrator checks keyring for a Refresh Token.
- If missing, it prints a login URL.
- User authorizes in browser; Orchestrator receives tokens and saves the Refresh Token to the keyring.
- Access Tokens are kept strictly in-memory.
The orchestrator will poll the BlueSky notification timeline. For every notification, it fetches the thread context to perform the Binary State Evaluation:
- State A (Ignore): The thread tip is a post by the Bot. The turn belongs to the user.
- State B (Process): The thread tip is a post by an Authorized User.
- Action: 1. Post ephemeral
Processing...message.
- Call Gemini (Read-Only mode).
- Post genuine reply.
- Delete the ephemeral
Processing...message.
To keep this phase truly minimalist and "safe," we apply these restrictions:
- No File Access: The agent is not provided with tools to read or write to the local disk.
- No Isolation: Since there is no filesystem risk, we skip
PRootfor this phase. - System Prompt: Gemini is instructed: "You are a dialogue-only agent. Do not suggest code changes or file edits. Focus only on conversation and logic analysis."
| Component | Library/Tool |
|---|---|
| Language | Node.js (Latest LTS) |
| BlueSky API | @atcute set of packages |
| Keyring | keytar |
| Inference | Google Generative AI SDK (Gemini 2.0/1.5) |
- Persistence: Reboot the computer and see if the bot resumes polling without asking for a login.
- Cleanliness: Confirm the
Processing...messages are deleted successfully after every turn. - Concurrency: Send messages in two different threads simultaneously and ensure replies stay in their respective lanes.
The keytar fallback logic is a resilient storage pattern that prioritizes security but ensures functionality across different environments like WSL2 or Termux. It operates on a "Best-Effort Encryption" principle.
The Orchestrator does not assume the environment is compatible with native system APIs. At startup, it performs a guarded attempt to load the keytar module. If the module is missing or the underlying system service (like D-Bus or libsecret) is unresponsive, the Orchestrator catches the failure immediately rather than crashing.
Based on the result of the probe, the system branches into one of two persistence paths:
-
Tier 1: System Keyring (Secure Path)
-
Used when keytar is successfully initialized.
-
Tokens are handed off to the OS (macOS Keychain, Windows Credential Vault, or Linux Secret Service).
-
The data is encrypted at rest by the operating system, protecting it from unauthorized file-system access.
-
Tier 2: Home-Directory JSON (Compatibility Path)
-
Used as the fail-safe for Termux, headless servers, or restricted sandboxes.
-
The tokens are stored in a hidden configuration file located in the user's home directory.
-
While this skips the system-level encryption, it ensures the bot remains stateless and portable, as the session can be recovered even without specialized system libraries.
To the rest of the bot's code, the storage method is invisible. The Orchestrator interacts with a unified "Token Manager" interface. Whether the token is pulled from an encrypted hardware enclave or a local text file, the internal logic only cares that a valid token is returned for the BlueSky session.
By marking keytar as an optional dependency in the project configuration, the bot remains installable on systems where native C++ compilation would normally fail. The installation process simply skips the "fucky" parts, allowing the pure-JavaScript fallback logic to take over automatically.
This adds a "Power User" override to the system. By introducing environment variables as the top-priority check, you allow the Orchestrator to skip the complex OAuth handshake entirely for local testing or CI/CD pipelines.
The Orchestrator will evaluate credentials in a strict Top-Down order. The moment it finds a valid set of credentials, it stops searching and initializes the session.
This is the "Manual Override." If the user provides CODESKY_HANDLE and CODESKY_APP_PASSWORD in the shell environment (or a .env file), the bot immediately uses the App Password flow.
- Why: This is perfect for Termux or quick local tests where you don't want to deal with browser redirects or keyring permissions.
- Flow: 1. Checks for both variables.
- If present, calls
agent.login()directly. - Skips the Keyring and OAuth checks entirely.
If the environment variables are missing, the bot looks for a persistent OAuth session in the system keyring (e.g., keytar).
- Why: This is the most "set-and-forget" stable method for a desktop/WSL2 environment.
- Flow:
- Attempts to retrieve a
refresh_tokenfrom the keyring. - If found, it silently refreshes the session.
If keytar is not available (like on Termux) and no environment variables are set, it checks the local home directory for a hidden session file.
- Why: Ensures the bot can still be "stable" (survive reboots) on platforms that lack a system keyring.
If all of the above fail, the bot assumes it is a fresh install.
- Action: It prints the OAuth login URL and waits for the user to complete the browser handshake. Once finished, it saves the result to whichever Tier (2 or 3) is available.
| Priority | Method | Trigger | Storage |
|---|---|---|---|
| 1 | App Password | CODESKY_... Env Vars |
None (Process only) |
| 2 | OAuth (Refresh) | No Env Vars + Keyring Present | System Keyring |
| 3 | OAuth (Refresh) | No Env Vars + Keyring Missing | ~/.bsky-session.json |
| 4 | OAuth (Login) | No previous session found | Starts new Handshake |
By adding this, you've created a "Universal" bot.
- On Windows/Linux, you'll likely use the OAuth/Keyring (Tier 2).
- In a Termux environment, you can simply run
export CODESKY_PASSWORD=...(Tier 1) and the bot will spring to life without needing a D-Bus or a browser.