mygensite 2.2.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 guided access control and 2-layer security",
9
- "version": "2.2.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.5.1"
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 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",
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.5.1",
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 — guided access control setup, 2-layer security (network + auth), unlimited TTL for static deploys",
4
- "version": "2.2.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.5.1",
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", "access-control"]
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.
@@ -167,7 +167,7 @@ await tunnel.extendTTL(3600);
167
167
 
168
168
  ### Slug (subdomain)
169
169
 
170
- - 3–63 characters, lowercase letters (`a-z`), numbers (`0-9`), and hyphens (`-`) only
170
+ - 4–63 characters, lowercase letters (`a-z`), numbers (`0-9`), and hyphens (`-`) only
171
171
  - Must start and end with a letter or number (not a hyphen)
172
172
  - Reserved words cannot be used: `www`, `api`, `dashboard`, `admin`, `mail`, `ftp`, `static`, `docs`, `status`, `health`, `internal`, `tunnel`, `app`, `web`
173
173
  - A slug used as a tunnel cannot be reused for static deployment (and vice versa). Delete the existing service first.
@@ -220,7 +220,7 @@ const tunnel = await mygensite({ port: 3000, subdomain: 'INVALID' });
220
220
  const { validate } = require('mygensite');
221
221
 
222
222
  validate.validateSlug('my-app'); // { valid: true }
223
- validate.validateSlug('AB'); // { valid: false, error: 'Slug must be 3-63 characters' }
223
+ validate.validateSlug('AB'); // { valid: false, error: 'Slug must be 4-63 characters' }
224
224
  validate.validateFilePath('assets/x.js');// { valid: true, cleaned: 'assets/x.js' }
225
225
  validate.validateFilePath('../etc'); // { valid: false, error: 'Path traversal...' }
226
226
  validate.validateTTL(30); // { valid: false, error: 'TTL must be...' }
@@ -233,7 +233,7 @@ validate.validateAccessMode('public'); // { valid: true }
233
233
 
234
234
  | status | error | description | fix |
235
235
  | --- | --- | --- | --- |
236
- | 400 | `invalid_slug` | Slug must be 3-63 chars, lowercase alphanumeric and hyphens | Use a valid slug format, e.g. `my-app-1` |
236
+ | 400 | `invalid_slug` | Slug must be 4-63 chars, lowercase alphanumeric and hyphens | Use a valid slug format, e.g. `my-app-1` |
237
237
  | 400 | `reserved_slug` | This slug is reserved and cannot be used | Choose a different slug. Reserved: www, api, dashboard, admin, etc. |
238
238
  | 400 | `invalid_ttl` | TTL must be between 60 and 86400 seconds | Use a value between 60 (1 min) and 86400 (24 hours) |
239
239
  | 400 | `invalid_access` | Access must be: public, ip | Use one of the valid access modes |
@@ -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. |
@@ -414,7 +414,7 @@ await site.delete(true);
414
414
  | status | error | description | fix |
415
415
  | --- | --- | --- | --- |
416
416
  | 400 | `no_files` | At least one file is required | Provide `directory` or `files` option |
417
- | 400 | `invalid_slug` | Invalid slug format | Use 3-63 chars, lowercase alphanumeric and hyphens |
417
+ | 400 | `invalid_slug` | Invalid slug format | Use 4-63 chars, lowercase alphanumeric and hyphens |
418
418
  | 400 | `reserved_slug` | Slug is reserved | Choose a different slug |
419
419
  | 409 | `slug_in_use` | Slug taken by another owner | Use a different slug |
420
420
  | 409 | `type_conflict` | Slug is in use as a tunnel | Use a different slug for static deployment |
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.
@@ -164,7 +164,7 @@ await tunnel.extendTTL(3600);
164
164
 
165
165
  ### Slug (서브도메인)
166
166
 
167
- - 3–63자, 소문자 영문(`a-z`), 숫자(`0-9`), 하이픈(`-`)만 허용
167
+ - 4–63자, 소문자 영문(`a-z`), 숫자(`0-9`), 하이픈(`-`)만 허용
168
168
  - 영문자 또는 숫자로 시작/끝나야 함 (하이픈으로 시작/끝 불가)
169
169
  - 예약어 사용 불가: `www`, `api`, `dashboard`, `admin`, `mail`, `ftp`, `static`, `docs`, `status`, `health`, `internal`, `tunnel`, `app`, `web`
170
170
  - 터널로 사용 중인 slug에 정적 배포 불가 (반대도 동일). 기존 서비스를 먼저 삭제해야 함.
@@ -217,7 +217,7 @@ const tunnel = await mygensite({ port: 3000, subdomain: 'INVALID' });
217
217
  const { validate } = require('mygensite');
218
218
 
219
219
  validate.validateSlug('my-app'); // { valid: true }
220
- validate.validateSlug('AB'); // { valid: false, error: 'Slug must be 3-63 characters' }
220
+ validate.validateSlug('AB'); // { valid: false, error: 'Slug must be 4-63 characters' }
221
221
  validate.validateFilePath('assets/x.js');// { valid: true, cleaned: 'assets/x.js' }
222
222
  validate.validateFilePath('../etc'); // { valid: false, error: '경로 탈출 불가...' }
223
223
  validate.validateTTL(30); // { valid: false, error: 'TTL 범위 초과...' }
@@ -230,7 +230,7 @@ validate.validateAccessMode('public'); // { valid: true }
230
230
 
231
231
  | 상태 | 에러 | 설명 | 해결 |
232
232
  | --- | --- | --- | --- |
233
- | 400 | `invalid_slug` | slug는 3-63자, 소문자 영숫자와 하이픈만 가능 | 올바른 형식 사용, 예: `my-app-1` |
233
+ | 400 | `invalid_slug` | slug는 4-63자, 소문자 영숫자와 하이픈만 가능 | 올바른 형식 사용, 예: `my-app-1` |
234
234
  | 400 | `reserved_slug` | 예약된 slug로 사용 불가 | 다른 slug 사용. 예약어: www, api, dashboard, admin 등 |
235
235
  | 400 | `invalid_ttl` | TTL은 60-86400초 범위여야 함 | 60(1분) ~ 86400(24시간) 사이 값 사용 |
236
236
  | 400 | `invalid_access` | 접근 모드는 public, ip 중 하나 | `public` 또는 `ip` 중 하나를 지정 |
@@ -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. |
@@ -412,7 +412,7 @@ await site.delete(true);
412
412
  | 상태 | 에러 | 설명 | 해결 |
413
413
  | --- | --- | --- | --- |
414
414
  | 400 | `no_files` | 최소 1개 파일 필요 | `directory` 또는 `files` 옵션 제공 |
415
- | 400 | `invalid_slug` | 잘못된 slug 형식 | 3-63자, 소문자 영숫자와 하이픈 사용 |
415
+ | 400 | `invalid_slug` | 잘못된 slug 형식 | 4-63자, 소문자 영숫자와 하이픈 사용 |
416
416
  | 400 | `reserved_slug` | 예약된 slug | 다른 slug 사용 |
417
417
  | 409 | `slug_in_use` | 다른 소유자가 사용 중인 slug | 다른 slug 사용 |
418
418
  | 409 | `type_conflict` | 터널로 사용 중인 slug | 정적 배포용으로 다른 slug 사용 |
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
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
@@ -78,6 +79,7 @@ const site = await mygensite.deploy({
78
79
  password: 'secret', // when auth_method includes 'password'
79
80
  owner_email: 'alice@company.com', // optional: email or Telegram username for dashboard
80
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,37 @@ 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
+
137
164
  ## TTL (Time to Live)
138
165
 
139
166
  | type | range | unlimited |
@@ -155,7 +182,7 @@ Full runnable examples in [`examples/`](https://github.com/theconnectsoft/mygens
155
182
 
156
183
  ### Slug (subdomain)
157
184
 
158
- - 3–63 characters, lowercase letters, numbers, and hyphens only
185
+ - 4–63 characters, lowercase letters, numbers, and hyphens only
159
186
  - Must start and end with a letter or number (not a hyphen)
160
187
  - Reserved: `www`, `api`, `dashboard`, `admin`, `docs`, `status`, `health`, etc.
161
188
  - A slug used as a tunnel cannot be reused for static deploy (and vice versa)
@@ -197,7 +224,7 @@ validate.validateTTL(30); // { valid: false, error: '...' }
197
224
 
198
225
  | status | error | when | fix |
199
226
  |--------|-------|------|-----|
200
- | 400 | `invalid_slug` | slug format invalid | use 3-63 chars, lowercase alphanum + hyphen (e.g. `my-app-1`) |
227
+ | 400 | `invalid_slug` | slug format invalid | use 4-63 chars, lowercase alphanum + hyphen (e.g. `my-app-1`) |
201
228
  | 400 | `reserved_slug` | slug is reserved | choose different slug. reserved: www, api, dashboard, admin, etc. |
202
229
  | 400 | `invalid_ttl` | TTL out of range | tunnels: 60-86400s, static: 0 (unlimited) or 60-259200s. Unlimited requires auth. |
203
230
  | 400 | `invalid_access` | bad access mode | use: public, ip |
@@ -228,6 +255,10 @@ mygensite deploy -d ./dist -s private-demo --auth-method password --password 'my
228
255
 
229
256
  # Redeploy (reuse admin_token)
230
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
231
262
  ```
232
263
 
233
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,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
@@ -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);
@@ -203,9 +207,15 @@ 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
- const res = await axios.post(`${host}/api/deploy`, form, { headers });
214
+ const res = await axios.post(`${host}/api/deploy`, form, {
215
+ headers,
216
+ maxBodyLength: 200 * 1024 * 1024,
217
+ maxContentLength: 200 * 1024 * 1024,
218
+ });
209
219
  const data = res.data;
210
220
 
211
221
  debug('deploy response: %j', data);
package/lib/validate.js CHANGED
@@ -8,12 +8,12 @@
8
8
  */
9
9
 
10
10
  // Must match: server/src/api/routes/tunnels.ts SLUG_REGEX
11
- const SLUG_REGEX = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
11
+ const SLUG_REGEX = /^[a-z0-9][a-z0-9-]{2,61}[a-z0-9]$/;
12
12
 
13
13
  const RESERVED_SLUGS = new Set([
14
14
  'www', 'api', 'dashboard', 'admin', 'mail',
15
15
  'ftp', 'static', 'docs', 'status', 'health',
16
- 'internal', 'tunnel', 'app', 'web',
16
+ 'internal', 'tunnel', 'app', 'web', 'mcp',
17
17
  ]);
18
18
 
19
19
  // Must match: server/src/api/routes/deploy.ts SAFE_PATH_SEGMENT
@@ -31,8 +31,8 @@ function validateSlug(slug) {
31
31
  if (!slug || typeof slug !== 'string') {
32
32
  return { valid: false, error: 'Slug is required' };
33
33
  }
34
- if (slug.length < 3 || slug.length > 63) {
35
- return { valid: false, error: 'Slug must be 3-63 characters' };
34
+ if (slug.length < 4 || slug.length > 63) {
35
+ return { valid: false, error: 'Slug must be 4-63 characters' };
36
36
  }
37
37
  if (!SLUG_REGEX.test(slug)) {
38
38
  return { valid: false, error: 'Slug must be lowercase alphanumeric and hyphens, starting and ending with alphanumeric' };
@@ -143,9 +143,26 @@ function validateAuthMethod(method) {
143
143
 
144
144
  // Email: basic check — has @, local part, domain with dot
145
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@]+$/;
146
148
  // Telegram username: 5-32 chars, alphanumeric + underscores (per Telegram rules)
147
149
  const TELEGRAM_USERNAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]{4,31}$/;
148
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
+
149
166
  /**
150
167
  * Validate owner_email value (email address or Telegram username).
151
168
  * @param {string} value
@@ -221,10 +238,12 @@ module.exports = {
221
238
  validateAuthMethod,
222
239
  validateAccessParams,
223
240
  validateOwner,
241
+ isValidEmailOrDomain,
224
242
  SLUG_REGEX,
225
243
  RESERVED_SLUGS,
226
244
  VALID_ACCESS_MODES,
227
245
  VALID_AUTH_METHODS,
228
246
  EMAIL_REGEX,
247
+ DOMAIN_PATTERN_REGEX,
229
248
  TELEGRAM_USERNAME_REGEX,
230
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.2.0",
4
+ "version": "2.5.1",
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
+ ```
@@ -12,7 +12,8 @@ Tunnel/deploy creation uses the **mygensite** Node.js library. Settings changes
12
12
 
13
13
  ## Owner Identity
14
14
 
15
- 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.
16
17
 
17
18
  The value can be either:
18
19
  - **Email address** (for Google login users): `alice@company.com`
@@ -22,9 +23,9 @@ Ownership matching is case-insensitive.
22
23
 
23
24
  ### First use
24
25
  1. Check if `.claude/mygen.json` exists
25
- 2. If not, ask the user **once**: "What email or Telegram username should I use for service management? (used for dashboard login)"
26
+ 2. If not, **you MUST ask the user**: "Email or Telegram username for dashboard management? (Enter to skip)"
26
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.
27
- 4. Save it to `.claude/mygen.json`:
28
+ 4. If the user provides a value, save it to `.claude/mygen.json`:
28
29
  ```json
29
30
  { "owner_email": "user@company.com" }
30
31
  ```
@@ -32,28 +33,59 @@ or for Telegram users (no `@`, not an email — this is valid):
32
33
  ```json
33
34
  { "owner_email": "mytelegramuser" }
34
35
  ```
36
+ 5. If the user skips (empty), save without owner_email:
37
+ ```json
38
+ {}
39
+ ```
35
40
 
36
41
  ### Subsequent uses
37
42
  - Read owner from `.claude/mygen.json` automatically
38
43
  - If the user says "change my email" or "change my owner", update the file
39
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
+
40
66
  ## Slug (Domain) Management
41
67
 
42
68
  ### Reuse by default
43
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`.
44
70
  - This means running `/share` repeatedly for the same service always keeps the same URL.
45
71
 
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"
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`
51
82
 
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.
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)
54
86
 
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'."**
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."**
57
89
 
58
90
  ## Decision: Tunnel vs Static Deploy
59
91
 
@@ -101,24 +133,24 @@ On the **first deploy** of a new service, ask the user these questions:
101
133
 
102
134
  #### Q1. Network access
103
135
  > **Who should be able to access this?**
104
- > 1. **Public** — anyone with the link (default)
105
- > 2. **IP-restricted** — only your current IP
136
+ > 1. **Public** — anyone with the link
137
+ > 2. **IP-restricted** — only your current IP (recommended)
106
138
 
107
139
  If IP-restricted, detect the user's public IP:
108
140
  ```bash
109
141
  curl -s https://ifconfig.me
110
142
  ```
111
143
 
112
- #### Q2. Authentication (recommended)
113
- > **Do you want to add authentication?** (recommended for security)
114
- > 1. **Password** — visitors enter a password to access
144
+ #### Q2. Authentication
145
+ > **Password protection is enabled by default.** Choose an authentication method:
146
+ > 1. **Password** — visitors enter a password to access (default)
115
147
  > 2. **Google OAuth** — only specific Google accounts can access
116
148
  > 3. **Telegram** — only specific Telegram users can access
117
- > 4. **None** — no authentication
149
+ > 4. **None** — no authentication (not recommended)
118
150
 
119
151
  If the user picks auth, ask for the details:
120
152
  - **Password**: "What password should visitors use?" (or auto-generate one)
121
- - **Google**: "Which email addresses should have access? (comma-separated)"
153
+ - **Google**: "Which email addresses should have access? (comma-separated, use @domain.com to allow an entire domain)"
122
154
  - **Telegram**: "Which Telegram user IDs should have access? (comma-separated)"
123
155
 
124
156
  Multiple auth methods can be combined (e.g. password + Google — visitors can use either).
@@ -127,11 +159,11 @@ Multiple auth methods can be combined (e.g. password + Google — visitors can u
127
159
 
128
160
  Use the answers to set these parameters in the deploy/tunnel script:
129
161
  ```js
130
- access: 'public', // or 'ip'
131
- allowed_ips: ['1.2.3.4'], // only when access='ip', user's detected IP
162
+ access: 'ip', // 'ip' (recommended) or 'public'
163
+ allowed_ips: ['1.2.3.4'], // user's detected IP (auto-detect via curl ifconfig.me)
132
164
  auth_method: 'password', // or 'google', 'telegram', 'password,google', or omit
133
165
  password: 'chosen-password', // when auth_method includes 'password'
134
- google: 'alice@co.com', // when auth_method includes 'google'
166
+ google: 'alice@co.com,@co.com', // when auth_method includes 'google' (supports @domain.com)
135
167
  telegram: '123456789', // when auth_method includes 'telegram'
136
168
  ```
137
169
 
@@ -173,7 +205,7 @@ console.log(JSON.stringify({
173
205
  ```
174
206
 
175
207
  ```bash
176
- node .claude/mygen-deploy.mjs && rm .claude/mygen-deploy.mjs
208
+ node .claude/mygen-deploy.mjs
177
209
  ```
178
210
 
179
211
  Parse the JSON output, update `.claude/mygen.json`.
@@ -187,7 +219,7 @@ lsof -i -P | grep LISTEN | grep -E ':(3000|5173|8000|8080|4200|5000)'
187
219
 
188
220
  If no server is running, start it in background first.
189
221
 
190
- Write a tunnel keeper script `.claude/mygen-tunnel.mjs`:
222
+ Write a tunnel keeper script `.claude/mygen-tunnel-{slug}.mjs` (slug-based filename for multi-tunnel support):
191
223
 
192
224
  ```js
193
225
  import localtunnel from 'mygensite';
@@ -225,22 +257,22 @@ setInterval(() => {
225
257
  }, 5 * 60 * 1000);
226
258
  ```
227
259
 
228
- Run in background and capture output:
260
+ Run in background and capture output (use the slug in all filenames):
229
261
  ```bash
230
- node .claude/mygen-tunnel.mjs > .claude/mygen-tunnel-out.log 2>.claude/mygen-tunnel-err.log &
262
+ node .claude/mygen-tunnel-{slug}.mjs > .claude/mygen-tunnel-{slug}-out.log 2>.claude/mygen-tunnel-{slug}-err.log &
231
263
  TUNNEL_PID=$!
232
- echo $TUNNEL_PID > .claude/mygen-tunnel.pid
264
+ echo $TUNNEL_PID > .claude/mygen-tunnel-{slug}.pid
233
265
 
234
266
  # Wait for tunnel to initialize
235
267
  for i in $(seq 1 10); do
236
- if [ -s .claude/mygen-tunnel-out.log ]; then break; fi
268
+ if [ -s .claude/mygen-tunnel-{slug}-out.log ]; then break; fi
237
269
  sleep 1
238
270
  done
239
- cat .claude/mygen-tunnel-out.log
271
+ cat .claude/mygen-tunnel-{slug}-out.log
240
272
  ```
241
273
 
242
274
  - **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.
275
+ - PID is saved to `.claude/mygen-tunnel-{slug}.pid` for later management (slug-based, supports multiple tunnels).
244
276
  - The script handles SIGINT/SIGTERM gracefully and logs heartbeats to stderr.
245
277
 
246
278
  ### 3. Save results and inform the user
@@ -255,7 +287,8 @@ cat .claude/mygen-tunnel-out.log
255
287
  "type": "tunnel",
256
288
  "port": 3000,
257
289
  "url": "https://{slug}.mygen.site",
258
- "access": "public",
290
+ "access": "ip",
291
+ "allowed_ips": ["1.2.3.4"],
259
292
  "auth_method": "password",
260
293
  "created_at": "2025-06-01T12:00:00Z"
261
294
  }
@@ -271,7 +304,7 @@ URL: https://{slug}.mygen.site
271
304
 
272
305
  Share this link — anyone can access it.
273
306
  Auto-expires in 24 hours.
274
- You can change the domain or access settings anytime — just ask.
307
+ You can change access settings anytime — just ask.
275
308
  ```
276
309
 
277
310
  **With password auth:**
@@ -304,12 +337,6 @@ Tunnel running in background (PID: {pid}).
304
337
  It stays open as long as the process is alive.
305
338
  ```
306
339
 
307
- ### 4. Add to .gitignore
308
-
309
- ```bash
310
- grep -q '.claude/' .gitignore 2>/dev/null || echo '.claude/' >> .gitignore
311
- ```
312
-
313
340
  ## Settings Changes (PATCH)
314
341
 
315
342
  When the user wants to change settings on an already-shared service:
@@ -413,22 +440,49 @@ After deleting, also remove the service entry from `.claude/mygen.json`.
413
440
 
414
441
  ## Tunnel Management
415
442
 
443
+ All tunnel files use slug-based names (`mygen-tunnel-{slug}.*`) to support multiple simultaneous tunnels.
444
+
416
445
  ### Check if tunnel is running
417
446
  ```bash
418
- if [ -f .claude/mygen-tunnel.pid ]; then
419
- PID=$(cat .claude/mygen-tunnel.pid)
447
+ if [ -f .claude/mygen-tunnel-{slug}.pid ]; then
448
+ PID=$(cat .claude/mygen-tunnel-{slug}.pid)
420
449
  kill -0 $PID 2>/dev/null && echo "Running (PID $PID)" || echo "Stopped"
421
450
  fi
422
451
  ```
423
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
+
424
467
  ### Stop tunnel
425
468
  ```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
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
429
472
  fi
430
473
  ```
431
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
+
432
486
  ### Restart tunnel
433
487
  Stop the old one, then run Step 2-B again. The same slug and admin_token will be reused from `.claude/mygen.json`.
434
488
 
@@ -440,11 +494,12 @@ Stop the old one, then run Step 2-B again. The same slug and admin_token will be
440
494
  ## Important Notes
441
495
 
442
496
  - 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.).
497
+ - **Reuse the same slug** for the same context. Ask for a subdomain on every new deploy (default: random).
444
498
  - Handle errors yourself (port conflicts, build failures, etc.).
445
499
  - If `$ARGUMENTS` explicitly specifies access (e.g. "password", "public"), skip the access control questions and use that directly.
446
500
  - If `$ARGUMENTS` is empty, ask the access control questions on first deploy.
447
501
  - admin_token is issued **only once**. If lost, it cannot be recovered. Always save to `.claude/mygen.json`.
502
+ - If service creation fails with `creator_ip_denied` (403), the user's IP is not in the server's allow-list.
448
503
  - **For tunnel/deploy creation, use the mygensite Node.js library** (not curl). For PATCH settings and DELETE, curl is fine.
449
504
  - Clean up temp scripts after use. Keep `.claude/mygen-tunnel.mjs` alive while tunnel is running.
450
505
  - Add `.claude/` to `.gitignore` (contains tokens).