@catalyst-cloud/cli 0.14.6 → 0.14.8

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 (79) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +7 -5
  3. package/dist/args.js +68 -18
  4. package/dist/browser.js +37 -3
  5. package/dist/cli.js +58 -12
  6. package/dist/config.js +17 -5
  7. package/dist/consent-browser.js +58 -0
  8. package/dist/daemon-authority-lifetime.js +171 -0
  9. package/dist/daemon-authority.js +216 -0
  10. package/dist/daemon-cache.js +348 -0
  11. package/dist/daemon-store.js +370 -0
  12. package/dist/daemon-supervisor.js +460 -0
  13. package/dist/env/onboard-draft-store.js +250 -0
  14. package/dist/env/onboard-draft.js +33 -0
  15. package/dist/env/selected-import-writer.js +163 -0
  16. package/dist/env/settings-authority.js +205 -0
  17. package/dist/events-cloud-tail.js +146 -0
  18. package/dist/events-cloud.js +619 -0
  19. package/dist/events.js +186 -31
  20. package/dist/oauth.js +129 -34
  21. package/dist/onboard-accounts.js +259 -0
  22. package/dist/onboard-automation-management.js +15 -0
  23. package/dist/onboard-bootstrap.js +162 -0
  24. package/dist/onboard-capabilities.js +15 -7
  25. package/dist/onboard-capacity.js +226 -0
  26. package/dist/onboard-existing.js +106 -32
  27. package/dist/onboard-file-snapshot.js +45 -0
  28. package/dist/onboard-github.js +14 -3
  29. package/dist/onboard-login-candidate.js +261 -0
  30. package/dist/onboard-next.js +139 -0
  31. package/dist/onboard-personal.js +14 -3
  32. package/dist/onboard-projects.js +350 -0
  33. package/dist/onboard-repositories.js +146 -37
  34. package/dist/onboard-runtime.js +108 -13
  35. package/dist/onboard-settings-context.js +141 -0
  36. package/dist/onboard-settings-import.js +127 -0
  37. package/dist/onboard-settings.js +234 -0
  38. package/dist/onboard-ui.js +85 -59
  39. package/dist/onboard-workflow.js +316 -0
  40. package/dist/onboard-workspace.js +14 -3
  41. package/dist/onboard.js +422 -39
  42. package/dist/owned-fetch.js +182 -0
  43. package/dist/pagination.js +13 -1
  44. package/dist/query.js +104 -31
  45. package/dist/ready.js +143 -35
  46. package/dist/replica.js +11 -4
  47. package/dist/repository-id.js +5 -0
  48. package/dist/sdk.js +33 -0
  49. package/dist/watch.js +2 -2
  50. package/package.json +3 -2
  51. package/skills/catalyst-github/SKILL.md +4 -2
  52. package/skills/catalyst-github/references/is-it-mergeable.md +1 -1
  53. package/skills/catalyst-github/references/local-replica.md +78 -0
  54. package/skills/catalyst-github/references/reading-from-the-cloud.md +24 -0
  55. package/skills/catalyst-github/references/what-a-pr-accumulates.md +1 -1
  56. package/skills/catalyst-github/scripts/is-it-mergeable.mjs +210 -45
  57. package/skills/catalyst-github/scripts/read-pr.mjs +51 -15
  58. package/skills/catalyst-linear/SKILL.md +5 -3
  59. package/skills/catalyst-linear/references/local-replica.md +91 -0
  60. package/skills/catalyst-linear/references/reading-a-ticket.md +3 -3
  61. package/skills/catalyst-linear/references/reading-from-the-cloud.md +38 -0
  62. package/skills/catalyst-linear/scripts/read-ticket.mjs +37 -12
  63. package/skills/catalyst-onboard/SKILL.md +6 -4
  64. package/skills/catalyst-onboard/references/local-replica.md +40 -0
  65. package/skills/catalyst-onboard/references/local-sync.md +8 -4
  66. package/skills/catalyst-onboard/scripts/local-sync.mjs +2 -2
  67. package/skills/catalyst-onboard/scripts/where-am-i.mjs +1 -1
  68. package/skills/run-this-project/SKILL.md +1 -1
  69. package/skills/unstick/SKILL.md +1 -1
  70. package/skills/what-needs-me/SKILL.md +1 -1
  71. package/skills/what-this-repo-needs/SKILL.md +1 -1
  72. package/skills/whats-happening/SKILL.md +4 -2
  73. package/skills/whats-happening/references/local-replica.md +51 -0
  74. package/skills/whats-happening/references/reading-from-the-cloud.md +21 -0
  75. package/skills/whats-happening/references/status-reply.md +1 -1
  76. package/skills/whats-happening/scripts/snapshot.mjs +104 -26
  77. package/vendor/settings/index.d.ts +4 -0
  78. package/vendor/settings/index.js +2937 -0
  79. package/vendor/settings/provenance.json +148 -0
@@ -1,18 +1,22 @@
1
1
  import { existsSync, realpathSync } from "node:fs";
2
2
  import { userInfo } from "node:os";
3
3
  import { join } from "node:path";
4
- import { openBrowser } from "./browser.js";
4
+ import { openConsentBrowser } from "./consent-browser.js";
5
5
  import { defaultSkillsDirFor, loadConfig, normalizeBaseUrl, readManifest, } from "./config.js";
6
6
  import { CliError } from "./errors.js";
7
7
  import { cmdLegacy, findLegacy } from "./legacy.js";
8
- import { bearerFor } from "./oauth.js";
9
8
  import { personalConsentAdapter } from "./onboard-personal.js";
10
9
  import { boundedOnboardSignin } from "./onboard-signin.js";
11
10
  import { existingLinearAdapters } from "./onboard-existing.js";
12
11
  import { linearWorkspaceAdapter } from "./onboard-workspace.js";
13
- import { existingRepositoryAdapter } from "./onboard-repositories.js";
12
+ import { firstProjectAdapters } from "./onboard-projects.js";
13
+ import { onboardCapacityAdapter } from "./onboard-capacity.js";
14
+ import { onboardAutomationManagementAdapter } from "./onboard-automation-management.js";
15
+ import { onboardWorkflowVerificationAdapter } from "./onboard-workflow.js";
16
+ import { onboardAccountsAdapter } from "./onboard-accounts.js";
14
17
  import { githubInstallationAdapter } from "./onboard-github.js";
15
18
  import { guardOnboardCapabilities, } from "./onboard-capabilities.js";
19
+ import { onboardSettingsAdapter, } from "./onboard-settings.js";
16
20
  import { fetchMe } from "./transport.js";
17
21
  const waiting = (reason) => ({
18
22
  state: "waiting",
@@ -45,7 +49,56 @@ export function createOnboardRuntime(args, ctx, hooks) {
45
49
  (journal.baseUrl &&
46
50
  normalizeBaseUrl(journal.baseUrl) !== normalizeBaseUrl(cfg.baseUrl))))
47
51
  throw new CliError("the saved setup belongs to another workspace or member", "onboard-identity-mismatch", 12);
48
- const me = await fetchMe(cfg.baseUrl, await bearerFor(stepCtx, cfg), stepCtx.fetch);
52
+ // The Q1 preview reads the current token directly; stopping setup must not rotate saved credentials.
53
+ const expiry = cfg.auth
54
+ ? Date.parse(cfg.auth.expiresAt) - stepCtx.now().getTime()
55
+ : 0;
56
+ const bearer = cfg.key ||
57
+ (cfg.auth && Number.isFinite(expiry) && expiry > 30_000
58
+ ? cfg.auth.accessToken
59
+ : undefined);
60
+ if (!bearer)
61
+ throw new CliError("Renew your login with catalyst login, then run catalyst onboard.", "onboard-login-refresh-required", 11);
62
+ const owned = new AbortController();
63
+ const signal = hooks.ui
64
+ ? AbortSignal.any([owned.signal, hooks.ui.signal])
65
+ : owned.signal;
66
+ const timer = setTimeout(() => owned.abort(), 30_000);
67
+ let remove = () => { };
68
+ let me;
69
+ try {
70
+ me = await new Promise((resolve, reject) => {
71
+ const stopped = () => reject(new CliError("Your membership could not be checked. Run catalyst onboard to retry.", "onboard-membership-unavailable", 11));
72
+ if (signal.aborted) {
73
+ stopped();
74
+ return;
75
+ }
76
+ signal.addEventListener("abort", stopped, { once: true });
77
+ remove = () => signal.removeEventListener("abort", stopped);
78
+ const boundedFetch = ((url, init) => {
79
+ if (signal.aborted)
80
+ return Promise.reject(new Error("membership_read_stopped"));
81
+ return stepCtx.fetch(url, {
82
+ ...init,
83
+ redirect: "error",
84
+ signal: init?.signal
85
+ ? AbortSignal.any([signal, init.signal])
86
+ : signal,
87
+ });
88
+ });
89
+ fetchMe(cfg.baseUrl, bearer, boundedFetch).then((value) => (signal.aborted ? stopped() : resolve(value)), reject);
90
+ });
91
+ }
92
+ finally {
93
+ clearTimeout(timer);
94
+ remove();
95
+ }
96
+ const current = loadConfig(stepCtx.home);
97
+ if (!current?.user ||
98
+ current.account !== cfg.account ||
99
+ current.user.id !== cfg.user.id ||
100
+ normalizeBaseUrl(current.baseUrl) !== normalizeBaseUrl(cfg.baseUrl))
101
+ throw new CliError("Your saved login changed while membership was checked. Run catalyst onboard again.", "onboard-identity-mismatch", 12);
49
102
  if (!me.user || me.account !== cfg.account || me.user.id !== cfg.user.id)
50
103
  throw new CliError("the saved login and live member do not match", "onboard-identity-mismatch", 12);
51
104
  // /me names a person within the account, not an independently exposed membership-row ID.
@@ -54,6 +107,12 @@ export function createOnboardRuntime(args, ctx, hooks) {
54
107
  membershipId: me.user.id,
55
108
  baseUrl: normalizeBaseUrl(cfg.baseUrl),
56
109
  role: me.user.role,
110
+ display: {
111
+ personLabel: me.user.label,
112
+ email: me.user.email,
113
+ workspaceName: me.name,
114
+ workspaceSlug: me.slug,
115
+ },
57
116
  };
58
117
  };
59
118
  const legacyCheck = async (stepCtx) => {
@@ -71,7 +130,7 @@ export function createOnboardRuntime(args, ctx, hooks) {
71
130
  };
72
131
  const personalAdapter = (provider) => personalConsentAdapter({
73
132
  provider,
74
- openBrowser: hooks.openBrowser ?? openBrowser,
133
+ openBrowser: hooks.openBrowser ?? openConsentBrowser,
75
134
  sleep: hooks.sleep,
76
135
  wait: hooks.ui
77
136
  ? (message, run) => hooks.ui.wait(message, run)
@@ -147,22 +206,49 @@ export function createOnboardRuntime(args, ctx, hooks) {
147
206
  ...linear,
148
207
  "linear.workspace": linearWorkspaceAdapter({
149
208
  fallback: linear["linear.workspace"],
150
- openBrowser: hooks.openBrowser ?? openBrowser,
209
+ openBrowser: hooks.openBrowser ?? openConsentBrowser,
151
210
  wait: hooks.ui
152
211
  ? (message, work) => hooks.ui.wait(message, work)
153
212
  : undefined,
154
213
  sleep: hooks.sleep,
155
214
  }),
156
215
  "github.install": githubInstallationAdapter({
157
- openBrowser: hooks.openBrowser ?? openBrowser,
216
+ openBrowser: hooks.openBrowser ?? openConsentBrowser,
158
217
  wait: hooks.ui
159
218
  ? (message, work) => hooks.ui.wait(message, work)
160
219
  : undefined,
161
220
  sleep: hooks.sleep,
162
221
  }),
163
- "github.repos": existingRepositoryAdapter(args, hooks.ui?.chooseRepositories
164
- ? (repositories) => hooks.ui.chooseRepositories(repositories.map((row) => ({ ...row })))
165
- : undefined),
222
+ settings: onboardSettingsAdapter({
223
+ ...hooks.settings,
224
+ review: hooks.ui?.reviewSettings
225
+ ? (summaries) => hooks.ui.reviewSettings(summaries)
226
+ : hooks.settings?.review,
227
+ message: hooks.ui
228
+ ? (text) => hooks.ui.message(text)
229
+ : (text) => ctx.stderr(text),
230
+ }),
231
+ ...firstProjectAdapters(args, {
232
+ chooseExisting: hooks.ui?.chooseRepositories
233
+ ? (rows) => hooks.ui.chooseRepositories(rows.map((row) => ({ ...row })))
234
+ : undefined,
235
+ chooseFirst: hooks.ui?.chooseFirstRepository
236
+ ? (rows) => hooks.ui.chooseFirstRepository(rows.map((row) => ({ ...row })))
237
+ : undefined,
238
+ message: (text) => (hooks.ui ? hooks.ui.message(text) : ctx.stderr(text)),
239
+ }),
240
+ accounts: onboardAccountsAdapter({
241
+ message: (text) => (hooks.ui ? hooks.ui.message(text) : ctx.stderr(text)),
242
+ }),
243
+ capacity: onboardCapacityAdapter({
244
+ message: (text) => (hooks.ui ? hooks.ui.message(text) : ctx.stderr(text)),
245
+ }),
246
+ "linear.adopt": onboardWorkflowVerificationAdapter({
247
+ message: (text) => (hooks.ui ? hooks.ui.message(text) : ctx.stderr(text)),
248
+ }),
249
+ "linear.automations": onboardAutomationManagementAdapter({
250
+ message: (text) => (hooks.ui ? hooks.ui.message(text) : ctx.stderr(text)),
251
+ }),
166
252
  "linear.personal": personalAdapter("linear"),
167
253
  "github.personal": personalAdapter("github"),
168
254
  daemon: {
@@ -220,11 +306,19 @@ export function createOnboardRuntime(args, ctx, hooks) {
220
306
  ],
221
307
  fallback: "connections",
222
308
  },
223
- "linear.automations": {
224
- check: [get("/api/v1/agent/tenant/readiness")],
309
+ "github.repos": {
310
+ check: [get("/api/v1/repos"), get("/api/v1/agent/contract")],
225
311
  fallback: "connections",
226
312
  },
227
- "github.repos": {
313
+ settings: {
314
+ check: [
315
+ get("/api/v1/agent/teams"),
316
+ get("/api/v1/repos"),
317
+ get("/api/v1/agent/contract"),
318
+ ],
319
+ fallback: "connections",
320
+ },
321
+ projects: {
228
322
  check: [get("/api/v1/repos"), get("/api/v1/agent/contract")],
229
323
  fallback: "connections",
230
324
  },
@@ -246,6 +340,7 @@ export function createOnboardRuntime(args, ctx, hooks) {
246
340
  // tenant-bound step guards can run. Unsupported/local-only steps make no endpoint requests.
247
341
  return {
248
342
  identity: (journal) => identity(ctx, journal),
343
+ ...(hooks.stageSignin ? { stageSignin: hooks.stageSignin } : {}),
249
344
  bindSignals: true,
250
345
  ui: hooks.ui,
251
346
  adapters,
@@ -0,0 +1,141 @@
1
+ import { verifyOnboardRoutes } from "./onboard-capabilities.js";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import { loadConfig, normalizeBaseUrl } from "./config.js";
5
+ import { readExistingOnboardJson, readOnboardTeamInventory, selectedOnboardTeam, } from "./onboard-existing.js";
6
+ import { readOnboardRepositoryInventory, selectedOnboardRepositories, } from "./onboard-repositories.js";
7
+ import { readLocalSettings, } from "./env/settings-authority.js";
8
+ const run = promisify(execFile);
9
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
10
+ ? value
11
+ : null;
12
+ const repoKey = (repo) => `${repo.owner}/${repo.name}`.toLowerCase();
13
+ /** Read only Git's local origin; no credential helper, provider call, hook or repo command runs. */
14
+ export async function readLocalRepositoryName(repoRoot, signal) {
15
+ try {
16
+ if (signal?.aborted)
17
+ return null;
18
+ const result = await run("git", ["config", "--local", "--get-all", "remote.origin.url"], {
19
+ cwd: repoRoot,
20
+ timeout: 3_000,
21
+ maxBuffer: 16_384,
22
+ signal,
23
+ env: {
24
+ PATH: process.env.PATH,
25
+ GIT_CONFIG_GLOBAL: "/dev/null",
26
+ GIT_CONFIG_SYSTEM: "/dev/null",
27
+ GIT_CONFIG_NOSYSTEM: "1",
28
+ GIT_TERMINAL_PROMPT: "0",
29
+ },
30
+ });
31
+ const urls = result.stdout.trim().split(/\r?\n/);
32
+ if (urls.length !== 1 || signal?.aborted)
33
+ return null;
34
+ const raw = urls[0];
35
+ if (/[\u0000-\u0020\u007f]/.test(raw))
36
+ return null;
37
+ const ssh = /^git@github\.com:([A-Za-z0-9][A-Za-z0-9-]{0,38})\/([A-Za-z0-9._-]{1,100}?)(?:\.git)?$/.exec(raw);
38
+ if (ssh)
39
+ return `${ssh[1]}/${ssh[2]}`.toLowerCase();
40
+ const url = new URL(raw);
41
+ if (url.protocol !== "https:" ||
42
+ url.hostname !== "github.com" ||
43
+ url.port ||
44
+ url.username ||
45
+ url.password ||
46
+ url.search ||
47
+ url.hash)
48
+ return null;
49
+ const path = url.pathname.replace(/^\//, "").replace(/\.git$/, "");
50
+ return /^[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100}$/.test(path)
51
+ ? path.toLowerCase()
52
+ : null;
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ /** Fresh account/member, team inventory and personal repository ACL establish authority. The
59
+ * journal chooses identifiers only; neither its done state nor the disk contract is proof. */
60
+ export function onboardSettingsAuthorityPorts(input) {
61
+ const selected = { ...input.repository };
62
+ const root = input.repoRoot;
63
+ return {
64
+ readSettings: readLocalSettings,
65
+ readContext: async (signal) => {
66
+ try {
67
+ const { ctx, journal } = input;
68
+ const cfg = loadConfig(ctx.home);
69
+ const account = journal.account ?? journal.tenant;
70
+ if (signal?.aborted ||
71
+ !cfg?.user ||
72
+ !account ||
73
+ account !== cfg.account ||
74
+ journal.membershipId !== cfg.user.id ||
75
+ !journal.baseUrl ||
76
+ normalizeBaseUrl(journal.baseUrl) !== normalizeBaseUrl(cfg.baseUrl))
77
+ return null;
78
+ const teamId = selectedOnboardTeam(journal);
79
+ if (!teamId ||
80
+ teamId !== selected.teamId ||
81
+ !selectedOnboardRepositories(journal).some((repo) => repoKey(repo) === repoKey(selected) &&
82
+ repo.repoId === selected.repoId &&
83
+ repo.teamId === teamId))
84
+ return null;
85
+ const supported = await verifyOnboardRoutes(ctx, journal, [
86
+ { method: "GET", path: "/api/v1/agent/teams" },
87
+ { method: "GET", path: "/api/v1/repos" },
88
+ { method: "GET", path: "/api/v1/agent/contract" },
89
+ ], signal);
90
+ if ("reason" in supported || signal?.aborted)
91
+ return null;
92
+ const meRead = await readExistingOnboardJson(ctx, "/api/v1/me", signal);
93
+ if ("reason" in meRead)
94
+ return null;
95
+ const me = object(meRead.body);
96
+ const user = object(me?.user);
97
+ if (me?.account !== account ||
98
+ user?.id !== cfg.user.id ||
99
+ typeof user.role !== "string" ||
100
+ !["owner", "admin"].includes(user.role))
101
+ return null;
102
+ const teams = await readOnboardTeamInventory(ctx, signal);
103
+ if ("reason" in teams)
104
+ return null;
105
+ const team = teams.teams.find((row) => row.id === teamId);
106
+ if (!team || !/^[A-Z][A-Z0-9]{0,15}$/.test(team.key))
107
+ return null;
108
+ const inventory = await readOnboardRepositoryInventory(ctx, journal, signal);
109
+ if ("reason" in inventory ||
110
+ !inventory.repositories.some((repo) => repoKey(repo) === repoKey(selected) &&
111
+ repo.repoId === selected.repoId &&
112
+ repo.teamId === selected.teamId))
113
+ return null;
114
+ const local = await (input.readRepositoryName ?? readLocalRepositoryName)(root, signal);
115
+ if (local !== repoKey(selected) || signal?.aborted)
116
+ return null;
117
+ const current = loadConfig(ctx.home);
118
+ if (!current?.user ||
119
+ current.account !== cfg.account ||
120
+ current.user.id !== cfg.user.id ||
121
+ normalizeBaseUrl(current.baseUrl) !== normalizeBaseUrl(cfg.baseUrl) ||
122
+ !["owner", "admin"].includes(current.user.role))
123
+ return null;
124
+ return {
125
+ accountId: account,
126
+ personId: cfg.user.id,
127
+ baseUrl: normalizeBaseUrl(cfg.baseUrl),
128
+ role: user.role,
129
+ teamId,
130
+ teamKey: team.key,
131
+ repoId: selected.repoId,
132
+ repoName: repoKey(selected),
133
+ repoRoot: root,
134
+ };
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ },
140
+ };
141
+ }
@@ -0,0 +1,127 @@
1
+ import { verifyOnboardRoutes } from "./onboard-capabilities.js";
2
+ import { loadConfig, normalizeBaseUrl } from "./config.js";
3
+ import { createSelectedImportWriter, } from "./env/selected-import-writer.js";
4
+ const uncertain = () => new Error("selected import outcome uncertain");
5
+ const bind = (context) => JSON.stringify([
6
+ context.accountId,
7
+ context.personId,
8
+ context.baseUrl,
9
+ context.role,
10
+ context.teamId,
11
+ context.teamKey,
12
+ context.repoId,
13
+ context.repoName,
14
+ context.repoRoot,
15
+ ]);
16
+ /** The review boundary owns declaration authority. Each actual write additionally rechecks
17
+ * current access and identity; neither the disk contract nor the receipt supplies a scope. */
18
+ export function createOnboardSettingsWriter(input) {
19
+ const expected = { ...input.context };
20
+ const binding = bind(expected);
21
+ try {
22
+ const url = new URL(expected.baseUrl);
23
+ if (url.protocol !== "https:" ||
24
+ url.origin !== expected.baseUrl ||
25
+ !["owner", "admin"].includes(expected.role))
26
+ throw uncertain();
27
+ }
28
+ catch {
29
+ throw uncertain();
30
+ }
31
+ const post = async (path, body) => {
32
+ const owned = new AbortController();
33
+ const signal = input.signal
34
+ ? AbortSignal.any([input.signal, owned.signal])
35
+ : owned.signal;
36
+ let timer;
37
+ let remove = () => { };
38
+ try {
39
+ return await new Promise((resolve, reject) => {
40
+ const stopped = () => reject(uncertain());
41
+ if (signal.aborted) {
42
+ stopped();
43
+ return;
44
+ }
45
+ signal.addEventListener("abort", stopped, { once: true });
46
+ remove = () => signal.removeEventListener("abort", stopped);
47
+ timer = setTimeout(() => owned.abort(), 30_000);
48
+ Promise.resolve()
49
+ .then(async () => {
50
+ const current = await input.ports.readContext(signal);
51
+ if (!current || bind(current) !== binding || signal.aborted)
52
+ throw uncertain();
53
+ const cfg = loadConfig(input.ctx.home);
54
+ if (!cfg?.user ||
55
+ cfg.account !== expected.accountId ||
56
+ cfg.user.id !== expected.personId ||
57
+ normalizeBaseUrl(cfg.baseUrl) !== expected.baseUrl ||
58
+ !["owner", "admin"].includes(cfg.user.role))
59
+ throw uncertain();
60
+ const expiry = cfg.auth
61
+ ? Date.parse(cfg.auth.expiresAt) - input.ctx.now().getTime()
62
+ : 0;
63
+ const bearer = cfg.key ||
64
+ (cfg.auth && Number.isFinite(expiry) && expiry > 30_000
65
+ ? cfg.auth.accessToken
66
+ : undefined);
67
+ if (!bearer || signal.aborted)
68
+ throw uncertain();
69
+ const supported = await verifyOnboardRoutes(input.ctx, {
70
+ account: expected.accountId,
71
+ tenant: expected.accountId,
72
+ membershipId: expected.personId,
73
+ baseUrl: expected.baseUrl,
74
+ }, [{ method: "POST", path }], signal);
75
+ if ("reason" in supported || signal.aborted)
76
+ throw uncertain();
77
+ const response = await input.ctx.fetch(`${expected.baseUrl}${path}`, {
78
+ method: "POST",
79
+ redirect: "error",
80
+ signal,
81
+ headers: {
82
+ authorization: `Bearer ${bearer}`,
83
+ accept: "application/json",
84
+ "content-type": "application/json",
85
+ },
86
+ body: JSON.stringify(body),
87
+ });
88
+ // Refusal bodies may contain unsafe text. The adapter needs their status only.
89
+ const result = {
90
+ status: response.status,
91
+ body: response.ok
92
+ ? (await response.json())
93
+ : undefined,
94
+ };
95
+ if (signal.aborted)
96
+ throw uncertain();
97
+ return result;
98
+ })
99
+ .then((value) => (signal.aborted ? stopped() : resolve(value)), () => reject(uncertain()));
100
+ });
101
+ }
102
+ finally {
103
+ if (timer)
104
+ clearTimeout(timer);
105
+ remove();
106
+ }
107
+ };
108
+ const writer = createSelectedImportWriter({
109
+ kind: input.kind,
110
+ writeVariable: (name, value) => post("/me/env-vars", {
111
+ scope: "repo",
112
+ repoId: expected.repoId,
113
+ name,
114
+ value,
115
+ }),
116
+ importSecrets: (text) => post("/me/secrets/import", {
117
+ repo: expected.repoName,
118
+ text,
119
+ rotateExisting: [],
120
+ source: "onboarding selected import",
121
+ }),
122
+ });
123
+ return async (text, names) => {
124
+ const result = await writer(text, names);
125
+ return { writtenNames: result.written, failedNames: result.failed };
126
+ };
127
+ }