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.
@@ -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 access control",
9
- "version": "2.1.0"
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 2-layer access control (network: public/ip + auth: password/google/telegram)",
16
- "version": "2.1.0",
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 with access control",
4
- "version": "2.1.0",
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: seconds, 60-86400 (default: 3600)
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 | use 60-86400 (seconds) |
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 < 60 || ttl > 86400) {
99
- return { valid: false, error: 'TTL must be between 60 and 86400 seconds (1 min to 24 hours)' };
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mygensite",
3
3
  "description": "Expose your localhost to mygen.site with access control",
4
- "version": "2.1.0",
4
+ "version": "2.2.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -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. Save it to `.claude/mygen.json`:
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": "telegram_username" }
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 email:
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
- # Framework-specific (example)
84
- npm run build
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
- npx mygensite deploy \
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
- - `--directory`: build output directory (auto-detect dist, build, out, etc.)
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
- ```bash
111
- # Find dev/start script in package.json and run it
112
- npm run dev &
113
- # Or framework-appropriate command
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
- Find the server port and create a tunnel:
228
+ Run in background and capture output:
117
229
  ```bash
118
- npx mygensite \
119
- --port {detected port} \
120
- --owner-email {email} \
121
- --access ${ARGUMENTS:-public} \
122
- --ttl 3600
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
- - Default access is public
126
- - TTL is 1 hour (shorter for tunnels)
127
- - **CRITICAL: The tunnel process must keep running.** The tunnel only works while the `npx mygensite` process is alive. If it exits, the tunnel closes immediately and users get 502. Run it in a way that stays alive (e.g. background with `&`, separate terminal, or keep the script running).
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": "static",
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
- If password-protected:
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
- If tunnel, add:
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
- The tunnel stays open as long as this process is running.
166
- Do NOT close this terminal or kill the process the tunnel will stop working.
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({ mode: 'public' });
188
- await site.updateAccess({ mode: 'password', password: 'new-password' });
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
- # Change access mode (e.g. password -> public)
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": {"mode": "public"}}'
350
+ -d '{"access": "public", "auth_method": ""}'
206
351
 
207
- # Change password
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 '{"access": {"password": "new-password"}}'
356
+ -d '{"auth_method": "password", "password": "new-password"}'
212
357
 
213
- # Add IP restriction
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 '{"access": {"mode": "ip_only", "allowed_ips": ["1.2.3.0/24"]}}'
362
+ -d '{"auth_method": "password,google", "password": "pw", "google": "alice@co.com"}'
218
363
 
219
- # Extend TTL (timer resets from now)
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 '{"ttl": 86400}'
368
+ -d '{"access": "ip", "allowed_ips": "1.2.3.0/24"}'
224
369
 
225
- # Change owner email
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 '{"owner_email": "new@company.com"}'
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 `access.password` |
237
- | Make it public | PATCH `access.mode` |
238
- | Restrict by IP | PATCH `access.allowed_ips` |
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 email | PATCH `owner_email` |
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
- - Let the slug (subdomain) be auto-generated unless the user specifies one.
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` is "password", deploy with password protection.
274
- - If `$ARGUMENTS` is empty, deploy as public.
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
- - Add `.claude/mygen.json` to `.gitignore` (contains tokens).
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).