promigence 0.1.0-beta.12

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 (97) hide show
  1. package/NOTICE +14 -0
  2. package/README.md +88 -0
  3. package/dist/account.js +850 -0
  4. package/dist/agent.js +374 -0
  5. package/dist/argv.js +159 -0
  6. package/dist/bin.js +332 -0
  7. package/dist/bundle.js +133 -0
  8. package/dist/capacity.js +69 -0
  9. package/dist/commands.d.ts +110 -0
  10. package/dist/commands.js +2675 -0
  11. package/dist/connectreq.js +125 -0
  12. package/dist/credfile.js +188 -0
  13. package/dist/credits.js +197 -0
  14. package/dist/devcontainer.d.ts +99 -0
  15. package/dist/devcontainer.js +744 -0
  16. package/dist/device.js +129 -0
  17. package/dist/dirsnap.js +378 -0
  18. package/dist/errfix.js +262 -0
  19. package/dist/generated.d.ts +35 -0
  20. package/dist/generated.js +307 -0
  21. package/dist/github.js +307 -0
  22. package/dist/gitlab.js +210 -0
  23. package/dist/guide.d.ts +19 -0
  24. package/dist/guide.js +366 -0
  25. package/dist/handlers.js +511 -0
  26. package/dist/index.d.ts +14 -0
  27. package/dist/index.js +7 -0
  28. package/dist/init.d.ts +40 -0
  29. package/dist/init.js +352 -0
  30. package/dist/install.d.ts +23 -0
  31. package/dist/install.js +127 -0
  32. package/dist/io.d.ts +37 -0
  33. package/dist/io.js +318 -0
  34. package/dist/live.js +469 -0
  35. package/dist/login.js +1063 -0
  36. package/dist/mcp.js +889 -0
  37. package/dist/money.js +5 -0
  38. package/dist/orgs.js +475 -0
  39. package/dist/pricing.d.ts +52 -0
  40. package/dist/pricing.js +181 -0
  41. package/dist/report.js +132 -0
  42. package/dist/routes.js +135 -0
  43. package/dist/runners-github.js +257 -0
  44. package/dist/runners-gitlab.js +125 -0
  45. package/dist/runners-spec.js +137 -0
  46. package/dist/runners.js +1458 -0
  47. package/dist/sdk/blake3.d.ts +10 -0
  48. package/dist/sdk/blake3.js +111 -0
  49. package/dist/sdk/bundle.d.ts +96 -0
  50. package/dist/sdk/bundle.js +227 -0
  51. package/dist/sdk/ca-guard.js +166 -0
  52. package/dist/sdk/caller.d.ts +73 -0
  53. package/dist/sdk/caller.js +47 -0
  54. package/dist/sdk/client.d.ts +3015 -0
  55. package/dist/sdk/client.js +2583 -0
  56. package/dist/sdk/command-errors.d.ts +41 -0
  57. package/dist/sdk/command-errors.js +36 -0
  58. package/dist/sdk/context.d.ts +119 -0
  59. package/dist/sdk/context.js +588 -0
  60. package/dist/sdk/core.d.ts +285 -0
  61. package/dist/sdk/core.js +266 -0
  62. package/dist/sdk/credstore.d.ts +90 -0
  63. package/dist/sdk/credstore.js +206 -0
  64. package/dist/sdk/dispatcher.js +86 -0
  65. package/dist/sdk/github.d.ts +158 -0
  66. package/dist/sdk/github.js +78 -0
  67. package/dist/sdk/gitlab.d.ts +104 -0
  68. package/dist/sdk/gitlab.js +20 -0
  69. package/dist/sdk/handles.d.ts +82 -0
  70. package/dist/sdk/handles.js +135 -0
  71. package/dist/sdk/hint.js +7 -0
  72. package/dist/sdk/http2.js +1921 -0
  73. package/dist/sdk/index.d.ts +34 -0
  74. package/dist/sdk/index.js +15 -0
  75. package/dist/sdk/keyed.gen.js +7 -0
  76. package/dist/sdk/models.gen.d.ts +6162 -0
  77. package/dist/sdk/models.gen.js +1 -0
  78. package/dist/sdk/proxy.js +249 -0
  79. package/dist/sdk/pty.d.ts +171 -0
  80. package/dist/sdk/pty.js +496 -0
  81. package/dist/sdk/sandbox.d.ts +537 -0
  82. package/dist/sdk/sandbox.js +725 -0
  83. package/dist/sdk/ssh.d.ts +25 -0
  84. package/dist/sdk/ssh.js +114 -0
  85. package/dist/sdk/trace.js +80 -0
  86. package/dist/sdk/transport-worker.js +466 -0
  87. package/dist/sdk/warm.js +40 -0
  88. package/dist/ssh.js +267 -0
  89. package/dist/surface.d.ts +95 -0
  90. package/dist/surface.js +1452 -0
  91. package/dist/templates.js +53 -0
  92. package/dist/term.js +50 -0
  93. package/dist/timing.js +71 -0
  94. package/dist/untar.js +324 -0
  95. package/dist/uploadguard.js +75 -0
  96. package/llms.txt +209 -0
  97. package/package.json +52 -0
package/dist/github.js ADDED
@@ -0,0 +1,307 @@
1
+ import { PromigenceError, parseGithubRepo, parseGitlabRepo, validGitRef, GITLAB_REPO_PREFIX, } from "./sdk/index.js";
2
+ import { bool, client, list, nexts, positional, str } from "./commands.js";
3
+ import { buildPrice } from "./pricing.js";
4
+ import { emit, okEnvelope, parseCount, parseDuration, usageError, write } from "./io.js";
5
+ import { headless, openBrowser } from "./login.js";
6
+ import { jsonLine, sh } from "./term.js";
7
+ import { gitlabRepos } from "./gitlab.js";
8
+ import { consoleConnect, consoleTicket } from "./connectreq.js";
9
+ export const CONNECT_WAIT_DEFAULT = "10m";
10
+ const POLL_MS_DEFAULT = 2000;
11
+ function pollMs() {
12
+ const v = Number(process.env["PROMIGENCE_GITHUB_POLL_MS"]);
13
+ return Number.isFinite(v) && v > 0 ? v : POLL_MS_DEFAULT;
14
+ }
15
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
16
+ function notConfigured(s) {
17
+ return usageError(s.message ?? "Connecting GitHub isn't available here yet", "Bring the code in another way: `promigence snapshot create --dir .` uploads the files git tracks here, or clone a public repository in a --setup step.");
18
+ }
19
+ function connectionLine(c) {
20
+ return ` ${c.account} ${c.account_type} · ${c.repository_selection} repositories · installation ${c.installation_id}${c.suspended ? " SUSPENDED on GitHub" : ""}`;
21
+ }
22
+ function trackLine(t) {
23
+ const state = t.build_id
24
+ ? `building ${t.build_id}`
25
+ : t.built_sha
26
+ ? `built ${t.built_sha.slice(0, 7)}${t.snapshot_id ? ` (${t.snapshot_id})` : ""}`
27
+ : "not built yet";
28
+ return ` ${t.track_id} ${t.repo}:${t.branch} -> ${t.alias} ${state}${t.last_error ? ` last error: ${t.last_error}` : ""}`;
29
+ }
30
+ const githubConnect = async (ctx) => {
31
+ const bound = parseDuration(str(ctx, "--wait-timeout") ?? CONNECT_WAIT_DEFAULT, "--wait-timeout");
32
+ const c = client(ctx);
33
+ const before = await c.github.status();
34
+ if (!before.configured)
35
+ throw notConfigured(before);
36
+ const ticket = await consoleTicket(() => c.github.ticket());
37
+ if (ticket) {
38
+ return consoleConnect(ctx, "github", {
39
+ ticket,
40
+ poll: () => c.github.request(ticket.request_id),
41
+ connections: async (r) => (await c.github.status()).connections.filter((x) => x.account === r.account),
42
+ bound,
43
+ waitText: str(ctx, "--wait-timeout") ?? CONNECT_WAIT_DEFAULT,
44
+ pollMs: pollMs(),
45
+ okNext: [
46
+ ["promigence repos", "the repositories the connection can read"],
47
+ ["promigence snapshot create --repo <owner>/<repo> --ref main --alias app --wait", "build a snapshot from one"],
48
+ ],
49
+ });
50
+ }
51
+ const known = new Set(before.connections.map((x) => `${x.installation_id}@${x.connected_at}`));
52
+ const link = await c.github.connect();
53
+ const opened = bool(ctx, "--no-open") || headless() ? false : openBrowser(link.install_url);
54
+ const noWait = bool(ctx, "--no-wait");
55
+ if (ctx.mode === "json") {
56
+ await write(process.stderr, jsonLine(process.stderr, {
57
+ hint: "install_url", install_url: link.install_url, expires_at: link.expires_at, opened,
58
+ }));
59
+ }
60
+ else {
61
+ await write(process.stderr, [
62
+ opened
63
+ ? "Opening GitHub to install the App on your account; then confirm by signing in to Promigence in the same browser…"
64
+ : "Open this link to install the App on your GitHub account, then confirm by signing in to Promigence in the same browser:",
65
+ ` ${opened ? "If it did not open: " : ""}${link.install_url}`,
66
+ " Choose the account, and all repositories or only the ones you pick. The link works for 15 minutes, for whoever opens it: keep it to yourself.",
67
+ ...(noWait ? [] : [` Waiting up to ${str(ctx, "--wait-timeout") ?? CONNECT_WAIT_DEFAULT} for GitHub (Ctrl-C stops waiting; the install still completes).`]),
68
+ ].join("\n") + "\n");
69
+ }
70
+ if (noWait) {
71
+ const next = nexts(["promigence github status", "after the install: the connected account"]);
72
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, {
73
+ install_url: link.install_url, expires_at: link.expires_at, opened, waited: false, connected: [],
74
+ }, next), [`install link: ${link.install_url}`, ` next: ${next[0].cmd}`], 0, ctx.pointer);
75
+ }
76
+ const t0 = Date.now();
77
+ let saidPending = false;
78
+ for (;;) {
79
+ await sleep(Math.min(pollMs(), Math.max(0, bound - (Date.now() - t0))));
80
+ const now = await c.github.status();
81
+ const fresh = now.connections.filter((x) => !known.has(`${x.installation_id}@${x.connected_at}`));
82
+ if (fresh.length) {
83
+ const next = nexts(["promigence repos", "the repositories the connection can read"], [sh `promigence snapshot create --repo ${`${fresh[0].account}/<repo>`} --ref main --alias app --wait`, "build a snapshot from one"]);
84
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, {
85
+ install_url: link.install_url, opened, waited: true, waited_ms: Date.now() - t0, connected: fresh,
86
+ }, next), [
87
+ `connected: ${fresh.map((x) => x.account).join(", ")}`,
88
+ ...fresh.map(connectionLine),
89
+ ` next: ${next[0].cmd}`,
90
+ ], 0, ctx.pointer);
91
+ }
92
+ if (!saidPending && now.pending.length && ctx.mode !== "json") {
93
+ saidPending = true;
94
+ await write(process.stderr, " GitHub has not confirmed the install to us yet; still waiting…\n");
95
+ }
96
+ if (Date.now() - t0 >= bound) {
97
+ throw new PromigenceError({
98
+ code: "timeout",
99
+ message: `no new GitHub connection within ${str(ctx, "--wait-timeout") ?? CONNECT_WAIT_DEFAULT}`,
100
+ fix: `Finish the install in the browser (the link works until ${link.expires_at}), then \`promigence github status\`; or run \`promigence github connect\` again for a new link. If you only changed which repositories an existing connection can read, that already took effect: \`promigence repos\`.`,
101
+ exitCode: 11,
102
+ });
103
+ }
104
+ }
105
+ };
106
+ const githubStatus = async (ctx) => {
107
+ const s = await client(ctx).github.status();
108
+ if (!s.configured) {
109
+ const next = nexts(["promigence snapshot create --dir . --alias app --wait", "bring the files git tracks here instead"]);
110
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...s }, next), [`github: ${s.message ?? "Connecting GitHub isn't available here yet"}`], 0, ctx.pointer);
111
+ }
112
+ const lines = [
113
+ s.connections.length
114
+ ? `github: connected to ${s.connections.length} account${s.connections.length === 1 ? "" : "s"}`
115
+ : "github: not connected",
116
+ ...s.connections.map(connectionLine),
117
+ ];
118
+ if (s.pending.length) {
119
+ lines.push("pending:", ...s.pending.map((p) => ` installation ${p.installation_id}${p.account ? ` (${p.account})` : ""}: ${p.message}`));
120
+ }
121
+ if (s.tracks.length)
122
+ lines.push("tracked branches (each push rebuilds, billed per second):", ...s.tracks.map(trackLine));
123
+ const next = s.connections.length
124
+ ? nexts(["promigence repos", "the repositories the connection can read"])
125
+ : nexts(["promigence github connect", "install the App on your GitHub account"]);
126
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...s }, next), lines, 0, ctx.pointer);
127
+ };
128
+ function notFound(message, fix) {
129
+ return new PromigenceError({ code: "not_found", message, fix, exitCode: 8 });
130
+ }
131
+ const githubDisconnect = async (ctx) => {
132
+ const arg = positional(ctx, "an installation id or a GitHub account", "promigence github disconnect acme");
133
+ const c = client(ctx);
134
+ let id = arg;
135
+ if (!/^\d+$/.test(arg)) {
136
+ const s = await c.github.status();
137
+ if (!s.configured)
138
+ throw notConfigured(s);
139
+ const hits = s.connections.filter((x) => x.account.toLowerCase() === arg.toLowerCase());
140
+ if (hits.length !== 1) {
141
+ throw notFound(`no GitHub connection for the account ${JSON.stringify(arg)}`, `\`promigence github status\` lists the connected accounts${s.connections.length ? ` (${s.connections.map((x) => x.account).join(", ")})` : ""}; pass the account or its installation id.`);
142
+ }
143
+ id = String(hits[0].installation_id);
144
+ }
145
+ const d = await c.github.disconnect(id);
146
+ const next = nexts(["promigence github status", "what is still connected"]);
147
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...d }, next), [
148
+ `disconnected: ${d.account} (installation ${d.installation_id})${d.tracks_removed ? `; ${d.tracks_removed} tracked branch${d.tracks_removed === 1 ? "" : "es"} stopped` : ""}`,
149
+ ` The App stays installed on GitHub until you uninstall it there: ${d.uninstall_url}`,
150
+ ], 0, ctx.pointer);
151
+ };
152
+ const githubUntrack = async (ctx) => {
153
+ const arg = positional(ctx, "a track id or the alias it moves", "promigence github untrack app-main");
154
+ const c = client(ctx);
155
+ let id = arg;
156
+ if (!arg.startsWith("trk_")) {
157
+ const s = await c.github.status();
158
+ if (!s.configured)
159
+ throw notConfigured(s);
160
+ const hits = s.tracks.filter((t) => t.alias === arg);
161
+ if (hits.length !== 1) {
162
+ throw notFound(`no tracked branch moves the alias ${JSON.stringify(arg)}`, "`promigence github status` lists the tracked branches; pass the alias or the trk_ id.");
163
+ }
164
+ id = hits[0].track_id;
165
+ }
166
+ const u = await c.github.untrack(id);
167
+ const next = nexts(["promigence github status", "the branches still tracked"]);
168
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...u }, next), [
169
+ `untracked: ${u.repo}:${u.branch} no longer rebuilds ${u.alias} on a push; the snapshot itself stays`,
170
+ ], 0, ctx.pointer);
171
+ };
172
+ const repos = async (ctx) => {
173
+ const provider = str(ctx, "--provider") ?? "github";
174
+ if (provider === "gitlab")
175
+ return gitlabRepos(ctx);
176
+ if (provider !== "github") {
177
+ throw usageError(`--provider must be github or gitlab, got ${JSON.stringify(provider)}`, "Pass --provider gitlab for GitLab projects; GitHub is the default.");
178
+ }
179
+ if (str(ctx, "--connection") !== undefined) {
180
+ throw usageError("--connection is for --provider gitlab", "Drop it, or add --provider gitlab.");
181
+ }
182
+ const pageArg = str(ctx, "--page");
183
+ const page = pageArg === undefined ? 1 : parseCount(pageArg, "--page");
184
+ const c = client(ctx);
185
+ const r = await c.github.repos({ page });
186
+ if (r.total === 0 && r.repos.length === 0) {
187
+ const s = await c.github.status().catch(() => undefined);
188
+ if (s && !s.configured)
189
+ throw notConfigured(s);
190
+ const next = nexts(["promigence github connect", "install the App on your GitHub account"]);
191
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...r }, next), [
192
+ s?.connections.length
193
+ ? "repos: none — the connection can read no repository; give the App access to some on GitHub"
194
+ : "repos: none — connect GitHub first",
195
+ ` next: ${next[0].cmd}`,
196
+ ], 0, ctx.pointer);
197
+ }
198
+ const width = Math.min(48, Math.max(...r.repos.map((x) => x.full_name.length), 10));
199
+ const lines = [
200
+ `repos: ${r.total}${r.has_more || page > 1 ? ` (page ${r.page})` : ""}`,
201
+ ...r.repos.map((x) => ` ${x.full_name.padEnd(width)} ${x.private ? "private" : "public "} ${x.default_branch}${x.archived ? " archived" : ""}`),
202
+ ];
203
+ if (r.has_more)
204
+ lines.push(` more: ${sh `promigence repos --page ${r.page + 1}`}`);
205
+ const first = r.repos[0];
206
+ const next = first
207
+ ? nexts([sh `promigence snapshot create --repo ${first.full_name} --ref ${first.default_branch} --alias app --wait`, "build a snapshot from it"])
208
+ : nexts(["promigence repos --page 1", "the first page"]);
209
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...r }, next), lines, 0, ctx.pointer);
210
+ };
211
+ const NOT_WITH_GITHUB = [
212
+ ["--image", "the build starts from a snapshot: build the image first (`snapshot create --image REF --alias base`), then pass --from base"],
213
+ ["--dockerfile", "the build starts from a snapshot: build the Dockerfile first, then pass --from <its alias>"],
214
+ ["--context", "it belongs to --dockerfile"],
215
+ ["--devcontainer", "it is not read for a connected repository; use --setup steps"],
216
+ ["--start", "a background process is part of an image snapshot"],
217
+ ["--also-repo", "clone further repositories in a --setup step"],
218
+ ["--dir", "--dir uploads a local tree instead; pass one of them"],
219
+ ];
220
+ const GITHUB_ONLY = ["--ref", "--from", "--track"];
221
+ export async function githubCreate(ctx) {
222
+ const repoArg = str(ctx, "--repo");
223
+ const isGitlab = typeof repoArg === "string" && repoArg.trim().startsWith(GITLAB_REPO_PREFIX);
224
+ const glPath = isGitlab ? parseGitlabRepo(repoArg) : undefined;
225
+ if (isGitlab && !glPath) {
226
+ throw usageError(`--repo ${JSON.stringify(repoArg)} is not a GitLab project path`, "Pass --repo gitlab:group/project (see `promigence repos --provider gitlab`).");
227
+ }
228
+ const gh = glPath ? { owner: "", name: "" } : parseGithubRepo(repoArg);
229
+ const shown = glPath ? `gitlab:${glPath}` : gh ? `${gh.owner}/${gh.name}` : "";
230
+ if (!gh) {
231
+ const stray = GITHUB_ONLY.find((f) => bool(ctx, f));
232
+ if (stray) {
233
+ throw usageError(`${stray} is for a connected GitHub repository (--repo owner/name)`, "Pass --repo your-org/app (see `promigence repos`), or drop it: a git URL is built at --commit SHA.");
234
+ }
235
+ return undefined;
236
+ }
237
+ for (const [flag, why] of NOT_WITH_GITHUB) {
238
+ if (bool(ctx, flag))
239
+ throw usageError(`${flag} is not taken with --repo ${shown}`, `Drop ${flag}: ${why}.`);
240
+ }
241
+ const ref = str(ctx, "--ref");
242
+ const commit = str(ctx, "--commit");
243
+ if (ref !== undefined && commit !== undefined) {
244
+ throw usageError("--ref and --commit name the same thing twice", "Pass one: a branch or tag as --ref, or a sha as --commit.");
245
+ }
246
+ if (ref !== undefined && !validGitRef(ref.trim())) {
247
+ throw usageError(`--ref must be a branch, a tag or a commit sha, got ${JSON.stringify(ref)}`, "Example: --ref main, --ref v1.2.0, or --ref $(git rev-parse HEAD).");
248
+ }
249
+ if (commit !== undefined && !/^[0-9a-f]{40}$/i.test(commit)) {
250
+ throw usageError(`--commit must be a full 40-character sha, got ${JSON.stringify(commit)}`, "Pass --commit $(git rev-parse HEAD), or a branch or tag as --ref.");
251
+ }
252
+ const track = bool(ctx, "--track");
253
+ const alias = str(ctx, "--alias");
254
+ if (track && alias === undefined) {
255
+ throw usageError("--track needs --alias", "Every push builds the snapshot again and moves the alias to it, so the alias is how you start the newest one: --alias app-main --track.");
256
+ }
257
+ if (track && commit !== undefined) {
258
+ throw usageError("--track follows a branch, not a commit", "Pass --ref <branch> --track (no --ref: the default branch).");
259
+ }
260
+ const repo = glPath ? `gitlab:${glPath}` : `github:${gh.owner}/${gh.name}`;
261
+ const from = str(ctx, "--from");
262
+ const setup = list(ctx, "--setup");
263
+ const verify = str(ctx, "--verify");
264
+ const tier = str(ctx, "--tier");
265
+ const opts = { repo };
266
+ if (ref !== undefined)
267
+ opts.ref = ref.trim();
268
+ if (commit !== undefined)
269
+ opts.commit = commit;
270
+ if (from !== undefined)
271
+ opts.from = from;
272
+ if (setup.length)
273
+ opts.setup = setup;
274
+ if (verify !== undefined)
275
+ opts.verify = { cmd: verify };
276
+ if (tier !== undefined)
277
+ opts.tier = tier;
278
+ if (track)
279
+ opts.track = true;
280
+ const at = ref ?? (commit ? commit.slice(0, 7) : undefined);
281
+ const label = at === undefined ? `${repo} (default branch)` : `${repo}@${at}`;
282
+ const submitted = {
283
+ build_price: null, repo,
284
+ ...(ref === undefined ? {} : { ref: ref.trim() }),
285
+ ...(commit === undefined ? {} : { commit: commit.toLowerCase() }),
286
+ ...(from === undefined ? {} : { from }),
287
+ ...(tier === undefined ? {} : { requested_tier: tier }),
288
+ ...(track ? { track: true } : {}),
289
+ };
290
+ if (track) {
291
+ const branch = ref ?? "the default branch";
292
+ const each = (await buildPrice(client(ctx), tier)).most_usd;
293
+ await write(process.stderr, ctx.mode === "json"
294
+ ? jsonLine(process.stderr, { hint: "track", repo, branch, alias, build_price_each: each, unit: "USD" })
295
+ : glPath
296
+ ? `tracking: every new commit on ${branch} (checked every minute) rebuilds this snapshot and moves ${alias} to it, each build billed per second while it runs (up to $${each.toFixed(2)}), charged to your org; stop with \`${sh `promigence gitlab untrack ${alias}`}\`\n`
297
+ : `tracking: every push to ${branch} rebuilds this snapshot and moves ${alias} to it, each build billed per second while it runs (up to $${each.toFixed(2)}), charged to your org; stop with \`${sh `promigence github untrack ${alias}`}\`\n`);
298
+ }
299
+ return { opts, label, submitted, tier };
300
+ }
301
+ export const GITHUB_HANDLERS = {
302
+ "github connect": githubConnect,
303
+ "github status": githubStatus,
304
+ "github disconnect": githubDisconnect,
305
+ "github untrack": githubUntrack,
306
+ repos,
307
+ };
package/dist/gitlab.js ADDED
@@ -0,0 +1,210 @@
1
+ import { PromigenceError, } from "./sdk/index.js";
2
+ import { bool, client, nexts, positional, str } from "./commands.js";
3
+ import { emit, okEnvelope, parseCount, parseDuration, usageError, write } from "./io.js";
4
+ import { headless, openBrowser } from "./login.js";
5
+ import { jsonLine, sh } from "./term.js";
6
+ import { consoleConnect, consoleTicket } from "./connectreq.js";
7
+ export const CONNECT_WAIT_DEFAULT = "10m";
8
+ const POLL_MS_DEFAULT = 2000;
9
+ function pollMs() {
10
+ const v = Number(process.env["PROMIGENCE_GITHUB_POLL_MS"]);
11
+ return Number.isFinite(v) && v > 0 ? v : POLL_MS_DEFAULT;
12
+ }
13
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
14
+ function notConfigured(s) {
15
+ return usageError(s.message ?? "Connecting GitLab isn't available here yet", "Bring the code in another way: `promigence snapshot create --dir .` uploads the files git tracks here, or clone in a --setup step.");
16
+ }
17
+ function connectionLine(c) {
18
+ return ` ${c.username} on ${c.gitlab_url} · ${c.connection_id}${c.broken ? ` BROKEN: ${c.broken_reason ?? "connect again"}` : ""}`;
19
+ }
20
+ function trackLine(t) {
21
+ const state = t.build_id
22
+ ? `building ${t.build_id}`
23
+ : t.built_sha
24
+ ? `built ${t.built_sha.slice(0, 7)}${t.snapshot_id ? ` (${t.snapshot_id})` : ""}`
25
+ : "not built yet";
26
+ return ` ${t.track_id} ${t.repo}:${t.branch} -> ${t.alias} ${state}${t.last_error ? ` last error: ${t.last_error}` : ""}`;
27
+ }
28
+ function notFound(message, fix) {
29
+ return new PromigenceError({ code: "not_found", message, fix, exitCode: 8 });
30
+ }
31
+ const gitlabConnect = async (ctx) => {
32
+ const bound = parseDuration(str(ctx, "--wait-timeout") ?? CONNECT_WAIT_DEFAULT, "--wait-timeout");
33
+ const c = client(ctx);
34
+ const before = await c.gitlab.status();
35
+ if (!before.configured)
36
+ throw notConfigured(before);
37
+ const ticket = await consoleTicket(() => c.gitlab.ticket());
38
+ if (ticket) {
39
+ return consoleConnect(ctx, "gitlab", {
40
+ ticket,
41
+ poll: () => c.gitlab.request(ticket.request_id),
42
+ connections: async (r) => (await c.gitlab.status()).connections.filter((x) => x.username === (r.account ?? "").split(" on ")[0]),
43
+ bound,
44
+ waitText: str(ctx, "--wait-timeout") ?? CONNECT_WAIT_DEFAULT,
45
+ pollMs: pollMs(),
46
+ okNext: [
47
+ ["promigence repos --provider gitlab", "the projects the connection can read"],
48
+ ["promigence snapshot create --repo gitlab:<group>/<project> --ref main --alias app --wait", "build a snapshot from one"],
49
+ ],
50
+ });
51
+ }
52
+ const known = new Set(before.connections.map((x) => x.connection_id));
53
+ const link = await c.gitlab.connect();
54
+ const opened = bool(ctx, "--no-open") || headless() ? false : openBrowser(link.authorize_url);
55
+ const noWait = bool(ctx, "--no-wait");
56
+ if (ctx.mode === "json") {
57
+ await write(process.stderr, jsonLine(process.stderr, {
58
+ hint: "authorize_url", authorize_url: link.authorize_url, expires_at: link.expires_at, opened, scopes: link.scopes,
59
+ }));
60
+ }
61
+ else {
62
+ await write(process.stderr, [
63
+ opened
64
+ ? `Opening ${link.gitlab_url} to authorize the Promigence application (read-only)…`
65
+ : `Open this link to authorize the Promigence application on ${link.gitlab_url} (read-only):`,
66
+ ` ${opened ? "If it did not open: " : ""}${link.authorize_url}`,
67
+ " Then sign in to Promigence on the page it shows: an owner of this organization confirms the connection there. The link works for 15 minutes, for whoever opens it: keep it to yourself.",
68
+ ...(noWait ? [] : [` Waiting up to ${str(ctx, "--wait-timeout") ?? CONNECT_WAIT_DEFAULT} (Ctrl-C stops waiting).`]),
69
+ ].join("\n") + "\n");
70
+ }
71
+ if (noWait) {
72
+ const next = nexts(["promigence gitlab status", "after the confirmation: the connection"]);
73
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, {
74
+ authorize_url: link.authorize_url, expires_at: link.expires_at, opened, waited: false, connected: [],
75
+ }, next), [`authorize link: ${link.authorize_url}`, ` next: ${next[0].cmd}`], 0, ctx.pointer);
76
+ }
77
+ const t0 = Date.now();
78
+ let saidPending = false;
79
+ for (;;) {
80
+ await sleep(Math.min(pollMs(), Math.max(0, bound - (Date.now() - t0))));
81
+ const now = await c.gitlab.status();
82
+ const fresh = now.connections.filter((x) => !known.has(x.connection_id));
83
+ if (fresh.length) {
84
+ const next = nexts(["promigence repos --provider gitlab", "the projects the connection can read"], [sh `promigence snapshot create --repo ${`gitlab:${fresh[0].username}/<project>`} --ref main --alias app --wait`, "build a snapshot from one"]);
85
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, {
86
+ authorize_url: link.authorize_url, opened, waited: true, waited_ms: Date.now() - t0, connected: fresh,
87
+ }, next), [
88
+ `connected: ${fresh.map((x) => x.username).join(", ")}`,
89
+ ...fresh.map(connectionLine),
90
+ ` next: ${next[0].cmd}`,
91
+ ], 0, ctx.pointer);
92
+ }
93
+ if (!saidPending && now.pending.length && ctx.mode !== "json") {
94
+ saidPending = true;
95
+ await write(process.stderr, " Authorized on GitLab; waiting for an owner to confirm it in that browser…\n");
96
+ }
97
+ if (Date.now() - t0 >= bound) {
98
+ throw new PromigenceError({
99
+ code: "timeout",
100
+ message: `no new GitLab connection within ${str(ctx, "--wait-timeout") ?? CONNECT_WAIT_DEFAULT}`,
101
+ fix: `Finish in the browser (the link works until ${link.expires_at}): authorize on GitLab, then sign in to Promigence as an owner of this organization on the page that follows. Then \`promigence gitlab status\`; or run \`promigence gitlab connect\` again for a new link.`,
102
+ exitCode: 11,
103
+ });
104
+ }
105
+ }
106
+ };
107
+ const gitlabStatus = async (ctx) => {
108
+ const s = await client(ctx).gitlab.status();
109
+ if (!s.configured) {
110
+ const next = nexts(["promigence snapshot create --dir . --alias app --wait", "bring the files git tracks here instead"]);
111
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...s }, next), [`gitlab: ${s.message ?? "Connecting GitLab isn't available here yet"}`], 0, ctx.pointer);
112
+ }
113
+ const lines = [
114
+ s.connections.length
115
+ ? `gitlab: connected to ${s.connections.length} GitLab user${s.connections.length === 1 ? "" : "s"}`
116
+ : "gitlab: not connected",
117
+ ...s.connections.map(connectionLine),
118
+ ];
119
+ if (s.pending.length) {
120
+ lines.push("waiting for an owner's confirmation:", ...s.pending.map((p) => ` ${p.username} (${p.connection_id}): ${p.message}`));
121
+ }
122
+ if (s.tracks.length) {
123
+ lines.push("tracked branches (each new commit rebuilds, billed per second; checked every minute):", ...s.tracks.map(trackLine));
124
+ }
125
+ if (s.connections.length) {
126
+ lines.push("optional, for instant rebuilds: a project webhook (push events) with each connection's URL and token in --json (webhook_url, webhook_token)");
127
+ }
128
+ const next = s.connections.length
129
+ ? nexts(["promigence repos --provider gitlab", "the projects the connection can read"])
130
+ : nexts(["promigence gitlab connect", "authorize the application on GitLab (read-only)"]);
131
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...s }, next), lines, 0, ctx.pointer);
132
+ };
133
+ const gitlabDisconnect = async (ctx) => {
134
+ const arg = positional(ctx, "a connection id or a GitLab username", "promigence gitlab disconnect octo-dev");
135
+ const c = client(ctx);
136
+ let id = arg;
137
+ if (!arg.startsWith("glc_")) {
138
+ const s = await c.gitlab.status();
139
+ if (!s.configured)
140
+ throw notConfigured(s);
141
+ const hits = s.connections.filter((x) => x.username.toLowerCase() === arg.toLowerCase());
142
+ if (hits.length !== 1) {
143
+ throw notFound(`no GitLab connection for the user ${JSON.stringify(arg)}`, `\`promigence gitlab status\` lists the connections${s.connections.length ? ` (${s.connections.map((x) => x.username).join(", ")})` : ""}; pass the username or its glc_ id.`);
144
+ }
145
+ id = hits[0].connection_id;
146
+ }
147
+ const d = await c.gitlab.disconnect(id);
148
+ const next = nexts(["promigence gitlab status", "what is still connected"]);
149
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...d }, next), [
150
+ `disconnected: ${d.username} (${d.connection_id})${d.tracks_removed ? `; ${d.tracks_removed} tracked branch${d.tracks_removed === 1 ? "" : "es"} stopped` : ""}`,
151
+ ` ${d.message}`,
152
+ ], 0, ctx.pointer);
153
+ };
154
+ const gitlabUntrack = async (ctx) => {
155
+ const arg = positional(ctx, "a track id or the alias it moves", "promigence gitlab untrack app-main");
156
+ const c = client(ctx);
157
+ let id = arg;
158
+ if (!arg.startsWith("trk_")) {
159
+ const s = await c.gitlab.status();
160
+ if (!s.configured)
161
+ throw notConfigured(s);
162
+ const hits = s.tracks.filter((t) => t.alias === arg);
163
+ if (hits.length !== 1) {
164
+ throw notFound(`no tracked GitLab branch moves the alias ${JSON.stringify(arg)}`, "`promigence gitlab status` lists the tracked branches; pass the alias or the trk_ id.");
165
+ }
166
+ id = hits[0].track_id;
167
+ }
168
+ const u = await c.gitlab.untrack(id);
169
+ const next = nexts(["promigence gitlab status", "the branches still tracked"]);
170
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...u }, next), [
171
+ `untracked: ${u.repo}:${u.branch} no longer rebuilds ${u.alias} on a new commit; the snapshot itself stays`,
172
+ ], 0, ctx.pointer);
173
+ };
174
+ export const gitlabRepos = async (ctx) => {
175
+ const pageArg = str(ctx, "--page");
176
+ const page = pageArg === undefined ? 1 : parseCount(pageArg, "--page");
177
+ const c = client(ctx);
178
+ const connectionId = str(ctx, "--connection");
179
+ const r = await c.gitlab.repos({ page, ...(connectionId ? { connectionId } : {}) });
180
+ if (r.repos.length === 0 && page === 1) {
181
+ const s = await c.gitlab.status().catch(() => undefined);
182
+ if (s && !s.configured)
183
+ throw notConfigured(s);
184
+ const next = nexts(["promigence gitlab connect", "authorize the application on GitLab (read-only)"]);
185
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...r }, next), [
186
+ s?.connections.length
187
+ ? "repos: none — the connected GitLab user is a member of no project they can read"
188
+ : "repos: none — connect GitLab first",
189
+ ` next: ${next[0].cmd}`,
190
+ ], 0, ctx.pointer);
191
+ }
192
+ const width = Math.min(56, Math.max(...r.repos.map((x) => x.path.length), 10));
193
+ const lines = [
194
+ `repos (gitlab): ${r.repos.length}${r.has_more || page > 1 ? ` (page ${r.page})` : ""}`,
195
+ ...r.repos.map((x) => ` ${x.path.padEnd(width)} ${x.private ? "private" : "public "} ${x.default_branch}${x.archived ? " archived" : ""}`),
196
+ ];
197
+ if (r.has_more)
198
+ lines.push(` more: ${sh `promigence repos --provider gitlab --page ${r.page + 1}`}`);
199
+ const first = r.repos[0];
200
+ const next = first
201
+ ? nexts([sh `promigence snapshot create --repo ${`gitlab:${first.path}`} --ref ${first.default_branch} --alias app --wait`, "build a snapshot from it"])
202
+ : nexts(["promigence repos --provider gitlab --page 1", "the first page"]);
203
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...r }, next), lines, 0, ctx.pointer);
204
+ };
205
+ export const GITLAB_HANDLERS = {
206
+ "gitlab connect": gitlabConnect,
207
+ "gitlab status": gitlabStatus,
208
+ "gitlab disconnect": gitlabDisconnect,
209
+ "gitlab untrack": gitlabUntrack,
210
+ };
@@ -0,0 +1,19 @@
1
+ import { type ErrorEntry } from "./generated.js";
2
+ export declare const BILLED_TEXT: Readonly<Record<string, string>>;
3
+ import type { Prices } from "./pricing.js";
4
+ export type Section = "core" | "commands" | "errors" | "cost";
5
+ export declare const SECTIONS: readonly Section[];
6
+ export declare const BUDGETS: Readonly<Record<Section, number>>;
7
+ export declare function lookupError(code: string): ErrorEntry | undefined;
8
+ export declare const ALL_ERROR_CODES: readonly string[];
9
+ export declare const estimateTokens: (s: string) => number;
10
+ export declare const HUMAN_START = "promigence run --count 1 --cap 1 -- python3 -c 'print(6*7)'";
11
+ export declare function humanHeader(): string;
12
+ export interface GuideOpts {
13
+ section?: Section;
14
+ code?: string;
15
+ prices?: Prices;
16
+ }
17
+ export declare function renderGuide(opts?: GuideOpts): string;
18
+ export declare function guideJson(opts?: GuideOpts): Record<string, unknown>;
19
+ export declare function renderLlmsTxt(): string;