otherwise-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +193 -0
  2. package/bin/otherwise.js +5 -0
  3. package/frontend/404.html +84 -0
  4. package/frontend/assets/OpenDyslexic3-Bold-CDyRs55Y.ttf +0 -0
  5. package/frontend/assets/OpenDyslexic3-Regular-CIBXa4WE.ttf +0 -0
  6. package/frontend/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  7. package/frontend/assets/conversational-worker-CeKiciGk.js +2929 -0
  8. package/frontend/assets/dictation-worker-D0aYfq8b.js +29 -0
  9. package/frontend/assets/gemini-color-CgSQmmva.png +0 -0
  10. package/frontend/assets/index-BLux5ps4.js +21 -0
  11. package/frontend/assets/index-Blh8_TEM.js +5272 -0
  12. package/frontend/assets/index-BpQ1PuKu.js +18 -0
  13. package/frontend/assets/index-Df737c8w.css +1 -0
  14. package/frontend/assets/index-xaYHL6wb.js +113 -0
  15. package/frontend/assets/ort-wasm-simd-threaded.asyncify-BynIiDiv.wasm +0 -0
  16. package/frontend/assets/ort-wasm-simd-threaded.jsep-B0T3yYHD.wasm +0 -0
  17. package/frontend/assets/transformers-tULNc5V3.js +31 -0
  18. package/frontend/assets/tts-worker-DPJWqT7N.js +2899 -0
  19. package/frontend/assets/voice-mode-worker-GzvIE_uh.js +2927 -0
  20. package/frontend/assets/worker-2d5ABSLU.js +31 -0
  21. package/frontend/banner.png +0 -0
  22. package/frontend/favicon.svg +3 -0
  23. package/frontend/google55e5ec47ee14a5f8.html +1 -0
  24. package/frontend/index.html +234 -0
  25. package/frontend/manifest.json +17 -0
  26. package/frontend/pdf.worker.min.mjs +21 -0
  27. package/frontend/robots.txt +5 -0
  28. package/frontend/sitemap.xml +27 -0
  29. package/package.json +81 -0
  30. package/src/agent/index.js +1066 -0
  31. package/src/agent/location.js +51 -0
  32. package/src/agent/prompt.js +548 -0
  33. package/src/agent/tools.js +4372 -0
  34. package/src/browser/detect.js +68 -0
  35. package/src/browser/session.js +1109 -0
  36. package/src/config.js +137 -0
  37. package/src/email/client.js +503 -0
  38. package/src/index.js +557 -0
  39. package/src/inference/anthropic.js +113 -0
  40. package/src/inference/google.js +373 -0
  41. package/src/inference/index.js +81 -0
  42. package/src/inference/ollama.js +383 -0
  43. package/src/inference/openai.js +140 -0
  44. package/src/inference/openrouter.js +378 -0
  45. package/src/inference/xai.js +200 -0
  46. package/src/logBridge.js +9 -0
  47. package/src/models.js +146 -0
  48. package/src/remote/client.js +225 -0
  49. package/src/scheduler/cron.js +243 -0
  50. package/src/server.js +3876 -0
  51. package/src/storage/db.js +1135 -0
  52. package/src/storage/supabase.js +364 -0
  53. package/src/tunnel/cloudflare.js +241 -0
  54. package/src/ui/components/App.jsx +687 -0
  55. package/src/ui/components/BrowserSelect.jsx +111 -0
  56. package/src/ui/components/FilePicker.jsx +472 -0
  57. package/src/ui/components/Header.jsx +444 -0
  58. package/src/ui/components/HelpPanel.jsx +173 -0
  59. package/src/ui/components/HistoryPanel.jsx +158 -0
  60. package/src/ui/components/MessageList.jsx +235 -0
  61. package/src/ui/components/ModelSelector.jsx +304 -0
  62. package/src/ui/components/PromptInput.jsx +515 -0
  63. package/src/ui/components/StreamingResponse.jsx +134 -0
  64. package/src/ui/components/ThinkingIndicator.jsx +365 -0
  65. package/src/ui/components/ToolExecution.jsx +714 -0
  66. package/src/ui/components/index.js +82 -0
  67. package/src/ui/context/TerminalContext.jsx +150 -0
  68. package/src/ui/context/index.js +13 -0
  69. package/src/ui/hooks/index.js +16 -0
  70. package/src/ui/hooks/useChatState.js +675 -0
  71. package/src/ui/hooks/useCommands.js +280 -0
  72. package/src/ui/hooks/useFileAttachments.js +216 -0
  73. package/src/ui/hooks/useKeyboardShortcuts.js +173 -0
  74. package/src/ui/hooks/useNotifications.js +185 -0
  75. package/src/ui/hooks/useTerminalSize.js +151 -0
  76. package/src/ui/hooks/useWebSocket.js +273 -0
  77. package/src/ui/index.js +94 -0
  78. package/src/ui/ink-runner.js +22 -0
  79. package/src/ui/utils/formatters.js +424 -0
  80. package/src/ui/utils/index.js +6 -0
  81. package/src/ui/utils/markdown.js +166 -0
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # Otherwise CLI
2
+
3
+ Your personal AI assistant that lives on your computer.
4
+
5
+ ## Installation
6
+
7
+ **From npm (recommended)**
8
+
9
+ ```bash
10
+ npm install -g otherwise-cli
11
+ otherwise
12
+ ```
13
+
14
+ **From source**
15
+
16
+ ```bash
17
+ cd cli
18
+ npm install
19
+ npm run build:frontend # build web UI (required before first run)
20
+ npm link # makes 'otherwise' available globally
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Start the server
26
+
27
+ ```bash
28
+ otherwise # Start server + Ink terminal UI
29
+ otherwise --verbose # Start server, no Ink UI — raw logs in terminal, chat in browser
30
+ otherwise -server-only # Start server only (no CLI, no logs)
31
+ otherwise -port 8080 # Use different port
32
+ ```
33
+
34
+ ### Configure API keys
35
+
36
+ ```bash
37
+ otherwise config # Show current config
38
+ otherwise config set anthropic sk-... # Set Anthropic API key
39
+ otherwise config set openai sk-... # Set OpenAI API key
40
+ otherwise config set google ... # Set Google API key
41
+ otherwise config set xai ... # Set xAI API key
42
+ otherwise config set model gpt-4o # Change default model
43
+ ```
44
+
45
+ ### Deploy publicly
46
+
47
+ ```bash
48
+ otherwise deploy -quick # Quick tunnel (temporary URL)
49
+ otherwise deploy # Named tunnel (custom domain)
50
+ ```
51
+
52
+ ### Connect to otherwise.ai (remote)
53
+
54
+ Link this CLI to your otherwise.ai account so the web app routes chat to this machine:
55
+
56
+ ```bash
57
+ otherwise connect # Device code flow: enter short code at otherwise.ai/connect
58
+ otherwise connect <token> # Or paste a pairing token from Settings → Connect your CLI
59
+ ```
60
+
61
+ **Chat persistence:** When the frontend is connected to the backend (otherwise.ai), chat list and history are read from Supabase. For new chats created on this CLI to appear in the cloud and survive refresh, set Supabase env vars on the machine running the CLI:
62
+
63
+ - `SUPABASE_URL` – your project URL
64
+ - `SUPABASE_SERVICE_KEY` – service role key (backend only)
65
+
66
+ If these are not set, the CLI will log: _"Frontend sent accessToken but Supabase is not configured..."_ and new chats will only exist locally (they disappear from the web app on refresh).
67
+
68
+ ## Features
69
+
70
+ - **Local AI Server** - Runs on your computer with access to your filesystem
71
+ - **Multiple Providers** - Claude, GPT, Gemini, Grok, Ollama
72
+ - **Agent Tools** - File read/write, shell commands, web search
73
+ - **Email Integration** - IMAP/SMTP for reading and sending email
74
+ - **Scheduled Tasks** - Cron-based task scheduling
75
+ - **Public Access** - Cloudflare Tunnel for custom domain hosting
76
+
77
+ ## Project Structure
78
+
79
+ ```
80
+ cli/
81
+ ├── bin/otherwise.js # CLI entry point
82
+ ├── src/
83
+ │ ├── index.js # Commander CLI
84
+ │ ├── server.js # Fastify server
85
+ │ ├── config.js # Configuration store
86
+ │ ├── inference/ # LLM providers
87
+ │ ├── agent/ # Agent loop & tools
88
+ │ ├── storage/ # SQLite database
89
+ │ ├── scheduler/ # Cron tasks
90
+ │ ├── email/ # IMAP/SMTP
91
+ │ └── tunnel/ # Cloudflare
92
+ └── frontend/ # Built React app (after build)
93
+ ```
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ # Build frontend and copy to CLI
99
+ npm run build:frontend
100
+
101
+ # Run in development mode
102
+ npm run dev
103
+ ```
104
+
105
+ ## Configuration
106
+
107
+ Config is stored at `~/.otherwise/config.json`:
108
+
109
+ ```json
110
+ {
111
+ "apiKeys": {
112
+ "anthropic": "sk-...",
113
+ "openai": "sk-...",
114
+ "google": "...",
115
+ "xai": "..."
116
+ },
117
+ "model": "claude-sonnet-4-20250514",
118
+ "permissions": {
119
+ "fileRead": ["~/*"],
120
+ "fileWrite": ["~/ai-workspace/*"],
121
+ "shell": true,
122
+ "email": true
123
+ }
124
+ }
125
+ ```
126
+
127
+ ## Agent Tools
128
+
129
+ The AI can use these tools during conversations:
130
+
131
+ | Tool | Description |
132
+ | ---------------------- | ------------------------- |
133
+ | `read_file` | Read file contents |
134
+ | `write_file` | Create or update files |
135
+ | `list_directory` | List files in a directory |
136
+ | `search_files` | Search for files by name |
137
+ | `execute_command` | Run shell commands |
138
+ | `web_search` | Search the web |
139
+ | `fetch_url` | Fetch content from a URL |
140
+ | `check_email` | Check inbox (IMAP) |
141
+ | `send_email` | Send email (SMTP) |
142
+ | `schedule_task` | Create a scheduled task |
143
+ | `list_scheduled_tasks` | View scheduled tasks |
144
+ | `cancel_task` | Cancel a scheduled task |
145
+
146
+ ### Debugging web search
147
+
148
+ When an engine returns 0 results, the CLI logs pattern counts and **the first 2,500 characters of the response HTML** so you can see what was actually returned. Run with **verbose** to see these logs:
149
+
150
+ ```bash
151
+ otherwise -verbose
152
+ ```
153
+
154
+ To write the **full HTML** for failed engines to a temp file (e.g. `otherwise-websearch-google-1234567890.html`), set:
155
+
156
+ ```bash
157
+ WEBSEARCH_DEBUG=1 otherwise -verbose
158
+ ```
159
+
160
+ Google and Bing requests use crawler User-Agents (AdsBot-Google and bingbot) to reduce bot detection and improve the chance of receiving static SERP HTML instead of captchas or JS-only pages.
161
+
162
+ When an engine returns **0 results**, the CLI automatically retries that engine using a **headless browser** (Playwright) so JavaScript-rendered or bot-blocked pages (e.g. Google, Ecosia/Cloudflare) can be parsed. To disable this fallback (e.g. to avoid launching Chromium), set:
163
+
164
+ ```bash
165
+ WEBSEARCH_USE_BROWSER=0 otherwise -verbose
166
+ ```
167
+
168
+ ### Proxy for Google and Bing
169
+
170
+ To reduce captcha/blocking from Google and Bing, you can route **all web search traffic** (initial fetch and browser retry) through a proxy:
171
+
172
+ ```bash
173
+ # HTTP/HTTPS proxy (used for every search engine request and headless browser)
174
+ export WEBSEARCH_PROXY=http://proxy.example.com:8080
175
+ # Or use standard env vars (same behavior)
176
+ export HTTPS_PROXY=http://proxy.example.com:8080
177
+ # With auth:
178
+ export WEBSEARCH_PROXY=http://user:pass@proxy.example.com:8080
179
+ otherwise -verbose
180
+ ```
181
+
182
+ Only **web search** requests use this proxy; other tools (e.g. `fetch_url`, API calls) are unchanged.
183
+
184
+ ### SerpAPI (optional) for Google and Bing
185
+
186
+ For reliable Google and Bing results without captcha or scraping, set **SerpAPI** (free tier: 100 searches/month):
187
+
188
+ ```bash
189
+ export SERPAPI_API_KEY=your_serpapi_key
190
+ otherwise
191
+ ```
192
+
193
+ When `SERPAPI_API_KEY` is set, Google and Bing use SerpAPI’s JSON API instead of scraping. Other engines (Brave, DuckDuckGo, Startpage, Yahoo, Ecosia) still use direct fetch/browser. Get a key at [serpapi.com](https://serpapi.com/).
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from '../src/index.js';
4
+
5
+ run();
@@ -0,0 +1,84 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>404 - Page Not Found | Otherwise</title>
7
+ <meta name="robots" content="noindex, follow" />
8
+ <meta
9
+ name="description"
10
+ content="The page you're looking for doesn't exist. Return to Otherwise homepage."
11
+ />
12
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
13
+ <link rel="alternate icon" href="/favicon.ico" />
14
+
15
+ <style media="screen">
16
+ body {
17
+ background: #111827;
18
+ color: #e5e7eb;
19
+ font-family:
20
+ ui-sans-serif,
21
+ system-ui,
22
+ -apple-system,
23
+ BlinkMacSystemFont,
24
+ 'Segoe UI',
25
+ Roboto,
26
+ 'Helvetica Neue',
27
+ Arial,
28
+ 'Noto Sans',
29
+ sans-serif,
30
+ 'Apple Color Emoji',
31
+ 'Segoe UI Emoji',
32
+ 'Segoe UI Symbol',
33
+ 'Noto Color Emoji';
34
+ margin: 0;
35
+ padding: 0;
36
+ display: flex;
37
+ flex-direction: column;
38
+ justify-content: center;
39
+ align-items: center;
40
+ height: 100vh;
41
+ text-align: center;
42
+ }
43
+ pre {
44
+ font-family:
45
+ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
46
+ 'Liberation Mono', 'Courier New', monospace;
47
+ font-size: 0.875rem;
48
+ line-height: 1.25rem;
49
+ white-space: pre;
50
+ }
51
+ h1 {
52
+ font-size: 1.875rem; /* 30px */
53
+ line-height: 2.25rem; /* 36px */
54
+ font-weight: 600;
55
+ margin-top: 2rem;
56
+ margin-bottom: 1rem;
57
+ }
58
+ h2 {
59
+ font-size: 1.125rem; /* 18px */
60
+ line-height: 1.75rem; /* 28px */
61
+ font-weight: 400;
62
+ margin-bottom: 2rem;
63
+ }
64
+ a {
65
+ display: inline-block;
66
+ background: #4f46e5;
67
+ color: white;
68
+ padding: 0.75rem 1.5rem;
69
+ border-radius: 0.5rem;
70
+ text-decoration: none;
71
+ font-weight: 600;
72
+ transition: background-color 0.2s;
73
+ }
74
+ a:hover {
75
+ background: #4338ca;
76
+ }
77
+ </style>
78
+ </head>
79
+ <body>
80
+ <h1>404 - Page Not Found</h1>
81
+ <h2>Looks like you're lost in the digital wilderness.</h2>
82
+ <a href="/">Go back home</a>
83
+ </body>
84
+ </html>
@@ -0,0 +1 @@
1
+ const e={};export{e as default};