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.
- package/dist/assets/{aiProvider-dBb0YcOs.js → aiProvider-ClVNxpA7.js} +1 -1
- package/dist/assets/ghostty-web-DvmLoe0k.js +13 -0
- package/dist/assets/index-BRmp9CJR.css +1 -0
- package/dist/assets/index-BeppnFcF.js +15 -0
- package/dist/assets/index-Bj1oZk28.js +76 -0
- package/dist/assets/index-BtJd96u-.js +5381 -0
- package/dist/assets/{visionAnalysis-CHtdqrls.js → visionAnalysis-BQvwKFWT.js} +1 -1
- package/dist/index.html +2 -2
- package/electron/main.cjs +39 -2
- package/electron/pty-server.cjs +120 -0
- package/package.json +4 -2
- package/dist/assets/index-B7ilsstg.js +0 -15
- package/dist/assets/index-BWvx7AMq.css +0 -1
- package/dist/assets/index-BuyyCfeJ.js +0 -76
- package/dist/assets/index-o3S66-xQ.js +0 -5324
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{g}from"./index-
|
|
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-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
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.
|
|
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.
|
|
59
|
+
"ws": "^8.19.0",
|
|
58
60
|
"zod": "^4.3.5"
|
|
59
61
|
},
|
|
60
62
|
"devDependencies": {
|