@bytespell/shella 0.2.2 → 0.2.3

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.
Files changed (85) hide show
  1. package/dist/bin/shella-init.js +28 -93
  2. package/dist/bin/shella-init.js.map +1 -1
  3. package/dist/src/api.d.ts.map +1 -1
  4. package/dist/src/api.js +36 -2
  5. package/dist/src/api.js.map +1 -1
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +60 -5
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/logger.d.ts.map +1 -1
  10. package/dist/src/logger.js +46 -1
  11. package/dist/src/logger.js.map +1 -1
  12. package/dist/src/plugin-manager.d.ts +22 -1
  13. package/dist/src/plugin-manager.d.ts.map +1 -1
  14. package/dist/src/plugin-manager.js +84 -32
  15. package/dist/src/plugin-manager.js.map +1 -1
  16. package/dist/src/port-allocator.js +1 -1
  17. package/dist/src/port-allocator.js.map +1 -1
  18. package/dist/src/registry.d.ts +15 -1
  19. package/dist/src/registry.d.ts.map +1 -1
  20. package/dist/src/registry.js +29 -0
  21. package/dist/src/registry.js.map +1 -1
  22. package/dist/src/types.d.ts +20 -2
  23. package/dist/src/types.d.ts.map +1 -1
  24. package/dist/src/types.js.map +1 -1
  25. package/dist/src/watcher.d.ts +4 -3
  26. package/dist/src/watcher.d.ts.map +1 -1
  27. package/dist/src/watcher.js +91 -74
  28. package/dist/src/watcher.js.map +1 -1
  29. package/package.json +6 -2
  30. package/bundled-plugins/agent/README.md +0 -3
  31. package/bundled-plugins/agent/components.json +0 -24
  32. package/bundled-plugins/agent/dist/assets/index-BGeDYr6P.css +0 -1
  33. package/bundled-plugins/agent/dist/assets/index-J14lI3Er.js +0 -49
  34. package/bundled-plugins/agent/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  35. package/bundled-plugins/agent/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  36. package/bundled-plugins/agent/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  37. package/bundled-plugins/agent/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  38. package/bundled-plugins/agent/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  39. package/bundled-plugins/agent/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  40. package/bundled-plugins/agent/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  41. package/bundled-plugins/agent/dist/index.html +0 -14
  42. package/bundled-plugins/agent/dist/vite.svg +0 -1
  43. package/bundled-plugins/agent/eslint.config.js +0 -23
  44. package/bundled-plugins/agent/index.html +0 -13
  45. package/bundled-plugins/agent/package-lock.json +0 -11034
  46. package/bundled-plugins/agent/package.json +0 -52
  47. package/bundled-plugins/agent/public/vite.svg +0 -1
  48. package/bundled-plugins/agent/server.js +0 -240
  49. package/bundled-plugins/agent/src/App.tsx +0 -586
  50. package/bundled-plugins/agent/src/assets/react.svg +0 -1
  51. package/bundled-plugins/agent/src/components/ui/alert-dialog.tsx +0 -182
  52. package/bundled-plugins/agent/src/components/ui/badge.tsx +0 -45
  53. package/bundled-plugins/agent/src/components/ui/button.tsx +0 -60
  54. package/bundled-plugins/agent/src/components/ui/card.tsx +0 -94
  55. package/bundled-plugins/agent/src/components/ui/combobox.tsx +0 -294
  56. package/bundled-plugins/agent/src/components/ui/dropdown-menu.tsx +0 -253
  57. package/bundled-plugins/agent/src/components/ui/field.tsx +0 -225
  58. package/bundled-plugins/agent/src/components/ui/input-group.tsx +0 -147
  59. package/bundled-plugins/agent/src/components/ui/input.tsx +0 -19
  60. package/bundled-plugins/agent/src/components/ui/label.tsx +0 -24
  61. package/bundled-plugins/agent/src/components/ui/select.tsx +0 -185
  62. package/bundled-plugins/agent/src/components/ui/separator.tsx +0 -26
  63. package/bundled-plugins/agent/src/components/ui/switch.tsx +0 -31
  64. package/bundled-plugins/agent/src/components/ui/textarea.tsx +0 -18
  65. package/bundled-plugins/agent/src/index.css +0 -131
  66. package/bundled-plugins/agent/src/lib/utils.ts +0 -6
  67. package/bundled-plugins/agent/src/main.tsx +0 -11
  68. package/bundled-plugins/agent/tsconfig.app.json +0 -32
  69. package/bundled-plugins/agent/tsconfig.json +0 -13
  70. package/bundled-plugins/agent/tsconfig.node.json +0 -26
  71. package/bundled-plugins/agent/vite.config.ts +0 -14
  72. package/bundled-plugins/terminal/index.html +0 -24
  73. package/bundled-plugins/terminal/package-lock.json +0 -3316
  74. package/bundled-plugins/terminal/package.json +0 -37
  75. package/bundled-plugins/terminal/server.ts +0 -169
  76. package/bundled-plugins/terminal/src/App.tsx +0 -168
  77. package/bundled-plugins/terminal/src/main.tsx +0 -9
  78. package/bundled-plugins/terminal/tsconfig.json +0 -22
  79. package/bundled-plugins/terminal/vite.config.ts +0 -10
  80. package/templates/express/package.json +0 -11
  81. package/templates/express/public/index.html +0 -64
  82. package/templates/express/server.js +0 -37
  83. package/templates/vanilla/package.json +0 -8
  84. package/templates/vanilla/public/index.html +0 -64
  85. package/templates/vanilla/server.js +0 -54
@@ -1,37 +0,0 @@
1
- {
2
- "name": "@shella/terminal",
3
- "private": true,
4
- "version": "0.2.2",
5
- "type": "module",
6
- "main": "server.ts",
7
- "shella": {
8
- "displayName": "Terminal",
9
- "devCommand": "tsx server.ts"
10
- },
11
- "scripts": {
12
- "dev": "tsx server.ts",
13
- "build": "vite build",
14
- "preview": "vite preview"
15
- },
16
- "dependencies": {
17
- "@xterm/addon-fit": "^0.10.0",
18
- "@xterm/addon-web-links": "^0.11.0",
19
- "@xterm/xterm": "^5.5.0",
20
- "express": "^5.0.0",
21
- "node-pty": "^1.2.0-beta.7",
22
- "react": "^19.2.0",
23
- "react-dom": "^19.2.0",
24
- "ws": "^8.18.0"
25
- },
26
- "devDependencies": {
27
- "@types/express": "^5.0.0",
28
- "@types/node": "^22.0.0",
29
- "@types/react": "^19.2.0",
30
- "@types/react-dom": "^19.2.0",
31
- "@types/ws": "^8.5.0",
32
- "@vitejs/plugin-react": "^4.3.0",
33
- "tsx": "^4.19.0",
34
- "typescript": "^5.6.0",
35
- "vite": "^6.0.0"
36
- }
37
- }
@@ -1,169 +0,0 @@
1
- import express from 'express'
2
- import { createServer } from 'http'
3
- import { WebSocketServer, WebSocket } from 'ws'
4
- import * as pty from 'node-pty'
5
- import path from 'path'
6
- import fs from 'fs'
7
- import { fileURLToPath } from 'url'
8
-
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
- const app = express()
11
- const PORT = process.env.PORT
12
-
13
- // Check if we're in dev mode (no dist folder)
14
- const isDev = !fs.existsSync(path.join(__dirname, 'dist', 'index.html'))
15
-
16
- // Allow embedding in iframes (for shella-web)
17
- app.use((_req, res, next) => {
18
- res.removeHeader('X-Frame-Options')
19
- res.setHeader('Content-Security-Policy', 'frame-ancestors *')
20
- next()
21
- })
22
-
23
- app.use(express.json())
24
-
25
- // Health check
26
- app.get('/api/health', (_req, res) => {
27
- res.json({ status: 'ok', shell: process.env.SHELL || '/bin/bash' })
28
- })
29
-
30
- async function startServer() {
31
- if (isDev) {
32
- // Dev mode: use Vite middleware for HMR
33
- const { createServer: createViteServer } = await import('vite')
34
- const vite = await createViteServer({
35
- server: { middlewareMode: true, hmr: { port: Number(PORT) + 1000 } },
36
- appType: 'spa',
37
- })
38
- app.use(vite.middlewares)
39
- } else {
40
- // Production: serve built files
41
- app.use(express.static(path.join(__dirname, 'dist')))
42
- app.get('*', (_req, res) => {
43
- res.sendFile(path.join(__dirname, 'dist', 'index.html'))
44
- })
45
- }
46
-
47
- const server = createServer(app)
48
-
49
- // PTY is spawned lazily on first client connection with correct size
50
- const shell = process.env.SHELL || '/bin/bash'
51
- const home = process.env.HOME || '/tmp'
52
-
53
- let ptyProcess: pty.IPty | null = null
54
-
55
- // Track connected clients
56
- const clients = new Set<WebSocket>()
57
-
58
- // Buffer recent output for new clients (scrollback)
59
- const outputBuffer: string[] = []
60
- const MAX_BUFFER_LINES = 1000
61
-
62
- function spawnPty(cols: number, rows: number) {
63
- if (ptyProcess) return // Already spawned
64
-
65
- ptyProcess = pty.spawn(shell, [], {
66
- name: 'xterm-256color',
67
- cols,
68
- rows,
69
- cwd: home,
70
- env: {
71
- ...process.env as Record<string, string>,
72
- // Suppress zsh's partial line marker (%) on fresh terminal
73
- PROMPT_EOL_MARK: '',
74
- },
75
- })
76
-
77
- console.log(`[terminal] PTY spawned: pid=${ptyProcess.pid}, shell=${shell}, size=${cols}x${rows}`)
78
-
79
- // PTY -> all connected clients
80
- ptyProcess.onData((data: string) => {
81
- // Buffer output for new clients
82
- outputBuffer.push(data)
83
- if (outputBuffer.length > MAX_BUFFER_LINES) {
84
- outputBuffer.shift()
85
- }
86
-
87
- // Broadcast to all connected clients
88
- const message = JSON.stringify({ type: 'output', data })
89
- for (const client of clients) {
90
- if (client.readyState === WebSocket.OPEN) {
91
- client.send(message)
92
- }
93
- }
94
- })
95
-
96
- ptyProcess.onExit(({ exitCode, signal }) => {
97
- console.log(`[terminal] PTY exited: code=${exitCode}, signal=${signal}`)
98
- const message = JSON.stringify({ type: 'exit', exitCode, signal })
99
- for (const client of clients) {
100
- if (client.readyState === WebSocket.OPEN) {
101
- client.send(message)
102
- client.close()
103
- }
104
- }
105
- // PTY died, shut down the instance
106
- process.exit(exitCode ?? 0)
107
- })
108
- }
109
-
110
- // WebSocket server for client connections
111
- const wss = new WebSocketServer({ server, path: '/ws' })
112
-
113
- wss.on('connection', (ws: WebSocket) => {
114
- clients.add(ws)
115
-
116
- // Send buffered output to new client so they see history (if PTY already running)
117
- if (ptyProcess && outputBuffer.length > 0) {
118
- ws.send(JSON.stringify({ type: 'output', data: outputBuffer.join('') }))
119
- }
120
-
121
- // Client -> PTY
122
- ws.on('message', (message: Buffer) => {
123
- try {
124
- const msg = JSON.parse(message.toString())
125
-
126
- switch (msg.type) {
127
- case 'input':
128
- ptyProcess?.write(msg.data)
129
- break
130
- case 'resize':
131
- if (msg.cols && msg.rows) {
132
- if (!ptyProcess) {
133
- // First resize spawns the PTY with correct size
134
- spawnPty(msg.cols, msg.rows)
135
- } else {
136
- ptyProcess.resize(msg.cols, msg.rows)
137
- }
138
- }
139
- break
140
- }
141
- } catch (err) {
142
- console.error('[terminal] Invalid message:', err)
143
- }
144
- })
145
-
146
- ws.on('close', () => {
147
- clients.delete(ws)
148
- })
149
-
150
- ws.on('error', (err) => {
151
- console.error('[terminal] WebSocket error:', err)
152
- clients.delete(ws)
153
- })
154
- })
155
-
156
- server.listen(PORT, () => {
157
- console.log(`[terminal] Running on port ${PORT}${isDev ? ' (dev mode with HMR)' : ''}`)
158
- })
159
-
160
- // Graceful shutdown - kill PTY when instance stops
161
- process.on('SIGTERM', () => {
162
- console.log('[terminal] SIGTERM received, shutting down')
163
- ptyProcess?.kill()
164
- wss.clients.forEach((client) => client.close())
165
- server.close(() => process.exit(0))
166
- })
167
- }
168
-
169
- startServer()
@@ -1,168 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react'
2
- import { Terminal } from '@xterm/xterm'
3
- import { FitAddon } from '@xterm/addon-fit'
4
- import { WebLinksAddon } from '@xterm/addon-web-links'
5
- import '@xterm/xterm/css/xterm.css'
6
-
7
- type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error'
8
-
9
- export default function App() {
10
- const terminalRef = useRef<HTMLDivElement>(null)
11
- const termRef = useRef<Terminal | null>(null)
12
- const wsRef = useRef<WebSocket | null>(null)
13
- const fitAddonRef = useRef<FitAddon | null>(null)
14
- const [status, setStatus] = useState<ConnectionStatus>('connecting')
15
-
16
- useEffect(() => {
17
- if (!terminalRef.current) return
18
-
19
- // Create terminal
20
- const term = new Terminal({
21
- cursorBlink: true,
22
- fontSize: 14,
23
- fontFamily: 'Menlo, Monaco, "Courier New", monospace',
24
- theme: {
25
- background: '#1a1a1a',
26
- foreground: '#e0e0e0',
27
- cursor: '#e0e0e0',
28
- cursorAccent: '#1a1a1a',
29
- selectionBackground: '#4a4a4a',
30
- black: '#1a1a1a',
31
- red: '#ff5555',
32
- green: '#50fa7b',
33
- yellow: '#f1fa8c',
34
- blue: '#6272a4',
35
- magenta: '#ff79c6',
36
- cyan: '#8be9fd',
37
- white: '#e0e0e0',
38
- brightBlack: '#4a4a4a',
39
- brightRed: '#ff6e6e',
40
- brightGreen: '#69ff94',
41
- brightYellow: '#ffffa5',
42
- brightBlue: '#d6acff',
43
- brightMagenta: '#ff92df',
44
- brightCyan: '#a4ffff',
45
- brightWhite: '#ffffff',
46
- },
47
- })
48
-
49
- // Add addons
50
- const fitAddon = new FitAddon()
51
- const webLinksAddon = new WebLinksAddon()
52
- term.loadAddon(fitAddon)
53
- term.loadAddon(webLinksAddon)
54
-
55
- // Open terminal in container
56
- term.open(terminalRef.current)
57
- fitAddon.fit()
58
-
59
- termRef.current = term
60
- fitAddonRef.current = fitAddon
61
-
62
- // Connect WebSocket
63
- const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
64
- const ws = new WebSocket(`${wsProtocol}//${window.location.host}/ws`)
65
- wsRef.current = ws
66
-
67
- ws.onopen = () => {
68
- setStatus('connected')
69
- // Send initial size
70
- const dims = fitAddon.proposeDimensions()
71
- if (dims) {
72
- ws.send(JSON.stringify({ type: 'resize', cols: dims.cols, rows: dims.rows }))
73
- }
74
- }
75
-
76
- ws.onmessage = (event) => {
77
- try {
78
- const msg = JSON.parse(event.data)
79
- switch (msg.type) {
80
- case 'output':
81
- term.write(msg.data)
82
- break
83
- case 'exit':
84
- term.write(`\r\n\x1b[31m[Process exited with code ${msg.exitCode}]\x1b[0m\r\n`)
85
- setStatus('disconnected')
86
- break
87
- }
88
- } catch {
89
- // Raw data fallback
90
- term.write(event.data)
91
- }
92
- }
93
-
94
- ws.onerror = () => {
95
- setStatus('error')
96
- }
97
-
98
- ws.onclose = () => {
99
- if (status !== 'error') {
100
- setStatus('disconnected')
101
- }
102
- }
103
-
104
- // Terminal input -> WebSocket
105
- term.onData((data) => {
106
- if (ws.readyState === WebSocket.OPEN) {
107
- ws.send(JSON.stringify({ type: 'input', data }))
108
- }
109
- })
110
-
111
- // Handle resize
112
- const handleResize = () => {
113
- fitAddon.fit()
114
- const dims = fitAddon.proposeDimensions()
115
- if (dims && ws.readyState === WebSocket.OPEN) {
116
- ws.send(JSON.stringify({ type: 'resize', cols: dims.cols, rows: dims.rows }))
117
- }
118
- }
119
-
120
- window.addEventListener('resize', handleResize)
121
-
122
- // Also observe container size changes (for panel resizes)
123
- const resizeObserver = new ResizeObserver(handleResize)
124
- resizeObserver.observe(terminalRef.current)
125
-
126
- // Cleanup
127
- return () => {
128
- window.removeEventListener('resize', handleResize)
129
- resizeObserver.disconnect()
130
- ws.close()
131
- term.dispose()
132
- }
133
- }, [])
134
-
135
- return (
136
- <div
137
- style={{
138
- width: '100%',
139
- height: '100%',
140
- backgroundColor: '#1a1a1a',
141
- display: 'flex',
142
- flexDirection: 'column',
143
- }}
144
- >
145
- {status !== 'connected' && (
146
- <div
147
- style={{
148
- padding: '8px 12px',
149
- backgroundColor: status === 'error' ? '#ff5555' : '#4a4a4a',
150
- color: '#fff',
151
- fontSize: '12px',
152
- }}
153
- >
154
- {status === 'connecting' && 'Connecting...'}
155
- {status === 'disconnected' && 'Disconnected'}
156
- {status === 'error' && 'Connection error'}
157
- </div>
158
- )}
159
- <div
160
- ref={terminalRef}
161
- style={{
162
- flex: 1,
163
- padding: '4px',
164
- }}
165
- />
166
- </div>
167
- )
168
- }
@@ -1,9 +0,0 @@
1
- import { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import App from './App'
4
-
5
- createRoot(document.getElementById('root')!).render(
6
- <StrictMode>
7
- <App />
8
- </StrictMode>
9
- )
@@ -1,22 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "useDefineForClassFields": true,
5
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
- "module": "ESNext",
7
- "skipLibCheck": true,
8
- "moduleResolution": "bundler",
9
- "allowImportingTsExtensions": true,
10
- "isolatedModules": true,
11
- "moduleDetection": "force",
12
- "noEmit": true,
13
- "jsx": "react-jsx",
14
- "strict": true,
15
- "noUnusedLocals": true,
16
- "noUnusedParameters": true,
17
- "noFallthroughCasesInSwitch": true,
18
- "noUncheckedIndexedAccess": true,
19
- "esModuleInterop": true
20
- },
21
- "include": ["src", "server.ts"]
22
- }
@@ -1,10 +0,0 @@
1
- import { defineConfig } from 'vite'
2
- import react from '@vitejs/plugin-react'
3
-
4
- export default defineConfig({
5
- plugins: [react()],
6
- build: {
7
- outDir: 'dist',
8
- emptyDirBeforeWrite: true,
9
- },
10
- })
@@ -1,11 +0,0 @@
1
- {
2
- "name": "shella-plugin",
3
- "version": "1.0.0",
4
- "main": "server.js",
5
- "shella": {
6
- "displayName": "My Plugin"
7
- },
8
- "dependencies": {
9
- "express": "^4.18.0"
10
- }
11
- }
@@ -1,64 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Shella Plugin</title>
7
- <style>
8
- * { box-sizing: border-box; margin: 0; padding: 0; }
9
- body {
10
- font-family: system-ui, -apple-system, sans-serif;
11
- background: #0a0a0a;
12
- color: #fafafa;
13
- min-height: 100vh;
14
- padding: 2rem;
15
- }
16
- .container { max-width: 600px; margin: 0 auto; }
17
- h1 { margin-bottom: 1rem; }
18
- .card {
19
- background: #1a1a1a;
20
- border: 1px solid #333;
21
- border-radius: 8px;
22
- padding: 1.5rem;
23
- margin-bottom: 1rem;
24
- }
25
- button {
26
- background: #fff;
27
- color: #000;
28
- border: none;
29
- padding: 0.5rem 1rem;
30
- border-radius: 4px;
31
- cursor: pointer;
32
- font-size: 1rem;
33
- }
34
- button:hover { background: #ddd; }
35
- #result {
36
- margin-top: 1rem;
37
- padding: 1rem;
38
- background: #262626;
39
- border-radius: 4px;
40
- display: none;
41
- }
42
- </style>
43
- </head>
44
- <body>
45
- <div class="container">
46
- <h1>Shella Plugin</h1>
47
- <div class="card">
48
- <p>This is your plugin scaffold. Edit this file and server.js to build your UI.</p>
49
- <br>
50
- <button onclick="testApi()">Test API</button>
51
- <div id="result"></div>
52
- </div>
53
- </div>
54
- <script>
55
- async function testApi() {
56
- const res = await fetch('/api/hello');
57
- const data = await res.json();
58
- const result = document.getElementById('result');
59
- result.style.display = 'block';
60
- result.textContent = JSON.stringify(data, null, 2);
61
- }
62
- </script>
63
- </body>
64
- </html>
@@ -1,37 +0,0 @@
1
- const express = require('express');
2
- const path = require('path');
3
-
4
- const app = express();
5
- const PORT = process.env.PORT;
6
-
7
- // Allow embedding in iframes (for shella-web)
8
- app.use((req, res, next) => {
9
- res.setHeader('Content-Security-Policy', "frame-ancestors *");
10
- next();
11
- });
12
-
13
- // Middleware
14
- app.use(express.json());
15
- app.use(express.static(path.join(__dirname, 'public')));
16
-
17
- // API routes
18
- app.get('/api/hello', (req, res) => {
19
- res.json({ message: 'Hello from the plugin!' });
20
- });
21
-
22
- // Add your API endpoints here
23
- // Example: Import from parent project
24
- // const { someFunction } = require('../src/mymodule.js');
25
- // app.post('/api/analyze', async (req, res) => {
26
- // const result = await someFunction(req.body);
27
- // res.json(result);
28
- // });
29
-
30
- const server = app.listen(PORT, () => {
31
- console.log(`Plugin running on port ${PORT}`);
32
- });
33
-
34
- // Graceful shutdown
35
- process.on('SIGTERM', () => {
36
- server.close(() => process.exit(0));
37
- });
@@ -1,8 +0,0 @@
1
- {
2
- "name": "shella-plugin",
3
- "version": "1.0.0",
4
- "main": "server.js",
5
- "shella": {
6
- "displayName": "My Plugin"
7
- }
8
- }
@@ -1,64 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Shella Plugin</title>
7
- <style>
8
- * { box-sizing: border-box; margin: 0; padding: 0; }
9
- body {
10
- font-family: system-ui, -apple-system, sans-serif;
11
- background: #0a0a0a;
12
- color: #fafafa;
13
- min-height: 100vh;
14
- padding: 2rem;
15
- }
16
- .container { max-width: 600px; margin: 0 auto; }
17
- h1 { margin-bottom: 1rem; }
18
- .card {
19
- background: #1a1a1a;
20
- border: 1px solid #333;
21
- border-radius: 8px;
22
- padding: 1.5rem;
23
- margin-bottom: 1rem;
24
- }
25
- button {
26
- background: #fff;
27
- color: #000;
28
- border: none;
29
- padding: 0.5rem 1rem;
30
- border-radius: 4px;
31
- cursor: pointer;
32
- font-size: 1rem;
33
- }
34
- button:hover { background: #ddd; }
35
- #result {
36
- margin-top: 1rem;
37
- padding: 1rem;
38
- background: #262626;
39
- border-radius: 4px;
40
- display: none;
41
- }
42
- </style>
43
- </head>
44
- <body>
45
- <div class="container">
46
- <h1>Shella Plugin</h1>
47
- <div class="card">
48
- <p>This is your plugin scaffold. Edit this file and server.js to build your UI.</p>
49
- <br>
50
- <button onclick="testApi()">Test API</button>
51
- <div id="result"></div>
52
- </div>
53
- </div>
54
- <script>
55
- async function testApi() {
56
- const res = await fetch('/api/hello');
57
- const data = await res.json();
58
- const result = document.getElementById('result');
59
- result.style.display = 'block';
60
- result.textContent = JSON.stringify(data, null, 2);
61
- }
62
- </script>
63
- </body>
64
- </html>
@@ -1,54 +0,0 @@
1
- const http = require('http');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- const PORT = process.env.PORT;
6
-
7
- // Simple static file server + API
8
- const server = http.createServer((req, res) => {
9
- // Allow embedding in iframes (for shella-web)
10
- res.setHeader('Content-Security-Policy', "frame-ancestors *");
11
- // API routes
12
- if (req.url === '/api/hello' && req.method === 'GET') {
13
- res.writeHead(200, { 'Content-Type': 'application/json' });
14
- res.end(JSON.stringify({ message: 'Hello from the plugin!' }));
15
- return;
16
- }
17
-
18
- // Serve static files from public/
19
- let filePath = path.join(__dirname, 'public', req.url === '/' ? 'index.html' : req.url);
20
- const ext = path.extname(filePath);
21
-
22
- const mimeTypes = {
23
- '.html': 'text/html',
24
- '.js': 'text/javascript',
25
- '.css': 'text/css',
26
- '.json': 'application/json',
27
- '.png': 'image/png',
28
- '.svg': 'image/svg+xml',
29
- };
30
-
31
- fs.readFile(filePath, (err, content) => {
32
- if (err) {
33
- if (err.code === 'ENOENT') {
34
- res.writeHead(404);
35
- res.end('Not found');
36
- } else {
37
- res.writeHead(500);
38
- res.end('Server error');
39
- }
40
- return;
41
- }
42
- res.writeHead(200, { 'Content-Type': mimeTypes[ext] || 'text/plain' });
43
- res.end(content);
44
- });
45
- });
46
-
47
- server.listen(PORT, () => {
48
- console.log(`Plugin running on port ${PORT}`);
49
- });
50
-
51
- // Graceful shutdown
52
- process.on('SIGTERM', () => {
53
- server.close(() => process.exit(0));
54
- });