nstantpage-agent 0.2.0

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 ADDED
@@ -0,0 +1,207 @@
1
+ # nstantpage-agent
2
+
3
+ Local development agent for [nstantpage.com](https://nstantpage.com) — run your projects on your own machine with full native performance while editing in the cloud.
4
+
5
+ ## What it does
6
+
7
+ The agent **replaces cloud Docker containers** for your project. Instead of running your dev server in a remote container (which can be slow for heavy projects), the agent:
8
+
9
+ - šŸ–„ļø Runs the dev server (Vite/Next.js) **natively on your machine**
10
+ - šŸ“ Receives file changes from the cloud editor through a secure tunnel
11
+ - šŸ” Runs TypeScript type checking locally (full speed, no container limits)
12
+ - šŸ“¦ Installs packages using your local npm/pnpm
13
+ - šŸ”— Connects to the cloud editor through a WebSocket tunnel
14
+
15
+ Your browser still uses the nstantpage.com editor — but all the heavy lifting happens on your computer.
16
+
17
+ ## Requirements
18
+
19
+ - **Node.js 18+** (20+ recommended)
20
+ - **npm**, **pnpm**, or **yarn**
21
+ - Works on **macOS**, **Windows**, and **Linux**
22
+
23
+ ## Installation & Usage
24
+
25
+ ### Quick Start (no install needed)
26
+
27
+ ```bash
28
+ npx nstantpage-agent start --project-id YOUR_PROJECT_ID
29
+ ```
30
+
31
+ ### Global Install
32
+
33
+ ```bash
34
+ npm install -g nstantpage-agent
35
+
36
+ # Then:
37
+ nstantpage login
38
+ nstantpage start --project-id YOUR_PROJECT_ID
39
+ ```
40
+
41
+ ### Step by Step
42
+
43
+ 1. **Authenticate** (one-time):
44
+ ```bash
45
+ npx nstantpage-agent login
46
+ ```
47
+ This opens your browser to authenticate with nstantpage.com.
48
+
49
+ 2. **Start the agent** in your project directory:
50
+ ```bash
51
+ cd /path/to/your/project
52
+ npx nstantpage-agent start --project-id 1234
53
+ ```
54
+
55
+ Or from anywhere:
56
+ ```bash
57
+ npx nstantpage-agent start /path/to/your/project --project-id 1234
58
+ ```
59
+
60
+ 3. **Open nstantpage.com** — you'll see the purple "Local" indicator in the header, confirming the agent is connected.
61
+
62
+ ### Project Configuration
63
+
64
+ Create a `.nstantpage.json` file in your project root to skip the `--project-id` flag:
65
+
66
+ ```json
67
+ {
68
+ "projectId": "1234"
69
+ }
70
+ ```
71
+
72
+ Then just run:
73
+ ```bash
74
+ npx nstantpage-agent start
75
+ ```
76
+
77
+ ## CLI Commands
78
+
79
+ | Command | Description |
80
+ |---------|-------------|
81
+ | `nstantpage login` | Authenticate with nstantpage.com |
82
+ | `nstantpage start [dir]` | Start the agent for a project |
83
+ | `nstantpage stop` | Stop the running agent |
84
+ | `nstantpage status` | Show agent connection status |
85
+
86
+ ### Start Options
87
+
88
+ | Flag | Default | Description |
89
+ |------|---------|-------------|
90
+ | `-p, --port <port>` | `3000` | Local dev server port |
91
+ | `-a, --api-port <port>` | `18924` | Internal API port |
92
+ | `--project-id <id>` | — | Link to a specific project |
93
+ | `--gateway <url>` | `wss://webprev.live` | Gateway URL |
94
+ | `--no-dev` | — | Don't start dev server automatically |
95
+
96
+ ## How It Works
97
+
98
+ ```
99
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” WebSocket Tunnel ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
100
+ │ nstantpage.com │ ◄──────────────────────────────► │ Your Computer │
101
+ │ (Cloud Editor) │ /live/* API + Dev traffic │ │
102
+ │ │ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │
103
+ │ - Chat with AI │ │ │ API Server │ │
104
+ │ - Edit code │ All /live/* requests │ │ (port 18924)│ │
105
+ │ - See preview │ ──────────────────────────────► │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │
106
+ │ │ │ │
107
+ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │
108
+ │ │ Preview iFrame│ │ Dev server traffic │ │ Vite/Next │ │
109
+ │ │ │◄─│─────────────────────────────────│ │ (port 3000) │ │
110
+ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │
111
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
112
+ ```
113
+
114
+ 1. **API Server** (port 18924): Handles file sync, type checking, error tracking, package installation — everything the cloud containers used to do.
115
+
116
+ 2. **Dev Server** (port 3000): Runs Vite/Next.js natively with full access to your machine's CPU and RAM. No container memory limits.
117
+
118
+ 3. **Tunnel**: Secure WebSocket connection to the gateway. Routes cloud editor requests to your local servers.
119
+
120
+ ## Testing Locally
121
+
122
+ ### Test the agent against a local gateway
123
+
124
+ ```bash
125
+ # Terminal 1: Start the gateway
126
+ cd PreviewGateway && node dist/index-v2.js
127
+
128
+ # Terminal 2: Start the agent
129
+ cd nstantpage-agent
130
+ npx tsx src/cli.ts start /path/to/project \
131
+ --project-id 1234 \
132
+ --gateway ws://localhost:4000
133
+
134
+ # Terminal 3: Verify connection
135
+ curl http://localhost:4000/live/agent-status?projectId=1234
136
+ # → {"connected":true,"projectId":"1234","agent":{"version":"0.2.0",...}}
137
+ ```
138
+
139
+ ### Test individual components
140
+
141
+ ```bash
142
+ cd nstantpage-agent
143
+
144
+ # Type check
145
+ npx tsc --noEmit
146
+
147
+ # Run in dev mode (auto-reloads)
148
+ npx tsx src/cli.ts start . --project-id test --gateway ws://localhost:4000
149
+
150
+ # Build
151
+ npm run build
152
+
153
+ # Run built version
154
+ node dist/cli.js start . --project-id test --gateway ws://localhost:4000
155
+ ```
156
+
157
+ ## Troubleshooting
158
+
159
+ ### "Not authenticated" error
160
+ Run `npx nstantpage-agent login` to authenticate.
161
+
162
+ ### Port already in use
163
+ Use `--port` and `--api-port` flags to change ports:
164
+ ```bash
165
+ npx nstantpage-agent start --port 3001 --api-port 18925
166
+ ```
167
+
168
+ ### Agent connects but preview doesn't load
169
+ - Make sure your project has a valid `package.json`
170
+ - Check that `npx vite` or `npx next dev` works in your project directory
171
+ - Try `--no-dev` to skip auto-starting the dev server, then start it manually
172
+
173
+ ### Windows: `npx` not found
174
+ Make sure Node.js is in your PATH. Try running from PowerShell or Git Bash.
175
+
176
+ ### macOS: Permission denied
177
+ If you get EACCES errors, the agent may need access to the project directory. Check file permissions.
178
+
179
+ ## Platform Notes
180
+
181
+ | Platform | Status | Notes |
182
+ |----------|--------|-------|
183
+ | macOS (Intel/Apple Silicon) | āœ… Full support | Recommended |
184
+ | Windows 10/11 | āœ… Full support | Use PowerShell or Git Bash |
185
+ | Linux (Ubuntu/Debian/Fedora) | āœ… Full support | Tested on Ubuntu 22.04+ |
186
+ | WSL2 | āœ… Works | Run inside WSL2 terminal |
187
+
188
+ ## Architecture
189
+
190
+ The agent consists of these modules:
191
+
192
+ - **`localServer.ts`** — HTTP server handling all `/live/*` API endpoints locally
193
+ - **`tunnel.ts`** — WebSocket client that connects to the gateway and routes requests
194
+ - **`devServer.ts`** — Manages local Vite/Next.js/Express dev server processes
195
+ - **`fileManager.ts`** — File I/O (replaces `docker cp`)
196
+ - **`checker.ts`** — TypeScript type checking (replaces container-based `tsc`)
197
+ - **`packageInstaller.ts`** — npm/pnpm/yarn package installation
198
+ - **`errorStore.ts`** — Error tracking and deduplication
199
+
200
+ ## Development
201
+
202
+ ```bash
203
+ cd nstantpage-agent
204
+ npm install
205
+ npm run build # Compile TypeScript
206
+ npm run dev # Run in watch mode with tsx
207
+ ```
@@ -0,0 +1,35 @@
1
+ /**
2
+ * TypeScript Checker — runs tsc to detect type errors.
3
+ * Replaces the container-based type checking from the gateway.
4
+ *
5
+ * Uses the TypeScript compiler API or `npx tsc --noEmit` for checking.
6
+ */
7
+ import { type StructuredError } from './errorStore.js';
8
+ export interface CheckerOptions {
9
+ projectDir: string;
10
+ projectId: string;
11
+ }
12
+ export declare class Checker {
13
+ private projectDir;
14
+ private projectId;
15
+ constructor(options: CheckerOptions);
16
+ /**
17
+ * Run TypeScript type checking on the project.
18
+ * Returns structured errors.
19
+ */
20
+ checkTypes(filterFiles?: string[]): Promise<StructuredError[]>;
21
+ /**
22
+ * Run a full check: type check + collect build errors from Vite stderr.
23
+ */
24
+ fullCheck(filterFiles?: string[]): Promise<{
25
+ typeErrors: StructuredError[];
26
+ allErrors: StructuredError[];
27
+ }>;
28
+ /**
29
+ * Validate hard patterns in source code (fast, stateless).
30
+ * Checks for common anti-patterns that break builds.
31
+ */
32
+ validateHardPatterns(filePath: string, code: string): Promise<StructuredError[]>;
33
+ private runTsc;
34
+ private parseTscOutput;
35
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * TypeScript Checker — runs tsc to detect type errors.
3
+ * Replaces the container-based type checking from the gateway.
4
+ *
5
+ * Uses the TypeScript compiler API or `npx tsc --noEmit` for checking.
6
+ */
7
+ import { spawn } from 'child_process';
8
+ import path from 'path';
9
+ import { existsSync } from 'fs';
10
+ import { parseErrorString } from './errorStore.js';
11
+ export class Checker {
12
+ projectDir;
13
+ projectId;
14
+ constructor(options) {
15
+ this.projectDir = options.projectDir;
16
+ this.projectId = options.projectId;
17
+ }
18
+ /**
19
+ * Run TypeScript type checking on the project.
20
+ * Returns structured errors.
21
+ */
22
+ async checkTypes(filterFiles) {
23
+ const tsconfigPath = path.join(this.projectDir, 'tsconfig.json');
24
+ if (!existsSync(tsconfigPath)) {
25
+ // No tsconfig — skip type checking
26
+ return [];
27
+ }
28
+ try {
29
+ const output = await this.runTsc(filterFiles);
30
+ return this.parseTscOutput(output, filterFiles);
31
+ }
32
+ catch (err) {
33
+ console.error(` [Checker] Type check failed: ${err.message}`);
34
+ return [];
35
+ }
36
+ }
37
+ /**
38
+ * Run a full check: type check + collect build errors from Vite stderr.
39
+ */
40
+ async fullCheck(filterFiles) {
41
+ const typeErrors = await this.checkTypes(filterFiles);
42
+ const allErrors = [...typeErrors];
43
+ // Deduplicate
44
+ const seen = new Set();
45
+ const deduped = allErrors.filter(e => {
46
+ if (seen.has(e.signature))
47
+ return false;
48
+ seen.add(e.signature);
49
+ return true;
50
+ });
51
+ return { typeErrors, allErrors: deduped };
52
+ }
53
+ /**
54
+ * Validate hard patterns in source code (fast, stateless).
55
+ * Checks for common anti-patterns that break builds.
56
+ */
57
+ async validateHardPatterns(filePath, code) {
58
+ const errors = [];
59
+ // Check for common issues
60
+ const lines = code.split('\n');
61
+ for (let i = 0; i < lines.length; i++) {
62
+ const line = lines[i];
63
+ const lineNum = i + 1;
64
+ // Detect require() in ES module context
65
+ if (/\brequire\s*\(/.test(line) && !line.includes('// eslint-disable')) {
66
+ // Only flag if it's not in a comment
67
+ const trimmed = line.trim();
68
+ if (!trimmed.startsWith('//') && !trimmed.startsWith('*')) {
69
+ errors.push({
70
+ file: filePath,
71
+ line: lineNum,
72
+ message: 'require() is not allowed in ES modules. Use import instead.',
73
+ type: 'hard',
74
+ signature: `hard:${filePath}:${lineNum}:require-in-esm`,
75
+ });
76
+ }
77
+ }
78
+ // Detect document/window in server-side code
79
+ if (filePath.startsWith('server/') && (/\bdocument\b/.test(line) || /\bwindow\b/.test(line))) {
80
+ const trimmed = line.trim();
81
+ if (!trimmed.startsWith('//') && !trimmed.startsWith('*')) {
82
+ errors.push({
83
+ file: filePath,
84
+ line: lineNum,
85
+ message: 'Browser APIs (document/window) cannot be used in server-side code.',
86
+ type: 'hard',
87
+ signature: `hard:${filePath}:${lineNum}:browser-api-in-server`,
88
+ });
89
+ }
90
+ }
91
+ }
92
+ return errors;
93
+ }
94
+ runTsc(filterFiles) {
95
+ return new Promise((resolve) => {
96
+ const args = ['tsc', '--noEmit', '--pretty', 'false'];
97
+ // If filtering specific files, pass them directly
98
+ // Otherwise use the project tsconfig
99
+ if (filterFiles && filterFiles.length > 0 && filterFiles.length <= 10) {
100
+ // For a small number of files, check them directly
101
+ // This is faster than checking the whole project
102
+ args.push('--project', path.join(this.projectDir, 'tsconfig.json'));
103
+ }
104
+ const proc = spawn('npx', args, {
105
+ cwd: this.projectDir,
106
+ env: { ...process.env, NODE_OPTIONS: '--max-old-space-size=512' },
107
+ shell: true,
108
+ stdio: ['pipe', 'pipe', 'pipe'],
109
+ });
110
+ let stdout = '';
111
+ let stderr = '';
112
+ proc.stdout?.on('data', (d) => { stdout += d.toString(); });
113
+ proc.stderr?.on('data', (d) => { stderr += d.toString(); });
114
+ proc.on('close', () => {
115
+ // tsc outputs errors to stdout
116
+ resolve(stdout || stderr);
117
+ });
118
+ proc.on('error', () => {
119
+ resolve(stderr || stdout);
120
+ });
121
+ // Timeout after 30 seconds
122
+ setTimeout(() => {
123
+ try {
124
+ proc.kill();
125
+ }
126
+ catch { }
127
+ resolve(stdout || stderr || 'Type check timed out');
128
+ }, 30_000);
129
+ });
130
+ }
131
+ parseTscOutput(output, filterFiles) {
132
+ if (!output.trim())
133
+ return [];
134
+ const lines = output.split('\n').filter(l => l.trim());
135
+ const errors = [];
136
+ const filterSet = filterFiles ? new Set(filterFiles.map(f => f.replace(/^\.\//, ''))) : null;
137
+ for (const line of lines) {
138
+ // Skip non-error lines
139
+ if (!line.includes('error TS') && !line.includes('warning TS'))
140
+ continue;
141
+ const parsed = parseErrorString(line, 'type');
142
+ // Filter to relevant files if specified
143
+ if (filterSet && parsed.file && !filterSet.has(parsed.file)) {
144
+ continue;
145
+ }
146
+ errors.push(parsed);
147
+ }
148
+ return errors;
149
+ }
150
+ }
151
+ //# sourceMappingURL=checker.js.map
package/dist/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * nstantpage-agent CLI
4
+ *
5
+ * Usage:
6
+ * nstantpage login — Authenticate with nstantpage.com
7
+ * nstantpage start [dir] — Start agent for a project directory
8
+ * nstantpage stop — Stop running agent
9
+ * nstantpage status — Show agent status
10
+ *
11
+ * The agent replaces Docker containers — your project runs natively on your
12
+ * machine with full performance, and the cloud editor connects through a tunnel.
13
+ */
14
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * nstantpage-agent CLI
4
+ *
5
+ * Usage:
6
+ * nstantpage login — Authenticate with nstantpage.com
7
+ * nstantpage start [dir] — Start agent for a project directory
8
+ * nstantpage stop — Stop running agent
9
+ * nstantpage status — Show agent status
10
+ *
11
+ * The agent replaces Docker containers — your project runs natively on your
12
+ * machine with full performance, and the cloud editor connects through a tunnel.
13
+ */
14
+ import { Command } from 'commander';
15
+ import chalk from 'chalk';
16
+ import { loginCommand } from './commands/login.js';
17
+ import { startCommand } from './commands/start.js';
18
+ import { statusCommand } from './commands/status.js';
19
+ const program = new Command();
20
+ program
21
+ .name('nstantpage')
22
+ .description('Local development agent for nstantpage.com — run projects on your machine, preview in the cloud')
23
+ .version('0.2.0');
24
+ program
25
+ .command('login')
26
+ .description('Authenticate with nstantpage.com')
27
+ .action(loginCommand);
28
+ program
29
+ .command('start')
30
+ .description('Start the agent for a project (replaces cloud containers)')
31
+ .argument('[directory]', 'Project directory (defaults to current directory)', '.')
32
+ .option('-p, --port <port>', 'Local dev server port', '3000')
33
+ .option('-a, --api-port <port>', 'Local API server port (internal)', '18924')
34
+ .option('--project-id <id>', 'Link to a specific project ID')
35
+ .option('--gateway <url>', 'Gateway URL', 'wss://webprev.live')
36
+ .option('--no-dev', 'Skip starting the dev server (start manually later)')
37
+ .action(startCommand);
38
+ program
39
+ .command('stop')
40
+ .description('Stop the running agent')
41
+ .action(async () => {
42
+ console.log(chalk.yellow('Stopping agent...'));
43
+ // Send SIGTERM to running agent process
44
+ const { getConfig } = await import('./config.js');
45
+ const conf = getConfig();
46
+ const pid = conf.get('agentPid');
47
+ if (pid) {
48
+ try {
49
+ process.kill(pid, 'SIGTERM');
50
+ conf.delete('agentPid');
51
+ console.log(chalk.green('Agent stopped'));
52
+ }
53
+ catch {
54
+ console.log(chalk.gray('Agent was not running'));
55
+ conf.delete('agentPid');
56
+ }
57
+ }
58
+ else {
59
+ console.log(chalk.gray('No agent running'));
60
+ }
61
+ });
62
+ program
63
+ .command('status')
64
+ .description('Show agent status')
65
+ .action(statusCommand);
66
+ program.parse();
67
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Login command — authenticate with nstantpage.com
3
+ */
4
+ export declare function loginCommand(): Promise<void>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Login command — authenticate with nstantpage.com
3
+ */
4
+ import chalk from 'chalk';
5
+ import open from 'open';
6
+ import http from 'http';
7
+ import { getConfig } from '../config.js';
8
+ export async function loginCommand() {
9
+ const conf = getConfig();
10
+ // Check if already logged in
11
+ const existingToken = conf.get('token');
12
+ if (existingToken) {
13
+ console.log(chalk.green('āœ“ Already authenticated'));
14
+ console.log(chalk.gray(' Run "nstantpage login --force" to re-authenticate'));
15
+ return;
16
+ }
17
+ console.log(chalk.blue('šŸ” Authenticating with nstantpage.com...\n'));
18
+ // Start local callback server
19
+ const callbackPort = 18923;
20
+ const token = await new Promise((resolve, reject) => {
21
+ const server = http.createServer((req, res) => {
22
+ const url = new URL(req.url, `http://localhost:${callbackPort}`);
23
+ const token = url.searchParams.get('token');
24
+ if (token) {
25
+ res.writeHead(200, { 'Content-Type': 'text/html' });
26
+ res.end(`
27
+ <html>
28
+ <body style="font-family: sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #0f172a; color: #f8fafc;">
29
+ <div style="text-align: center;">
30
+ <h1>āœ“ Authenticated!</h1>
31
+ <p>You can close this tab and return to the terminal.</p>
32
+ </div>
33
+ </body>
34
+ </html>
35
+ `);
36
+ server.close();
37
+ resolve(token);
38
+ }
39
+ else {
40
+ res.writeHead(400);
41
+ res.end('Missing token');
42
+ }
43
+ });
44
+ server.listen(callbackPort, () => {
45
+ const loginUrl = `https://nstantpage.com/auth/agent?callback=http://localhost:${callbackPort}/callback`;
46
+ console.log(chalk.gray(` Opening browser to authenticate...`));
47
+ console.log(chalk.gray(` If browser doesn't open, visit: ${loginUrl}\n`));
48
+ open(loginUrl).catch(() => {
49
+ // Browser didn't open, user can copy URL
50
+ });
51
+ });
52
+ // Timeout after 5 minutes
53
+ setTimeout(() => {
54
+ server.close();
55
+ reject(new Error('Authentication timed out'));
56
+ }, 5 * 60 * 1000);
57
+ });
58
+ conf.set('token', token);
59
+ console.log(chalk.green('\nāœ“ Successfully authenticated!'));
60
+ console.log(chalk.gray(' Run "nstantpage start" to connect a project'));
61
+ }
62
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Start command — launch the nstantpage agent
3
+ *
4
+ * Architecture:
5
+ * 1. Start local API server (handles /live/* requests for file sync, checks, etc.)
6
+ * 2. Start local dev server (Vite/Next.js — runs project on user's machine)
7
+ * 3. Connect tunnel to gateway (relays requests from cloud to local machine)
8
+ *
9
+ * The agent fully replaces Docker containers:
10
+ * - Files are on the user's disk (no docker cp needed)
11
+ * - Dev server runs natively (no container overhead)
12
+ * - Type checking runs locally (full IDE speed)
13
+ * - Package installation uses local npm/pnpm
14
+ */
15
+ interface StartOptions {
16
+ port: string;
17
+ apiPort: string;
18
+ projectId?: string;
19
+ gateway: string;
20
+ noDev?: boolean;
21
+ }
22
+ export declare function startCommand(directory: string, options: StartOptions): Promise<void>;
23
+ export {};