mygensite 2.1.0 → 2.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/.claude-plugin/marketplace.json +4 -4
- package/.claude-plugin/plugin.json +3 -3
- package/README.md +20 -3
- package/examples/README.md +31 -0
- package/examples/manage-service.mjs +88 -0
- package/examples/static-deploy.mjs +63 -0
- package/examples/tunnel-basic.mjs +49 -0
- package/lib/Tunnel.js +1 -1
- package/lib/deploy.js +1 -1
- package/lib/validate.js +19 -4
- package/package.json +1 -1
- package/skills/share/SKILL.md +237 -63
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
"email": "dev@theconnectsoft.com"
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
|
-
"description": "Share your localhost or static sites via mygen.site with 2-layer
|
|
9
|
-
"version": "2.
|
|
8
|
+
"description": "Share your localhost or static sites via mygen.site with guided access control and 2-layer security",
|
|
9
|
+
"version": "2.2.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "mygensite",
|
|
14
14
|
"source": "./",
|
|
15
|
-
"description": "Share what you built via mygen.site — tunnels and static deploys with
|
|
16
|
-
"version": "2.
|
|
15
|
+
"description": "Share what you built via mygen.site — tunnels and static deploys with interactive access control setup on first deploy (public/IP + password/Google/Telegram auth), slug reuse, background tunnel keeper, and unlimited TTL for static sites",
|
|
16
|
+
"version": "2.2.0",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "TheConnectSoft"
|
|
19
19
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mygensite",
|
|
3
|
-
"description": "Share your localhost or static sites via mygen.site
|
|
4
|
-
"version": "2.
|
|
3
|
+
"description": "Share your localhost or static sites via mygen.site — guided access control setup, 2-layer security (network + auth), unlimited TTL for static deploys",
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TheConnectSoft",
|
|
7
7
|
"email": "dev@theconnectsoft.com",
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"homepage": "https://mygen.site",
|
|
11
11
|
"repository": "https://github.com/theconnectsoft/mygensite",
|
|
12
12
|
"license": "MIT",
|
|
13
|
-
"keywords": ["tunnel", "deploy", "share", "mygen.site"]
|
|
13
|
+
"keywords": ["tunnel", "deploy", "share", "mygen.site", "access-control"]
|
|
14
14
|
}
|
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ const tunnel = await mygensite({
|
|
|
47
47
|
telegram: '123456', // required when auth_method includes 'telegram'
|
|
48
48
|
|
|
49
49
|
owner_email: 'alice@company.com', // optional: email or Telegram username for dashboard
|
|
50
|
-
ttl: 3600, // optional:
|
|
50
|
+
ttl: 3600, // optional: 60-86400 seconds (default: 3600)
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
// Result
|
|
@@ -77,7 +77,7 @@ const site = await mygensite.deploy({
|
|
|
77
77
|
auth_method: 'password', // optional: CSV of 'password', 'google', 'telegram'
|
|
78
78
|
password: 'secret', // when auth_method includes 'password'
|
|
79
79
|
owner_email: 'alice@company.com', // optional: email or Telegram username for dashboard
|
|
80
|
-
ttl: 86400, // optional: seconds (default: 3600)
|
|
80
|
+
ttl: 86400, // optional: 0 (unlimited) or 60-259200 seconds (default: 3600)
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
// Result
|
|
@@ -134,6 +134,23 @@ await site.delete();
|
|
|
134
134
|
|
|
135
135
|
Both layers apply sequentially: IP check → auth check.
|
|
136
136
|
|
|
137
|
+
## TTL (Time to Live)
|
|
138
|
+
|
|
139
|
+
| type | range | unlimited |
|
|
140
|
+
|------|-------|-----------|
|
|
141
|
+
| Tunnel | 60–86400 seconds (max 24h) | not supported |
|
|
142
|
+
| Static | 60–259200 seconds (max 3 days) | `ttl: 0` — requires at least one auth method |
|
|
143
|
+
|
|
144
|
+
Unlimited TTL (`ttl: 0`) is only available for static deploys and requires at least one auth method (password, google, or telegram). You cannot remove auth from a service with unlimited TTL without also setting a finite TTL.
|
|
145
|
+
|
|
146
|
+
## Examples
|
|
147
|
+
|
|
148
|
+
Full runnable examples in [`examples/`](https://github.com/theconnectsoft/mygensite/tree/main/examples):
|
|
149
|
+
|
|
150
|
+
- **[tunnel-basic.mjs](https://github.com/theconnectsoft/mygensite/blob/main/examples/tunnel-basic.mjs)** — Tunnel with signal handling and heartbeat (background-friendly)
|
|
151
|
+
- **[static-deploy.mjs](https://github.com/theconnectsoft/mygensite/blob/main/examples/static-deploy.mjs)** — Static deploy with unlimited TTL and auth
|
|
152
|
+
- **[manage-service.mjs](https://github.com/theconnectsoft/mygensite/blob/main/examples/manage-service.mjs)** — Settings, TTL, redeploy, delete via manage()
|
|
153
|
+
|
|
137
154
|
## Constraints
|
|
138
155
|
|
|
139
156
|
### Slug (subdomain)
|
|
@@ -182,7 +199,7 @@ validate.validateTTL(30); // { valid: false, error: '...' }
|
|
|
182
199
|
|--------|-------|------|-----|
|
|
183
200
|
| 400 | `invalid_slug` | slug format invalid | use 3-63 chars, lowercase alphanum + hyphen (e.g. `my-app-1`) |
|
|
184
201
|
| 400 | `reserved_slug` | slug is reserved | choose different slug. reserved: www, api, dashboard, admin, etc. |
|
|
185
|
-
| 400 | `invalid_ttl` | TTL out of range |
|
|
202
|
+
| 400 | `invalid_ttl` | TTL out of range | tunnels: 60-86400s, static: 0 (unlimited) or 60-259200s. Unlimited requires auth. |
|
|
186
203
|
| 400 | `invalid_access` | bad access mode | use: public, ip |
|
|
187
204
|
| 401 | `unauthorized` | wrong admin_token | use the `admin_token` from tunnel creation response |
|
|
188
205
|
| 404 | `not_found` | service not found | verify slug is correct and tunnel is active |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
## tunnel-basic.mjs
|
|
4
|
+
|
|
5
|
+
Expose a local server to the internet. Stays alive in background with signal handling and heartbeat.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node examples/tunnel-basic.mjs 3000 my-app
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## static-deploy.mjs
|
|
12
|
+
|
|
13
|
+
Deploy a directory as a static site. Supports unlimited TTL (`ttl: 0`).
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node examples/static-deploy.mjs ./dist my-site
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## manage-service.mjs
|
|
20
|
+
|
|
21
|
+
Manage an existing service — change settings, extend TTL, redeploy, delete.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
node examples/manage-service.mjs my-site tok_xxx public
|
|
25
|
+
node examples/manage-service.mjs my-site tok_xxx password
|
|
26
|
+
node examples/manage-service.mjs my-site tok_xxx extend
|
|
27
|
+
node examples/manage-service.mjs my-site tok_xxx redeploy
|
|
28
|
+
node examples/manage-service.mjs my-site tok_xxx delete
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Actions: `public`, `password`, `google`, `telegram`, `ip`, `extend`, `redeploy`, `delete`, `purge`
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service management examples — settings changes, TTL, redeploy, delete.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* node manage-service.mjs <slug> <admin_token> <action>
|
|
6
|
+
*
|
|
7
|
+
* Actions: public, password, google, telegram, ip, extend, redeploy, delete, purge
|
|
8
|
+
*/
|
|
9
|
+
import mygensite from '../localtunnel.js';
|
|
10
|
+
|
|
11
|
+
const slug = process.argv[2];
|
|
12
|
+
const admin_token = process.argv[3];
|
|
13
|
+
const action = process.argv[4] || 'public';
|
|
14
|
+
|
|
15
|
+
if (!slug || !admin_token) {
|
|
16
|
+
console.error('Usage: node manage-service.mjs <slug> <admin_token> <action>');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const site = mygensite.manage({ slug, admin_token });
|
|
21
|
+
|
|
22
|
+
switch (action) {
|
|
23
|
+
// --- Make public (remove all auth) ---
|
|
24
|
+
case 'public':
|
|
25
|
+
await site.updateAccess({ access: 'public', auth_method: '' });
|
|
26
|
+
console.log('Made public');
|
|
27
|
+
break;
|
|
28
|
+
|
|
29
|
+
// --- Add password protection ---
|
|
30
|
+
case 'password':
|
|
31
|
+
await site.updateAccess({ auth_method: 'password', password: 'new-secret' });
|
|
32
|
+
console.log('Password set');
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
// --- Add Google OAuth ---
|
|
36
|
+
case 'google':
|
|
37
|
+
await site.updateAccess({
|
|
38
|
+
auth_method: 'password,google',
|
|
39
|
+
password: 'backup-pass',
|
|
40
|
+
google: 'alice@company.com,bob@company.com',
|
|
41
|
+
});
|
|
42
|
+
console.log('Google + password auth set');
|
|
43
|
+
break;
|
|
44
|
+
|
|
45
|
+
// --- Add Telegram auth ---
|
|
46
|
+
case 'telegram':
|
|
47
|
+
await site.updateAccess({
|
|
48
|
+
auth_method: 'telegram',
|
|
49
|
+
telegram: '123456789',
|
|
50
|
+
});
|
|
51
|
+
console.log('Telegram auth set');
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
// --- Restrict by IP ---
|
|
55
|
+
case 'ip':
|
|
56
|
+
await site.updateAccess({ access: 'ip', allowed_ips: '10.0.0.0/8,192.168.1.0/24' });
|
|
57
|
+
console.log('IP restriction set');
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
// --- Extend TTL (0 = unlimited for static) ---
|
|
61
|
+
case 'extend':
|
|
62
|
+
await site.extendTTL(0); // unlimited
|
|
63
|
+
console.log('TTL set to unlimited');
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
// --- Redeploy with new files ---
|
|
67
|
+
case 'redeploy':
|
|
68
|
+
await site.redeploy('./dist');
|
|
69
|
+
console.log('Redeployed');
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
// --- Soft delete (recoverable) ---
|
|
73
|
+
case 'delete':
|
|
74
|
+
await site.delete();
|
|
75
|
+
console.log('Soft deleted');
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
// --- Purge delete (S3 files removed, unrecoverable) ---
|
|
79
|
+
case 'purge':
|
|
80
|
+
await site.delete(true);
|
|
81
|
+
console.log('Purged');
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
console.error(`Unknown action: ${action}`);
|
|
86
|
+
console.error('Actions: public, password, google, telegram, ip, extend, redeploy, delete, purge');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static file deployment examples.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* node static-deploy.mjs <directory> [subdomain]
|
|
6
|
+
*
|
|
7
|
+
* Deploys a directory to {subdomain}.mygen.site.
|
|
8
|
+
* TTL 0 = unlimited (no expiry).
|
|
9
|
+
*/
|
|
10
|
+
import mygensite from '../localtunnel.js';
|
|
11
|
+
|
|
12
|
+
const directory = process.argv[2];
|
|
13
|
+
const subdomain = process.argv[3] || undefined;
|
|
14
|
+
|
|
15
|
+
if (!directory) {
|
|
16
|
+
console.error('Usage: node static-deploy.mjs <directory> [subdomain]');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// --- Example 1: Deploy a directory (public, unlimited) ---
|
|
21
|
+
|
|
22
|
+
const site = await mygensite.deploy({
|
|
23
|
+
directory,
|
|
24
|
+
subdomain,
|
|
25
|
+
owner_email: 'you@example.com',
|
|
26
|
+
access: 'public',
|
|
27
|
+
ttl: 0, // unlimited — static only (max 259200 for timed)
|
|
28
|
+
// admin_token: 'tok_xxx', // pass this to redeploy an existing site
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log(JSON.stringify({
|
|
32
|
+
url: site.url,
|
|
33
|
+
slug: site.slug,
|
|
34
|
+
admin_token: site.admin_token,
|
|
35
|
+
password: site.password || null,
|
|
36
|
+
expires_at: site.expires_at || null, // null when unlimited
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// --- Example 2: Deploy with password protection ---
|
|
41
|
+
/*
|
|
42
|
+
const protectedSite = await mygensite.deploy({
|
|
43
|
+
directory: './dist',
|
|
44
|
+
subdomain: 'private-demo',
|
|
45
|
+
owner_email: 'you@example.com',
|
|
46
|
+
auth_method: 'password',
|
|
47
|
+
password: 'secret123',
|
|
48
|
+
ttl: 86400, // 1 day
|
|
49
|
+
});
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
// --- Example 3: Deploy from in-memory files ---
|
|
54
|
+
/*
|
|
55
|
+
const site = await mygensite.deploy({
|
|
56
|
+
files: [
|
|
57
|
+
{ name: 'index.html', content: Buffer.from('<h1>Hello</h1>'), contentType: 'text/html' },
|
|
58
|
+
{ name: 'style.css', content: Buffer.from('body { color: red; }'), contentType: 'text/css' },
|
|
59
|
+
],
|
|
60
|
+
owner_email: 'you@example.com',
|
|
61
|
+
ttl: 0,
|
|
62
|
+
});
|
|
63
|
+
*/
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic tunnel example — expose a local server to the internet.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* node tunnel-basic.mjs [port] [subdomain]
|
|
6
|
+
*
|
|
7
|
+
* The tunnel stays alive in the background with signal handling and heartbeat.
|
|
8
|
+
* Ideal for AI agents that need to keep a tunnel running.
|
|
9
|
+
*/
|
|
10
|
+
import mygensite from '../localtunnel.js';
|
|
11
|
+
|
|
12
|
+
const port = Number(process.argv[2]) || 3000;
|
|
13
|
+
const subdomain = process.argv[3] || undefined;
|
|
14
|
+
|
|
15
|
+
const tunnel = await mygensite({
|
|
16
|
+
port,
|
|
17
|
+
subdomain,
|
|
18
|
+
owner_email: 'you@example.com',
|
|
19
|
+
access: 'public',
|
|
20
|
+
ttl: 3600, // 1 hour (max 86400 for tunnels)
|
|
21
|
+
// admin_token: 'tok_xxx', // pass this to reconnect to an existing tunnel
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// First line: JSON for programmatic consumption
|
|
25
|
+
console.log(JSON.stringify({
|
|
26
|
+
url: tunnel.url,
|
|
27
|
+
slug: tunnel.clientId,
|
|
28
|
+
admin_token: tunnel.admin_token,
|
|
29
|
+
password: tunnel.password || null,
|
|
30
|
+
expires_at: tunnel.expires_at || null,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Graceful shutdown
|
|
34
|
+
process.on('SIGINT', () => { tunnel.close(); process.exit(0); });
|
|
35
|
+
process.on('SIGTERM', () => { tunnel.close(); process.exit(0); });
|
|
36
|
+
|
|
37
|
+
tunnel.on('close', () => {
|
|
38
|
+
console.error('[tunnel] closed');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
tunnel.on('error', (err) => {
|
|
43
|
+
console.error('[tunnel] error:', err.message);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Heartbeat every 5 minutes
|
|
47
|
+
setInterval(() => {
|
|
48
|
+
console.error(`[tunnel] alive — ${tunnel.url}`);
|
|
49
|
+
}, 5 * 60 * 1000);
|
package/lib/Tunnel.js
CHANGED
|
@@ -25,7 +25,7 @@ module.exports = class Tunnel extends EventEmitter {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
if (opts.ttl != null) {
|
|
28
|
-
const ttlCheck = validateTTL(Number(opts.ttl));
|
|
28
|
+
const ttlCheck = validateTTL(Number(opts.ttl), { allowUnlimited: false, max: 86400 });
|
|
29
29
|
if (!ttlCheck.valid) {
|
|
30
30
|
throw new Error(ttlCheck.error);
|
|
31
31
|
}
|
package/lib/deploy.js
CHANGED
|
@@ -106,7 +106,7 @@ async function deploy(options) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
if (ttl != null) {
|
|
109
|
-
const ttlCheck = validateTTL(Number(ttl));
|
|
109
|
+
const ttlCheck = validateTTL(Number(ttl), { allowUnlimited: true, max: 259200 });
|
|
110
110
|
if (!ttlCheck.valid) {
|
|
111
111
|
throw new Error(ttlCheck.error);
|
|
112
112
|
}
|
package/lib/validate.js
CHANGED
|
@@ -88,15 +88,25 @@ function validateFilePath(name) {
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Validate TTL value.
|
|
91
|
-
* @param {number} ttl - TTL in seconds
|
|
91
|
+
* @param {number} ttl - TTL in seconds (0 = unlimited, for static deploys only)
|
|
92
|
+
* @param {object} [opts] - Options
|
|
93
|
+
* @param {boolean} [opts.allowUnlimited=true] - Whether to allow 0 (unlimited)
|
|
94
|
+
* @param {number} [opts.max=259200] - Maximum TTL in seconds
|
|
92
95
|
* @returns {{ valid: boolean, error?: string }}
|
|
93
96
|
*/
|
|
94
|
-
function validateTTL(ttl) {
|
|
97
|
+
function validateTTL(ttl, opts) {
|
|
98
|
+
const allowUnlimited = opts?.allowUnlimited !== false;
|
|
99
|
+
const max = opts?.max || 259200;
|
|
95
100
|
if (typeof ttl !== 'number' || !Number.isFinite(ttl)) {
|
|
96
101
|
return { valid: false, error: 'TTL must be a number' };
|
|
97
102
|
}
|
|
98
|
-
if (ttl
|
|
99
|
-
return
|
|
103
|
+
if (ttl === 0) {
|
|
104
|
+
return allowUnlimited
|
|
105
|
+
? { valid: true }
|
|
106
|
+
: { valid: false, error: 'Unlimited TTL (0) is not allowed for tunnels' };
|
|
107
|
+
}
|
|
108
|
+
if (ttl < 60 || ttl > max) {
|
|
109
|
+
return { valid: false, error: `TTL must be ${allowUnlimited ? '0 (unlimited) or ' : ''}between 60 and ${max} seconds` };
|
|
100
110
|
}
|
|
101
111
|
return { valid: true };
|
|
102
112
|
}
|
|
@@ -195,6 +205,11 @@ function validateAccessParams(opts) {
|
|
|
195
205
|
}
|
|
196
206
|
}
|
|
197
207
|
|
|
208
|
+
// Unlimited TTL requires auth
|
|
209
|
+
if (opts.ttl === 0 && !authMethod) {
|
|
210
|
+
return { valid: false, error: 'Unlimited TTL (0) requires at least one auth method (password, google, or telegram)' };
|
|
211
|
+
}
|
|
212
|
+
|
|
198
213
|
return { valid: true };
|
|
199
214
|
}
|
|
200
215
|
|
package/package.json
CHANGED
package/skills/share/SKILL.md
CHANGED
|
@@ -8,6 +8,7 @@ allowed-tools: Bash, Read, Glob, Grep, Write, Edit, Agent
|
|
|
8
8
|
# Share (mygen.site)
|
|
9
9
|
|
|
10
10
|
Share what the user built via a `{name}.mygen.site` URL.
|
|
11
|
+
Tunnel/deploy creation uses the **mygensite** Node.js library. Settings changes and deletion can use curl.
|
|
11
12
|
|
|
12
13
|
## Owner Identity
|
|
13
14
|
|
|
@@ -22,19 +23,38 @@ Ownership matching is case-insensitive.
|
|
|
22
23
|
### First use
|
|
23
24
|
1. Check if `.claude/mygen.json` exists
|
|
24
25
|
2. If not, ask the user **once**: "What email or Telegram username should I use for service management? (used for dashboard login)"
|
|
25
|
-
3.
|
|
26
|
+
3. **Do NOT validate the format as email-only.** The value can be a plain username without `@` — that's a valid Telegram username. Accept any non-empty string the user provides.
|
|
27
|
+
4. Save it to `.claude/mygen.json`:
|
|
26
28
|
```json
|
|
27
29
|
{ "owner_email": "user@company.com" }
|
|
28
30
|
```
|
|
29
|
-
or for Telegram users:
|
|
31
|
+
or for Telegram users (no `@`, not an email — this is valid):
|
|
30
32
|
```json
|
|
31
|
-
{ "owner_email": "
|
|
33
|
+
{ "owner_email": "mytelegramuser" }
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
### Subsequent uses
|
|
35
37
|
- Read owner from `.claude/mygen.json` automatically
|
|
36
38
|
- If the user says "change my email" or "change my owner", update the file
|
|
37
39
|
|
|
40
|
+
## Slug (Domain) Management
|
|
41
|
+
|
|
42
|
+
### Reuse by default
|
|
43
|
+
- When `.claude/mygen.json` already has a service entry for the **same context** (same port for tunnels, same build directory for static), reuse that slug and `admin_token`.
|
|
44
|
+
- This means running `/share` repeatedly for the same service always keeps the same URL.
|
|
45
|
+
|
|
46
|
+
### When to ask for a new domain
|
|
47
|
+
Ask the user what domain they want **only when** the context clearly requires a **different** share than what's already saved:
|
|
48
|
+
- Sharing a **different port** than the existing tunnel
|
|
49
|
+
- Sharing a **different directory** or a second static site
|
|
50
|
+
- User explicitly says "share this on a different URL" or "new domain"
|
|
51
|
+
|
|
52
|
+
Ask: "What subdomain would you like? (e.g. `my-api` → `my-api.mygen.site`, blank for auto-generated)"
|
|
53
|
+
- If blank → let the server auto-generate.
|
|
54
|
+
|
|
55
|
+
### Domain can be changed later
|
|
56
|
+
Always inform the user (at least on first use): **"You can change the domain anytime by asking, e.g. 'change my domain to new-name'."**
|
|
57
|
+
|
|
38
58
|
## Decision: Tunnel vs Static Deploy
|
|
39
59
|
|
|
40
60
|
Decide **automatically** based on the criteria below. Do not ask the user.
|
|
@@ -62,11 +82,7 @@ Check if mygensite is installed:
|
|
|
62
82
|
npx mygensite --help 2>/dev/null || npm install -g mygensite
|
|
63
83
|
```
|
|
64
84
|
|
|
65
|
-
Load owner
|
|
66
|
-
```bash
|
|
67
|
-
cat .claude/mygen.json 2>/dev/null
|
|
68
|
-
```
|
|
69
|
-
If missing, ask the user and save it.
|
|
85
|
+
Load config from `.claude/mygen.json`. If missing, ask the user for owner identity.
|
|
70
86
|
|
|
71
87
|
### 1. Analyze the project
|
|
72
88
|
|
|
@@ -75,84 +91,190 @@ Quickly assess the project structure:
|
|
|
75
91
|
- Check for build output directories
|
|
76
92
|
- Check for server start scripts
|
|
77
93
|
- Check for running local servers (lsof -i -P | grep LISTEN)
|
|
94
|
+
- Check `.claude/mygen.json` for existing services matching current context
|
|
95
|
+
|
|
96
|
+
### 1.5. Access Control Setup (first deploy only)
|
|
97
|
+
|
|
98
|
+
**Skip this step** if reusing an existing service from `.claude/mygen.json` (same port/directory context). Settings are already saved.
|
|
99
|
+
|
|
100
|
+
On the **first deploy** of a new service, ask the user these questions:
|
|
101
|
+
|
|
102
|
+
#### Q1. Network access
|
|
103
|
+
> **Who should be able to access this?**
|
|
104
|
+
> 1. **Public** — anyone with the link (default)
|
|
105
|
+
> 2. **IP-restricted** — only your current IP
|
|
106
|
+
|
|
107
|
+
If IP-restricted, detect the user's public IP:
|
|
108
|
+
```bash
|
|
109
|
+
curl -s https://ifconfig.me
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Q2. Authentication (recommended)
|
|
113
|
+
> **Do you want to add authentication?** (recommended for security)
|
|
114
|
+
> 1. **Password** — visitors enter a password to access
|
|
115
|
+
> 2. **Google OAuth** — only specific Google accounts can access
|
|
116
|
+
> 3. **Telegram** — only specific Telegram users can access
|
|
117
|
+
> 4. **None** — no authentication
|
|
118
|
+
|
|
119
|
+
If the user picks auth, ask for the details:
|
|
120
|
+
- **Password**: "What password should visitors use?" (or auto-generate one)
|
|
121
|
+
- **Google**: "Which email addresses should have access? (comma-separated)"
|
|
122
|
+
- **Telegram**: "Which Telegram user IDs should have access? (comma-separated)"
|
|
123
|
+
|
|
124
|
+
Multiple auth methods can be combined (e.g. password + Google — visitors can use either).
|
|
125
|
+
|
|
126
|
+
#### Applying the choices
|
|
127
|
+
|
|
128
|
+
Use the answers to set these parameters in the deploy/tunnel script:
|
|
129
|
+
```js
|
|
130
|
+
access: 'public', // or 'ip'
|
|
131
|
+
allowed_ips: ['1.2.3.4'], // only when access='ip', user's detected IP
|
|
132
|
+
auth_method: 'password', // or 'google', 'telegram', 'password,google', or omit
|
|
133
|
+
password: 'chosen-password', // when auth_method includes 'password'
|
|
134
|
+
google: 'alice@co.com', // when auth_method includes 'google'
|
|
135
|
+
telegram: '123456789', // when auth_method includes 'telegram'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Save the chosen access settings in `.claude/mygen.json` alongside the service entry so they can be reused on redeploy.
|
|
139
|
+
|
|
140
|
+
> **Tip**: If `$ARGUMENTS` explicitly says "public" or "password", skip the questions and use that directly.
|
|
78
141
|
|
|
79
142
|
### 2-A. Static deploy
|
|
80
143
|
|
|
81
144
|
Build first if needed:
|
|
82
145
|
```bash
|
|
83
|
-
#
|
|
84
|
-
|
|
146
|
+
npm run build # or framework-appropriate command
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Write a temporary deploy script `.claude/mygen-deploy.mjs` and run it:
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
import localtunnel from 'mygensite';
|
|
153
|
+
|
|
154
|
+
const site = await localtunnel.deploy({
|
|
155
|
+
directory: './dist', // auto-detect: dist, build, out, .next, or '.'
|
|
156
|
+
subdomain: '{slug_or_undefined}', // from mygen.json or omit for auto
|
|
157
|
+
owner_email: '{owner_email}',
|
|
158
|
+
access: '{access}', // from access control setup
|
|
159
|
+
auth_method: '{auth_method}', // from access control setup, omit if none
|
|
160
|
+
password: '{password}', // when auth_method includes 'password'
|
|
161
|
+
google: '{emails}', // when auth_method includes 'google'
|
|
162
|
+
telegram: '{ids}', // when auth_method includes 'telegram'
|
|
163
|
+
ttl: 86400,
|
|
164
|
+
admin_token: '{token_or_undefined}', // if redeploying existing service
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
console.log(JSON.stringify({
|
|
168
|
+
url: site.url, slug: site.slug,
|
|
169
|
+
admin_token: site.admin_token,
|
|
170
|
+
password: site.password || null,
|
|
171
|
+
expires_at: site.expires_at || null,
|
|
172
|
+
}));
|
|
85
173
|
```
|
|
86
174
|
|
|
87
|
-
Deploy:
|
|
88
175
|
```bash
|
|
89
|
-
|
|
90
|
-
--directory ./dist \
|
|
91
|
-
--owner-email {email} \
|
|
92
|
-
--access ${ARGUMENTS:-public} \
|
|
93
|
-
--ttl 86400
|
|
176
|
+
node .claude/mygen-deploy.mjs && rm .claude/mygen-deploy.mjs
|
|
94
177
|
```
|
|
95
178
|
|
|
96
|
-
|
|
97
|
-
- If no build directory exists and there are only HTML files, use current directory (`.`)
|
|
98
|
-
- Default access is public
|
|
99
|
-
- TTL is 24 hours
|
|
179
|
+
Parse the JSON output, update `.claude/mygen.json`.
|
|
100
180
|
|
|
101
181
|
### 2-B. Tunnel
|
|
102
182
|
|
|
103
183
|
Check if a local server is running; if not, start it:
|
|
104
184
|
```bash
|
|
105
|
-
# Check running ports
|
|
106
185
|
lsof -i -P | grep LISTEN | grep -E ':(3000|5173|8000|8080|4200|5000)'
|
|
107
186
|
```
|
|
108
187
|
|
|
109
|
-
If no server is running
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
188
|
+
If no server is running, start it in background first.
|
|
189
|
+
|
|
190
|
+
Write a tunnel keeper script `.claude/mygen-tunnel.mjs`:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
import localtunnel from 'mygensite';
|
|
194
|
+
|
|
195
|
+
const tunnel = await localtunnel({
|
|
196
|
+
port: {detected_port},
|
|
197
|
+
subdomain: '{slug_or_undefined}',
|
|
198
|
+
owner_email: '{owner_email}',
|
|
199
|
+
access: '{access}',
|
|
200
|
+
auth_method: '{auth_method}', // from access control setup, omit if none
|
|
201
|
+
password: '{password}', // when auth_method includes 'password'
|
|
202
|
+
google: '{emails}', // when auth_method includes 'google'
|
|
203
|
+
telegram: '{ids}', // when auth_method includes 'telegram'
|
|
204
|
+
ttl: 3600,
|
|
205
|
+
admin_token: '{token_or_undefined}',
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Output connection info as JSON (first line)
|
|
209
|
+
console.log(JSON.stringify({
|
|
210
|
+
url: tunnel.url, slug: tunnel.clientId,
|
|
211
|
+
admin_token: tunnel.admin_token,
|
|
212
|
+
password: tunnel.password || null,
|
|
213
|
+
expires_at: tunnel.expires_at || null,
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
// Keep alive — graceful shutdown on signals
|
|
217
|
+
process.on('SIGINT', () => { tunnel.close(); process.exit(0); });
|
|
218
|
+
process.on('SIGTERM', () => { tunnel.close(); process.exit(0); });
|
|
219
|
+
tunnel.on('close', () => { console.error('Tunnel closed'); process.exit(1); });
|
|
220
|
+
tunnel.on('error', (err) => { console.error('Tunnel error:', err.message); });
|
|
221
|
+
|
|
222
|
+
// Heartbeat every 5 min
|
|
223
|
+
setInterval(() => {
|
|
224
|
+
console.error(`[tunnel] alive — ${tunnel.url}`);
|
|
225
|
+
}, 5 * 60 * 1000);
|
|
114
226
|
```
|
|
115
227
|
|
|
116
|
-
|
|
228
|
+
Run in background and capture output:
|
|
117
229
|
```bash
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
230
|
+
node .claude/mygen-tunnel.mjs > .claude/mygen-tunnel-out.log 2>.claude/mygen-tunnel-err.log &
|
|
231
|
+
TUNNEL_PID=$!
|
|
232
|
+
echo $TUNNEL_PID > .claude/mygen-tunnel.pid
|
|
233
|
+
|
|
234
|
+
# Wait for tunnel to initialize
|
|
235
|
+
for i in $(seq 1 10); do
|
|
236
|
+
if [ -s .claude/mygen-tunnel-out.log ]; then break; fi
|
|
237
|
+
sleep 1
|
|
238
|
+
done
|
|
239
|
+
cat .claude/mygen-tunnel-out.log
|
|
123
240
|
```
|
|
124
241
|
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
242
|
+
- **CRITICAL**: The tunnel keeper script stays running in background. Do NOT delete it while active.
|
|
243
|
+
- PID is saved to `.claude/mygen-tunnel.pid` for later management.
|
|
244
|
+
- The script handles SIGINT/SIGTERM gracefully and logs heartbeats to stderr.
|
|
128
245
|
|
|
129
246
|
### 3. Save results and inform the user
|
|
130
247
|
|
|
131
|
-
**Always** save the result (slug, admin_token) to `.claude/mygen.json`:
|
|
248
|
+
**Always** save the result (slug, admin_token, access settings) to `.claude/mygen.json`:
|
|
132
249
|
```json
|
|
133
250
|
{
|
|
134
251
|
"owner_email": "user@company.com",
|
|
135
252
|
"services": {
|
|
136
253
|
"{slug}": {
|
|
137
254
|
"admin_token": "tok_xxx",
|
|
138
|
-
"type": "
|
|
255
|
+
"type": "tunnel",
|
|
256
|
+
"port": 3000,
|
|
139
257
|
"url": "https://{slug}.mygen.site",
|
|
258
|
+
"access": "public",
|
|
259
|
+
"auth_method": "password",
|
|
140
260
|
"created_at": "2025-06-01T12:00:00Z"
|
|
141
261
|
}
|
|
142
262
|
}
|
|
143
263
|
}
|
|
144
264
|
```
|
|
145
265
|
|
|
146
|
-
After deploy/tunnel completes, inform the user **concisely
|
|
266
|
+
After deploy/tunnel completes, inform the user **concisely** based on settings:
|
|
147
267
|
|
|
268
|
+
**Public, no auth:**
|
|
148
269
|
```
|
|
149
270
|
URL: https://{slug}.mygen.site
|
|
150
271
|
|
|
151
272
|
Share this link — anyone can access it.
|
|
152
273
|
Auto-expires in 24 hours.
|
|
274
|
+
You can change the domain or access settings anytime — just ask.
|
|
153
275
|
```
|
|
154
276
|
|
|
155
|
-
|
|
277
|
+
**With password auth:**
|
|
156
278
|
```
|
|
157
279
|
URL: https://{slug}.mygen.site
|
|
158
280
|
Password: {password}
|
|
@@ -160,10 +282,32 @@ Password: {password}
|
|
|
160
282
|
Share the link and password together.
|
|
161
283
|
```
|
|
162
284
|
|
|
163
|
-
|
|
285
|
+
**With Google auth:**
|
|
286
|
+
```
|
|
287
|
+
URL: https://{slug}.mygen.site
|
|
288
|
+
Allowed: {emails}
|
|
289
|
+
|
|
290
|
+
Only the listed Google accounts can access (via OAuth login).
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**With IP restriction:**
|
|
294
|
+
```
|
|
295
|
+
URL: https://{slug}.mygen.site
|
|
296
|
+
Restricted to: {ip}
|
|
297
|
+
|
|
298
|
+
Only accessible from the allowed IP address.
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
If tunnel:
|
|
164
302
|
```
|
|
165
|
-
|
|
166
|
-
|
|
303
|
+
Tunnel running in background (PID: {pid}).
|
|
304
|
+
It stays open as long as the process is alive.
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 4. Add to .gitignore
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
grep -q '.claude/' .gitignore 2>/dev/null || echo '.claude/' >> .gitignore
|
|
167
311
|
```
|
|
168
312
|
|
|
169
313
|
## Settings Changes (PATCH)
|
|
@@ -184,8 +328,9 @@ const site = mygensite.manage({
|
|
|
184
328
|
admin_token: '{admin_token}', // read from .claude/mygen.json
|
|
185
329
|
});
|
|
186
330
|
|
|
187
|
-
await site.updateAccess({
|
|
188
|
-
await site.updateAccess({
|
|
331
|
+
await site.updateAccess({ access: 'public', auth_method: '' });
|
|
332
|
+
await site.updateAccess({ auth_method: 'password', password: 'new-password' });
|
|
333
|
+
await site.updateAccess({ auth_method: 'password,google', password: 'pw', google: 'a@co.com' });
|
|
189
334
|
await site.extendTTL(86400);
|
|
190
335
|
await site.redeploy('./dist'); // only when files changed
|
|
191
336
|
await site.delete();
|
|
@@ -198,46 +343,47 @@ await site.delete();
|
|
|
198
343
|
Read the admin_token for the service from `.claude/mygen.json`:
|
|
199
344
|
|
|
200
345
|
```bash
|
|
201
|
-
#
|
|
346
|
+
# Make fully public (no auth)
|
|
202
347
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
203
348
|
-H "Authorization: Bearer {admin_token}" \
|
|
204
349
|
-H "Content-Type: application/json" \
|
|
205
|
-
-d '{"access":
|
|
350
|
+
-d '{"access": "public", "auth_method": ""}'
|
|
206
351
|
|
|
207
|
-
#
|
|
352
|
+
# Add password auth
|
|
208
353
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
209
354
|
-H "Authorization: Bearer {admin_token}" \
|
|
210
355
|
-H "Content-Type: application/json" \
|
|
211
|
-
-d '{"
|
|
356
|
+
-d '{"auth_method": "password", "password": "new-password"}'
|
|
212
357
|
|
|
213
|
-
# Add
|
|
358
|
+
# Add Google OAuth + password
|
|
214
359
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
215
360
|
-H "Authorization: Bearer {admin_token}" \
|
|
216
361
|
-H "Content-Type: application/json" \
|
|
217
|
-
-d '{"
|
|
362
|
+
-d '{"auth_method": "password,google", "password": "pw", "google": "alice@co.com"}'
|
|
218
363
|
|
|
219
|
-
#
|
|
364
|
+
# Restrict by IP
|
|
220
365
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
221
366
|
-H "Authorization: Bearer {admin_token}" \
|
|
222
367
|
-H "Content-Type: application/json" \
|
|
223
|
-
-d '{"
|
|
368
|
+
-d '{"access": "ip", "allowed_ips": "1.2.3.0/24"}'
|
|
224
369
|
|
|
225
|
-
#
|
|
370
|
+
# Extend TTL (timer resets from now)
|
|
226
371
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
227
372
|
-H "Authorization: Bearer {admin_token}" \
|
|
228
373
|
-H "Content-Type: application/json" \
|
|
229
|
-
-d '{"
|
|
374
|
+
-d '{"ttl": 86400}'
|
|
230
375
|
```
|
|
231
376
|
|
|
232
377
|
### PATCH vs Redeploy
|
|
233
378
|
|
|
234
379
|
| Request | Method |
|
|
235
380
|
|---------|--------|
|
|
236
|
-
| Change password | PATCH `
|
|
237
|
-
| Make it public | PATCH `access
|
|
238
|
-
|
|
|
381
|
+
| Change password | PATCH `auth_method` + `password` |
|
|
382
|
+
| Make it public | PATCH `access` + `auth_method: ""` |
|
|
383
|
+
| Add Google auth | PATCH `auth_method` + `google` |
|
|
384
|
+
| Restrict by IP | PATCH `access: "ip"` + `allowed_ips` |
|
|
239
385
|
| Extend time | PATCH `ttl` |
|
|
240
|
-
| Change
|
|
386
|
+
| Change owner | PATCH `owner_email` (email or Telegram username) |
|
|
241
387
|
| Update content (files changed) | Redeploy (`deploy --admin-token`) |
|
|
242
388
|
| Upload new files | Redeploy (`deploy --admin-token`) |
|
|
243
389
|
|
|
@@ -265,12 +411,40 @@ curl -X DELETE "https://mygen.site/api/services/{slug}?purge=true" \
|
|
|
265
411
|
|
|
266
412
|
After deleting, also remove the service entry from `.claude/mygen.json`.
|
|
267
413
|
|
|
414
|
+
## Tunnel Management
|
|
415
|
+
|
|
416
|
+
### Check if tunnel is running
|
|
417
|
+
```bash
|
|
418
|
+
if [ -f .claude/mygen-tunnel.pid ]; then
|
|
419
|
+
PID=$(cat .claude/mygen-tunnel.pid)
|
|
420
|
+
kill -0 $PID 2>/dev/null && echo "Running (PID $PID)" || echo "Stopped"
|
|
421
|
+
fi
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Stop tunnel
|
|
425
|
+
```bash
|
|
426
|
+
if [ -f .claude/mygen-tunnel.pid ]; then
|
|
427
|
+
kill $(cat .claude/mygen-tunnel.pid) 2>/dev/null
|
|
428
|
+
rm -f .claude/mygen-tunnel.pid .claude/mygen-tunnel-out.log .claude/mygen-tunnel-err.log
|
|
429
|
+
fi
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Restart tunnel
|
|
433
|
+
Stop the old one, then run Step 2-B again. The same slug and admin_token will be reused from `.claude/mygen.json`.
|
|
434
|
+
|
|
435
|
+
## Reference
|
|
436
|
+
|
|
437
|
+
- API docs: https://mygen.site/docs (NOT /api/docs)
|
|
438
|
+
- LLM-readable docs: https://mygen.site/llms.txt
|
|
439
|
+
|
|
268
440
|
## Important Notes
|
|
269
441
|
|
|
270
|
-
- Do not ask the user for technical choices. Decide automatically.
|
|
271
|
-
-
|
|
442
|
+
- Do not ask the user for technical choices (tunnel vs static). Decide automatically.
|
|
443
|
+
- **Reuse the same slug** for the same context. Only ask for a new domain when sharing something different (different port, different directory, etc.).
|
|
272
444
|
- Handle errors yourself (port conflicts, build failures, etc.).
|
|
273
|
-
- If `$ARGUMENTS`
|
|
274
|
-
- If `$ARGUMENTS` is empty,
|
|
445
|
+
- If `$ARGUMENTS` explicitly specifies access (e.g. "password", "public"), skip the access control questions and use that directly.
|
|
446
|
+
- If `$ARGUMENTS` is empty, ask the access control questions on first deploy.
|
|
275
447
|
- admin_token is issued **only once**. If lost, it cannot be recovered. Always save to `.claude/mygen.json`.
|
|
276
|
-
-
|
|
448
|
+
- **For tunnel/deploy creation, use the mygensite Node.js library** (not curl). For PATCH settings and DELETE, curl is fine.
|
|
449
|
+
- Clean up temp scripts after use. Keep `.claude/mygen-tunnel.mjs` alive while tunnel is running.
|
|
450
|
+
- Add `.claude/` to `.gitignore` (contains tokens).
|