@cardelli/ambit 0.3.0 → 0.3.2

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 (46) hide show
  1. package/README.md +32 -13
  2. package/esm/cli/commands/create/index.js +10 -28
  3. package/esm/cli/commands/create/machine.d.ts +2 -1
  4. package/esm/cli/commands/create/machine.d.ts.map +1 -1
  5. package/esm/cli/commands/create/machine.js +69 -28
  6. package/esm/cli/commands/list/apps.d.ts +2 -0
  7. package/esm/cli/commands/list/apps.d.ts.map +1 -0
  8. package/esm/cli/commands/list/apps.js +72 -0
  9. package/esm/cli/commands/list/index.d.ts +2 -0
  10. package/esm/cli/commands/list/index.d.ts.map +1 -0
  11. package/esm/cli/commands/list/index.js +62 -0
  12. package/esm/cli/commands/list/networks.d.ts +2 -0
  13. package/esm/cli/commands/list/networks.d.ts.map +1 -0
  14. package/esm/cli/commands/{list.js → list/networks.js} +17 -27
  15. package/esm/cli/commands/status/app.d.ts +2 -0
  16. package/esm/cli/commands/status/app.d.ts.map +1 -0
  17. package/esm/cli/commands/status/app.js +141 -0
  18. package/esm/cli/commands/status/index.d.ts +2 -0
  19. package/esm/cli/commands/status/index.d.ts.map +1 -0
  20. package/esm/cli/commands/status/index.js +68 -0
  21. package/esm/cli/commands/status/network.d.ts +2 -0
  22. package/esm/cli/commands/status/network.d.ts.map +1 -0
  23. package/esm/cli/commands/status/network.js +104 -0
  24. package/esm/cli/commands/status/networks.d.ts +2 -0
  25. package/esm/cli/commands/status/networks.d.ts.map +1 -0
  26. package/esm/cli/commands/status/networks.js +62 -0
  27. package/esm/deno.js +1 -1
  28. package/esm/main.d.ts +2 -2
  29. package/esm/main.d.ts.map +1 -1
  30. package/esm/main.js +5 -2
  31. package/esm/router/start.sh +7 -4
  32. package/esm/schemas/fly.d.ts +84 -4
  33. package/esm/schemas/fly.d.ts.map +1 -1
  34. package/esm/schemas/fly.js +34 -2
  35. package/esm/util/constants.d.ts +0 -1
  36. package/esm/util/constants.d.ts.map +1 -1
  37. package/esm/util/constants.js +0 -1
  38. package/esm/util/discovery.d.ts +4 -1
  39. package/esm/util/discovery.d.ts.map +1 -1
  40. package/esm/util/discovery.js +3 -0
  41. package/package.json +1 -1
  42. package/esm/cli/commands/list.d.ts +0 -2
  43. package/esm/cli/commands/list.d.ts.map +0 -1
  44. package/esm/cli/commands/status.d.ts +0 -2
  45. package/esm/cli/commands/status.d.ts.map +0 -1
  46. package/esm/cli/commands/status.js +0 -334
package/README.md CHANGED
@@ -160,32 +160,51 @@ These work with all three modes.
160
160
  | `-y`, `--yes` | Skip confirmation prompts |
161
161
  | `--json` | Machine-readable JSON output |
162
162
 
163
- ### `ambit status [network|app]`
163
+ ### `ambit list networks|apps`
164
164
 
165
- Without a subcommand, defaults to showing all routers (same as `status network`).
165
+ #### `ambit list networks`
166
166
 
167
- #### `ambit status network [<name>]`
167
+ Lists all networks and their routers in a table showing the network name, app name, region, machine state, Tailscale connectivity status, and ACL tag.
168
168
 
169
- Without a network name, shows a summary table of all routers. With a name, shows detailed status for a specific network: machine state, SOCKS proxy, Tailscale IP, subnet, and apps on the network.
169
+ | Flag | Description |
170
+ | ----------------- | ------------------------------ |
171
+ | `--org <org>` | Fly.io organization slug |
172
+ | `--json` | Machine-readable JSON output |
173
+
174
+ #### `ambit list apps <network>`
175
+
176
+ Lists all workload apps on a specific network in a table showing the app name, region, and machine state.
170
177
 
171
178
  | Flag | Description |
172
179
  | ----------------- | ------------------------------ |
173
180
  | `--org <org>` | Fly.io organization slug |
174
181
  | `--json` | Machine-readable JSON output |
175
182
 
176
- #### `ambit status app <app>.<network>`
183
+ ### `ambit status [networks|network|app]`
177
184
 
178
- Shows detailed status for a specific app: machines, Flycast IPs, and the backing router.
185
+ Without a subcommand, defaults to showing all networks (same as `status networks`).
179
186
 
180
- | Flag | Description |
181
- | -------------------- | -------------------------------------------- |
182
- | `--network <name>` | Target network (if not using dot syntax) |
183
- | `--org <org>` | Fly.io organization slug |
184
- | `--json` | Machine-readable JSON output |
187
+ #### `ambit status networks`
188
+
189
+ Shows a summary table of all networks and their routers.
185
190
 
186
- ### `ambit list`
191
+ | Flag | Description |
192
+ | ----------------- | ------------------------------ |
193
+ | `--org <org>` | Fly.io organization slug |
194
+ | `--json` | Machine-readable JSON output |
195
+
196
+ #### `ambit status network <name>`
197
+
198
+ Shows detailed status for a specific network: machine state, SOCKS proxy, Tailscale IP, subnet, and apps on the network.
199
+
200
+ | Flag | Description |
201
+ | ----------------- | ------------------------------ |
202
+ | `--org <org>` | Fly.io organization slug |
203
+ | `--json` | Machine-readable JSON output |
187
204
 
188
- Lists all discovered routers across networks in a table showing the network name, app name, region, machine state, Tailscale connectivity status, and ACL tag.
205
+ #### `ambit status app <app>.<network>`
206
+
207
+ Shows detailed status for a specific app: machines, Flycast IPs, and the backing router.
189
208
 
190
209
  | Flag | Description |
191
210
  | ----------------- | ------------------------------ |
@@ -12,9 +12,9 @@ import { isPublicTld } from "../../../util/guard.js";
12
12
  import { createFlyProvider } from "../../../providers/fly.js";
13
13
  import { createTailscaleProvider, } from "../../../providers/tailscale.js";
14
14
  import { getCredentialStore } from "../../../util/credentials.js";
15
- import { FLY_PRIVATE_SUBNET, TAILSCALE_API_KEY_PREFIX, } from "../../../util/constants.js";
15
+ import { TAILSCALE_API_KEY_PREFIX } from "../../../util/constants.js";
16
16
  import { resolveOrg } from "../../../util/resolve.js";
17
- import { assertAdditivePatch, isAutoApproverConfigured, isTagOwnerConfigured, patchAutoApprover, patchTagOwner, } from "../../../util/tailscale-local.js";
17
+ import { assertAdditivePatch, isAutoApproverConfigured, isTagOwnerConfigured, patchTagOwner, } from "../../../util/tailscale-local.js";
18
18
  import { createTransition, hydrateCreate, reportSkipped, } from "./machine.js";
19
19
  // =============================================================================
20
20
  // Stage 1: Fly.io Configuration
@@ -107,40 +107,21 @@ const stageTailscaleConfig = async (out, opts) => {
107
107
  else {
108
108
  tagOwnerSpinner.success(`${opts.tag} Found in Tailscale ACL`);
109
109
  }
110
- if (!opts.manual) {
111
- const hasApprover = isAutoApproverConfigured(policy, opts.tag);
112
- if (!hasApprover && policy) {
113
- const beforeApprover = policy;
114
- policy = patchAutoApprover(policy, opts.tag, FLY_PRIVATE_SUBNET);
115
- assertAdditivePatch(beforeApprover, policy);
116
- const validateApprover = await tailscale.acl.validatePolicy(policy);
117
- if (!validateApprover.ok) {
118
- return handleAclSetFailure(out, validateApprover, `Validating autoApprover patch for ${opts.tag}`);
119
- }
120
- const approverSpinner = out.spinner(`Adding autoApprover for ${opts.tag}`);
121
- const result = await tailscale.acl.setPolicy(policy);
122
- if (!result.ok) {
123
- approverSpinner.fail(`Adding autoApprover for ${opts.tag}`);
124
- return handleAclSetFailure(out, result, `Adding autoApprover for ${opts.tag}`);
125
- }
126
- approverSpinner.success(`Added autoApprover for ${opts.tag}`);
127
- }
128
- }
129
- else if (opts.json) {
110
+ if (opts.manual && opts.json) {
130
111
  const approverSpinner = out.spinner(`Checking autoApprovers for ${opts.tag}`);
131
112
  const hasApprover = isAutoApproverConfigured(policy, opts.tag);
132
113
  if (!hasApprover) {
133
114
  approverSpinner.fail(`Auto-approve Not Configured for ${opts.tag}`);
134
115
  out.blank()
135
- .text(" In JSON mode, ambit can't interactively approve the router's")
136
- .text(` network connections. You can set this up from the Tailscale dashboard:`)
116
+ .text(" In --manual --json mode, ambit can't interactively approve the")
117
+ .text(" router's subnet routes. Set up autoApprovers first:")
137
118
  .link(" https://login.tailscale.com/admin/acls/visual/auto-approvers")
138
- .dim(` Route: ${FLY_PRIVATE_SUBNET} Owner: ${opts.tag}`)
119
+ .dim(` Route: <subnet>/48 Owner: ${opts.tag}`)
139
120
  .blank()
140
- .dim(" Or you can do it manually with this JSON config:")
141
- .dim(` "autoApprovers": { "routes": { "${FLY_PRIVATE_SUBNET}": ["${opts.tag}"] } }`)
121
+ .dim(" Or in the ACL file:")
122
+ .dim(` "autoApprovers": { "routes": { "<subnet>/48": ["${opts.tag}"] } }`)
142
123
  .blank();
143
- return out.die(`Set Up Auto-approve for ${opts.tag} to Use --json`);
124
+ return out.die(`Set Up Auto-approve for ${opts.tag} to Use --manual --json`);
144
125
  }
145
126
  approverSpinner.success(`Auto-approve Configured for ${opts.tag}`);
146
127
  }
@@ -334,6 +315,7 @@ ${bold("EXAMPLES")}
334
315
  region,
335
316
  tag,
336
317
  shouldApprove,
318
+ manual,
337
319
  });
338
320
  };
339
321
  // =============================================================================
@@ -3,7 +3,7 @@ import { Result } from "../../../lib/result.js";
3
3
  import { type FlyProvider } from "../../../providers/fly.js";
4
4
  import type { TailscaleProvider } from "../../../providers/tailscale.js";
5
5
  import type { TailscaleDevice } from "../../../schemas/tailscale.js";
6
- export type CreatePhase = "create_app" | "deploy_router" | "await_device" | "approve_routes" | "configure_dns" | "accept_routes" | "complete";
6
+ export type CreatePhase = "create_app" | "deploy_router" | "approve_routes" | "configure_dns" | "accept_routes" | "complete";
7
7
  export type CreateResult = {
8
8
  network: string;
9
9
  router: {
@@ -22,6 +22,7 @@ export interface CreateCtx {
22
22
  region: string;
23
23
  tag: string;
24
24
  shouldApprove: boolean;
25
+ manual: boolean;
25
26
  appName: string;
26
27
  routerId: string;
27
28
  device?: TailscaleDevice;
@@ -1 +1 @@
1
- {"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/create/machine.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAQhD,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAS7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAOrE,MAAM,MAAM,WAAW,GACnB,YAAY,GACZ,eAAe,GACf,cAAc,GACd,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,UAAU,CAAC;AAMf,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,WAAW,CAAC;IACjB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD,eAAO,MAAM,aAAa,GACxB,KAAK,MAAM,CAAC,YAAY,CAAC,EACzB,YAAY,WAAW,SAMxB,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,KAAK,SAAS,KACb,OAAO,CAAC,WAAW,CA4BrB,CAAC;AAMF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,WAAW,EAClB,KAAK,SAAS,KACb,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAoH7B,CAAC"}
1
+ {"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/create/machine.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAQhD,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAW7E,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAOrE,MAAM,MAAM,WAAW,GACnB,YAAY,GACZ,eAAe,GACf,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,UAAU,CAAC;AAMf,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACxD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,WAAW,CAAC;IACjB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAcD,eAAO,MAAM,aAAa,GACxB,KAAK,MAAM,CAAC,YAAY,CAAC,EACzB,YAAY,WAAW,SAMxB,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,KAAK,SAAS,KACb,OAAO,CAAC,WAAW,CA4BrB,CAAC;AAMF,eAAO,MAAM,gBAAgB,GAC3B,OAAO,WAAW,EAClB,KAAK,SAAS,KACb,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CA6K7B,CAAC"}
@@ -7,7 +7,7 @@ import { extractSubnet } from "../../../util/fly-transforms.js";
7
7
  import { ROUTER_DOCKER_DIR, SECRET_NETWORK_NAME, SECRET_ROUTER_ID, SECRET_TAILSCALE_AUTHKEY, } from "../../../util/constants.js";
8
8
  import { FlyDeployError } from "../../../providers/fly.js";
9
9
  import { getRouterAppName } from "../../../util/naming.js";
10
- import { enableAcceptRoutes, isAcceptRoutesEnabled, isAutoApproverConfigured, isTailscaleInstalled, waitForDevice, } from "../../../util/tailscale-local.js";
10
+ import { assertAdditivePatch, enableAcceptRoutes, isAcceptRoutesEnabled, isAutoApproverConfigured, isTailscaleInstalled, patchAutoApprover, waitForDevice, } from "../../../util/tailscale-local.js";
11
11
  import { findRouterApp, getRouterMachineInfo } from "../../../util/discovery.js";
12
12
  // =============================================================================
13
13
  // Phase Labels
@@ -15,7 +15,6 @@ import { findRouterApp, getRouterMachineInfo } from "../../../util/discovery.js"
15
15
  const CREATE_PHASES = [
16
16
  { phase: "create_app", label: "Fly App Created" },
17
17
  { phase: "deploy_router", label: "Router Deployed" },
18
- { phase: "await_device", label: "Router in Tailnet" },
19
18
  { phase: "approve_routes", label: "Routes Approved" },
20
19
  { phase: "configure_dns", label: "Split DNS Configured" },
21
20
  { phase: "accept_routes", label: "Accept Routes Enabled" },
@@ -44,7 +43,7 @@ export const hydrateCreate = async (ctx) => {
44
43
  return "complete";
45
44
  const device = await ctx.tailscale.devices.getByHostname(router.appName);
46
45
  if (!device)
47
- return "await_device";
46
+ return "approve_routes";
48
47
  ctx.device = device;
49
48
  const routes = await ctx.tailscale.routes.get(device.id);
50
49
  if (!routes || routes.unapproved.length > 0)
@@ -70,15 +69,7 @@ export const createTransition = async (phase, ctx) => {
70
69
  return Result.ok("deploy_router");
71
70
  }
72
71
  case "deploy_router": {
73
- const authKey = await ctx.tailscale.auth.createKey({
74
- reusable: false,
75
- ephemeral: false,
76
- preauthorized: true,
77
- tags: [ctx.tag],
78
- });
79
- ctx.out.ok("Auth Key Created");
80
- await ctx.out.spin("Setting Secrets", () => ctx.fly.secrets.set(ctx.appName, {
81
- [SECRET_TAILSCALE_AUTHKEY]: authKey,
72
+ await ctx.out.spin("Staging Secrets", () => ctx.fly.secrets.set(ctx.appName, {
82
73
  [SECRET_NETWORK_NAME]: ctx.network,
83
74
  [SECRET_ROUTER_ID]: ctx.routerId,
84
75
  }, { stage: true }));
@@ -102,34 +93,84 @@ export const createTransition = async (phase, ctx) => {
102
93
  const m = machines.find((m) => m.private_ip);
103
94
  if (m?.private_ip)
104
95
  ctx.subnet = extractSubnet(m.private_ip);
105
- if (!ctx.shouldApprove)
106
- return Result.ok("complete");
107
- return Result.ok("await_device");
96
+ return Result.ok("approve_routes");
108
97
  }
109
- case "await_device": {
110
- ctx.device = await waitForDevice(ctx.tailscale, ctx.appName, 180000);
111
- ctx.out.ok(`Router Joined Tailnet: ${ctx.device.addresses[0]}`);
98
+ case "approve_routes": {
112
99
  if (!ctx.subnet) {
113
100
  const machines = await ctx.fly.machines.list(ctx.appName);
114
101
  const m = machines.find((m) => m.private_ip);
115
102
  if (m?.private_ip)
116
103
  ctx.subnet = extractSubnet(m.private_ip);
117
104
  }
118
- return Result.ok("approve_routes");
119
- }
120
- case "approve_routes": {
121
- if (!ctx.device || !ctx.subnet) {
122
- return Result.err("Missing Device or Subnet");
123
- }
124
- const policy = await ctx.tailscale.acl.getPolicy();
105
+ if (!ctx.subnet)
106
+ return Result.err("Missing Subnet");
107
+ let policy = await ctx.tailscale.acl.getPolicy();
125
108
  const hasAutoApprover = isAutoApproverConfigured(policy, ctx.tag);
126
- if (hasAutoApprover) {
127
- ctx.out.ok("Routes Auto-Approved via ACL Policy");
109
+ let approverReady = hasAutoApprover;
110
+ if (!hasAutoApprover && !ctx.manual && policy) {
111
+ const before = policy;
112
+ policy = patchAutoApprover(policy, ctx.tag, ctx.subnet);
113
+ assertAdditivePatch(before, policy);
114
+ const vr = await ctx.tailscale.acl.validatePolicy(policy);
115
+ if (!vr.ok) {
116
+ ctx.out.warn(`Could Not Validate autoApprover Patch: ${vr.error ?? `HTTP ${vr.status}`}`);
117
+ }
118
+ else {
119
+ const sr = await ctx.tailscale.acl.setPolicy(policy);
120
+ if (sr.ok) {
121
+ ctx.out.ok(`Added autoApprover for ${ctx.tag} → ${ctx.subnet}`);
122
+ approverReady = true;
123
+ }
124
+ else if (sr.status === 403) {
125
+ ctx.out.warn("API Token Lacks ACL Write Permission — Will Approve Routes Manually");
126
+ }
127
+ else {
128
+ ctx.out.warn(`Could Not Set autoApprover: ${sr.error ?? `HTTP ${sr.status}`}`);
129
+ }
130
+ }
128
131
  }
129
- else {
132
+ // If the device isn't in the tailnet yet, the router hasn't
133
+ // authenticated. Mint an auth key and deliver it — the non-staged
134
+ // secrets set triggers a Fly restart. The router boots with the key,
135
+ // authenticates, and advertises routes. With autoApprover in place,
136
+ // routes are auto-approved immediately.
137
+ if (!ctx.device) {
138
+ const existing = await ctx.tailscale.devices.getByHostname(ctx.appName);
139
+ if (existing) {
140
+ ctx.device = existing;
141
+ }
142
+ else {
143
+ const authKey = await ctx.tailscale.auth.createKey({
144
+ reusable: false,
145
+ ephemeral: false,
146
+ preauthorized: true,
147
+ tags: [ctx.tag],
148
+ });
149
+ ctx.out.ok("Auth Key Created");
150
+ const keySpinner = ctx.out.spinner("Delivering Auth Key (restarting router)");
151
+ await ctx.fly.secrets.set(ctx.appName, {
152
+ [SECRET_TAILSCALE_AUTHKEY]: authKey,
153
+ });
154
+ keySpinner.success("Auth Key Delivered");
155
+ if (!ctx.shouldApprove) {
156
+ ctx.out.dim(" Skipping Route Approval (--no-auto-approve)");
157
+ return Result.ok("complete");
158
+ }
159
+ ctx.device = await waitForDevice(ctx.tailscale, ctx.appName, 180000);
160
+ ctx.out.ok(`Router Joined Tailnet: ${ctx.device.addresses[0]}`);
161
+ }
162
+ }
163
+ const routes = await ctx.tailscale.routes.get(ctx.device.id);
164
+ if (routes && routes.unapproved.length > 0) {
165
+ if (approverReady) {
166
+ ctx.out.warn("Routes Not Auto-Approved Despite autoApprover — Approving Manually");
167
+ }
130
168
  await ctx.tailscale.routes.approve(ctx.device.id, [ctx.subnet]);
131
169
  ctx.out.ok("Subnet Routes Approved");
132
170
  }
171
+ else {
172
+ ctx.out.ok("Routes Auto-Approved via ACL Policy");
173
+ }
133
174
  return Result.ok("configure_dns");
134
175
  }
135
176
  case "configure_dns": {
@@ -0,0 +1,2 @@
1
+ export declare const listApps: (argv: string[]) => Promise<void>;
2
+ //# sourceMappingURL=apps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/list/apps.ts"],"names":[],"mappings":"AAgCA,eAAO,MAAM,QAAQ,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAqE3D,CAAC"}
@@ -0,0 +1,72 @@
1
+ // =============================================================================
2
+ // List Apps — List All Workload Apps on a Network
3
+ // =============================================================================
4
+ import { parseArgs } from "../../../deps/jsr.io/@std/cli/1.0.28/mod.js";
5
+ import { Table } from "../../../deps/jsr.io/@cliffy/table/1.0.0/mod.js";
6
+ import { bold } from "../../../lib/cli.js";
7
+ import { checkArgs } from "../../../lib/args.js";
8
+ import { createOutput } from "../../../lib/output.js";
9
+ import { findRouterApp, listWorkloadAppsOnNetwork, } from "../../../util/discovery.js";
10
+ import { initSession } from "../../../util/session.js";
11
+ // =============================================================================
12
+ // List Apps Command
13
+ // =============================================================================
14
+ export const listApps = async (argv) => {
15
+ const opts = { string: ["org"], boolean: ["help", "json"] };
16
+ const args = parseArgs(argv, opts);
17
+ checkArgs(args, opts, "ambit list apps");
18
+ if (args.help) {
19
+ console.log(`
20
+ ${bold("ambit list apps")} - List Apps on a Network
21
+
22
+ ${bold("USAGE")}
23
+ ambit list apps <network> [--org <org>] [--json]
24
+
25
+ ${bold("OPTIONS")}
26
+ --org <org> Fly.io organization slug
27
+ --json Output as JSON
28
+
29
+ ${bold("EXAMPLES")}
30
+ ambit list apps browsers
31
+ ambit list apps browsers --json
32
+ `);
33
+ return;
34
+ }
35
+ const out = createOutput(args.json);
36
+ const network = typeof args._[0] === "string" ? args._[0] : undefined;
37
+ if (!network) {
38
+ return out.die("Missing Network Name. Usage: ambit list apps <network>");
39
+ }
40
+ const { fly, org } = await initSession(out, {
41
+ json: args.json,
42
+ org: args.org,
43
+ });
44
+ const router = await findRouterApp(fly, org, network);
45
+ if (!router) {
46
+ return out.die(`No Network Found: '${network}'`);
47
+ }
48
+ const workloads = await listWorkloadAppsOnNetwork(fly, org, network);
49
+ if (workloads.length === 0) {
50
+ out.blank()
51
+ .text(`No Apps Found on Network '${network}'.`)
52
+ .dim(" Deploy one with: ambit deploy <app>.<network>")
53
+ .blank();
54
+ out.done({ network, apps: [] });
55
+ out.print();
56
+ return;
57
+ }
58
+ out.blank().header(`Apps on '${network}'`).blank();
59
+ const rows = workloads.map((w) => [w.appName, w.status]);
60
+ const table = new Table()
61
+ .header(["App", "Status"])
62
+ .body(rows)
63
+ .indent(2)
64
+ .padding(2);
65
+ out.text(table.toString());
66
+ out.blank();
67
+ out.done({
68
+ network,
69
+ apps: workloads.map((w) => ({ appName: w.appName, status: w.status })),
70
+ });
71
+ out.print();
72
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/list/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
1
+ // =============================================================================
2
+ // List Command - List Networks or Apps
3
+ // =============================================================================
4
+ import * as dntShim from "../../../_dnt.shims.js";
5
+ import { parseArgs } from "../../../deps/jsr.io/@std/cli/1.0.28/mod.js";
6
+ import { bold } from "../../../lib/cli.js";
7
+ import { registerCommand } from "../../mod.js";
8
+ import { listNetworks } from "./networks.js";
9
+ import { listApps } from "./apps.js";
10
+ // =============================================================================
11
+ // Top-Level Help
12
+ // =============================================================================
13
+ const showListHelp = () => {
14
+ console.log(`
15
+ ${bold("ambit list")} - List Networks or Apps
16
+
17
+ ${bold("USAGE")}
18
+ ambit list networks [options]
19
+ ambit list apps <network> [options]
20
+
21
+ ${bold("SUBCOMMANDS")}
22
+ networks List all networks and their routers
23
+ apps List workload apps on a specific network
24
+
25
+ ${bold("OPTIONS")}
26
+ --org <org> Fly.io organization slug
27
+ --json Output as JSON
28
+
29
+ ${bold("EXAMPLES")}
30
+ ambit list networks
31
+ ambit list apps browsers
32
+ ambit list apps browsers --json
33
+
34
+ Run 'ambit list networks --help' or 'ambit list apps --help' for details.
35
+ `);
36
+ };
37
+ // =============================================================================
38
+ // Dispatcher
39
+ // =============================================================================
40
+ const list = async (argv) => {
41
+ const subcommand = typeof argv[0] === "string" ? argv[0] : undefined;
42
+ if (subcommand === "networks")
43
+ return listNetworks(argv.slice(1));
44
+ if (subcommand === "apps")
45
+ return listApps(argv.slice(1));
46
+ const args = parseArgs(argv, { boolean: ["help"] });
47
+ if (args.help) {
48
+ showListHelp();
49
+ return;
50
+ }
51
+ showListHelp();
52
+ dntShim.Deno.exit(1);
53
+ };
54
+ // =============================================================================
55
+ // Register Command
56
+ // =============================================================================
57
+ registerCommand({
58
+ name: "list",
59
+ description: "List networks or apps",
60
+ usage: "ambit list networks|apps [options]",
61
+ run: list,
62
+ });
@@ -0,0 +1,2 @@
1
+ export declare const listNetworks: (argv: string[]) => Promise<void>;
2
+ //# sourceMappingURL=networks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"networks.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/list/networks.ts"],"names":[],"mappings":"AAqEA,eAAO,MAAM,YAAY,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CA2B/D,CAAC"}
@@ -1,28 +1,27 @@
1
1
  // =============================================================================
2
- // List Command - List All Discovered Routers
2
+ // List Networks List All Discovered Routers Across Networks
3
3
  // =============================================================================
4
- import { parseArgs } from "../../deps/jsr.io/@std/cli/1.0.28/mod.js";
5
- import { Table } from "../../deps/jsr.io/@cliffy/table/1.0.0/mod.js";
6
- import { bold } from "../../lib/cli.js";
7
- import { checkArgs } from "../../lib/args.js";
8
- import { createOutput } from "../../lib/output.js";
9
- import { registerCommand } from "../mod.js";
10
- import { discoverRouters } from "../../util/discovery.js";
11
- import { initSession } from "../../util/session.js";
4
+ import { parseArgs } from "../../../deps/jsr.io/@std/cli/1.0.28/mod.js";
5
+ import { Table } from "../../../deps/jsr.io/@cliffy/table/1.0.0/mod.js";
6
+ import { bold } from "../../../lib/cli.js";
7
+ import { checkArgs } from "../../../lib/args.js";
8
+ import { createOutput } from "../../../lib/output.js";
9
+ import { discoverRouters } from "../../../util/discovery.js";
10
+ import { initSession } from "../../../util/session.js";
12
11
  // =============================================================================
13
12
  // Stage: Render
14
13
  // =============================================================================
15
14
  const stageRender = (out, routers) => {
16
15
  if (routers.length === 0) {
17
16
  out.blank()
18
- .text("No Routers Found.")
17
+ .text("No Networks Found.")
19
18
  .dim(" Create one with: ambit create <network>")
20
19
  .blank();
21
20
  out.done({ routers: [] });
22
21
  out.print();
23
22
  return;
24
23
  }
25
- out.blank().header("Routers").blank();
24
+ out.blank().header("Networks").blank();
26
25
  const rows = routers.map((r) => {
27
26
  const tsStatus = r.tailscale
28
27
  ? (r.tailscale.online ? "online" : "offline")
@@ -31,14 +30,14 @@ const stageRender = (out, routers) => {
31
30
  return [
32
31
  r.network,
33
32
  r.appName,
33
+ r.status,
34
34
  r.machine?.region ?? "-",
35
- r.machine?.state ?? "unknown",
36
35
  tsStatus,
37
36
  tag,
38
37
  ];
39
38
  });
40
39
  const table = new Table()
41
- .header(["Network", "App", "Region", "State", "Tailscale", "Tag"])
40
+ .header(["Network", "App", "Status", "Region", "Tailscale", "Tag"])
42
41
  .body(rows)
43
42
  .indent(2)
44
43
  .padding(2);
@@ -48,18 +47,18 @@ const stageRender = (out, routers) => {
48
47
  out.print();
49
48
  };
50
49
  // =============================================================================
51
- // List Command
50
+ // List Networks Command
52
51
  // =============================================================================
53
- const list = async (argv) => {
52
+ export const listNetworks = async (argv) => {
54
53
  const opts = { string: ["org"], boolean: ["help", "json"] };
55
54
  const args = parseArgs(argv, opts);
56
- checkArgs(args, opts, "ambit list");
55
+ checkArgs(args, opts, "ambit list networks");
57
56
  if (args.help) {
58
57
  console.log(`
59
- ${bold("ambit list")} - List All Discovered Routers
58
+ ${bold("ambit list networks")} - List All Networks
60
59
 
61
60
  ${bold("USAGE")}
62
- ambit list [--org <org>] [--json]
61
+ ambit list networks [--org <org>] [--json]
63
62
 
64
63
  ${bold("OPTIONS")}
65
64
  --org <org> Fly.io organization slug
@@ -75,12 +74,3 @@ ${bold("OPTIONS")}
75
74
  const routers = await discoverRouters(out, fly, tailscale, org);
76
75
  stageRender(out, routers);
77
76
  };
78
- // =============================================================================
79
- // Register Command
80
- // =============================================================================
81
- registerCommand({
82
- name: "list",
83
- description: "List all discovered routers across networks",
84
- usage: "ambit list [--org <org>] [--json]",
85
- run: list,
86
- });
@@ -0,0 +1,2 @@
1
+ export declare const statusApp: (argv: string[]) => Promise<void>;
2
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/status/app.ts"],"names":[],"mappings":"AA4JA,eAAO,MAAM,SAAS,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAyD5D,CAAC"}