@supacloud/admin 0.1.11 → 0.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.
Files changed (3) hide show
  1. package/README.md +21 -0
  2. package/dist/index.js +312 -2
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -30,3 +30,24 @@ Project commands owned by this CLI:
30
30
  - `project restore`
31
31
  - `project restart`
32
32
  - `project update_settings`
33
+
34
+ Gateway / Caddy commands (config is injected via the Caddy JSON Admin API; requires admin privileges):
35
+
36
+ - `gateway routes` — list custom gateway routes
37
+ - `gateway upsert_route` — create or replace a route
38
+ - `gateway update_route` — replace a route by id
39
+ - `gateway delete_route` — remove a route by id
40
+ - `gateway config` — update rate-limit tier, CORS origins, or JWT settings
41
+ - `gateway get_certificate` / `gateway update_certificate`
42
+ - `gateway issue_certificate` — issue or renew with lego
43
+ - `gateway deploy_certificate` — deploy an existing PEM cert/key pair
44
+ - `gateway rebuild` — rebuild all tenant gateway configs (`--clean` for a full rebuild)
45
+ - `gateway custom_hostname` / `gateway set_custom_hostname` / `gateway delete_custom_hostname` / `gateway verify_custom_hostname`
46
+
47
+ ```bash
48
+ supacloud-admin gateway routes --ref abc123
49
+ supacloud-admin gateway upsert_route --ref abc123 --route_id webhook \
50
+ --hosts "api.example.com" --paths "/webhook/*" --upstream 10.0.0.5:8080
51
+ supacloud-admin gateway config --ref abc123 --rate_limit_tier pro
52
+ supacloud-admin gateway rebuild --ref abc123 --clean
53
+ ```
package/dist/index.js CHANGED
@@ -34043,7 +34043,7 @@ Actions: list, deploy, deploy_bundle, source, delete, check`, {
34043
34043
  slug: exports_external.string().optional().describe("[deploy/deploy_bundle/source/delete/check] Function name"),
34044
34044
  code: exports_external.string().optional().describe("[deploy/check] Function source code (TypeScript)"),
34045
34045
  path: exports_external.string().optional().describe("[deploy/check] Local file path to read code from (alternative to code)"),
34046
- files: exports_external.record(exports_external.string()).optional().describe("[deploy_bundle] File map: { 'index.ts': '...', '_shared/x.ts': '...' }"),
34046
+ files: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("[deploy_bundle] File map: { 'index.ts': '...', '_shared/x.ts': '...' }"),
34047
34047
  entrypoint: exports_external.string().optional().describe("[deploy_bundle] Entrypoint file (default: index.ts)"),
34048
34048
  minify: exports_external.boolean().optional().describe("[deploy/deploy_bundle] Minify bundle")
34049
34049
  }, async (args) => {
@@ -34300,7 +34300,7 @@ function registerAdminProjectCliTools(server, http) {
34300
34300
  name: exports_external.string().optional().describe("[create] Project name"),
34301
34301
  region: exports_external.string().optional().describe("[create] Region (default: local)"),
34302
34302
  organization_id: exports_external.string().optional().describe("[create] Organization ID"),
34303
- settings: exports_external.record(exports_external.unknown()).optional().describe("[update_settings] Config fields to update"),
34303
+ settings: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("[update_settings] Config fields to update"),
34304
34304
  log_type: exports_external.enum(["all", "auth", "database", "api"]).optional().describe("[logs] Filter by service")
34305
34305
  }, async ({ action, ref, name, region, organization_id, settings, log_type }) => {
34306
34306
  let text;
@@ -34370,6 +34370,300 @@ function registerAdminProjectCliTools(server, http) {
34370
34370
  });
34371
34371
  }
34372
34372
 
34373
+ // src/shared/tools/gateway-tools.ts
34374
+ var stringArray = exports_external.preprocess((value) => {
34375
+ if (value === undefined || value === null)
34376
+ return;
34377
+ if (Array.isArray(value))
34378
+ return value;
34379
+ const text = String(value).trim();
34380
+ if (!text)
34381
+ return [];
34382
+ if (text.startsWith("[")) {
34383
+ try {
34384
+ return JSON.parse(text);
34385
+ } catch {
34386
+ return [text];
34387
+ }
34388
+ }
34389
+ return text.split(",").map((item) => item.trim()).filter(Boolean);
34390
+ }, exports_external.array(exports_external.string()).optional());
34391
+ var headersRecord = exports_external.preprocess((value) => {
34392
+ if (value === undefined || value === null)
34393
+ return;
34394
+ if (typeof value === "object" && !Array.isArray(value))
34395
+ return value;
34396
+ const text = String(value).trim();
34397
+ if (!text)
34398
+ return;
34399
+ try {
34400
+ const parsed = JSON.parse(text);
34401
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
34402
+ return parsed;
34403
+ } catch {}
34404
+ const out = {};
34405
+ for (const part of text.split(",")) {
34406
+ const idx = part.indexOf(":");
34407
+ if (idx > 0)
34408
+ out[part.slice(0, idx).trim()] = part.slice(idx + 1).trim();
34409
+ }
34410
+ return Object.keys(out).length > 0 ? out : undefined;
34411
+ }, exports_external.record(exports_external.string(), exports_external.string()).optional());
34412
+ var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
34413
+ var simple2 = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
34414
+ function registerGatewayTools(server, http, options = {}) {
34415
+ const { projectRef } = options;
34416
+ server.tool("gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
34417
+ Actions: routes, upsert_route, update_route, delete_route, config, get_certificate, update_certificate, issue_certificate, deploy_certificate, rebuild, custom_hostname, set_custom_hostname, delete_custom_hostname, verify_custom_hostname`, {
34418
+ action: exports_external.enum([
34419
+ "routes",
34420
+ "upsert_route",
34421
+ "update_route",
34422
+ "delete_route",
34423
+ "config",
34424
+ "get_certificate",
34425
+ "update_certificate",
34426
+ "issue_certificate",
34427
+ "deploy_certificate",
34428
+ "rebuild",
34429
+ "custom_hostname",
34430
+ "set_custom_hostname",
34431
+ "delete_custom_hostname",
34432
+ "verify_custom_hostname"
34433
+ ]).describe("Action"),
34434
+ ref: exports_external.string().optional().describe(projectRef ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
34435
+ route_id: exports_external.string().optional().describe("[upsert_route/update_route/delete_route] 路由 ID(字母/数字/_/-,1-64)"),
34436
+ hosts: stringArray.describe("[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
34437
+ paths: stringArray.describe("[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-20)"),
34438
+ upstream: exports_external.string().optional().describe("[upsert_route/update_route] 反代上游 host:port 或 http(s)://host[:port]"),
34439
+ upstream_tls_insecure_skip_verify: exports_external.boolean().optional().describe("[upsert_route/update_route] 上游 TLS 跳过校验"),
34440
+ static_root: exports_external.string().optional().describe("[upsert_route/update_route] 静态站点根目录(与 upstream 二选一)"),
34441
+ rewrite_uri: exports_external.string().optional().describe("[upsert_route/update_route] 重写 URI(以 / 开头)"),
34442
+ strip_prefix: exports_external.string().optional().describe("[upsert_route/update_route] 去除前缀"),
34443
+ headers: headersRecord.describe("[upsert_route/update_route] 自定义请求头,JSON 或 K:V,K2:V2"),
34444
+ cors: stringArray.describe("[upsert_route/update_route] 额外 CORS 源,逗号分隔"),
34445
+ priority: exports_external.number().optional().describe("[upsert_route/update_route] 路由优先级"),
34446
+ enabled: exports_external.boolean().optional().describe("[upsert_route/update_route] 是否启用"),
34447
+ rate_limit_tier: exports_external.enum(["free", "pro", "enterprise"]).optional().describe("[config] 限流档位"),
34448
+ cors_origins: exports_external.string().optional().describe("[config] CORS 源(逗号分隔)"),
34449
+ jwt_enabled: exports_external.boolean().optional().describe("[config] 是否启用 JWT"),
34450
+ jwt_secret: exports_external.string().optional().describe("[config] JWT 密钥"),
34451
+ cert_mode: exports_external.enum(["lego", "manual"]).optional().describe("[update_certificate] 证书模式"),
34452
+ challenge: exports_external.enum(["dns-01", "http-01"]).optional().describe("[update_certificate/issue_certificate] ACME challenge"),
34453
+ email: exports_external.string().optional().describe("[update_certificate/issue_certificate] ACME 邮箱"),
34454
+ dns_provider: exports_external.string().optional().describe("[update_certificate/issue_certificate] DNS 提供商"),
34455
+ dns_env: stringArray.describe("[update_certificate/issue_certificate] DNS 环境变量 KEY=VALUE 列表"),
34456
+ domains: stringArray.describe("[update_certificate/issue_certificate/deploy_certificate] 域名列表"),
34457
+ auto_renew: exports_external.boolean().optional().describe("[update_certificate/issue_certificate] 自动续期"),
34458
+ renew: exports_external.boolean().optional().describe("[issue_certificate] 仅续期已有证书"),
34459
+ cert: exports_external.string().optional().describe("[deploy_certificate] PEM 证书内容"),
34460
+ key: exports_external.string().optional().describe("[deploy_certificate] PEM 私钥内容"),
34461
+ clean: exports_external.boolean().optional().describe("[rebuild] 清理后全量重建"),
34462
+ custom_hostname: exports_external.string().optional().describe("[set_custom_hostname] 自定义域名")
34463
+ }, async (args) => {
34464
+ const resolveRef3 = (override) => {
34465
+ const ref2 = projectRef || override;
34466
+ if (!ref2)
34467
+ throw new Error("'ref' is required for this action");
34468
+ return ref2;
34469
+ };
34470
+ const need = (field, value) => {
34471
+ if (value === undefined || value === null || value === "")
34472
+ throw new Error(`'${field}' is required for '${args.action}'`);
34473
+ };
34474
+ const {
34475
+ action,
34476
+ ref,
34477
+ route_id,
34478
+ hosts,
34479
+ paths,
34480
+ upstream,
34481
+ upstream_tls_insecure_skip_verify,
34482
+ static_root,
34483
+ rewrite_uri,
34484
+ strip_prefix,
34485
+ headers,
34486
+ cors,
34487
+ priority,
34488
+ enabled,
34489
+ rate_limit_tier,
34490
+ cors_origins,
34491
+ jwt_enabled,
34492
+ jwt_secret,
34493
+ cert_mode,
34494
+ challenge,
34495
+ email: email3,
34496
+ dns_provider,
34497
+ dns_env,
34498
+ domains,
34499
+ auto_renew,
34500
+ renew,
34501
+ cert,
34502
+ key,
34503
+ clean,
34504
+ custom_hostname
34505
+ } = args;
34506
+ const projectRefValue = resolveRef3(ref);
34507
+ let text;
34508
+ switch (action) {
34509
+ case "routes":
34510
+ text = ok2(await http.get(`/v1/projects/${projectRefValue}/gateway/routes`));
34511
+ break;
34512
+ case "upsert_route": {
34513
+ need("route_id", route_id);
34514
+ need("hosts", hosts);
34515
+ need("paths", paths);
34516
+ const body = {
34517
+ id: route_id,
34518
+ hosts,
34519
+ path: paths.length === 1 ? paths[0] : paths
34520
+ };
34521
+ if (upstream !== undefined)
34522
+ body.upstream = upstream;
34523
+ if (upstream_tls_insecure_skip_verify !== undefined)
34524
+ body.upstream_tls_insecure_skip_verify = upstream_tls_insecure_skip_verify;
34525
+ if (static_root !== undefined)
34526
+ body.static_root = static_root;
34527
+ if (rewrite_uri !== undefined)
34528
+ body.rewrite_uri = rewrite_uri;
34529
+ if (strip_prefix !== undefined)
34530
+ body.strip_prefix = strip_prefix;
34531
+ if (headers !== undefined)
34532
+ body.headers = headers;
34533
+ if (cors !== undefined)
34534
+ body.cors = cors;
34535
+ if (priority !== undefined)
34536
+ body.priority = priority;
34537
+ if (enabled !== undefined)
34538
+ body.enabled = enabled;
34539
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/routes`, body));
34540
+ break;
34541
+ }
34542
+ case "update_route": {
34543
+ need("route_id", route_id);
34544
+ need("hosts", hosts);
34545
+ need("paths", paths);
34546
+ const body = {
34547
+ hosts,
34548
+ path: paths.length === 1 ? paths[0] : paths
34549
+ };
34550
+ if (upstream !== undefined)
34551
+ body.upstream = upstream;
34552
+ if (upstream_tls_insecure_skip_verify !== undefined)
34553
+ body.upstream_tls_insecure_skip_verify = upstream_tls_insecure_skip_verify;
34554
+ if (static_root !== undefined)
34555
+ body.static_root = static_root;
34556
+ if (rewrite_uri !== undefined)
34557
+ body.rewrite_uri = rewrite_uri;
34558
+ if (strip_prefix !== undefined)
34559
+ body.strip_prefix = strip_prefix;
34560
+ if (headers !== undefined)
34561
+ body.headers = headers;
34562
+ if (cors !== undefined)
34563
+ body.cors = cors;
34564
+ if (priority !== undefined)
34565
+ body.priority = priority;
34566
+ if (enabled !== undefined)
34567
+ body.enabled = enabled;
34568
+ text = ok2(await http.put(`/v1/projects/${projectRefValue}/gateway/routes/${route_id}`, body));
34569
+ break;
34570
+ }
34571
+ case "delete_route": {
34572
+ need("route_id", route_id);
34573
+ text = ok2(await http.delete(`/v1/projects/${projectRefValue}/gateway/routes/${route_id}`));
34574
+ break;
34575
+ }
34576
+ case "config": {
34577
+ const body = {};
34578
+ if (rate_limit_tier !== undefined)
34579
+ body.rate_limit_tier = rate_limit_tier;
34580
+ if (cors_origins !== undefined)
34581
+ body.cors_origins = cors_origins;
34582
+ if (jwt_enabled !== undefined)
34583
+ body.jwt_enabled = jwt_enabled;
34584
+ if (jwt_secret !== undefined)
34585
+ body.jwt_secret = jwt_secret;
34586
+ if (Object.keys(body).length === 0)
34587
+ throw new Error("At least one of rate_limit_tier, cors_origins, jwt_enabled, jwt_secret is required");
34588
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/config`, body));
34589
+ break;
34590
+ }
34591
+ case "get_certificate":
34592
+ text = ok2(await http.get(`/v1/projects/${projectRefValue}/gateway/certificate`));
34593
+ break;
34594
+ case "update_certificate": {
34595
+ const body = {};
34596
+ if (cert_mode !== undefined)
34597
+ body.mode = cert_mode;
34598
+ if (challenge !== undefined)
34599
+ body.challenge = challenge;
34600
+ if (email3 !== undefined)
34601
+ body.email = email3;
34602
+ if (dns_provider !== undefined)
34603
+ body.dns_provider = dns_provider;
34604
+ if (dns_env !== undefined)
34605
+ body.dns_env = dns_env;
34606
+ if (domains !== undefined)
34607
+ body.domains = domains;
34608
+ if (auto_renew !== undefined)
34609
+ body.auto_renew = auto_renew;
34610
+ text = ok2(await http.put(`/v1/projects/${projectRefValue}/gateway/certificate`, body));
34611
+ break;
34612
+ }
34613
+ case "issue_certificate": {
34614
+ const body = {};
34615
+ if (challenge !== undefined)
34616
+ body.challenge = challenge;
34617
+ if (email3 !== undefined)
34618
+ body.email = email3;
34619
+ if (dns_provider !== undefined)
34620
+ body.dns_provider = dns_provider;
34621
+ if (dns_env !== undefined)
34622
+ body.dns_env = dns_env;
34623
+ if (domains !== undefined)
34624
+ body.domains = domains;
34625
+ if (auto_renew !== undefined)
34626
+ body.auto_renew = auto_renew;
34627
+ if (renew !== undefined)
34628
+ body.renew = renew;
34629
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/certificate/issue`, body));
34630
+ break;
34631
+ }
34632
+ case "deploy_certificate": {
34633
+ need("cert", cert);
34634
+ need("key", key);
34635
+ const body = { cert, key };
34636
+ if (domains !== undefined)
34637
+ body.domains = domains;
34638
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/certificate/deploy`, body));
34639
+ break;
34640
+ }
34641
+ case "rebuild": {
34642
+ const query = clean ? "?clean=true" : "";
34643
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/rebuild-all${query}`));
34644
+ break;
34645
+ }
34646
+ case "custom_hostname":
34647
+ text = ok2(await http.get(`/v1/projects/${projectRefValue}/custom-hostname`));
34648
+ break;
34649
+ case "set_custom_hostname": {
34650
+ need("custom_hostname", custom_hostname);
34651
+ text = simple2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname`, { custom_hostname }), `Custom hostname ${custom_hostname} requested`);
34652
+ break;
34653
+ }
34654
+ case "delete_custom_hostname":
34655
+ text = simple2(await http.delete(`/v1/projects/${projectRefValue}/custom-hostname`), "Custom hostname removed");
34656
+ break;
34657
+ case "verify_custom_hostname":
34658
+ text = ok2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname/verify`));
34659
+ break;
34660
+ default:
34661
+ text = `❌ Unknown action: ${action}`;
34662
+ }
34663
+ return { content: [{ type: "text", text }] };
34664
+ });
34665
+ }
34666
+
34373
34667
  // src/index.ts
34374
34668
  var adminProjectActionSchema = exports_external.enum([
34375
34669
  "list",
@@ -34456,6 +34750,10 @@ EXAMPLES
34456
34750
  supacloud-admin project create --name my-app
34457
34751
  supacloud-admin project list
34458
34752
  supacloud-admin platform metrics
34753
+ supacloud-admin gateway routes --ref abc123
34754
+ supacloud-admin gateway upsert_route --ref abc123 --route_id webhook --hosts "api.example.com" --paths "/webhook/*" --upstream 10.0.0.5:8080
34755
+ supacloud-admin gateway config --ref abc123 --rate_limit_tier pro
34756
+ supacloud-admin gateway rebuild --ref abc123 --clean
34459
34757
  `);
34460
34758
  }
34461
34759
  function createAdminTools() {
@@ -34514,6 +34812,17 @@ function createAdminTools() {
34514
34812
  ]
34515
34813
  })
34516
34814
  };
34815
+ tools.gateway = {
34816
+ schema: { action: exports_external.string() },
34817
+ callback: async () => ({
34818
+ content: [
34819
+ {
34820
+ type: "text",
34821
+ text: "⚠️ Gateway / Caddy commands require SUPACLOUD_API_URL and SUPACLOUD_API_TOKEN (admin privileges)."
34822
+ }
34823
+ ]
34824
+ })
34825
+ };
34517
34826
  };
34518
34827
  registerAdminHelp();
34519
34828
  if (context.host) {
@@ -34532,6 +34841,7 @@ function createAdminTools() {
34532
34841
  token: context.apiToken
34533
34842
  });
34534
34843
  Object.assign(tools, captureTools((server) => registerAdminProjectCliTools(server, http)));
34844
+ Object.assign(tools, captureTools((server) => registerGatewayTools(server, http)));
34535
34845
  const advancedTools = captureTools((server) => registerAdvancedTools(server, http));
34536
34846
  if (advancedTools.platform) {
34537
34847
  tools.platform = advancedTools.platform;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/admin",
3
- "version": "0.1.11",
3
+ "version": "0.2.0",
4
4
  "description": "Platform administration CLI for SupaCloud operators",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,12 +30,12 @@
30
30
  "directory": "packages/admin"
31
31
  },
32
32
  "dependencies": {
33
- "ssh2": "^1.16.0",
33
+ "ssh2": "^1.17.0",
34
34
  "zod": "^4.4.3"
35
35
  },
36
36
  "devDependencies": {
37
- "@types/bun": "^1.3.9",
37
+ "@types/bun": "^1.3.14",
38
38
  "@types/ssh2": "^1.15.5",
39
- "typescript": "^6.0.0"
39
+ "typescript": "^6.0.3"
40
40
  }
41
41
  }