@mediamonks/canopy-cli 0.7.40 → 0.7.42
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/dist/{ai-EyrXJC5z.mjs → ai-DpQ1nLka.mjs} +2 -2
- package/dist/{api-ArfD6-t5.mjs → api-Do9c8tZ_.mjs} +73 -3
- package/dist/{browse-DEhnJAPT.mjs → browse-Dp5HytqK.mjs} +2 -2
- package/dist/{db-DGrCgj-R.mjs → db-BNsX7sXo.mjs} +4 -4
- package/dist/{deploy-B7KItUnh.mjs → deploy-CXMLABvH.mjs} +8 -8
- package/dist/{dev-03LA_Etx.mjs → dev-CPR-9slK.mjs} +6 -6
- package/dist/dev-Dj3wFKHe.mjs +2 -0
- package/dist/docs/ci.md +19 -1
- package/dist/docs/cli-rename.md +51 -0
- package/dist/docs/git.md +5 -0
- package/dist/docs/index.json +11 -1
- package/dist/docs/platform-egress.md +69 -12
- package/dist/docs/rename.md +65 -0
- package/dist/docs/troubleshooting.md +16 -0
- package/dist/{docs-B0-IEPjd.mjs → docs-CkJGcCpR.mjs} +4 -4
- package/dist/egress-D6qyaaAn.mjs +397 -0
- package/dist/{feedback-BsJ_6ke2.mjs → feedback-DMgUUl0V.mjs} +4 -4
- package/dist/index.mjs +33 -32
- package/dist/{local-CkS1XhRu.mjs → local-w2wXfE1d.mjs} +2 -2
- package/dist/{login-B2FJGIrD.mjs → login-CeNvHLUv.mjs} +3 -3
- package/dist/{logs-wSKh_FmL.mjs → logs-BzgJfWNP.mjs} +3 -3
- package/dist/{mcp-DtRIDtaL.mjs → mcp-DFLaXiY_.mjs} +8 -6
- package/dist/{migrations-CG_O5Ghw.mjs → migrations-Bsu4quya.mjs} +2 -2
- package/dist/{open-COFMburt.mjs → open-D71Ms1nG.mjs} +2 -2
- package/dist/{package-C1xCnyOB.mjs → package-CVDpzGgA.mjs} +1 -1
- package/dist/{project-template-DtTfXyLI.mjs → project-template-BXfHpsI8.mjs} +3 -3
- package/dist/project-template-BbmSmraT.mjs +2 -0
- package/dist/{projects-B3Wi01lw.mjs → projects-BWTIk80a.mjs} +5 -5
- package/dist/rename-BdIFrqvG.mjs +81 -0
- package/dist/repo-JBKJqyoS.mjs +2 -0
- package/dist/{repo-4x-CJwCp.mjs → repo-LSOxnmUk.mjs} +5 -3
- package/dist/{schedule-CbaDIJXr.mjs → schedule-DpMIpCGC.mjs} +3 -3
- package/dist/{secrets-CmY4dUHz.mjs → secrets-BVx8L0Gj.mjs} +3 -3
- package/dist/{site-DM2J10CB.mjs → site-0i7wAJqg.mjs} +24 -1
- package/dist/{skill-AqZYYsE6.mjs → skill-D58yV6K8.mjs} +2 -2
- package/dist/{staging-ypR79MlD.mjs → staging-ynPlMiHW.mjs} +3 -3
- package/dist/{token-CDrHQ5Xb.mjs → token-CxiPrKn_.mjs} +3 -3
- package/dist/{ui-CaHgvvkJ.mjs → ui-CDUJq-X-.mjs} +1 -1
- package/dist/{upgrade-DSjmifH_.mjs → upgrade-Dll5rhOQ.mjs} +2 -2
- package/dist/{usage-CP8rBVZi.mjs → usage-BtPsb7Iu.mjs} +10 -3
- package/package.json +1 -1
- package/dist/dev-dJ88L73Y.mjs +0 -2
- package/dist/egress-CzxRD6Tp.mjs +0 -188
- package/dist/project-template-DlzMx8Oj.mjs +0 -2
- package/dist/repo-CmbakFVi.mjs +0 -2
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import * as p from "@clack/prompts";
|
|
6
|
+
//#region src/egress.ts
|
|
7
|
+
/**
|
|
8
|
+
* canopy egress — manage the project's outbound-host policy: the allowlist
|
|
9
|
+
* (docs/16-egress-allowlist.md) and the egress mode (docs/25-egress-modes.md).
|
|
10
|
+
* In allowlist mode, worker fetches to hosts outside the list are denied
|
|
11
|
+
* (`egress_denied`) on targets that enforce egress control; open mode allows
|
|
12
|
+
* any public hostname behind a built-in blocklist and an outbound rate limit.
|
|
13
|
+
* Policy lives on the platform, not in canopy.json, so a repo file can never
|
|
14
|
+
* widen egress silently on deploy.
|
|
15
|
+
*/
|
|
16
|
+
function printList({ mode, hosts, denied, enforced }, slug) {
|
|
17
|
+
if (mode === "open") console.log(`${slug} is in OPEN egress mode — the worker may fetch any public host (the allowlist below is kept but not enforced; \`canopy egress mode allowlist\` restores it).`);
|
|
18
|
+
if (hosts.length === 0) console.log(`No extra egress hosts for ${slug} — the worker can reach platform hosts only.`);
|
|
19
|
+
else for (const host of hosts) console.log(host);
|
|
20
|
+
if (denied.length > 0) {
|
|
21
|
+
console.log(`\nRecently blocked (allow with \`canopy egress allow <host>\`):`);
|
|
22
|
+
for (const d of denied) {
|
|
23
|
+
const why = d.reason === "rate_limited" ? "rate limited" : "blocked";
|
|
24
|
+
console.log(` ${d.host} ×${d.count} ${why}, last ${formatDateTime(d.last_seen)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (!enforced) console.error("(note: this site's target does not enforce egress yet — the policy is recorded for when it does)");
|
|
28
|
+
}
|
|
29
|
+
const listCommand = defineCommand({
|
|
30
|
+
meta: {
|
|
31
|
+
name: "list",
|
|
32
|
+
description: "List the project's allowed outbound hosts"
|
|
33
|
+
},
|
|
34
|
+
args: { ...projectDirArgs },
|
|
35
|
+
run: reportErrors("egress list", async ({ args }) => {
|
|
36
|
+
const { slug } = resolveProject(args);
|
|
37
|
+
printList(await apiRequest("GET", `/projects/${slug}/egress`), slug);
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
const allowCommand = defineCommand({
|
|
41
|
+
meta: {
|
|
42
|
+
name: "allow",
|
|
43
|
+
description: "Allow the worker to fetch a host (e.g. api.example.com or *.googleapis.com)"
|
|
44
|
+
},
|
|
45
|
+
args: {
|
|
46
|
+
host: {
|
|
47
|
+
type: "positional",
|
|
48
|
+
description: "Hostname to allow",
|
|
49
|
+
required: true
|
|
50
|
+
},
|
|
51
|
+
...projectDirArgs
|
|
52
|
+
},
|
|
53
|
+
run: reportErrors("egress allow", async ({ args }) => {
|
|
54
|
+
const { slug } = resolveProject(args);
|
|
55
|
+
const current = await apiRequest("GET", `/projects/${slug}/egress`);
|
|
56
|
+
const updated = await apiRequest("PUT", `/projects/${slug}/egress`, { hosts: [...current.hosts, args.host] });
|
|
57
|
+
console.log(`Allowed ${args.host} for ${slug} — running workers pick it up now.\n`);
|
|
58
|
+
printList(updated, slug);
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
const removeCommand = defineCommand({
|
|
62
|
+
meta: {
|
|
63
|
+
name: "remove",
|
|
64
|
+
description: "Remove a host from the allowlist"
|
|
65
|
+
},
|
|
66
|
+
args: {
|
|
67
|
+
host: {
|
|
68
|
+
type: "positional",
|
|
69
|
+
description: "Hostname to remove",
|
|
70
|
+
required: true
|
|
71
|
+
},
|
|
72
|
+
...projectDirArgs
|
|
73
|
+
},
|
|
74
|
+
run: reportErrors("egress remove", async ({ args }) => {
|
|
75
|
+
const { slug } = resolveProject(args);
|
|
76
|
+
const current = await apiRequest("GET", `/projects/${slug}/egress`);
|
|
77
|
+
const wanted = args.host.trim().toLowerCase();
|
|
78
|
+
if (!current.hosts.includes(wanted)) {
|
|
79
|
+
console.error(`${wanted} is not on the allowlist for ${slug} (see \`canopy egress list\`).`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
const updated = await apiRequest("PUT", `/projects/${slug}/egress`, { hosts: current.hosts.filter((h) => h !== wanted) });
|
|
83
|
+
console.log(`Removed ${wanted} from ${slug} — running workers stop reaching it now.\n`);
|
|
84
|
+
printList(updated, slug);
|
|
85
|
+
})
|
|
86
|
+
});
|
|
87
|
+
/** The plain-language risk summary shown before open mode is enabled (docs/25 §2). */
|
|
88
|
+
function openModeRiskSummary(slug, clientAccess) {
|
|
89
|
+
const lines = [
|
|
90
|
+
`Open egress mode lets ${slug}'s worker contact ANY public host:`,
|
|
91
|
+
"",
|
|
92
|
+
" - If the URLs it fetches come from user input, users control where it connects.",
|
|
93
|
+
" - Abusive outbound traffic can get the project rate-limited or paused.",
|
|
94
|
+
" - A built-in blocklist (private IPs, localhost, cloud metadata) and an",
|
|
95
|
+
" outbound rate limit still apply, and every visited host is logged",
|
|
96
|
+
" (`canopy egress log`)."
|
|
97
|
+
];
|
|
98
|
+
if (clientAccess) lines.push("", " ! This project has client access enabled — untrusted visitors can drive", " what the worker fetches.");
|
|
99
|
+
lines.push("", "It's the right choice for apps that fetch arbitrary user-provided URLs", "(link collectors, feed readers, scrapers). Everything else is better served", "by the allowlist.");
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
const modeCommand = defineCommand({
|
|
103
|
+
meta: {
|
|
104
|
+
name: "mode",
|
|
105
|
+
description: "Show or switch the egress mode (allowlist | open)"
|
|
106
|
+
},
|
|
107
|
+
args: {
|
|
108
|
+
mode: {
|
|
109
|
+
type: "positional",
|
|
110
|
+
description: "allowlist | open (omit to show)",
|
|
111
|
+
required: false
|
|
112
|
+
},
|
|
113
|
+
"accept-risk": {
|
|
114
|
+
type: "boolean",
|
|
115
|
+
description: "Acknowledge the open-mode risk summary without an interactive prompt"
|
|
116
|
+
},
|
|
117
|
+
...projectDirArgs
|
|
118
|
+
},
|
|
119
|
+
run: reportErrors("egress mode", async ({ args }) => {
|
|
120
|
+
const { slug } = resolveProject(args);
|
|
121
|
+
const current = await apiRequest("GET", `/projects/${slug}/egress`);
|
|
122
|
+
const wanted = args.mode?.trim().toLowerCase();
|
|
123
|
+
if (!wanted) {
|
|
124
|
+
console.log(current.mode);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (wanted !== "allowlist" && wanted !== "open") {
|
|
128
|
+
console.error(`unknown mode "${wanted}" — expected allowlist or open.`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
if (wanted === current.mode) {
|
|
132
|
+
console.log(`${slug} is already in ${wanted} mode.`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (wanted === "open") {
|
|
136
|
+
const access = await apiRequest("GET", `/projects/${slug}/client-password`).catch(() => ({ enabled: false }));
|
|
137
|
+
console.log(openModeRiskSummary(slug, access.enabled));
|
|
138
|
+
console.log("");
|
|
139
|
+
if (!args["accept-risk"]) {
|
|
140
|
+
requireTty("pass --accept-risk to enable open mode without confirmation.");
|
|
141
|
+
if (unwrap(await p.text({ message: `Type the project slug ("${slug}") to enable open mode.` })).trim() !== slug) {
|
|
142
|
+
p.cancel("Aborted — egress mode unchanged.");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const updated = await withContext(apiRequest("PUT", `/projects/${slug}/egress`, { mode: wanted }), {
|
|
148
|
+
action: `switch ${slug} to ${wanted} egress mode`,
|
|
149
|
+
fixes: { owner_only: "Only the project owner can switch egress to open mode — ask them to run this command or flip it in the dashboard." }
|
|
150
|
+
});
|
|
151
|
+
if (wanted === "open") {
|
|
152
|
+
console.log(`Open egress mode is ON for ${slug} — running workers pick it up now.`);
|
|
153
|
+
console.log(`Switch back anytime with \`canopy egress mode allowlist\`.`);
|
|
154
|
+
} else {
|
|
155
|
+
console.log(`Allowlist mode restored for ${slug} — only listed hosts are reachable.\n`);
|
|
156
|
+
printList(updated, slug);
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
const logCommand = defineCommand({
|
|
161
|
+
meta: {
|
|
162
|
+
name: "log",
|
|
163
|
+
description: "Show the outbound traffic log — hosts the worker actually fetched"
|
|
164
|
+
},
|
|
165
|
+
args: { ...projectDirArgs },
|
|
166
|
+
run: reportErrors("egress log", async ({ args }) => {
|
|
167
|
+
const { slug } = resolveProject(args);
|
|
168
|
+
const { visits } = await apiRequest("GET", `/projects/${slug}/egress/log`);
|
|
169
|
+
if (visits.length === 0) {
|
|
170
|
+
console.log(`No outbound traffic recorded for ${slug} yet.`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
for (const v of visits) console.log(`${v.host} ×${v.count} last ${formatDateTime(v.last_seen)}`);
|
|
174
|
+
})
|
|
175
|
+
});
|
|
176
|
+
/** The note `all` prints: what routing every request costs. */
|
|
177
|
+
const FIXED_IP_ALL_NOTE = [
|
|
178
|
+
"Every outbound request now leaves from the fixed addresses. That means:",
|
|
179
|
+
" - each request takes a slower route (an extra hop, and a cold start after idle),",
|
|
180
|
+
" - it all counts toward the daily fixed-address transfer quota,",
|
|
181
|
+
" - and it leaves from addresses shared with other projects.",
|
|
182
|
+
"Listing only the hosts that need a fixed address (`canopy egress fixed-ip add <host>`)",
|
|
183
|
+
"is usually the better choice."
|
|
184
|
+
].join("\n");
|
|
185
|
+
/** `canopy egress fixed-ip` with no argument: mode, hosts, addresses, flags. */
|
|
186
|
+
function renderFixedIp(view, slug, egressMode) {
|
|
187
|
+
const lines = [];
|
|
188
|
+
const addresses = view.addresses.join(", ");
|
|
189
|
+
if (!view.optional) {
|
|
190
|
+
lines.push(`Every outbound request of ${slug} leaves from the fixed addresses: ${addresses}`);
|
|
191
|
+
lines.push("Ask partners to allow both. There is nothing to set on this site.");
|
|
192
|
+
return lines.join("\n");
|
|
193
|
+
}
|
|
194
|
+
if (view.mode === "off") {
|
|
195
|
+
lines.push(`Fixed outbound addresses are off for ${slug} — requests leave normally.`);
|
|
196
|
+
lines.push(`Route a partner's host through ${addresses} with \`canopy egress fixed-ip add <host>\`.`);
|
|
197
|
+
return lines.join("\n");
|
|
198
|
+
}
|
|
199
|
+
if (view.mode === "all") lines.push(`Every outbound request of ${slug} leaves from: ${addresses}`);
|
|
200
|
+
else {
|
|
201
|
+
lines.push(`Requests of ${slug} to these hosts leave from ${addresses}:`);
|
|
202
|
+
for (const host of view.hosts) {
|
|
203
|
+
const flag = view.notAllowed.includes(host) ? " (not allowed — requests are denied)" : "";
|
|
204
|
+
lines.push(` ${host}${flag}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (view.notAllowed.length > 0 && egressMode === "allowlist") lines.push(`Routing doesn't allow a host: add it with \`canopy egress allow <host>\` to reach it.`);
|
|
208
|
+
return lines.join("\n");
|
|
209
|
+
}
|
|
210
|
+
/** The PUT body `add` sends; from off it starts a fresh list with just this host. */
|
|
211
|
+
function fixedIpAddBody(view, host) {
|
|
212
|
+
if (view.mode === "off") return {
|
|
213
|
+
mode: "hosts",
|
|
214
|
+
hosts: [host]
|
|
215
|
+
};
|
|
216
|
+
return { hosts: [...view.hosts, host] };
|
|
217
|
+
}
|
|
218
|
+
/** The PUT body `remove` sends, or null when the host isn't routed. */
|
|
219
|
+
function fixedIpRemoveBody(view, host) {
|
|
220
|
+
const wanted = host.trim().toLowerCase();
|
|
221
|
+
if (!view.hosts.includes(wanted)) return null;
|
|
222
|
+
const hosts = view.hosts.filter((h) => h !== wanted);
|
|
223
|
+
return view.mode === "hosts" && hosts.length === 0 ? {
|
|
224
|
+
mode: "off",
|
|
225
|
+
hosts
|
|
226
|
+
} : { hosts };
|
|
227
|
+
}
|
|
228
|
+
const FIXED_IP_FIXES = {
|
|
229
|
+
owner_only: "Only the project owner can turn on fixed outbound addresses or route all traffic — ask them to run this command or turn it on in the dashboard.",
|
|
230
|
+
fixed_ip_not_optional: "Every request on this site already leaves from the fixed addresses — `canopy egress fixed-ip` shows them.",
|
|
231
|
+
not_supported_on_target: "This site has no fixed outbound addresses: it runs no project backends to send from them."
|
|
232
|
+
};
|
|
233
|
+
/** The current setting, or a clear exit where the site offers none. */
|
|
234
|
+
async function loadFixedIp(slug) {
|
|
235
|
+
const egress = await apiRequest("GET", `/projects/${slug}/egress`);
|
|
236
|
+
if (!egress.fixedIp) {
|
|
237
|
+
console.error(egress.fixedIp === void 0 ? "This site's API predates fixed outbound addresses." : "This site has no fixed outbound addresses (it runs no project backends).");
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
egress,
|
|
242
|
+
view: egress.fixedIp
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function putFixedIp(slug, fixedIp, action) {
|
|
246
|
+
return withContext(apiRequest("PUT", `/projects/${slug}/egress`, { fixedIp }), {
|
|
247
|
+
action,
|
|
248
|
+
fixes: FIXED_IP_FIXES
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function requireOptional(view, slug) {
|
|
252
|
+
if (view.optional) return;
|
|
253
|
+
console.log(renderFixedIp(view, slug, "allowlist"));
|
|
254
|
+
process.exit(0);
|
|
255
|
+
}
|
|
256
|
+
let fixedIpSubcommandRan = false;
|
|
257
|
+
const fixedIpAddCommand = defineCommand({
|
|
258
|
+
meta: {
|
|
259
|
+
name: "add",
|
|
260
|
+
description: "Route a host through the fixed addresses (turns the setting on)"
|
|
261
|
+
},
|
|
262
|
+
args: {
|
|
263
|
+
host: {
|
|
264
|
+
type: "positional",
|
|
265
|
+
description: "Hostname (api.partner.com or *.partner.com)",
|
|
266
|
+
required: true
|
|
267
|
+
},
|
|
268
|
+
allow: {
|
|
269
|
+
type: "boolean",
|
|
270
|
+
description: "Also add the host to the allowlist when it isn't on it"
|
|
271
|
+
},
|
|
272
|
+
...projectDirArgs
|
|
273
|
+
},
|
|
274
|
+
run: reportErrors("egress fixed-ip add", async ({ args }) => {
|
|
275
|
+
fixedIpSubcommandRan = true;
|
|
276
|
+
const { slug } = resolveProject(args);
|
|
277
|
+
const { egress, view } = await loadFixedIp(slug);
|
|
278
|
+
requireOptional(view, slug);
|
|
279
|
+
const updated = await putFixedIp(slug, fixedIpAddBody(view, args.host), `route ${args.host} through the fixed addresses for ${slug}`);
|
|
280
|
+
const fixed = updated.fixedIp;
|
|
281
|
+
console.log(`Requests of ${slug} to ${args.host} now leave from the fixed addresses.`);
|
|
282
|
+
console.log(`Ask the partner to allow both: ${fixed.addresses.join(", ")}\n`);
|
|
283
|
+
if (fixed.mode === "all") console.log("(All traffic is routed right now, so this host's requests already were.)\n");
|
|
284
|
+
const host = fixed.hosts.find((h) => h === args.host.trim().toLowerCase()) ?? args.host;
|
|
285
|
+
if (egress.mode === "allowlist" && fixed.notAllowed.includes(host)) {
|
|
286
|
+
let allow = Boolean(args.allow);
|
|
287
|
+
if (!allow && process.stdin.isTTY) allow = unwrap(await p.confirm({
|
|
288
|
+
message: `${host} isn't on the allowlist, so requests to it are denied. Allow it now?`,
|
|
289
|
+
initialValue: true
|
|
290
|
+
}));
|
|
291
|
+
if (allow) {
|
|
292
|
+
const allowed = await apiRequest("PUT", `/projects/${slug}/egress`, { hosts: [...egress.hosts, host] });
|
|
293
|
+
console.log(`Allowed ${host} too.\n`);
|
|
294
|
+
console.log(renderFixedIp(allowed.fixedIp, slug, allowed.mode));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
console.log(renderFixedIp(fixed, slug, updated.mode));
|
|
299
|
+
})
|
|
300
|
+
});
|
|
301
|
+
const fixedIpRemoveCommand = defineCommand({
|
|
302
|
+
meta: {
|
|
303
|
+
name: "remove",
|
|
304
|
+
description: "Stop routing a host through the fixed addresses"
|
|
305
|
+
},
|
|
306
|
+
args: {
|
|
307
|
+
host: {
|
|
308
|
+
type: "positional",
|
|
309
|
+
description: "Hostname to stop routing",
|
|
310
|
+
required: true
|
|
311
|
+
},
|
|
312
|
+
...projectDirArgs
|
|
313
|
+
},
|
|
314
|
+
run: reportErrors("egress fixed-ip remove", async ({ args }) => {
|
|
315
|
+
fixedIpSubcommandRan = true;
|
|
316
|
+
const { slug } = resolveProject(args);
|
|
317
|
+
const { view } = await loadFixedIp(slug);
|
|
318
|
+
requireOptional(view, slug);
|
|
319
|
+
const body = fixedIpRemoveBody(view, args.host);
|
|
320
|
+
if (!body) {
|
|
321
|
+
console.error(`${args.host.trim().toLowerCase()} isn't routed through the fixed addresses for ${slug} (see \`canopy egress fixed-ip\`).`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
const updated = await putFixedIp(slug, body, `stop routing ${args.host} for ${slug}`);
|
|
325
|
+
console.log(body.mode === "off" ? `That was the last routed host — fixed outbound addresses are off for ${slug}.` : `Requests of ${slug} to ${args.host} leave normally again.\n`);
|
|
326
|
+
if (body.mode !== "off") console.log(renderFixedIp(updated.fixedIp, slug, updated.mode));
|
|
327
|
+
})
|
|
328
|
+
});
|
|
329
|
+
const fixedIpAllCommand = defineCommand({
|
|
330
|
+
meta: {
|
|
331
|
+
name: "all",
|
|
332
|
+
description: "Send every outbound request from the fixed addresses"
|
|
333
|
+
},
|
|
334
|
+
args: { ...projectDirArgs },
|
|
335
|
+
run: reportErrors("egress fixed-ip all", async ({ args }) => {
|
|
336
|
+
fixedIpSubcommandRan = true;
|
|
337
|
+
const { slug } = resolveProject(args);
|
|
338
|
+
const { view } = await loadFixedIp(slug);
|
|
339
|
+
requireOptional(view, slug);
|
|
340
|
+
console.log(`${FIXED_IP_ALL_NOTE}\n`);
|
|
341
|
+
const updated = await putFixedIp(slug, { mode: "all" }, `route all of ${slug}'s requests through the fixed addresses`);
|
|
342
|
+
console.log(renderFixedIp(updated.fixedIp, slug, updated.mode));
|
|
343
|
+
})
|
|
344
|
+
});
|
|
345
|
+
const fixedIpOffCommand = defineCommand({
|
|
346
|
+
meta: {
|
|
347
|
+
name: "off",
|
|
348
|
+
description: "Turn fixed outbound addresses off — requests leave normally"
|
|
349
|
+
},
|
|
350
|
+
args: { ...projectDirArgs },
|
|
351
|
+
run: reportErrors("egress fixed-ip off", async ({ args }) => {
|
|
352
|
+
fixedIpSubcommandRan = true;
|
|
353
|
+
const { slug } = resolveProject(args);
|
|
354
|
+
const { view } = await loadFixedIp(slug);
|
|
355
|
+
requireOptional(view, slug);
|
|
356
|
+
if (view.mode === "off") {
|
|
357
|
+
console.log(`Fixed outbound addresses are already off for ${slug}.`);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
await putFixedIp(slug, { mode: "off" }, `turn fixed outbound addresses off for ${slug}`);
|
|
361
|
+
console.log(`Fixed outbound addresses are off for ${slug} — requests leave normally now.`);
|
|
362
|
+
})
|
|
363
|
+
});
|
|
364
|
+
const showFixedIp = reportErrors("egress fixed-ip", async ({ args }) => {
|
|
365
|
+
const { slug } = resolveProject(args);
|
|
366
|
+
const { egress, view } = await loadFixedIp(slug);
|
|
367
|
+
console.log(renderFixedIp(view, slug, egress.mode));
|
|
368
|
+
});
|
|
369
|
+
const egressCommand = defineCommand({
|
|
370
|
+
meta: {
|
|
371
|
+
name: "egress",
|
|
372
|
+
description: "Manage which outbound hosts the project's worker may fetch"
|
|
373
|
+
},
|
|
374
|
+
subCommands: {
|
|
375
|
+
list: listCommand,
|
|
376
|
+
allow: allowCommand,
|
|
377
|
+
remove: removeCommand,
|
|
378
|
+
mode: modeCommand,
|
|
379
|
+
log: logCommand,
|
|
380
|
+
"fixed-ip": defineCommand({
|
|
381
|
+
meta: {
|
|
382
|
+
name: "fixed-ip",
|
|
383
|
+
description: "Show or change which requests leave from Canopy's fixed outbound addresses"
|
|
384
|
+
},
|
|
385
|
+
args: { ...projectDirArgs },
|
|
386
|
+
subCommands: {
|
|
387
|
+
add: fixedIpAddCommand,
|
|
388
|
+
remove: fixedIpRemoveCommand,
|
|
389
|
+
all: fixedIpAllCommand,
|
|
390
|
+
off: fixedIpOffCommand
|
|
391
|
+
},
|
|
392
|
+
run: (context) => fixedIpSubcommandRan ? void 0 : showFixedIp(context)
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
//#endregion
|
|
397
|
+
export { egressCommand };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import {
|
|
3
|
-
import { a as apiRequest, c as credentials, i as apiDownload, o as apiUpload, s as commonHeaders } from "./api-
|
|
4
|
-
import { r as reportErrors, t as formatDateTime } from "./ui-
|
|
1
|
+
import { t as version } from "./package-CVDpzGgA.mjs";
|
|
2
|
+
import { f as tryReadCanopyJson, l as resolveProject, o as projectDirArgs, p as tryResolveProject, r as siteConfig } from "./site-0i7wAJqg.mjs";
|
|
3
|
+
import { a as apiRequest, c as credentials, i as apiDownload, o as apiUpload, s as commonHeaders } from "./api-Do9c8tZ_.mjs";
|
|
4
|
+
import { r as reportErrors, t as formatDateTime } from "./ui-CDUJq-X-.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { basename, extname, join } from "node:path";
|
|
7
7
|
import { readFile, writeFile } from "node:fs/promises";
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "./package-
|
|
3
|
-
import {
|
|
2
|
+
import { t as version } from "./package-CVDpzGgA.mjs";
|
|
3
|
+
import { i as siteHeaderLine, n as setInvocationProfile } from "./site-0i7wAJqg.mjs";
|
|
4
4
|
import { a as notifyOfUpdate, o as runRefresh } from "./version-check-CcsA1QjI.mjs";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
6
6
|
//#region src/index.ts
|
|
@@ -63,36 +63,37 @@ const main = defineCommand({
|
|
|
63
63
|
description: "canopy — create and deploy vibecoded projects"
|
|
64
64
|
},
|
|
65
65
|
subCommands: {
|
|
66
|
-
login: () => import("./login-
|
|
67
|
-
whoami: () => import("./login-
|
|
68
|
-
logout: () => import("./login-
|
|
69
|
-
create: () => import("./projects-
|
|
70
|
-
init: () => import("./projects-
|
|
71
|
-
projects: () => import("./projects-
|
|
72
|
-
delete: () => import("./projects-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
66
|
+
login: () => import("./login-CeNvHLUv.mjs").then((m) => m.loginCommand),
|
|
67
|
+
whoami: () => import("./login-CeNvHLUv.mjs").then((m) => m.whoamiCommand),
|
|
68
|
+
logout: () => import("./login-CeNvHLUv.mjs").then((m) => m.logoutCommand),
|
|
69
|
+
create: () => import("./projects-BWTIk80a.mjs").then((m) => m.createCommand),
|
|
70
|
+
init: () => import("./projects-BWTIk80a.mjs").then((m) => m.initCommand),
|
|
71
|
+
projects: () => import("./projects-BWTIk80a.mjs").then((m) => m.projectsCommand),
|
|
72
|
+
delete: () => import("./projects-BWTIk80a.mjs").then((m) => m.deleteCommand),
|
|
73
|
+
rename: () => import("./rename-BdIFrqvG.mjs").then((m) => m.renameCommand),
|
|
74
|
+
dev: () => import("./dev-Dj3wFKHe.mjs").then((m) => m.devCommand),
|
|
75
|
+
deploy: () => import("./deploy-CXMLABvH.mjs").then((m) => m.deployCommand),
|
|
76
|
+
promote: () => import("./deploy-CXMLABvH.mjs").then((m) => m.promoteCommand),
|
|
77
|
+
staging: () => import("./staging-ynPlMiHW.mjs").then((m) => m.stagingCommand),
|
|
78
|
+
rollback: () => import("./deploy-CXMLABvH.mjs").then((m) => m.rollbackCommand),
|
|
79
|
+
download: () => import("./deploy-CXMLABvH.mjs").then((m) => m.downloadCommand),
|
|
80
|
+
token: () => import("./token-CxiPrKn_.mjs").then((m) => m.tokenCommand),
|
|
81
|
+
egress: () => import("./egress-D6qyaaAn.mjs").then((m) => m.egressCommand),
|
|
82
|
+
secrets: () => import("./secrets-BVx8L0Gj.mjs").then((m) => m.secretsCommand),
|
|
83
|
+
schedule: () => import("./schedule-DpMIpCGC.mjs").then((m) => m.scheduleCommand),
|
|
84
|
+
feedback: () => import("./feedback-DMgUUl0V.mjs").then((m) => m.feedbackCommand),
|
|
85
|
+
db: () => import("./db-BNsX7sXo.mjs").then((m) => m.dbCommand),
|
|
86
|
+
usage: () => import("./usage-BtPsb7Iu.mjs").then((m) => m.usageCommand),
|
|
87
|
+
ai: () => import("./ai-DpQ1nLka.mjs").then((m) => m.aiCommand),
|
|
88
|
+
open: () => import("./open-D71Ms1nG.mjs").then((m) => m.openCommand),
|
|
89
|
+
browse: () => import("./browse-Dp5HytqK.mjs").then((m) => m.browseCommand),
|
|
90
|
+
docs: () => import("./docs-CkJGcCpR.mjs").then((m) => m.docsCommand),
|
|
91
|
+
skill: () => import("./skill-D58yV6K8.mjs").then((m) => m.skillCommand),
|
|
92
|
+
upgrade: () => import("./upgrade-Dll5rhOQ.mjs").then((m) => m.upgradeCommand),
|
|
93
|
+
logs: () => import("./logs-BzgJfWNP.mjs").then((m) => m.logsCommand),
|
|
94
|
+
mcp: () => import("./mcp-DFLaXiY_.mjs").then((m) => m.mcpCommand),
|
|
95
|
+
repo: () => import("./repo-JBKJqyoS.mjs").then((m) => m.repoCommand),
|
|
96
|
+
template: () => import("./project-template-BbmSmraT.mjs").then((m) => m.templateCommand)
|
|
96
97
|
}
|
|
97
98
|
});
|
|
98
99
|
const quiet = process.argv[2] === "mcp" && process.argv[3] === "serve" || process.argv[2] === "repo" && process.argv[3] === "credential";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as apiDataRequest } from "./api-
|
|
1
|
+
import { r as apiDataRequest } from "./api-Do9c8tZ_.mjs";
|
|
2
2
|
import { a as realtimeRouter, c as META_COLUMNS, l as blobRouter, n as WORKER_ATTEST_HEADER, o as dbRouter, s as hasPrivateNames } from "./src-RUf3Gfuh.mjs";
|
|
3
|
-
import { a as rewriteApiPath } from "./dev-
|
|
3
|
+
import { a as rewriteApiPath } from "./dev-CPR-9slK.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join, relative, sep } from "node:path";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import { n as agentHeaders } from "./api-
|
|
3
|
-
import { n as openBrowser, r as reportErrors } from "./ui-
|
|
1
|
+
import { _ as setActiveProfile, b as getBakedProfile, g as readConfigFile, h as listBakedProfiles, m as clearAllProfileTokens, r as siteConfig, v as upsertProfileAuth, x as isProfileId, y as writeConfigFile } from "./site-0i7wAJqg.mjs";
|
|
2
|
+
import { n as agentHeaders } from "./api-Do9c8tZ_.mjs";
|
|
3
|
+
import { n as openBrowser, r as reportErrors } from "./ui-CDUJq-X-.mjs";
|
|
4
4
|
import { n as clearRepoTokenCache } from "./repo-credential-CNDRM-tG.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { hostname } from "node:os";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { r as reportErrors } from "./ui-
|
|
1
|
+
import { l as resolveProject, s as projectFlagArgs } from "./site-0i7wAJqg.mjs";
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
|
|
3
|
+
import { r as reportErrors } from "./ui-CDUJq-X-.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
//#region src/logs.ts
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import {
|
|
3
|
-
import { a as apiRequest, t as ApiError } from "./api-
|
|
1
|
+
import { t as version } from "./package-CVDpzGgA.mjs";
|
|
2
|
+
import { c as projectUrl, d as stagingUrl, p as tryResolveProject, r as siteConfig } from "./site-0i7wAJqg.mjs";
|
|
3
|
+
import { a as apiRequest, d as noticeRenamesOnly, l as currentSlug, t as ApiError } from "./api-Do9c8tZ_.mjs";
|
|
4
4
|
import { t as liveDevServer } from "./dev-server-file-Dk9pk4AR.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
@@ -342,15 +342,16 @@ async function connect(args) {
|
|
|
342
342
|
requireSignIn();
|
|
343
343
|
const tokens = agentTokens(slug, channel);
|
|
344
344
|
await tokens.token();
|
|
345
|
+
const current = currentSlug(slug);
|
|
345
346
|
const upstream = await connectUpstream({
|
|
346
|
-
url: `${channel === "staging" ? stagingUrl(
|
|
347
|
-
project:
|
|
347
|
+
url: `${channel === "staging" ? stagingUrl(current) : projectUrl(current)}${MCP_PATH}`,
|
|
348
|
+
project: current,
|
|
348
349
|
tokens
|
|
349
350
|
});
|
|
350
351
|
const config = siteConfig();
|
|
351
352
|
return {
|
|
352
353
|
upstream,
|
|
353
|
-
slug,
|
|
354
|
+
slug: current,
|
|
354
355
|
channel,
|
|
355
356
|
where: `${config.profileId ?? config.apiUrl}, ${channel}`
|
|
356
357
|
};
|
|
@@ -375,6 +376,7 @@ const serveCommand = defineCommand({
|
|
|
375
376
|
async run({ args }) {
|
|
376
377
|
console.log = console.error;
|
|
377
378
|
console.info = console.error;
|
|
379
|
+
noticeRenamesOnly();
|
|
378
380
|
let connection;
|
|
379
381
|
try {
|
|
380
382
|
connection = await connect(args);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as apiDataRequest } from "./api-
|
|
1
|
+
import { f as tryReadCanopyJson } from "./site-0i7wAJqg.mjs";
|
|
2
|
+
import { r as apiDataRequest } from "./api-Do9c8tZ_.mjs";
|
|
3
3
|
import { readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
//#region src/migrations.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as openBrowser, r as reportErrors } from "./ui-
|
|
1
|
+
import { c as projectUrl, d as stagingUrl, l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
|
|
2
|
+
import { n as openBrowser, r as reportErrors } from "./ui-CDUJq-X-.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/open.ts
|
|
5
5
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
1
|
+
import { l as resolveProject, o as projectDirArgs } from "./site-0i7wAJqg.mjs";
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CDUJq-X-.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
/** Plain words for the API's template errors. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as apiRequest,
|
|
3
|
-
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-
|
|
4
|
-
import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-
|
|
5
|
-
import { i as offerRepoOnCreate, n as cloneProjectRepo } from "./repo-
|
|
1
|
+
import { c as projectUrl, f as tryReadCanopyJson, r as siteConfig } from "./site-0i7wAJqg.mjs";
|
|
2
|
+
import { a as apiRequest, f as withContext } from "./api-Do9c8tZ_.mjs";
|
|
3
|
+
import { a as unwrap, i as requireTty, r as reportErrors } from "./ui-CDUJq-X-.mjs";
|
|
4
|
+
import { i as nextSteps, n as describeCopy, r as fetchTemplate, t as TEMPLATE_FIXES } from "./project-template-BXfHpsI8.mjs";
|
|
5
|
+
import { i as offerRepoOnCreate, n as cloneProjectRepo } from "./repo-LSOxnmUk.mjs";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, relative, resolve, sep } from "node:path";
|