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.
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +14 -0
- package/README.en.md +36 -24
- package/README.ko.md +34 -25
- package/README.md +75 -23
- package/bin/lt.js +35 -4
- package/lib/Tunnel.js +27 -1
- package/lib/deploy.js +31 -18
- package/lib/validate.js +61 -2
- package/package.json +1 -1
- package/skills/share/SKILL.md +266 -0
|
@@ -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:
|
|
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`
|
|
44
|
-
- `--
|
|
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 --
|
|
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
|
-
|
|
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); //
|
|
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)
|
|
108
|
-
- `
|
|
109
|
-
- `
|
|
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)` | `
|
|
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({
|
|
150
|
+
// Switch to public access (remove auth)
|
|
151
|
+
await tunnel.updateAccess({ auth_method: '' });
|
|
146
152
|
|
|
147
153
|
// Add password protection
|
|
148
|
-
await tunnel.updateAccess({
|
|
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({
|
|
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
|
|
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` |
|
|
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 | | `
|
|
288
|
-
| `
|
|
289
|
-
| `
|
|
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=
|
|
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=
|
|
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)` | `
|
|
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({
|
|
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({
|
|
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에
|
|
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` 접근
|
|
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 --
|
|
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
|
-
|
|
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); // {
|
|
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) 접근 제어
|
|
108
|
-
- `
|
|
109
|
-
- `
|
|
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(
|
|
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({
|
|
150
|
+
// 공개로 전환 (인증 제거)
|
|
151
|
+
await tunnel.updateAccess({ access: 'public', auth_method: '' });
|
|
146
152
|
|
|
147
153
|
// 비밀번호 보호 추가
|
|
148
|
-
await tunnel.updateAccess({
|
|
154
|
+
await tunnel.updateAccess({ auth_method: 'password', password: 'newpass' });
|
|
149
155
|
|
|
150
|
-
// IP 제한
|
|
151
|
-
await tunnel.updateAccess({
|
|
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,
|
|
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
|
|
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 | | `
|
|
288
|
-
| `
|
|
289
|
-
| `
|
|
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=
|
|
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=
|
|
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(
|
|
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({
|
|
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({
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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({
|
|
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
|
|
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({
|
|
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({
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 --
|
|
196
|
+
# Tunnel (public)
|
|
197
|
+
mygensite --port 3000 --subdomain my-app --ttl 7200
|
|
160
198
|
|
|
161
|
-
#
|
|
162
|
-
mygensite
|
|
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 --
|
|
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=
|
|
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=
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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 = '
|
|
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
|
|
113
|
-
if (
|
|
114
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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 = (
|
|
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: (
|
|
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', '
|
|
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
|
|
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
|
@@ -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).
|