stacks-ai 0.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.
@@ -0,0 +1,183 @@
1
+ const { app, BrowserWindow } = require('electron')
2
+ const { spawn } = require('child_process')
3
+ const path = require('path')
4
+ const net = require('net')
5
+ const fs = require('fs')
6
+
7
+ const isDev = process.env.NODE_ENV === 'development'
8
+
9
+ let mainWindow = null
10
+ let viteProcess = null
11
+ let serverPort = null
12
+
13
+ // Find an available port
14
+ function findAvailablePort(startPort) {
15
+ if (!startPort) startPort = 3000
16
+ return new Promise((resolve, reject) => {
17
+ const server = net.createServer()
18
+
19
+ server.listen(startPort, () => {
20
+ const port = server.address().port
21
+ server.close(() => resolve(port))
22
+ })
23
+
24
+ server.on('error', (err) => {
25
+ if (err.code === 'EADDRINUSE') {
26
+ findAvailablePort(startPort + 1).then(resolve).catch(reject)
27
+ } else {
28
+ reject(err)
29
+ }
30
+ })
31
+ })
32
+ }
33
+
34
+ // Wait for server to be ready
35
+ function waitForServer(port, maxAttempts) {
36
+ if (!maxAttempts) maxAttempts = 30
37
+
38
+ function attempt(i) {
39
+ if (i >= maxAttempts) return Promise.resolve(false)
40
+
41
+ return new Promise((resolve, reject) => {
42
+ const socket = net.createConnection(port, 'localhost')
43
+ socket.on('connect', () => {
44
+ socket.destroy()
45
+ resolve(true)
46
+ })
47
+ socket.on('error', () => {
48
+ setTimeout(() => {
49
+ attempt(i + 1).then(resolve).catch(reject)
50
+ }, 1000)
51
+ })
52
+ })
53
+ }
54
+
55
+ return attempt(0)
56
+ }
57
+
58
+ // Start Vite dev server
59
+ function startViteServer() {
60
+ return findAvailablePort(3000).then((port) => {
61
+ serverPort = port
62
+ console.log(`Starting Vite server on port ${serverPort}...`)
63
+
64
+ const appDir = path.join(__dirname, '..')
65
+
66
+ return new Promise((resolve, reject) => {
67
+ // Start vite server
68
+ viteProcess = spawn('npx', ['vite', '--port', serverPort.toString(), '--host', '0.0.0.0'], {
69
+ cwd: appDir,
70
+ stdio: 'pipe',
71
+ env: {
72
+ ...process.env,
73
+ FORCE_COLOR: '1'
74
+ }
75
+ })
76
+
77
+ viteProcess.stdout.on('data', (data) => {
78
+ console.log(`[Vite] ${data}`)
79
+ })
80
+
81
+ viteProcess.stderr.on('data', (data) => {
82
+ console.error(`[Vite Error] ${data}`)
83
+ })
84
+
85
+ viteProcess.on('error', (error) => {
86
+ console.error('Failed to start Vite:', error)
87
+ reject(error)
88
+ })
89
+
90
+ viteProcess.on('exit', (code) => {
91
+ console.log(`Vite process exited with code ${code}`)
92
+ if (!mainWindow || mainWindow.isDestroyed()) {
93
+ app.quit()
94
+ }
95
+ })
96
+
97
+ // Wait for server to be ready
98
+ setTimeout(() => {
99
+ waitForServer(serverPort).then((ready) => {
100
+ if (ready) {
101
+ console.log(`Vite server ready on http://localhost:${serverPort}`)
102
+ resolve()
103
+ } else {
104
+ reject(new Error('Vite server failed to start'))
105
+ }
106
+ }).catch(reject)
107
+ }, 2000)
108
+ })
109
+ })
110
+ }
111
+
112
+ function createWindow() {
113
+ mainWindow = new BrowserWindow({
114
+ width: 1400,
115
+ height: 900,
116
+ frame: false,
117
+ webPreferences: {
118
+ nodeIntegration: false,
119
+ contextIsolation: true,
120
+ webSecurity: !isDev // Allow local file loading in production
121
+ },
122
+ title: 'Stacks',
123
+ backgroundColor: '#000000'
124
+ })
125
+
126
+ if (isDev) {
127
+ // Development: Load from Vite server
128
+ mainWindow.loadURL(`http://localhost:${serverPort}`)
129
+ mainWindow.webContents.openDevTools()
130
+ } else {
131
+ // Production: Load from dist folder
132
+ const distPath = path.join(__dirname, '..', 'dist', 'index.html')
133
+ mainWindow.loadFile(distPath)
134
+ }
135
+
136
+ mainWindow.on('closed', () => {
137
+ mainWindow = null
138
+ })
139
+ }
140
+
141
+ app.whenReady().then(() => {
142
+ if (isDev) {
143
+ // Development: Start Vite first
144
+ startViteServer()
145
+ .then(() => {
146
+ createWindow()
147
+ })
148
+ .catch((error) => {
149
+ console.error('Failed to start application:', error)
150
+ app.quit()
151
+ })
152
+ } else {
153
+ // Production: Load directly from dist
154
+ createWindow()
155
+ }
156
+
157
+ app.on('activate', () => {
158
+ if (BrowserWindow.getAllWindows().length === 0) {
159
+ createWindow()
160
+ }
161
+ })
162
+ })
163
+
164
+ app.on('window-all-closed', () => {
165
+ if (process.platform !== 'darwin') {
166
+ app.quit()
167
+ }
168
+ })
169
+
170
+ app.on('before-quit', () => {
171
+ // Kill vite process when app quits (dev mode only)
172
+ if (viteProcess && !viteProcess.killed) {
173
+ console.log('Stopping Vite server...')
174
+ viteProcess.kill('SIGTERM')
175
+
176
+ // Force kill after 2 seconds if still running
177
+ setTimeout(() => {
178
+ if (viteProcess && !viteProcess.killed) {
179
+ viteProcess.kill('SIGKILL')
180
+ }
181
+ }, 2000)
182
+ }
183
+ })
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "stacks-ai",
3
+ "version": "0.1.1",
4
+ "description": "Stacks - AI-powered infinite canvas for notes, images, and creative organization",
5
+ "author": "Jason Kneen",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "electron/main.cjs",
9
+ "bin": {
10
+ "stacks-ai": "bin/stacks.cjs"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "dist/assets/",
15
+ "dist/fonts/",
16
+ "dist/index.html",
17
+ "dist/mcp/*.js",
18
+ "electron/",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "dev": "vite",
23
+ "build": "vite build && npm run mcp:build",
24
+ "build:prod": "npm run clean && NODE_ENV=production vite build && npm run mcp:build",
25
+ "preview": "vite preview",
26
+ "clean": "rm -rf dist node_modules/.vite",
27
+ "electron": "electron .",
28
+ "electron:dev": "NODE_ENV=development electron .",
29
+ "prepublishOnly": "npm run build:prod",
30
+ "mcp:dev": "tsx watch mcp/server.ts",
31
+ "mcp:build": "tsc -p tsconfig.mcp.json",
32
+ "mcp:start": "node dist/mcp/server.js",
33
+ "mcp:proxy": "tsx mcp/proxy.ts",
34
+ "mcp:proxy:start": "node dist/mcp/proxy.js",
35
+ "version:patch": "npm version patch --no-git-tag-version",
36
+ "version:minor": "npm version minor --no-git-tag-version",
37
+ "version:major": "npm version major --no-git-tag-version"
38
+ },
39
+ "dependencies": {
40
+ "@ai-sdk/anthropic": "^3.0.2",
41
+ "@ai-sdk/google": "^3.0.2",
42
+ "@ai-sdk/openai": "^3.0.2",
43
+ "@anthropic-ai/sdk": "^0.71.2",
44
+ "@google/genai": "^1.34.0",
45
+ "@modelcontextprotocol/sdk": "^1.25.1",
46
+ "@paper-design/shaders-react": "^0.0.68",
47
+ "@types/better-sqlite3": "^7.6.13",
48
+ "ai": "^6.0.6",
49
+ "better-sqlite3": "^12.5.0",
50
+ "elkjs": "0.9.3",
51
+ "lucide-react": "^0.562.0",
52
+ "openai": "^6.15.0",
53
+ "react": "^19.2.3",
54
+ "react-dom": "^19.2.3",
55
+ "tsx": "^4.21.0",
56
+ "ws": "^8.18.3",
57
+ "zod": "^4.3.5"
58
+ },
59
+ "devDependencies": {
60
+ "@tailwindcss/postcss": "^4.1.18",
61
+ "@tailwindcss/vite": "^4.1.18",
62
+ "@types/node": "^22.14.0",
63
+ "@types/ws": "^8.18.1",
64
+ "@vitejs/plugin-react": "^5.0.0",
65
+ "autoprefixer": "^10.4.23",
66
+ "electron": "^39.2.7",
67
+ "postcss": "^8.5.6",
68
+ "tailwindcss": "^4.1.18",
69
+ "typescript": "~5.8.2",
70
+ "vite": "^6.2.0",
71
+ "web-worker": "^1.5.0"
72
+ },
73
+ "repository": {
74
+ "type": "git",
75
+ "url": "git+https://github.com/jasonkneen/stacks.git"
76
+ },
77
+ "keywords": [
78
+ "stacks",
79
+ "canvas",
80
+ "notes",
81
+ "ai",
82
+ "workspace",
83
+ "electron",
84
+ "infinite-canvas"
85
+ ]
86
+ }