@cardelli/ambit 0.2.0 → 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.
package/README.md ADDED
@@ -0,0 +1,261 @@
1
+ # Ambit
2
+
3
+ Ambit lets you deploy apps (databases, internal tools, dashboards) that live in the cloud but are completely inaccessible to strangers. For example: you build something on your laptop and want to reach it from your phone. With Ambit, that just works: a random person on the internet cannot access it, but any device you own can.
4
+
5
+ ## The Problem
6
+
7
+ Putting a database or dashboard on the public internet forces you to build login pages, configure firewalls, and worry about bots scanning your IP. Even when you do it right, you've spent hours on plumbing that has nothing to do with what you were actually trying to build.
8
+
9
+ ## The Solution: "Cloud Localhost"
10
+
11
+ Ambit bridges two services: [Fly.io](https://fly.io) (where your apps run) and [Tailscale](https://tailscale.com) (a VPN that links your devices into a private network). Unlike a typical VPN that hides your browsing or spoofs your location, Tailscale creates a sealed private network: devices you enroll can connect to each other, and devices you haven't enrolled cannot. Ambit deploys your apps inside that sealed network, so they are only reachable from devices you have explicitly added to it. A stranger on the internet cannot connect to your app any more than they can connect to a server running on your laptop — there is simply no route to it from the outside.
12
+
13
+ - **No Login Pages to Build.** If your device is enrolled with your Tailscale
14
+ account, you can access the app. If it isn't, the connection is refused before the app ever sees it. You never have to write a login page.
15
+ - **No Security Auditing To Do.** A database with a weak password, an admin
16
+ panel with no login, an API with no rate limiting — all of these are fine, because the only machines that can connect are ones you already trust.
17
+ - **Human-Readable Addresses.** Your apps get names like
18
+ `http://my-dashboard.lab` instead of IP addresses.
19
+
20
+ ## Networks with Human-Readable Addresses (Ambits)
21
+
22
+ Each private network you create is called an ambit, and every app you deploy to it gets an address under that network's name — so `http://my-dashboard.lab` means the `my-dashboard` app on the `lab` ambit. These addresses work for anyone on your Tailscale network, which means you can share them with teammates and they just work, no extra configuration needed.
23
+
24
+ You can create as many ambits as you want, and through Tailscale's ACL settings you can decide precisely which of your devices or users can reach each one, as well as which ambits are allowed to talk to each other. You might, for example, give your whole team access to a `staging` ambit while keeping a `personal` ambit entirely to yourself.
25
+
26
+ ## Why You Need This
27
+
28
+ ### For the "Person with an AI Agent"
29
+
30
+ If you use Claude Code or OpenClaw, you want the agent to deploy things for testing. Giving it full cloud access is risky since it might deploy a public app with no password.
31
+
32
+ With Ambit, the agent deploys to `http://test-app.sandbox`. You can see it, the agent can see it, and nobody else knows it exists.
33
+
34
+ ### For the Developer
35
+
36
+ Setting up auth for a personal tool is a tedious barrier. Run `ambit deploy my-tool.lab` and the app is live and secure in minutes. The architecture enforces security so your config doesn't have to.
37
+
38
+ ## How It Works
39
+
40
+ 1. Ambit creates an encrypted tunnel between your machine and the cloud using Tailscale. 2. Your apps deploy into that tunnel. 3. Any device on your Tailscale network can reach them.
41
+
42
+ ## Quick Start
43
+
44
+ ### 1. Install
45
+
46
+ ```bash
47
+ npx @cardelli/ambit --help
48
+ ```
49
+
50
+ ### 2. Create a Network
51
+
52
+ ```bash
53
+ npx @cardelli/ambit create lab
54
+ ```
55
+
56
+ ### 3. Deploy an App
57
+
58
+ ```bash
59
+ npx @cardelli/ambit deploy my-crazy-site.lab
60
+ ```
61
+
62
+ ### 4. Visit It
63
+
64
+ Open `http://my-crazy-site.lab`. It works for you and nobody else.
65
+
66
+ ## Commands
67
+
68
+ ### `ambit create <network>`
69
+
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, and configures your local machine to accept the new routes.
71
+
72
+ | Flag | Description |
73
+ | ---------------------- | ---------------------------------------------------------- |
74
+ | `--org <org>` | Fly.io organization slug |
75
+ | `--region <region>` | Fly.io region (default: `iad`) |
76
+ | `--api-key <key>` | Tailscale API access token (tskey-api-...) |
77
+ | `--tag <tag>` | Tailscale ACL tag for the router (default: `tag:ambit-<network>`) |
78
+ | `--no-auto-approve` | Skip waiting for router and approving routes |
79
+ | `-y`, `--yes` | Skip confirmation prompts |
80
+ | `--json` | Machine-readable JSON output (implies `--no-auto-approve`) |
81
+
82
+ ### `ambit deploy <app>.<network>`
83
+
84
+ This puts an app onto your private network. This is what you run whenever you want to host something. If the app doesn't exist yet, ambit creates it on the right network for you. The network can be specified as part of the name (`my-app.lab`) or with `--network` (`my-app --network lab`).
85
+
86
+ Before deploying, ambit scans your config for settings that don't make sense on a private network (like `force_https`, which only matters for public traffic). After deploying, it checks that no public IPs were allocated, releases any that were, and verifies the app has a private address on the network you specified.
87
+
88
+ There are three deployment modes. They are mutually exclusive — you can only use one at a time.
89
+
90
+ #### Config mode (default)
91
+
92
+ Deploy from a `fly.toml`. If you don't pass `--config`, ambit looks for a `fly.toml` in the current directory.
93
+
94
+ ```bash
95
+ npx @cardelli/ambit deploy my-app.lab
96
+ npx @cardelli/ambit deploy my-app.lab --config ./my-config/fly.toml
97
+ ```
98
+
99
+ | Flag | Description |
100
+ | ------------------ | --------------------------------- |
101
+ | `--config <path>` | Explicit fly.toml path (optional) |
102
+
103
+ #### Image mode
104
+
105
+ Deploy a Docker image directly, without needing a `fly.toml`. Ambit generates a minimal config for you with auto start/stop enabled.
106
+
107
+ ```bash
108
+ npx @cardelli/ambit deploy my-app.lab --image registry/img:latest
109
+ npx @cardelli/ambit deploy my-app.lab --image registry/img:latest --main-port 3000
110
+ ```
111
+
112
+ | Flag | Description |
113
+ | -------------------- | ---------------------------------------------------------------------------- |
114
+ | `--image <img>` | Docker image to deploy (required) |
115
+ | `--main-port <port>` | Internal port for the HTTP service (default: `80`, `none` to skip) |
116
+
117
+ #### Template mode
118
+
119
+ Fetch a ready-made configuration from GitHub and deploy it. The reference format is `owner/repo/path[@ref]` — pin to a tag, branch, or commit with `@`.
120
+
121
+ ```bash
122
+ npx @cardelli/ambit deploy my-browser.lab --template ToxicPine/ambit-templates/chromatic
123
+ npx @cardelli/ambit deploy my-browser.lab --template ToxicPine/ambit-templates/chromatic@v1.0
124
+ ```
125
+
126
+ | Flag | Description |
127
+ | ------------------- | ------------------------------------------ |
128
+ | `--template <ref>` | GitHub template reference (required) |
129
+
130
+ #### Shared flags
131
+
132
+ These work with all three modes.
133
+
134
+ | Flag | Description |
135
+ | --------------------- | -------------------------------------------------------------- |
136
+ | `--network <name>` | Target network |
137
+ | `--org <org>` | Fly.io organization slug |
138
+ | `--region <region>` | Primary deployment region |
139
+ | `-y`, `--yes` | Skip confirmation prompts |
140
+ | `--json` | Machine-readable JSON output |
141
+
142
+ ### `ambit status [network|app]`
143
+
144
+ Without a subcommand, defaults to showing all routers (same as `status network`).
145
+
146
+ #### `ambit status network [<name>]`
147
+
148
+ 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.
149
+
150
+ | Flag | Description |
151
+ | ----------------- | ------------------------------ |
152
+ | `--org <org>` | Fly.io organization slug |
153
+ | `--json` | Machine-readable JSON output |
154
+
155
+ #### `ambit status app <app>.<network>`
156
+
157
+ Shows detailed status for a specific app: machines, Flycast IPs, and the backing router.
158
+
159
+ | Flag | Description |
160
+ | -------------------- | -------------------------------------------- |
161
+ | `--network <name>` | Target network (if not using dot syntax) |
162
+ | `--org <org>` | Fly.io organization slug |
163
+ | `--json` | Machine-readable JSON output |
164
+
165
+ ### `ambit list`
166
+
167
+ Lists all discovered routers across networks in a table showing the network name, app name, region, machine state, Tailscale connectivity status, and ACL tag.
168
+
169
+ | Flag | Description |
170
+ | ----------------- | ------------------------------ |
171
+ | `--org <org>` | Fly.io organization slug |
172
+ | `--json` | Machine-readable JSON output |
173
+
174
+ ### `ambit destroy network <name>`
175
+
176
+ Tears down a network: destroys the router, cleans up DNS, and removes the Tailscale device. If there are workload apps still on the network, ambit warns you before proceeding. Reminds you to clean up any ACL entries you added.
177
+
178
+ | Flag | Description |
179
+ | ----------------- | ------------------------------ |
180
+ | `--org <org>` | Fly.io organization slug |
181
+ | `-y`, `--yes` | Skip confirmation prompts |
182
+ | `--json` | Machine-readable JSON output |
183
+
184
+ ### `ambit destroy app <app>.<network>`
185
+
186
+ Destroys a workload app on a network. The network can be specified as part of the name (`my-app.lab`) or with `--network` (`my-app --network lab`). Verifies the app exists on the specified network before destroying it.
187
+
188
+ | Flag | Description |
189
+ | -------------------- | -------------------------------------------- |
190
+ | `--network <name>` | Target network (if not using dot syntax) |
191
+ | `--org <org>` | Fly.io organization slug |
192
+ | `-y`, `--yes` | Skip confirmation prompts |
193
+ | `--json` | Machine-readable JSON output |
194
+
195
+ ### `ambit doctor [network|app]`
196
+
197
+ This helps you diagnose issues, run it if something seems broken. It checks that Tailscale is running, routes are accepted, the router is healthy, and all parts of the system can talk to each other. Without a subcommand, defaults to checking all routers (same as `doctor network`).
198
+
199
+ #### `ambit doctor network [<name>]`
200
+
201
+ Checks router health. Without a network name, checks all routers. With a name, checks only the specified network. Also approves any unapproved subnet routes it finds.
202
+
203
+ | Flag | Description |
204
+ | -------------------- | -------------------------------------------- |
205
+ | `--network <name>` | Alias for the positional `<name>` argument |
206
+ | `--org <org>` | Fly.io organization slug |
207
+ | `--json` | Machine-readable JSON output |
208
+
209
+ #### `ambit doctor app <app>.<network>`
210
+
211
+ Checks app health: verifies the app is deployed and running, then checks the router for that network.
212
+
213
+ | Flag | Description |
214
+ | -------------------- | -------------------------------------------- |
215
+ | `--network <name>` | Target network (if not using dot syntax) |
216
+ | `--org <org>` | Fly.io organization slug |
217
+ | `--json` | Machine-readable JSON output |
218
+
219
+ ## Access Control
220
+
221
+ Ambit doesn't touch your Tailscale ACL policy. After creating a router, it prints the exact policy entries you need so you can control who on your tailnet can reach which networks. By default, if you haven't restricted anything, all your devices can reach everything.
222
+
223
+ If you want to lock it down, two rules do the job — one for DNS queries and one for data traffic:
224
+
225
+ ```jsonc
226
+ {
227
+ "tagOwners": {
228
+ "tag:ambit-infra": ["autogroup:admin"]
229
+ },
230
+ "autoApprovers": {
231
+ "routes": {
232
+ "fdaa:X:XXXX::/48": ["tag:ambit-infra"]
233
+ }
234
+ },
235
+ "acls": [
236
+ {
237
+ "action": "accept",
238
+ "src": ["group:team"],
239
+ "dst": ["tag:ambit-infra:53"]
240
+ },
241
+ { "action": "accept", "src": ["group:team"], "dst": ["fdaa:X:XXXX::/48:*"] }
242
+ ]
243
+ }
244
+ ```
245
+
246
+ ## Multiple Networks
247
+
248
+ You can create as many networks as you want, and each one gets its own TLD on your tailnet. The SOCKS proxy on each router means containers on one network can reach services on another by going through the tailnet, so a browser on your `browsers` network can connect to a database on your `infra` network.
249
+
250
+ ```bash
251
+ npx @cardelli/ambit create infra
252
+ npx @cardelli/ambit create browsers
253
+ ```
254
+
255
+ ## Agent Skill
256
+
257
+ Install the Ambit [skill](https://skills.sh) to give your AI coding agent reference documentation for all the CLI commands. Works with Claude Code, Cursor, Windsurf, and other AI coding agents:
258
+
259
+ ```bash
260
+ npx skills add ToxicPine/ambit-skills --skill ambit-cli
261
+ ```
@@ -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.0",
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.0",
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