@cardelli/ambit 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,15 +65,41 @@ Open `http://my-crazy-site.lab`. It works for you and nobody else.
65
65
 
66
66
  ## Commands
67
67
 
68
+ ### `ambit auth login`
69
+
70
+ Authenticates with both Fly.io and Tailscale. Only prompts for credentials that are missing or invalid — existing valid tokens are preserved. Run this before using any other command.
71
+
72
+ | Flag | Description |
73
+ | ----------------------- | ---------------------------------------------- |
74
+ | `--ts-api-key <key>` | Tailscale API access token (tskey-api-...) |
75
+ | `--fly-api-key <token>` | Fly.io API token |
76
+ | `--json` | Machine-readable JSON output |
77
+
78
+ ### `ambit auth whoami`
79
+
80
+ Shows the current authentication status for both Fly.io and Tailscale.
81
+
82
+ | Flag | Description |
83
+ | -------- | --------------------------- |
84
+ | `--json` | Machine-readable JSON output |
85
+
86
+ ### `ambit auth logout`
87
+
88
+ Clears all stored credentials and logs out of Fly.io.
89
+
90
+ | Flag | Description |
91
+ | ------------- | ------------------------ |
92
+ | `-y`, `--yes` | Skip confirmation prompt |
93
+ | `--json` | Machine-readable JSON |
94
+
68
95
  ### `ambit create <network>`
69
96
 
70
- This is the first command you run, it sets up your private network: a named slice of the cloud that only your devices can reach. Under the hood it handles Fly.io and Tailscale authentication, deploys the router, sets up DNS, configures your local machine to accept the new routes, and automatically adds the router's tag to your Tailscale ACL policy.
97
+ Creates a private network. Deploys a Tailscale subnet router on a Fly.io custom network, sets up DNS, configures your local machine to accept the new routes, and automatically adds the router's tag to your Tailscale ACL policy. Requires `ambit auth login` first.
71
98
 
72
99
  | Flag | Description |
73
100
  | ------------------- | --------------------------------------------------------------------------- |
74
101
  | `--org <org>` | Fly.io organization slug |
75
102
  | `--region <region>` | Fly.io region (default: `iad`) |
76
- | `--api-key <key>` | Tailscale API access token (tskey-api-...) |
77
103
  | `--tag <tag>` | Tailscale ACL tag for the router (default: `tag:ambit-<network>`) |
78
104
  | `--manual` | Skip automatic Tailscale ACL configuration (tagOwners + autoApprovers) |
79
105
  | `--no-auto-approve` | Skip waiting for router and approving routes |
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/auth.ts"],"names":[],"mappings":""}
@@ -0,0 +1,336 @@
1
+ // =============================================================================
2
+ // Auth Command - Manage Fly.io and Tailscale Authentication
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, confirm, readSecret } 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 { runCommand } from "../../lib/command.js";
11
+ import { createTailscaleProvider } from "../../providers/tailscale.js";
12
+ import { getCredentialStore } from "../../util/credentials.js";
13
+ import { TAILSCALE_API_KEY_PREFIX } from "../../util/constants.js";
14
+ import { FlyAuthSchema } from "../../schemas/fly.js";
15
+ import { fileExists } from "../../lib/cli.js";
16
+ // =============================================================================
17
+ // Helpers
18
+ // =============================================================================
19
+ const tryFlyWhoami = async (token) => {
20
+ const result = await runCommand(["fly", "auth", "whoami", "--json"], token ? { env: { FLY_API_TOKEN: token } } : undefined);
21
+ if (!result.ok)
22
+ return null;
23
+ const auth = result.json();
24
+ if (!auth.ok)
25
+ return null;
26
+ const parsed = FlyAuthSchema.safeParse(auth.value);
27
+ return parsed.success ? parsed.data.email : null;
28
+ };
29
+ const readFlyConfigToken = async () => {
30
+ const home = dntShim.Deno.env.get("HOME") || dntShim.Deno.env.get("USERPROFILE") || "";
31
+ const configPath = `${home}/.fly/config.yml`;
32
+ if (!(await fileExists(configPath)))
33
+ return null;
34
+ const content = await dntShim.Deno.readTextFile(configPath);
35
+ const match = content.match(/access_token:\s*(.+)/);
36
+ return match?.[1]?.trim() ?? null;
37
+ };
38
+ // =============================================================================
39
+ // Auth Login
40
+ // =============================================================================
41
+ const authLogin = async (argv) => {
42
+ const opts = {
43
+ string: ["ts-api-key", "fly-api-key"],
44
+ boolean: ["help", "json"],
45
+ };
46
+ const args = parseArgs(argv, opts);
47
+ checkArgs(args, opts, "ambit auth login", 0);
48
+ if (args.help) {
49
+ console.log(`
50
+ ${bold("ambit auth login")} - Authenticate with Fly.io and Tailscale
51
+
52
+ ${bold("USAGE")}
53
+ ambit auth login [options]
54
+
55
+ ${bold("OPTIONS")}
56
+ --ts-api-key <key> Tailscale API access token (tskey-api-...)
57
+ --fly-api-key <token> Fly.io API token
58
+ --json Output as JSON
59
+
60
+ ${bold("DESCRIPTION")}
61
+ Authenticates with both Fly.io and Tailscale. Only prompts for
62
+ credentials that are missing or invalid — existing valid tokens
63
+ are preserved.
64
+
65
+ ${bold("EXAMPLES")}
66
+ ambit auth login
67
+ ambit auth login --ts-api-key tskey-api-... --fly-api-key fo1_...
68
+ `);
69
+ return;
70
+ }
71
+ const out = createOutput(args.json);
72
+ const credentials = getCredentialStore();
73
+ // =========================================================================
74
+ // Step 1: Fly.io
75
+ // =========================================================================
76
+ out.blank().header("Fly.io Authentication").blank();
77
+ let flyEmail;
78
+ if (args["fly-api-key"]) {
79
+ const email = await tryFlyWhoami(args["fly-api-key"]);
80
+ if (!email) {
81
+ return out.die("Invalid Fly.io API Token");
82
+ }
83
+ await credentials.setFlyToken(args["fly-api-key"]);
84
+ out.ok(`Authenticated as ${email}`);
85
+ flyEmail = email;
86
+ }
87
+ else {
88
+ const storedToken = await credentials.getFlyToken();
89
+ const storedEmail = storedToken
90
+ ? await tryFlyWhoami(storedToken)
91
+ : null;
92
+ if (storedEmail) {
93
+ out.ok(`Already Authenticated as ${storedEmail}`);
94
+ flyEmail = storedEmail;
95
+ }
96
+ else {
97
+ if (args.json) {
98
+ return out.die("Not Authenticated with Fly.io. Provide --fly-api-key in JSON Mode");
99
+ }
100
+ const loginResult = await runCommand(["fly", "auth", "login"], {
101
+ interactive: true,
102
+ });
103
+ if (!loginResult.ok) {
104
+ return out.die("Fly.io Authentication Failed");
105
+ }
106
+ const token = await readFlyConfigToken();
107
+ if (!token) {
108
+ return out.die("Could Not Read Fly.io Token After Login");
109
+ }
110
+ const email = await tryFlyWhoami(token);
111
+ if (!email) {
112
+ return out.die("Fly.io Authentication Verification Failed");
113
+ }
114
+ await credentials.setFlyToken(token);
115
+ out.ok(`Authenticated as ${email}`);
116
+ flyEmail = email;
117
+ }
118
+ }
119
+ // =========================================================================
120
+ // Step 2: Tailscale
121
+ // =========================================================================
122
+ out.blank().header("Tailscale Authentication").blank();
123
+ let tailscaleOk = false;
124
+ if (args["ts-api-key"]) {
125
+ if (!args["ts-api-key"].startsWith(TAILSCALE_API_KEY_PREFIX)) {
126
+ return out.die("Invalid Token Format. Expected 'tskey-api-...' (API Access Token, Not Auth Key)");
127
+ }
128
+ const tailscale = createTailscaleProvider(args["ts-api-key"]);
129
+ const validateSpinner = out.spinner("Validating API Access Token");
130
+ const isValid = await tailscale.auth.validateKey();
131
+ if (!isValid) {
132
+ validateSpinner.fail("Invalid API Access Token");
133
+ return out.die("Failed to Validate Tailscale API Access Token");
134
+ }
135
+ validateSpinner.success("API Access Token Validated");
136
+ await credentials.setTailscaleApiKey(args["ts-api-key"]);
137
+ tailscaleOk = true;
138
+ }
139
+ else {
140
+ const storedKey = await credentials.getTailscaleApiKey();
141
+ if (storedKey) {
142
+ const tailscale = createTailscaleProvider(storedKey);
143
+ const validateSpinner = out.spinner("Checking Stored API Key");
144
+ const isValid = await tailscale.auth.validateKey();
145
+ if (isValid) {
146
+ validateSpinner.success("API Key Already Configured");
147
+ tailscaleOk = true;
148
+ }
149
+ else {
150
+ validateSpinner.fail("Stored API Key Is Invalid or Expired");
151
+ // Fall through to prompt
152
+ }
153
+ }
154
+ if (!tailscaleOk) {
155
+ if (args.json) {
156
+ return out.die("Tailscale API Key Not Configured. Provide --ts-api-key in JSON Mode");
157
+ }
158
+ out.dim("Ambit Needs an API Access Token (Not an Auth Key) to Manage Your Tailnet.")
159
+ .dim("Create One at:").link(" https://login.tailscale.com/admin/settings/keys")
160
+ .blank();
161
+ const apiKey = await readSecret("API access token (tskey-api-...): ");
162
+ if (!apiKey) {
163
+ return out.die("Tailscale API Access Token Required");
164
+ }
165
+ if (!apiKey.startsWith(TAILSCALE_API_KEY_PREFIX)) {
166
+ return out.die("Invalid Token Format. Expected 'tskey-api-...' (API Access Token, Not Auth Key)");
167
+ }
168
+ const tailscale = createTailscaleProvider(apiKey);
169
+ const validateSpinner = out.spinner("Validating API Access Token");
170
+ const isValid = await tailscale.auth.validateKey();
171
+ if (!isValid) {
172
+ validateSpinner.fail("Invalid API Access Token");
173
+ return out.die("Failed to Validate Tailscale API Access Token");
174
+ }
175
+ validateSpinner.success("API Access Token Validated");
176
+ await credentials.setTailscaleApiKey(apiKey);
177
+ tailscaleOk = true;
178
+ }
179
+ }
180
+ out.blank();
181
+ out.done({ fly: flyEmail, tailscale: tailscaleOk });
182
+ out.print();
183
+ };
184
+ // =============================================================================
185
+ // Auth Whoami
186
+ // =============================================================================
187
+ const authWhoami = async (argv) => {
188
+ const opts = {
189
+ boolean: ["help", "json"],
190
+ };
191
+ const args = parseArgs(argv, opts);
192
+ checkArgs(args, opts, "ambit auth whoami", 0);
193
+ if (args.help) {
194
+ console.log(`
195
+ ${bold("ambit auth whoami")} - Show Current Authentication Status
196
+
197
+ ${bold("USAGE")}
198
+ ambit auth whoami [options]
199
+
200
+ ${bold("OPTIONS")}
201
+ --json Output as JSON
202
+
203
+ ${bold("EXAMPLES")}
204
+ ambit auth whoami
205
+ ambit auth whoami --json
206
+ `);
207
+ return;
208
+ }
209
+ const out = createOutput(args.json);
210
+ const credentials = getCredentialStore();
211
+ // =========================================================================
212
+ // Step 1: Fly.io
213
+ // =========================================================================
214
+ let flyEmail = null;
215
+ const storedToken = await credentials.getFlyToken();
216
+ if (storedToken) {
217
+ flyEmail = await tryFlyWhoami(storedToken);
218
+ }
219
+ if (!flyEmail) {
220
+ flyEmail = await tryFlyWhoami();
221
+ }
222
+ // =========================================================================
223
+ // Step 2: Tailscale
224
+ // =========================================================================
225
+ let tailscaleOk = false;
226
+ const storedKey = await credentials.getTailscaleApiKey();
227
+ if (storedKey) {
228
+ const tailscale = createTailscaleProvider(storedKey);
229
+ tailscaleOk = await tailscale.auth.validateKey();
230
+ }
231
+ // =========================================================================
232
+ // Output
233
+ // =========================================================================
234
+ out.blank();
235
+ out.text(` Fly.io: ${flyEmail ?? "Not Authenticated"}`);
236
+ out.text(` Tailscale: ${tailscaleOk ? "API Key Configured" : "Not Configured"}`);
237
+ out.blank();
238
+ out.done({ fly: flyEmail, tailscale: tailscaleOk });
239
+ out.print();
240
+ };
241
+ // =============================================================================
242
+ // Auth Logout
243
+ // =============================================================================
244
+ const authLogout = async (argv) => {
245
+ const opts = {
246
+ boolean: ["help", "json", "yes"],
247
+ alias: { y: "yes" },
248
+ };
249
+ const args = parseArgs(argv, opts);
250
+ checkArgs(args, opts, "ambit auth logout", 0);
251
+ if (args.help) {
252
+ console.log(`
253
+ ${bold("ambit auth logout")} - Clear Stored Credentials
254
+
255
+ ${bold("USAGE")}
256
+ ambit auth logout [options]
257
+
258
+ ${bold("OPTIONS")}
259
+ -y, --yes Skip confirmation prompt
260
+ --json Output as JSON
261
+
262
+ ${bold("EXAMPLES")}
263
+ ambit auth logout
264
+ ambit auth logout --yes
265
+ `);
266
+ return;
267
+ }
268
+ const out = createOutput(args.json);
269
+ if (!args.yes && !args.json) {
270
+ const ok = await confirm("This will clear all stored credentials. Continue?");
271
+ if (!ok) {
272
+ return out.die("Aborted");
273
+ }
274
+ }
275
+ const credentials = getCredentialStore();
276
+ await credentials.clear();
277
+ out.ok("Cleared Stored Credentials");
278
+ await runCommand(["fly", "auth", "logout"]);
279
+ out.ok("Logged Out of Fly.io");
280
+ out.blank();
281
+ out.done({ fly: true, tailscale: true });
282
+ out.print();
283
+ };
284
+ // =============================================================================
285
+ // Top-Level Help
286
+ // =============================================================================
287
+ const showAuthHelp = () => {
288
+ console.log(`
289
+ ${bold("ambit auth")} - Manage Fly.io and Tailscale Authentication
290
+
291
+ ${bold("USAGE")}
292
+ ambit auth <subcommand> [options]
293
+
294
+ ${bold("SUBCOMMANDS")}
295
+ login Authenticate with Fly.io and Tailscale
296
+ whoami Show current authentication status
297
+ logout Clear stored credentials
298
+
299
+ ${bold("EXAMPLES")}
300
+ ambit auth login
301
+ ambit auth whoami
302
+ ambit auth logout
303
+
304
+ Run 'ambit auth <subcommand> --help' for details.
305
+ `);
306
+ };
307
+ // =============================================================================
308
+ // Dispatcher
309
+ // =============================================================================
310
+ const auth = async (argv) => {
311
+ const subcommand = typeof argv[0] === "string" ? argv[0] : undefined;
312
+ if (subcommand === "login")
313
+ return authLogin(argv.slice(1));
314
+ if (subcommand === "whoami")
315
+ return authWhoami(argv.slice(1));
316
+ if (subcommand === "logout")
317
+ return authLogout(argv.slice(1));
318
+ const opts = { boolean: ["help"] };
319
+ const args = parseArgs(argv, opts);
320
+ checkArgs(args, opts, "ambit auth", 0);
321
+ if (args.help) {
322
+ showAuthHelp();
323
+ return;
324
+ }
325
+ showAuthHelp();
326
+ dntShim.Deno.exit(1);
327
+ };
328
+ // =============================================================================
329
+ // Register Command
330
+ // =============================================================================
331
+ registerCommand({
332
+ name: "auth",
333
+ description: "Manage Fly.io and Tailscale authentication",
334
+ usage: "ambit auth login|whoami|logout [options]",
335
+ run: auth,
336
+ });
@@ -2,7 +2,7 @@
2
2
  // Create Command - Create Tailscale Subnet Router on Fly.io Custom Network
3
3
  // =============================================================================
4
4
  import { parseArgs } from "../../../deps/jsr.io/@std/cli/1.0.28/mod.js";
5
- import { bold, readSecret } from "../../../lib/cli.js";
5
+ import { bold } from "../../../lib/cli.js";
6
6
  import { checkArgs } from "../../../lib/args.js";
7
7
  import { createOutput } from "../../../lib/output.js";
8
8
  import { runMachine } from "../../../lib/machine.js";
@@ -21,7 +21,8 @@ import { createTransition, hydrateCreate, reportSkipped, } from "./machine.js";
21
21
  // =============================================================================
22
22
  const stageFlyConfig = async (out, opts) => {
23
23
  out.header("Step 1: Fly.io Configuration").blank();
24
- const fly = createFlyProvider();
24
+ const flyToken = await getCredentialStore().getFlyToken();
25
+ const fly = createFlyProvider(flyToken ?? undefined);
25
26
  await fly.auth.ensureInstalled();
26
27
  const email = await fly.auth.login({ interactive: !opts.json });
27
28
  out.ok(`Authenticated as ${email}`);
@@ -44,18 +45,9 @@ const handleAclSetFailure = (out, result, action) => {
44
45
  const stageTailscaleConfig = async (out, opts) => {
45
46
  out.header("Step 2: Tailscale Configuration").blank();
46
47
  const credentials = getCredentialStore();
47
- let apiKey = opts.apiKey || (await credentials.getTailscaleApiKey());
48
+ const apiKey = await credentials.getTailscaleApiKey();
48
49
  if (!apiKey) {
49
- if (opts.json) {
50
- return out.die("--api-key Is Required in JSON Mode");
51
- }
52
- out.dim("Ambit Needs an API Access Token (Not an Auth Key) to Manage Your Tailnet.")
53
- .dim("Create One at:").link(" https://login.tailscale.com/admin/settings/keys")
54
- .blank();
55
- apiKey = await readSecret("API access token (tskey-api-...): ");
56
- if (!apiKey) {
57
- return out.die("Tailscale API Access Token Required");
58
- }
50
+ return out.die("Tailscale API Key Required. Run 'ambit auth login'");
59
51
  }
60
52
  if (!apiKey.startsWith(TAILSCALE_API_KEY_PREFIX)) {
61
53
  return out.die("Invalid Token Format. Expected 'tskey-api-...' (API Access Token, Not Auth Key)");
@@ -68,7 +60,6 @@ const stageTailscaleConfig = async (out, opts) => {
68
60
  return out.die("Failed to Validate Tailscale API Access Token");
69
61
  }
70
62
  validateSpinner.success("API Access Token Validated");
71
- await credentials.setTailscaleApiKey(apiKey);
72
63
  const tagOwnerSpinner = out.spinner(`Checking tagOwners for ${opts.tag}`);
73
64
  let policy = await tailscale.acl.getPolicy();
74
65
  const hasTagOwner = isTagOwnerConfigured(policy, opts.tag);
@@ -240,7 +231,7 @@ const stageSummary = async (out, _fly, tailscale, ctx, opts) => {
240
231
  // =============================================================================
241
232
  const create = async (argv) => {
242
233
  const opts = {
243
- string: ["org", "region", "api-key", "tag"],
234
+ string: ["org", "region", "tag"],
244
235
  boolean: ["help", "yes", "json", "no-auto-approve", "manual"],
245
236
  alias: { y: "yes" },
246
237
  };
@@ -256,7 +247,6 @@ ${bold("USAGE")}
256
247
  ${bold("OPTIONS")}
257
248
  --org <org> Fly.io organization slug
258
249
  --region <region> Fly.io region (default: iad)
259
- --api-key <key> Tailscale API access token (tskey-api-...)
260
250
  --tag <tag> Tailscale ACL tag for the router (default: tag:ambit-<network>)
261
251
  --manual Skip automatic Tailscale ACL configuration (tagOwners + autoApprovers)
262
252
  --no-auto-approve Skip waiting for router and approving routes
@@ -305,7 +295,6 @@ ${bold("EXAMPLES")}
305
295
  const tailscale = await stageTailscaleConfig(out, {
306
296
  json: args.json,
307
297
  manual,
308
- apiKey: args["api-key"],
309
298
  tag,
310
299
  network,
311
300
  });
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/cli/mod.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAQD,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,KAAG,IAElD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAO,GAAG,SAEnD,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,OAAO,EAExC,CAAC;AAQF,eAAO,MAAM,QAAQ,QAAO,IAiC3B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAMF,eAAO,MAAM,MAAM,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAuCzD,CAAC"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/cli/mod.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAQD,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,KAAG,IAElD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAO,GAAG,SAEnD,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,OAAO,EAExC,CAAC;AAQF,eAAO,MAAM,QAAQ,QAAO,IAkC3B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAMF,eAAO,MAAM,MAAM,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAuCzD,CAAC"}
package/esm/cli/mod.js CHANGED
@@ -30,6 +30,7 @@ ${bold("USAGE")}
30
30
  ambit <command> [options]
31
31
 
32
32
  ${bold("COMMANDS")}
33
+ auth Manage Fly.io and Tailscale authentication
33
34
  create Create a Tailscale subnet router on a Fly.io custom network
34
35
  deploy Deploy an app safely on a custom private network
35
36
  share Grant a Tailscale group access to a network via ACL rules
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Deploy apps to the cloud that only you and your AI agents can reach",
5
5
  "license": "MIT",
6
6
  "tasks": {
@@ -2,7 +2,7 @@
2
2
  * The character used to separate entries in the PATH environment variable.
3
3
  * On Windows, this is `;`. On all other platforms, this is `:`.
4
4
  */
5
- export declare const DELIMITER: ";" | ":";
5
+ export declare const DELIMITER: ":" | ";";
6
6
  /**
7
7
  * The character used to separate components of a file path.
8
8
  * On Windows, this is `\`. On all other platforms, this is `/`.
package/esm/main.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import "./_dnt.polyfills.js";
3
+ import "./cli/commands/auth.js";
3
4
  import "./cli/commands/create/index.js";
4
5
  import "./cli/commands/deploy/index.js";
5
6
  import "./cli/commands/share.js";
package/esm/main.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,qBAAqB,CAAC;AAoC7B,OAAO,gCAAgC,CAAC;AACxC,OAAO,gCAAgC,CAAC;AACxC,OAAO,yBAAyB,CAAC;AACjC,OAAO,8BAA8B,CAAC;AACtC,OAAO,gCAAgC,CAAC;AACxC,OAAO,iCAAiC,CAAC;AACzC,OAAO,0BAA0B,CAAC;AAClC,OAAO,2BAA2B,CAAC;AACnC,OAAO,wBAAwB,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,qBAAqB,CAAC;AAoC7B,OAAO,wBAAwB,CAAC;AAChC,OAAO,gCAAgC,CAAC;AACxC,OAAO,gCAAgC,CAAC;AACxC,OAAO,yBAAyB,CAAC;AACjC,OAAO,8BAA8B,CAAC;AACtC,OAAO,gCAAgC,CAAC;AACxC,OAAO,iCAAiC,CAAC;AACzC,OAAO,0BAA0B,CAAC;AAClC,OAAO,2BAA2B,CAAC;AACnC,OAAO,wBAAwB,CAAC"}
package/esm/main.js CHANGED
@@ -32,6 +32,7 @@ import * as dntShim from "./_dnt.shims.js";
32
32
  // =============================================================================
33
33
  import { runCli } from "./cli/mod.js";
34
34
  import { Spinner, statusErr } from "./lib/cli.js";
35
+ import "./cli/commands/auth.js";
35
36
  import "./cli/commands/create/index.js";
36
37
  import "./cli/commands/deploy/index.js";
37
38
  import "./cli/commands/share.js";
@@ -85,5 +85,5 @@ export interface FlyProvider {
85
85
  app(app: string, options: SafeDeployOptions): Promise<void>;
86
86
  };
87
87
  }
88
- export declare const createFlyProvider: () => FlyProvider;
88
+ export declare const createFlyProvider: (token?: string) => FlyProvider;
89
89
  //# sourceMappingURL=fly.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../src/providers/fly.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../src/providers/fly.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EAIf,KAAK,KAAK,EAEV,KAAK,UAAU,EAIhB,MAAM,mBAAmB,CAAC;AAa3B;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAMxC;AAMD,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;AAE9E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE;QACJ,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,EAAE;YAAE,WAAW,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACzD,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;KAC7B,CAAC;IACF,IAAI,EAAE;QACJ,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;KACzC,CAAC;IACF,IAAI,EAAE;QACJ,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACtC,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QACpD,MAAM,CACJ,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAAC;QACjB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACvC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;KAClE,CAAC;IACF,QAAQ,EAAE;QACR,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QACzC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;QAClE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACxD,CAAC;IACF,OAAO,EAAE;QACP,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC,CAAC;QACpE,GAAG,CACD,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CAAC;QACjB,KAAK,CACH,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CAAC;QACjB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACpC,CAAC;IACF,GAAG,EAAE;QACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACrD,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC9D,CAAC;IACF,KAAK,EAAE;QACL,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACtD,CAAC;IACF,MAAM,EAAE;QACN,MAAM,CACJ,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;QACjB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC7D,CAAC;CACH;AAMD,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,KAAG,WAogBlD,CAAC"}
@@ -31,7 +31,14 @@ export class FlyDeployError extends Error {
31
31
  // =============================================================================
32
32
  // Create Fly Provider
33
33
  // =============================================================================
34
- export const createFlyProvider = () => {
34
+ export const createFlyProvider = (token) => {
35
+ const envOverride = token ? { FLY_API_TOKEN: token } : undefined;
36
+ const run = (args, opts) => runCommand(args, envOverride
37
+ ? { ...opts, env: { ...opts?.env, ...envOverride } }
38
+ : opts);
39
+ const runJ = (args, opts) => runJson(args, envOverride
40
+ ? { ...opts, env: { ...opts?.env, ...envOverride } }
41
+ : opts);
35
42
  const provider = {
36
43
  auth: {
37
44
  async ensureInstalled() {
@@ -41,7 +48,7 @@ export const createFlyProvider = () => {
41
48
  },
42
49
  async login(opts) {
43
50
  const interactive = opts?.interactive ?? true;
44
- const result = await runCommand(["fly", "auth", "whoami", "--json"]);
51
+ const result = await run(["fly", "auth", "whoami", "--json"]);
45
52
  if (result.ok) {
46
53
  const auth = result.json();
47
54
  if (auth.ok) {
@@ -52,15 +59,15 @@ export const createFlyProvider = () => {
52
59
  }
53
60
  }
54
61
  if (!interactive) {
55
- return die("Not Authenticated with Fly.io. Run 'fly auth login' First");
62
+ return die("Not Authenticated with Fly.io. Run 'ambit auth login' First");
56
63
  }
57
- const loginResult = await runCommand(["fly", "auth", "login"], {
64
+ const loginResult = await run(["fly", "auth", "login"], {
58
65
  interactive: true,
59
66
  });
60
67
  if (!loginResult.ok) {
61
68
  return die("Fly.io Authentication Failed");
62
69
  }
63
- const checkResult = await runCommand([
70
+ const checkResult = await run([
64
71
  "fly",
65
72
  "auth",
66
73
  "whoami",
@@ -76,22 +83,24 @@ export const createFlyProvider = () => {
76
83
  return parsed.data.email;
77
84
  },
78
85
  async getToken() {
86
+ if (token)
87
+ return token;
79
88
  const home = dntShim.Deno.env.get("HOME") || dntShim.Deno.env.get("USERPROFILE") || "";
80
89
  const configPath = `${home}/.fly/config.yml`;
81
90
  if (!(await fileExists(configPath))) {
82
- return die("Fly Config Not Found at ~/.fly/config.yml. Run 'fly auth login' First");
91
+ return die("Fly Config Not Found at ~/.fly/config.yml. Run 'ambit auth login' First");
83
92
  }
84
93
  const content = await dntShim.Deno.readTextFile(configPath);
85
94
  const match = content.match(/access_token:\s*(.+)/);
86
95
  if (!match || !match[1]) {
87
- return die("No Access Token Found in ~/.fly/config.yml. Run 'fly auth login' First");
96
+ return die("No Access Token Found in ~/.fly/config.yml. Run 'ambit auth login' First");
88
97
  }
89
98
  return match[1].trim();
90
99
  },
91
100
  },
92
101
  orgs: {
93
102
  async list() {
94
- const result = await runJson(["fly", "orgs", "list", "--json"]);
103
+ const result = await runJ(["fly", "orgs", "list", "--json"]);
95
104
  if (!result.ok) {
96
105
  return die("Failed to List Organizations");
97
106
  }
@@ -108,7 +117,7 @@ export const createFlyProvider = () => {
108
117
  if (org) {
109
118
  args.push("--org", org);
110
119
  }
111
- const result = await runCommand(args);
120
+ const result = await run(args);
112
121
  return result.json().flatMap((data) => {
113
122
  const parsed = FlyAppsListSchema.safeParse(data);
114
123
  return parsed.success
@@ -117,10 +126,10 @@ export const createFlyProvider = () => {
117
126
  }).unwrapOr([]);
118
127
  },
119
128
  async listWithNetwork(org) {
120
- const token = await provider.auth.getToken();
129
+ const apiToken = await provider.auth.getToken();
121
130
  const response = await fetch(`https://api.machines.dev/v1/apps?org_slug=${encodeURIComponent(org)}`, {
122
131
  headers: {
123
- Authorization: `Bearer ${token}`,
132
+ Authorization: `Bearer ${apiToken}`,
124
133
  "Content-Type": "application/json",
125
134
  },
126
135
  });
@@ -142,13 +151,13 @@ export const createFlyProvider = () => {
142
151
  if (opts?.network) {
143
152
  args.push("--network", opts.network);
144
153
  }
145
- const result = await runCommand(args);
154
+ const result = await run(args);
146
155
  if (!result.ok) {
147
156
  return die(`Failed to Create App '${appName}'`);
148
157
  }
149
158
  },
150
159
  async delete(name) {
151
- const result = await runCommand([
160
+ const result = await run([
152
161
  "fly",
153
162
  "apps",
154
163
  "destroy",
@@ -160,7 +169,7 @@ export const createFlyProvider = () => {
160
169
  }
161
170
  },
162
171
  async exists(name) {
163
- const result = await runCommand([
172
+ const result = await run([
164
173
  "fly",
165
174
  "status",
166
175
  "-a",
@@ -176,7 +185,7 @@ export const createFlyProvider = () => {
176
185
  });
177
186
  },
178
187
  async getConfig(name) {
179
- const result = await runCommand(["fly", "config", "show", "-a", name]);
188
+ const result = await run(["fly", "config", "show", "-a", name]);
180
189
  return result.json().match({
181
190
  ok: (v) => v,
182
191
  err: () => null,
@@ -185,7 +194,7 @@ export const createFlyProvider = () => {
185
194
  },
186
195
  machines: {
187
196
  async list(app) {
188
- const result = await runJson(["fly", "machines", "list", "-a", app, "--json"]);
197
+ const result = await runJ(["fly", "machines", "list", "-a", app, "--json"]);
189
198
  return result.flatMap((data) => {
190
199
  const parsed = FlyMachinesListSchema.safeParse(data);
191
200
  return parsed.success
@@ -217,7 +226,7 @@ export const createFlyProvider = () => {
217
226
  if (config.region) {
218
227
  args.push("--region", config.region);
219
228
  }
220
- const result = await runCommand(args);
229
+ const result = await run(args);
221
230
  if (!result.ok) {
222
231
  return die(result.stderr || "Unknown Error");
223
232
  }
@@ -231,7 +240,7 @@ export const createFlyProvider = () => {
231
240
  },
232
241
  async destroy(app, machineId) {
233
242
  const shortId = machineId.slice(0, 8);
234
- const result = await runCommand([
243
+ const result = await run([
235
244
  "fly",
236
245
  "machines",
237
246
  "destroy",
@@ -247,7 +256,7 @@ export const createFlyProvider = () => {
247
256
  },
248
257
  secrets: {
249
258
  async list(app) {
250
- const result = await runJson(["fly", "secrets", "list", "-a", app, "--json"]);
259
+ const result = await runJ(["fly", "secrets", "list", "-a", app, "--json"]);
251
260
  return result.unwrapOr([]);
252
261
  },
253
262
  async set(app, secrets, opts) {
@@ -260,7 +269,7 @@ export const createFlyProvider = () => {
260
269
  if (opts?.stage) {
261
270
  args.push("--stage");
262
271
  }
263
- const result = await runCommand(args);
272
+ const result = await run(args);
264
273
  if (!result.ok) {
265
274
  return die("Failed to Set Secrets");
266
275
  }
@@ -272,13 +281,13 @@ export const createFlyProvider = () => {
272
281
  if (opts?.stage) {
273
282
  args.push("--stage");
274
283
  }
275
- const result = await runCommand(args);
284
+ const result = await run(args);
276
285
  if (!result.ok) {
277
286
  return die("Failed to Unset Secrets");
278
287
  }
279
288
  },
280
289
  async deploy(app) {
281
- const result = await runCommand([
290
+ const result = await run([
282
291
  "fly",
283
292
  "secrets",
284
293
  "deploy",
@@ -292,7 +301,7 @@ export const createFlyProvider = () => {
292
301
  },
293
302
  ips: {
294
303
  async list(app) {
295
- const result = await runCommand([
304
+ const result = await run([
296
305
  "fly",
297
306
  "ips",
298
307
  "list",
@@ -308,7 +317,7 @@ export const createFlyProvider = () => {
308
317
  }).unwrapOr([]);
309
318
  },
310
319
  async release(app, address) {
311
- const result = await runCommand([
320
+ const result = await run([
312
321
  "fly",
313
322
  "ips",
314
323
  "release",
@@ -321,7 +330,7 @@ export const createFlyProvider = () => {
321
330
  }
322
331
  },
323
332
  async allocateFlycast(app, network) {
324
- const result = await runCommand([
333
+ const result = await run([
325
334
  "fly",
326
335
  "ips",
327
336
  "allocate-v6",
@@ -338,7 +347,7 @@ export const createFlyProvider = () => {
338
347
  },
339
348
  certs: {
340
349
  async list(app) {
341
- const result = await runCommand([
350
+ const result = await run([
342
351
  "fly",
343
352
  "certs",
344
353
  "list",
@@ -351,7 +360,7 @@ export const createFlyProvider = () => {
351
360
  .filter((h) => typeof h === "string")).unwrapOr([]);
352
361
  },
353
362
  async remove(app, hostname) {
354
- await runCommand([
363
+ await run([
355
364
  "fly",
356
365
  "certs",
357
366
  "remove",
@@ -376,7 +385,7 @@ export const createFlyProvider = () => {
376
385
  if (config?.region) {
377
386
  args.push("--primary-region", config.region);
378
387
  }
379
- const result = await runCommand(args);
388
+ const result = await run(args);
380
389
  if (!result.ok) {
381
390
  throw new FlyDeployError(app, result.stderr);
382
391
  }
@@ -400,7 +409,7 @@ export const createFlyProvider = () => {
400
409
  if (options.region) {
401
410
  args.push("--primary-region", options.region);
402
411
  }
403
- const result = await runCommand(args);
412
+ const result = await run(args);
404
413
  if (!result.ok) {
405
414
  throw new FlyDeployError(flyAppName, result.stderr);
406
415
  }
@@ -18,8 +18,8 @@ export type FlyAppStatus = z.infer<typeof FlyAppStatusEnum>;
18
18
  * Terminal: destroyed, replaced, migrated
19
19
  */
20
20
  export declare const FlyMachineStateEnum: z.ZodEnum<{
21
- suspended: "suspended";
22
21
  created: "created";
22
+ suspended: "suspended";
23
23
  started: "started";
24
24
  stopped: "stopped";
25
25
  failed: "failed";
@@ -85,8 +85,8 @@ export declare const FlyMachineSchema: z.ZodObject<{
85
85
  id: z.ZodString;
86
86
  name: z.ZodString;
87
87
  state: z.ZodCatch<z.ZodEnum<{
88
- suspended: "suspended";
89
88
  created: "created";
89
+ suspended: "suspended";
90
90
  started: "started";
91
91
  stopped: "stopped";
92
92
  failed: "failed";
@@ -130,8 +130,8 @@ export declare const FlyMachinesListSchema: z.ZodArray<z.ZodObject<{
130
130
  id: z.ZodString;
131
131
  name: z.ZodString;
132
132
  state: z.ZodCatch<z.ZodEnum<{
133
- suspended: "suspended";
134
133
  created: "created";
134
+ suspended: "suspended";
135
135
  started: "started";
136
136
  stopped: "stopped";
137
137
  failed: "failed";
@@ -4,6 +4,7 @@ export declare const ROUTER_DOCKER_DIR: string;
4
4
  export declare const SOCKS_PROXY_PORT = 1080;
5
5
  export declare const TAILSCALE_API_KEY_PREFIX = "tskey-api-";
6
6
  export declare const ENV_TAILSCALE_API_KEY = "TAILSCALE_API_KEY";
7
+ export declare const ENV_FLY_API_TOKEN = "FLY_API_TOKEN";
7
8
  export declare const FLYCTL_INSTALL_URL = "https://fly.io/docs/flyctl/install/";
8
9
  export declare const SECRET_TAILSCALE_AUTHKEY = "TAILSCALE_AUTHKEY";
9
10
  export declare const SECRET_NETWORK_NAME = "NETWORK_NAME";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/util/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C,eAAO,MAAM,iBAAiB,QACnB,CAAC;AAMZ,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAMrC,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAMzD,eAAO,MAAM,kBAAkB,wCAAwC,CAAC;AAMxE,eAAO,MAAM,wBAAwB,sBAAsB,CAAC;AAC5D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAM5C,eAAO,MAAM,2BAA2B,yBAAyB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/util/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C,eAAO,MAAM,iBAAiB,QACnB,CAAC;AAMZ,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAMrC,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAMjD,eAAO,MAAM,kBAAkB,wCAAwC,CAAC;AAMxE,eAAO,MAAM,wBAAwB,sBAAsB,CAAC;AAC5D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAM5C,eAAO,MAAM,2BAA2B,yBAAyB,CAAC"}
@@ -17,6 +17,7 @@ export const SOCKS_PROXY_PORT = 1080;
17
17
  // =============================================================================
18
18
  export const TAILSCALE_API_KEY_PREFIX = "tskey-api-";
19
19
  export const ENV_TAILSCALE_API_KEY = "TAILSCALE_API_KEY";
20
+ export const ENV_FLY_API_TOKEN = "FLY_API_TOKEN";
20
21
  // =============================================================================
21
22
  // External URLs
22
23
  // =============================================================================
@@ -1,6 +1,9 @@
1
1
  export interface CredentialStore {
2
2
  getTailscaleApiKey(): Promise<string | null>;
3
3
  setTailscaleApiKey(key: string): Promise<void>;
4
+ getFlyToken(): Promise<string | null>;
5
+ setFlyToken(token: string): Promise<void>;
6
+ clear(): Promise<void>;
4
7
  }
5
8
  export declare const createConfigCredentialStore: () => CredentialStore;
6
9
  export declare const getCredentialStore: () => CredentialStore;
@@ -17,5 +20,6 @@ export declare const checkDependencies: (out: {
17
20
  die(msg: string): never;
18
21
  }) => Promise<{
19
22
  tailscaleKey: string;
23
+ flyToken: string | null;
20
24
  }>;
21
25
  //# sourceMappingURL=credentials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/util/credentials.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,eAAe;IAC9B,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,eAAO,MAAM,2BAA2B,QAAO,eA0B9C,CAAC;AAMF,eAAO,MAAM,kBAAkB,QAAO,eAerC,CAAC;AAMF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC5B,KAAK;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,KAC1D,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,CAyBlC,CAAC"}
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/util/credentials.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,eAAe;IAC9B,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA+BD,eAAO,MAAM,2BAA2B,QAAO,eAiC9C,CAAC;AAMF,eAAO,MAAM,kBAAkB,QAAO,eA8BrC,CAAC;AAMF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC5B,KAAK;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,KAC1D,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAoD3D,CAAC"}
@@ -1,41 +1,68 @@
1
1
  // =============================================================================
2
- // Credential Store - Persistent Tailscale API Key Storage
2
+ // Credential Store - Persistent Tailscale & Fly.io Token Storage
3
3
  // =============================================================================
4
4
  import * as dntShim from "../_dnt.shims.js";
5
5
  import { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
6
6
  import { commandExists, ensureConfigDir, fileExists, getConfigDir, } from "../lib/cli.js";
7
- import { ENV_TAILSCALE_API_KEY } from "./constants.js";
7
+ import { ENV_FLY_API_TOKEN, ENV_TAILSCALE_API_KEY } from "./constants.js";
8
8
  // =============================================================================
9
9
  // Schema
10
10
  // =============================================================================
11
11
  const CredentialsSchema = z.object({
12
- apiKey: z.string(),
12
+ apiKey: z.string().optional(),
13
+ flyToken: z.string().optional(),
13
14
  });
14
15
  // =============================================================================
15
16
  // Config File Implementation
16
17
  // =============================================================================
17
18
  const getCredentialsPath = () => `${getConfigDir()}/credentials.json`;
19
+ const readCredentials = async () => {
20
+ const path = getCredentialsPath();
21
+ if (!(await fileExists(path)))
22
+ return {};
23
+ try {
24
+ const content = await dntShim.Deno.readTextFile(path);
25
+ const result = CredentialsSchema.safeParse(JSON.parse(content));
26
+ return result.success ? result.data : {};
27
+ }
28
+ catch {
29
+ return {};
30
+ }
31
+ };
32
+ const writeCredentials = async (data) => {
33
+ await ensureConfigDir();
34
+ const path = getCredentialsPath();
35
+ await dntShim.Deno.writeTextFile(path, JSON.stringify(data, null, 2) + "\n");
36
+ };
18
37
  export const createConfigCredentialStore = () => {
19
38
  return {
20
39
  async getTailscaleApiKey() {
40
+ const data = await readCredentials();
41
+ return data.apiKey ?? null;
42
+ },
43
+ async setTailscaleApiKey(key) {
44
+ const data = await readCredentials();
45
+ data.apiKey = key;
46
+ await writeCredentials(data);
47
+ },
48
+ async getFlyToken() {
49
+ const data = await readCredentials();
50
+ return data.flyToken ?? null;
51
+ },
52
+ async setFlyToken(token) {
53
+ const data = await readCredentials();
54
+ data.flyToken = token;
55
+ await writeCredentials(data);
56
+ },
57
+ async clear() {
21
58
  const path = getCredentialsPath();
22
- if (!(await fileExists(path))) {
23
- return null;
24
- }
25
59
  try {
26
- const content = await dntShim.Deno.readTextFile(path);
27
- const result = CredentialsSchema.safeParse(JSON.parse(content));
28
- return result.success ? result.data.apiKey : null;
60
+ await dntShim.Deno.remove(path);
29
61
  }
30
62
  catch {
31
- return null;
63
+ // File may not exist
32
64
  }
33
65
  },
34
- async setTailscaleApiKey(key) {
35
- await ensureConfigDir();
36
- const path = getCredentialsPath();
37
- await dntShim.Deno.writeTextFile(path, JSON.stringify({ apiKey: key }, null, 2) + "\n");
38
- },
39
66
  };
40
67
  };
41
68
  // =============================================================================
@@ -53,6 +80,18 @@ export const getCredentialStore = () => {
53
80
  async setTailscaleApiKey(key) {
54
81
  await fileStore.setTailscaleApiKey(key);
55
82
  },
83
+ async getFlyToken() {
84
+ const envToken = dntShim.Deno.env.get(ENV_FLY_API_TOKEN);
85
+ if (envToken)
86
+ return envToken;
87
+ return await fileStore.getFlyToken();
88
+ },
89
+ async setFlyToken(token) {
90
+ await fileStore.setFlyToken(token);
91
+ },
92
+ async clear() {
93
+ await fileStore.clear();
94
+ },
56
95
  };
57
96
  };
58
97
  // =============================================================================
@@ -71,9 +110,33 @@ export const checkDependencies = async (out) => {
71
110
  if (!(await commandExists("fly"))) {
72
111
  errors.push("Flyctl Not Found. Install from https://fly.io/docs/flyctl/install/");
73
112
  }
74
- const key = await getCredentialStore().getTailscaleApiKey();
113
+ const credentials = getCredentialStore();
114
+ const key = await credentials.getTailscaleApiKey();
75
115
  if (!key) {
76
- errors.push("Tailscale API Key Required. Run 'ambit create' or set TAILSCALE_API_KEY");
116
+ errors.push("Tailscale API Key Required. Run 'ambit auth login' or set TAILSCALE_API_KEY");
117
+ }
118
+ let flyToken = await credentials.getFlyToken();
119
+ if (!flyToken) {
120
+ // Adopt token from flyctl's own config if available
121
+ const home = dntShim.Deno.env.get("HOME") || dntShim.Deno.env.get("USERPROFILE") || "";
122
+ const configPath = `${home}/.fly/config.yml`;
123
+ if (await fileExists(configPath)) {
124
+ try {
125
+ const content = await dntShim.Deno.readTextFile(configPath);
126
+ const match = content.match(/access_token:\s*(.+)/);
127
+ if (match?.[1]) {
128
+ const adopted = match[1].trim();
129
+ await credentials.setFlyToken(adopted);
130
+ flyToken = adopted;
131
+ }
132
+ }
133
+ catch {
134
+ // Ignore read errors
135
+ }
136
+ }
137
+ }
138
+ if (!flyToken) {
139
+ errors.push("Fly.io Token Required. Run 'ambit auth login' or set FLY_API_TOKEN");
77
140
  }
78
141
  if (errors.length === 1) {
79
142
  return out.die(errors[0]);
@@ -83,5 +146,5 @@ export const checkDependencies = async (out) => {
83
146
  out.err(e);
84
147
  return out.die("Missing Prerequisites");
85
148
  }
86
- return { tailscaleKey: key };
149
+ return { tailscaleKey: key, flyToken };
87
150
  };
@@ -10,8 +10,8 @@ import { resolveOrg } from "./resolve.js";
10
10
  * validates fly CLI + Tailscale key, authenticates with Fly, and resolves org.
11
11
  */
12
12
  export const initSession = async (out, opts) => {
13
- const { tailscaleKey } = await checkDependencies(out);
14
- const fly = createFlyProvider();
13
+ const { tailscaleKey, flyToken } = await checkDependencies(out);
14
+ const fly = createFlyProvider(flyToken ?? undefined);
15
15
  await fly.auth.login({ interactive: !opts.json });
16
16
  const tailscale = createTailscaleProvider(tailscaleKey);
17
17
  const org = await resolveOrg(fly, opts, out);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Deploy apps to the cloud that only you and your AI agents can reach",
5
5
  "license": "MIT",
6
6
  "scripts": {},