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.
- package/.claude-plugin/marketplace.json +5 -5
- package/.claude-plugin/plugin.json +10 -4
- package/README.en.md +7 -7
- package/README.ko.md +7 -7
- package/README.md +35 -4
- package/bin/lt.js +10 -2
- package/hooks/git-guard.sh +19 -0
- package/hooks/hooks.json +19 -0
- package/hooks/tunnel-cleanup.sh +12 -0
- package/hooks/tunnel-reminder.sh +15 -0
- package/lib/Tunnel.js +6 -0
- package/lib/deploy.js +11 -1
- package/lib/validate.js +23 -4
- package/package.json +1 -1
- package/rules/deployment-awareness.md +10 -0
- package/rules/safe-defaults.md +10 -0
- package/rules/tunnel-keepalive.md +36 -0
- package/skills/share/SKILL.md +99 -44
|
@@ -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
|
|
9
|
-
"version": "2.
|
|
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
|
|
16
|
-
"version": "2.
|
|
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
|
|
4
|
-
"version": "2.
|
|
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": [
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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는
|
|
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 형식 |
|
|
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',
|
|
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
|
-
-
|
|
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
|
|
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"}'
|
package/hooks/hooks.json
ADDED
|
@@ -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, {
|
|
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-]{
|
|
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 <
|
|
35
|
-
return { valid: false, error: 'Slug must be
|
|
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
|
@@ -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
|
+
```
|
package/skills/share/SKILL.md
CHANGED
|
@@ -12,7 +12,8 @@ Tunnel/deploy creation uses the **mygensite** Node.js library. Settings changes
|
|
|
12
12
|
|
|
13
13
|
## Owner Identity
|
|
14
14
|
|
|
15
|
-
|
|
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
|
|
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.
|
|
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
|
-
###
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
- Sharing a **different
|
|
50
|
-
-
|
|
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
|
-
|
|
53
|
-
- If
|
|
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
|
|
56
|
-
|
|
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
|
|
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
|
|
113
|
-
> **
|
|
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: '
|
|
131
|
-
allowed_ips: ['1.2.3.4'], //
|
|
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',
|
|
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
|
|
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": "
|
|
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
|
|
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.
|
|
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).
|