@cardelli/ambit 0.2.1 → 0.2.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.
@@ -12,7 +12,7 @@ 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, SOCKS_PROXY_PORT, TAILSCALE_API_KEY_PREFIX, } from "../../../util/constants.js";
15
+ import { FLY_PRIVATE_SUBNET, TAILSCALE_API_KEY_PREFIX, } from "../../../util/constants.js";
16
16
  import { resolveOrg } from "../../../util/resolve.js";
17
17
  import { isAutoApproverConfigured, isTagOwnerConfigured } from "../../../util/tailscale-local.js";
18
18
  import { createTransition, hydrateCreate, reportSkipped, } from "./machine.js";
@@ -43,7 +43,7 @@ const stageTailscaleConfig = async (out, opts) => {
43
43
  return out.die("--api-key Is Required in JSON Mode");
44
44
  }
45
45
  out.dim("Ambit Needs an API Access Token (Not an Auth Key) to Manage Your Tailnet.")
46
- .dim("Create One at: https://login.tailscale.com/admin/settings/keys")
46
+ .dim("Create One at:").link(" https://login.tailscale.com/admin/settings/keys")
47
47
  .blank();
48
48
  apiKey = await readSecret("API access token (tskey-api-...): ");
49
49
  if (!apiKey) {
@@ -66,35 +66,39 @@ const stageTailscaleConfig = async (out, opts) => {
66
66
  const policy = await tailscale.acl.getPolicy();
67
67
  const hasTagOwner = isTagOwnerConfigured(policy, opts.tag);
68
68
  if (!hasTagOwner) {
69
- tagOwnerSpinner.fail(`Tag ${opts.tag} Not Configured in tagOwners`);
69
+ tagOwnerSpinner.fail(`${opts.tag} Not Set Up Yet`);
70
70
  out.blank()
71
- .text(` The Tag ${opts.tag} Does Not Exist in Your Tailscale ACL tagOwners.`)
72
- .text(" Tailscale Will Reject Auth Keys for Undefined Tags.")
71
+ .text(` You need to grant yourself permission to create the "${opts.network}"`)
72
+ .text(` network by setting up ${opts.tag} in Tailscale.`)
73
73
  .blank()
74
- .text(" Add This Tag in Your Tailscale ACL Settings:")
75
- .dim(" https://login.tailscale.com/admin/acls/visual/tags")
74
+ .text(` You can create the tag and assign it to yourself or a group`)
75
+ .text(` you're a part of in the Tailscale dashboard:`)
76
+ .link(" https://login.tailscale.com/admin/acls/visual/tags")
76
77
  .blank()
78
+ .dim(" Or you can do it manually in the JSON editor:")
79
+ .link(" https://login.tailscale.com/admin/acls/file")
77
80
  .dim(` "tagOwners": { "${opts.tag}": ["autogroup:admin"] }`)
78
81
  .blank();
79
- return out.die(`Add ${opts.tag} to tagOwners Before Creating Router`);
82
+ return out.die(`Set Up ${opts.tag} in Tailscale, Then Try Again`);
80
83
  }
81
- tagOwnerSpinner.success(`Tag ${opts.tag} Configured in tagOwners`);
84
+ tagOwnerSpinner.success(`${opts.tag} Found in Tailscale ACL`);
82
85
  if (opts.json) {
83
86
  const approverSpinner = out.spinner(`Checking autoApprovers for ${opts.tag}`);
84
87
  const hasApprover = isAutoApproverConfigured(policy, opts.tag);
85
88
  if (!hasApprover) {
86
- approverSpinner.fail(`autoApprovers Not Configured for ${opts.tag}`);
89
+ approverSpinner.fail(`Auto-approve Not Configured for ${opts.tag}`);
87
90
  out.blank()
88
- .text(" JSON mode skips interactive route approval.")
89
- .text(` Configure autoApprovers for ${opts.tag} so routes are approved on deploy.`)
91
+ .text(" In JSON mode, ambit can't interactively approve subnet routes.")
92
+ .text(` Add an autoApprovers rule so Tailscale automatically trusts`)
93
+ .text(` routes advertised by ${opts.tag}:`)
90
94
  .blank()
91
- .dim(" Add to your ACL at: https://login.tailscale.com/admin/acls/file")
95
+ .dim(" https://login.tailscale.com/admin/acls/file")
92
96
  .blank()
93
97
  .dim(` "autoApprovers": { "routes": { "${FLY_PRIVATE_SUBNET}": ["${opts.tag}"] } }`)
94
98
  .blank();
95
- return out.die(`Configure autoApprovers for ${opts.tag} Before Using --json`);
99
+ return out.die(`Add autoApprovers for ${opts.tag} to Use --json`);
96
100
  }
97
- approverSpinner.success(`autoApprovers Configured for ${opts.tag}`);
101
+ approverSpinner.success(`Auto-approve Configured for ${opts.tag}`);
98
102
  }
99
103
  out.blank();
100
104
  return tailscale;
@@ -157,52 +161,52 @@ const stageSummary = async (out, fly, tailscale, ctx, opts) => {
157
161
  .header(" Router Created!")
158
162
  .header("=".repeat(50))
159
163
  .blank()
160
- .text(`Any Flycast App on the "${opts.network}" Network Is Reachable as:`)
161
- .text(` <app-name>.${opts.network}`)
162
- .blank();
163
- if (ctx.subnet) {
164
- const machines = await fly.machines.list(ctx.appName);
165
- const routerMachine = machines.find((m) => m.private_ip);
166
- if (routerMachine?.private_ip) {
167
- out.text("SOCKS5 Proxy Available at:")
168
- .text(` socks5://[${routerMachine.private_ip}]:${SOCKS_PROXY_PORT}`)
169
- .dim("Containers on This Network Can Use It to Reach Your Tailnet.")
170
- .blank();
171
- }
172
- }
173
- out.dim("Deploy an App to This Network:")
174
- .dim(` ambit deploy my-app --network ${opts.network}`)
164
+ .text(`The "${opts.network}" Network Is Ready.`)
175
165
  .blank()
176
- .dim("Invite People to Your Tailnet:")
177
- .dim(" https://login.tailscale.com/admin/users")
178
- .dim("Control Their Access:")
179
- .dim(" https://login.tailscale.com/admin/acls/visual/general-access-rules")
166
+ .text(`You Can Deploy Apps to It With:`)
167
+ .text(` npx @cardelli/ambit deploy <app-name>.${opts.network}`)
168
+ .blank();
169
+ out.dim("Invite People to Your Tailnet:")
170
+ .link(" https://login.tailscale.com/admin/users")
180
171
  .blank();
181
- if (ctx.subnet && !hasAutoApprover) {
182
- out.header("Recommended: Configure autoApprovers")
183
- .blank()
184
- .dim(" Add to Your Tailnet Policy File at:")
185
- .dim(" https://login.tailscale.com/admin/acls/file")
186
- .blank()
187
- .text(` "autoApprovers": { "routes": { "${ctx.subnet}": ["${opts.tag}"] } }`)
188
- .blank()
189
- .dim(" Routes Were Approved via API for This Session.")
190
- .dim(" autoApprovers Will Auto-Approve on Future Restarts.")
191
- .blank();
192
- }
193
172
  if (ctx.subnet) {
194
- out.header("Recommended ACL Rules:")
173
+ out.header("Next: Allow Traffic Through the Router")
174
+ .blank()
175
+ .text(" You must configure Tailscale so that traffic can flow from your")
176
+ .text(` devices through the router and into the "${opts.network}" network.`);
177
+ if (!hasAutoApprover) {
178
+ out.blank()
179
+ .text(" 1. Automatically Allow Traffic Through the Router:")
180
+ .dim(" Tailscale needs to trust the router's network connections.")
181
+ .blank()
182
+ .dim(" You can do this from the Tailscale dashboard:")
183
+ .link(" https://login.tailscale.com/admin/acls/visual/auto-approvers")
184
+ .dim(` Route: ${ctx.subnet} Owner: ${opts.tag}`)
185
+ .blank()
186
+ .dim(" Or you can do it manually with this JSON config:")
187
+ .link(" https://login.tailscale.com/admin/acls/file")
188
+ .dim(` "autoApprovers": { "routes": { "${ctx.subnet}": ["${opts.tag}"] } }`);
189
+ if (opts.shouldApprove) {
190
+ out.blank().dim(" Traffic Was Allowed via API for This Session.");
191
+ }
192
+ }
193
+ out.blank()
194
+ .text(` ${hasAutoApprover ? "1" : "2"}. Control Who Can Reach Apps on This Network:`)
195
+ .dim(" This lets you restrict which users or devices can access your apps.")
195
196
  .blank()
196
- .dim(" To Restrict Access, Add ACL Rules to Your Policy File:")
197
- .dim(" https://login.tailscale.com/admin/acls/file")
197
+ .dim(" You can do this from the Tailscale dashboard:")
198
+ .link(" https://login.tailscale.com/admin/acls/visual/general-access-rules")
199
+ .dim(` Source: group:YOUR_GROUP Destination: ${opts.tag}:*`)
198
200
  .blank()
199
- .dim(` {"action": "accept", "src": ["group:YOUR_GROUP"], "dst": ["${opts.tag}:53"]}`)
200
- .dim(` {"action": "accept", "src": ["group:YOUR_GROUP"], "dst": ["${ctx.subnet}:*"]}`)
201
+ .dim(" Or you can do it manually with this JSON config:")
202
+ .link(" https://login.tailscale.com/admin/acls/file")
203
+ .dim(` {"action": "accept", "src": ["group:YOUR_GROUP"], "dst": ["${opts.tag}:53"]}`)
204
+ .dim(` {"action": "accept", "src": ["group:YOUR_GROUP"], "dst": ["${ctx.subnet}:*"]}`)
201
205
  .blank();
202
206
  }
203
207
  if (!opts.shouldApprove) {
204
208
  out.dim("Route Approval Was Skipped. To Complete Setup:")
205
- .dim(` ambit doctor --network ${opts.network}`)
209
+ .dim(` ambit doctor network ${opts.network}`)
206
210
  .blank();
207
211
  }
208
212
  out.print();
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Deploy apps to the cloud that only you and your AI agents can reach",
5
5
  "license": "MIT",
6
6
  "tasks": {
@@ -19,6 +19,7 @@ export declare class Output<T extends Record<string, unknown>> {
19
19
  warn(text: string): this;
20
20
  text(text: string): this;
21
21
  dim(text: string): this;
22
+ link(text: string): this;
22
23
  header(text: string): this;
23
24
  blank(): this;
24
25
  spinner(message: string): {
@@ -1 +1 @@
1
- {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,CAAC,CAAC;AAChD,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAMvD,qBAAa,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnD,OAAO,CAAC,MAAM,CAGE;IAChB,OAAO,CAAC,QAAQ,CAAU;gBAEd,QAAQ,EAAE,OAAO;IAS7B,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;IAMnB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAKzD,KAAK,IAAI,IAAI;IAUb,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKtB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKvB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKvB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK1B,KAAK,IAAI,IAAI;IAKb,OAAO,CACL,OAAO,EAAE,MAAM,GACd;QAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,IAAI,IAAI,CAAA;KAAE;IAiBlE,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAgB9D,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK;IAS3B,MAAM,IAAI,OAAO;CAGlB;AAMD,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,QAAQ,EAAE,OAAO,GAChB,MAAM,CAAC,CAAC,CAAC,CAEX"}
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,CAAC,CAAC;AAChD,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAMvD,qBAAa,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnD,OAAO,CAAC,MAAM,CAGE;IAChB,OAAO,CAAC,QAAQ,CAAU;gBAEd,QAAQ,EAAE,OAAO;IAS7B,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;IAMnB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAKzD,KAAK,IAAI,IAAI;IAUb,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKtB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKvB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKvB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK1B,KAAK,IAAI,IAAI;IAKb,OAAO,CACL,OAAO,EAAE,MAAM,GACd;QAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,IAAI,IAAI,CAAA;KAAE;IAiBlE,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAgB9D,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK;IAS3B,MAAM,IAAI,OAAO;CAGlB;AAMD,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,QAAQ,EAAE,OAAO,GAChB,MAAM,CAAC,CAAC,CAAC,CAEX"}
package/esm/lib/output.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // =============================================================================
2
2
  // Output - Unified Output Handling for CLI Commands
3
3
  // =============================================================================
4
- import { bold, dim, Spinner, statusErr, statusInfo, statusOk, statusWarn, } from "./cli.js";
4
+ import { blue, bold, dim, Spinner, statusErr, statusInfo, statusOk, statusWarn, } from "./cli.js";
5
5
  // =============================================================================
6
6
  // Output Class
7
7
  // =============================================================================
@@ -67,6 +67,11 @@ export class Output {
67
67
  console.log(dim(text));
68
68
  return this;
69
69
  }
70
+ link(text) {
71
+ if (!this.jsonMode)
72
+ console.log(blue(text));
73
+ return this;
74
+ }
70
75
  header(text) {
71
76
  if (!this.jsonMode)
72
77
  console.log(bold(text));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Deploy apps to the cloud that only you and your AI agents can reach",
5
5
  "license": "MIT",
6
6
  "scripts": {},
File without changes
File without changes
File without changes