@cardelli/ambit 0.1.5 → 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 (139) hide show
  1. package/esm/cli/commands/create/index.d.ts +2 -0
  2. package/esm/cli/commands/create/index.d.ts.map +1 -0
  3. package/esm/cli/commands/create/index.js +292 -0
  4. package/esm/cli/commands/create/machine.d.ts +33 -0
  5. package/esm/cli/commands/create/machine.d.ts.map +1 -0
  6. package/esm/cli/commands/create/machine.js +162 -0
  7. package/esm/cli/commands/deploy/index.d.ts +2 -0
  8. package/esm/cli/commands/deploy/index.d.ts.map +1 -0
  9. package/esm/cli/commands/deploy/index.js +290 -0
  10. package/esm/cli/commands/deploy/machine.d.ts +52 -0
  11. package/esm/cli/commands/deploy/machine.d.ts.map +1 -0
  12. package/esm/cli/commands/deploy/machine.js +116 -0
  13. package/esm/cli/commands/deploy/modes.d.ts +18 -0
  14. package/esm/cli/commands/deploy/modes.d.ts.map +1 -0
  15. package/esm/cli/commands/deploy/modes.js +152 -0
  16. package/esm/cli/commands/destroy/app.d.ts +2 -0
  17. package/esm/cli/commands/destroy/app.d.ts.map +1 -0
  18. package/esm/cli/commands/destroy/app.js +173 -0
  19. package/esm/cli/commands/destroy/index.d.ts +2 -0
  20. package/esm/cli/commands/destroy/index.d.ts.map +1 -0
  21. package/esm/cli/commands/destroy/index.js +63 -0
  22. package/esm/cli/commands/destroy/network.d.ts +2 -0
  23. package/esm/cli/commands/destroy/network.d.ts.map +1 -0
  24. package/esm/cli/commands/destroy/network.js +210 -0
  25. package/esm/cli/commands/doctor.d.ts.map +1 -0
  26. package/esm/cli/commands/doctor.js +295 -0
  27. package/esm/{src/cli → cli}/commands/list.d.ts.map +1 -1
  28. package/esm/{src/cli → cli}/commands/list.js +39 -54
  29. package/esm/cli/commands/status.d.ts.map +1 -0
  30. package/esm/cli/commands/status.js +331 -0
  31. package/esm/cli/mod.d.ts.map +1 -0
  32. package/esm/{src/cli → cli}/mod.js +4 -4
  33. package/esm/deno.d.ts +4 -18
  34. package/esm/deno.js +5 -19
  35. package/esm/deps/jsr.io/@std/path/1.1.4/constants.d.ts +1 -1
  36. package/esm/lib/args.d.ts +11 -0
  37. package/esm/lib/args.d.ts.map +1 -0
  38. package/esm/lib/args.js +28 -0
  39. package/esm/lib/cli.d.ts +0 -1
  40. package/esm/lib/cli.d.ts.map +1 -1
  41. package/esm/lib/cli.js +0 -1
  42. package/esm/lib/command.d.ts +0 -3
  43. package/esm/lib/command.d.ts.map +1 -1
  44. package/esm/lib/command.js +2 -13
  45. package/esm/lib/machine.d.ts +11 -0
  46. package/esm/lib/machine.d.ts.map +1 -0
  47. package/esm/lib/machine.js +15 -0
  48. package/esm/lib/output.d.ts +2 -1
  49. package/esm/lib/output.d.ts.map +1 -1
  50. package/esm/lib/output.js +21 -3
  51. package/esm/lib/result.d.ts +0 -1
  52. package/esm/lib/result.d.ts.map +1 -1
  53. package/esm/lib/result.js +0 -1
  54. package/esm/main.d.ts +6 -6
  55. package/esm/main.d.ts.map +1 -1
  56. package/esm/main.js +7 -9
  57. package/esm/providers/fly.d.ts +81 -0
  58. package/esm/providers/fly.d.ts.map +1 -0
  59. package/esm/providers/fly.js +372 -0
  60. package/esm/providers/tailscale.d.ts +31 -0
  61. package/esm/providers/tailscale.d.ts.map +1 -0
  62. package/esm/providers/tailscale.js +150 -0
  63. package/esm/{src/schemas → schemas}/fly.d.ts +1 -11
  64. package/esm/schemas/fly.d.ts.map +1 -0
  65. package/esm/{src/schemas → schemas}/fly.js +14 -56
  66. package/esm/{src/schemas → schemas}/tailscale.d.ts +1 -2
  67. package/esm/schemas/tailscale.d.ts.map +1 -0
  68. package/esm/{src/schemas → schemas}/tailscale.js +2 -3
  69. package/esm/src/{docker/router → router}/Dockerfile +0 -11
  70. package/esm/src/{docker/router → router}/start.sh +18 -9
  71. package/esm/util/constants.d.ts +13 -0
  72. package/esm/util/constants.d.ts.map +1 -0
  73. package/esm/util/constants.js +34 -0
  74. package/esm/{src → util}/credentials.d.ts +0 -1
  75. package/esm/util/credentials.d.ts.map +1 -0
  76. package/esm/{src → util}/credentials.js +3 -5
  77. package/esm/{src → util}/discovery.d.ts +16 -3
  78. package/esm/util/discovery.d.ts.map +1 -0
  79. package/esm/{src → util}/discovery.js +24 -15
  80. package/esm/util/fly-transforms.d.ts +27 -0
  81. package/esm/util/fly-transforms.d.ts.map +1 -0
  82. package/esm/util/fly-transforms.js +87 -0
  83. package/esm/{src → util}/guard.d.ts +1 -2
  84. package/esm/util/guard.d.ts.map +1 -0
  85. package/esm/{src → util}/guard.js +27 -27
  86. package/esm/util/naming.d.ts +5 -0
  87. package/esm/util/naming.d.ts.map +1 -0
  88. package/esm/util/naming.js +12 -0
  89. package/esm/{src → util}/resolve.d.ts +2 -3
  90. package/esm/util/resolve.d.ts.map +1 -0
  91. package/esm/{src → util}/resolve.js +1 -2
  92. package/esm/util/session.d.ts +16 -0
  93. package/esm/util/session.d.ts.map +1 -0
  94. package/esm/util/session.js +19 -0
  95. package/esm/util/tailscale-local.d.ts +13 -0
  96. package/esm/util/tailscale-local.d.ts.map +1 -0
  97. package/esm/util/tailscale-local.js +63 -0
  98. package/esm/{src → util}/template.d.ts +0 -1
  99. package/esm/util/template.d.ts.map +1 -0
  100. package/esm/{src → util}/template.js +0 -1
  101. package/package.json +1 -49
  102. package/esm/lib/paths.d.ts +0 -3
  103. package/esm/lib/paths.d.ts.map +0 -1
  104. package/esm/lib/paths.js +0 -5
  105. package/esm/src/cli/commands/create.d.ts +0 -2
  106. package/esm/src/cli/commands/create.d.ts.map +0 -1
  107. package/esm/src/cli/commands/create.js +0 -308
  108. package/esm/src/cli/commands/deploy.d.ts +0 -2
  109. package/esm/src/cli/commands/deploy.d.ts.map +0 -1
  110. package/esm/src/cli/commands/deploy.js +0 -430
  111. package/esm/src/cli/commands/destroy.d.ts +0 -2
  112. package/esm/src/cli/commands/destroy.d.ts.map +0 -1
  113. package/esm/src/cli/commands/destroy.js +0 -340
  114. package/esm/src/cli/commands/doctor.d.ts.map +0 -1
  115. package/esm/src/cli/commands/doctor.js +0 -141
  116. package/esm/src/cli/commands/status.d.ts.map +0 -1
  117. package/esm/src/cli/commands/status.js +0 -152
  118. package/esm/src/cli/mod.d.ts.map +0 -1
  119. package/esm/src/credentials.d.ts.map +0 -1
  120. package/esm/src/discovery.d.ts.map +0 -1
  121. package/esm/src/guard.d.ts.map +0 -1
  122. package/esm/src/providers/fly.d.ts +0 -76
  123. package/esm/src/providers/fly.d.ts.map +0 -1
  124. package/esm/src/providers/fly.js +0 -407
  125. package/esm/src/providers/tailscale.d.ts +0 -31
  126. package/esm/src/providers/tailscale.d.ts.map +0 -1
  127. package/esm/src/providers/tailscale.js +0 -189
  128. package/esm/src/resolve.d.ts.map +0 -1
  129. package/esm/src/schemas/config.d.ts +0 -5
  130. package/esm/src/schemas/config.d.ts.map +0 -1
  131. package/esm/src/schemas/config.js +0 -22
  132. package/esm/src/schemas/fly.d.ts.map +0 -1
  133. package/esm/src/schemas/tailscale.d.ts.map +0 -1
  134. package/esm/src/template.d.ts.map +0 -1
  135. /package/esm/{src/cli → cli}/commands/doctor.d.ts +0 -0
  136. /package/esm/{src/cli → cli}/commands/list.d.ts +0 -0
  137. /package/esm/{src/cli → cli}/commands/status.d.ts +0 -0
  138. /package/esm/{src/cli → cli}/mod.d.ts +0 -0
  139. /package/esm/src/{docker/router → router}/fly.toml +0 -0
@@ -12,23 +12,18 @@
12
12
  // Commands compose them into whatever view they need.
13
13
  //
14
14
  // =============================================================================
15
- import "../_dnt.polyfills.js";
16
- import { getRouterSuffix, getWorkloadAppName } from "./providers/fly.js";
17
- import { extractSubnet } from "./schemas/config.js";
18
- // =============================================================================
19
- // Constants
20
- // =============================================================================
21
- const ROUTER_APP_PREFIX = "ambit-";
22
- const DEFAULT_NETWORK = "default";
15
+ import { getRouterSuffix, getWorkloadAppName } from "./naming.js";
16
+ import { extractSubnet } from "./fly-transforms.js";
17
+ import { DEFAULT_FLY_NETWORK, ROUTER_APP_PREFIX, } from "./constants.js";
23
18
  // =============================================================================
24
19
  // 1. Which routers exist? (Fly REST API)
25
20
  // =============================================================================
26
21
  /** List all ambit apps on custom networks in an org. */
27
22
  export const listRouterApps = async (fly, org) => {
28
- const apps = await fly.listAppsWithNetwork(org);
23
+ const apps = await fly.apps.listWithNetwork(org);
29
24
  return apps
30
25
  .filter((app) => app.name.startsWith(ROUTER_APP_PREFIX) &&
31
- app.network !== DEFAULT_NETWORK)
26
+ app.network !== DEFAULT_FLY_NETWORK)
32
27
  .map((app) => ({
33
28
  appName: app.name,
34
29
  network: app.network,
@@ -43,7 +38,7 @@ export const findRouterApp = async (fly, org, network) => {
43
38
  };
44
39
  /** List all non-router apps on a specific custom network in an org. */
45
40
  export const listWorkloadAppsOnNetwork = async (fly, org, network) => {
46
- const apps = await fly.listAppsWithNetwork(org);
41
+ const apps = await fly.apps.listWithNetwork(org);
47
42
  return apps
48
43
  .filter((app) => !app.name.startsWith(ROUTER_APP_PREFIX) &&
49
44
  app.network === network)
@@ -57,10 +52,10 @@ export const listWorkloadAppsOnNetwork = async (fly, org, network) => {
57
52
  * When a network is provided, resolves the router-suffixed Fly app name
58
53
  * (e.g. "thing" on network "lab" with routerId "abc123" → "thing-abc123"). */
59
54
  export const findWorkloadApp = async (fly, org, appName, network) => {
60
- const apps = await fly.listAppsWithNetwork(org);
55
+ const apps = await fly.apps.listWithNetwork(org);
61
56
  const workloads = apps
62
57
  .filter((app) => !app.name.startsWith(ROUTER_APP_PREFIX) &&
63
- app.network !== DEFAULT_NETWORK)
58
+ app.network !== DEFAULT_FLY_NETWORK)
64
59
  .map((app) => ({
65
60
  appName: app.name,
66
61
  network: app.network,
@@ -86,7 +81,7 @@ export const findWorkloadApp = async (fly, org, appName, network) => {
86
81
  // =============================================================================
87
82
  /** Get machine info for a router app. Returns null if no machines exist. */
88
83
  export const getRouterMachineInfo = async (fly, appName) => {
89
- const machines = await fly.listMachines(appName);
84
+ const machines = await fly.machines.list(appName);
90
85
  const machine = machines[0];
91
86
  if (!machine)
92
87
  return null;
@@ -105,7 +100,7 @@ export const getRouterMachineInfo = async (fly, appName) => {
105
100
  /** Get tailscale device info for a router. Returns null if not found. */
106
101
  export const getRouterTailscaleInfo = async (tailscale, appName) => {
107
102
  try {
108
- const device = await tailscale.getDeviceByHostname(appName);
103
+ const device = await tailscale.devices.getByHostname(appName);
109
104
  if (!device)
110
105
  return null;
111
106
  return {
@@ -119,3 +114,17 @@ export const getRouterTailscaleInfo = async (tailscale, appName) => {
119
114
  return null;
120
115
  }
121
116
  };
117
+ /**
118
+ * Discover all routers in an org and hydrate each with machine + Tailscale state.
119
+ * Shows a spinner while fetching. Fetches all routers in parallel.
120
+ */
121
+ export const discoverRouters = async (out, fly, tailscale, org) => {
122
+ const spinner = out.spinner("Discovering Routers");
123
+ const routerApps = await listRouterApps(fly, org);
124
+ spinner.success(`Found ${routerApps.length} Router${routerApps.length !== 1 ? "s" : ""}`);
125
+ return Promise.all(routerApps.map(async (app) => ({
126
+ ...app,
127
+ machine: await getRouterMachineInfo(fly, app.appName),
128
+ tailscale: await getRouterTailscaleInfo(tailscale, app.appName),
129
+ })));
130
+ };
@@ -0,0 +1,27 @@
1
+ import type { FlyMachine } from "../schemas/fly.js";
2
+ import { type FlyMachineGuestSchema } from "../schemas/fly.js";
3
+ import type { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
4
+ /**
5
+ * Map Fly machine state to internal state.
6
+ * Fly states: created, starting, started, stopping, stopped, destroying, destroyed
7
+ */
8
+ export declare const mapFlyMachineState: (flyState: string) => "creating" | "running" | "frozen" | "failed";
9
+ /**
10
+ * Map Fly guest config to machine size enum.
11
+ */
12
+ export declare const mapFlyMachineSize: (guest?: z.infer<typeof FlyMachineGuestSchema>) => "shared-cpu-1x" | "shared-cpu-2x" | "shared-cpu-4x";
13
+ import type { MachineResult } from "../providers/fly.js";
14
+ /** Map raw Fly machines to internal MachineResult format. */
15
+ export declare const mapMachines: (raw: FlyMachine[]) => MachineResult[];
16
+ import type { MachineSize } from "../providers/fly.js";
17
+ export declare const getSizeConfig: (size: MachineSize) => {
18
+ cpus: number;
19
+ memoryMb: number;
20
+ };
21
+ /**
22
+ * Pull the last non-empty, non-decoration line from fly stderr.
23
+ * Fly often prints progress lines then the actual error at the end.
24
+ */
25
+ export declare const extractErrorDetail: (stderr: string) => string;
26
+ export declare const extractSubnet: (privateIp: string) => string;
27
+ //# sourceMappingURL=fly-transforms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fly-transforms.d.ts","sourceRoot":"","sources":["../../src/util/fly-transforms.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,4CAA4C,CAAC;AAMpE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,UAAU,MAAM,KACf,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,QAiBtC,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,KAC5C,eAAe,GAAG,eAAe,GAAG,eAOtC,CAAC;AAMF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,6DAA6D;AAC7D,eAAO,MAAM,WAAW,GAAI,KAAK,UAAU,EAAE,KAAG,aAAa,EAQ5D,CAAC;AAMF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,MAAM,aAAa,GACxB,MAAM,WAAW,KAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CASlC,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,KAAG,MAOnD,CAAC;AAMF,eAAO,MAAM,aAAa,GAAI,WAAW,MAAM,KAAG,MAKjD,CAAC"}
@@ -0,0 +1,87 @@
1
+ // =============================================================================
2
+ // Fly Transforms — Pure Data Transformations
3
+ // =============================================================================
4
+ // =============================================================================
5
+ // Machine State Mapping
6
+ // =============================================================================
7
+ /**
8
+ * Map Fly machine state to internal state.
9
+ * Fly states: created, starting, started, stopping, stopped, destroying, destroyed
10
+ */
11
+ export const mapFlyMachineState = (flyState) => {
12
+ switch (flyState.toLowerCase()) {
13
+ case "started":
14
+ return "running";
15
+ case "stopped":
16
+ case "suspended":
17
+ return "frozen";
18
+ case "created":
19
+ case "starting":
20
+ return "creating";
21
+ case "destroying":
22
+ case "destroyed":
23
+ case "failed":
24
+ return "failed";
25
+ default:
26
+ return "creating";
27
+ }
28
+ };
29
+ // =============================================================================
30
+ // Machine Size Mapping
31
+ // =============================================================================
32
+ /**
33
+ * Map Fly guest config to machine size enum.
34
+ */
35
+ export const mapFlyMachineSize = (guest) => {
36
+ if (!guest)
37
+ return "shared-cpu-1x";
38
+ const cpus = guest.cpus;
39
+ if (cpus >= 4)
40
+ return "shared-cpu-4x";
41
+ if (cpus >= 2)
42
+ return "shared-cpu-2x";
43
+ return "shared-cpu-1x";
44
+ };
45
+ /** Map raw Fly machines to internal MachineResult format. */
46
+ export const mapMachines = (raw) => {
47
+ return raw.map((m) => ({
48
+ id: m.id,
49
+ state: mapFlyMachineState(m.state),
50
+ size: mapFlyMachineSize(m.config?.guest),
51
+ region: m.region,
52
+ privateIp: m.private_ip,
53
+ }));
54
+ };
55
+ export const getSizeConfig = (size) => {
56
+ switch (size) {
57
+ case "shared-cpu-1x":
58
+ return { cpus: 1, memoryMb: 1024 };
59
+ case "shared-cpu-2x":
60
+ return { cpus: 2, memoryMb: 2048 };
61
+ case "shared-cpu-4x":
62
+ return { cpus: 4, memoryMb: 4096 };
63
+ }
64
+ };
65
+ // =============================================================================
66
+ // Error Detail Extraction
67
+ // =============================================================================
68
+ /**
69
+ * Pull the last non-empty, non-decoration line from fly stderr.
70
+ * Fly often prints progress lines then the actual error at the end.
71
+ */
72
+ export const extractErrorDetail = (stderr) => {
73
+ const lines = stderr
74
+ .split("\n")
75
+ .map((l) => l.replace(/\x1b\[[0-9;]*m/g, "").trim())
76
+ .filter((l) => l.length > 0 && !l.startsWith("-->") && l !== "Error");
77
+ return lines[lines.length - 1] ?? "unknown error";
78
+ };
79
+ // =============================================================================
80
+ // Subnet Extraction
81
+ // =============================================================================
82
+ export const extractSubnet = (privateIp) => {
83
+ // privateIp format: fdaa:X:XXXX::Y
84
+ // Extract first 3 hextets and append ::/48
85
+ const parts = privateIp.split(":");
86
+ return `${parts[0]}:${parts[1]}:${parts[2]}::/48`;
87
+ };
@@ -1,5 +1,4 @@
1
- import "../_dnt.polyfills.js";
2
- import type { FlyProvider } from "./providers/fly.js";
1
+ import type { FlyProvider } from "../providers/fly.js";
3
2
  /**
4
3
  * Returns true if the given name is a public TLD (case-insensitive).
5
4
  * Used to prevent creating networks that would shadow real DNS.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/util/guard.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAsLvD;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,OACN,CAAC;AAMtC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAOjD;AAMD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CA8ChE;AAMD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,iBAAiB,CAAC,CA4F5B"}
@@ -9,8 +9,8 @@
9
9
  // - auditDeploy: post-deploy check that releases public IPs and reports
10
10
  //
11
11
  // =============================================================================
12
- import "../_dnt.polyfills.js";
13
12
  import { parse as parseToml } from "../deps/jsr.io/@std/toml/1.0.11/mod.js";
13
+ import { ROUTER_APP_PREFIX } from "./constants.js";
14
14
  // =============================================================================
15
15
  // Public TLD Blocklist (source: https://data.iana.org/TLD/tlds-alpha-by-domain.txt)
16
16
  // =============================================================================
@@ -201,7 +201,7 @@ export const isPublicTld = (name) => PUBLIC_TLDS.has(name.toLowerCase());
201
201
  * Workload apps must not start with the ambit- prefix.
202
202
  */
203
203
  export function assertNotRouter(app) {
204
- if (app.startsWith("ambit-")) {
204
+ if (app.startsWith(ROUTER_APP_PREFIX)) {
205
205
  throw new Error("Cannot deploy ambit infrastructure apps (ambit-* prefix). " +
206
206
  "Use 'ambit create' to manage routers.");
207
207
  }
@@ -235,23 +235,21 @@ export function scanFlyToml(tomlContent) {
235
235
  result.errors.push("http_service.force_https is enabled. " +
236
236
  "This implies public HTTPS which is incompatible with Flycast-only deployment.");
237
237
  }
238
- // Check [[services]] for TLS on 443 — dangerous if a public IP were ever added
239
- const services = parsed.services;
240
- if (Array.isArray(services)) {
241
- result.warnings.push("fly.toml uses [[services]] blocks. Consider migrating to [http_service].");
242
- for (const svc of services) {
243
- const ports = svc.ports;
238
+ // Check [[services]] for TLS handler on port 443
239
+ if (Array.isArray(parsed.services)) {
240
+ const hasTlsHandler = parsed.services.some((svc) => {
241
+ const ports = svc?.ports;
244
242
  if (!Array.isArray(ports))
245
- continue;
246
- for (const port of ports) {
247
- const handlers = port.handlers;
248
- if (port.port === 443 &&
249
- Array.isArray(handlers) &&
250
- handlers.includes("tls")) {
251
- result.errors.push("Service has TLS handler on port 443. " +
252
- "This is designed for public HTTPS and incompatible with Flycast-only deployment.");
253
- }
254
- }
243
+ return false;
244
+ return ports.some((p) => {
245
+ const port = p;
246
+ const handlers = port?.handlers;
247
+ return Array.isArray(handlers) && handlers.includes("tls") &&
248
+ port?.port === 443;
249
+ });
250
+ });
251
+ if (hasTlsHandler) {
252
+ result.errors.push("TLS Handler on Port 443 Is Incompatible with Flycast-Only Deployment.");
255
253
  }
256
254
  }
257
255
  return result;
@@ -271,7 +269,7 @@ export async function auditDeploy(fly, app, targetNetwork) {
271
269
  warnings: [],
272
270
  };
273
271
  // Phase 1: Check and clean IPs — only keep Flycast on the target network
274
- const ips = await fly.listIps(app);
272
+ const ips = await fly.ips.list(app);
275
273
  for (const ip of ips) {
276
274
  const ipNetwork = ip.Network?.Name || "default";
277
275
  if (ip.Type === "private_v6" && ipNetwork === targetNetwork) {
@@ -283,23 +281,23 @@ export async function auditDeploy(fly, app, targetNetwork) {
283
281
  }
284
282
  else if (ip.Type === "private_v6") {
285
283
  // Flycast on wrong network (e.g. default from --flycast flag) — release
286
- await fly.releaseIp(app, ip.Address);
284
+ await fly.ips.release(app, ip.Address);
287
285
  result.warnings.push(`Released Flycast IP ${ip.Address} on wrong network '${ipNetwork}' (expected '${targetNetwork}')`);
288
286
  }
289
287
  else {
290
288
  // Public IP — release immediately
291
- await fly.releaseIp(app, ip.Address);
289
+ await fly.ips.release(app, ip.Address);
292
290
  result.public_ips_released++;
293
291
  }
294
292
  }
295
293
  // Phase 2: Remove auto-generated .fly.dev certs to keep the app fully private
296
- const certs = await fly.listCerts(app);
294
+ const certs = await fly.certs.list(app);
297
295
  for (const hostname of certs) {
298
- await fly.removeCert(app, hostname);
296
+ await fly.certs.remove(app, hostname);
299
297
  result.certs_removed++;
300
298
  }
301
299
  // Phase 3: Inspect merged config for dangerous patterns
302
- const config = await fly.getConfig(app);
300
+ const config = await fly.apps.getConfig(app);
303
301
  if (config) {
304
302
  const services = config.services;
305
303
  if (Array.isArray(services) && services.length > 0) {
@@ -320,10 +318,12 @@ export async function auditDeploy(fly, app, targetNetwork) {
320
318
  // Phase 4: Verify Flycast allocation on target network
321
319
  const hasTargetFlycast = result.flycast_allocations.some((a) => a.network === targetNetwork);
322
320
  if (!hasTargetFlycast) {
323
- // Allocate on the target network
324
- await fly.allocateFlycastIp(app, targetNetwork);
321
+ // Allocate on the target network, then re-list to get the actual address
322
+ await fly.ips.allocateFlycast(app, targetNetwork);
323
+ const refreshed = await fly.ips.list(app);
324
+ const newIp = refreshed.find((ip) => ip.Type === "private_v6" && ip.Network?.Name === targetNetwork);
325
325
  result.flycast_allocations.push({
326
- address: "(newly allocated)",
326
+ address: newIp?.Address ?? "allocated",
327
327
  network: targetNetwork,
328
328
  });
329
329
  }
@@ -0,0 +1,5 @@
1
+ export declare const getRouterAppName: (network: string, randomSuffix: string) => string;
2
+ export declare const getRouterSuffix: (routerAppName: string, network: string) => string;
3
+ export declare const getWorkloadAppName: (name: string, routerId: string) => string;
4
+ export declare const getRouterTag: (network: string) => string;
5
+ //# sourceMappingURL=naming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/util/naming.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB,GAC3B,SAAS,MAAM,EACf,cAAc,MAAM,KACnB,MAEF,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,eAAe,MAAM,EACrB,SAAS,MAAM,KACd,MAGF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,MAAM,MAAM,EACZ,UAAU,MAAM,KACf,MAEF,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,MAAgC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { ROUTER_APP_PREFIX } from "./constants.js";
2
+ export const getRouterAppName = (network, randomSuffix) => {
3
+ return `${ROUTER_APP_PREFIX}${network}-${randomSuffix}`;
4
+ };
5
+ export const getRouterSuffix = (routerAppName, network) => {
6
+ const prefix = `${ROUTER_APP_PREFIX}${network}-`;
7
+ return routerAppName.slice(prefix.length);
8
+ };
9
+ export const getWorkloadAppName = (name, routerId) => {
10
+ return `${name}-${routerId}`;
11
+ };
12
+ export const getRouterTag = (network) => `tag:ambit-${network}`;
@@ -1,11 +1,10 @@
1
- import "../_dnt.polyfills.js";
2
1
  import type { Output } from "../lib/output.js";
3
- import type { FlyProvider } from "./providers/fly.js";
2
+ import type { FlyProvider } from "../providers/fly.js";
4
3
  /**
5
4
  * Resolve Fly.io organization: --org flag → single org auto-select → prompt.
6
5
  */
7
6
  export declare const resolveOrg: (fly: FlyProvider, args: {
8
7
  org?: string;
9
8
  json?: boolean;
10
- }, out: Output<Record<string, unknown>>) => Promise<string>;
9
+ }, out: Output<any>) => Promise<string>;
11
10
  //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/util/resolve.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAMvD;;GAEG;AACH,eAAO,MAAM,UAAU,GACrB,KAAK,WAAW,EAChB,MAAM;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,EACtC,KAAK,MAAM,CAAC,GAAG,CAAC,KACf,OAAO,CAAC,MAAM,CA6BhB,CAAC"}
@@ -1,7 +1,6 @@
1
1
  // =============================================================================
2
2
  // Resolve - Org Resolution Helper
3
3
  // =============================================================================
4
- import "../_dnt.polyfills.js";
5
4
  import { prompt } from "../lib/cli.js";
6
5
  // =============================================================================
7
6
  // Resolve Org
@@ -15,7 +14,7 @@ export const resolveOrg = async (fly, args, out) => {
15
14
  if (args.json) {
16
15
  return out.die("--org Is Required in JSON Mode");
17
16
  }
18
- const orgs = await fly.listOrgs();
17
+ const orgs = await fly.orgs.list();
19
18
  const orgSlugs = Object.keys(orgs);
20
19
  if (orgSlugs.length === 0) {
21
20
  return out.die("No Fly.io Organizations Found");
@@ -0,0 +1,16 @@
1
+ import { type FlyProvider } from "../providers/fly.js";
2
+ import { type TailscaleProvider } from "../providers/tailscale.js";
3
+ import type { Output } from "../lib/output.js";
4
+ /**
5
+ * Bootstrap the three shared prerequisites every command needs:
6
+ * validates fly CLI + Tailscale key, authenticates with Fly, and resolves org.
7
+ */
8
+ export declare const initSession: <T extends Record<string, unknown>>(out: Output<T>, opts: {
9
+ json: boolean;
10
+ org?: string;
11
+ }) => Promise<{
12
+ fly: FlyProvider;
13
+ tailscale: TailscaleProvider;
14
+ org: string;
15
+ }>;
16
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/util/session.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAI/C;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAU,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjE,KAAK,MAAM,CAAC,CAAC,CAAC,EACd,MAAM;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,KACpC,OAAO,CAAC;IAAE,GAAG,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,iBAAiB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAOzE,CAAC"}
@@ -0,0 +1,19 @@
1
+ // =============================================================================
2
+ // Session — Shared Prerequisites Initialization
3
+ // =============================================================================
4
+ import { createFlyProvider } from "../providers/fly.js";
5
+ import { createTailscaleProvider, } from "../providers/tailscale.js";
6
+ import { checkDependencies } from "./credentials.js";
7
+ import { resolveOrg } from "./resolve.js";
8
+ /**
9
+ * Bootstrap the three shared prerequisites every command needs:
10
+ * validates fly CLI + Tailscale key, authenticates with Fly, and resolves org.
11
+ */
12
+ export const initSession = async (out, opts) => {
13
+ const { tailscaleKey } = await checkDependencies(out);
14
+ const fly = createFlyProvider();
15
+ await fly.auth.login({ interactive: !opts.json });
16
+ const tailscale = createTailscaleProvider(tailscaleKey);
17
+ const org = await resolveOrg(fly, opts, out);
18
+ return { fly, tailscale, org };
19
+ };
@@ -0,0 +1,13 @@
1
+ import type { TailscaleDevice } from "../schemas/tailscale.js";
2
+ import type { TailscaleProvider } from "../providers/tailscale.js";
3
+ export declare const isTailscaleInstalled: () => Promise<boolean>;
4
+ export declare const isAcceptRoutesEnabled: () => Promise<boolean>;
5
+ /**
6
+ * Enable accept-routes on the local client.
7
+ * Returns true if successful, false if it failed (likely permissions).
8
+ */
9
+ export declare const enableAcceptRoutes: () => Promise<boolean>;
10
+ export declare const waitForDevice: (provider: TailscaleProvider, hostname: string, timeoutMs?: number) => Promise<TailscaleDevice>;
11
+ export declare const isTagOwnerConfigured: (policy: Record<string, unknown> | null, tag: string) => boolean;
12
+ export declare const isAutoApproverConfigured: (policy: Record<string, unknown> | null, tag: string) => boolean;
13
+ //# sourceMappingURL=tailscale-local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailscale-local.d.ts","sourceRoot":"","sources":["../../src/util/tailscale-local.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAMnE,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,OAAO,CAE5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,OAAO,CAM7D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,OAAO,CAG1D,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,EAC3B,UAAU,MAAM,EAChB,YAAW,MAAe,KACzB,OAAO,CAAC,eAAe,CAazB,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OASF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OAgBF,CAAC"}
@@ -0,0 +1,63 @@
1
+ // =============================================================================
2
+ // Tailscale Local — CLI Operations + Pure Policy Checks
3
+ // =============================================================================
4
+ import { commandExists, die } from "../lib/cli.js";
5
+ import { runCommand } from "../lib/command.js";
6
+ // =============================================================================
7
+ // Local CLI Operations
8
+ // =============================================================================
9
+ export const isTailscaleInstalled = async () => {
10
+ return await commandExists("tailscale");
11
+ };
12
+ export const isAcceptRoutesEnabled = async () => {
13
+ const result = await runCommand(["tailscale", "debug", "prefs"]);
14
+ return result.json().match({
15
+ ok: (prefs) => prefs.RouteAll === true,
16
+ err: () => false,
17
+ });
18
+ };
19
+ /**
20
+ * Enable accept-routes on the local client.
21
+ * Returns true if successful, false if it failed (likely permissions).
22
+ */
23
+ export const enableAcceptRoutes = async () => {
24
+ const result = await runCommand(["tailscale", "set", "--accept-routes"]);
25
+ return result.ok;
26
+ };
27
+ // =============================================================================
28
+ // Wait for Device
29
+ // =============================================================================
30
+ export const waitForDevice = async (provider, hostname, timeoutMs = 120000) => {
31
+ const startTime = Date.now();
32
+ const pollInterval = 5000;
33
+ while (Date.now() - startTime < timeoutMs) {
34
+ const device = await provider.devices.getByHostname(hostname);
35
+ if (device) {
36
+ return device;
37
+ }
38
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
39
+ }
40
+ return die(`Timeout Waiting for Device '${hostname}'`);
41
+ };
42
+ // =============================================================================
43
+ // Pure Policy Checks
44
+ // =============================================================================
45
+ export const isTagOwnerConfigured = (policy, tag) => {
46
+ if (!policy)
47
+ return false;
48
+ const tagOwners = policy.tagOwners;
49
+ if (!tagOwners)
50
+ return false;
51
+ return tag in tagOwners;
52
+ };
53
+ export const isAutoApproverConfigured = (policy, tag) => {
54
+ if (!policy)
55
+ return false;
56
+ const autoApprovers = policy.autoApprovers;
57
+ if (!autoApprovers)
58
+ return false;
59
+ const routes = autoApprovers.routes;
60
+ if (!routes)
61
+ return false;
62
+ return Object.values(routes).some((approvers) => Array.isArray(approvers) && approvers.includes(tag));
63
+ };
@@ -1,4 +1,3 @@
1
- import "../_dnt.polyfills.js";
2
1
  import { Result } from "../lib/result.js";
3
2
  /** Parsed GitHub template reference. */
4
3
  export interface TemplateRef {
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/util/template.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1C,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CACtC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CACzC,CAAC;AAyBF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,WAAW,GAAG,IAyB5D,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,KACf,OAAO,CAAC,mBAAmB,CAqG7B,CAAC"}
@@ -14,7 +14,6 @@
14
14
  // ToxicPine/ambit-templates/cdp@main → explicit branch
15
15
  //
16
16
  // =============================================================================
17
- import "../_dnt.polyfills.js";
18
17
  import * as dntShim from "../_dnt.shims.js";
19
18
  import { join } from "../deps/jsr.io/@std/path/1.1.4/mod.js";
20
19
  import { runCommand } from "../lib/command.js";
package/package.json CHANGED
@@ -1,56 +1,8 @@
1
1
  {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "Deploy apps to the cloud that only you and your AI agents can reach",
5
5
  "license": "MIT",
6
- "module": "./esm/src/providers/fly.js",
7
- "exports": {
8
- "./providers/fly": {
9
- "import": "./esm/src/providers/fly.js"
10
- },
11
- "./providers/tailscale": {
12
- "import": "./esm/src/providers/tailscale.js"
13
- },
14
- "./schemas/config": {
15
- "import": "./esm/src/schemas/config.js"
16
- },
17
- "./schemas/fly": {
18
- "import": "./esm/src/schemas/fly.js"
19
- },
20
- "./schemas/tailscale": {
21
- "import": "./esm/src/schemas/tailscale.js"
22
- },
23
- "./lib/cli": {
24
- "import": "./esm/lib/cli.js"
25
- },
26
- "./lib/command": {
27
- "import": "./esm/lib/command.js"
28
- },
29
- "./lib/output": {
30
- "import": "./esm/lib/output.js"
31
- },
32
- "./lib/result": {
33
- "import": "./esm/lib/result.js"
34
- },
35
- "./lib/paths": {
36
- "import": "./esm/lib/paths.js"
37
- },
38
- "./src/credentials": {
39
- "import": "./esm/src/credentials.js"
40
- },
41
- "./src/discovery": {
42
- "import": "./esm/src/discovery.js"
43
- },
44
- "./src/guard": {
45
- "import": "./esm/src/guard.js"
46
- },
47
- "./src/resolve": {
48
- "import": "./esm/src/resolve.js"
49
- },
50
- "./src/template": {
51
- "import": "./esm/src/template.js"
52
- }
53
- },
54
6
  "scripts": {},
55
7
  "bin": {
56
8
  "ambit": "./esm/main.js"
@@ -1,3 +0,0 @@
1
- import "../_dnt.polyfills.js";
2
- export declare const getRouterDockerDir: () => string;
3
- //# sourceMappingURL=paths.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAGA,OAAO,sBAAsB,CAAC;AAG9B,eAAO,MAAM,kBAAkB,QAAO,MACqB,CAAC"}
package/esm/lib/paths.js DELETED
@@ -1,5 +0,0 @@
1
- // =============================================================================
2
- // Path Helpers - Locate Package Resources
3
- // =============================================================================
4
- import "../_dnt.polyfills.js";
5
- export const getRouterDockerDir = () => new URL("../src/docker/router", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).pathname;
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=create.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/src/cli/commands/create.ts"],"names":[],"mappings":""}