mygensite 2.0.0 → 2.2.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 +4 -4
- package/.claude-plugin/plugin.json +3 -3
- package/README.en.md +5 -0
- package/README.ko.md +5 -0
- package/README.md +22 -5
- 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/lib/Tunnel.js +8 -2
- package/lib/deploy.js +8 -2
- package/lib/validate.js +52 -4
- package/package.json +1 -1
- package/skills/share/SKILL.md +250 -66
|
@@ -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 and 2-layer security",
|
|
9
|
+
"version": "2.2.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 setup on first deploy (public/IP + password/Google/Telegram auth), slug reuse, background tunnel keeper, and unlimited TTL for static sites",
|
|
16
|
+
"version": "2.2.0",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "TheConnectSoft"
|
|
19
19
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mygensite",
|
|
3
|
-
"description": "Share your localhost or static sites via mygen.site
|
|
4
|
-
"version": "
|
|
3
|
+
"description": "Share your localhost or static sites via mygen.site — guided access control setup, 2-layer security (network + auth), unlimited TTL for static deploys",
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TheConnectSoft",
|
|
7
7
|
"email": "dev@theconnectsoft.com",
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"homepage": "https://mygen.site",
|
|
11
11
|
"repository": "https://github.com/theconnectsoft/mygensite",
|
|
12
12
|
"license": "MIT",
|
|
13
|
-
"keywords": ["tunnel", "deploy", "share", "mygen.site"]
|
|
13
|
+
"keywords": ["tunnel", "deploy", "share", "mygen.site", "access-control"]
|
|
14
14
|
}
|
package/README.en.md
CHANGED
|
@@ -420,6 +420,11 @@ await site.delete(true);
|
|
|
420
420
|
| 409 | `type_conflict` | Slug is in use as a tunnel | Use a different slug for static deployment |
|
|
421
421
|
| 413 | `file_too_large` | Total upload exceeds 50MB | Reduce file sizes or split into multiple deployments |
|
|
422
422
|
|
|
423
|
+
## Documentation
|
|
424
|
+
|
|
425
|
+
- [API docs](https://mygen.site/docs) — full endpoint reference
|
|
426
|
+
- [LLM-readable docs](https://mygen.site/llms.txt)
|
|
427
|
+
|
|
423
428
|
## Compatibility
|
|
424
429
|
|
|
425
430
|
mygensite is fully compatible with any localtunnel server. Extension options are sent as query parameters and silently ignored by servers that don't support them.
|
package/README.ko.md
CHANGED
|
@@ -418,6 +418,11 @@ await site.delete(true);
|
|
|
418
418
|
| 409 | `type_conflict` | 터널로 사용 중인 slug | 정적 배포용으로 다른 slug 사용 |
|
|
419
419
|
| 413 | `file_too_large` | 총 업로드 크기 50MB 초과 | 파일 크기 줄이기 |
|
|
420
420
|
|
|
421
|
+
## 문서
|
|
422
|
+
|
|
423
|
+
- [API 문서](https://mygen.site/docs) — 전체 엔드포인트 레퍼런스
|
|
424
|
+
- [LLM용 문서](https://mygen.site/llms.txt)
|
|
425
|
+
|
|
421
426
|
## 호환성
|
|
422
427
|
|
|
423
428
|
mygensite는 모든 localtunnel 서버와 완전 호환됩니다. 확장 옵션은 쿼리 파라미터로 전송되며, 지원하지 않는 서버에서는 무시됩니다.
|
package/README.md
CHANGED
|
@@ -46,8 +46,8 @@ const tunnel = await mygensite({
|
|
|
46
46
|
google: 'alice@company.com', // required when auth_method includes 'google'
|
|
47
47
|
telegram: '123456', // required when auth_method includes 'telegram'
|
|
48
48
|
|
|
49
|
-
owner_email: 'alice@company.com', // optional: dashboard
|
|
50
|
-
ttl: 3600, // optional:
|
|
49
|
+
owner_email: 'alice@company.com', // optional: email or Telegram username for dashboard
|
|
50
|
+
ttl: 3600, // optional: 60-86400 seconds (default: 3600)
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
// Result
|
|
@@ -76,8 +76,8 @@ const site = await mygensite.deploy({
|
|
|
76
76
|
access: 'public', // optional: 'public' | 'ip' (default: 'public')
|
|
77
77
|
auth_method: 'password', // optional: CSV of 'password', 'google', 'telegram'
|
|
78
78
|
password: 'secret', // when auth_method includes 'password'
|
|
79
|
-
owner_email: 'alice@company.com', // optional: dashboard
|
|
80
|
-
ttl: 86400, // optional: seconds (default: 3600)
|
|
79
|
+
owner_email: 'alice@company.com', // optional: email or Telegram username for dashboard
|
|
80
|
+
ttl: 86400, // optional: 0 (unlimited) or 60-259200 seconds (default: 3600)
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
// Result
|
|
@@ -134,6 +134,23 @@ await site.delete();
|
|
|
134
134
|
|
|
135
135
|
Both layers apply sequentially: IP check → auth check.
|
|
136
136
|
|
|
137
|
+
## TTL (Time to Live)
|
|
138
|
+
|
|
139
|
+
| type | range | unlimited |
|
|
140
|
+
|------|-------|-----------|
|
|
141
|
+
| Tunnel | 60–86400 seconds (max 24h) | not supported |
|
|
142
|
+
| Static | 60–259200 seconds (max 3 days) | `ttl: 0` — requires at least one auth method |
|
|
143
|
+
|
|
144
|
+
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.
|
|
145
|
+
|
|
146
|
+
## Examples
|
|
147
|
+
|
|
148
|
+
Full runnable examples in [`examples/`](https://github.com/theconnectsoft/mygensite/tree/main/examples):
|
|
149
|
+
|
|
150
|
+
- **[tunnel-basic.mjs](https://github.com/theconnectsoft/mygensite/blob/main/examples/tunnel-basic.mjs)** — Tunnel with signal handling and heartbeat (background-friendly)
|
|
151
|
+
- **[static-deploy.mjs](https://github.com/theconnectsoft/mygensite/blob/main/examples/static-deploy.mjs)** — Static deploy with unlimited TTL and auth
|
|
152
|
+
- **[manage-service.mjs](https://github.com/theconnectsoft/mygensite/blob/main/examples/manage-service.mjs)** — Settings, TTL, redeploy, delete via manage()
|
|
153
|
+
|
|
137
154
|
## Constraints
|
|
138
155
|
|
|
139
156
|
### Slug (subdomain)
|
|
@@ -182,7 +199,7 @@ validate.validateTTL(30); // { valid: false, error: '...' }
|
|
|
182
199
|
|--------|-------|------|-----|
|
|
183
200
|
| 400 | `invalid_slug` | slug format invalid | use 3-63 chars, lowercase alphanum + hyphen (e.g. `my-app-1`) |
|
|
184
201
|
| 400 | `reserved_slug` | slug is reserved | choose different slug. reserved: www, api, dashboard, admin, etc. |
|
|
185
|
-
| 400 | `invalid_ttl` | TTL out of range |
|
|
202
|
+
| 400 | `invalid_ttl` | TTL out of range | tunnels: 60-86400s, static: 0 (unlimited) or 60-259200s. Unlimited requires auth. |
|
|
186
203
|
| 400 | `invalid_access` | bad access mode | use: public, ip |
|
|
187
204
|
| 401 | `unauthorized` | wrong admin_token | use the `admin_token` from tunnel creation response |
|
|
188
205
|
| 404 | `not_found` | service not found | verify slug is correct and tunnel is active |
|
|
@@ -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);
|
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, validateAuthMethod, validateAccessParams } = require('./validate');
|
|
9
|
+
const { validateSlug, validateTTL, validateAccessMode, validateAuthMethod, validateAccessParams, validateOwner } = require('./validate');
|
|
10
10
|
|
|
11
11
|
module.exports = class Tunnel extends EventEmitter {
|
|
12
12
|
constructor(opts = {}) {
|
|
@@ -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
|
}
|
|
@@ -42,6 +42,12 @@ module.exports = class Tunnel extends EventEmitter {
|
|
|
42
42
|
throw new Error(authCheck.error);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
if (opts.owner_email) {
|
|
46
|
+
const ownerCheck = validateOwner(opts.owner_email);
|
|
47
|
+
if (!ownerCheck.valid) {
|
|
48
|
+
throw new Error(ownerCheck.error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
45
51
|
// Validate param consistency (mismatched params)
|
|
46
52
|
const paramsCheck = validateAccessParams(opts);
|
|
47
53
|
if (!paramsCheck.valid) {
|
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, validateAuthMethod, validateAccessParams } = require('./validate');
|
|
6
|
+
const { validateSlug, validateFilePath, validateTTL, validateAccessMode, validateAuthMethod, validateAccessParams, validateOwner } = require('./validate');
|
|
7
7
|
|
|
8
8
|
const DEFAULT_HOST = 'https://mygen.site';
|
|
9
9
|
|
|
@@ -106,7 +106,7 @@ async function deploy(options) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
if (ttl != null) {
|
|
109
|
-
const ttlCheck = validateTTL(Number(ttl));
|
|
109
|
+
const ttlCheck = validateTTL(Number(ttl), { allowUnlimited: true, max: 259200 });
|
|
110
110
|
if (!ttlCheck.valid) {
|
|
111
111
|
throw new Error(ttlCheck.error);
|
|
112
112
|
}
|
|
@@ -123,6 +123,12 @@ async function deploy(options) {
|
|
|
123
123
|
throw new Error(authCheck.error);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
+
if (owner_email) {
|
|
127
|
+
const ownerCheck = validateOwner(owner_email);
|
|
128
|
+
if (!ownerCheck.valid) {
|
|
129
|
+
throw new Error(ownerCheck.error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
126
132
|
// Validate param consistency
|
|
127
133
|
const paramsCheck = validateAccessParams({ access, auth_method, password, google, telegram, allowed_ips });
|
|
128
134
|
if (!paramsCheck.valid) {
|
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
|
}
|
|
@@ -131,6 +141,36 @@ function validateAuthMethod(method) {
|
|
|
131
141
|
return { valid: true, methods };
|
|
132
142
|
}
|
|
133
143
|
|
|
144
|
+
// Email: basic check — has @, local part, domain with dot
|
|
145
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
146
|
+
// Telegram username: 5-32 chars, alphanumeric + underscores (per Telegram rules)
|
|
147
|
+
const TELEGRAM_USERNAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]{4,31}$/;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Validate owner_email value (email address or Telegram username).
|
|
151
|
+
* @param {string} value
|
|
152
|
+
* @returns {{ valid: boolean, type?: 'email'|'telegram', error?: string }}
|
|
153
|
+
*/
|
|
154
|
+
function validateOwner(value) {
|
|
155
|
+
if (!value || typeof value !== 'string') {
|
|
156
|
+
return { valid: false, error: 'Owner identity is required' };
|
|
157
|
+
}
|
|
158
|
+
const trimmed = value.trim();
|
|
159
|
+
if (!trimmed) {
|
|
160
|
+
return { valid: false, error: 'Owner identity is required' };
|
|
161
|
+
}
|
|
162
|
+
if (EMAIL_REGEX.test(trimmed)) {
|
|
163
|
+
return { valid: true, type: 'email' };
|
|
164
|
+
}
|
|
165
|
+
if (TELEGRAM_USERNAME_REGEX.test(trimmed)) {
|
|
166
|
+
return { valid: true, type: 'telegram' };
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
valid: false,
|
|
170
|
+
error: 'Owner must be a valid email address or Telegram username (5-32 chars, letters/numbers/underscores)',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
134
174
|
/**
|
|
135
175
|
* Validate that access params are consistent (no mismatched params).
|
|
136
176
|
* Mirrors server-side validation in parseAccessParams.
|
|
@@ -165,6 +205,11 @@ function validateAccessParams(opts) {
|
|
|
165
205
|
}
|
|
166
206
|
}
|
|
167
207
|
|
|
208
|
+
// Unlimited TTL requires auth
|
|
209
|
+
if (opts.ttl === 0 && !authMethod) {
|
|
210
|
+
return { valid: false, error: 'Unlimited TTL (0) requires at least one auth method (password, google, or telegram)' };
|
|
211
|
+
}
|
|
212
|
+
|
|
168
213
|
return { valid: true };
|
|
169
214
|
}
|
|
170
215
|
|
|
@@ -175,8 +220,11 @@ module.exports = {
|
|
|
175
220
|
validateAccessMode,
|
|
176
221
|
validateAuthMethod,
|
|
177
222
|
validateAccessParams,
|
|
223
|
+
validateOwner,
|
|
178
224
|
SLUG_REGEX,
|
|
179
225
|
RESERVED_SLUGS,
|
|
180
226
|
VALID_ACCESS_MODES,
|
|
181
227
|
VALID_AUTH_METHODS,
|
|
228
|
+
EMAIL_REGEX,
|
|
229
|
+
TELEGRAM_USERNAME_REGEX,
|
|
182
230
|
};
|
package/package.json
CHANGED
package/skills/share/SKILL.md
CHANGED
|
@@ -8,22 +8,52 @@ 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
|
-
## Owner
|
|
13
|
+
## Owner Identity
|
|
13
14
|
|
|
14
|
-
Deploys and tunnels require `--owner-email`.
|
|
15
|
+
Deploys and tunnels require `--owner-email`. This identifies who owns the service on the dashboard.
|
|
16
|
+
|
|
17
|
+
The value can be either:
|
|
18
|
+
- **Email address** (for Google login users): `alice@company.com`
|
|
19
|
+
- **Telegram username** (for Telegram login users): `thetelegramuser`
|
|
20
|
+
|
|
21
|
+
Ownership matching is case-insensitive.
|
|
15
22
|
|
|
16
23
|
### First use
|
|
17
24
|
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.
|
|
25
|
+
2. If not, ask the user **once**: "What email or Telegram username should I use for service management? (used for dashboard login)"
|
|
26
|
+
3. **Do NOT validate the format as email-only.** The value can be a plain username without `@` — that's a valid Telegram username. Accept any non-empty string the user provides.
|
|
27
|
+
4. Save it to `.claude/mygen.json`:
|
|
20
28
|
```json
|
|
21
29
|
{ "owner_email": "user@company.com" }
|
|
22
30
|
```
|
|
31
|
+
or for Telegram users (no `@`, not an email — this is valid):
|
|
32
|
+
```json
|
|
33
|
+
{ "owner_email": "mytelegramuser" }
|
|
34
|
+
```
|
|
23
35
|
|
|
24
36
|
### Subsequent uses
|
|
25
|
-
- Read
|
|
26
|
-
- If the user says "change my email", update the file
|
|
37
|
+
- Read owner from `.claude/mygen.json` automatically
|
|
38
|
+
- If the user says "change my email" or "change my owner", update the file
|
|
39
|
+
|
|
40
|
+
## Slug (Domain) Management
|
|
41
|
+
|
|
42
|
+
### Reuse by default
|
|
43
|
+
- When `.claude/mygen.json` already has a service entry for the **same context** (same port for tunnels, same build directory for static), reuse that slug and `admin_token`.
|
|
44
|
+
- This means running `/share` repeatedly for the same service always keeps the same URL.
|
|
45
|
+
|
|
46
|
+
### When to ask for a new domain
|
|
47
|
+
Ask the user what domain they want **only when** the context clearly requires a **different** share than what's already saved:
|
|
48
|
+
- Sharing a **different port** than the existing tunnel
|
|
49
|
+
- Sharing a **different directory** or a second static site
|
|
50
|
+
- User explicitly says "share this on a different URL" or "new domain"
|
|
51
|
+
|
|
52
|
+
Ask: "What subdomain would you like? (e.g. `my-api` → `my-api.mygen.site`, blank for auto-generated)"
|
|
53
|
+
- If blank → let the server auto-generate.
|
|
54
|
+
|
|
55
|
+
### Domain can be changed later
|
|
56
|
+
Always inform the user (at least on first use): **"You can change the domain anytime by asking, e.g. 'change my domain to new-name'."**
|
|
27
57
|
|
|
28
58
|
## Decision: Tunnel vs Static Deploy
|
|
29
59
|
|
|
@@ -52,11 +82,7 @@ Check if mygensite is installed:
|
|
|
52
82
|
npx mygensite --help 2>/dev/null || npm install -g mygensite
|
|
53
83
|
```
|
|
54
84
|
|
|
55
|
-
Load owner
|
|
56
|
-
```bash
|
|
57
|
-
cat .claude/mygen.json 2>/dev/null
|
|
58
|
-
```
|
|
59
|
-
If missing, ask the user and save it.
|
|
85
|
+
Load config from `.claude/mygen.json`. If missing, ask the user for owner identity.
|
|
60
86
|
|
|
61
87
|
### 1. Analyze the project
|
|
62
88
|
|
|
@@ -65,84 +91,190 @@ Quickly assess the project structure:
|
|
|
65
91
|
- Check for build output directories
|
|
66
92
|
- Check for server start scripts
|
|
67
93
|
- Check for running local servers (lsof -i -P | grep LISTEN)
|
|
94
|
+
- Check `.claude/mygen.json` for existing services matching current context
|
|
95
|
+
|
|
96
|
+
### 1.5. Access Control Setup (first deploy only)
|
|
97
|
+
|
|
98
|
+
**Skip this step** if reusing an existing service from `.claude/mygen.json` (same port/directory context). Settings are already saved.
|
|
99
|
+
|
|
100
|
+
On the **first deploy** of a new service, ask the user these questions:
|
|
101
|
+
|
|
102
|
+
#### Q1. Network access
|
|
103
|
+
> **Who should be able to access this?**
|
|
104
|
+
> 1. **Public** — anyone with the link (default)
|
|
105
|
+
> 2. **IP-restricted** — only your current IP
|
|
106
|
+
|
|
107
|
+
If IP-restricted, detect the user's public IP:
|
|
108
|
+
```bash
|
|
109
|
+
curl -s https://ifconfig.me
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Q2. Authentication (recommended)
|
|
113
|
+
> **Do you want to add authentication?** (recommended for security)
|
|
114
|
+
> 1. **Password** — visitors enter a password to access
|
|
115
|
+
> 2. **Google OAuth** — only specific Google accounts can access
|
|
116
|
+
> 3. **Telegram** — only specific Telegram users can access
|
|
117
|
+
> 4. **None** — no authentication
|
|
118
|
+
|
|
119
|
+
If the user picks auth, ask for the details:
|
|
120
|
+
- **Password**: "What password should visitors use?" (or auto-generate one)
|
|
121
|
+
- **Google**: "Which email addresses should have access? (comma-separated)"
|
|
122
|
+
- **Telegram**: "Which Telegram user IDs should have access? (comma-separated)"
|
|
123
|
+
|
|
124
|
+
Multiple auth methods can be combined (e.g. password + Google — visitors can use either).
|
|
125
|
+
|
|
126
|
+
#### Applying the choices
|
|
127
|
+
|
|
128
|
+
Use the answers to set these parameters in the deploy/tunnel script:
|
|
129
|
+
```js
|
|
130
|
+
access: 'public', // or 'ip'
|
|
131
|
+
allowed_ips: ['1.2.3.4'], // only when access='ip', user's detected IP
|
|
132
|
+
auth_method: 'password', // or 'google', 'telegram', 'password,google', or omit
|
|
133
|
+
password: 'chosen-password', // when auth_method includes 'password'
|
|
134
|
+
google: 'alice@co.com', // when auth_method includes 'google'
|
|
135
|
+
telegram: '123456789', // when auth_method includes 'telegram'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Save the chosen access settings in `.claude/mygen.json` alongside the service entry so they can be reused on redeploy.
|
|
139
|
+
|
|
140
|
+
> **Tip**: If `$ARGUMENTS` explicitly says "public" or "password", skip the questions and use that directly.
|
|
68
141
|
|
|
69
142
|
### 2-A. Static deploy
|
|
70
143
|
|
|
71
144
|
Build first if needed:
|
|
72
145
|
```bash
|
|
73
|
-
#
|
|
74
|
-
|
|
146
|
+
npm run build # or framework-appropriate command
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Write a temporary deploy script `.claude/mygen-deploy.mjs` and run it:
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
import localtunnel from 'mygensite';
|
|
153
|
+
|
|
154
|
+
const site = await localtunnel.deploy({
|
|
155
|
+
directory: './dist', // auto-detect: dist, build, out, .next, or '.'
|
|
156
|
+
subdomain: '{slug_or_undefined}', // from mygen.json or omit for auto
|
|
157
|
+
owner_email: '{owner_email}',
|
|
158
|
+
access: '{access}', // from access control setup
|
|
159
|
+
auth_method: '{auth_method}', // from access control setup, omit if none
|
|
160
|
+
password: '{password}', // when auth_method includes 'password'
|
|
161
|
+
google: '{emails}', // when auth_method includes 'google'
|
|
162
|
+
telegram: '{ids}', // when auth_method includes 'telegram'
|
|
163
|
+
ttl: 86400,
|
|
164
|
+
admin_token: '{token_or_undefined}', // if redeploying existing service
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
console.log(JSON.stringify({
|
|
168
|
+
url: site.url, slug: site.slug,
|
|
169
|
+
admin_token: site.admin_token,
|
|
170
|
+
password: site.password || null,
|
|
171
|
+
expires_at: site.expires_at || null,
|
|
172
|
+
}));
|
|
75
173
|
```
|
|
76
174
|
|
|
77
|
-
Deploy:
|
|
78
175
|
```bash
|
|
79
|
-
|
|
80
|
-
--directory ./dist \
|
|
81
|
-
--owner-email {email} \
|
|
82
|
-
--access ${ARGUMENTS:-public} \
|
|
83
|
-
--ttl 86400
|
|
176
|
+
node .claude/mygen-deploy.mjs && rm .claude/mygen-deploy.mjs
|
|
84
177
|
```
|
|
85
178
|
|
|
86
|
-
|
|
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
|
|
179
|
+
Parse the JSON output, update `.claude/mygen.json`.
|
|
90
180
|
|
|
91
181
|
### 2-B. Tunnel
|
|
92
182
|
|
|
93
183
|
Check if a local server is running; if not, start it:
|
|
94
184
|
```bash
|
|
95
|
-
# Check running ports
|
|
96
185
|
lsof -i -P | grep LISTEN | grep -E ':(3000|5173|8000|8080|4200|5000)'
|
|
97
186
|
```
|
|
98
187
|
|
|
99
|
-
If no server is running
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
188
|
+
If no server is running, start it in background first.
|
|
189
|
+
|
|
190
|
+
Write a tunnel keeper script `.claude/mygen-tunnel.mjs`:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
import localtunnel from 'mygensite';
|
|
194
|
+
|
|
195
|
+
const tunnel = await localtunnel({
|
|
196
|
+
port: {detected_port},
|
|
197
|
+
subdomain: '{slug_or_undefined}',
|
|
198
|
+
owner_email: '{owner_email}',
|
|
199
|
+
access: '{access}',
|
|
200
|
+
auth_method: '{auth_method}', // from access control setup, omit if none
|
|
201
|
+
password: '{password}', // when auth_method includes 'password'
|
|
202
|
+
google: '{emails}', // when auth_method includes 'google'
|
|
203
|
+
telegram: '{ids}', // when auth_method includes 'telegram'
|
|
204
|
+
ttl: 3600,
|
|
205
|
+
admin_token: '{token_or_undefined}',
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Output connection info as JSON (first line)
|
|
209
|
+
console.log(JSON.stringify({
|
|
210
|
+
url: tunnel.url, slug: tunnel.clientId,
|
|
211
|
+
admin_token: tunnel.admin_token,
|
|
212
|
+
password: tunnel.password || null,
|
|
213
|
+
expires_at: tunnel.expires_at || null,
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
// Keep alive — graceful shutdown on signals
|
|
217
|
+
process.on('SIGINT', () => { tunnel.close(); process.exit(0); });
|
|
218
|
+
process.on('SIGTERM', () => { tunnel.close(); process.exit(0); });
|
|
219
|
+
tunnel.on('close', () => { console.error('Tunnel closed'); process.exit(1); });
|
|
220
|
+
tunnel.on('error', (err) => { console.error('Tunnel error:', err.message); });
|
|
221
|
+
|
|
222
|
+
// Heartbeat every 5 min
|
|
223
|
+
setInterval(() => {
|
|
224
|
+
console.error(`[tunnel] alive — ${tunnel.url}`);
|
|
225
|
+
}, 5 * 60 * 1000);
|
|
104
226
|
```
|
|
105
227
|
|
|
106
|
-
|
|
228
|
+
Run in background and capture output:
|
|
107
229
|
```bash
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
230
|
+
node .claude/mygen-tunnel.mjs > .claude/mygen-tunnel-out.log 2>.claude/mygen-tunnel-err.log &
|
|
231
|
+
TUNNEL_PID=$!
|
|
232
|
+
echo $TUNNEL_PID > .claude/mygen-tunnel.pid
|
|
233
|
+
|
|
234
|
+
# Wait for tunnel to initialize
|
|
235
|
+
for i in $(seq 1 10); do
|
|
236
|
+
if [ -s .claude/mygen-tunnel-out.log ]; then break; fi
|
|
237
|
+
sleep 1
|
|
238
|
+
done
|
|
239
|
+
cat .claude/mygen-tunnel-out.log
|
|
113
240
|
```
|
|
114
241
|
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
242
|
+
- **CRITICAL**: The tunnel keeper script stays running in background. Do NOT delete it while active.
|
|
243
|
+
- PID is saved to `.claude/mygen-tunnel.pid` for later management.
|
|
244
|
+
- The script handles SIGINT/SIGTERM gracefully and logs heartbeats to stderr.
|
|
118
245
|
|
|
119
246
|
### 3. Save results and inform the user
|
|
120
247
|
|
|
121
|
-
**Always** save the result (slug, admin_token) to `.claude/mygen.json`:
|
|
248
|
+
**Always** save the result (slug, admin_token, access settings) to `.claude/mygen.json`:
|
|
122
249
|
```json
|
|
123
250
|
{
|
|
124
251
|
"owner_email": "user@company.com",
|
|
125
252
|
"services": {
|
|
126
253
|
"{slug}": {
|
|
127
254
|
"admin_token": "tok_xxx",
|
|
128
|
-
"type": "
|
|
255
|
+
"type": "tunnel",
|
|
256
|
+
"port": 3000,
|
|
129
257
|
"url": "https://{slug}.mygen.site",
|
|
258
|
+
"access": "public",
|
|
259
|
+
"auth_method": "password",
|
|
130
260
|
"created_at": "2025-06-01T12:00:00Z"
|
|
131
261
|
}
|
|
132
262
|
}
|
|
133
263
|
}
|
|
134
264
|
```
|
|
135
265
|
|
|
136
|
-
After deploy/tunnel completes, inform the user **concisely
|
|
266
|
+
After deploy/tunnel completes, inform the user **concisely** based on settings:
|
|
137
267
|
|
|
268
|
+
**Public, no auth:**
|
|
138
269
|
```
|
|
139
270
|
URL: https://{slug}.mygen.site
|
|
140
271
|
|
|
141
272
|
Share this link — anyone can access it.
|
|
142
273
|
Auto-expires in 24 hours.
|
|
274
|
+
You can change the domain or access settings anytime — just ask.
|
|
143
275
|
```
|
|
144
276
|
|
|
145
|
-
|
|
277
|
+
**With password auth:**
|
|
146
278
|
```
|
|
147
279
|
URL: https://{slug}.mygen.site
|
|
148
280
|
Password: {password}
|
|
@@ -150,10 +282,32 @@ Password: {password}
|
|
|
150
282
|
Share the link and password together.
|
|
151
283
|
```
|
|
152
284
|
|
|
153
|
-
|
|
285
|
+
**With Google auth:**
|
|
286
|
+
```
|
|
287
|
+
URL: https://{slug}.mygen.site
|
|
288
|
+
Allowed: {emails}
|
|
289
|
+
|
|
290
|
+
Only the listed Google accounts can access (via OAuth login).
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**With IP restriction:**
|
|
294
|
+
```
|
|
295
|
+
URL: https://{slug}.mygen.site
|
|
296
|
+
Restricted to: {ip}
|
|
297
|
+
|
|
298
|
+
Only accessible from the allowed IP address.
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
If tunnel:
|
|
302
|
+
```
|
|
303
|
+
Tunnel running in background (PID: {pid}).
|
|
304
|
+
It stays open as long as the process is alive.
|
|
154
305
|
```
|
|
155
|
-
|
|
156
|
-
|
|
306
|
+
|
|
307
|
+
### 4. Add to .gitignore
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
grep -q '.claude/' .gitignore 2>/dev/null || echo '.claude/' >> .gitignore
|
|
157
311
|
```
|
|
158
312
|
|
|
159
313
|
## Settings Changes (PATCH)
|
|
@@ -174,8 +328,9 @@ const site = mygensite.manage({
|
|
|
174
328
|
admin_token: '{admin_token}', // read from .claude/mygen.json
|
|
175
329
|
});
|
|
176
330
|
|
|
177
|
-
await site.updateAccess({
|
|
178
|
-
await site.updateAccess({
|
|
331
|
+
await site.updateAccess({ access: 'public', auth_method: '' });
|
|
332
|
+
await site.updateAccess({ auth_method: 'password', password: 'new-password' });
|
|
333
|
+
await site.updateAccess({ auth_method: 'password,google', password: 'pw', google: 'a@co.com' });
|
|
179
334
|
await site.extendTTL(86400);
|
|
180
335
|
await site.redeploy('./dist'); // only when files changed
|
|
181
336
|
await site.delete();
|
|
@@ -188,46 +343,47 @@ await site.delete();
|
|
|
188
343
|
Read the admin_token for the service from `.claude/mygen.json`:
|
|
189
344
|
|
|
190
345
|
```bash
|
|
191
|
-
#
|
|
346
|
+
# Make fully public (no auth)
|
|
192
347
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
193
348
|
-H "Authorization: Bearer {admin_token}" \
|
|
194
349
|
-H "Content-Type: application/json" \
|
|
195
|
-
-d '{"access":
|
|
350
|
+
-d '{"access": "public", "auth_method": ""}'
|
|
196
351
|
|
|
197
|
-
#
|
|
352
|
+
# Add password auth
|
|
198
353
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
199
354
|
-H "Authorization: Bearer {admin_token}" \
|
|
200
355
|
-H "Content-Type: application/json" \
|
|
201
|
-
-d '{"
|
|
356
|
+
-d '{"auth_method": "password", "password": "new-password"}'
|
|
202
357
|
|
|
203
|
-
# Add
|
|
358
|
+
# Add Google OAuth + password
|
|
204
359
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
205
360
|
-H "Authorization: Bearer {admin_token}" \
|
|
206
361
|
-H "Content-Type: application/json" \
|
|
207
|
-
-d '{"
|
|
362
|
+
-d '{"auth_method": "password,google", "password": "pw", "google": "alice@co.com"}'
|
|
208
363
|
|
|
209
|
-
#
|
|
364
|
+
# Restrict by IP
|
|
210
365
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
211
366
|
-H "Authorization: Bearer {admin_token}" \
|
|
212
367
|
-H "Content-Type: application/json" \
|
|
213
|
-
-d '{"
|
|
368
|
+
-d '{"access": "ip", "allowed_ips": "1.2.3.0/24"}'
|
|
214
369
|
|
|
215
|
-
#
|
|
370
|
+
# Extend TTL (timer resets from now)
|
|
216
371
|
curl -X PATCH https://mygen.site/api/services/{slug} \
|
|
217
372
|
-H "Authorization: Bearer {admin_token}" \
|
|
218
373
|
-H "Content-Type: application/json" \
|
|
219
|
-
-d '{"
|
|
374
|
+
-d '{"ttl": 86400}'
|
|
220
375
|
```
|
|
221
376
|
|
|
222
377
|
### PATCH vs Redeploy
|
|
223
378
|
|
|
224
379
|
| Request | Method |
|
|
225
380
|
|---------|--------|
|
|
226
|
-
| Change password | PATCH `
|
|
227
|
-
| Make it public | PATCH `access
|
|
228
|
-
|
|
|
381
|
+
| Change password | PATCH `auth_method` + `password` |
|
|
382
|
+
| Make it public | PATCH `access` + `auth_method: ""` |
|
|
383
|
+
| Add Google auth | PATCH `auth_method` + `google` |
|
|
384
|
+
| Restrict by IP | PATCH `access: "ip"` + `allowed_ips` |
|
|
229
385
|
| Extend time | PATCH `ttl` |
|
|
230
|
-
| Change
|
|
386
|
+
| Change owner | PATCH `owner_email` (email or Telegram username) |
|
|
231
387
|
| Update content (files changed) | Redeploy (`deploy --admin-token`) |
|
|
232
388
|
| Upload new files | Redeploy (`deploy --admin-token`) |
|
|
233
389
|
|
|
@@ -255,12 +411,40 @@ curl -X DELETE "https://mygen.site/api/services/{slug}?purge=true" \
|
|
|
255
411
|
|
|
256
412
|
After deleting, also remove the service entry from `.claude/mygen.json`.
|
|
257
413
|
|
|
414
|
+
## Tunnel Management
|
|
415
|
+
|
|
416
|
+
### Check if tunnel is running
|
|
417
|
+
```bash
|
|
418
|
+
if [ -f .claude/mygen-tunnel.pid ]; then
|
|
419
|
+
PID=$(cat .claude/mygen-tunnel.pid)
|
|
420
|
+
kill -0 $PID 2>/dev/null && echo "Running (PID $PID)" || echo "Stopped"
|
|
421
|
+
fi
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Stop tunnel
|
|
425
|
+
```bash
|
|
426
|
+
if [ -f .claude/mygen-tunnel.pid ]; then
|
|
427
|
+
kill $(cat .claude/mygen-tunnel.pid) 2>/dev/null
|
|
428
|
+
rm -f .claude/mygen-tunnel.pid .claude/mygen-tunnel-out.log .claude/mygen-tunnel-err.log
|
|
429
|
+
fi
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Restart tunnel
|
|
433
|
+
Stop the old one, then run Step 2-B again. The same slug and admin_token will be reused from `.claude/mygen.json`.
|
|
434
|
+
|
|
435
|
+
## Reference
|
|
436
|
+
|
|
437
|
+
- API docs: https://mygen.site/docs (NOT /api/docs)
|
|
438
|
+
- LLM-readable docs: https://mygen.site/llms.txt
|
|
439
|
+
|
|
258
440
|
## Important Notes
|
|
259
441
|
|
|
260
|
-
- Do not ask the user for technical choices. Decide automatically.
|
|
261
|
-
-
|
|
442
|
+
- Do not ask the user for technical choices (tunnel vs static). Decide automatically.
|
|
443
|
+
- **Reuse the same slug** for the same context. Only ask for a new domain when sharing something different (different port, different directory, etc.).
|
|
262
444
|
- Handle errors yourself (port conflicts, build failures, etc.).
|
|
263
|
-
- If `$ARGUMENTS`
|
|
264
|
-
- If `$ARGUMENTS` is empty,
|
|
445
|
+
- If `$ARGUMENTS` explicitly specifies access (e.g. "password", "public"), skip the access control questions and use that directly.
|
|
446
|
+
- If `$ARGUMENTS` is empty, ask the access control questions on first deploy.
|
|
265
447
|
- admin_token is issued **only once**. If lost, it cannot be recovered. Always save to `.claude/mygen.json`.
|
|
266
|
-
-
|
|
448
|
+
- **For tunnel/deploy creation, use the mygensite Node.js library** (not curl). For PATCH settings and DELETE, curl is fine.
|
|
449
|
+
- Clean up temp scripts after use. Keep `.claude/mygen-tunnel.mjs` alive while tunnel is running.
|
|
450
|
+
- Add `.claude/` to `.gitignore` (contains tokens).
|