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