stacks-ai 0.2.7 → 0.2.9

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.
@@ -1,4 +1,4 @@
1
- import{g}from"./index-BuyyCfeJ.js";import{c as h,o as y,a as m}from"./index-B7ilsstg.js";const r={},p=t=>{const e={anthropic:"anthropic-api-key",openai:"openai-api-key",google:"gemini-api-key"},a=localStorage.getItem(e[t]);return a||{anthropic:r==null?void 0:r.VITE_ANTHROPIC_API_KEY,openai:r==null?void 0:r.VITE_OPENAI_API_KEY,google:r==null?void 0:r.VITE_GEMINI_API_KEY}[t]||""},u=async t=>{const a=await(await fetch(t)).blob();return new Promise((o,l)=>{const n=new FileReader;n.onloadend=()=>{const c=n.result,[,s]=c.split(",");o({data:s,mimeType:a.type})},n.onerror=l,n.readAsDataURL(a)})},P=async(t,e)=>{const a=(e==null?void 0:e.provider)||localStorage.getItem("ai-provider")||"google",o=p(a);if(!o)throw new Error(`${a} API key not configured. Please add your API key in Settings → Providers.`);const{data:l,mimeType:n}=await u(t),c=localStorage.getItem("image-analysis-model");let s;switch(a){case"anthropic":s=m((e==null?void 0:e.model)||c||"claude-sonnet-4-5-20250929",{apiKey:o});break;case"openai":s=y((e==null?void 0:e.model)||c||"gpt-4o",{apiKey:o});break;case"google":{s=h({apiKey:o})((e==null?void 0:e.model)||c||"gemini-2.0-flash-exp");break}default:throw new Error(`Unknown provider: ${a}`)}const d=(await g({model:s,messages:[{role:"user",content:[{type:"image",image:`data:${n};base64,${l}`},{type:"text",text:`Analyze this image and provide:
1
+ import{g}from"./index-Bj1oZk28.js";import{c as h,o as y,a as m}from"./index-BeppnFcF.js";const r={},p=t=>{const e={anthropic:"anthropic-api-key",openai:"openai-api-key",google:"gemini-api-key"},a=localStorage.getItem(e[t]);return a||{anthropic:r==null?void 0:r.VITE_ANTHROPIC_API_KEY,openai:r==null?void 0:r.VITE_OPENAI_API_KEY,google:r==null?void 0:r.VITE_GEMINI_API_KEY}[t]||""},u=async t=>{const a=await(await fetch(t)).blob();return new Promise((o,l)=>{const n=new FileReader;n.onloadend=()=>{const c=n.result,[,s]=c.split(",");o({data:s,mimeType:a.type})},n.onerror=l,n.readAsDataURL(a)})},P=async(t,e)=>{const a=(e==null?void 0:e.provider)||localStorage.getItem("ai-provider")||"google",o=p(a);if(!o)throw new Error(`${a} API key not configured. Please add your API key in Settings → Providers.`);const{data:l,mimeType:n}=await u(t),c=localStorage.getItem("image-analysis-model");let s;switch(a){case"anthropic":s=m((e==null?void 0:e.model)||c||"claude-sonnet-4-5-20250929",{apiKey:o});break;case"openai":s=y((e==null?void 0:e.model)||c||"gpt-4o",{apiKey:o});break;case"google":{s=h({apiKey:o})((e==null?void 0:e.model)||c||"gemini-2.0-flash-exp");break}default:throw new Error(`Unknown provider: ${a}`)}const d=(await g({model:s,messages:[{role:"user",content:[{type:"image",image:`data:${n};base64,${l}`},{type:"text",text:`Analyze this image and provide:
2
2
  1. A one-sentence description of what you see
3
3
  2. The 6 most dominant colors as hex codes
4
4
 
package/dist/index.html CHANGED
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
6
6
  <title>Stacks</title>
7
- <script type="module" crossorigin src="./assets/index-o3S66-xQ.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-BWvx7AMq.css">
7
+ <script type="module" crossorigin src="./assets/index-BtJd96u-.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-BRmp9CJR.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root" style="width: 100%; height: 100%;"></div>
package/electron/main.cjs CHANGED
@@ -1,8 +1,9 @@
1
- const { app, BrowserWindow, nativeImage } = require('electron')
1
+ const { app, BrowserWindow, nativeImage, session } = require('electron')
2
2
  const { spawn } = require('child_process')
3
3
  const path = require('path')
4
4
  const net = require('net')
5
5
  const fs = require('fs')
6
+ const { startPtyServer, stopPtyServer } = require('./pty-server.cjs')
6
7
 
7
8
  // Set app name for macOS menu
8
9
  app.name = 'Stacks'
@@ -124,12 +125,45 @@ function createWindow() {
124
125
  webPreferences: {
125
126
  nodeIntegration: false,
126
127
  contextIsolation: true,
127
- webSecurity: !isDev // Allow local file loading in production
128
+ webSecurity: !isDev, // Allow local file loading in production
129
+ webviewTag: true // Enable webview for browser component
128
130
  },
129
131
  title: 'Stacks',
130
132
  backgroundColor: '#000000'
131
133
  })
132
134
 
135
+ // Configure CORS bypass for both default session and browser partition
136
+ const configureSession = (sess) => {
137
+ sess.webRequest.onBeforeSendHeaders((details, callback) => {
138
+ // Remove origin header to bypass CORS for embedded browser
139
+ delete details.requestHeaders['Origin']
140
+ callback({ requestHeaders: details.requestHeaders })
141
+ })
142
+
143
+ sess.webRequest.onHeadersReceived((details, callback) => {
144
+ // Remove restrictive CORS headers
145
+ const headers = { ...details.responseHeaders }
146
+ delete headers['x-frame-options']
147
+ delete headers['X-Frame-Options']
148
+ delete headers['content-security-policy']
149
+ delete headers['Content-Security-Policy']
150
+
151
+ // Allow all origins
152
+ headers['access-control-allow-origin'] = ['*']
153
+ headers['access-control-allow-methods'] = ['GET, POST, PUT, DELETE, OPTIONS']
154
+ headers['access-control-allow-headers'] = ['*']
155
+
156
+ callback({ responseHeaders: headers })
157
+ })
158
+ }
159
+
160
+ // Apply to default session (for iframes) and webview partition
161
+ configureSession(session.defaultSession)
162
+ configureSession(session.fromPartition('persist:browser'))
163
+
164
+ // Start PTY server for Ghostty terminal
165
+ startPtyServer()
166
+
133
167
  if (isDev) {
134
168
  // Development: Load from Vite server
135
169
  mainWindow.loadURL(`http://localhost:${serverPort}`)
@@ -175,6 +209,9 @@ app.on('window-all-closed', () => {
175
209
  })
176
210
 
177
211
  app.on('before-quit', () => {
212
+ // Stop PTY server
213
+ stopPtyServer()
214
+
178
215
  // Kill vite process when app quits (dev mode only)
179
216
  if (viteProcess && !viteProcess.killed) {
180
217
  console.log('Stopping Vite server...')
@@ -0,0 +1,120 @@
1
+ const WebSocket = require('ws');
2
+ const os = require('os');
3
+ const pty = require('node-pty');
4
+
5
+ const PTY_PORT = 3100;
6
+
7
+ let wss = null;
8
+ const terminals = new Map();
9
+
10
+ function getShell() {
11
+ if (process.platform === 'win32') {
12
+ return 'powershell.exe';
13
+ }
14
+ return process.env.SHELL || '/bin/zsh';
15
+ }
16
+
17
+ function startPtyServer() {
18
+ if (wss) {
19
+ console.log('[PTY] Server already running');
20
+ return;
21
+ }
22
+
23
+ wss = new WebSocket.Server({ port: PTY_PORT });
24
+ console.log(`[PTY] WebSocket server started on ws://localhost:${PTY_PORT}`);
25
+
26
+ wss.on('connection', (ws) => {
27
+ console.log('[PTY] Client connected');
28
+
29
+ // Spawn a new PTY process
30
+ const shell = getShell();
31
+ const ptyProcess = pty.spawn(shell, [], {
32
+ name: 'xterm-256color',
33
+ cols: 80,
34
+ rows: 24,
35
+ cwd: os.homedir(),
36
+ env: {
37
+ ...process.env,
38
+ TERM: 'xterm-256color',
39
+ COLORTERM: 'truecolor',
40
+ },
41
+ });
42
+
43
+ const terminalId = ptyProcess.pid.toString();
44
+ terminals.set(terminalId, { pty: ptyProcess, ws });
45
+
46
+ console.log(`[PTY] Spawned ${shell} with PID ${ptyProcess.pid}`);
47
+
48
+ // Send PTY output to WebSocket
49
+ ptyProcess.onData((data) => {
50
+ if (ws.readyState === WebSocket.OPEN) {
51
+ ws.send(data);
52
+ }
53
+ });
54
+
55
+ ptyProcess.onExit(({ exitCode, signal }) => {
56
+ console.log(`[PTY] Process ${ptyProcess.pid} exited with code ${exitCode}, signal ${signal}`);
57
+ terminals.delete(terminalId);
58
+ if (ws.readyState === WebSocket.OPEN) {
59
+ ws.close();
60
+ }
61
+ });
62
+
63
+ // Receive input from WebSocket
64
+ ws.on('message', (message) => {
65
+ const data = message.toString();
66
+
67
+ // Check for resize command
68
+ if (data.startsWith('\x1b[RESIZE:')) {
69
+ const match = data.match(/\x1b\[RESIZE:(\d+),(\d+)\]/);
70
+ if (match) {
71
+ const cols = parseInt(match[1], 10);
72
+ const rows = parseInt(match[2], 10);
73
+ ptyProcess.resize(cols, rows);
74
+ console.log(`[PTY] Resized to ${cols}x${rows}`);
75
+ }
76
+ return;
77
+ }
78
+
79
+ ptyProcess.write(data);
80
+ });
81
+
82
+ ws.on('close', () => {
83
+ console.log(`[PTY] Client disconnected, killing PID ${ptyProcess.pid}`);
84
+ ptyProcess.kill();
85
+ terminals.delete(terminalId);
86
+ });
87
+
88
+ ws.on('error', (err) => {
89
+ console.error('[PTY] WebSocket error:', err.message);
90
+ ptyProcess.kill();
91
+ terminals.delete(terminalId);
92
+ });
93
+ });
94
+
95
+ wss.on('error', (err) => {
96
+ console.error('[PTY] Server error:', err.message);
97
+ });
98
+ }
99
+
100
+ function stopPtyServer() {
101
+ if (!wss) return;
102
+
103
+ // Kill all terminal processes
104
+ terminals.forEach(({ pty }) => {
105
+ try {
106
+ pty.kill();
107
+ } catch (e) {
108
+ // Ignore
109
+ }
110
+ });
111
+ terminals.clear();
112
+
113
+ // Close WebSocket server
114
+ wss.close(() => {
115
+ console.log('[PTY] Server stopped');
116
+ });
117
+ wss = null;
118
+ }
119
+
120
+ module.exports = { startPtyServer, stopPtyServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stacks-ai",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Stacks - AI-powered infinite canvas for notes, images, and creative organization",
5
5
  "author": "Jason Kneen",
6
6
  "license": "MIT",
@@ -49,12 +49,14 @@
49
49
  "ai": "^6.0.6",
50
50
  "better-sqlite3": "^12.5.0",
51
51
  "elkjs": "0.9.3",
52
+ "ghostty-web": "^0.4.0",
52
53
  "lucide-react": "^0.562.0",
54
+ "node-pty": "^1.1.0",
53
55
  "openai": "^6.15.0",
54
56
  "react": "^19.2.3",
55
57
  "react-dom": "^19.2.3",
56
58
  "tsx": "^4.21.0",
57
- "ws": "^8.18.3",
59
+ "ws": "^8.19.0",
58
60
  "zod": "^4.3.5"
59
61
  },
60
62
  "devDependencies": {