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.
- package/README.md +93 -0
- package/bin/stacks.cjs +215 -0
- package/dist/assets/aiProvider-DYAx3DVK.js +1 -0
- package/dist/assets/index-C1agmKFP.js +76 -0
- package/dist/assets/index-C8w_QbzK.js +15 -0
- package/dist/assets/index-Dp9e0AZR.js +5252 -0
- package/dist/assets/index-Q3CD-OmM.css +1 -0
- package/dist/assets/indexedDB-dA1h4x9Q.js +1 -0
- package/dist/assets/visionAnalysis-BGbWI8eX.js +9 -0
- package/dist/fonts/frank-the-architect.ttf +0 -0
- package/dist/index.html +13 -0
- package/dist/mcp/proxy.js +404 -0
- package/dist/mcp/server.js +529 -0
- package/electron/main.cjs +183 -0
- package/package.json +86 -0
|
@@ -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
|
+
}
|