Skip to content
Open
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
91 changes: 91 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,23 @@ FISH_API_KEY=your-fish-audio-api-key-here
# Optional: which model the brain runs (default sonnet; always passed explicitly)
# JARVIS_BRAIN_MODEL=sonnet

# Optional: while Claude's usage limit holds, ChatGPT stands in (the Codex
# CLI, on your ChatGPT subscription) through the same tools and gates, and
# JARVIS goes back to Claude when the limit resets. Off unless set: switched
# on, the conversation goes to OpenAI. Sign Codex in for JARVIS first with
# `python scripts/chatgpt_setup.py login`. See docs/chatgpt-fallback.md.
# JARVIS_CHATGPT_FALLBACK=1
# JARVIS_CHATGPT_MODEL=gpt-5.5
# JARVIS_CODEX_PATH=C:\\path\\to\\codex.exe

# Optional: Fish Audio voice model (defaults to JARVIS MCU voice)
# FISH_VOICE_ID=612b878b113047d9a770c069c8b4fdfe

# Optional: which Fish Audio TTS model speaks. The default is the service's
# own (s2.1-pro), which is billed per character and answers 402 on an
# account with no credit. s2.1-pro-free is the free tier.
# FISH_MODEL=s2.1-pro-free

# Optional: Your name (JARVIS will address you by name)
# USER_NAME=Tony

Expand All @@ -29,3 +43,80 @@ FISH_API_KEY=your-fish-audio-api-key-here
# WEATHER_LATITUDE=51.5072
# WEATHER_LONGITUDE=-0.1276
# WEATHER_UNIT=fahrenheit # or "celsius"

# Optional: give JARVIS a Telegram bot so he can reach you when you are away
# from the desk — approval cards with Approve/Reject buttons, sessions waiting
# on you, failed or finished work (the urgent ones as a voice note in his
# voice) — and so you can text him back. The easier of the two lines: no
# phone number, no Facebook, no 24-hour window. In Telegram open @BotFather,
# send /newbot, paste the token here (or under Settings → Telegram), then
# press Pair there (or `python scripts/telegram_setup.py pair`) and send the
# code to the bot from your phone: that fills in TELEGRAM_OWNER_ID. Calls are
# NOT available (voice notes are); see docs/telegram.md.
# TELEGRAM_BOT_TOKEN=123456789:AAHxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# TELEGRAM_OWNER_ID=424242001
# Let a button tap or "approve" on the phone decide a card (default on); send
# the urgent lines as a voice note too (default on).
# TELEGRAM_APPROVALS=1
# TELEGRAM_VOICE_NOTES=1

# Optional: give JARVIS a WhatsApp number so he can reach you when you are
# away from the desk — approval cards with Approve/Reject buttons, sessions
# waiting on you, failed or finished work (the urgent ones as a voice note
# in his voice) — and so you can text him back. The number comes from Kapso
# (kapso.ai; the free plan includes a pre-verified US number and 2,000
# messages a month). `python scripts/whatsapp_setup.py numbers` prints the
# phone number id once the key is set; see docs/whatsapp.md for the rest.
# Calls are NOT available (voice notes are); see docs/whatsapp.md for why.
# KAPSO_API_KEY=your-kapso-api-key
# WHATSAPP_PHONE_NUMBER_ID=123456789012345
# Your own WhatsApp number, E.164. Falls back to JARVIS_OWNER_PHONE (the
# number the Twilio provider calls), so one line can serve both.
# WHATSAPP_OWNER_NUMBER=+14155550132
# An approved UTILITY template with one named body parameter `message`, used
# when more than 24 hours have passed since your last message to him and
# WhatsApp will not accept free-form text. `python scripts/whatsapp_setup.py
# template` creates one called jarvis_attention and tells you when Meta has
# approved it. Without it he cannot reach you outside that window.
# WHATSAPP_TEMPLATE=jarvis_attention
# WHATSAPP_TEMPLATE_LANGUAGE=en_US
# Read your messages at all (default on); let a button tap or "approve" on
# the phone decide a card (default on); send the urgent lines as a voice note
# too (default on; each one is a Fish Audio synthesis).
# WHATSAPP_INBOUND=1
# WHATSAPP_APPROVALS=1
# WHATSAPP_VOICE_NOTES=1
# How often he reads the line: quickly for ten minutes after either of you
# has written, slowly otherwise. Kapso's free plan logs 100,000 API requests
# a month, which the defaults stay well under.
# WHATSAPP_POLL_HOT_SECONDS=3
# WHATSAPP_POLL_IDLE_SECONDS=20
# Only if he ignores your messages: some countries' WhatsApp ids differ from
# the E.164 number (Mexico, Argentina). The log names the number he ignored.
# WHATSAPP_OWNER_WA_ID=5215512345678

# How long a turn may be SILENT before the brain is judged stuck (seconds).
# Not how long a turn may TAKE: one waiting on a tool is alive.
# JARVIS_BRAIN_TURN_TIMEOUT=90
# The backstop under that hold: the most a whole turn may take, ever.
# JARVIS_BRAIN_TURN_CEILING=300
# How much CONVERSATION (tokens, over the resident floor of prompt and tool
# schemas) the brain holds before it hands over to a fresh generation at the
# next pause. Capped automatically so the floor plus this much stays within
# three quarters of the model's window (never below 20,000 on that account).
# See "Context rotation" in the README.
# JARVIS_BRAIN_CONTEXT_BUDGET=120000

# LinkedIn's official API (docs/linkedin.md). Your own app's credentials; you
# sign in yourself from the Business desk, and the token stays in the data folder.
# LINKEDIN_CLIENT_ID=
# LINKEDIN_CLIENT_SECRET=
# LINKEDIN_MEDIA_ROOTS=C:\dev\linkedin-media
# The company page, once LinkedIn grants its app the Community Management API:
# LINKEDIN_ORG_CLIENT_ID=
# LINKEDIN_ORG_CLIENT_SECRET=
# LINKEDIN_ORGANIZATION_ID=
# Interim limits; raise them yourself once the API route has run cleanly.
# LINKEDIN_POSTS_PER_DAY=1
# LINKEDIN_COMMENTS_PER_DAY=5
# LINKEDIN_MIN_POST_GAP_HOURS=6
18 changes: 18 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# One line ending in the repository and on every checkout. The index has
# always been LF; with core.autocrlf=true and no attributes, a Windows
# checkout rewrote every touched file to CRLF on the way back and git
# warned about each one, while macOS CI checked out different bytes.
* text=auto eol=lf

# Binary content: never normalised, never diffed as text.
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.woff binary
*.woff2 binary
*.mp3 binary
*.wav binary
*.zip binary
*.pdf binary
49 changes: 38 additions & 11 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# Run the suite on every push and pull request.
#
# macOS, not Ubuntu, on purpose: JARVIS is a macOS-only project, and while
# every macOS-specific subprocess (osascript, screencapture, sips, claude) is
# faked at the seam in tests, the environment the suite was written against
# is a Mac. A green run here means what a green run on the author's machine
# means. Ubuntu would be cheaper and would also be a second environment
# nobody develops on.
# Both supported server platforms are checked, including the frontend tests.
# Desktop-specific subprocesses are faked at the test seam.
#
# The suite needs: Python 3.11+ (the code uses `X | None` at runtime; 3.9,
# The suite supports Python 3.12/3.13 (the code uses `X | None` at runtime; 3.9,
# which macOS ships as `python3`, dies at import), Node for the frontend
# build that tests/test_dashboard_page.py drives, and Playwright's Chromium
# for the same test plus read_page / look_at_page. It does NOT need a Fish
Expand All @@ -22,15 +18,23 @@ on:

jobs:
pytest:
runs-on: macos-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
python: ["3.12", "3.13"]
runs-on: ${{ matrix.os }}
# A hang guard, not a budget. The suite takes ~10 minutes on macOS but
# ~28 on the Windows runner — no one test, just ~0.4 s apiece of process
# and ACL work across 4,400 — so 25 there cancelled the suite at 98%.
timeout-minutes: ${{ matrix.os == 'windows-latest' && 45 || 25 }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: ${{ matrix.python }}
cache: pip

- uses: actions/setup-node@v4
Expand All @@ -39,8 +43,27 @@ jobs:
cache: npm
cache-dependency-path: frontend/package-lock.json

# Into a clean virtualenv, as docs/dependency-upgrades.md says: the lock
# check below compares EVERY installed distribution with constraints.txt,
# and the runner's own Python is not the project's alone (the Windows
# image ships pipx and its dependencies in it). Every later step finds
# the venv's python first on PATH.
- name: Project virtualenv
shell: bash
env:
VENV: ${{ runner.temp }}/venv
run: |
python -m venv "$VENV"
if [ "$RUNNER_OS" = "Windows" ]; then echo "$VENV/Scripts"; else echo "$VENV/bin"; fi >> "$GITHUB_PATH"
echo "VIRTUAL_ENV=$VENV" >> "$GITHUB_ENV"

- name: Python dependencies
run: pip install -r requirements.txt
run: python -m pip install -r requirements-dev.txt -c constraints.txt

- name: Verify dependency constraints
run: |
python -m pip check
python scripts/lock_dependencies.py

- name: Playwright Chromium
run: python -m playwright install chromium
Expand All @@ -53,6 +76,10 @@ jobs:
run: npx tsc --noEmit
working-directory: frontend

- name: Frontend tests
run: npm test
working-directory: frontend

- name: Frontend build
run: npm run build
working-directory: frontend
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Environment
.env
.env.local
.env.*
!.env.example

# Dependencies
node_modules/
Expand All @@ -18,6 +20,11 @@ __pycache__/
data/*.jsonl
data/active_session.json
data/.jarvis_output.txt
# Runtime lock held by maintenance.py while the server owns the data dir
*.runtime.lock
# Local launcher logs
.jarvis-open.stdout.log
.jarvis-open.stderr.log

# Certificates
*.pem
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
Loading