@insforge/cli 0.1.41 → 0.1.43

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/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync6 } from "fs";
5
- import { join as join7, dirname } from "path";
4
+ import { readFileSync as readFileSync7 } from "fs";
5
+ import { join as join8, dirname } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { Command } from "commander";
8
8
  import * as clack14 from "@clack/prompts";
@@ -797,6 +797,47 @@ async function reportCliUsage(toolName, success, maxRetries = 1) {
797
797
  }
798
798
  }
799
799
 
800
+ // src/lib/analytics.ts
801
+ import { PostHog } from "posthog-node";
802
+ var POSTHOG_API_KEY = "phc_ueV1ii62wdBTkH7E70ugyeqHIHu8dFDdjs0qq3TZhJz";
803
+ var POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
804
+ var client = null;
805
+ function getClient() {
806
+ if (!POSTHOG_API_KEY) return null;
807
+ if (!client) {
808
+ client = new PostHog(POSTHOG_API_KEY, { host: POSTHOG_HOST });
809
+ }
810
+ return client;
811
+ }
812
+ function captureEvent(distinctId, event, properties) {
813
+ try {
814
+ getClient()?.capture({ distinctId, event, properties });
815
+ } catch {
816
+ }
817
+ }
818
+ function trackCommand(command, distinctId, properties) {
819
+ captureEvent(distinctId, "cli_command_invoked", {
820
+ command,
821
+ ...properties
822
+ });
823
+ }
824
+ function trackDiagnose(subcommand, config) {
825
+ captureEvent(config.project_id, "cli_diagnose_invoked", {
826
+ subcommand,
827
+ project_id: config.project_id,
828
+ project_name: config.project_name,
829
+ org_id: config.org_id,
830
+ region: config.region,
831
+ oss_mode: config.project_id === "oss-project"
832
+ });
833
+ }
834
+ async function shutdownAnalytics() {
835
+ try {
836
+ if (client) await client.shutdown();
837
+ } catch {
838
+ }
839
+ }
840
+
800
841
  // src/commands/projects/link.ts
801
842
  function buildOssHost(appkey, region) {
802
843
  return `https://${appkey}.${region}.insforge.app`;
@@ -831,6 +872,7 @@ function registerProjectLinkCommand(program2) {
831
872
  } else {
832
873
  outputSuccess(`Linked to direct project at ${projectConfig2.oss_host}`);
833
874
  }
875
+ trackCommand("link", "oss-org", { direct: true });
834
876
  await installSkills(json);
835
877
  await reportCliUsage("cli.link_direct", true, 6);
836
878
  try {
@@ -916,6 +958,7 @@ function registerProjectLinkCommand(program2) {
916
958
  oss_host: buildOssHost(project.appkey, project.region)
917
959
  };
918
960
  saveProjectConfig(projectConfig);
961
+ trackCommand("link", project.organization_id);
919
962
  if (json) {
920
963
  outputJson({ success: true, project: { id: project.id, name: project.name, region: project.region } });
921
964
  } else {
@@ -930,6 +973,8 @@ function registerProjectLinkCommand(program2) {
930
973
  } catch (err) {
931
974
  await reportCliUsage("cli.link", false);
932
975
  handleError(err, json);
976
+ } finally {
977
+ await shutdownAnalytics();
933
978
  }
934
979
  });
935
980
  }
@@ -967,7 +1012,15 @@ async function ossFetch(path4, options = {}) {
967
1012
  const res = await fetch(`${config.oss_host}${path4}`, { ...options, headers });
968
1013
  if (!res.ok) {
969
1014
  const err = await res.json().catch(() => ({}));
970
- throw new CLIError(err.error ?? `OSS request failed: ${res.status}`);
1015
+ let message = err.message ?? err.error ?? `OSS request failed: ${res.status}`;
1016
+ if (err.nextActions) {
1017
+ message += `
1018
+ ${err.nextActions}`;
1019
+ }
1020
+ if (res.status === 404 && path4.startsWith("/api/compute")) {
1021
+ message = "Compute services are not available on this backend.\nSelf-hosted: upgrade your InsForge instance. Cloud: contact your InsForge admin to enable compute.";
1022
+ }
1023
+ throw new CLIError(message);
971
1024
  }
972
1025
  return res;
973
1026
  }
@@ -1896,41 +1949,6 @@ async function readEnvFile(cwd) {
1896
1949
  return [];
1897
1950
  }
1898
1951
 
1899
- // src/lib/analytics.ts
1900
- import { PostHog } from "posthog-node";
1901
- var POSTHOG_API_KEY = "phc_ueV1ii62wdBTkH7E70ugyeqHIHu8dFDdjs0qq3TZhJz";
1902
- var POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
1903
- var client = null;
1904
- function getClient() {
1905
- if (!POSTHOG_API_KEY) return null;
1906
- if (!client) {
1907
- client = new PostHog(POSTHOG_API_KEY, { host: POSTHOG_HOST });
1908
- }
1909
- return client;
1910
- }
1911
- function captureEvent(distinctId, event, properties) {
1912
- try {
1913
- getClient()?.capture({ distinctId, event, properties });
1914
- } catch {
1915
- }
1916
- }
1917
- function trackDiagnose(subcommand, config) {
1918
- captureEvent(config.project_id, "cli_diagnose_invoked", {
1919
- subcommand,
1920
- project_id: config.project_id,
1921
- project_name: config.project_name,
1922
- org_id: config.org_id,
1923
- region: config.region,
1924
- oss_mode: config.project_id === "oss-project"
1925
- });
1926
- }
1927
- async function shutdownAnalytics() {
1928
- try {
1929
- if (client) await client.shutdown();
1930
- } catch {
1931
- }
1932
- }
1933
-
1934
1952
  // src/commands/deployments/deploy.ts
1935
1953
  import * as path2 from "path";
1936
1954
  import * as fs2 from "fs/promises";
@@ -2363,6 +2381,7 @@ function registerCreateCommand(program2) {
2363
2381
  }
2364
2382
  }
2365
2383
  await installSkills(json);
2384
+ trackCommand("create", orgId);
2366
2385
  await reportCliUsage("cli.create", true, 6);
2367
2386
  if (hasTemplate) {
2368
2387
  const installSpinner = !json ? clack10.spinner() : null;
@@ -3271,6 +3290,445 @@ function registerSchedulesLogsCommand(schedulesCmd2) {
3271
3290
  });
3272
3291
  }
3273
3292
 
3293
+ // src/commands/compute/list.ts
3294
+ function registerComputeListCommand(computeCmd2) {
3295
+ computeCmd2.command("list").description("List all compute services").action(async (_opts, cmd) => {
3296
+ const { json } = getRootOpts(cmd);
3297
+ try {
3298
+ await requireAuth();
3299
+ const res = await ossFetch("/api/compute/services");
3300
+ const raw = await res.json();
3301
+ const services = Array.isArray(raw) ? raw : [];
3302
+ if (json) {
3303
+ outputJson(services);
3304
+ } else {
3305
+ if (services.length === 0) {
3306
+ console.log("No compute services found.");
3307
+ await reportCliUsage("cli.compute.list", true);
3308
+ return;
3309
+ }
3310
+ outputTable(
3311
+ ["Name", "Status", "Image", "CPU", "Memory", "Endpoint"],
3312
+ services.map((s) => [
3313
+ String(s.name ?? "-"),
3314
+ String(s.status ?? "-"),
3315
+ String(s.imageUrl ?? "-"),
3316
+ String(s.cpu ?? "-"),
3317
+ s.memory ? `${s.memory}MB` : "-",
3318
+ String(s.endpointUrl ?? "-")
3319
+ ])
3320
+ );
3321
+ }
3322
+ await reportCliUsage("cli.compute.list", true);
3323
+ } catch (err) {
3324
+ await reportCliUsage("cli.compute.list", false);
3325
+ handleError(err, json);
3326
+ }
3327
+ });
3328
+ }
3329
+
3330
+ // src/commands/compute/get.ts
3331
+ function registerComputeGetCommand(computeCmd2) {
3332
+ computeCmd2.command("get <id>").description("Get details of a compute service").action(async (id, _opts, cmd) => {
3333
+ const { json } = getRootOpts(cmd);
3334
+ try {
3335
+ await requireAuth();
3336
+ const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}`);
3337
+ const service = await res.json();
3338
+ if (json) {
3339
+ outputJson(service);
3340
+ } else {
3341
+ outputInfo(`Name: ${service.name}`);
3342
+ outputInfo(`ID: ${service.id}`);
3343
+ outputInfo(`Status: ${service.status}`);
3344
+ outputInfo(`Image: ${service.imageUrl}`);
3345
+ outputInfo(`CPU: ${service.cpu}`);
3346
+ outputInfo(`Memory: ${service.memory}MB`);
3347
+ outputInfo(`Region: ${service.region}`);
3348
+ outputInfo(`Endpoint: ${service.endpointUrl ?? "n/a"}`);
3349
+ outputInfo(`Created: ${service.createdAt}`);
3350
+ }
3351
+ await reportCliUsage("cli.compute.get", true);
3352
+ } catch (err) {
3353
+ await reportCliUsage("cli.compute.get", false);
3354
+ handleError(err, json);
3355
+ }
3356
+ });
3357
+ }
3358
+
3359
+ // src/commands/compute/create.ts
3360
+ function registerComputeCreateCommand(computeCmd2) {
3361
+ computeCmd2.command("create").description("Create and deploy a compute service").requiredOption("--name <name>", "Service name (DNS-safe, e.g. my-api)").requiredOption("--image <image>", "Docker image URL (e.g. nginx:alpine)").option("--port <port>", "Container port", "8080").option("--cpu <tier>", "CPU tier (shared-1x, shared-2x, performance-1x, performance-2x, performance-4x)", "shared-1x").option("--memory <mb>", "Memory in MB (256, 512, 1024, 2048, 4096, 8192)", "512").option("--region <region>", "Fly.io region", "iad").option("--env <json>", "Environment variables as JSON object").action(async (opts, cmd) => {
3362
+ const { json } = getRootOpts(cmd);
3363
+ try {
3364
+ await requireAuth();
3365
+ const body = {
3366
+ name: opts.name,
3367
+ imageUrl: opts.image,
3368
+ port: Number(opts.port),
3369
+ cpu: opts.cpu,
3370
+ memory: Number(opts.memory),
3371
+ region: opts.region
3372
+ };
3373
+ if (opts.env) {
3374
+ try {
3375
+ body.envVars = JSON.parse(opts.env);
3376
+ } catch {
3377
+ throw new CLIError("Invalid JSON for --env");
3378
+ }
3379
+ }
3380
+ const res = await ossFetch("/api/compute/services", {
3381
+ method: "POST",
3382
+ body: JSON.stringify(body)
3383
+ });
3384
+ const service = await res.json();
3385
+ if (json) {
3386
+ outputJson(service);
3387
+ } else {
3388
+ outputSuccess(`Service "${service.name}" created [${service.status}]`);
3389
+ if (service.endpointUrl) {
3390
+ console.log(` Endpoint: ${service.endpointUrl}`);
3391
+ }
3392
+ }
3393
+ await reportCliUsage("cli.compute.create", true);
3394
+ } catch (err) {
3395
+ await reportCliUsage("cli.compute.create", false);
3396
+ handleError(err, json);
3397
+ }
3398
+ });
3399
+ }
3400
+
3401
+ // src/commands/compute/update.ts
3402
+ function registerComputeUpdateCommand(computeCmd2) {
3403
+ computeCmd2.command("update <id>").description("Update a compute service").option("--image <image>", "Docker image URL").option("--port <port>", "Container port").option("--cpu <tier>", "CPU tier").option("--memory <mb>", "Memory in MB").option("--region <region>", "Fly.io region").option("--env <json>", "Environment variables as JSON object").action(async (id, opts, cmd) => {
3404
+ const { json } = getRootOpts(cmd);
3405
+ try {
3406
+ await requireAuth();
3407
+ const body = {};
3408
+ if (opts.image) body.imageUrl = opts.image;
3409
+ if (opts.port) {
3410
+ if (!Number.isFinite(Number(opts.port))) {
3411
+ throw new CLIError("Invalid value for --port: must be a number");
3412
+ }
3413
+ body.port = Number(opts.port);
3414
+ }
3415
+ if (opts.cpu) body.cpu = opts.cpu;
3416
+ if (opts.memory) {
3417
+ if (!Number.isFinite(Number(opts.memory))) {
3418
+ throw new CLIError("Invalid value for --memory: must be a number");
3419
+ }
3420
+ body.memory = Number(opts.memory);
3421
+ }
3422
+ if (opts.region) body.region = opts.region;
3423
+ if (opts.env) {
3424
+ try {
3425
+ body.envVars = JSON.parse(opts.env);
3426
+ } catch {
3427
+ throw new CLIError("Invalid JSON for --env");
3428
+ }
3429
+ }
3430
+ if (Object.keys(body).length === 0) {
3431
+ throw new CLIError("No update fields provided. Use --image, --port, --cpu, --memory, --region, or --env.");
3432
+ }
3433
+ const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}`, {
3434
+ method: "PATCH",
3435
+ body: JSON.stringify(body)
3436
+ });
3437
+ const service = await res.json();
3438
+ if (json) {
3439
+ outputJson(service);
3440
+ } else {
3441
+ outputSuccess(`Service "${service.name}" updated [${service.status}]`);
3442
+ }
3443
+ await reportCliUsage("cli.compute.update", true);
3444
+ } catch (err) {
3445
+ await reportCliUsage("cli.compute.update", false);
3446
+ handleError(err, json);
3447
+ }
3448
+ });
3449
+ }
3450
+
3451
+ // src/commands/compute/delete.ts
3452
+ function registerComputeDeleteCommand(computeCmd2) {
3453
+ computeCmd2.command("delete <id>").description("Delete a compute service and its Fly.io resources").action(async (id, _opts, cmd) => {
3454
+ const { json } = getRootOpts(cmd);
3455
+ try {
3456
+ await requireAuth();
3457
+ const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}`, {
3458
+ method: "DELETE"
3459
+ });
3460
+ const data = await res.json();
3461
+ if (json) {
3462
+ outputJson(data);
3463
+ } else {
3464
+ outputSuccess("Service deleted.");
3465
+ }
3466
+ await reportCliUsage("cli.compute.delete", true);
3467
+ } catch (err) {
3468
+ await reportCliUsage("cli.compute.delete", false);
3469
+ handleError(err, json);
3470
+ }
3471
+ });
3472
+ }
3473
+
3474
+ // src/commands/compute/start.ts
3475
+ function registerComputeStartCommand(computeCmd2) {
3476
+ computeCmd2.command("start <id>").description("Start a stopped compute service").action(async (id, _opts, cmd) => {
3477
+ const { json } = getRootOpts(cmd);
3478
+ try {
3479
+ await requireAuth();
3480
+ const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}/start`, {
3481
+ method: "POST"
3482
+ });
3483
+ const service = await res.json();
3484
+ if (json) {
3485
+ outputJson(service);
3486
+ } else {
3487
+ outputSuccess(`Service "${service.name}" started [${service.status}]`);
3488
+ if (service.endpointUrl) {
3489
+ console.log(` Endpoint: ${service.endpointUrl}`);
3490
+ }
3491
+ }
3492
+ await reportCliUsage("cli.compute.start", true);
3493
+ } catch (err) {
3494
+ await reportCliUsage("cli.compute.start", false);
3495
+ handleError(err, json);
3496
+ }
3497
+ });
3498
+ }
3499
+
3500
+ // src/commands/compute/stop.ts
3501
+ function registerComputeStopCommand(computeCmd2) {
3502
+ computeCmd2.command("stop <id>").description("Stop a running compute service").action(async (id, _opts, cmd) => {
3503
+ const { json } = getRootOpts(cmd);
3504
+ try {
3505
+ await requireAuth();
3506
+ const res = await ossFetch(`/api/compute/services/${encodeURIComponent(id)}/stop`, {
3507
+ method: "POST"
3508
+ });
3509
+ const service = await res.json();
3510
+ if (json) {
3511
+ outputJson(service);
3512
+ } else {
3513
+ outputSuccess(`Service "${service.name}" stopped.`);
3514
+ }
3515
+ await reportCliUsage("cli.compute.stop", true);
3516
+ } catch (err) {
3517
+ await reportCliUsage("cli.compute.stop", false);
3518
+ handleError(err, json);
3519
+ }
3520
+ });
3521
+ }
3522
+
3523
+ // src/commands/compute/logs.ts
3524
+ function registerComputeLogsCommand(computeCmd2) {
3525
+ computeCmd2.command("logs <id>").description("Get compute service logs (machine events)").option("--limit <n>", "Max number of log entries", "50").action(async (id, opts, cmd) => {
3526
+ const { json } = getRootOpts(cmd);
3527
+ try {
3528
+ await requireAuth();
3529
+ const limit = Math.max(1, Math.min(Number(opts.limit) || 50, 1e3));
3530
+ const res = await ossFetch(
3531
+ `/api/compute/services/${encodeURIComponent(id)}/logs?limit=${limit}`
3532
+ );
3533
+ const logs = await res.json();
3534
+ if (json) {
3535
+ outputJson(logs);
3536
+ } else {
3537
+ if (!Array.isArray(logs) || logs.length === 0) {
3538
+ console.log("No logs found.");
3539
+ await reportCliUsage("cli.compute.logs", true);
3540
+ return;
3541
+ }
3542
+ for (const entry of logs) {
3543
+ const ts = new Date(entry.timestamp).toISOString();
3544
+ console.log(`${ts} ${entry.message}`);
3545
+ }
3546
+ }
3547
+ await reportCliUsage("cli.compute.logs", true);
3548
+ } catch (err) {
3549
+ await reportCliUsage("cli.compute.logs", false);
3550
+ handleError(err, json);
3551
+ }
3552
+ });
3553
+ }
3554
+
3555
+ // src/commands/compute/deploy.ts
3556
+ import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, renameSync } from "fs";
3557
+ import { join as join7 } from "path";
3558
+ import { execSync, spawn } from "child_process";
3559
+ function parseFlyToml(dir) {
3560
+ const tomlPath = join7(dir, "fly.toml");
3561
+ if (!existsSync5(tomlPath)) return {};
3562
+ const content = readFileSync6(tomlPath, "utf-8");
3563
+ const config = {};
3564
+ const portMatch = content.match(/internal_port\s*=\s*(\d+)/);
3565
+ if (portMatch) config.internalPort = Number(portMatch[1]);
3566
+ const regionMatch = content.match(/primary_region\s*=\s*'([^']+)'/);
3567
+ if (regionMatch) config.region = regionMatch[1];
3568
+ const memoryMatch = content.match(/memory\s*=\s*'(\d+)/);
3569
+ if (memoryMatch) config.memory = memoryMatch[1];
3570
+ const cpuKindMatch = content.match(/cpu_kind\s*=\s*'([^']+)'/);
3571
+ if (cpuKindMatch) config.cpuKind = cpuKindMatch[1];
3572
+ const cpusMatch = content.match(/cpus\s*=\s*(\d+)/);
3573
+ if (cpusMatch) config.cpus = Number(cpusMatch[1]);
3574
+ return config;
3575
+ }
3576
+ function memoryToCpuTier(cpuKind, cpus) {
3577
+ if (!cpuKind) return void 0;
3578
+ const kind = cpuKind === "performance" ? "performance" : "shared";
3579
+ const count = cpus ?? 1;
3580
+ return `${kind}-${count}x`;
3581
+ }
3582
+ function generateFlyToml(appName, opts) {
3583
+ const cpuParts = opts.cpu.split("-");
3584
+ const cpuKind = cpuParts[0] ?? "shared";
3585
+ const cpuCount = parseInt(cpuParts[1] ?? "1", 10);
3586
+ return `# Auto-generated by InsForge CLI
3587
+ app = '${appName}'
3588
+ primary_region = '${opts.region}'
3589
+
3590
+ [build]
3591
+ dockerfile = 'Dockerfile'
3592
+
3593
+ [http_service]
3594
+ internal_port = ${opts.port}
3595
+ force_https = true
3596
+ auto_stop_machines = 'stop'
3597
+ auto_start_machines = true
3598
+ min_machines_running = 0
3599
+
3600
+ [[vm]]
3601
+ memory = '${opts.memory}mb'
3602
+ cpu_kind = '${cpuKind}'
3603
+ cpus = ${cpuCount}
3604
+ `;
3605
+ }
3606
+ function checkFlyctl() {
3607
+ try {
3608
+ execSync("flyctl version", { stdio: "pipe" });
3609
+ } catch {
3610
+ throw new CLIError(
3611
+ "flyctl is not installed.\nInstall it with: curl -L https://fly.io/install.sh | sh\nOr: brew install flyctl"
3612
+ );
3613
+ }
3614
+ }
3615
+ function getFlyToken() {
3616
+ const token = process.env.FLY_API_TOKEN;
3617
+ if (!token) {
3618
+ throw new CLIError(
3619
+ "FLY_API_TOKEN environment variable is required for compute deploy.\nSet it with: export FLY_API_TOKEN=<your-fly-token>"
3620
+ );
3621
+ }
3622
+ return token;
3623
+ }
3624
+ function registerComputeDeployCommand(computeCmd2) {
3625
+ computeCmd2.command("deploy [directory]").description("Build and deploy a Dockerfile as a compute service").requiredOption("--name <name>", "Service name").option("--port <port>", "Container port").option("--cpu <tier>", "CPU tier (shared-1x, shared-2x, performance-1x, etc.)").option("--memory <mb>", "Memory in MB (256, 512, 1024, 2048, 4096, 8192)").option("--region <region>", "Fly.io region").option("--env <json>", "Environment variables as JSON object").action(async (directory, opts, cmd) => {
3626
+ const { json } = getRootOpts(cmd);
3627
+ try {
3628
+ await requireAuth();
3629
+ checkFlyctl();
3630
+ const flyToken = getFlyToken();
3631
+ const dir = directory ?? process.cwd();
3632
+ const dockerfilePath = join7(dir, "Dockerfile");
3633
+ if (!existsSync5(dockerfilePath)) {
3634
+ throw new CLIError(`No Dockerfile found in ${dir}`);
3635
+ }
3636
+ const flyTomlDefaults = parseFlyToml(dir);
3637
+ const port = Number(opts.port) || flyTomlDefaults.internalPort || 8080;
3638
+ const cpu = opts.cpu || memoryToCpuTier(flyTomlDefaults.cpuKind, flyTomlDefaults.cpus) || "shared-1x";
3639
+ const memory = Number(opts.memory) || Number(flyTomlDefaults.memory) || 512;
3640
+ const region = opts.region || flyTomlDefaults.region || "iad";
3641
+ let envVars;
3642
+ if (opts.env) {
3643
+ try {
3644
+ envVars = JSON.parse(opts.env);
3645
+ } catch {
3646
+ throw new CLIError("Invalid JSON for --env");
3647
+ }
3648
+ }
3649
+ if (!json) outputInfo(`Checking for existing service "${opts.name}"...`);
3650
+ const listRes = await ossFetch("/api/compute/services");
3651
+ const services = await listRes.json();
3652
+ const existing = services.find((s) => s.name === opts.name);
3653
+ let serviceId;
3654
+ let flyAppId;
3655
+ if (existing) {
3656
+ serviceId = existing.id;
3657
+ flyAppId = existing.flyAppId;
3658
+ if (!json) outputInfo(`Found existing service, redeploying...`);
3659
+ } else {
3660
+ if (!json) outputInfo(`Creating service "${opts.name}"...`);
3661
+ const body = {
3662
+ name: opts.name,
3663
+ imageUrl: "dockerfile",
3664
+ port,
3665
+ cpu,
3666
+ memory,
3667
+ region
3668
+ };
3669
+ if (envVars) body.envVars = envVars;
3670
+ const deployRes = await ossFetch("/api/compute/services/deploy", {
3671
+ method: "POST",
3672
+ body: JSON.stringify(body)
3673
+ });
3674
+ const service = await deployRes.json();
3675
+ serviceId = service.id;
3676
+ flyAppId = service.flyAppId;
3677
+ }
3678
+ const existingTomlPath = join7(dir, "fly.toml");
3679
+ const backupTomlPath = join7(dir, "fly.toml.insforge-backup");
3680
+ let hadExistingToml = false;
3681
+ if (existsSync5(existingTomlPath)) {
3682
+ hadExistingToml = true;
3683
+ renameSync(existingTomlPath, backupTomlPath);
3684
+ }
3685
+ const tomlContent = generateFlyToml(flyAppId, { port, memory, cpu, region });
3686
+ writeFileSync4(existingTomlPath, tomlContent);
3687
+ try {
3688
+ if (!json) outputInfo("Building and deploying (this may take a few minutes)...");
3689
+ await new Promise((resolve2, reject) => {
3690
+ const child = spawn(
3691
+ "flyctl",
3692
+ ["deploy", "--remote-only", "--app", flyAppId, "--access-token", flyToken, "--yes"],
3693
+ { cwd: dir, stdio: json ? "pipe" : "inherit" }
3694
+ );
3695
+ child.on("close", (code) => {
3696
+ if (code === 0) resolve2();
3697
+ else reject(new CLIError(`flyctl deploy failed with exit code ${code}`));
3698
+ });
3699
+ child.on("error", (err) => reject(new CLIError(`flyctl deploy error: ${err.message}`)));
3700
+ });
3701
+ if (!json) outputInfo("Syncing deployment info...");
3702
+ const syncRes = await ossFetch(
3703
+ `/api/compute/services/${encodeURIComponent(serviceId)}/sync`,
3704
+ { method: "PATCH" }
3705
+ );
3706
+ const synced = await syncRes.json();
3707
+ if (json) {
3708
+ outputJson(synced);
3709
+ } else {
3710
+ outputSuccess(`Service "${synced.name}" deployed [${synced.status}]`);
3711
+ if (synced.endpointUrl) {
3712
+ console.log(` Endpoint: ${synced.endpointUrl}`);
3713
+ }
3714
+ }
3715
+ await reportCliUsage("cli.compute.deploy", true);
3716
+ } finally {
3717
+ try {
3718
+ unlinkSync2(existingTomlPath);
3719
+ if (hadExistingToml) {
3720
+ renameSync(backupTomlPath, existingTomlPath);
3721
+ }
3722
+ } catch {
3723
+ }
3724
+ }
3725
+ } catch (err) {
3726
+ await reportCliUsage("cli.compute.deploy", false);
3727
+ handleError(err, json);
3728
+ }
3729
+ });
3730
+ }
3731
+
3274
3732
  // src/commands/logs.ts
3275
3733
  var VALID_SOURCES = ["insforge.logs", "postgREST.logs", "postgres.logs", "function.logs", "function-deploy.logs"];
3276
3734
  var SOURCE_LOOKUP = new Map(VALID_SOURCES.map((s) => [s.toLowerCase(), s]));
@@ -4019,7 +4477,7 @@ function formatBytesCompact(bytes) {
4019
4477
 
4020
4478
  // src/index.ts
4021
4479
  var __dirname = dirname(fileURLToPath(import.meta.url));
4022
- var pkg = JSON.parse(readFileSync6(join7(__dirname, "../package.json"), "utf-8"));
4480
+ var pkg = JSON.parse(readFileSync7(join8(__dirname, "../package.json"), "utf-8"));
4023
4481
  var INSFORGE_LOGO = `
4024
4482
  \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
4025
4483
  \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
@@ -4087,6 +4545,16 @@ registerLogsCommand(program);
4087
4545
  registerMetadataCommand(program);
4088
4546
  var diagnoseCmd = program.command("diagnose");
4089
4547
  registerDiagnoseCommands(diagnoseCmd);
4548
+ var computeCmd = program.command("compute").description("Manage compute services (Docker containers on Fly.io)");
4549
+ registerComputeListCommand(computeCmd);
4550
+ registerComputeGetCommand(computeCmd);
4551
+ registerComputeCreateCommand(computeCmd);
4552
+ registerComputeDeployCommand(computeCmd);
4553
+ registerComputeUpdateCommand(computeCmd);
4554
+ registerComputeDeleteCommand(computeCmd);
4555
+ registerComputeStartCommand(computeCmd);
4556
+ registerComputeStopCommand(computeCmd);
4557
+ registerComputeLogsCommand(computeCmd);
4090
4558
  var schedulesCmd = program.command("schedules").description("Manage scheduled tasks (cron jobs)");
4091
4559
  registerSchedulesListCommand(schedulesCmd);
4092
4560
  registerSchedulesGetCommand(schedulesCmd);