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.
@@ -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 access control",
9
- "version": "2.0.0"
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 2-layer access control (network: public/ip + auth: password/google/telegram)",
16
- "version": "2.0.0",
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 with access control",
4
- "version": "1.0.0",
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 management
50
- ttl: 3600, // optional: seconds, 60-86400 (default: 3600)
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 management
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 | use 60-86400 (seconds) |
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 < 60 || ttl > 86400) {
99
- return { valid: false, error: 'TTL must be between 60 and 86400 seconds (1 min to 24 hours)' };
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mygensite",
3
3
  "description": "Expose your localhost to mygen.site with access control",
4
- "version": "2.0.0",
4
+ "version": "2.2.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -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 Email
13
+ ## Owner Identity
13
14
 
14
- Deploys and tunnels require `--owner-email`. The email is used for dashboard management.
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. Save it to `.claude/mygen.json`:
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 email from `.claude/mygen.json` automatically
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 email:
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
- # Framework-specific (example)
74
- npm run build
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
- npx mygensite deploy \
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
- - `--directory`: build output directory (auto-detect dist, build, out, etc.)
87
- - If no build directory exists and there are only HTML files, use current directory (`.`)
88
- - Default access is public
89
- - TTL is 24 hours
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
- ```bash
101
- # Find dev/start script in package.json and run it
102
- npm run dev &
103
- # Or framework-appropriate command
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
- Find the server port and create a tunnel:
228
+ Run in background and capture output:
107
229
  ```bash
108
- npx mygensite \
109
- --port {detected port} \
110
- --owner-email {email} \
111
- --access ${ARGUMENTS:-public} \
112
- --ttl 3600
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
- - Default access is public
116
- - TTL is 1 hour (shorter for tunnels)
117
- - **CRITICAL: The tunnel process must keep running.** The tunnel only works while the `npx mygensite` process is alive. If it exits, the tunnel closes immediately and users get 502. Run it in a way that stays alive (e.g. background with `&`, separate terminal, or keep the script running).
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": "static",
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
- If password-protected:
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
- If tunnel, add:
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
- The tunnel stays open as long as this process is running.
156
- Do NOT close this terminal or kill the process — the tunnel will stop working.
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({ mode: 'public' });
178
- await site.updateAccess({ mode: 'password', password: 'new-password' });
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
- # Change access mode (e.g. password -> public)
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": {"mode": "public"}}'
350
+ -d '{"access": "public", "auth_method": ""}'
196
351
 
197
- # Change password
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 '{"access": {"password": "new-password"}}'
356
+ -d '{"auth_method": "password", "password": "new-password"}'
202
357
 
203
- # Add IP restriction
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 '{"access": {"mode": "ip_only", "allowed_ips": ["1.2.3.0/24"]}}'
362
+ -d '{"auth_method": "password,google", "password": "pw", "google": "alice@co.com"}'
208
363
 
209
- # Extend TTL (timer resets from now)
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 '{"ttl": 86400}'
368
+ -d '{"access": "ip", "allowed_ips": "1.2.3.0/24"}'
214
369
 
215
- # Change owner email
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 '{"owner_email": "new@company.com"}'
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 `access.password` |
227
- | Make it public | PATCH `access.mode` |
228
- | Restrict by IP | PATCH `access.allowed_ips` |
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 email | PATCH `owner_email` |
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
- - Let the slug (subdomain) be auto-generated unless the user specifies one.
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` is "password", deploy with password protection.
264
- - If `$ARGUMENTS` is empty, deploy as public.
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
- - Add `.claude/mygen.json` to `.gitignore` (contains tokens).
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).