@nicmeriano/spool 0.0.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/dist/cli.js +236 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/vite.d.ts +14 -0
- package/dist/vite.js +70 -0
- package/dist/vite.js.map +1 -0
- package/package.json +66 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/init.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as readline from "readline";
|
|
7
|
+
function detectFramework(cwd) {
|
|
8
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
9
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
10
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
11
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
12
|
+
if (deps["next"]) {
|
|
13
|
+
return { name: "Next.js", devCommand: "npm run dev" };
|
|
14
|
+
}
|
|
15
|
+
if (deps["vite"]) {
|
|
16
|
+
return { name: "Vite", devCommand: "npm run dev" };
|
|
17
|
+
}
|
|
18
|
+
if (deps["react-scripts"]) {
|
|
19
|
+
return { name: "Create React App", devCommand: "npm start" };
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function prompt(question, defaultValue) {
|
|
24
|
+
const rl = readline.createInterface({
|
|
25
|
+
input: process.stdin,
|
|
26
|
+
output: process.stdout
|
|
27
|
+
});
|
|
28
|
+
const displayDefault = defaultValue ? ` (${defaultValue})` : "";
|
|
29
|
+
return new Promise((resolve2) => {
|
|
30
|
+
rl.question(`${question}${displayDefault}: `, (answer) => {
|
|
31
|
+
rl.close();
|
|
32
|
+
resolve2(answer.trim() || defaultValue || "");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function runInit(cwd) {
|
|
37
|
+
console.log("Initializing Spool...\n");
|
|
38
|
+
const configPath = path.join(cwd, "spool.config.ts");
|
|
39
|
+
if (fs.existsSync(configPath)) {
|
|
40
|
+
console.log("spool.config.ts already exists. Skipping.\n");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const framework = detectFramework(cwd);
|
|
44
|
+
if (framework) {
|
|
45
|
+
console.log(`Detected framework: ${framework.name}
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
const defaultDevCmd = framework?.devCommand || "npm run dev";
|
|
49
|
+
const devCommand = await prompt("Dev command", defaultDevCmd);
|
|
50
|
+
const configContent = `import { defineConfig } from '@nicmeriano/spool';
|
|
51
|
+
|
|
52
|
+
export default defineConfig({
|
|
53
|
+
devCommand: '${devCommand}',
|
|
54
|
+
});
|
|
55
|
+
`;
|
|
56
|
+
fs.writeFileSync(configPath, configContent, "utf-8");
|
|
57
|
+
console.log(`
|
|
58
|
+
Created spool.config.ts`);
|
|
59
|
+
const spoolDir = path.join(cwd, ".spool");
|
|
60
|
+
if (!fs.existsSync(spoolDir)) {
|
|
61
|
+
fs.mkdirSync(spoolDir, { recursive: true });
|
|
62
|
+
console.log("Created .spool/ directory");
|
|
63
|
+
}
|
|
64
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
65
|
+
if (fs.existsSync(gitignorePath)) {
|
|
66
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
67
|
+
if (!content.includes(".spool")) {
|
|
68
|
+
fs.appendFileSync(gitignorePath, "\n.spool/\n");
|
|
69
|
+
console.log("Added .spool/ to .gitignore");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
console.log('\nDone! Run "spool open" to start.');
|
|
73
|
+
console.log("\nNote: If you're not using Vite, add this script tag to your HTML manually:");
|
|
74
|
+
console.log(' <script src="http://localhost:3142/inject.js"></script>\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/commands/open.ts
|
|
78
|
+
import { spawn } from "child_process";
|
|
79
|
+
|
|
80
|
+
// src/utils/load-config.ts
|
|
81
|
+
import * as fs2 from "fs";
|
|
82
|
+
import * as path2 from "path";
|
|
83
|
+
async function loadConfig(cwd) {
|
|
84
|
+
const configPath = path2.resolve(cwd, "spool.config.ts");
|
|
85
|
+
if (!fs2.existsSync(configPath)) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`No spool.config.ts found in ${cwd}.
|
|
88
|
+
Run "spool init" to create one.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const { createJiti } = await import("jiti");
|
|
92
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
93
|
+
const config = await jiti.import(configPath);
|
|
94
|
+
return config.default ?? config;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/utils/port.ts
|
|
98
|
+
import * as net from "net";
|
|
99
|
+
async function findAvailablePort(preferred) {
|
|
100
|
+
return new Promise((resolve2, reject) => {
|
|
101
|
+
const server = net.createServer();
|
|
102
|
+
server.listen(preferred, () => {
|
|
103
|
+
server.close(() => resolve2(preferred));
|
|
104
|
+
});
|
|
105
|
+
server.on("error", (err) => {
|
|
106
|
+
if (err.code === "EADDRINUSE") {
|
|
107
|
+
findAvailablePort(preferred + 1).then(resolve2).catch(reject);
|
|
108
|
+
} else {
|
|
109
|
+
reject(err);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/utils/browser.ts
|
|
116
|
+
import { exec } from "child_process";
|
|
117
|
+
function openBrowser(url) {
|
|
118
|
+
const platform = process.platform;
|
|
119
|
+
let command2;
|
|
120
|
+
if (platform === "darwin") {
|
|
121
|
+
command2 = `open "${url}"`;
|
|
122
|
+
} else if (platform === "win32") {
|
|
123
|
+
command2 = `start "${url}"`;
|
|
124
|
+
} else {
|
|
125
|
+
command2 = `xdg-open "${url}"`;
|
|
126
|
+
}
|
|
127
|
+
exec(command2, (error) => {
|
|
128
|
+
if (error) {
|
|
129
|
+
console.error(`Could not open browser: ${error.message}`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/constants.ts
|
|
135
|
+
var DEFAULT_SERVER_PORT = 3142;
|
|
136
|
+
|
|
137
|
+
// src/commands/open.ts
|
|
138
|
+
async function runOpen(cwd, options = {}) {
|
|
139
|
+
const config = await loadConfig(cwd);
|
|
140
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
141
|
+
console.warn("Warning: ANTHROPIC_API_KEY is not set. Claude features will not work.\n");
|
|
142
|
+
}
|
|
143
|
+
const preferredPort = options.port ?? config.port ?? DEFAULT_SERVER_PORT;
|
|
144
|
+
const port = await findAvailablePort(preferredPort);
|
|
145
|
+
if (port !== preferredPort) {
|
|
146
|
+
console.log(`Port ${preferredPort} is in use, using ${port} instead.`);
|
|
147
|
+
}
|
|
148
|
+
console.log(`Starting Spool server on port ${port}...`);
|
|
149
|
+
const { startServer } = await import("@nicmeriano/spool-server");
|
|
150
|
+
startServer({ port, cwd });
|
|
151
|
+
if (config.devCommand) {
|
|
152
|
+
console.log(`Starting dev server: ${config.devCommand}`);
|
|
153
|
+
const [cmd, ...args2] = config.devCommand.split(" ");
|
|
154
|
+
const child = spawn(cmd, args2, {
|
|
155
|
+
cwd,
|
|
156
|
+
stdio: "inherit",
|
|
157
|
+
shell: true
|
|
158
|
+
});
|
|
159
|
+
child.on("error", (error) => {
|
|
160
|
+
console.error(`Dev server error: ${error.message}`);
|
|
161
|
+
});
|
|
162
|
+
process.on("SIGINT", () => {
|
|
163
|
+
child.kill();
|
|
164
|
+
process.exit(0);
|
|
165
|
+
});
|
|
166
|
+
process.on("SIGTERM", () => {
|
|
167
|
+
child.kill();
|
|
168
|
+
process.exit(0);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (!options.noOpen) {
|
|
172
|
+
const shellUrl = `http://localhost:${port}`;
|
|
173
|
+
console.log(`
|
|
174
|
+
Opening Spool at ${shellUrl}
|
|
175
|
+
`);
|
|
176
|
+
setTimeout(() => {
|
|
177
|
+
openBrowser(shellUrl);
|
|
178
|
+
}, 1e3);
|
|
179
|
+
} else {
|
|
180
|
+
console.log(`
|
|
181
|
+
Spool server running at http://localhost:${port}
|
|
182
|
+
`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/cli.ts
|
|
187
|
+
var args = process.argv.slice(2);
|
|
188
|
+
var command = args[0];
|
|
189
|
+
function printUsage() {
|
|
190
|
+
console.log(`
|
|
191
|
+
Usage: spool <command> [options]
|
|
192
|
+
|
|
193
|
+
Commands:
|
|
194
|
+
init Initialize spool in your project
|
|
195
|
+
open Start spool server and open the UI
|
|
196
|
+
|
|
197
|
+
Options (open):
|
|
198
|
+
--port <n> Server port (default: 3142)
|
|
199
|
+
--no-open Don't open browser automatically
|
|
200
|
+
|
|
201
|
+
Examples:
|
|
202
|
+
spool init
|
|
203
|
+
spool open
|
|
204
|
+
spool open --port 4000
|
|
205
|
+
spool open --no-open
|
|
206
|
+
`);
|
|
207
|
+
}
|
|
208
|
+
async function main() {
|
|
209
|
+
const cwd = process.cwd();
|
|
210
|
+
switch (command) {
|
|
211
|
+
case "init":
|
|
212
|
+
await runInit(cwd);
|
|
213
|
+
break;
|
|
214
|
+
case "open": {
|
|
215
|
+
const portIndex = args.indexOf("--port");
|
|
216
|
+
const port = portIndex !== -1 ? parseInt(args[portIndex + 1], 10) : void 0;
|
|
217
|
+
const noOpen = args.includes("--no-open");
|
|
218
|
+
await runOpen(cwd, { port, noOpen });
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case "--help":
|
|
222
|
+
case "-h":
|
|
223
|
+
case void 0:
|
|
224
|
+
printUsage();
|
|
225
|
+
break;
|
|
226
|
+
default:
|
|
227
|
+
console.error(`Unknown command: ${command}`);
|
|
228
|
+
printUsage();
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
main().catch((error) => {
|
|
233
|
+
console.error(error.message || error);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
});
|
|
236
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/init.ts","../src/commands/open.ts","../src/utils/load-config.ts","../src/utils/port.ts","../src/utils/browser.ts","../src/constants.ts","../src/cli.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as readline from 'node:readline';\n\n/**\n * Detect the project framework\n */\nfunction detectFramework(cwd: string): { name: string; devCommand: string } | null {\n const pkgPath = path.join(cwd, 'package.json');\n if (!fs.existsSync(pkgPath)) return null;\n\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n\n if (deps['next']) {\n return { name: 'Next.js', devCommand: 'npm run dev' };\n }\n if (deps['vite']) {\n return { name: 'Vite', devCommand: 'npm run dev' };\n }\n if (deps['react-scripts']) {\n return { name: 'Create React App', devCommand: 'npm start' };\n }\n\n return null;\n}\n\n/**\n * Prompt the user for input\n */\nfunction prompt(question: string, defaultValue?: string): Promise<string> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const displayDefault = defaultValue ? ` (${defaultValue})` : '';\n\n return new Promise((resolve) => {\n rl.question(`${question}${displayDefault}: `, (answer) => {\n rl.close();\n resolve(answer.trim() || defaultValue || '');\n });\n });\n}\n\n/**\n * Run the spool init command\n */\nexport async function runInit(cwd: string): Promise<void> {\n console.log('Initializing Spool...\\n');\n\n // Check if config already exists\n const configPath = path.join(cwd, 'spool.config.ts');\n if (fs.existsSync(configPath)) {\n console.log('spool.config.ts already exists. Skipping.\\n');\n return;\n }\n\n // Detect framework\n const framework = detectFramework(cwd);\n if (framework) {\n console.log(`Detected framework: ${framework.name}\\n`);\n }\n\n // Prompt for dev command\n const defaultDevCmd = framework?.devCommand || 'npm run dev';\n const devCommand = await prompt('Dev command', defaultDevCmd);\n\n // Write spool.config.ts\n const configContent = `import { defineConfig } from '@nicmeriano/spool';\n\nexport default defineConfig({\n devCommand: '${devCommand}',\n});\n`;\n\n fs.writeFileSync(configPath, configContent, 'utf-8');\n console.log(`\\nCreated spool.config.ts`);\n\n // Create .spool directory\n const spoolDir = path.join(cwd, '.spool');\n if (!fs.existsSync(spoolDir)) {\n fs.mkdirSync(spoolDir, { recursive: true });\n console.log('Created .spool/ directory');\n }\n\n // Add .spool/ to .gitignore\n const gitignorePath = path.join(cwd, '.gitignore');\n if (fs.existsSync(gitignorePath)) {\n const content = fs.readFileSync(gitignorePath, 'utf-8');\n if (!content.includes('.spool')) {\n fs.appendFileSync(gitignorePath, '\\n.spool/\\n');\n console.log('Added .spool/ to .gitignore');\n }\n }\n\n console.log('\\nDone! Run \"spool open\" to start.');\n console.log('\\nNote: If you\\'re not using Vite, add this script tag to your HTML manually:');\n console.log(' <script src=\"http://localhost:3142/inject.js\"></script>\\n');\n}\n","import { spawn } from 'node:child_process';\nimport { loadConfig } from '../utils/load-config.js';\nimport { findAvailablePort } from '../utils/port.js';\nimport { openBrowser } from '../utils/browser.js';\nimport { DEFAULT_SERVER_PORT } from '../constants.js';\n\nexport interface OpenOptions {\n port?: number;\n noOpen?: boolean;\n}\n\n/**\n * Run the spool open command\n */\nexport async function runOpen(cwd: string, options: OpenOptions = {}): Promise<void> {\n // Load config\n const config = await loadConfig(cwd);\n\n // Check for ANTHROPIC_API_KEY\n if (!process.env.ANTHROPIC_API_KEY) {\n console.warn('Warning: ANTHROPIC_API_KEY is not set. Claude features will not work.\\n');\n }\n\n // Find available port\n const preferredPort = options.port ?? config.port ?? DEFAULT_SERVER_PORT;\n const port = await findAvailablePort(preferredPort);\n if (port !== preferredPort) {\n console.log(`Port ${preferredPort} is in use, using ${port} instead.`);\n }\n\n // Start the server\n console.log(`Starting Spool server on port ${port}...`);\n const { startServer } = await import('@nicmeriano/spool-server');\n\n // Start server (runs in background)\n startServer({ port, cwd });\n\n // Spawn dev command if configured\n if (config.devCommand) {\n console.log(`Starting dev server: ${config.devCommand}`);\n const [cmd, ...args] = config.devCommand.split(' ');\n const child = spawn(cmd, args, {\n cwd,\n stdio: 'inherit',\n shell: true,\n });\n\n child.on('error', (error) => {\n console.error(`Dev server error: ${error.message}`);\n });\n\n // Clean up on exit\n process.on('SIGINT', () => {\n child.kill();\n process.exit(0);\n });\n process.on('SIGTERM', () => {\n child.kill();\n process.exit(0);\n });\n }\n\n // Open browser\n if (!options.noOpen) {\n const shellUrl = `http://localhost:${port}`;\n console.log(`\\nOpening Spool at ${shellUrl}\\n`);\n\n // Wait a moment for server to start\n setTimeout(() => {\n openBrowser(shellUrl);\n }, 1000);\n } else {\n console.log(`\\nSpool server running at http://localhost:${port}\\n`);\n }\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n/**\n * Load spool.config.ts from the project root\n */\nexport async function loadConfig(cwd: string): Promise<import('../config.js').SpoolConfig> {\n const configPath = path.resolve(cwd, 'spool.config.ts');\n\n if (!fs.existsSync(configPath)) {\n throw new Error(\n `No spool.config.ts found in ${cwd}.\\nRun \"spool init\" to create one.`\n );\n }\n\n // Use jiti for TypeScript config loading\n const { createJiti } = await import('jiti');\n const jiti = createJiti(import.meta.url, { interopDefault: true });\n const config = await jiti.import(configPath) as { default?: import('../config.js').SpoolConfig } & import('../config.js').SpoolConfig;\n\n return config.default ?? config;\n}\n","import * as net from 'node:net';\n\n/**\n * Find an available port, starting from the preferred port\n */\nexport async function findAvailablePort(preferred: number): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n server.listen(preferred, () => {\n server.close(() => resolve(preferred));\n });\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n // Try next port\n findAvailablePort(preferred + 1).then(resolve).catch(reject);\n } else {\n reject(err);\n }\n });\n });\n}\n","import { exec } from 'node:child_process';\n\n/**\n * Open a URL in the default browser\n */\nexport function openBrowser(url: string): void {\n const platform = process.platform;\n let command: string;\n\n if (platform === 'darwin') {\n command = `open \"${url}\"`;\n } else if (platform === 'win32') {\n command = `start \"${url}\"`;\n } else {\n command = `xdg-open \"${url}\"`;\n }\n\n exec(command, (error) => {\n if (error) {\n console.error(`Could not open browser: ${error.message}`);\n }\n });\n}\n","export const DEFAULT_SERVER_PORT = 3142;\nexport const DEFAULT_APP_PORT = 5173;\n","import { runInit } from './commands/init.js';\nimport { runOpen } from './commands/open.js';\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nfunction printUsage(): void {\n console.log(`\nUsage: spool <command> [options]\n\nCommands:\n init Initialize spool in your project\n open Start spool server and open the UI\n\nOptions (open):\n --port <n> Server port (default: 3142)\n --no-open Don't open browser automatically\n\nExamples:\n spool init\n spool open\n spool open --port 4000\n spool open --no-open\n`);\n}\n\nasync function main(): Promise<void> {\n const cwd = process.cwd();\n\n switch (command) {\n case 'init':\n await runInit(cwd);\n break;\n\n case 'open': {\n const portIndex = args.indexOf('--port');\n const port = portIndex !== -1 ? parseInt(args[portIndex + 1], 10) : undefined;\n const noOpen = args.includes('--no-open');\n await runOpen(cwd, { port, noOpen });\n break;\n }\n\n case '--help':\n case '-h':\n case undefined:\n printUsage();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n printUsage();\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n console.error(error.message || error);\n process.exit(1);\n});\n"],"mappings":"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","names":["resolve","fs","path","resolve","command","args"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface SpoolConfig {
|
|
2
|
+
/** Dev command to start the app (e.g., "npm run dev") */
|
|
3
|
+
devCommand?: string;
|
|
4
|
+
/** Server port (default: 3142) */
|
|
5
|
+
port?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Helper to define a typed spool config
|
|
9
|
+
*/
|
|
10
|
+
declare function defineConfig(config: SpoolConfig): SpoolConfig;
|
|
11
|
+
|
|
12
|
+
export { type SpoolConfig, defineConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["export interface SpoolConfig {\n /** Dev command to start the app (e.g., \"npm run dev\") */\n devCommand?: string;\n /** Server port (default: 3142) */\n port?: number;\n}\n\n/**\n * Helper to define a typed spool config\n */\nexport function defineConfig(config: SpoolConfig): SpoolConfig {\n return config;\n}\n"],"mappings":";AAUO,SAAS,aAAa,QAAkC;AAC7D,SAAO;AACT;","names":[]}
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface SpoolVitePluginOptions {
|
|
4
|
+
/** Spool server port (default: 3142) */
|
|
5
|
+
port?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Vite plugin for Spool
|
|
9
|
+
* - Removes X-Frame-Options header to allow iframe embedding
|
|
10
|
+
* - Injects the inject.js script tag
|
|
11
|
+
*/
|
|
12
|
+
declare function spool(options?: SpoolVitePluginOptions): Plugin;
|
|
13
|
+
|
|
14
|
+
export { type SpoolVitePluginOptions, spool as default, spool };
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/vite.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
function resolveServerPort(configuredPort) {
|
|
5
|
+
try {
|
|
6
|
+
const portFile = resolve(process.cwd(), ".spool/port");
|
|
7
|
+
const content = readFileSync(portFile, "utf-8").trim();
|
|
8
|
+
const discovered = parseInt(content, 10);
|
|
9
|
+
if (!isNaN(discovered) && discovered > 0) return discovered;
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
return configuredPort;
|
|
13
|
+
}
|
|
14
|
+
function reportAppUrl(appUrl, configuredPort, retriesLeft = 10) {
|
|
15
|
+
const serverPort = resolveServerPort(configuredPort);
|
|
16
|
+
fetch(`http://localhost:${serverPort}/api/app-url`, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers: { "Content-Type": "application/json" },
|
|
19
|
+
body: JSON.stringify({ url: appUrl })
|
|
20
|
+
}).then((res) => {
|
|
21
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
22
|
+
console.log(`[spool] Reported app URL ${appUrl} to server on port ${serverPort}`);
|
|
23
|
+
}).catch(() => {
|
|
24
|
+
if (retriesLeft > 0) {
|
|
25
|
+
setTimeout(() => reportAppUrl(appUrl, configuredPort, retriesLeft - 1), 1e3);
|
|
26
|
+
} else {
|
|
27
|
+
console.warn(`[spool] Failed to report app URL to server after retries`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function spool(options = {}) {
|
|
32
|
+
const configuredPort = options.port ?? 3142;
|
|
33
|
+
return {
|
|
34
|
+
name: "spool",
|
|
35
|
+
configureServer(server) {
|
|
36
|
+
server.middlewares.use((_req, res, next) => {
|
|
37
|
+
const originalWriteHead = res.writeHead.bind(res);
|
|
38
|
+
res.writeHead = function(...args) {
|
|
39
|
+
res.removeHeader("X-Frame-Options");
|
|
40
|
+
return originalWriteHead(...args);
|
|
41
|
+
};
|
|
42
|
+
next();
|
|
43
|
+
});
|
|
44
|
+
server.httpServer?.on("listening", () => {
|
|
45
|
+
const addr = server.httpServer?.address();
|
|
46
|
+
if (addr && typeof addr === "object") {
|
|
47
|
+
reportAppUrl(`http://localhost:${addr.port}`, configuredPort);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
transformIndexHtml() {
|
|
52
|
+
const serverPort = resolveServerPort(configuredPort);
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
tag: "script",
|
|
56
|
+
attrs: {
|
|
57
|
+
src: `http://localhost:${serverPort}/inject.js`
|
|
58
|
+
},
|
|
59
|
+
injectTo: "head"
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
var vite_default = spool;
|
|
66
|
+
export {
|
|
67
|
+
vite_default as default,
|
|
68
|
+
spool
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { Plugin } from 'vite';\n\nexport interface SpoolVitePluginOptions {\n /** Spool server port (default: 3142) */\n port?: number;\n}\n\n/**\n * Read the actual Spool server port from .spool/port (written by the server on startup).\n * Falls back to the configured port if the file doesn't exist yet.\n */\nfunction resolveServerPort(configuredPort: number): number {\n try {\n const portFile = resolve(process.cwd(), '.spool/port');\n const content = readFileSync(portFile, 'utf-8').trim();\n const discovered = parseInt(content, 10);\n if (!isNaN(discovered) && discovered > 0) return discovered;\n } catch {\n // File doesn't exist yet — server may not have started\n }\n return configuredPort;\n}\n\n/**\n * Try to POST the app URL to the Spool server, retrying if the server isn't ready yet.\n * Re-reads .spool/port on each attempt so it picks up late-starting servers.\n * Validates the response to ensure we're talking to the actual Spool server.\n */\nfunction reportAppUrl(\n appUrl: string,\n configuredPort: number,\n retriesLeft: number = 10\n): void {\n const serverPort = resolveServerPort(configuredPort);\n fetch(`http://localhost:${serverPort}/api/app-url`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ url: appUrl }),\n })\n .then((res) => {\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n console.log(`[spool] Reported app URL ${appUrl} to server on port ${serverPort}`);\n })\n .catch(() => {\n if (retriesLeft > 0) {\n setTimeout(() => reportAppUrl(appUrl, configuredPort, retriesLeft - 1), 1000);\n } else {\n console.warn(`[spool] Failed to report app URL to server after retries`);\n }\n });\n}\n\n/**\n * Vite plugin for Spool\n * - Removes X-Frame-Options header to allow iframe embedding\n * - Injects the inject.js script tag\n */\nexport function spool(options: SpoolVitePluginOptions = {}): Plugin {\n const configuredPort = options.port ?? 3142;\n\n return {\n name: 'spool',\n\n configureServer(server) {\n // Remove X-Frame-Options to allow iframe embedding\n server.middlewares.use((_req, res, next) => {\n const originalWriteHead = res.writeHead.bind(res) as typeof res.writeHead;\n res.writeHead = function (...args: Parameters<typeof originalWriteHead>) {\n res.removeHeader('X-Frame-Options');\n return originalWriteHead(...args);\n } as typeof res.writeHead;\n next();\n });\n\n // Report actual bound port to Spool server (retries if server isn't ready)\n server.httpServer?.on('listening', () => {\n const addr = server.httpServer?.address();\n if (addr && typeof addr === 'object') {\n reportAppUrl(`http://localhost:${addr.port}`, configuredPort);\n }\n });\n },\n\n transformIndexHtml() {\n const serverPort = resolveServerPort(configuredPort);\n return [\n {\n tag: 'script',\n attrs: {\n src: `http://localhost:${serverPort}/inject.js`,\n },\n injectTo: 'head',\n },\n ];\n },\n };\n}\n\nexport default spool;\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AAYxB,SAAS,kBAAkB,gBAAgC;AACzD,MAAI;AACF,UAAM,WAAW,QAAQ,QAAQ,IAAI,GAAG,aAAa;AACrD,UAAM,UAAU,aAAa,UAAU,OAAO,EAAE,KAAK;AACrD,UAAM,aAAa,SAAS,SAAS,EAAE;AACvC,QAAI,CAAC,MAAM,UAAU,KAAK,aAAa,EAAG,QAAO;AAAA,EACnD,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAOA,SAAS,aACP,QACA,gBACA,cAAsB,IAChB;AACN,QAAM,aAAa,kBAAkB,cAAc;AACnD,QAAM,oBAAoB,UAAU,gBAAgB;AAAA,IAClD,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU,EAAE,KAAK,OAAO,CAAC;AAAA,EACtC,CAAC,EACE,KAAK,CAAC,QAAQ;AACb,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,QAAQ,IAAI,MAAM,EAAE;AACjD,YAAQ,IAAI,4BAA4B,MAAM,sBAAsB,UAAU,EAAE;AAAA,EAClF,CAAC,EACA,MAAM,MAAM;AACX,QAAI,cAAc,GAAG;AACnB,iBAAW,MAAM,aAAa,QAAQ,gBAAgB,cAAc,CAAC,GAAG,GAAI;AAAA,IAC9E,OAAO;AACL,cAAQ,KAAK,0DAA0D;AAAA,IACzE;AAAA,EACF,CAAC;AACL;AAOO,SAAS,MAAM,UAAkC,CAAC,GAAW;AAClE,QAAM,iBAAiB,QAAQ,QAAQ;AAEvC,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,gBAAgB,QAAQ;AAEtB,aAAO,YAAY,IAAI,CAAC,MAAM,KAAK,SAAS;AAC1C,cAAM,oBAAoB,IAAI,UAAU,KAAK,GAAG;AAChD,YAAI,YAAY,YAAa,MAA4C;AACvE,cAAI,aAAa,iBAAiB;AAClC,iBAAO,kBAAkB,GAAG,IAAI;AAAA,QAClC;AACA,aAAK;AAAA,MACP,CAAC;AAGD,aAAO,YAAY,GAAG,aAAa,MAAM;AACvC,cAAM,OAAO,OAAO,YAAY,QAAQ;AACxC,YAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,uBAAa,oBAAoB,KAAK,IAAI,IAAI,cAAc;AAAA,QAC9D;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,qBAAqB;AACnB,YAAM,aAAa,kBAAkB,cAAc;AACnD,aAAO;AAAA,QACL;AAAA,UACE,KAAK;AAAA,UACL,OAAO;AAAA,YACL,KAAK,oBAAoB,UAAU;AAAA,UACrC;AAAA,UACA,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,eAAQ;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nicmeriano/spool",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Spool CLI - UI feedback tool for Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"spool": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./vite": {
|
|
17
|
+
"import": "./dist/vite.js",
|
|
18
|
+
"types": "./dist/vite.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"spool",
|
|
26
|
+
"claude",
|
|
27
|
+
"ui-feedback",
|
|
28
|
+
"cli"
|
|
29
|
+
],
|
|
30
|
+
"license": "UNLICENSED",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/nicmeriano/react-inspect.git",
|
|
34
|
+
"directory": "packages/spool"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"jiti": "^2.4.0",
|
|
41
|
+
"@nicmeriano/spool-server": "0.0.1"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"vite": ">=5.0.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"vite": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
53
|
+
"tsup": "^8.3.0",
|
|
54
|
+
"typescript": "^5.7.0",
|
|
55
|
+
"vite": "^6.0.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18.0.0"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsup",
|
|
62
|
+
"dev": "tsup --watch",
|
|
63
|
+
"clean": "rm -rf dist",
|
|
64
|
+
"typecheck": "tsc --noEmit"
|
|
65
|
+
}
|
|
66
|
+
}
|