@cardelli/ambit 0.2.3 → 0.3.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.
Files changed (43) hide show
  1. package/README.md +38 -22
  2. package/esm/cli/commands/create/index.js +53 -17
  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 +70 -29
  6. package/esm/cli/commands/deploy/index.js +2 -4
  7. package/esm/cli/commands/deploy/machine.d.ts.map +1 -1
  8. package/esm/cli/commands/destroy/app.d.ts.map +1 -1
  9. package/esm/cli/commands/destroy/index.js +2 -0
  10. package/esm/cli/commands/destroy/network.d.ts.map +1 -1
  11. package/esm/cli/commands/destroy/network.js +66 -5
  12. package/esm/cli/commands/doctor.js +13 -4
  13. package/esm/cli/commands/list.js +1 -1
  14. package/esm/cli/commands/share.d.ts +2 -0
  15. package/esm/cli/commands/share.d.ts.map +1 -0
  16. package/esm/cli/commands/share.js +250 -0
  17. package/esm/cli/commands/status.js +4 -1
  18. package/esm/cli/mod.d.ts.map +1 -1
  19. package/esm/cli/mod.js +2 -0
  20. package/esm/deno.js +1 -1
  21. package/esm/lib/command.d.ts.map +1 -1
  22. package/esm/lib/command.js +5 -7
  23. package/esm/main.d.ts +1 -0
  24. package/esm/main.d.ts.map +1 -1
  25. package/esm/main.js +2 -0
  26. package/esm/providers/fly.d.ts.map +1 -1
  27. package/esm/providers/fly.js +14 -3
  28. package/esm/providers/tailscale.d.ts +7 -0
  29. package/esm/providers/tailscale.d.ts.map +1 -1
  30. package/esm/providers/tailscale.js +23 -1
  31. package/esm/router/start.sh +7 -4
  32. package/esm/util/constants.d.ts +0 -1
  33. package/esm/util/constants.d.ts.map +1 -1
  34. package/esm/util/constants.js +0 -1
  35. package/esm/util/credentials.d.ts.map +1 -1
  36. package/esm/util/credentials.js +1 -1
  37. package/esm/util/discovery.d.ts.map +1 -1
  38. package/esm/util/discovery.js +1 -1
  39. package/esm/util/tailscale-local.d.ts +41 -0
  40. package/esm/util/tailscale-local.d.ts.map +1 -1
  41. package/esm/util/tailscale-local.js +146 -0
  42. package/esm/util/template.d.ts.map +1 -1
  43. package/package.json +1 -1
@@ -8,6 +8,47 @@ export declare const isAcceptRoutesEnabled: () => Promise<boolean>;
8
8
  */
9
9
  export declare const enableAcceptRoutes: () => Promise<boolean>;
10
10
  export declare const waitForDevice: (provider: TailscaleProvider, hostname: string, timeoutMs?: number) => Promise<TailscaleDevice>;
11
+ /**
12
+ * Asserts that a patch operation only added data and never removed any
13
+ * top-level key or shortened any existing array. Throws if the invariant is
14
+ * violated so callers can surface a hard error before writing to the API.
15
+ */
16
+ export declare const assertAdditivePatch: (original: Record<string, unknown>, patched: Record<string, unknown>) => void;
11
17
  export declare const isTagOwnerConfigured: (policy: Record<string, unknown> | null, tag: string) => boolean;
12
18
  export declare const isAutoApproverConfigured: (policy: Record<string, unknown> | null, tag: string) => boolean;
19
+ /**
20
+ * Returns a new policy with the given tag added to tagOwners.
21
+ * No-op if the tag is already present.
22
+ */
23
+ export declare const patchTagOwner: (policy: Record<string, unknown>, tag: string, owners?: string[]) => Record<string, unknown>;
24
+ /**
25
+ * Returns a new policy with the given route + tag added to autoApprovers.
26
+ * No-op if the tag is already an approver for any route.
27
+ */
28
+ export declare const patchAutoApprover: (policy: Record<string, unknown>, tag: string, route: string) => Record<string, unknown>;
29
+ /**
30
+ * Returns true if there is already an accept rule where `src` contains the
31
+ * given source member and `dst` contains the given destination.
32
+ */
33
+ export declare const isAclRuleConfigured: (policy: Record<string, unknown> | null, src: string, dst: string) => boolean;
34
+ /**
35
+ * Returns a new policy with an accept rule `src → dst` appended to `acls`.
36
+ * No-op if an identical rule already exists.
37
+ */
38
+ export declare const patchAclRule: (policy: Record<string, unknown>, src: string, dst: string) => Record<string, unknown>;
39
+ /**
40
+ * Returns a new policy with all accept rules matching `src → dst` removed from `acls`.
41
+ * No-op if no such rule exists.
42
+ */
43
+ export declare const unpatchAclRule: (policy: Record<string, unknown>, src: string, dst: string) => Record<string, unknown>;
44
+ /**
45
+ * Returns a new policy with the given tag removed from tagOwners.
46
+ * No-op if the tag is not present.
47
+ */
48
+ export declare const unpatchTagOwner: (policy: Record<string, unknown>, tag: string) => Record<string, unknown>;
49
+ /**
50
+ * Returns a new policy with the given tag removed from all autoApprovers routes.
51
+ * If a route's approver list becomes empty after removal, the route entry is dropped.
52
+ */
53
+ export declare const unpatchAutoApprover: (policy: Record<string, unknown>, tag: string) => Record<string, unknown>;
13
54
  //# sourceMappingURL=tailscale-local.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tailscale-local.d.ts","sourceRoot":"","sources":["../../src/util/tailscale-local.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAMnE,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,OAAO,CAE5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,OAAO,CAM7D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,OAAO,CAG1D,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,EAC3B,UAAU,MAAM,EAChB,YAAW,MAAe,KACzB,OAAO,CAAC,eAAe,CAazB,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OASF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OAgBF,CAAC"}
1
+ {"version":3,"file":"tailscale-local.d.ts","sourceRoot":"","sources":["../../src/util/tailscale-local.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAMnE,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,OAAO,CAE5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,OAAO,CAM7D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,OAAO,CAG1D,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,EAC3B,UAAU,MAAM,EAChB,YAAW,MAAe,KACzB,OAAO,CAAC,eAAe,CAazB,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,IAiBF,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OASF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OAgBF,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,EACX,SAAQ,MAAM,EAAwB,KACrC,MAAM,CAAC,MAAM,EAAE,OAAO,CAIxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,EACX,OAAO,MAAM,KACZ,MAAM,CAAC,MAAM,EAAE,OAAO,CAYxB,CAAC;AAaF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,EACX,KAAK,MAAM,KACV,OAUF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,EACX,KAAK,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAOxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,EACX,KAAK,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAaxB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAQxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CA+BxB,CAAC"}
@@ -40,6 +40,28 @@ export const waitForDevice = async (provider, hostname, timeoutMs = 120000) => {
40
40
  return die(`Timeout Waiting for Device '${hostname}'`);
41
41
  };
42
42
  // =============================================================================
43
+ // Sanity Guards
44
+ // =============================================================================
45
+ /**
46
+ * Asserts that a patch operation only added data and never removed any
47
+ * top-level key or shortened any existing array. Throws if the invariant is
48
+ * violated so callers can surface a hard error before writing to the API.
49
+ */
50
+ export const assertAdditivePatch = (original, patched) => {
51
+ for (const key of Object.keys(original)) {
52
+ if (!(key in patched)) {
53
+ throw new Error(`ACL sanity check failed: key '${key}' was unexpectedly removed`);
54
+ }
55
+ const origVal = original[key];
56
+ const patchedVal = patched[key];
57
+ if (Array.isArray(origVal) && Array.isArray(patchedVal)) {
58
+ if (patchedVal.length < origVal.length) {
59
+ throw new Error(`ACL sanity check failed: '${key}' array shrank (${origVal.length} → ${patchedVal.length} entries)`);
60
+ }
61
+ }
62
+ }
63
+ };
64
+ // =============================================================================
43
65
  // Pure Policy Checks
44
66
  // =============================================================================
45
67
  export const isTagOwnerConfigured = (policy, tag) => {
@@ -61,3 +83,127 @@ export const isAutoApproverConfigured = (policy, tag) => {
61
83
  return false;
62
84
  return Object.values(routes).some((approvers) => Array.isArray(approvers) && approvers.includes(tag));
63
85
  };
86
+ // =============================================================================
87
+ // ACL Policy Patching (Pure)
88
+ // =============================================================================
89
+ /**
90
+ * Returns a new policy with the given tag added to tagOwners.
91
+ * No-op if the tag is already present.
92
+ */
93
+ export const patchTagOwner = (policy, tag, owners = ["autogroup:admin"]) => {
94
+ const tagOwners = (policy.tagOwners ?? {});
95
+ if (tag in tagOwners)
96
+ return policy;
97
+ return { ...policy, tagOwners: { ...tagOwners, [tag]: owners } };
98
+ };
99
+ /**
100
+ * Returns a new policy with the given route + tag added to autoApprovers.
101
+ * No-op if the tag is already an approver for any route.
102
+ */
103
+ export const patchAutoApprover = (policy, tag, route) => {
104
+ const autoApprovers = (policy.autoApprovers ?? {});
105
+ const routes = (autoApprovers.routes ?? {});
106
+ const existing = routes[route] ?? [];
107
+ if (existing.includes(tag))
108
+ return policy;
109
+ return {
110
+ ...policy,
111
+ autoApprovers: {
112
+ ...autoApprovers,
113
+ routes: { ...routes, [route]: [...existing, tag] },
114
+ },
115
+ };
116
+ };
117
+ /**
118
+ * Returns true if there is already an accept rule where `src` contains the
119
+ * given source member and `dst` contains the given destination.
120
+ */
121
+ export const isAclRuleConfigured = (policy, src, dst) => {
122
+ if (!policy)
123
+ return false;
124
+ const acls = policy.acls;
125
+ if (!Array.isArray(acls))
126
+ return false;
127
+ return acls.some((rule) => rule.action === "accept" &&
128
+ Array.isArray(rule.src) && rule.src.includes(src) &&
129
+ Array.isArray(rule.dst) && rule.dst.includes(dst));
130
+ };
131
+ /**
132
+ * Returns a new policy with an accept rule `src → dst` appended to `acls`.
133
+ * No-op if an identical rule already exists.
134
+ */
135
+ export const patchAclRule = (policy, src, dst) => {
136
+ if (isAclRuleConfigured(policy, src, dst))
137
+ return policy;
138
+ const acls = (policy.acls ?? []);
139
+ return {
140
+ ...policy,
141
+ acls: [...acls, { action: "accept", src: [src], dst: [dst] }],
142
+ };
143
+ };
144
+ /**
145
+ * Returns a new policy with all accept rules matching `src → dst` removed from `acls`.
146
+ * No-op if no such rule exists.
147
+ */
148
+ export const unpatchAclRule = (policy, src, dst) => {
149
+ const acls = policy.acls;
150
+ if (!Array.isArray(acls))
151
+ return policy;
152
+ const filtered = acls.filter((rule) => !(rule.action === "accept" &&
153
+ Array.isArray(rule.src) && rule.src.includes(src) &&
154
+ Array.isArray(rule.dst) && rule.dst.includes(dst)));
155
+ if (filtered.length === acls.length)
156
+ return policy;
157
+ return { ...policy, acls: filtered };
158
+ };
159
+ // =============================================================================
160
+ // ACL Policy Un-patching (Pure) — inverse of the patch helpers above
161
+ // =============================================================================
162
+ /**
163
+ * Returns a new policy with the given tag removed from tagOwners.
164
+ * No-op if the tag is not present.
165
+ */
166
+ export const unpatchTagOwner = (policy, tag) => {
167
+ const tagOwners = policy.tagOwners;
168
+ if (!tagOwners || !(tag in tagOwners))
169
+ return policy;
170
+ const { [tag]: _, ...rest } = tagOwners;
171
+ const hasKeys = Object.keys(rest).length > 0;
172
+ return hasKeys
173
+ ? { ...policy, tagOwners: rest }
174
+ : { ...policy, tagOwners: {} };
175
+ };
176
+ /**
177
+ * Returns a new policy with the given tag removed from all autoApprovers routes.
178
+ * If a route's approver list becomes empty after removal, the route entry is dropped.
179
+ */
180
+ export const unpatchAutoApprover = (policy, tag) => {
181
+ const autoApprovers = policy.autoApprovers;
182
+ if (!autoApprovers)
183
+ return policy;
184
+ const routes = autoApprovers.routes;
185
+ if (!routes)
186
+ return policy;
187
+ const cleaned = {};
188
+ let changed = false;
189
+ for (const [route, approvers] of Object.entries(routes)) {
190
+ if (!Array.isArray(approvers)) {
191
+ cleaned[route] = approvers;
192
+ continue;
193
+ }
194
+ const filtered = approvers.filter((a) => a !== tag);
195
+ if (filtered.length !== approvers.length)
196
+ changed = true;
197
+ if (filtered.length > 0)
198
+ cleaned[route] = filtered;
199
+ }
200
+ if (!changed)
201
+ return policy;
202
+ return {
203
+ ...policy,
204
+ autoApprovers: {
205
+ ...autoApprovers,
206
+ routes: cleaned,
207
+ },
208
+ };
209
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/util/template.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1C,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CACtC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CACzC,CAAC;AAyBF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,WAAW,GAAG,IAyB5D,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,KACf,OAAO,CAAC,mBAAmB,CAqG7B,CAAC"}
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/util/template.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1C,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CACtC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CACzC,CAAC;AAyBF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,WAAW,GAAG,IAyB5D,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,KACf,OAAO,CAAC,mBAAmB,CAuG7B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.2.3",
3
+ "version": "0.3.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": {},