mygensite 2.1.0 → 2.4.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, 2-layer security, rules, and hooks",
9
+ "version": "2.4.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, rules for safe defaults and deployment awareness, hooks for git/tunnel safety, and multi-tunnel support",
16
+ "version": "2.4.0",
17
17
  "author": {
18
18
  "name": "TheConnectSoft"
19
19
  },
@@ -21,4 +21,4 @@
21
21
  "license": "MIT"
22
22
  }
23
23
  ]
24
- }
24
+ }
@@ -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, 2-layer security, rules for safe defaults, hooks for git/tunnel safety, multi-tunnel support",
4
+ "version": "2.3.5",
5
5
  "author": {
6
6
  "name": "TheConnectSoft",
7
7
  "email": "dev@theconnectsoft.com",
@@ -10,5 +10,11 @@
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"]
14
- }
13
+ "keywords": [
14
+ "tunnel",
15
+ "deploy",
16
+ "share",
17
+ "mygen.site",
18
+ "access-control"
19
+ ]
20
+ }
package/README.en.md CHANGED
@@ -43,7 +43,7 @@ Below are some common arguments. See `mygensite --help` for all options.
43
43
  - `--access` network layer: `public`, `ip` (default: `public`)
44
44
  - `--auth-method` auth layer (CSV): `password`, `google`, `telegram`
45
45
  - `--password` password (when auth-method includes password)
46
- - `--google` allowed Google email(s)
46
+ - `--google` allowed Google email(s) or `@domain.com` patterns
47
47
  - `--telegram` allowed Telegram user ID(s)
48
48
  - `--owner-email` owner email for dashboard management
49
49
  - `--ttl` tunnel TTL in seconds, 60-86400 (default: 3600)
@@ -111,7 +111,7 @@ const mygensite = require('mygensite');
111
111
  - `auth_method` (string) Auth methods CSV: `password`, `google`, `telegram`. Default: none.
112
112
  - `password` (string) Password (when auth_method includes 'password'). Auto-generated if omitted.
113
113
  - `allowed_ips` (string[]) IP whitelist for `ip` access. Supports CIDR notation.
114
- - `google` (string|string[]) Allowed Google email(s) (when auth_method includes 'google').
114
+ - `google` (string|string[]) Allowed Google email(s) or domain patterns like `@company.com` (when auth_method includes 'google').
115
115
  - `telegram` (string|string[]) Allowed Telegram user ID(s) (when auth_method includes 'telegram').
116
116
  - `owner_email` (string) Owner email for dashboard management.
117
117
  - `ttl` (number) Tunnel TTL in seconds (60-86400). Default: 3600.
@@ -296,7 +296,7 @@ console.log(site.expires_at); // "2025-06-02T12:00:00Z"
296
296
  | `access` | string | | `public` | Network access: `public`, `ip`. |
297
297
  | `auth_method` | string | | — | Auth methods CSV: `password`, `google`, `telegram`. |
298
298
  | `password` | string | | auto | Password (when auth_method includes 'password'). |
299
- | `google` | string\|string[] | | — | Allowed Google email(s) (when auth_method includes 'google'). |
299
+ | `google` | string\|string[] | | — | Allowed Google email(s) or `@domain.com` patterns (when auth_method includes 'google'). |
300
300
  | `telegram` | string\|string[] | | — | Allowed Telegram user ID(s) (when auth_method includes 'telegram'). |
301
301
  | `allowed_ips` | string[] | | — | IP whitelist for `ip` access. CIDR supported. |
302
302
  | `owner_email` | string | | — | Owner email for dashboard management. |
package/README.ko.md CHANGED
@@ -43,7 +43,7 @@ mygen.site에 연결하여 터널을 생성하고, 사용할 URL을 알려줍니
43
43
  - `--access` 네트워크 접근 제어: `public`, `ip` (기본: `public`)
44
44
  - `--auth-method` 인증 방식 (CSV): `password`, `google`, `telegram`
45
45
  - `--password` 접근 제어 비밀번호 (미지정 시 자동 생성)
46
- - `--google` Google OAuth 허용 이메일 (CSV)
46
+ - `--google` Google OAuth 허용 이메일 또는 `@domain.com` 패턴 (CSV)
47
47
  - `--telegram` Telegram 허용 사용자 ID (CSV)
48
48
  - `--owner-email` 대시보드 관리용 소유자 이메일
49
49
  - `--ttl` 터널 유효 시간(초), 60-86400 (기본: 3600)
@@ -111,7 +111,7 @@ const mygensite = require('mygensite');
111
111
  - `auth_method` (string) 인증 방식 (Layer 2, CSV): `password`, `google`, `telegram`. 복수 지정 시 콤마 구분 (예: `password,google`).
112
112
  - `password` (string) 접근 제어 비밀번호. `auth_method`에 `password` 포함 시 사용. 미지정 시 자동 생성.
113
113
  - `allowed_ips` (string[]) `access`가 `ip`일 때 허용할 IP 목록. CIDR 표기 지원.
114
- - `google` (string[]) `auth_method`에 `google` 포함 시 허용할 이메일 목록.
114
+ - `google` (string[]) `auth_method`에 `google` 포함 시 허용할 이메일 또는 `@domain.com` 도메인 패턴 목록.
115
115
  - `telegram` (string[]) `auth_method`에 `telegram` 포함 시 허용할 Telegram 사용자 ID 목록.
116
116
  - `owner_email` (string) 대시보드 관리용 소유자 이메일.
117
117
  - `ttl` (number) 터널 유효 시간(초), 60-86400. 기본값: 3600.
@@ -294,7 +294,7 @@ console.log(site.expires_at); // "2025-06-02T12:00:00Z"
294
294
  | `auth_method` | string | | — | 인증 방식 (Layer 2, CSV): `password`, `google`, `telegram`. |
295
295
  | `password` | string | | 자동 | 접근 제어 비밀번호. `auth_method`에 `password` 포함 시 사용. |
296
296
  | `allowed_ips` | string[] | | — | `access`가 `ip`일 때 허용할 IP. CIDR 지원. |
297
- | `google` | string[] | | — | `auth_method`에 `google` 포함 시 허용할 이메일 목록. |
297
+ | `google` | string[] | | — | `auth_method`에 `google` 포함 시 허용할 이메일 또는 `@domain.com` 패턴 목록. |
298
298
  | `telegram` | string[] | | — | `auth_method`에 `telegram` 포함 시 허용할 Telegram 사용자 ID 목록. |
299
299
  | `owner_email` | string | | — | 대시보드 관리용 소유자 이메일. |
300
300
  | `ttl` | number | | 3600 | 사이트 유효 시간(초), 60-86400. |
package/README.md CHANGED
@@ -43,11 +43,12 @@ const tunnel = await mygensite({
43
43
  // Layer 2: Auth method(s)
44
44
  auth_method: 'password', // optional: CSV of 'password', 'google', 'telegram'
45
45
  password: 'secret', // required when auth_method includes 'password'
46
- google: 'alice@company.com', // required when auth_method includes 'google'
46
+ google: 'alice@company.com,@company.com', // required when auth_method includes 'google' (supports @domain.com patterns)
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
+ token: 'mgs_xxx', // optional: API token (or set MYGENSITE_TOKEN env)
51
52
  });
52
53
 
53
54
  // Result
@@ -77,7 +78,8 @@ const site = await mygensite.deploy({
77
78
  auth_method: 'password', // optional: CSV of 'password', 'google', 'telegram'
78
79
  password: 'secret', // when auth_method includes 'password'
79
80
  owner_email: 'alice@company.com', // optional: email or Telegram username for dashboard
80
- ttl: 86400, // optional: seconds (default: 3600)
81
+ ttl: 86400, // optional: 0 (unlimited) or 60-259200 seconds (default: 3600)
82
+ token: 'mgs_xxx', // optional: API token (or set MYGENSITE_TOKEN env)
81
83
  });
82
84
 
83
85
  // Result
@@ -128,12 +130,54 @@ await site.delete();
128
130
  |-------|----------|
129
131
  | _(empty)_ | no authentication (default) |
130
132
  | `password` | password form + cookie session |
131
- | `google` | Google OAuth → allowed emails only |
133
+ | `google` | Google OAuth → allowed emails only (supports `@domain.com` patterns) |
132
134
  | `telegram` | Telegram login → allowed user IDs only |
133
135
  | `password,google` | password OR Google (user picks) |
134
136
 
135
137
  Both layers apply sequentially: IP check → auth check.
136
138
 
139
+ ## API Token Authentication
140
+
141
+ By default, service creation (tunnel/deploy) is restricted to allowed IPs. To create from any IP, generate an API token from the [dashboard](https://mygen.site/dashboard/api).
142
+
143
+ ```js
144
+ // Option 1: Pass token directly
145
+ const tunnel = await mygensite({ port: 3000, token: 'mgs_xxx' });
146
+ const site = await mygensite.deploy({ directory: './dist', token: 'mgs_xxx' });
147
+
148
+ // Option 2: Set environment variable (auto-detected)
149
+ // export MYGENSITE_TOKEN=mgs_xxx
150
+ const tunnel = await mygensite({ port: 3000 }); // token picked up from env
151
+ ```
152
+
153
+ ```bash
154
+ # CLI
155
+ mygensite --port 3000 --token mgs_xxx
156
+ mygensite deploy -d ./dist --token mgs_xxx
157
+
158
+ # Or via environment variable
159
+ MYGENSITE_TOKEN=mgs_xxx mygensite --port 3000
160
+ ```
161
+
162
+ When using an API token, `owner_email` is automatically set to the token owner's account email.
163
+
164
+ ## TTL (Time to Live)
165
+
166
+ | type | range | unlimited |
167
+ |------|-------|-----------|
168
+ | Tunnel | 60–86400 seconds (max 24h) | not supported |
169
+ | Static | 60–259200 seconds (max 3 days) | `ttl: 0` — requires at least one auth method |
170
+
171
+ 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.
172
+
173
+ ## Examples
174
+
175
+ Full runnable examples in [`examples/`](https://github.com/theconnectsoft/mygensite/tree/main/examples):
176
+
177
+ - **[tunnel-basic.mjs](https://github.com/theconnectsoft/mygensite/blob/main/examples/tunnel-basic.mjs)** — Tunnel with signal handling and heartbeat (background-friendly)
178
+ - **[static-deploy.mjs](https://github.com/theconnectsoft/mygensite/blob/main/examples/static-deploy.mjs)** — Static deploy with unlimited TTL and auth
179
+ - **[manage-service.mjs](https://github.com/theconnectsoft/mygensite/blob/main/examples/manage-service.mjs)** — Settings, TTL, redeploy, delete via manage()
180
+
137
181
  ## Constraints
138
182
 
139
183
  ### Slug (subdomain)
@@ -182,7 +226,7 @@ validate.validateTTL(30); // { valid: false, error: '...' }
182
226
  |--------|-------|------|-----|
183
227
  | 400 | `invalid_slug` | slug format invalid | use 3-63 chars, lowercase alphanum + hyphen (e.g. `my-app-1`) |
184
228
  | 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) |
229
+ | 400 | `invalid_ttl` | TTL out of range | tunnels: 60-86400s, static: 0 (unlimited) or 60-259200s. Unlimited requires auth. |
186
230
  | 400 | `invalid_access` | bad access mode | use: public, ip |
187
231
  | 401 | `unauthorized` | wrong admin_token | use the `admin_token` from tunnel creation response |
188
232
  | 404 | `not_found` | service not found | verify slug is correct and tunnel is active |
@@ -211,6 +255,10 @@ mygensite deploy -d ./dist -s private-demo --auth-method password --password 'my
211
255
 
212
256
  # Redeploy (reuse admin_token)
213
257
  mygensite deploy -d ./dist-v2 -s demo --admin-token tok_xxx
258
+
259
+ # With API token (skip IP restriction)
260
+ mygensite --port 3000 -s my-app --token mgs_xxx
261
+ MYGENSITE_TOKEN=mgs_xxx mygensite deploy -d ./dist -s demo
214
262
  ```
215
263
 
216
264
  ## curl Deploy
package/bin/lt.js CHANGED
@@ -38,7 +38,7 @@ yargs
38
38
  describe: 'Password for password auth',
39
39
  })
40
40
  .option('google', {
41
- describe: 'Allowed Google emails (CSV)',
41
+ describe: 'Allowed Google emails or @domain.com patterns (CSV)',
42
42
  })
43
43
  .option('telegram', {
44
44
  describe: 'Allowed Telegram user IDs (CSV)',
@@ -52,6 +52,9 @@ yargs
52
52
  })
53
53
  .option('admin-token', {
54
54
  describe: 'Admin token for redeployment',
55
+ })
56
+ .option('token', {
57
+ describe: 'API token (mgs_xxx) for authentication. Also reads MYGENSITE_TOKEN env.',
55
58
  });
56
59
  }, async (argv) => {
57
60
  try {
@@ -67,6 +70,7 @@ yargs
67
70
  owner_email: argv.ownerEmail,
68
71
  ttl: argv.ttl,
69
72
  admin_token: argv.adminToken,
73
+ token: argv.token,
70
74
  });
71
75
 
72
76
  console.log('your url is: %s', result.url);
@@ -139,7 +143,7 @@ yargs
139
143
  describe: 'Password for password auth',
140
144
  })
141
145
  .option('google', {
142
- describe: 'Allowed Google emails (CSV)',
146
+ describe: 'Allowed Google emails or @domain.com patterns (CSV)',
143
147
  })
144
148
  .option('telegram', {
145
149
  describe: 'Allowed Telegram user IDs (CSV)',
@@ -154,6 +158,9 @@ yargs
154
158
  .option('admin-token', {
155
159
  describe: 'Admin token for reconnecting to an existing tunnel',
156
160
  })
161
+ .option('token', {
162
+ describe: 'API token (mgs_xxx) for authentication. Also reads MYGENSITE_TOKEN env.',
163
+ })
157
164
  .boolean('local-https')
158
165
  .boolean('allow-invalid-cert')
159
166
  .boolean('print-requests');
@@ -184,6 +191,7 @@ yargs
184
191
  owner_email: argv.ownerEmail,
185
192
  ttl: argv.ttl,
186
193
  admin_token: argv.adminToken,
194
+ token: argv.token,
187
195
  });
188
196
  } catch (err) {
189
197
  console.error('tunnel failed: %s', err.message);
@@ -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);
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+ # PreToolUse hook: block git staging of mygen secret files
3
+ INPUT=$(cat)
4
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name')
5
+
6
+ if [ "$TOOL" != "Bash" ]; then
7
+ echo '{"decision":"allow"}'
8
+ exit 0
9
+ fi
10
+
11
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command')
12
+
13
+ # Check for direct staging of mygen files
14
+ if echo "$CMD" | grep -qE 'git add.*\.claude/mygen'; then
15
+ echo '{"decision":"block","reason":"Blocked: .claude/mygen.json contains admin tokens that should never be committed."}'
16
+ exit 0
17
+ fi
18
+
19
+ echo '{"decision":"allow"}'
@@ -0,0 +1,19 @@
1
+ {
2
+ "hooks": [
3
+ {
4
+ "event": "PreToolUse",
5
+ "script": "hooks/git-guard.sh",
6
+ "description": "Prevent staging mygen secret files"
7
+ },
8
+ {
9
+ "event": "PostToolUse",
10
+ "script": "hooks/tunnel-cleanup.sh",
11
+ "description": "Clean stale tunnel PID files"
12
+ },
13
+ {
14
+ "event": "Stop",
15
+ "script": "hooks/tunnel-reminder.sh",
16
+ "description": "Remind about running tunnels on session end"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+ # PostToolUse hook: clean stale tunnel PID files
3
+ for pidfile in .claude/mygen-tunnel-*.pid; do
4
+ [ -f "$pidfile" ] || continue
5
+ PID=$(cat "$pidfile")
6
+ if ! kill -0 "$PID" 2>/dev/null; then
7
+ SLUG=$(basename "$pidfile" | sed 's/mygen-tunnel-//;s/\.pid//')
8
+ rm -f "$pidfile"
9
+ rm -f ".claude/mygen-tunnel-${SLUG}-out.log" ".claude/mygen-tunnel-${SLUG}-err.log"
10
+ rm -f ".claude/mygen-tunnel-${SLUG}.mjs"
11
+ fi
12
+ done
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ # Stop hook: remind about running tunnels
3
+ RUNNING=""
4
+ for pidfile in .claude/mygen-tunnel-*.pid; do
5
+ [ -f "$pidfile" ] || continue
6
+ PID=$(cat "$pidfile")
7
+ if kill -0 "$PID" 2>/dev/null; then
8
+ SLUG=$(basename "$pidfile" | sed 's/mygen-tunnel-//;s/\.pid//')
9
+ RUNNING="${RUNNING} - ${SLUG}.mygen.site (PID $PID)\n"
10
+ fi
11
+ done
12
+
13
+ if [ -n "$RUNNING" ]; then
14
+ echo -e "Running tunnels:\n${RUNNING}To stop: kill <PID>" >&2
15
+ fi
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
  }
@@ -100,6 +100,12 @@ module.exports = class Tunnel extends EventEmitter {
100
100
  responseType: 'json',
101
101
  };
102
102
 
103
+ // API token authentication (token option or MYGENSITE_TOKEN env)
104
+ const token = opt.token || process.env.MYGENSITE_TOKEN;
105
+ if (token) {
106
+ params.headers = { Authorization: `Bearer ${token}` };
107
+ }
108
+
103
109
  // Build extended query params for mygensite server
104
110
  const queryParams = {};
105
111
  if (opt.access) queryParams.access = opt.access;
package/lib/deploy.js CHANGED
@@ -96,8 +96,12 @@ async function deploy(options) {
96
96
  telegram,
97
97
  ttl,
98
98
  admin_token,
99
+ token,
99
100
  } = options;
100
101
 
102
+ // API token authentication (token option or MYGENSITE_TOKEN env)
103
+ const apiToken = token || process.env.MYGENSITE_TOKEN;
104
+
101
105
  // Client-side validation — fail fast before API call
102
106
  if (subdomain) {
103
107
  const slugCheck = validateSlug(subdomain);
@@ -106,7 +110,7 @@ async function deploy(options) {
106
110
  }
107
111
  }
108
112
  if (ttl != null) {
109
- const ttlCheck = validateTTL(Number(ttl));
113
+ const ttlCheck = validateTTL(Number(ttl), { allowUnlimited: true, max: 259200 });
110
114
  if (!ttlCheck.valid) {
111
115
  throw new Error(ttlCheck.error);
112
116
  }
@@ -203,6 +207,8 @@ async function deploy(options) {
203
207
  const headers = { ...form.getHeaders() };
204
208
  if (admin_token) {
205
209
  headers.Authorization = `Bearer ${admin_token}`;
210
+ } else if (apiToken) {
211
+ headers.Authorization = `Bearer ${apiToken}`;
206
212
  }
207
213
 
208
214
  const res = await axios.post(`${host}/api/deploy`, form, { headers });
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
  }
@@ -133,9 +143,26 @@ function validateAuthMethod(method) {
133
143
 
134
144
  // Email: basic check — has @, local part, domain with dot
135
145
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
146
+ // Domain pattern: @domain.com (for allowed_emails — matches all emails from that domain)
147
+ const DOMAIN_PATTERN_REGEX = /^@[^\s@]+\.[^\s@]+$/;
136
148
  // Telegram username: 5-32 chars, alphanumeric + underscores (per Telegram rules)
137
149
  const TELEGRAM_USERNAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]{4,31}$/;
138
150
 
151
+ /**
152
+ * Validate an email or domain pattern for allowed_emails list.
153
+ * Accepts either a full email (alice@co.com) or a domain pattern (@co.com).
154
+ * Domain patterns match all emails from that domain.
155
+ * @param {string} value
156
+ * @returns {boolean}
157
+ */
158
+ function isValidEmailOrDomain(value) {
159
+ if (!value || typeof value !== 'string') return false;
160
+ if (value.startsWith('@')) {
161
+ return value.length <= 255 && DOMAIN_PATTERN_REGEX.test(value);
162
+ }
163
+ return EMAIL_REGEX.test(value);
164
+ }
165
+
139
166
  /**
140
167
  * Validate owner_email value (email address or Telegram username).
141
168
  * @param {string} value
@@ -195,6 +222,11 @@ function validateAccessParams(opts) {
195
222
  }
196
223
  }
197
224
 
225
+ // Unlimited TTL requires auth
226
+ if (opts.ttl === 0 && !authMethod) {
227
+ return { valid: false, error: 'Unlimited TTL (0) requires at least one auth method (password, google, or telegram)' };
228
+ }
229
+
198
230
  return { valid: true };
199
231
  }
200
232
 
@@ -206,10 +238,12 @@ module.exports = {
206
238
  validateAuthMethod,
207
239
  validateAccessParams,
208
240
  validateOwner,
241
+ isValidEmailOrDomain,
209
242
  SLUG_REGEX,
210
243
  RESERVED_SLUGS,
211
244
  VALID_ACCESS_MODES,
212
245
  VALID_AUTH_METHODS,
213
246
  EMAIL_REGEX,
247
+ DOMAIN_PATTERN_REGEX,
214
248
  TELEGRAM_USERNAME_REGEX,
215
249
  };
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.4.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: deployment-awareness
3
+ description: Recognize active mygen.site deployments and suggest redeploy after file changes
4
+ ---
5
+
6
+ - When a conversation starts with `.claude/mygen.json` present, acknowledge active services
7
+ - If a static deploy exists and build output files are changed, suggest "Do you want to redeploy?"
8
+ - If tunnel PID files (`.claude/mygen-tunnel-*.pid`) exist, be aware of running tunnel processes
9
+ - Never auto-deploy or auto-redeploy. Only suggest
10
+ - When the user says "update it", "reflect changes", "deploy changes", "업데이트 반영해줘", "변경사항 배포해줘", invoke the /share skill
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: safe-defaults
3
+ description: Default to password protection unless user explicitly requests public access
4
+ ---
5
+
6
+ - Unless the user explicitly says "public", "공개", "no password", "비밀번호 없이", default to IP-restricted + password authentication
7
+ - When presenting network options, recommend IP-restricted as the default
8
+ - When presenting auth options, recommend password as the default
9
+ - If the project contains .env, credentials, secrets, or API key files, add a warning about exposure risk
10
+ - Ambiguous requests like "just share it quickly" or "그냥 빨리 공유해줘" still get IP-restricted + password protection by default
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: tunnel-keepalive
3
+ description: Ensure tunnel scripts stay alive with proper keepalive patterns, not exit immediately
4
+ ---
5
+
6
+ When writing tunnel scripts (`.claude/mygen-tunnel-*.mjs`), always follow these patterns:
7
+
8
+ 1. **setInterval required**: Keep the process alive after tunnel creation
9
+ ```js
10
+ setInterval(() => {
11
+ console.error(`[tunnel] alive — ${tunnel.url}`);
12
+ }, 5 * 60 * 1000);
13
+ ```
14
+
15
+ 2. **Signal handlers required**: Clean shutdown on SIGINT/SIGTERM
16
+ ```js
17
+ process.on('SIGINT', () => { tunnel.close(); process.exit(0); });
18
+ process.on('SIGTERM', () => { tunnel.close(); process.exit(0); });
19
+ ```
20
+
21
+ 3. **Event handlers required**: Log unexpected disconnects
22
+ ```js
23
+ tunnel.on('close', () => { console.error('Tunnel closed'); process.exit(1); });
24
+ tunnel.on('error', (err) => { console.error('Tunnel error:', err.message); });
25
+ ```
26
+
27
+ 4. **Never do these**:
28
+ - End the script after console.log without keepalive (process exits immediately)
29
+ - Use setTimeout instead of setInterval (runs only once)
30
+ - Call process.exit() outside of signal/event handlers
31
+
32
+ 5. **Background execution required**: Always run with `&` and save the PID
33
+ ```bash
34
+ node .claude/mygen-tunnel-{slug}.mjs > .claude/mygen-tunnel-{slug}-out.log 2>.claude/mygen-tunnel-{slug}-err.log &
35
+ echo $! > .claude/mygen-tunnel-{slug}.pid
36
+ ```
@@ -8,10 +8,12 @@ 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
 
14
- Deploys and tunnels require `--owner-email`. This identifies who owns the service on the dashboard.
15
+ `--owner-email` links the service to a dashboard account for web-based management.
16
+ The user can also skip this — management via `admin_token` (PATCH, DELETE, redeploy) always works.
15
17
 
16
18
  The value can be either:
17
19
  - **Email address** (for Google login users): `alice@company.com`
@@ -21,20 +23,70 @@ Ownership matching is case-insensitive.
21
23
 
22
24
  ### First use
23
25
  1. Check if `.claude/mygen.json` exists
24
- 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
+ 2. If not, **you MUST ask the user**: "Email or Telegram username for dashboard management? (Enter to skip)"
27
+ 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.
28
+ 4. If the user provides a value, save it to `.claude/mygen.json`:
26
29
  ```json
27
30
  { "owner_email": "user@company.com" }
28
31
  ```
29
- or for Telegram users:
32
+ or for Telegram users (no `@`, not an email — this is valid):
30
33
  ```json
31
- { "owner_email": "telegram_username" }
34
+ { "owner_email": "mytelegramuser" }
35
+ ```
36
+ 5. If the user skips (empty), save without owner_email:
37
+ ```json
38
+ {}
32
39
  ```
33
40
 
34
41
  ### Subsequent uses
35
42
  - Read owner from `.claude/mygen.json` automatically
36
43
  - If the user says "change my email" or "change my owner", update the file
37
44
 
45
+ ## API Token (optional)
46
+
47
+ If the `MYGENSITE_TOKEN` env variable is set, or `token` option is passed, the mygensite client automatically uses it for authentication. This allows service creation from any IP address.
48
+
49
+ The token can be generated from the dashboard: https://mygen.site/dashboard/api
50
+
51
+ Usage:
52
+ ```bash
53
+ # Environment variable (recommended — auto-detected)
54
+ export MYGENSITE_TOKEN=mgs_xxx
55
+
56
+ # Or pass directly in code
57
+ const tunnel = await mygensite({ port: 3000, token: 'mgs_xxx' });
58
+ const site = await mygensite.deploy({ directory: './dist', token: 'mgs_xxx' });
59
+
60
+ # Or CLI flag
61
+ mygensite --port 3000 --token mgs_xxx
62
+ ```
63
+
64
+ When a token is used, `owner_email` is automatically set to the token owner's email. The `.claude/mygen.json` `owner_email` is still used for display purposes but won't override the token-based owner.
65
+
66
+ ## Slug (Domain) Management
67
+
68
+ ### Reuse by default
69
+ - 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`.
70
+ - This means running `/share` repeatedly for the same service always keeps the same URL.
71
+
72
+ ### New service — always ask for subdomain
73
+ When there is **no matching service** in `.claude/mygen.json` for the current context, you MUST ask for a subdomain. This happens when:
74
+ - First time running `/share` in a project (no `.claude/mygen.json` or empty services)
75
+ - Sharing a **different port** than any existing tunnel entry
76
+ - Sharing a **different build directory** than any existing static entry
77
+ - User explicitly asks for a "new URL", "different domain", "새 도메인" etc.
78
+
79
+ Ask:
80
+ > **Subdomain?** (Enter for random: `auto-generated.mygen.site`)
81
+ > e.g. `my-api` → `my-api.mygen.site`
82
+
83
+ - Default (Enter/blank) → server auto-generates a random slug
84
+ - If the user specifies a slug and it's **already taken** (409 error), inform them and ask again:
85
+ > `my-api.mygen.site` is already taken. Choose another subdomain? (Enter for random)
86
+
87
+ ### Domain cannot be changed after creation
88
+ The subdomain (slug) is permanent once created. To use a different domain, create a new service and delete the old one. Inform the user on first use: **"The subdomain can't be changed later — choose carefully, or press Enter for a random one."**
89
+
38
90
  ## Decision: Tunnel vs Static Deploy
39
91
 
40
92
  Decide **automatically** based on the criteria below. Do not ask the user.
@@ -62,11 +114,7 @@ Check if mygensite is installed:
62
114
  npx mygensite --help 2>/dev/null || npm install -g mygensite
63
115
  ```
64
116
 
65
- Load owner email:
66
- ```bash
67
- cat .claude/mygen.json 2>/dev/null
68
- ```
69
- If missing, ask the user and save it.
117
+ Load config from `.claude/mygen.json`. If missing, ask the user for owner identity.
70
118
 
71
119
  ### 1. Analyze the project
72
120
 
@@ -75,84 +123,191 @@ Quickly assess the project structure:
75
123
  - Check for build output directories
76
124
  - Check for server start scripts
77
125
  - Check for running local servers (lsof -i -P | grep LISTEN)
126
+ - Check `.claude/mygen.json` for existing services matching current context
127
+
128
+ ### 1.5. Access Control Setup (first deploy only)
129
+
130
+ **Skip this step** if reusing an existing service from `.claude/mygen.json` (same port/directory context). Settings are already saved.
131
+
132
+ On the **first deploy** of a new service, ask the user these questions:
133
+
134
+ #### Q1. Network access
135
+ > **Who should be able to access this?**
136
+ > 1. **Public** — anyone with the link
137
+ > 2. **IP-restricted** — only your current IP (recommended)
138
+
139
+ If IP-restricted, detect the user's public IP:
140
+ ```bash
141
+ curl -s https://ifconfig.me
142
+ ```
143
+
144
+ #### Q2. Authentication
145
+ > **Password protection is enabled by default.** Choose an authentication method:
146
+ > 1. **Password** — visitors enter a password to access (default)
147
+ > 2. **Google OAuth** — only specific Google accounts can access
148
+ > 3. **Telegram** — only specific Telegram users can access
149
+ > 4. **None** — no authentication (not recommended)
150
+
151
+ If the user picks auth, ask for the details:
152
+ - **Password**: "What password should visitors use?" (or auto-generate one)
153
+ - **Google**: "Which email addresses should have access? (comma-separated, use @domain.com to allow an entire domain)"
154
+ - **Telegram**: "Which Telegram user IDs should have access? (comma-separated)"
155
+
156
+ Multiple auth methods can be combined (e.g. password + Google — visitors can use either).
157
+
158
+ #### Applying the choices
159
+
160
+ Use the answers to set these parameters in the deploy/tunnel script:
161
+ ```js
162
+ access: 'ip', // 'ip' (recommended) or 'public'
163
+ allowed_ips: ['1.2.3.4'], // user's detected IP (auto-detect via curl ifconfig.me)
164
+ auth_method: 'password', // or 'google', 'telegram', 'password,google', or omit
165
+ password: 'chosen-password', // when auth_method includes 'password'
166
+ google: 'alice@co.com,@co.com', // when auth_method includes 'google' (supports @domain.com)
167
+ telegram: '123456789', // when auth_method includes 'telegram'
168
+ ```
169
+
170
+ Save the chosen access settings in `.claude/mygen.json` alongside the service entry so they can be reused on redeploy.
171
+
172
+ > **Tip**: If `$ARGUMENTS` explicitly says "public" or "password", skip the questions and use that directly.
78
173
 
79
174
  ### 2-A. Static deploy
80
175
 
81
176
  Build first if needed:
82
177
  ```bash
83
- # Framework-specific (example)
84
- npm run build
178
+ npm run build # or framework-appropriate command
179
+ ```
180
+
181
+ Write a temporary deploy script `.claude/mygen-deploy.mjs` and run it:
182
+
183
+ ```js
184
+ import localtunnel from 'mygensite';
185
+
186
+ const site = await localtunnel.deploy({
187
+ directory: './dist', // auto-detect: dist, build, out, .next, or '.'
188
+ subdomain: '{slug_or_undefined}', // from mygen.json or omit for auto
189
+ owner_email: '{owner_email}',
190
+ access: '{access}', // from access control setup
191
+ auth_method: '{auth_method}', // from access control setup, omit if none
192
+ password: '{password}', // when auth_method includes 'password'
193
+ google: '{emails}', // when auth_method includes 'google'
194
+ telegram: '{ids}', // when auth_method includes 'telegram'
195
+ ttl: 86400,
196
+ admin_token: '{token_or_undefined}', // if redeploying existing service
197
+ });
198
+
199
+ console.log(JSON.stringify({
200
+ url: site.url, slug: site.slug,
201
+ admin_token: site.admin_token,
202
+ password: site.password || null,
203
+ expires_at: site.expires_at || null,
204
+ }));
85
205
  ```
86
206
 
87
- Deploy:
88
207
  ```bash
89
- npx mygensite deploy \
90
- --directory ./dist \
91
- --owner-email {email} \
92
- --access ${ARGUMENTS:-public} \
93
- --ttl 86400
208
+ node .claude/mygen-deploy.mjs
94
209
  ```
95
210
 
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
211
+ Parse the JSON output, update `.claude/mygen.json`.
100
212
 
101
213
  ### 2-B. Tunnel
102
214
 
103
215
  Check if a local server is running; if not, start it:
104
216
  ```bash
105
- # Check running ports
106
217
  lsof -i -P | grep LISTEN | grep -E ':(3000|5173|8000|8080|4200|5000)'
107
218
  ```
108
219
 
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
220
+ If no server is running, start it in background first.
221
+
222
+ Write a tunnel keeper script `.claude/mygen-tunnel-{slug}.mjs` (slug-based filename for multi-tunnel support):
223
+
224
+ ```js
225
+ import localtunnel from 'mygensite';
226
+
227
+ const tunnel = await localtunnel({
228
+ port: {detected_port},
229
+ subdomain: '{slug_or_undefined}',
230
+ owner_email: '{owner_email}',
231
+ access: '{access}',
232
+ auth_method: '{auth_method}', // from access control setup, omit if none
233
+ password: '{password}', // when auth_method includes 'password'
234
+ google: '{emails}', // when auth_method includes 'google'
235
+ telegram: '{ids}', // when auth_method includes 'telegram'
236
+ ttl: 3600,
237
+ admin_token: '{token_or_undefined}',
238
+ });
239
+
240
+ // Output connection info as JSON (first line)
241
+ console.log(JSON.stringify({
242
+ url: tunnel.url, slug: tunnel.clientId,
243
+ admin_token: tunnel.admin_token,
244
+ password: tunnel.password || null,
245
+ expires_at: tunnel.expires_at || null,
246
+ }));
247
+
248
+ // Keep alive — graceful shutdown on signals
249
+ process.on('SIGINT', () => { tunnel.close(); process.exit(0); });
250
+ process.on('SIGTERM', () => { tunnel.close(); process.exit(0); });
251
+ tunnel.on('close', () => { console.error('Tunnel closed'); process.exit(1); });
252
+ tunnel.on('error', (err) => { console.error('Tunnel error:', err.message); });
253
+
254
+ // Heartbeat every 5 min
255
+ setInterval(() => {
256
+ console.error(`[tunnel] alive — ${tunnel.url}`);
257
+ }, 5 * 60 * 1000);
114
258
  ```
115
259
 
116
- Find the server port and create a tunnel:
260
+ Run in background and capture output (use the slug in all filenames):
117
261
  ```bash
118
- npx mygensite \
119
- --port {detected port} \
120
- --owner-email {email} \
121
- --access ${ARGUMENTS:-public} \
122
- --ttl 3600
262
+ node .claude/mygen-tunnel-{slug}.mjs > .claude/mygen-tunnel-{slug}-out.log 2>.claude/mygen-tunnel-{slug}-err.log &
263
+ TUNNEL_PID=$!
264
+ echo $TUNNEL_PID > .claude/mygen-tunnel-{slug}.pid
265
+
266
+ # Wait for tunnel to initialize
267
+ for i in $(seq 1 10); do
268
+ if [ -s .claude/mygen-tunnel-{slug}-out.log ]; then break; fi
269
+ sleep 1
270
+ done
271
+ cat .claude/mygen-tunnel-{slug}-out.log
123
272
  ```
124
273
 
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).
274
+ - **CRITICAL**: The tunnel keeper script stays running in background. Do NOT delete it while active.
275
+ - PID is saved to `.claude/mygen-tunnel-{slug}.pid` for later management (slug-based, supports multiple tunnels).
276
+ - The script handles SIGINT/SIGTERM gracefully and logs heartbeats to stderr.
128
277
 
129
278
  ### 3. Save results and inform the user
130
279
 
131
- **Always** save the result (slug, admin_token) to `.claude/mygen.json`:
280
+ **Always** save the result (slug, admin_token, access settings) to `.claude/mygen.json`:
132
281
  ```json
133
282
  {
134
283
  "owner_email": "user@company.com",
135
284
  "services": {
136
285
  "{slug}": {
137
286
  "admin_token": "tok_xxx",
138
- "type": "static",
287
+ "type": "tunnel",
288
+ "port": 3000,
139
289
  "url": "https://{slug}.mygen.site",
290
+ "access": "ip",
291
+ "allowed_ips": ["1.2.3.4"],
292
+ "auth_method": "password",
140
293
  "created_at": "2025-06-01T12:00:00Z"
141
294
  }
142
295
  }
143
296
  }
144
297
  ```
145
298
 
146
- After deploy/tunnel completes, inform the user **concisely**:
299
+ After deploy/tunnel completes, inform the user **concisely** based on settings:
147
300
 
301
+ **Public, no auth:**
148
302
  ```
149
303
  URL: https://{slug}.mygen.site
150
304
 
151
305
  Share this link — anyone can access it.
152
306
  Auto-expires in 24 hours.
307
+ You can change access settings anytime — just ask.
153
308
  ```
154
309
 
155
- If password-protected:
310
+ **With password auth:**
156
311
  ```
157
312
  URL: https://{slug}.mygen.site
158
313
  Password: {password}
@@ -160,10 +315,26 @@ Password: {password}
160
315
  Share the link and password together.
161
316
  ```
162
317
 
163
- If tunnel, add:
318
+ **With Google auth:**
319
+ ```
320
+ URL: https://{slug}.mygen.site
321
+ Allowed: {emails}
322
+
323
+ Only the listed Google accounts can access (via OAuth login).
324
+ ```
325
+
326
+ **With IP restriction:**
327
+ ```
328
+ URL: https://{slug}.mygen.site
329
+ Restricted to: {ip}
330
+
331
+ Only accessible from the allowed IP address.
164
332
  ```
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.
333
+
334
+ If tunnel:
335
+ ```
336
+ Tunnel running in background (PID: {pid}).
337
+ It stays open as long as the process is alive.
167
338
  ```
168
339
 
169
340
  ## Settings Changes (PATCH)
@@ -184,8 +355,9 @@ const site = mygensite.manage({
184
355
  admin_token: '{admin_token}', // read from .claude/mygen.json
185
356
  });
186
357
 
187
- await site.updateAccess({ mode: 'public' });
188
- await site.updateAccess({ mode: 'password', password: 'new-password' });
358
+ await site.updateAccess({ access: 'public', auth_method: '' });
359
+ await site.updateAccess({ auth_method: 'password', password: 'new-password' });
360
+ await site.updateAccess({ auth_method: 'password,google', password: 'pw', google: 'a@co.com' });
189
361
  await site.extendTTL(86400);
190
362
  await site.redeploy('./dist'); // only when files changed
191
363
  await site.delete();
@@ -198,46 +370,47 @@ await site.delete();
198
370
  Read the admin_token for the service from `.claude/mygen.json`:
199
371
 
200
372
  ```bash
201
- # Change access mode (e.g. password -> public)
373
+ # Make fully public (no auth)
202
374
  curl -X PATCH https://mygen.site/api/services/{slug} \
203
375
  -H "Authorization: Bearer {admin_token}" \
204
376
  -H "Content-Type: application/json" \
205
- -d '{"access": {"mode": "public"}}'
377
+ -d '{"access": "public", "auth_method": ""}'
206
378
 
207
- # Change password
379
+ # Add password auth
208
380
  curl -X PATCH https://mygen.site/api/services/{slug} \
209
381
  -H "Authorization: Bearer {admin_token}" \
210
382
  -H "Content-Type: application/json" \
211
- -d '{"access": {"password": "new-password"}}'
383
+ -d '{"auth_method": "password", "password": "new-password"}'
212
384
 
213
- # Add IP restriction
385
+ # Add Google OAuth + password
214
386
  curl -X PATCH https://mygen.site/api/services/{slug} \
215
387
  -H "Authorization: Bearer {admin_token}" \
216
388
  -H "Content-Type: application/json" \
217
- -d '{"access": {"mode": "ip_only", "allowed_ips": ["1.2.3.0/24"]}}'
389
+ -d '{"auth_method": "password,google", "password": "pw", "google": "alice@co.com"}'
218
390
 
219
- # Extend TTL (timer resets from now)
391
+ # Restrict by IP
220
392
  curl -X PATCH https://mygen.site/api/services/{slug} \
221
393
  -H "Authorization: Bearer {admin_token}" \
222
394
  -H "Content-Type: application/json" \
223
- -d '{"ttl": 86400}'
395
+ -d '{"access": "ip", "allowed_ips": "1.2.3.0/24"}'
224
396
 
225
- # Change owner email
397
+ # Extend TTL (timer resets from now)
226
398
  curl -X PATCH https://mygen.site/api/services/{slug} \
227
399
  -H "Authorization: Bearer {admin_token}" \
228
400
  -H "Content-Type: application/json" \
229
- -d '{"owner_email": "new@company.com"}'
401
+ -d '{"ttl": 86400}'
230
402
  ```
231
403
 
232
404
  ### PATCH vs Redeploy
233
405
 
234
406
  | Request | Method |
235
407
  |---------|--------|
236
- | Change password | PATCH `access.password` |
237
- | Make it public | PATCH `access.mode` |
238
- | Restrict by IP | PATCH `access.allowed_ips` |
408
+ | Change password | PATCH `auth_method` + `password` |
409
+ | Make it public | PATCH `access` + `auth_method: ""` |
410
+ | Add Google auth | PATCH `auth_method` + `google` |
411
+ | Restrict by IP | PATCH `access: "ip"` + `allowed_ips` |
239
412
  | Extend time | PATCH `ttl` |
240
- | Change email | PATCH `owner_email` |
413
+ | Change owner | PATCH `owner_email` (email or Telegram username) |
241
414
  | Update content (files changed) | Redeploy (`deploy --admin-token`) |
242
415
  | Upload new files | Redeploy (`deploy --admin-token`) |
243
416
 
@@ -265,12 +438,68 @@ curl -X DELETE "https://mygen.site/api/services/{slug}?purge=true" \
265
438
 
266
439
  After deleting, also remove the service entry from `.claude/mygen.json`.
267
440
 
441
+ ## Tunnel Management
442
+
443
+ All tunnel files use slug-based names (`mygen-tunnel-{slug}.*`) to support multiple simultaneous tunnels.
444
+
445
+ ### Check if tunnel is running
446
+ ```bash
447
+ if [ -f .claude/mygen-tunnel-{slug}.pid ]; then
448
+ PID=$(cat .claude/mygen-tunnel-{slug}.pid)
449
+ kill -0 $PID 2>/dev/null && echo "Running (PID $PID)" || echo "Stopped"
450
+ fi
451
+ ```
452
+
453
+ ### Check all tunnels
454
+ ```bash
455
+ for pidfile in .claude/mygen-tunnel-*.pid; do
456
+ [ -f "$pidfile" ] || continue
457
+ SLUG=$(basename "$pidfile" | sed 's/mygen-tunnel-//;s/\.pid//')
458
+ PID=$(cat "$pidfile")
459
+ if kill -0 "$PID" 2>/dev/null; then
460
+ echo "$SLUG: Running (PID $PID)"
461
+ else
462
+ echo "$SLUG: Stopped"
463
+ fi
464
+ done
465
+ ```
466
+
467
+ ### Stop tunnel
468
+ ```bash
469
+ if [ -f .claude/mygen-tunnel-{slug}.pid ]; then
470
+ kill $(cat .claude/mygen-tunnel-{slug}.pid) 2>/dev/null
471
+ rm -f .claude/mygen-tunnel-{slug}.pid .claude/mygen-tunnel-{slug}-out.log .claude/mygen-tunnel-{slug}-err.log .claude/mygen-tunnel-{slug}.mjs
472
+ fi
473
+ ```
474
+
475
+ ### Stop all tunnels
476
+ ```bash
477
+ for pidfile in .claude/mygen-tunnel-*.pid; do
478
+ [ -f "$pidfile" ] || continue
479
+ PID=$(cat "$pidfile")
480
+ SLUG=$(basename "$pidfile" | sed 's/mygen-tunnel-//;s/\.pid//')
481
+ kill "$PID" 2>/dev/null
482
+ rm -f "$pidfile" ".claude/mygen-tunnel-${SLUG}-out.log" ".claude/mygen-tunnel-${SLUG}-err.log" ".claude/mygen-tunnel-${SLUG}.mjs"
483
+ done
484
+ ```
485
+
486
+ ### Restart tunnel
487
+ Stop the old one, then run Step 2-B again. The same slug and admin_token will be reused from `.claude/mygen.json`.
488
+
489
+ ## Reference
490
+
491
+ - API docs: https://mygen.site/docs (NOT /api/docs)
492
+ - LLM-readable docs: https://mygen.site/llms.txt
493
+
268
494
  ## Important Notes
269
495
 
270
- - Do not ask the user for technical choices. Decide automatically.
271
- - Let the slug (subdomain) be auto-generated unless the user specifies one.
496
+ - Do not ask the user for technical choices (tunnel vs static). Decide automatically.
497
+ - **Reuse the same slug** for the same context. Ask for a subdomain on every new deploy (default: random).
272
498
  - 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.
499
+ - If `$ARGUMENTS` explicitly specifies access (e.g. "password", "public"), skip the access control questions and use that directly.
500
+ - If `$ARGUMENTS` is empty, ask the access control questions on first deploy.
275
501
  - 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).
502
+ - If service creation fails with `creator_ip_denied` (403), the user's IP is not in the server's allow-list.
503
+ - **For tunnel/deploy creation, use the mygensite Node.js library** (not curl). For PATCH settings and DELETE, curl is fine.
504
+ - Clean up temp scripts after use. Keep `.claude/mygen-tunnel.mjs` alive while tunnel is running.
505
+ - Add `.claude/` to `.gitignore` (contains tokens).