mygensite 1.4.0 → 2.0.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.
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "theconnectsoft-mygensite",
3
+ "owner": {
4
+ "name": "TheConnectSoft",
5
+ "email": "dev@theconnectsoft.com"
6
+ },
7
+ "metadata": {
8
+ "description": "Share your localhost or static sites via mygen.site with 2-layer access control",
9
+ "version": "2.0.0"
10
+ },
11
+ "plugins": [
12
+ {
13
+ "name": "mygensite",
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.0.0",
17
+ "author": {
18
+ "name": "TheConnectSoft"
19
+ },
20
+ "homepage": "https://mygen.site",
21
+ "license": "MIT"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "mygensite",
3
+ "description": "Share your localhost or static sites via mygen.site with access control",
4
+ "version": "1.0.0",
5
+ "author": {
6
+ "name": "TheConnectSoft",
7
+ "email": "dev@theconnectsoft.com",
8
+ "url": "https://mygen.site"
9
+ },
10
+ "homepage": "https://mygen.site",
11
+ "repository": "https://github.com/theconnectsoft/mygensite",
12
+ "license": "MIT",
13
+ "keywords": ["tunnel", "deploy", "share", "mygen.site"]
14
+ }
package/README.en.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Expose your localhost to the world via [mygen.site](https://mygen.site) with access control.
4
4
 
5
- A fork of [localtunnel](https://github.com/localtunnel/localtunnel) with extended features: password protection, IP whitelisting, TTL, owner management, and admin tokens.
5
+ A fork of [localtunnel](https://github.com/localtunnel/localtunnel) with extended features: 2-layer access control (network + auth), Google OAuth, Telegram login, TTL, owner management, and admin tokens.
6
6
 
7
7
  ## Quickstart
8
8
 
@@ -40,13 +40,16 @@ Below are some common arguments. See `mygensite --help` for all options.
40
40
  - `--subdomain` request a named subdomain (default is random)
41
41
  - `--host` upstream server URL (default: `https://mygen.site`)
42
42
  - `--local-host` proxy to a hostname other than localhost
43
- - `--access` access control mode: `public`, `password`, `ip_only`, `both` (default: `both`)
44
- - `--password` password for access control (auto-generated if omitted)
43
+ - `--access` network layer: `public`, `ip` (default: `public`)
44
+ - `--auth-method` auth layer (CSV): `password`, `google`, `telegram`
45
+ - `--password` password (when auth-method includes password)
46
+ - `--google` allowed Google email(s)
47
+ - `--telegram` allowed Telegram user ID(s)
45
48
  - `--owner-email` owner email for dashboard management
46
49
  - `--ttl` tunnel TTL in seconds, 60-86400 (default: 3600)
47
50
 
48
51
  ```
49
- mygensite --port 3000 --subdomain my-app --access password --password secret --ttl 7200
52
+ mygensite --port 3000 --subdomain my-app --auth-method password --password secret --ttl 7200
50
53
  ```
51
54
 
52
55
  Output includes URL, password, and admin_token for runtime management.
@@ -70,7 +73,7 @@ const mygensite = require('mygensite');
70
73
  const tunnel = await mygensite({
71
74
  port: 3000,
72
75
  subdomain: 'my-app',
73
- access: 'password',
76
+ auth_method: 'password',
74
77
  password: 'secret',
75
78
  owner_email: 'alice@company.com',
76
79
  ttl: 3600,
@@ -79,7 +82,7 @@ const mygensite = require('mygensite');
79
82
  console.log(tunnel.url); // https://my-app.mygen.site
80
83
  console.log(tunnel.password); // "secret"
81
84
  console.log(tunnel.admin_token); // "tok_xxx"
82
- console.log(tunnel.access); // { mode: "password", ... }
85
+ console.log(tunnel.access); // "public"
83
86
  console.log(tunnel.expires_at); // "2025-06-01T13:00:00Z"
84
87
 
85
88
  tunnel.on('close', () => {
@@ -104,9 +107,12 @@ const mygensite = require('mygensite');
104
107
 
105
108
  ##### mygensite extensions
106
109
 
107
- - `access` (string) Access control mode: `public`, `password`, `ip_only`, `both`. Default: `both`.
108
- - `password` (string) Password for access control. Auto-generated if omitted.
109
- - `allowed_ips` (string[]) IP whitelist for `ip_only` or `both` mode. Supports CIDR notation.
110
+ - `access` (string) Network access: `public`, `ip`. Default: `public`.
111
+ - `auth_method` (string) Auth methods CSV: `password`, `google`, `telegram`. Default: none.
112
+ - `password` (string) Password (when auth_method includes 'password'). Auto-generated if omitted.
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').
115
+ - `telegram` (string|string[]) Allowed Telegram user ID(s) (when auth_method includes 'telegram').
110
116
  - `owner_email` (string) Owner email for dashboard management.
111
117
  - `ttl` (number) Tunnel TTL in seconds (60-86400). Default: 3600.
112
118
 
@@ -135,20 +141,23 @@ const mygensite = require('mygensite');
135
141
  | method | args | description |
136
142
  | --- | --- | --- |
137
143
  | `close()` | | Close the tunnel |
138
- | `updateAccess(access)` | `{ mode, password, allowed_ips }` | Update access control at runtime. Returns a Promise. |
144
+ | `updateAccess(access)` | `object` | Update access settings at runtime. Returns a Promise. |
139
145
  | `extendTTL(ttl)` | seconds (number) | Extend the tunnel TTL. Returns a Promise. |
140
146
 
141
147
  ### Runtime management
142
148
 
143
149
  ```js
144
- // Switch to public access
145
- await tunnel.updateAccess({ mode: 'public' });
150
+ // Switch to public access (remove auth)
151
+ await tunnel.updateAccess({ auth_method: '' });
146
152
 
147
153
  // Add password protection
148
- await tunnel.updateAccess({ mode: 'password', password: 'newpass' });
154
+ await tunnel.updateAccess({ auth_method: 'password', password: 'newpass' });
155
+
156
+ // Add Google OAuth
157
+ await tunnel.updateAccess({ auth_method: 'password,google', google: 'alice@co.com' });
149
158
 
150
159
  // Restrict by IP
151
- await tunnel.updateAccess({ mode: 'ip_only', allowed_ips: ['1.2.3.0/24'] });
160
+ await tunnel.updateAccess({ access: 'ip', allowed_ips: ['1.2.3.0/24'] });
152
161
 
153
162
  // Extend TTL by 1 hour
154
163
  await tunnel.extendTTL(3600);
@@ -227,7 +236,7 @@ validate.validateAccessMode('public'); // { valid: true }
227
236
  | 400 | `invalid_slug` | Slug must be 3-63 chars, lowercase alphanumeric and hyphens | Use a valid slug format, e.g. `my-app-1` |
228
237
  | 400 | `reserved_slug` | This slug is reserved and cannot be used | Choose a different slug. Reserved: www, api, dashboard, admin, etc. |
229
238
  | 400 | `invalid_ttl` | TTL must be between 60 and 86400 seconds | Use a value between 60 (1 min) and 86400 (24 hours) |
230
- | 400 | `invalid_access` | Access mode must be: public, password, ip_only, both | Use one of the four valid modes |
239
+ | 400 | `invalid_access` | Access must be: public, ip | Use one of the valid access modes |
231
240
  | 409 | `slug_in_use` | This slug is already in use | Use a different slug, or omit `subdomain` for a random one |
232
241
  | 503 | — | Server is temporarily unavailable | Retry after a few seconds |
233
242
 
@@ -237,7 +246,7 @@ validate.validateAccessMode('public'); // { valid: true }
237
246
  | --- | --- | --- | --- |
238
247
  | 401 | `unauthorized` | Invalid or missing admin_token | Use the `admin_token` returned from tunnel creation |
239
248
  | 404 | `not_found` | Service not found | Check that the slug is correct and the tunnel is still active |
240
- | 400 | `invalid_access` | Invalid access mode | Use one of: public, password, ip_only, both |
249
+ | 400 | `invalid_access` | Access must be: public, ip | Use one of the valid access modes |
241
250
  | 400 | `invalid_ttl` | TTL out of range | Use a value between 60 and 86400 |
242
251
 
243
252
  ### Gateway errors (when accessing the tunnel URL)
@@ -284,9 +293,12 @@ console.log(site.expires_at); // "2025-06-02T12:00:00Z"
284
293
  | `files` | Array | * | — | Alternative to `directory`. Array of `{ name, content, contentType? }` objects. |
285
294
  | `subdomain` | string | | random | Request a specific subdomain. |
286
295
  | `host` | string | | `https://mygen.site` | Server URL. |
287
- | `access` | string | | `both` | Access control mode: `public`, `password`, `ip_only`, `both`. |
288
- | `password` | string | | auto | Password for access control. |
289
- | `allowed_ips` | string[] | | | IP whitelist for `ip_only` or `both` mode. CIDR supported. |
296
+ | `access` | string | | `public` | Network access: `public`, `ip`. |
297
+ | `auth_method` | string | | | Auth methods CSV: `password`, `google`, `telegram`. |
298
+ | `password` | string | | auto | Password (when auth_method includes 'password'). |
299
+ | `google` | string\|string[] | | — | Allowed Google email(s) (when auth_method includes 'google'). |
300
+ | `telegram` | string\|string[] | | — | Allowed Telegram user ID(s) (when auth_method includes 'telegram'). |
301
+ | `allowed_ips` | string[] | | — | IP whitelist for `ip` access. CIDR supported. |
290
302
  | `owner_email` | string | | — | Owner email for dashboard management. |
291
303
  | `ttl` | number | | 3600 | Site TTL in seconds (60-86400). |
292
304
  | `admin_token` | string | | — | Provide for redeployment to an existing slug. |
@@ -313,12 +325,12 @@ Multipart `filename` strips directory paths (e.g. `assets/style.css` becomes `st
313
325
  ```bash
314
326
  # Flat files (no subdirectories) — filepaths not needed
315
327
  curl -X POST https://mygen.site/api/deploy \
316
- -F slug=demo -F access='{"mode":"public"}' \
328
+ -F slug=demo -F access=public \
317
329
  -F files=@index.html -F files=@style.css
318
330
 
319
331
  # With subdirectories — filepaths required
320
332
  curl -X POST https://mygen.site/api/deploy \
321
- -F slug=demo -F access='{"mode":"public"}' \
333
+ -F slug=demo -F access=public \
322
334
  -F 'filepaths=["index.html","assets/style.css","assets/js/app.js"]' \
323
335
  -F files=@index.html \
324
336
  -F files=@assets/style.css \
@@ -345,7 +357,7 @@ The returned object contains the deployment result plus convenience methods:
345
357
 
346
358
  | method | args | description |
347
359
  | --- | --- | --- |
348
- | `updateAccess(access)` | `{ mode, password, allowed_ips }` | Update access control. Returns a Promise. |
360
+ | `updateAccess(access)` | `object` | Update access settings. Returns a Promise. |
349
361
  | `extendTTL(ttl)` | seconds (number) | Extend the site TTL. Returns a Promise. |
350
362
  | `redeploy(directory)` | directory path (string) | Upload new files, replacing all existing files. Returns a Promise. |
351
363
  | `delete(purge?)` | purge (boolean) | Delete the site. `false` = soft delete (files kept), `true` = purge S3 files. Returns a Promise. |
@@ -364,7 +376,7 @@ const site = mygensite.manage({
364
376
  });
365
377
 
366
378
  // Same methods as deploy result
367
- await site.updateAccess({ mode: 'public' });
379
+ await site.updateAccess({ access: 'public' });
368
380
  await site.extendTTL(86400);
369
381
  await site.redeploy('./dist-v2');
370
382
  await site.delete();
@@ -385,7 +397,7 @@ await site.delete();
385
397
  await site.redeploy('./dist-v2');
386
398
 
387
399
  // Add password protection after deployment
388
- await site.updateAccess({ mode: 'password', password: 'secret' });
400
+ await site.updateAccess({ auth_method: 'password', password: 'secret' });
389
401
 
390
402
  // Extend TTL by 24 hours
391
403
  await site.extendTTL(86400);
package/README.ko.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [mygen.site](https://mygen.site)를 통해 로컬 서버를 접근 제어와 함께 외부에 노출합니다.
4
4
 
5
- [localtunnel](https://github.com/localtunnel/localtunnel) fork에 비밀번호 보호, IP 화이트리스트, TTL, 소유자 관리, admin token 기능을 추가했습니다.
5
+ [localtunnel](https://github.com/localtunnel/localtunnel) fork에 2-레이어 접근 제어(네트워크 + 인증), Google OAuth, Telegram 로그인, TTL, 소유자 관리, admin token 기능을 추가했습니다.
6
6
 
7
7
  ## 빠른 시작
8
8
 
@@ -40,13 +40,16 @@ mygen.site에 연결하여 터널을 생성하고, 사용할 URL을 알려줍니
40
40
  - `--subdomain` 원하는 서브도메인 지정 (기본: 랜덤)
41
41
  - `--host` 업스트림 서버 URL (기본: `https://mygen.site`)
42
42
  - `--local-host` localhost 대신 프록시할 호스트명
43
- - `--access` 접근 제어 모드: `public`, `password`, `ip_only`, `both` (기본: `both`)
43
+ - `--access` 네트워크 접근 제어: `public`, `ip` (기본: `public`)
44
+ - `--auth-method` 인증 방식 (CSV): `password`, `google`, `telegram`
44
45
  - `--password` 접근 제어 비밀번호 (미지정 시 자동 생성)
46
+ - `--google` Google OAuth 허용 이메일 (CSV)
47
+ - `--telegram` Telegram 허용 사용자 ID (CSV)
45
48
  - `--owner-email` 대시보드 관리용 소유자 이메일
46
49
  - `--ttl` 터널 유효 시간(초), 60-86400 (기본: 3600)
47
50
 
48
51
  ```
49
- mygensite --port 3000 --subdomain my-app --access password --password secret --ttl 7200
52
+ mygensite --port 3000 --subdomain my-app --auth-method password --password secret --ttl 7200
50
53
  ```
51
54
 
52
55
  출력에 URL, 비밀번호, admin_token이 포함됩니다.
@@ -70,7 +73,7 @@ const mygensite = require('mygensite');
70
73
  const tunnel = await mygensite({
71
74
  port: 3000,
72
75
  subdomain: 'my-app',
73
- access: 'password',
76
+ auth_method: 'password',
74
77
  password: 'secret',
75
78
  owner_email: 'alice@company.com',
76
79
  ttl: 3600,
@@ -79,7 +82,7 @@ const mygensite = require('mygensite');
79
82
  console.log(tunnel.url); // https://my-app.mygen.site
80
83
  console.log(tunnel.password); // "secret"
81
84
  console.log(tunnel.admin_token); // "tok_xxx"
82
- console.log(tunnel.access); // { mode: "password", ... }
85
+ console.log(tunnel.access); // { access: "public", auth_methods: "password", ... }
83
86
  console.log(tunnel.expires_at); // "2025-06-01T13:00:00Z"
84
87
 
85
88
  tunnel.on('close', () => {
@@ -104,9 +107,12 @@ const mygensite = require('mygensite');
104
107
 
105
108
  ##### mygensite 확장
106
109
 
107
- - `access` (string) 접근 제어 모드: `public`, `password`, `ip_only`, `both`. 기본값: `both`.
108
- - `password` (string) 접근 제어 비밀번호. 미지정자동 생성.
109
- - `allowed_ips` (string[]) `ip_only` 또는 `both` 모드에서 허용할 IP 목록. CIDR 표기 지원.
110
+ - `access` (string) 네트워크 접근 제어 (Layer 1): `public`, `ip`. 기본값: `public`.
111
+ - `auth_method` (string) 인증 방식 (Layer 2, CSV): `password`, `google`, `telegram`. 복수 지정 콤마 구분 (예: `password,google`).
112
+ - `password` (string) 접근 제어 비밀번호. `auth_method`에 `password` 포함 사용. 미지정 자동 생성.
113
+ - `allowed_ips` (string[]) `access`가 `ip`일 때 허용할 IP 목록. CIDR 표기 지원.
114
+ - `google` (string[]) `auth_method`에 `google` 포함 시 허용할 이메일 목록.
115
+ - `telegram` (string[]) `auth_method`에 `telegram` 포함 시 허용할 Telegram 사용자 ID 목록.
110
116
  - `owner_email` (string) 대시보드 관리용 소유자 이메일.
111
117
  - `ttl` (number) 터널 유효 시간(초), 60-86400. 기본값: 3600.
112
118
 
@@ -135,20 +141,20 @@ const mygensite = require('mygensite');
135
141
  | 메서드 | 인자 | 설명 |
136
142
  | --- | --- | --- |
137
143
  | `close()` | | 터널 종료 |
138
- | `updateAccess(access)` | `{ mode, password, allowed_ips }` | 런타임에 접근 제어 변경. Promise 반환. |
144
+ | `updateAccess(settings)` | object | 런타임에 접근 제어 변경. Promise 반환. |
139
145
  | `extendTTL(ttl)` | 초 (number) | 터널 TTL 연장. Promise 반환. |
140
146
 
141
147
  ### 런타임 관리
142
148
 
143
149
  ```js
144
- // 공개로 전환
145
- await tunnel.updateAccess({ mode: 'public' });
150
+ // 공개로 전환 (인증 제거)
151
+ await tunnel.updateAccess({ access: 'public', auth_method: '' });
146
152
 
147
153
  // 비밀번호 보호 추가
148
- await tunnel.updateAccess({ mode: 'password', password: 'newpass' });
154
+ await tunnel.updateAccess({ auth_method: 'password', password: 'newpass' });
149
155
 
150
- // IP 제한
151
- await tunnel.updateAccess({ mode: 'ip_only', allowed_ips: ['1.2.3.0/24'] });
156
+ // IP 제한 + Google OAuth
157
+ await tunnel.updateAccess({ access: 'ip', allowed_ips: ['1.2.3.0/24'], auth_method: 'google', google: ['user@company.com'] });
152
158
 
153
159
  // TTL 1시간 연장
154
160
  await tunnel.extendTTL(3600);
@@ -227,7 +233,7 @@ validate.validateAccessMode('public'); // { valid: true }
227
233
  | 400 | `invalid_slug` | slug는 3-63자, 소문자 영숫자와 하이픈만 가능 | 올바른 형식 사용, 예: `my-app-1` |
228
234
  | 400 | `reserved_slug` | 예약된 slug로 사용 불가 | 다른 slug 사용. 예약어: www, api, dashboard, admin 등 |
229
235
  | 400 | `invalid_ttl` | TTL은 60-86400초 범위여야 함 | 60(1분) ~ 86400(24시간) 사이 값 사용 |
230
- | 400 | `invalid_access` | 접근 모드는 public, password, ip_only, both 중 하나 | 4가지 모드 중 하나를 지정 |
236
+ | 400 | `invalid_access` | 접근 모드는 public, ip 중 하나 | `public` 또는 `ip` 중 하나를 지정 |
231
237
  | 409 | `slug_in_use` | 이미 사용 중인 slug | 다른 slug 사용, 또는 `subdomain` 생략하여 랜덤 할당 |
232
238
  | 503 | — | 서버 일시 장애 | 몇 초 후 재시도 |
233
239
 
@@ -237,7 +243,7 @@ validate.validateAccessMode('public'); // { valid: true }
237
243
  | --- | --- | --- | --- |
238
244
  | 401 | `unauthorized` | admin_token 없음 또는 불일치 | 터널 생성 시 반환된 `admin_token` 사용 |
239
245
  | 404 | `not_found` | 서비스 없음 | slug가 맞는지, 터널이 아직 활성 상태인지 확인 |
240
- | 400 | `invalid_access` | 잘못된 접근 모드 | public, password, ip_only, both 중 하나 사용 |
246
+ | 400 | `invalid_access` | 잘못된 접근 모드 | `public` 또는 `ip` 중 하나 사용 |
241
247
  | 400 | `invalid_ttl` | TTL 범위 초과 | 60 ~ 86400 사이 값 사용 |
242
248
 
243
249
  ### Gateway 에러 (터널 URL 접속 시)
@@ -246,7 +252,7 @@ validate.validateAccessMode('public'); // { valid: true }
246
252
  | --- | --- | --- |
247
253
  | 404 | 서비스 없음 | slug가 존재하고 삭제되지 않았는지 확인 |
248
254
  | 410 | 서비스 만료 | `extendTTL()`로 연장하거나 새 터널 생성 |
249
- | 403 | IP 접근 거부 | `allowed_ips`에 IP 추가, 또는 `public` 모드로 변경 |
255
+ | 403 | IP 접근 거부 | `allowed_ips`에 IP 추가, 또는 `access`를 `public`으로 변경 |
250
256
  | 401 | 비밀번호 틀림 | 올바른 비밀번호로 재시도 |
251
257
  | 502 | 서비스 오프라인 (터널 연결 끊김) | 터널 클라이언트 재시작 |
252
258
  | 504 | 서비스 응답 시간 초과 | 로컬 서버가 실행 중이고 응답 가능한지 확인 |
@@ -284,9 +290,12 @@ console.log(site.expires_at); // "2025-06-02T12:00:00Z"
284
290
  | `files` | Array | * | — | `directory` 대신 사용. `{ name, content, contentType? }` 객체 배열. |
285
291
  | `subdomain` | string | | 랜덤 | 원하는 서브도메인 지정. |
286
292
  | `host` | string | | `https://mygen.site` | 서버 URL. |
287
- | `access` | string | | `both` | 접근 제어 모드: `public`, `password`, `ip_only`, `both`. |
288
- | `password` | string | | 자동 | 접근 제어 비밀번호. |
289
- | `allowed_ips` | string[] | | | `ip_only` 또는 `both` 모드에서 허용할 IP. CIDR 지원. |
293
+ | `access` | string | | `public` | 네트워크 접근 제어 (Layer 1): `public`, `ip`. |
294
+ | `auth_method` | string | | | 인증 방식 (Layer 2, CSV): `password`, `google`, `telegram`. |
295
+ | `password` | string | | 자동 | 접근 제어 비밀번호. `auth_method`에 `password` 포함 사용. |
296
+ | `allowed_ips` | string[] | | — | `access`가 `ip`일 때 허용할 IP. CIDR 지원. |
297
+ | `google` | string[] | | — | `auth_method`에 `google` 포함 시 허용할 이메일 목록. |
298
+ | `telegram` | string[] | | — | `auth_method`에 `telegram` 포함 시 허용할 Telegram 사용자 ID 목록. |
290
299
  | `owner_email` | string | | — | 대시보드 관리용 소유자 이메일. |
291
300
  | `ttl` | number | | 3600 | 사이트 유효 시간(초), 60-86400. |
292
301
  | `admin_token` | string | | — | 기존 slug에 재배포할 때 사용. |
@@ -313,12 +322,12 @@ Multipart의 `filename`은 디렉토리 경로를 제거합니다 (예: `assets/
313
322
  ```bash
314
323
  # 플랫 파일 (서브디렉토리 없음) - filepaths 불필요
315
324
  curl -X POST https://mygen.site/api/deploy \
316
- -F slug=demo -F access='{"mode":"public"}' \
325
+ -F slug=demo -F access=public \
317
326
  -F files=@index.html -F files=@style.css
318
327
 
319
328
  # 서브디렉토리 있음 - filepaths 필수
320
329
  curl -X POST https://mygen.site/api/deploy \
321
- -F slug=demo -F access='{"mode":"public"}' \
330
+ -F slug=demo -F access=public \
322
331
  -F 'filepaths=["index.html","assets/style.css","assets/js/app.js"]' \
323
332
  -F files=@index.html \
324
333
  -F files=@assets/style.css \
@@ -345,7 +354,7 @@ curl -X POST https://mygen.site/api/deploy \
345
354
 
346
355
  | 메서드 | 인자 | 설명 |
347
356
  | --- | --- | --- |
348
- | `updateAccess(access)` | `{ mode, password, allowed_ips }` | 접근 제어 변경. Promise 반환. |
357
+ | `updateAccess(settings)` | object | 접근 제어 변경. Promise 반환. |
349
358
  | `extendTTL(ttl)` | 초 (number) | TTL 연장. Promise 반환. |
350
359
  | `redeploy(directory)` | 디렉토리 경로 (string) | 새 파일로 교체 업로드. Promise 반환. |
351
360
  | `delete(purge?)` | purge (boolean) | 사이트 삭제. `false` = 소프트 삭제 (파일 유지), `true` = S3 파일까지 삭제. Promise 반환. |
@@ -365,7 +374,7 @@ const site = mygensite.manage({
365
374
  });
366
375
 
367
376
  // deploy 결과와 동일한 메서드 사용 가능
368
- await site.updateAccess({ mode: 'public' });
377
+ await site.updateAccess({ access: 'public' });
369
378
  await site.extendTTL(86400);
370
379
  await site.redeploy('./dist-v2');
371
380
  await site.delete();
@@ -386,7 +395,7 @@ await site.delete();
386
395
  await site.redeploy('./dist-v2');
387
396
 
388
397
  // 배포 후 비밀번호 보호 추가
389
- await site.updateAccess({ mode: 'password', password: 'secret' });
398
+ await site.updateAccess({ auth_method: 'password', password: 'secret' });
390
399
 
391
400
  // TTL 24시간 연장
392
401
  await site.extendTTL(86400);
package/README.md CHANGED
@@ -2,12 +2,30 @@
2
2
 
3
3
  Expose your localhost to the world via [mygen.site](https://mygen.site) with access control.
4
4
 
5
+ Built for AI agents — works as a [Claude Code](https://claude.com/claude-code) plugin with a `/share` skill for natural language deployment.
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
8
10
  npm install mygensite
9
11
  ```
10
12
 
13
+ ## Claude Code Integration
14
+
15
+ Install the `/share` skill as a plugin — then just say "share this" or "deploy this":
16
+
17
+ ```
18
+ # In Claude Code (recommended):
19
+ /plugin marketplace add theconnectsoft/mygensite
20
+ /plugin install mygensite@theconnectsoft-mygensite
21
+
22
+ # Or manually:
23
+ mkdir -p .claude/skills/share
24
+ curl -o .claude/skills/share/SKILL.md https://mygen.site/share-skill.md
25
+ ```
26
+
27
+ The skill auto-detects tunnel vs static deploy, manages tokens, and handles settings changes — no manual API calls needed.
28
+
11
29
  ## Tunnel (expose local server)
12
30
 
13
31
  ```js
@@ -17,9 +35,17 @@ const tunnel = await mygensite({
17
35
  port: 3000, // required: local port
18
36
  subdomain: 'my-app', // optional: default random
19
37
  host: 'https://mygen.site', // optional: default mygen.site
20
- access: 'password', // optional: public | password | ip_only | both (default: both)
21
- password: 'secret', // optional: auto-generated if omitted
22
- allowed_ips: ['1.2.3.0/24'], // optional: for ip_only or both
38
+
39
+ // Layer 1: Network access
40
+ access: 'public', // optional: 'public' | 'ip' (default: 'public')
41
+ allowed_ips: ['1.2.3.0/24'], // required when access='ip'
42
+
43
+ // Layer 2: Auth method(s)
44
+ auth_method: 'password', // optional: CSV of 'password', 'google', 'telegram'
45
+ password: 'secret', // required when auth_method includes 'password'
46
+ google: 'alice@company.com', // required when auth_method includes 'google'
47
+ telegram: '123456', // required when auth_method includes 'telegram'
48
+
23
49
  owner_email: 'alice@company.com', // optional: dashboard management
24
50
  ttl: 3600, // optional: seconds, 60-86400 (default: 3600)
25
51
  });
@@ -31,7 +57,7 @@ tunnel.admin_token // "tok_xxx"
31
57
  tunnel.expires_at // "2025-06-01T13:00:00Z"
32
58
 
33
59
  // Runtime management
34
- await tunnel.updateAccess({ mode: 'public' });
60
+ await tunnel.updateAccess({ access: 'public' });
35
61
  await tunnel.extendTTL(3600);
36
62
 
37
63
  // Cleanup
@@ -47,7 +73,9 @@ const site = await mygensite.deploy({
47
73
  directory: './dist', // required: local directory to upload
48
74
  subdomain: 'demo', // optional: default random
49
75
  host: 'https://mygen.site', // optional: default mygen.site
50
- access: 'public', // optional: default both
76
+ access: 'public', // optional: 'public' | 'ip' (default: 'public')
77
+ auth_method: 'password', // optional: CSV of 'password', 'google', 'telegram'
78
+ password: 'secret', // when auth_method includes 'password'
51
79
  owner_email: 'alice@company.com', // optional: dashboard management
52
80
  ttl: 86400, // optional: seconds (default: 3600)
53
81
  });
@@ -59,7 +87,7 @@ site.slug // "demo"
59
87
  site.expires_at // "2025-06-02T12:00:00Z"
60
88
 
61
89
  // Management
62
- await site.updateAccess({ mode: 'password', password: 'secret' });
90
+ await site.updateAccess({ auth_method: 'password', password: 'secret' });
63
91
  await site.extendTTL(86400);
64
92
  await site.redeploy('./dist-v2'); // upload new files
65
93
  await site.delete(); // soft delete
@@ -81,20 +109,30 @@ const site = mygensite.manage({
81
109
  });
82
110
 
83
111
  // Same methods as deploy result
84
- await site.updateAccess({ mode: 'public' });
112
+ await site.updateAccess({ access: 'public' });
85
113
  await site.extendTTL(86400);
86
114
  await site.redeploy('./dist-v2');
87
115
  await site.delete();
88
116
  ```
89
117
 
90
- ## Access Modes
118
+ ## Access Control (2-Layer Model)
119
+
120
+ ### Layer 1 — Network (`access`)
121
+ | value | behavior |
122
+ |-------|----------|
123
+ | `public` | anyone can reach (default) |
124
+ | `ip` | only `allowed_ips` can reach |
125
+
126
+ ### Layer 2 — Auth (`auth_method`)
127
+ | value | behavior |
128
+ |-------|----------|
129
+ | _(empty)_ | no authentication (default) |
130
+ | `password` | password form + cookie session |
131
+ | `google` | Google OAuth → allowed emails only |
132
+ | `telegram` | Telegram login → allowed user IDs only |
133
+ | `password,google` | password OR Google (user picks) |
91
134
 
92
- | mode | behavior |
93
- |------|----------|
94
- | `public` | anyone can access |
95
- | `password` | password required |
96
- | `ip_only` | allowed_ips only |
97
- | `both` | allowed_ips + password (default) |
135
+ Both layers apply sequentially: IP check → auth check.
98
136
 
99
137
  ## Constraints
100
138
 
@@ -145,7 +183,7 @@ validate.validateTTL(30); // { valid: false, error: '...' }
145
183
  | 400 | `invalid_slug` | slug format invalid | use 3-63 chars, lowercase alphanum + hyphen (e.g. `my-app-1`) |
146
184
  | 400 | `reserved_slug` | slug is reserved | choose different slug. reserved: www, api, dashboard, admin, etc. |
147
185
  | 400 | `invalid_ttl` | TTL out of range | use 60-86400 (seconds) |
148
- | 400 | `invalid_access` | bad access mode | use: public, password, ip_only, both |
186
+ | 400 | `invalid_access` | bad access mode | use: public, ip |
149
187
  | 401 | `unauthorized` | wrong admin_token | use the `admin_token` from tunnel creation response |
150
188
  | 404 | `not_found` | service not found | verify slug is correct and tunnel is active |
151
189
  | 409 | `slug_in_use` | slug already taken | use different slug, or omit `subdomain` for random |
@@ -155,14 +193,21 @@ validate.validateTTL(30); // { valid: false, error: '...' }
155
193
  ## CLI
156
194
 
157
195
  ```bash
158
- # Tunnel
159
- mygensite --port 3000 --subdomain my-app --access password --password 'secret' --ttl 7200
196
+ # Tunnel (public)
197
+ mygensite --port 3000 --subdomain my-app --ttl 7200
160
198
 
161
- # Deploy
162
- mygensite deploy --directory ./dist --subdomain demo --access public --ttl 86400
199
+ # Tunnel with password auth
200
+ mygensite --port 3000 --subdomain my-app --auth-method password --password 'secret'
201
+
202
+ # Tunnel with IP restriction + Google auth
203
+ mygensite --port 3000 -s my-app --access ip --allowed-ips '1.2.3.0/24' \
204
+ --auth-method google --google 'alice@company.com'
205
+
206
+ # Deploy (static)
207
+ mygensite deploy --directory ./dist --subdomain demo --ttl 86400
163
208
 
164
209
  # Deploy with password
165
- mygensite deploy -d ./dist -s private-demo --access password --password 'mypass'
210
+ mygensite deploy -d ./dist -s private-demo --auth-method password --password 'mypass'
166
211
 
167
212
  # Redeploy (reuse admin_token)
168
213
  mygensite deploy -d ./dist-v2 -s demo --admin-token tok_xxx
@@ -171,14 +216,19 @@ mygensite deploy -d ./dist-v2 -s demo --admin-token tok_xxx
171
216
  ## curl Deploy
172
217
 
173
218
  ```bash
174
- # Simple (flat files)
219
+ # Simple (flat files, public)
175
220
  curl -X POST https://mygen.site/api/deploy \
176
- -F slug=demo -F access='{"mode":"public"}' \
221
+ -F slug=demo -F access=public \
177
222
  -F files=@index.html -F files=@style.css
178
223
 
224
+ # With password auth
225
+ curl -X POST https://mygen.site/api/deploy \
226
+ -F slug=demo -F auth_method=password -F password=secret \
227
+ -F files=@index.html
228
+
179
229
  # With subdirectories — use filepaths to preserve directory structure
180
230
  curl -X POST https://mygen.site/api/deploy \
181
- -F slug=demo -F access='{"mode":"public"}' \
231
+ -F slug=demo -F access=public \
182
232
  -F 'filepaths=["index.html","assets/style.css","assets/js/app.js"]' \
183
233
  -F files=@index.html \
184
234
  -F files=@assets/style.css \
@@ -225,6 +275,8 @@ const tunnel = await mygensite({ port: 3000, password: 'my!p@ss$word' });
225
275
 
226
276
  - [English (detailed)](./README.en.md) — full API reference, all options, events, methods
227
277
  - [한국어](./README.ko.md) — 한국어 상세 문서
278
+ - [API docs](https://mygen.site/docs) — full endpoint reference
279
+ - [llms.txt](https://mygen.site/llms.txt) — LLM-readable docs
228
280
 
229
281
  ## License
230
282
 
package/bin/lt.js CHANGED
@@ -29,10 +29,19 @@ yargs
29
29
  describe: 'Subdomain (slug)',
30
30
  })
31
31
  .option('access', {
32
- describe: 'Access mode: public, password, ip_only, both',
32
+ describe: 'Access mode: public, ip',
33
+ })
34
+ .option('auth-method', {
35
+ describe: 'Auth methods (CSV): password, google, telegram',
33
36
  })
34
37
  .option('password', {
35
- describe: 'Password for access control',
38
+ describe: 'Password for password auth',
39
+ })
40
+ .option('google', {
41
+ describe: 'Allowed Google emails (CSV)',
42
+ })
43
+ .option('telegram', {
44
+ describe: 'Allowed Telegram user IDs (CSV)',
36
45
  })
37
46
  .option('owner-email', {
38
47
  describe: 'Owner email for dashboard',
@@ -51,7 +60,10 @@ yargs
51
60
  subdomain: argv.subdomain,
52
61
  directory: argv.directory,
53
62
  access: argv.access,
63
+ auth_method: argv.authMethod,
54
64
  password: argv.password,
65
+ google: argv.google,
66
+ telegram: argv.telegram,
55
67
  owner_email: argv.ownerEmail,
56
68
  ttl: argv.ttl,
57
69
  admin_token: argv.adminToken,
@@ -65,6 +77,9 @@ yargs
65
77
  if (result.password) {
66
78
  console.log('your password is: %s', result.password);
67
79
  }
80
+ if (result.auth_methods) {
81
+ console.log('auth methods: %s', result.auth_methods);
82
+ }
68
83
  if (result.expires_at) {
69
84
  console.log('expires at: %s', result.expires_at);
70
85
  }
@@ -115,10 +130,19 @@ yargs
115
130
  describe: 'Print basic request info',
116
131
  })
117
132
  .option('access', {
118
- describe: 'Access control mode: public, password, ip_only, both',
133
+ describe: 'Access mode: public, ip',
134
+ })
135
+ .option('auth-method', {
136
+ describe: 'Auth methods (CSV): password, google, telegram',
119
137
  })
120
138
  .option('password', {
121
- describe: 'Password for access control',
139
+ describe: 'Password for password auth',
140
+ })
141
+ .option('google', {
142
+ describe: 'Allowed Google emails (CSV)',
143
+ })
144
+ .option('telegram', {
145
+ describe: 'Allowed Telegram user IDs (CSV)',
122
146
  })
123
147
  .option('owner-email', {
124
148
  describe: 'Owner email for dashboard management',
@@ -153,7 +177,10 @@ yargs
153
177
  local_ca: argv.localCa,
154
178
  allow_invalid_cert: argv.allowInvalidCert,
155
179
  access: argv.access,
180
+ auth_method: argv.authMethod,
156
181
  password: argv.password,
182
+ google: argv.google,
183
+ telegram: argv.telegram,
157
184
  owner_email: argv.ownerEmail,
158
185
  ttl: argv.ttl,
159
186
  admin_token: argv.adminToken,
@@ -177,6 +204,10 @@ yargs
177
204
  console.log('your admin_token is: %s', tunnel.admin_token);
178
205
  }
179
206
 
207
+ if (tunnel.auth_methods) {
208
+ console.log('auth methods: %s', tunnel.auth_methods);
209
+ }
210
+
180
211
  if (tunnel.cachedUrl) {
181
212
  console.log('your cachedUrl is: %s', tunnel.cachedUrl);
182
213
  }
package/lib/Tunnel.js CHANGED
@@ -6,7 +6,7 @@ const axios = require('axios');
6
6
  const debug = require('debug')('localtunnel:client');
7
7
 
8
8
  const TunnelCluster = require('./TunnelCluster');
9
- const { validateSlug, validateTTL, validateAccessMode } = require('./validate');
9
+ const { validateSlug, validateTTL, validateAccessMode, validateAuthMethod, validateAccessParams } = require('./validate');
10
10
 
11
11
  module.exports = class Tunnel extends EventEmitter {
12
12
  constructor(opts = {}) {
@@ -36,6 +36,17 @@ module.exports = class Tunnel extends EventEmitter {
36
36
  throw new Error(accessCheck.error);
37
37
  }
38
38
  }
39
+ if (opts.auth_method) {
40
+ const authCheck = validateAuthMethod(opts.auth_method);
41
+ if (!authCheck.valid) {
42
+ throw new Error(authCheck.error);
43
+ }
44
+ }
45
+ // Validate param consistency (mismatched params)
46
+ const paramsCheck = validateAccessParams(opts);
47
+ if (!paramsCheck.valid) {
48
+ throw new Error(paramsCheck.error);
49
+ }
39
50
  }
40
51
 
41
52
  _getInfo(body) {
@@ -48,6 +59,10 @@ module.exports = class Tunnel extends EventEmitter {
48
59
  this.password = body.password || null;
49
60
  this.admin_token = body.admin_token || null;
50
61
  this.access = body.access || null;
62
+ this.auth_methods = body.auth_methods || null;
63
+ this.allowed_ips = body.allowed_ips || null;
64
+ this.allowed_emails = body.allowed_emails || null;
65
+ this.allowed_telegram_ids = body.allowed_telegram_ids || null;
51
66
  this.expires_at = body.expires_at || null;
52
67
 
53
68
  return {
@@ -82,12 +97,23 @@ module.exports = class Tunnel extends EventEmitter {
82
97
  // Build extended query params for mygensite server
83
98
  const queryParams = {};
84
99
  if (opt.access) queryParams.access = opt.access;
100
+ if (opt.auth_method) queryParams.auth_method = opt.auth_method;
85
101
  if (opt.password) queryParams.password = opt.password;
86
102
  if (opt.allowed_ips) {
87
103
  queryParams.allowed_ips = Array.isArray(opt.allowed_ips)
88
104
  ? opt.allowed_ips.join(',')
89
105
  : opt.allowed_ips;
90
106
  }
107
+ if (opt.google) {
108
+ queryParams.google = Array.isArray(opt.google)
109
+ ? opt.google.join(',')
110
+ : opt.google;
111
+ }
112
+ if (opt.telegram) {
113
+ queryParams.telegram = Array.isArray(opt.telegram)
114
+ ? opt.telegram.join(',')
115
+ : opt.telegram;
116
+ }
91
117
  if (opt.owner_email) queryParams.owner_email = opt.owner_email;
92
118
  if (opt.ttl) queryParams.ttl = String(opt.ttl);
93
119
  if (opt.admin_token) queryParams.admin_token = opt.admin_token;
package/lib/deploy.js CHANGED
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const axios = require('axios');
4
4
  const FormData = require('form-data');
5
5
  const debug = require('debug')('mygensite:deploy');
6
- const { validateSlug, validateFilePath, validateTTL, validateAccessMode } = require('./validate');
6
+ const { validateSlug, validateFilePath, validateTTL, validateAccessMode, validateAuthMethod, validateAccessParams } = require('./validate');
7
7
 
8
8
  const DEFAULT_HOST = 'https://mygen.site';
9
9
 
@@ -88,9 +88,12 @@ async function deploy(options) {
88
88
  directory,
89
89
  files,
90
90
  owner_email,
91
- access = 'both',
91
+ access = 'public',
92
+ auth_method,
92
93
  password,
93
94
  allowed_ips,
95
+ google,
96
+ telegram,
94
97
  ttl,
95
98
  admin_token,
96
99
  } = options;
@@ -109,14 +112,22 @@ async function deploy(options) {
109
112
  }
110
113
  }
111
114
  if (access) {
112
- const mode = typeof access === 'string' ? access : access.mode;
113
- if (mode) {
114
- const accessCheck = validateAccessMode(mode);
115
- if (!accessCheck.valid) {
116
- throw new Error(accessCheck.error);
117
- }
115
+ const accessCheck = validateAccessMode(access);
116
+ if (!accessCheck.valid) {
117
+ throw new Error(accessCheck.error);
118
118
  }
119
119
  }
120
+ if (auth_method) {
121
+ const authCheck = validateAuthMethod(auth_method);
122
+ if (!authCheck.valid) {
123
+ throw new Error(authCheck.error);
124
+ }
125
+ }
126
+ // Validate param consistency
127
+ const paramsCheck = validateAccessParams({ access, auth_method, password, google, telegram, allowed_ips });
128
+ if (!paramsCheck.valid) {
129
+ throw new Error(paramsCheck.error);
130
+ }
120
131
 
121
132
  // Build file list
122
133
  let fileList;
@@ -156,19 +167,21 @@ async function deploy(options) {
156
167
  if (subdomain) form.append('slug', subdomain);
157
168
  if (owner_email) form.append('owner_email', owner_email);
158
169
  if (ttl) form.append('ttl', String(ttl));
170
+ if (access) form.append('access', access);
171
+ if (auth_method) form.append('auth_method', auth_method);
159
172
  if (password) form.append('password', password);
160
173
  if (allowed_ips) {
161
174
  const ips = Array.isArray(allowed_ips) ? allowed_ips.join(',') : allowed_ips;
162
175
  form.append('allowed_ips', ips);
163
176
  }
164
-
165
- // Build access field
166
- const accessObj = { mode: typeof access === 'string' ? access : access.mode || 'both' };
167
- if (typeof access === 'object') {
168
- if (access.password) accessObj.password = access.password;
169
- if (access.allowed_ips) accessObj.allowed_ips = access.allowed_ips;
177
+ if (google) {
178
+ const emails = Array.isArray(google) ? google.join(',') : google;
179
+ form.append('google', emails);
180
+ }
181
+ if (telegram) {
182
+ const ids = Array.isArray(telegram) ? telegram.join(',') : telegram;
183
+ form.append('telegram', ids);
170
184
  }
171
- form.append('access', JSON.stringify(accessObj));
172
185
 
173
186
  // Send file paths as separate JSON field (busboy strips directories from filename)
174
187
  form.append('filepaths', JSON.stringify(fileList.map(f => f.name)));
@@ -196,8 +209,8 @@ async function deploy(options) {
196
209
  const siteSlug = data.slug;
197
210
  const siteToken = data.admin_token || admin_token;
198
211
 
199
- // Add convenience methods
200
- data.updateAccess = (newAccess) => patchService(siteHost, siteSlug, siteToken, { access: newAccess });
212
+ // Add convenience methods (body is passed directly to PATCH /api/services/:slug)
213
+ data.updateAccess = (body) => patchService(siteHost, siteSlug, siteToken, body);
201
214
  data.extendTTL = (newTtl) => patchService(siteHost, siteSlug, siteToken, { ttl: newTtl });
202
215
  data.redeploy = (dir) => deploy({ ...options, directory: dir, admin_token: siteToken });
203
216
  data.delete = (purge) => deleteService(siteHost, siteSlug, siteToken, purge);
@@ -229,7 +242,7 @@ function manage(options) {
229
242
  slug,
230
243
  admin_token,
231
244
  url: `https://${slug}.${host.replace(/^https?:\/\//, '')}`,
232
- updateAccess: (access) => patchService(host, slug, admin_token, { access }),
245
+ updateAccess: (body) => patchService(host, slug, admin_token, body),
233
246
  extendTTL: (ttl) => patchService(host, slug, admin_token, { ttl }),
234
247
  redeploy: (dir) => deploy({ host, subdomain: slug, directory: dir, admin_token }),
235
248
  delete: (purge) => deleteService(host, slug, admin_token, purge),
package/lib/validate.js CHANGED
@@ -19,7 +19,8 @@ const RESERVED_SLUGS = new Set([
19
19
  // Must match: server/src/api/routes/deploy.ts SAFE_PATH_SEGMENT
20
20
  const SAFE_PATH_SEGMENT = /^[a-zA-Z0-9_\-. ]+$/;
21
21
 
22
- const VALID_ACCESS_MODES = ['public', 'password', 'ip_only', 'both'];
22
+ const VALID_ACCESS_MODES = ['public', 'ip'];
23
+ const VALID_AUTH_METHODS = ['password', 'google', 'telegram'];
23
24
 
24
25
  /**
25
26
  * Validate a slug (subdomain) name.
@@ -107,17 +108,75 @@ function validateTTL(ttl) {
107
108
  */
108
109
  function validateAccessMode(mode) {
109
110
  if (!VALID_ACCESS_MODES.includes(mode)) {
110
- return { valid: false, error: `Access mode must be one of: ${VALID_ACCESS_MODES.join(', ')}` };
111
+ return { valid: false, error: `Access must be one of: ${VALID_ACCESS_MODES.join(', ')}` };
111
112
  }
112
113
  return { valid: true };
113
114
  }
114
115
 
116
+ /**
117
+ * Validate auth_method CSV value.
118
+ * @param {string} method - CSV of auth methods (e.g. "password,google")
119
+ * @returns {{ valid: boolean, methods?: string[], error?: string }}
120
+ */
121
+ function validateAuthMethod(method) {
122
+ if (!method) {
123
+ return { valid: true, methods: [] };
124
+ }
125
+ const methods = method.split(',').map(s => s.trim()).filter(Boolean);
126
+ for (const m of methods) {
127
+ if (!VALID_AUTH_METHODS.includes(m)) {
128
+ return { valid: false, error: `Invalid auth method: "${m}". Must be: ${VALID_AUTH_METHODS.join(', ')}` };
129
+ }
130
+ }
131
+ return { valid: true, methods };
132
+ }
133
+
134
+ /**
135
+ * Validate that access params are consistent (no mismatched params).
136
+ * Mirrors server-side validation in parseAccessParams.
137
+ * @param {object} opts
138
+ * @returns {{ valid: boolean, error?: string }}
139
+ */
140
+ function validateAccessParams(opts) {
141
+ const access = opts.access || 'public';
142
+ const authMethod = opts.auth_method || null;
143
+ const methods = authMethod ? authMethod.split(',').map(s => s.trim()).filter(Boolean) : [];
144
+
145
+ // Reject allowed_ips when access is public
146
+ if (access === 'public' && opts.allowed_ips) {
147
+ return { valid: false, error: "allowed_ips not allowed when access is 'public'" };
148
+ }
149
+
150
+ // Reject auth params without auth_method
151
+ if (!authMethod && (opts.password || opts.google || opts.telegram)) {
152
+ return { valid: false, error: 'auth parameters (password/google/telegram) require auth_method to be specified' };
153
+ }
154
+
155
+ // Reject mismatched params
156
+ if (authMethod) {
157
+ if (!methods.includes('password') && opts.password) {
158
+ return { valid: false, error: "password not allowed when auth_method doesn't include 'password'" };
159
+ }
160
+ if (!methods.includes('google') && opts.google) {
161
+ return { valid: false, error: "google not allowed when auth_method doesn't include 'google'" };
162
+ }
163
+ if (!methods.includes('telegram') && opts.telegram) {
164
+ return { valid: false, error: "telegram not allowed when auth_method doesn't include 'telegram'" };
165
+ }
166
+ }
167
+
168
+ return { valid: true };
169
+ }
170
+
115
171
  module.exports = {
116
172
  validateSlug,
117
173
  validateFilePath,
118
174
  validateTTL,
119
175
  validateAccessMode,
176
+ validateAuthMethod,
177
+ validateAccessParams,
120
178
  SLUG_REGEX,
121
179
  RESERVED_SLUGS,
122
180
  VALID_ACCESS_MODES,
181
+ VALID_AUTH_METHODS,
123
182
  };
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": "1.4.0",
4
+ "version": "2.0.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -0,0 +1,266 @@
1
+ ---
2
+ name: share
3
+ description: Share what the user built with others. Triggers on "share this", "deploy this", "make this accessible", "show my team", "create a URL", "change password", "make it public", "extend TTL", "공유해줘", "배포해줘", "외부에서 접속하게 해줘", "URL 만들어줘", "비밀번호 바꿔줘", "공개로 바꿔줘", "TTL 연장해줘", "팀한테 보여주고 싶어".
4
+ argument-hint: "[public or password]"
5
+ allowed-tools: Bash, Read, Glob, Grep, Write, Edit, Agent
6
+ ---
7
+
8
+ # Share (mygen.site)
9
+
10
+ Share what the user built via a `{name}.mygen.site` URL.
11
+
12
+ ## Owner Email
13
+
14
+ Deploys and tunnels require `--owner-email`. The email is used for dashboard management.
15
+
16
+ ### First use
17
+ 1. Check if `.claude/mygen.json` exists
18
+ 2. If not, ask the user **once**: "What email should I use for service management? (used for dashboard login)"
19
+ 3. Save it to `.claude/mygen.json`:
20
+ ```json
21
+ { "owner_email": "user@company.com" }
22
+ ```
23
+
24
+ ### Subsequent uses
25
+ - Read email from `.claude/mygen.json` automatically
26
+ - If the user says "change my email", update the file
27
+
28
+ ## Decision: Tunnel vs Static Deploy
29
+
30
+ Decide **automatically** based on the criteria below. Do not ask the user.
31
+
32
+ ### Choose static deploy when
33
+ - Project has only HTML/CSS/JS files (build output)
34
+ - Build output directories exist: `dist/`, `build/`, `out/`, `public/`, `.next/`
35
+ - No server process needed
36
+ - SPA build output (React, Vue, Svelte, etc.)
37
+ - Simple HTML files to share
38
+
39
+ ### Choose tunnel when
40
+ - A local server is running or can be started
41
+ - Uses a server framework (Express, FastAPI, Django, Rails, etc.)
42
+ - Needs dynamic features: API server, WebSocket, SSR
43
+ - Runs via `npm run dev`, `python manage.py runserver`, etc.
44
+ - Requires database connections
45
+
46
+ ## Execution Steps
47
+
48
+ ### 0. Prerequisites
49
+
50
+ Check if mygensite is installed:
51
+ ```bash
52
+ npx mygensite --help 2>/dev/null || npm install -g mygensite
53
+ ```
54
+
55
+ Load owner email:
56
+ ```bash
57
+ cat .claude/mygen.json 2>/dev/null
58
+ ```
59
+ If missing, ask the user and save it.
60
+
61
+ ### 1. Analyze the project
62
+
63
+ Quickly assess the project structure:
64
+ - Check package.json, requirements.txt, etc.
65
+ - Check for build output directories
66
+ - Check for server start scripts
67
+ - Check for running local servers (lsof -i -P | grep LISTEN)
68
+
69
+ ### 2-A. Static deploy
70
+
71
+ Build first if needed:
72
+ ```bash
73
+ # Framework-specific (example)
74
+ npm run build
75
+ ```
76
+
77
+ Deploy:
78
+ ```bash
79
+ npx mygensite deploy \
80
+ --directory ./dist \
81
+ --owner-email {email} \
82
+ --access ${ARGUMENTS:-public} \
83
+ --ttl 86400
84
+ ```
85
+
86
+ - `--directory`: build output directory (auto-detect dist, build, out, etc.)
87
+ - If no build directory exists and there are only HTML files, use current directory (`.`)
88
+ - Default access is public
89
+ - TTL is 24 hours
90
+
91
+ ### 2-B. Tunnel
92
+
93
+ Check if a local server is running; if not, start it:
94
+ ```bash
95
+ # Check running ports
96
+ lsof -i -P | grep LISTEN | grep -E ':(3000|5173|8000|8080|4200|5000)'
97
+ ```
98
+
99
+ If no server is running:
100
+ ```bash
101
+ # Find dev/start script in package.json and run it
102
+ npm run dev &
103
+ # Or framework-appropriate command
104
+ ```
105
+
106
+ Find the server port and create a tunnel:
107
+ ```bash
108
+ npx mygensite \
109
+ --port {detected port} \
110
+ --owner-email {email} \
111
+ --access ${ARGUMENTS:-public} \
112
+ --ttl 3600
113
+ ```
114
+
115
+ - Default access is public
116
+ - TTL is 1 hour (shorter for tunnels)
117
+ - **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).
118
+
119
+ ### 3. Save results and inform the user
120
+
121
+ **Always** save the result (slug, admin_token) to `.claude/mygen.json`:
122
+ ```json
123
+ {
124
+ "owner_email": "user@company.com",
125
+ "services": {
126
+ "{slug}": {
127
+ "admin_token": "tok_xxx",
128
+ "type": "static",
129
+ "url": "https://{slug}.mygen.site",
130
+ "created_at": "2025-06-01T12:00:00Z"
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ After deploy/tunnel completes, inform the user **concisely**:
137
+
138
+ ```
139
+ URL: https://{slug}.mygen.site
140
+
141
+ Share this link — anyone can access it.
142
+ Auto-expires in 24 hours.
143
+ ```
144
+
145
+ If password-protected:
146
+ ```
147
+ URL: https://{slug}.mygen.site
148
+ Password: {password}
149
+
150
+ Share the link and password together.
151
+ ```
152
+
153
+ If tunnel, add:
154
+ ```
155
+ The tunnel stays open as long as this process is running.
156
+ Do NOT close this terminal or kill the process — the tunnel will stop working.
157
+ ```
158
+
159
+ ## Settings Changes (PATCH)
160
+
161
+ When the user wants to change settings on an already-shared service:
162
+ - "change password", "make it public", "restrict by IP", "extend time", etc.
163
+
164
+ **Never redeploy.** Change settings via PATCH on the existing service.
165
+
166
+ ### Method 1: Node.js (recommended)
167
+
168
+ Read slug and admin_token from `.claude/mygen.json`, create a management handle with `manage()`:
169
+
170
+ ```js
171
+ const mygensite = require('mygensite');
172
+ const site = mygensite.manage({
173
+ slug: '{slug}',
174
+ admin_token: '{admin_token}', // read from .claude/mygen.json
175
+ });
176
+
177
+ await site.updateAccess({ mode: 'public' });
178
+ await site.updateAccess({ mode: 'password', password: 'new-password' });
179
+ await site.extendTTL(86400);
180
+ await site.redeploy('./dist'); // only when files changed
181
+ await site.delete();
182
+ ```
183
+
184
+ > **Important:** Do not redeploy just to change settings. Use `manage()`.
185
+
186
+ ### Method 2: curl
187
+
188
+ Read the admin_token for the service from `.claude/mygen.json`:
189
+
190
+ ```bash
191
+ # Change access mode (e.g. password -> public)
192
+ curl -X PATCH https://mygen.site/api/services/{slug} \
193
+ -H "Authorization: Bearer {admin_token}" \
194
+ -H "Content-Type: application/json" \
195
+ -d '{"access": {"mode": "public"}}'
196
+
197
+ # Change password
198
+ curl -X PATCH https://mygen.site/api/services/{slug} \
199
+ -H "Authorization: Bearer {admin_token}" \
200
+ -H "Content-Type: application/json" \
201
+ -d '{"access": {"password": "new-password"}}'
202
+
203
+ # Add IP restriction
204
+ curl -X PATCH https://mygen.site/api/services/{slug} \
205
+ -H "Authorization: Bearer {admin_token}" \
206
+ -H "Content-Type: application/json" \
207
+ -d '{"access": {"mode": "ip_only", "allowed_ips": ["1.2.3.0/24"]}}'
208
+
209
+ # Extend TTL (timer resets from now)
210
+ curl -X PATCH https://mygen.site/api/services/{slug} \
211
+ -H "Authorization: Bearer {admin_token}" \
212
+ -H "Content-Type: application/json" \
213
+ -d '{"ttl": 86400}'
214
+
215
+ # Change owner email
216
+ curl -X PATCH https://mygen.site/api/services/{slug} \
217
+ -H "Authorization: Bearer {admin_token}" \
218
+ -H "Content-Type: application/json" \
219
+ -d '{"owner_email": "new@company.com"}'
220
+ ```
221
+
222
+ ### PATCH vs Redeploy
223
+
224
+ | Request | Method |
225
+ |---------|--------|
226
+ | Change password | PATCH `access.password` |
227
+ | Make it public | PATCH `access.mode` |
228
+ | Restrict by IP | PATCH `access.allowed_ips` |
229
+ | Extend time | PATCH `ttl` |
230
+ | Change email | PATCH `owner_email` |
231
+ | Update content (files changed) | Redeploy (`deploy --admin-token`) |
232
+ | Upload new files | Redeploy (`deploy --admin-token`) |
233
+
234
+ **If files haven't changed, use PATCH. Redeploying for settings changes is wasteful.**
235
+
236
+ When redeployment is needed, always use the existing admin_token:
237
+ ```bash
238
+ npx mygensite deploy \
239
+ --directory ./dist \
240
+ --subdomain {existing slug} \
241
+ --admin-token {existing admin_token}
242
+ ```
243
+
244
+ ## Delete Service
245
+
246
+ ```bash
247
+ # Soft delete (recoverable)
248
+ curl -X DELETE https://mygen.site/api/services/{slug} \
249
+ -H "Authorization: Bearer {admin_token}"
250
+
251
+ # Full delete (files removed, unrecoverable)
252
+ curl -X DELETE "https://mygen.site/api/services/{slug}?purge=true" \
253
+ -H "Authorization: Bearer {admin_token}"
254
+ ```
255
+
256
+ After deleting, also remove the service entry from `.claude/mygen.json`.
257
+
258
+ ## Important Notes
259
+
260
+ - Do not ask the user for technical choices. Decide automatically.
261
+ - Let the slug (subdomain) be auto-generated unless the user specifies one.
262
+ - Handle errors yourself (port conflicts, build failures, etc.).
263
+ - If `$ARGUMENTS` is "password", deploy with password protection.
264
+ - If `$ARGUMENTS` is empty, deploy as public.
265
+ - admin_token is issued **only once**. If lost, it cannot be recovered. Always save to `.claude/mygen.json`.
266
+ - Add `.claude/mygen.json` to `.gitignore` (contains tokens).