@cardelli/ambit 0.1.4 → 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 (141) 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/deps/jsr.io/@zod/zod/4.3.6/src/v4/core/json-schema-generator.d.ts +1 -1
  37. package/esm/lib/args.d.ts +11 -0
  38. package/esm/lib/args.d.ts.map +1 -0
  39. package/esm/lib/args.js +28 -0
  40. package/esm/lib/cli.d.ts +0 -2
  41. package/esm/lib/cli.d.ts.map +1 -1
  42. package/esm/lib/cli.js +41 -27
  43. package/esm/lib/command.d.ts +21 -49
  44. package/esm/lib/command.d.ts.map +1 -1
  45. package/esm/lib/command.js +55 -95
  46. package/esm/lib/machine.d.ts +11 -0
  47. package/esm/lib/machine.d.ts.map +1 -0
  48. package/esm/lib/machine.js +15 -0
  49. package/esm/lib/output.d.ts +3 -2
  50. package/esm/lib/output.d.ts.map +1 -1
  51. package/esm/lib/output.js +25 -11
  52. package/esm/lib/result.d.ts +18 -7
  53. package/esm/lib/result.d.ts.map +1 -1
  54. package/esm/lib/result.js +46 -1
  55. package/esm/main.d.ts +6 -6
  56. package/esm/main.d.ts.map +1 -1
  57. package/esm/main.js +7 -9
  58. package/esm/providers/fly.d.ts +81 -0
  59. package/esm/providers/fly.d.ts.map +1 -0
  60. package/esm/providers/fly.js +372 -0
  61. package/esm/providers/tailscale.d.ts +31 -0
  62. package/esm/providers/tailscale.d.ts.map +1 -0
  63. package/esm/providers/tailscale.js +150 -0
  64. package/esm/{src/schemas → schemas}/fly.d.ts +1 -11
  65. package/esm/schemas/fly.d.ts.map +1 -0
  66. package/esm/{src/schemas → schemas}/fly.js +14 -56
  67. package/esm/{src/schemas → schemas}/tailscale.d.ts +1 -2
  68. package/esm/schemas/tailscale.d.ts.map +1 -0
  69. package/esm/{src/schemas → schemas}/tailscale.js +2 -3
  70. package/esm/src/{docker/router → router}/Dockerfile +0 -11
  71. package/esm/src/router/start.sh +101 -0
  72. package/esm/util/constants.d.ts +13 -0
  73. package/esm/util/constants.d.ts.map +1 -0
  74. package/esm/util/constants.js +34 -0
  75. package/esm/{src → util}/credentials.d.ts +0 -1
  76. package/esm/util/credentials.d.ts.map +1 -0
  77. package/esm/{src → util}/credentials.js +3 -5
  78. package/esm/{src → util}/discovery.d.ts +20 -4
  79. package/esm/util/discovery.d.ts.map +1 -0
  80. package/esm/{src → util}/discovery.js +38 -15
  81. package/esm/util/fly-transforms.d.ts +27 -0
  82. package/esm/util/fly-transforms.d.ts.map +1 -0
  83. package/esm/util/fly-transforms.js +87 -0
  84. package/esm/{src → util}/guard.d.ts +1 -2
  85. package/esm/util/guard.d.ts.map +1 -0
  86. package/esm/{src → util}/guard.js +27 -27
  87. package/esm/util/naming.d.ts +5 -0
  88. package/esm/util/naming.d.ts.map +1 -0
  89. package/esm/util/naming.js +12 -0
  90. package/esm/{src → util}/resolve.d.ts +2 -3
  91. package/esm/util/resolve.d.ts.map +1 -0
  92. package/esm/{src → util}/resolve.js +1 -2
  93. package/esm/util/session.d.ts +16 -0
  94. package/esm/util/session.d.ts.map +1 -0
  95. package/esm/util/session.js +19 -0
  96. package/esm/util/tailscale-local.d.ts +13 -0
  97. package/esm/util/tailscale-local.d.ts.map +1 -0
  98. package/esm/util/tailscale-local.js +63 -0
  99. package/esm/{src → util}/template.d.ts +2 -4
  100. package/esm/util/template.d.ts.map +1 -0
  101. package/esm/{src → util}/template.js +14 -17
  102. package/package.json +1 -43
  103. package/esm/lib/paths.d.ts +0 -3
  104. package/esm/lib/paths.d.ts.map +0 -1
  105. package/esm/lib/paths.js +0 -5
  106. package/esm/src/cli/commands/create.d.ts +0 -2
  107. package/esm/src/cli/commands/create.d.ts.map +0 -1
  108. package/esm/src/cli/commands/create.js +0 -294
  109. package/esm/src/cli/commands/deploy.d.ts +0 -2
  110. package/esm/src/cli/commands/deploy.d.ts.map +0 -1
  111. package/esm/src/cli/commands/deploy.js +0 -426
  112. package/esm/src/cli/commands/destroy.d.ts +0 -2
  113. package/esm/src/cli/commands/destroy.d.ts.map +0 -1
  114. package/esm/src/cli/commands/destroy.js +0 -340
  115. package/esm/src/cli/commands/doctor.d.ts.map +0 -1
  116. package/esm/src/cli/commands/doctor.js +0 -141
  117. package/esm/src/cli/commands/status.d.ts.map +0 -1
  118. package/esm/src/cli/commands/status.js +0 -152
  119. package/esm/src/cli/mod.d.ts.map +0 -1
  120. package/esm/src/credentials.d.ts.map +0 -1
  121. package/esm/src/discovery.d.ts.map +0 -1
  122. package/esm/src/docker/router/start.sh +0 -146
  123. package/esm/src/guard.d.ts.map +0 -1
  124. package/esm/src/providers/fly.d.ts +0 -70
  125. package/esm/src/providers/fly.d.ts.map +0 -1
  126. package/esm/src/providers/fly.js +0 -411
  127. package/esm/src/providers/tailscale.d.ts +0 -31
  128. package/esm/src/providers/tailscale.d.ts.map +0 -1
  129. package/esm/src/providers/tailscale.js +0 -195
  130. package/esm/src/resolve.d.ts.map +0 -1
  131. package/esm/src/schemas/config.d.ts +0 -5
  132. package/esm/src/schemas/config.d.ts.map +0 -1
  133. package/esm/src/schemas/config.js +0 -22
  134. package/esm/src/schemas/fly.d.ts.map +0 -1
  135. package/esm/src/schemas/tailscale.d.ts.map +0 -1
  136. package/esm/src/template.d.ts.map +0 -1
  137. /package/esm/{src/cli → cli}/commands/doctor.d.ts +0 -0
  138. /package/esm/{src/cli → cli}/commands/list.d.ts +0 -0
  139. /package/esm/{src/cli → cli}/commands/status.d.ts +0 -0
  140. /package/esm/{src/cli → cli}/mod.d.ts +0 -0
  141. /package/esm/src/{docker/router → router}/fly.toml +0 -0
@@ -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,6 +1,4 @@
1
- import type { Result } from "../lib/result.js";
2
- /** Kinds of errors that can occur when fetching a template from GitHub. */
3
- export type TemplateErrorKind = "NotFound" | "RateLimited" | "HttpError" | "ExtractionFailed" | "PathNotFound" | "PathNotDirectory" | "EmptyArchive" | "NetworkError";
1
+ import { Result } from "../lib/result.js";
4
2
  /** Parsed GitHub template reference. */
5
3
  export interface TemplateRef {
6
4
  owner: string;
@@ -12,7 +10,7 @@ export interface TemplateRef {
12
10
  export type TemplateFetchResult = Result<{
13
11
  tempDir: string;
14
12
  templateDir: string;
15
- }, TemplateErrorKind>;
13
+ }>;
16
14
  /**
17
15
  * Parse a template reference string into its components.
18
16
  * Returns null if the format is invalid.
@@ -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"}
@@ -17,15 +17,12 @@
17
17
  import * as dntShim from "../_dnt.shims.js";
18
18
  import { join } from "../deps/jsr.io/@std/path/1.1.4/mod.js";
19
19
  import { runCommand } from "../lib/command.js";
20
+ import { Result } from "../lib/result.js";
20
21
  // =============================================================================
21
22
  // Internal Helpers
22
23
  // =============================================================================
23
24
  /** Shorthand for returning a typed fetch error. */
24
- const fail = (kind, message) => ({
25
- ok: false,
26
- kind,
27
- message,
28
- });
25
+ const fail = (message) => Result.err(message);
29
26
  /** Format a template reference for display. */
30
27
  const formatRef = (ref) => {
31
28
  const base = ref.path === "."
@@ -96,9 +93,9 @@ export const fetchTemplate = async (ref) => {
96
93
  }
97
94
  catch { /* ignore */ }
98
95
  };
99
- const cleanFail = (kind, message) => {
96
+ const cleanFail = (message) => {
100
97
  cleanup();
101
- return fail(kind, message);
98
+ return fail(message);
102
99
  };
103
100
  try {
104
101
  const url = ref.ref
@@ -113,13 +110,13 @@ export const fetchTemplate = async (ref) => {
113
110
  if (!response.ok) {
114
111
  const repo = formatRepo(ref);
115
112
  if (response.status === 404) {
116
- return cleanFail("NotFound", `Template Repository Not Found: ${repo}. ` +
113
+ return cleanFail(`Template Repository Not Found: ${repo}. ` +
117
114
  "Check that the repository exists and is public.");
118
115
  }
119
116
  if (response.status === 403) {
120
- return cleanFail("RateLimited", "GitHub API Rate Limit Exceeded. Try again later.");
117
+ return cleanFail("GitHub API Rate Limit Exceeded. Try again later.");
121
118
  }
122
- return cleanFail("HttpError", `GitHub API Returned HTTP ${response.status} for ${repo}`);
119
+ return cleanFail(`GitHub API Returned HTTP ${response.status} for ${repo}`);
123
120
  }
124
121
  // Write tarball to disk
125
122
  const tarballPath = join(tempDir, "template.tar.gz");
@@ -135,31 +132,31 @@ export const fetchTemplate = async (ref) => {
135
132
  "-C",
136
133
  extractDir,
137
134
  ]);
138
- if (!extractResult.success) {
139
- return cleanFail("ExtractionFailed", "Failed to Extract Template Archive");
135
+ if (!extractResult.ok) {
136
+ return cleanFail("Failed to Extract Template Archive");
140
137
  }
141
138
  // GitHub tarballs have a single top-level dir (owner-repo-commitish/)
142
139
  const entries = [...dntShim.Deno.readDirSync(extractDir)];
143
140
  const topLevel = entries.find((e) => e.isDirectory);
144
141
  if (!topLevel) {
145
- return cleanFail("EmptyArchive", "Template Archive Has No Top-Level Directory");
142
+ return cleanFail("Template Archive Has No Top-Level Directory");
146
143
  }
147
144
  // Locate the template subdirectory
148
145
  const templateDir = join(extractDir, topLevel.name, ref.path);
149
146
  try {
150
147
  const stat = dntShim.Deno.statSync(templateDir);
151
148
  if (!stat.isDirectory) {
152
- return cleanFail("PathNotDirectory", `Template Path '${ref.path}' Is Not a Directory in ${formatRepo(ref)}`);
149
+ return cleanFail(`Template Path '${ref.path}' Is Not a Directory in ${formatRepo(ref)}`);
153
150
  }
154
151
  }
155
152
  catch {
156
- return cleanFail("PathNotFound", `Template Path '${ref.path}' Not Found in ${formatRepo(ref)}`);
153
+ return cleanFail(`Template Path '${ref.path}' Not Found in ${formatRepo(ref)}`);
157
154
  }
158
- return { ok: true, tempDir, templateDir };
155
+ return Result.ok({ tempDir, templateDir });
159
156
  }
160
157
  catch (e) {
161
158
  if (e instanceof TypeError) {
162
- return cleanFail("NetworkError", "Network Error: Could Not Reach GitHub");
159
+ return cleanFail("Network Error: Could Not Reach GitHub");
163
160
  }
164
161
  cleanup();
165
162
  throw e;
package/package.json CHANGED
@@ -1,50 +1,8 @@
1
1
  {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.1.4",
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/paths": {
33
- "import": "./esm/lib/paths.js"
34
- },
35
- "./src/credentials": {
36
- "import": "./esm/src/credentials.js"
37
- },
38
- "./src/discovery": {
39
- "import": "./esm/src/discovery.js"
40
- },
41
- "./src/guard": {
42
- "import": "./esm/src/guard.js"
43
- },
44
- "./src/resolve": {
45
- "import": "./esm/src/resolve.js"
46
- }
47
- },
48
6
  "scripts": {},
49
7
  "bin": {
50
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":""}