cortex-md 1.1.1

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.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # Cortex — Personal AI Operating System
2
+
3
+ [![GitHub Release](https://img.shields.io/github/v/release/angshuman/cortex?style=flat-square&label=Latest%20Release)](https://github.com/angshuman/cortex/releases/latest)
4
+ [![Downloads](https://img.shields.io/github/downloads/angshuman/cortex/total?style=flat-square&label=Downloads)](https://github.com/angshuman/cortex/releases/latest)
5
+ [![Build](https://img.shields.io/github/actions/workflow/status/angshuman/cortex/release.yml?style=flat-square&label=Build)](https://github.com/angshuman/cortex/actions)
6
+
7
+ A local-first personal operating system with an AI reasoner, note-taking, task management, and browser automation via MCP. Built with Node.js, TypeScript, React, and Tailwind CSS.
8
+
9
+ ---
10
+
11
+ ## Download
12
+
13
+ **[Go to the Releases page to download Cortex](https://github.com/angshuman/cortex/releases/latest)**
14
+
15
+ ### Latest Release
16
+
17
+ | Platform | Architecture | Download |
18
+ |----------|-------------|----------|
19
+ | **Windows** | x64 / ARM64 | [Cortex-Setup.exe](https://github.com/angshuman/cortex/releases/latest/download/Cortex-Setup-1.1.1.exe) |
20
+ | **macOS** | Apple Silicon | [Cortex-arm64.dmg](https://github.com/angshuman/cortex/releases/latest/download/Cortex-1.1.1-arm64.dmg) |
21
+ | **macOS** | Intel | [Cortex.dmg](https://github.com/angshuman/cortex/releases/latest/download/Cortex-1.1.1.dmg) |
22
+ | **Linux** | x64 | [Cortex.AppImage](https://github.com/angshuman/cortex/releases/latest/download/Cortex-1.1.1.AppImage) |
23
+ | **Linux** | ARM64 | [Cortex-arm64.AppImage](https://github.com/angshuman/cortex/releases/latest/download/Cortex-1.1.1-arm64.AppImage) |
24
+ | **Linux** | x64 (deb) | [cortex_amd64.deb](https://github.com/angshuman/cortex/releases/latest/download/cortex_1.1.1_amd64.deb) |
25
+ | **Linux** | ARM64 (deb) | [cortex_arm64.deb](https://github.com/angshuman/cortex/releases/latest/download/cortex_1.1.1_arm64.deb) |
26
+
27
+ > On first launch, a setup dialog asks for an API key (OpenAI, Anthropic, xAI, or Google Gemini). No environment variables needed.
28
+
29
+ ---
30
+
31
+ ## Features
32
+
33
+ - **AI Chat & Reasoning** — Converse with Claude, OpenAI, Grok, or Gemini. The agent reasons through problems, creates notes, manages tasks, and uses tools.
34
+ - **Notes** — Full markdown editor with image support (paste screenshots), folder hierarchy, inbox quick dump, and rendered preview.
35
+ - **Tasks** — Pure no-AI task management with kanban and list views, subtasks, priorities, due dates, and status tracking.
36
+ - **Search** — Unified search across notes, tasks, and chat history. Configurable for local keyword or OpenAI vector search.
37
+ - **Skills System** — Extensible tools the AI agent can use. Built-in skills for note-taking, task management, web search, and browser automation.
38
+ - **Browser Use (MCP)** — Connect a Playwright MCP server for real browser automation.
39
+ - **Multi-Vault Workspaces** — Separate vaults for work, personal, etc. Each with its own notes, tasks, and chats.
40
+ - **API Key Management** — Add, verify, and manage API keys from the UI. No env vars required.
41
+ - **Local Filesystem** — All data stored as plain JSON and Markdown files. Human-readable, navigable, portable.
42
+ - **Sync** — Point vault folders to OneDrive, Google Drive, USB, or any path for portability.
43
+ - **Desktop App** — Native Electron app with system tray, single-instance lock, and platform menus.
44
+
45
+ ## Data Structure
46
+
47
+ ```
48
+ .cortex-data/
49
+ ├── vaults/
50
+ │ ├── personal/
51
+ │ │ ├── notes/ # Markdown files + index
52
+ │ │ ├── tasks/ # tasks.json
53
+ │ │ ├── chat/ # Session files
54
+ │ │ ├── skills/ # Skill definitions
55
+ │ │ └── files/ # Uploaded assets
56
+ │ └── work/
57
+ │ └── ...
58
+ ├── vaults.json # Vault registry
59
+ └── config.json # Global settings + API keys
60
+ ```
61
+
62
+ ## Quick Start (Development)
63
+
64
+ ```bash
65
+ git clone https://github.com/angshuman/cortex.git
66
+ cd cortex
67
+ npm install
68
+ npm run dev
69
+ ```
70
+
71
+ Open [http://localhost:5000](http://localhost:5000) — the API key setup dialog will appear on first launch.
72
+
73
+ You can also set keys via environment variables:
74
+
75
+ ```bash
76
+ # macOS / Linux / Git Bash
77
+ export OPENAI_API_KEY=sk-...
78
+
79
+ # Windows PowerShell
80
+ $env:OPENAI_API_KEY = "sk-..."
81
+ ```
82
+
83
+ > **Tip:** Create a `.env` file in the project root for persistent config:
84
+ > ```
85
+ > OPENAI_API_KEY=sk-...
86
+ > CORTEX_DATA_DIR=C:\Users\you\OneDrive\cortex-data
87
+ > ```
88
+
89
+ ## Electron Desktop App
90
+
91
+ ```bash
92
+ # Dev mode — build + launch
93
+ npm run electron:dev
94
+
95
+ # Package for your platform
96
+ npm run electron:pack:win # Windows .exe installer
97
+ npm run electron:pack:mac # macOS .dmg
98
+ npm run electron:pack:linux # Linux .AppImage + .deb
99
+ ```
100
+
101
+ ## Configuration
102
+
103
+ ### Environment Variables
104
+
105
+ | Variable | Description |
106
+ |---|---|
107
+ | `ANTHROPIC_API_KEY` | Claude API key |
108
+ | `OPENAI_API_KEY` | OpenAI API key |
109
+ | `GROK_API_KEY` | Grok/xAI API key |
110
+ | `GOOGLE_API_KEY` | Google Gemini API key |
111
+ | `CORTEX_DATA_DIR` | Custom data directory (default: `.cortex-data/`) |
112
+
113
+ ### Settings UI
114
+
115
+ - **API Keys** — Add, verify, and manage provider keys from Settings > General.
116
+ - **AI Provider** — Auto-detected from first configured key. Override model per-vault.
117
+ - **Vector Search** — Local keyword or OpenAI embeddings.
118
+ - **MCP Servers** — Add Playwright or custom MCP servers for extended capabilities.
119
+ - **Agent Tuning** — Max turns, temperature, token limits, custom system prompt.
120
+ - **Data Directory** — Shown in settings. Sync vault folders for portability.
121
+
122
+ ## Skills
123
+
124
+ Built-in skills (always available):
125
+
126
+ | Skill | Tools | Description |
127
+ |---|---|---|
128
+ | **note-taker** | `create_note`, `read_note`, `list_notes`, `update_note` | Manage notes |
129
+ | **task-manager** | `create_task`, `list_tasks`, `update_task`, `complete_task` | Manage tasks |
130
+ | **web-search** | `web_search` | Search the web |
131
+ | **browser-use** | `browser_navigate`, `browser_screenshot`, `browser_click`, `browser_type`, `browser_snapshot` | Browser automation via MCP |
132
+
133
+ Add custom skills via the Skills JSON in your vault's `skills/skills.json`.
134
+
135
+ ## Tech Stack
136
+
137
+ - **Backend**: Express + TypeScript, filesystem storage, WebSocket for real-time chat
138
+ - **Frontend**: React + Vite + Tailwind CSS + shadcn/ui
139
+ - **Desktop**: Electron with system tray and native menus
140
+ - **AI**: Anthropic SDK, OpenAI SDK (also used for Grok and Gemini via compatible API)
141
+ - **Markdown**: `marked` for rendering
142
+ - **Icons**: `lucide-react`
143
+ - **Build**: Vite (client) + esbuild (server + Electron) + electron-builder (packaging)
144
+ - **CI/CD**: GitHub Actions — auto-builds Windows/macOS/Linux on tag push
145
+
146
+ ## Architecture
147
+
148
+ - No database — everything is JSON/Markdown files on disk
149
+ - AI agent has a reasoning loop with tool use (configurable depth per message)
150
+ - WebSocket streams agent events (thoughts, tool calls, results, messages) to the UI
151
+ - Skills define tools the agent can invoke, with parameters and instructions
152
+ - MCP browser support is built into the skill system
153
+ - Electron wraps the Express server + React client into a native desktop app
154
+
155
+ ## Code Signing
156
+
157
+ The binaries are currently unsigned. To enable code signing, add these GitHub repository secrets:
158
+
159
+ **Windows:**
160
+ - `WIN_CERT_P12_BASE64` — Base64-encoded .p12 certificate
161
+ - `WIN_CERT_PASSWORD` — Certificate password
162
+
163
+ **macOS:**
164
+ - `MAC_CERT_P12_BASE64` — Base64-encoded Developer ID Application .p12
165
+ - `MAC_CERT_PASSWORD` — Certificate password
166
+ - `APPLE_ID` — Apple ID for notarization
167
+ - `APPLE_APP_SPECIFIC_PASSWORD` — App-specific password
168
+ - `APPLE_TEAM_ID` — Apple Developer Team ID
169
+
170
+ ## License
171
+
172
+ MIT
package/bin/cortex.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'child_process';
3
+ import { createRequire } from 'module';
4
+ import { fileURLToPath } from 'url';
5
+ import path from 'path';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const require = createRequire(import.meta.url);
9
+
10
+ const electronPath = require('electron');
11
+ const mainPath = path.join(__dirname, '..', 'dist', 'electron', 'main.cjs');
12
+
13
+ const child = spawn(electronPath, [mainPath], {
14
+ stdio: 'inherit',
15
+ windowsHide: false,
16
+ });
17
+
18
+ child.on('close', (code) => process.exit(code ?? 0));
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // electron/main.ts
26
+ var import_electron = require("electron");
27
+ var import_path = __toESM(require("path"), 1);
28
+ var import_net = __toESM(require("net"), 1);
29
+ var gotLock = import_electron.app.requestSingleInstanceLock();
30
+ if (!gotLock) {
31
+ import_electron.app.quit();
32
+ }
33
+ var mainWindow = null;
34
+ var tray = null;
35
+ var serverPort = 0;
36
+ function getDataDir() {
37
+ if (process.env.CORTEX_DATA_DIR) {
38
+ return process.env.CORTEX_DATA_DIR;
39
+ }
40
+ return import_path.default.join(import_electron.app.getPath("userData"), ".cortex-data");
41
+ }
42
+ function findAvailablePort() {
43
+ return new Promise((resolve, reject) => {
44
+ const srv = import_net.default.createServer();
45
+ srv.listen(0, "127.0.0.1", () => {
46
+ const addr = srv.address();
47
+ if (addr && typeof addr !== "string") {
48
+ const port = addr.port;
49
+ srv.close(() => resolve(port));
50
+ } else {
51
+ reject(new Error("Could not find available port"));
52
+ }
53
+ });
54
+ srv.on("error", reject);
55
+ });
56
+ }
57
+ async function startServer(port) {
58
+ process.env.NODE_ENV = "production";
59
+ process.env.PORT = String(port);
60
+ process.env.CORTEX_DATA_DIR = getDataDir();
61
+ process.env.ELECTRON = "1";
62
+ const serverPath = import_electron.app.isPackaged ? import_path.default.join(process.resourcesPath, "server", "index.cjs") : import_path.default.join(__dirname, "..", "index.cjs");
63
+ return new Promise((resolve, reject) => {
64
+ try {
65
+ console.log(`[Cortex] Loading server from: ${serverPath}`);
66
+ require(serverPath);
67
+ console.log("[Cortex] Server module loaded, waiting for port...");
68
+ let attempts = 0;
69
+ const maxAttempts = 300;
70
+ const check = setInterval(() => {
71
+ attempts++;
72
+ const testConn = import_net.default.createConnection({ port, host: "127.0.0.1" }, () => {
73
+ testConn.end();
74
+ clearInterval(check);
75
+ resolve();
76
+ });
77
+ testConn.on("error", () => {
78
+ if (attempts >= maxAttempts) {
79
+ clearInterval(check);
80
+ reject(new Error("Server did not start within 30 seconds"));
81
+ }
82
+ });
83
+ }, 100);
84
+ } catch (err) {
85
+ console.error("[Cortex] Failed to load server module:", err);
86
+ reject(err);
87
+ }
88
+ });
89
+ }
90
+ function createWindow() {
91
+ mainWindow = new import_electron.BrowserWindow({
92
+ width: 1280,
93
+ height: 800,
94
+ minWidth: 900,
95
+ minHeight: 600,
96
+ title: "Cortex",
97
+ icon: getIconPath(),
98
+ backgroundColor: "#0e1117",
99
+ show: false,
100
+ webPreferences: {
101
+ preload: import_path.default.join(__dirname, "preload.cjs"),
102
+ nodeIntegration: false,
103
+ contextIsolation: true,
104
+ spellcheck: true
105
+ }
106
+ });
107
+ mainWindow.loadURL(`http://127.0.0.1:${serverPort}`);
108
+ mainWindow.once("ready-to-show", () => {
109
+ mainWindow?.show();
110
+ });
111
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
112
+ if (url.startsWith("http://127.0.0.1") || url.startsWith("http://localhost")) {
113
+ return { action: "allow" };
114
+ }
115
+ import_electron.shell.openExternal(url);
116
+ return { action: "deny" };
117
+ });
118
+ mainWindow.on("close", (event) => {
119
+ if (process.platform === "darwin" && tray) {
120
+ event.preventDefault();
121
+ mainWindow?.hide();
122
+ }
123
+ });
124
+ mainWindow.on("closed", () => {
125
+ mainWindow = null;
126
+ });
127
+ }
128
+ function getIconPath() {
129
+ if (import_electron.app.isPackaged) {
130
+ return import_path.default.join(process.resourcesPath, "icons", "icon.png");
131
+ }
132
+ const iconPath = import_path.default.join(__dirname, "..", "..", "electron", "icons", "icon.png");
133
+ return iconPath;
134
+ }
135
+ function createTray() {
136
+ const iconPath = getIconPath();
137
+ let trayIcon;
138
+ try {
139
+ trayIcon = import_electron.nativeImage.createFromPath(iconPath);
140
+ if (trayIcon.isEmpty()) {
141
+ trayIcon = import_electron.nativeImage.createEmpty();
142
+ }
143
+ } catch {
144
+ trayIcon = import_electron.nativeImage.createEmpty();
145
+ }
146
+ if (!trayIcon.isEmpty()) {
147
+ trayIcon = trayIcon.resize({ width: 16, height: 16 });
148
+ }
149
+ tray = new import_electron.Tray(trayIcon);
150
+ tray.setToolTip("Cortex");
151
+ const contextMenu = import_electron.Menu.buildFromTemplate([
152
+ {
153
+ label: "Open Cortex",
154
+ click: () => {
155
+ if (mainWindow) {
156
+ mainWindow.show();
157
+ mainWindow.focus();
158
+ } else {
159
+ createWindow();
160
+ }
161
+ }
162
+ },
163
+ { type: "separator" },
164
+ {
165
+ label: `Data: ${getDataDir()}`,
166
+ enabled: false
167
+ },
168
+ {
169
+ label: "Open Data Folder",
170
+ click: () => {
171
+ import_electron.shell.openPath(getDataDir());
172
+ }
173
+ },
174
+ { type: "separator" },
175
+ {
176
+ label: "Quit Cortex",
177
+ click: () => {
178
+ import_electron.app.quit();
179
+ }
180
+ }
181
+ ]);
182
+ tray.setContextMenu(contextMenu);
183
+ tray.on("double-click", () => {
184
+ if (mainWindow) {
185
+ mainWindow.show();
186
+ mainWindow.focus();
187
+ }
188
+ });
189
+ }
190
+ function setupMenu() {
191
+ const template = [
192
+ {
193
+ label: import_electron.app.name,
194
+ submenu: [
195
+ { role: "about" },
196
+ { type: "separator" },
197
+ {
198
+ label: "Open Data Folder",
199
+ click: () => import_electron.shell.openPath(getDataDir())
200
+ },
201
+ { type: "separator" },
202
+ { role: "hide" },
203
+ { role: "hideOthers" },
204
+ { role: "unhide" },
205
+ { type: "separator" },
206
+ { role: "quit" }
207
+ ]
208
+ },
209
+ {
210
+ label: "Edit",
211
+ submenu: [
212
+ { role: "undo" },
213
+ { role: "redo" },
214
+ { type: "separator" },
215
+ { role: "cut" },
216
+ { role: "copy" },
217
+ { role: "paste" },
218
+ { role: "selectAll" }
219
+ ]
220
+ },
221
+ {
222
+ label: "View",
223
+ submenu: [
224
+ { role: "reload" },
225
+ { role: "forceReload" },
226
+ { role: "toggleDevTools" },
227
+ { type: "separator" },
228
+ { role: "resetZoom" },
229
+ { role: "zoomIn" },
230
+ { role: "zoomOut" },
231
+ { type: "separator" },
232
+ { role: "togglefullscreen" }
233
+ ]
234
+ },
235
+ {
236
+ label: "Window",
237
+ submenu: [
238
+ { role: "minimize" },
239
+ { role: "zoom" },
240
+ { type: "separator" },
241
+ { role: "front" }
242
+ ]
243
+ }
244
+ ];
245
+ import_electron.Menu.setApplicationMenu(import_electron.Menu.buildFromTemplate(template));
246
+ }
247
+ import_electron.app.on("ready", async () => {
248
+ import_electron.ipcMain.handle("open-folder", async (_event, folderPath) => {
249
+ if (folderPath && typeof folderPath === "string") {
250
+ return import_electron.shell.openPath(folderPath);
251
+ }
252
+ return "Invalid path";
253
+ });
254
+ try {
255
+ serverPort = await findAvailablePort();
256
+ console.log(`[Cortex] Starting server on port ${serverPort}...`);
257
+ console.log(`[Cortex] Data directory: ${getDataDir()}`);
258
+ await startServer(serverPort);
259
+ console.log(`[Cortex] Server ready at http://127.0.0.1:${serverPort}`);
260
+ setupMenu();
261
+ createTray();
262
+ createWindow();
263
+ } catch (err) {
264
+ console.error("[Cortex] Failed to start:", err);
265
+ import_electron.dialog.showErrorBox(
266
+ "Cortex failed to start",
267
+ `Error: ${err instanceof Error ? err.message : String(err)}
268
+
269
+ Please check your configuration and try again.`
270
+ );
271
+ import_electron.app.quit();
272
+ }
273
+ });
274
+ import_electron.app.on("second-instance", () => {
275
+ if (mainWindow) {
276
+ if (mainWindow.isMinimized()) mainWindow.restore();
277
+ mainWindow.show();
278
+ mainWindow.focus();
279
+ }
280
+ });
281
+ import_electron.app.on("window-all-closed", () => {
282
+ if (process.platform !== "darwin") {
283
+ import_electron.app.quit();
284
+ }
285
+ });
286
+ import_electron.app.on("activate", () => {
287
+ if (mainWindow === null) {
288
+ createWindow();
289
+ } else {
290
+ mainWindow.show();
291
+ }
292
+ });
293
+ import_electron.app.on("before-quit", () => {
294
+ if (mainWindow) {
295
+ mainWindow.removeAllListeners("close");
296
+ mainWindow.close();
297
+ }
298
+ if (tray) {
299
+ tray.destroy();
300
+ tray = null;
301
+ }
302
+ });
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ // electron/preload.ts
4
+ var import_electron = require("electron");
5
+ import_electron.contextBridge.exposeInMainWorld("cortexDesktop", {
6
+ platform: process.platform,
7
+ isElectron: true,
8
+ versions: {
9
+ electron: process.versions.electron,
10
+ node: process.versions.node,
11
+ chrome: process.versions.chrome
12
+ },
13
+ /** Open a folder in the native file manager (Explorer / Finder) */
14
+ openFolder: (folderPath) => import_electron.ipcRenderer.invoke("open-folder", folderPath)
15
+ });
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <rect width="512" height="512" rx="108" fill="#111318"/>
3
+ <g fill="none" stroke="#e5e7eb" stroke-linecap="round" stroke-linejoin="round">
4
+ <!-- C arc -->
5
+ <path d="M 320,126 C 270,90 210,80 165,100 C 100,128 66,190 66,256 C 66,322 100,384 165,412 C 210,432 270,422 320,386" stroke-width="36"/>
6
+ <!-- Three connector lines from the C opening -->
7
+ <line x1="320" y1="126" x2="380" y2="126" stroke-width="28"/>
8
+ <line x1="345" y1="256" x2="405" y2="256" stroke-width="28"/>
9
+ <line x1="320" y1="386" x2="380" y2="386" stroke-width="28"/>
10
+ </g>
11
+ <!-- Terminal dots -->
12
+ <circle cx="388" cy="126" r="18" fill="#e5e7eb"/>
13
+ <circle cx="413" cy="256" r="18" fill="#e5e7eb"/>
14
+ <circle cx="388" cy="386" r="18" fill="#e5e7eb"/>
15
+ </svg>