@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.
- package/CHANGELOG.md +14 -0
- package/README.md +7 -5
- package/dist/args.js +68 -18
- package/dist/browser.js +37 -3
- package/dist/cli.js +58 -12
- package/dist/config.js +17 -5
- package/dist/consent-browser.js +58 -0
- package/dist/daemon-authority-lifetime.js +171 -0
- package/dist/daemon-authority.js +216 -0
- package/dist/daemon-cache.js +348 -0
- package/dist/daemon-store.js +370 -0
- package/dist/daemon-supervisor.js +460 -0
- package/dist/env/onboard-draft-store.js +250 -0
- package/dist/env/onboard-draft.js +33 -0
- package/dist/env/selected-import-writer.js +163 -0
- package/dist/env/settings-authority.js +205 -0
- package/dist/events-cloud-tail.js +146 -0
- package/dist/events-cloud.js +619 -0
- package/dist/events.js +186 -31
- package/dist/oauth.js +129 -34
- package/dist/onboard-accounts.js +259 -0
- package/dist/onboard-automation-management.js +15 -0
- package/dist/onboard-bootstrap.js +162 -0
- package/dist/onboard-capabilities.js +15 -7
- package/dist/onboard-capacity.js +226 -0
- package/dist/onboard-existing.js +106 -32
- package/dist/onboard-file-snapshot.js +45 -0
- package/dist/onboard-github.js +14 -3
- package/dist/onboard-login-candidate.js +261 -0
- package/dist/onboard-next.js +139 -0
- package/dist/onboard-personal.js +14 -3
- package/dist/onboard-projects.js +350 -0
- package/dist/onboard-repositories.js +146 -37
- package/dist/onboard-runtime.js +108 -13
- package/dist/onboard-settings-context.js +141 -0
- package/dist/onboard-settings-import.js +127 -0
- package/dist/onboard-settings.js +234 -0
- package/dist/onboard-ui.js +85 -59
- package/dist/onboard-workflow.js +316 -0
- package/dist/onboard-workspace.js +14 -3
- package/dist/onboard.js +422 -39
- package/dist/owned-fetch.js +182 -0
- package/dist/pagination.js +13 -1
- package/dist/query.js +104 -31
- package/dist/ready.js +143 -35
- package/dist/replica.js +11 -4
- package/dist/repository-id.js +5 -0
- package/dist/sdk.js +33 -0
- package/dist/watch.js +2 -2
- package/package.json +3 -2
- package/skills/catalyst-github/SKILL.md +4 -2
- package/skills/catalyst-github/references/is-it-mergeable.md +1 -1
- package/skills/catalyst-github/references/local-replica.md +78 -0
- package/skills/catalyst-github/references/reading-from-the-cloud.md +24 -0
- package/skills/catalyst-github/references/what-a-pr-accumulates.md +1 -1
- package/skills/catalyst-github/scripts/is-it-mergeable.mjs +210 -45
- package/skills/catalyst-github/scripts/read-pr.mjs +51 -15
- package/skills/catalyst-linear/SKILL.md +5 -3
- package/skills/catalyst-linear/references/local-replica.md +91 -0
- package/skills/catalyst-linear/references/reading-a-ticket.md +3 -3
- package/skills/catalyst-linear/references/reading-from-the-cloud.md +38 -0
- package/skills/catalyst-linear/scripts/read-ticket.mjs +37 -12
- package/skills/catalyst-onboard/SKILL.md +6 -4
- package/skills/catalyst-onboard/references/local-replica.md +40 -0
- package/skills/catalyst-onboard/references/local-sync.md +8 -4
- package/skills/catalyst-onboard/scripts/local-sync.mjs +2 -2
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +1 -1
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +4 -2
- package/skills/whats-happening/references/local-replica.md +51 -0
- package/skills/whats-happening/references/reading-from-the-cloud.md +21 -0
- package/skills/whats-happening/references/status-reply.md +1 -1
- package/skills/whats-happening/scripts/snapshot.mjs +104 -26
- package/vendor/settings/index.d.ts +4 -0
- package/vendor/settings/index.js +2937 -0
- package/vendor/settings/provenance.json +148 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { constants, closeSync, fsyncSync, openSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { DEFAULT_BASE_URL, cliPath, configPathFor, contractPathFor, defaultReplicaDbFor, loadConfig, normalizeBaseUrl, readManifest, writeConfig, } from "./config.js";
|
|
4
|
+
import { contractVersionInRange } from "./contract.js";
|
|
5
|
+
import { CliError, UsageError } from "./errors.js";
|
|
6
|
+
import { deviceFlowLogin } from "./oauth.js";
|
|
7
|
+
import { fetchMe } from "./transport.js";
|
|
8
|
+
import { onboardFileSnapshot } from "./onboard-file-snapshot.js";
|
|
9
|
+
const stopped = () => new CliError("Sign-in paused. Your saved connection was not changed.", "onboard-signin-paused", 11);
|
|
10
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
|
|
11
|
+
? value
|
|
12
|
+
: null;
|
|
13
|
+
async function bounded(external, ms, run) {
|
|
14
|
+
const owned = new AbortController();
|
|
15
|
+
const signal = external
|
|
16
|
+
? AbortSignal.any([owned.signal, external])
|
|
17
|
+
: owned.signal;
|
|
18
|
+
const timer = setTimeout(() => owned.abort(), ms);
|
|
19
|
+
let remove = () => { };
|
|
20
|
+
try {
|
|
21
|
+
return await new Promise((resolve, reject) => {
|
|
22
|
+
const abort = () => reject(stopped());
|
|
23
|
+
if (signal.aborted) {
|
|
24
|
+
abort();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
28
|
+
remove = () => signal.removeEventListener("abort", abort);
|
|
29
|
+
Promise.resolve()
|
|
30
|
+
.then(() => {
|
|
31
|
+
if (signal.aborted)
|
|
32
|
+
throw stopped();
|
|
33
|
+
return run(signal);
|
|
34
|
+
})
|
|
35
|
+
.then((value) => (signal.aborted ? abort() : resolve(value)), reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
remove();
|
|
41
|
+
owned.abort();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function boundedResponse(response, signal) {
|
|
45
|
+
if (!response.body)
|
|
46
|
+
return response;
|
|
47
|
+
let bytes = 0;
|
|
48
|
+
const limit = new TransformStream({
|
|
49
|
+
transform(chunk, controller) {
|
|
50
|
+
if (signal.aborted)
|
|
51
|
+
throw stopped();
|
|
52
|
+
bytes += chunk.byteLength;
|
|
53
|
+
if (bytes > 1024 * 1024)
|
|
54
|
+
throw new CliError("The sign-in response could not be verified.", "onboard-response-unverified", 11);
|
|
55
|
+
controller.enqueue(chunk);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
return new Response(response.body.pipeThrough(limit), {
|
|
59
|
+
status: response.status,
|
|
60
|
+
statusText: response.statusText,
|
|
61
|
+
headers: response.headers,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Staging keeps tokens in this closure. Neither config nor receipt nor auth caches is changed. */
|
|
65
|
+
export async function stageOnboardLogin(ctx, input) {
|
|
66
|
+
const timeoutMs = input.timeoutMs ?? 600_000;
|
|
67
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 600_000)
|
|
68
|
+
throw new UsageError("onboarding sign-in timeout must be 1 to 600000 milliseconds");
|
|
69
|
+
const reviewDeadline = Date.now() + timeoutMs;
|
|
70
|
+
const original = onboardFileSnapshot(configPathFor(ctx.home));
|
|
71
|
+
const saved = loadConfig(ctx.home);
|
|
72
|
+
const originalContract = onboardFileSnapshot(contractPathFor(ctx.home));
|
|
73
|
+
const baseUrl = normalizeBaseUrl(saved?.baseUrl ??
|
|
74
|
+
input.baseUrl ??
|
|
75
|
+
ctx.env.CATALYST_CLOUD_BASE_URL ??
|
|
76
|
+
DEFAULT_BASE_URL);
|
|
77
|
+
if (input.baseUrl && normalizeBaseUrl(input.baseUrl) !== baseUrl)
|
|
78
|
+
throw new CliError("Resume against the original cloud to change this login.", "onboard-base-url-mismatch", 12);
|
|
79
|
+
const url = new URL(baseUrl);
|
|
80
|
+
if (url.protocol !== "https:" || url.origin !== baseUrl)
|
|
81
|
+
throw new CliError("The saved cloud origin could not be verified.", "onboard-base-url-mismatch", 12);
|
|
82
|
+
const manifest = readManifest();
|
|
83
|
+
const validate = async (auth, signal) => {
|
|
84
|
+
if (signal.aborted)
|
|
85
|
+
throw stopped();
|
|
86
|
+
if (!Number.isFinite(Date.parse(auth.expiresAt)) ||
|
|
87
|
+
Date.parse(auth.expiresAt) - ctx.now().getTime() <= 30_000)
|
|
88
|
+
throw new CliError("Sign in again before accepting this plan.", "onboard-login-refresh-required", 11);
|
|
89
|
+
const fetchImpl = async (request, init) => boundedResponse(await ctx.fetch(request, {
|
|
90
|
+
...init,
|
|
91
|
+
redirect: "error",
|
|
92
|
+
signal: init?.signal
|
|
93
|
+
? AbortSignal.any([signal, init.signal])
|
|
94
|
+
: signal,
|
|
95
|
+
}), signal);
|
|
96
|
+
const me = await fetchMe(baseUrl, auth.accessToken, fetchImpl);
|
|
97
|
+
if (signal.aborted)
|
|
98
|
+
throw stopped();
|
|
99
|
+
if (!me.user)
|
|
100
|
+
throw new CliError("Onboarding needs your personal login.", "onboard-person-required", 12);
|
|
101
|
+
// Cached contracts and loadContract's stale fallback cannot approve a staged identity.
|
|
102
|
+
const response = await fetchImpl(`${baseUrl}/api/v1/agent/contract`, {
|
|
103
|
+
headers: {
|
|
104
|
+
authorization: `Bearer ${auth.accessToken}`,
|
|
105
|
+
accept: "application/json",
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok)
|
|
109
|
+
throw new CliError("The workspace could not be verified. Your saved connection was not changed.", "onboard-candidate-unverified", 11);
|
|
110
|
+
const text = await response.text();
|
|
111
|
+
if (signal.aborted)
|
|
112
|
+
throw stopped();
|
|
113
|
+
if (Buffer.byteLength(text) > 1024 * 1024)
|
|
114
|
+
throw new CliError("The workspace response could not be verified.", "onboard-candidate-unverified", 11);
|
|
115
|
+
let contract;
|
|
116
|
+
try {
|
|
117
|
+
contract = object(JSON.parse(text));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
contract = null;
|
|
121
|
+
}
|
|
122
|
+
if (object(contract?.account)?.id !== me.account ||
|
|
123
|
+
typeof contract?.contractVersion !== "string" ||
|
|
124
|
+
contractVersionInRange(contract.contractVersion, manifest.tenantContractRange) !== true)
|
|
125
|
+
throw new CliError("The workspace response did not match this login.", "onboard-candidate-unverified", 11);
|
|
126
|
+
return me;
|
|
127
|
+
};
|
|
128
|
+
const staged = await bounded(input.signal, timeoutMs, async (signal) => {
|
|
129
|
+
const stageCtx = {
|
|
130
|
+
...ctx,
|
|
131
|
+
env: { ...ctx.env, CATALYST_CLOUD_TOKEN: undefined },
|
|
132
|
+
fetch: async (request, init) => boundedResponse(await ctx.fetch(request, {
|
|
133
|
+
...init,
|
|
134
|
+
redirect: "error",
|
|
135
|
+
signal: init?.signal
|
|
136
|
+
? AbortSignal.any([signal, init.signal])
|
|
137
|
+
: signal,
|
|
138
|
+
}), signal),
|
|
139
|
+
};
|
|
140
|
+
let auth;
|
|
141
|
+
try {
|
|
142
|
+
auth = await deviceFlowLogin(stageCtx, baseUrl, {
|
|
143
|
+
...input.device,
|
|
144
|
+
discoveryCache: false,
|
|
145
|
+
signal,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
if (signal.aborted)
|
|
150
|
+
throw stopped();
|
|
151
|
+
throw new CliError("Sign-in could not be verified. Your saved connection was not changed.", "onboard-signin-unverified", 11);
|
|
152
|
+
}
|
|
153
|
+
const me = await bounded(signal, 30_000, (readSignal) => validate(auth, readSignal));
|
|
154
|
+
if (onboardFileSnapshot(configPathFor(ctx.home)) !== original)
|
|
155
|
+
throw new CliError("Your saved login changed. Run setup again.", "onboard-login-changed", 11);
|
|
156
|
+
return { auth, me };
|
|
157
|
+
});
|
|
158
|
+
const user = staged.me.user;
|
|
159
|
+
if (!user)
|
|
160
|
+
throw new CliError("Onboarding needs your personal login.", "onboard-person-required", 12);
|
|
161
|
+
const identity = {
|
|
162
|
+
account: staged.me.account,
|
|
163
|
+
membershipId: user.id,
|
|
164
|
+
baseUrl,
|
|
165
|
+
role: user.role,
|
|
166
|
+
display: Object.freeze({
|
|
167
|
+
personLabel: user.label,
|
|
168
|
+
email: user.email,
|
|
169
|
+
workspaceName: staged.me.name,
|
|
170
|
+
workspaceSlug: staged.me.slug,
|
|
171
|
+
}),
|
|
172
|
+
};
|
|
173
|
+
let accepted = false;
|
|
174
|
+
return {
|
|
175
|
+
identity: Object.freeze(identity),
|
|
176
|
+
get accepted() {
|
|
177
|
+
return accepted;
|
|
178
|
+
},
|
|
179
|
+
async accept(signal, beforePublish) {
|
|
180
|
+
if (accepted)
|
|
181
|
+
throw new CliError("This sign-in preview was already accepted.", "onboard-candidate-used", 11);
|
|
182
|
+
const remaining = reviewDeadline - Date.now();
|
|
183
|
+
if (remaining <= 0)
|
|
184
|
+
throw stopped();
|
|
185
|
+
const me = await bounded(signal, Math.min(30_000, remaining), (readSignal) => validate(staged.auth, readSignal));
|
|
186
|
+
if (me.account !== identity.account ||
|
|
187
|
+
me.user?.id !== identity.membershipId ||
|
|
188
|
+
me.user.role !== identity.role)
|
|
189
|
+
throw new CliError("This login changed during review. Run setup again.", "onboard-login-changed", 11);
|
|
190
|
+
if (signal?.aborted)
|
|
191
|
+
throw stopped();
|
|
192
|
+
if (onboardFileSnapshot(configPathFor(ctx.home)) !== original)
|
|
193
|
+
throw new CliError("Your saved login changed. Run setup again.", "onboard-login-changed", 11);
|
|
194
|
+
if (onboardFileSnapshot(contractPathFor(ctx.home)) !== originalContract)
|
|
195
|
+
throw new CliError("Your setup files changed during review. Run setup again.", "onboard-login-changed", 11);
|
|
196
|
+
beforePublish?.();
|
|
197
|
+
const config = {
|
|
198
|
+
baseUrl,
|
|
199
|
+
account: me.account,
|
|
200
|
+
slug: me.slug,
|
|
201
|
+
name: me.name,
|
|
202
|
+
principal: me.principal,
|
|
203
|
+
permissions: me.permissions,
|
|
204
|
+
user: me.user,
|
|
205
|
+
auth: staged.auth,
|
|
206
|
+
joinedAt: ctx.now().toISOString(),
|
|
207
|
+
lastSkillBundleVersion: manifest.version,
|
|
208
|
+
cliPath: cliPath(),
|
|
209
|
+
...(saved?.skillsDir ? { skillsDir: saved.skillsDir } : {}),
|
|
210
|
+
// A new workspace never inherits the old workspace's database path.
|
|
211
|
+
replicaDb: saved?.account === me.account
|
|
212
|
+
? (saved.replicaDb ?? defaultReplicaDbFor(ctx.home))
|
|
213
|
+
: undefined,
|
|
214
|
+
};
|
|
215
|
+
// This is an accepted connection, not a two-file transaction. Existing config writers do
|
|
216
|
+
// not share the receipt lock; the precheck refuses visible changes but is not a CAS claim.
|
|
217
|
+
try {
|
|
218
|
+
writeConfig(ctx.home, config);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
const current = loadConfig(ctx.home);
|
|
222
|
+
accepted =
|
|
223
|
+
current?.account === config.account &&
|
|
224
|
+
current.user?.id === config.user?.id &&
|
|
225
|
+
current.auth?.accessToken === config.auth?.accessToken;
|
|
226
|
+
if (accepted)
|
|
227
|
+
throw new CliError("Your connection was accepted, but setup could not be recorded. Run catalyst onboard to resume.", "onboard-connection-accepted", 11);
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
accepted = true;
|
|
231
|
+
const fd = openSync(configPathFor(ctx.home), constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
232
|
+
try {
|
|
233
|
+
fsyncSync(fd);
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
closeSync(fd);
|
|
237
|
+
}
|
|
238
|
+
const directory = openSync(dirname(configPathFor(ctx.home)), constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
239
|
+
try {
|
|
240
|
+
fsyncSync(directory);
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
closeSync(directory);
|
|
244
|
+
}
|
|
245
|
+
// The old cache has no identity scope. Invalidate it only after this reviewed connection
|
|
246
|
+
// is accepted, and only while its observed bytes still match. A failure remains partial.
|
|
247
|
+
if (onboardFileSnapshot(contractPathFor(ctx.home)) !== originalContract)
|
|
248
|
+
throw new CliError("Your connection was accepted, but setup could not be recorded. Run catalyst onboard to resume.", "onboard-connection-accepted", 11);
|
|
249
|
+
if (originalContract !== null) {
|
|
250
|
+
unlinkSync(contractPathFor(ctx.home));
|
|
251
|
+
const cacheDirectory = openSync(dirname(contractPathFor(ctx.home)), constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
252
|
+
try {
|
|
253
|
+
fsyncSync(cacheDirectory);
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
closeSync(cacheDirectory);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { loadConfig, normalizeBaseUrl } from "./config.js";
|
|
2
|
+
/** What a person reads for a step that is not done. Each line ends in an action they can take. */
|
|
3
|
+
const REASONS = {
|
|
4
|
+
account_identity_unverified: "Your login changed while coding accounts were checked. Run catalyst onboard again to verify your person and workspace.",
|
|
5
|
+
account_inventory_unavailable: "Coding accounts could not be read. Run catalyst onboard again to retry.",
|
|
6
|
+
account_inventory_unverified: "The server did not return a fresh supported coding-account list. Run catalyst onboard after the server update.",
|
|
7
|
+
account_validation_admin_required: "An administrator must check the workspace's coding-account access.",
|
|
8
|
+
account_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard.",
|
|
9
|
+
account_validation_unavailable: "The provider check could not finish. Run catalyst onboard again to retry.",
|
|
10
|
+
account_validation_unverified: "The server did not return a fresh provider check. Run catalyst onboard after the server update.",
|
|
11
|
+
account_provider_access_unverified: "Coding-account provider access is not freshly verified. Check the account in the workspace's coding-account settings, then resume setup.",
|
|
12
|
+
codex_provider_access_unverified: "Codex credentials are stored, but provider access is not freshly verified. Setup did not refresh or rotate the Codex login.",
|
|
13
|
+
account_provider_walled: "Claude's usage limit is spent for now. Check the account's reset time, then run catalyst onboard again.",
|
|
14
|
+
account_provider_rejected: "Claude refused the stored login. Replace it in the workspace's coding-account settings, then resume setup.",
|
|
15
|
+
personal_consent_handoff: "The browser approval link could not be verified. Run catalyst onboard to try again.",
|
|
16
|
+
personal_identity_refused: "Your current login no longer matches this setup. Resume with the original workspace and person.",
|
|
17
|
+
personal_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
18
|
+
onboard_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
19
|
+
personal_status_unavailable: "Your personal connection could not be checked. Run catalyst onboard to try again.",
|
|
20
|
+
workspace_browser_unavailable: "This computer could not open a browser. Connect Linear from this workspace’s Integrations page in your browser, then run catalyst onboard to resume.",
|
|
21
|
+
personal_browser_unavailable: "This computer could not open a browser. Finish this login’s connection from Connected accounts in your browser, then run catalyst onboard to resume.",
|
|
22
|
+
onboarding_capability_unavailable: "The server's setup capabilities could not be checked. Run catalyst onboard to try again.",
|
|
23
|
+
onboarding_capability_identity_unverified: "The server's setup capabilities belong to another login or workspace. Sign in to the original workspace to resume.",
|
|
24
|
+
onboarding_capability_login_refresh_required: "Your login needs a refresh before setup capabilities can be checked. Run catalyst login, then catalyst onboard.",
|
|
25
|
+
workspace_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
26
|
+
workspace_consent_refused: "The server did not accept this login for Linear workspace setup. Sign in again or ask your workspace administrator.",
|
|
27
|
+
github_installation_status_unavailable: "Your GitHub App installation could not be checked. Run catalyst onboard to try again.",
|
|
28
|
+
github_installation_status_shape: "The server returned a GitHub installation result that could not be verified.",
|
|
29
|
+
github_installation_consent_handoff: "The GitHub installation approval link could not be verified. Run catalyst onboard to try again.",
|
|
30
|
+
github_installation_consent_refused: "The server did not accept this login for GitHub App setup. Sign in again or ask your workspace administrator.",
|
|
31
|
+
github_installation_identity_refused: "GitHub setup belongs to another login or workspace. Sign in to the original workspace to resume.",
|
|
32
|
+
github_installation_admin_required: "A workspace owner or administrator approves the GitHub App installation.",
|
|
33
|
+
github_installation_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
34
|
+
github_installation_browser_unavailable: "This computer could not open a browser. Install the GitHub App from this workspace’s Integrations page in your browser, then run catalyst onboard to resume.",
|
|
35
|
+
step_not_available_in_this_release: "This setup step is not available yet.",
|
|
36
|
+
prerequisite_not_ready: "Waiting for an earlier setup step.",
|
|
37
|
+
local_sync_not_selected: "Using cloud reads. Local sync was not selected.",
|
|
38
|
+
local_sync_capability_unavailable: "Local sync was selected but could not be verified.",
|
|
39
|
+
member_scope: "Your workspace administrator handles this step.",
|
|
40
|
+
onboarding_checks_pending: "Some required checks are still unverified.",
|
|
41
|
+
interrupted: "Setup paused. Run the same command to resume.",
|
|
42
|
+
signin_timeout: "Sign-in timed out. Run catalyst onboard to try again.",
|
|
43
|
+
linear_workspace_unverified: "Your Linear workspace connection could not be verified.",
|
|
44
|
+
team_inventory_empty: "No existing Linear teams could be verified.",
|
|
45
|
+
team_inventory_shape: "The cloud returned a team list that could not be verified.",
|
|
46
|
+
team_read_unavailable: "Your existing Linear teams could not be read. Run catalyst onboard to try again.",
|
|
47
|
+
team_read_identity_unverified: "Your login changed while setup was reading this workspace. Resume with the original workspace and person.",
|
|
48
|
+
project_identity_unverified: "Your login changed while setup was checking this project. Resume with the original workspace and person.",
|
|
49
|
+
project_options_unavailable: "Available teams and repositories could not be checked. Run catalyst onboard to try again.",
|
|
50
|
+
project_provider_inventory_unavailable: "Linear or GitHub access could not be verified. Check this workspace's connections, then resume.",
|
|
51
|
+
project_options_unverified: "The server returned a team or repository list that could not be verified.",
|
|
52
|
+
project_registration_visibility_pending: "Your project exists, but its repository binding is not visible yet. Run catalyst onboard to check again.",
|
|
53
|
+
project_binding_conflict: "This team or repository is already bound to a project. Ask your workspace administrator to check its binding, then resume.",
|
|
54
|
+
project_login_refresh_required: "Renew your login with catalyst onboard, then review the plan before creating this project.",
|
|
55
|
+
project_create_unverified: "Project creation could not be confirmed. Run catalyst onboard to check for the project before trying again.",
|
|
56
|
+
team_read_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
57
|
+
team_choice_required: "Choose an existing team with catalyst onboard --team <team ID or key>.",
|
|
58
|
+
team_selection_unverified: "Your selected Linear team could not be verified. Run catalyst onboard --team <team ID or key> to choose again.",
|
|
59
|
+
repository_choice_required: "Choose repositories with catalyst onboard --repo <owner/name>. Repeat --repo to choose more than one.",
|
|
60
|
+
repository_inventory_empty: "No accessible repositories are registered to this Linear team yet.",
|
|
61
|
+
repository_read_unavailable: "Your repository list could not be read. Run catalyst onboard to try again.",
|
|
62
|
+
repository_inventory_shape: "The cloud returned a repository list that could not be verified.",
|
|
63
|
+
repository_contract_unavailable: "Current repository IDs could not be checked. Run catalyst onboard to try again.",
|
|
64
|
+
repository_contract_unverified: "The cloud returned repository IDs that could not be verified for this workspace.",
|
|
65
|
+
repository_id_unverified: "A repository's current ID could not be verified. Keep your selection and try again.",
|
|
66
|
+
repository_binding_ambiguous: "A repository's Linear team binding is ambiguous. Your administrator needs to check it.",
|
|
67
|
+
repository_identity_unverified: "Sign in to the original workspace to choose repositories.",
|
|
68
|
+
settings_review_timeout: "The settings review timed out. Run catalyst onboard to resume.",
|
|
69
|
+
settings_checkout_unverified: "Setup could not verify the repository's local checkout or settings file. Settings are optional for a first ticket. To finish them later, run catalyst onboard from the repository's checkout.",
|
|
70
|
+
settings_draft_storage_unavailable: "A private settings draft could not be kept. Run catalyst onboard to retry.",
|
|
71
|
+
settings_draft_storage_timeout: "Keeping your settings draft took too long. Run catalyst onboard to retry.",
|
|
72
|
+
settings_approval_unverified: "Settings still need review and approval; kept drafts do not count. Settings are optional for a first ticket. To finish them later, run catalyst onboard from the repository's checkout.",
|
|
73
|
+
repository_selection_unverified: "Your selected repositories could not be verified. Run catalyst onboard --repo <owner/name> to choose again.",
|
|
74
|
+
workflow_identity_unverified: "Setup could not confirm the signed-in workspace, person or role for this team. Run catalyst onboard again as a workspace owner or administrator.",
|
|
75
|
+
workflow_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard.",
|
|
76
|
+
workflow_mapping_changed: "The team's workflow changed while setup checked it. Run catalyst onboard again.",
|
|
77
|
+
workflow_unavailable: "Setup could not read the team's workflow. Run catalyst onboard again to retry.",
|
|
78
|
+
capacity_admission_unverified: "No runner is allowed to take work for this workspace yet. Enroll a self-hosted runner host, or ask Catalyst to enable cloud runners for it. Then run catalyst onboard.",
|
|
79
|
+
capacity_currently_full: "Every runner slot for this repository is in use. Run catalyst onboard again after current work finishes.",
|
|
80
|
+
capacity_unavailable: "Setup could not read runner capacity. Run catalyst onboard again to retry.",
|
|
81
|
+
housekeeping_service_unverified: "The daily update needs a user service manager, launchd on macOS or systemd --user on Linux. Setup cannot schedule it on this computer. It is optional, and work does not depend on it.",
|
|
82
|
+
};
|
|
83
|
+
/** The saved login's web address, or nothing when the config is missing, unreadable or not http(s). */
|
|
84
|
+
export function savedOnboardBaseUrl(home) {
|
|
85
|
+
try {
|
|
86
|
+
const cfg = loadConfig(home);
|
|
87
|
+
const url = cfg ? webUrl(cfg.baseUrl) : undefined;
|
|
88
|
+
return url ? normalizeBaseUrl(url) : undefined;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Receipt and config text reaches the terminal only after URL parsing has escaped it.
|
|
95
|
+
function webUrl(value) {
|
|
96
|
+
if (typeof value !== "string")
|
|
97
|
+
return undefined;
|
|
98
|
+
try {
|
|
99
|
+
const url = new URL(value);
|
|
100
|
+
return ["https:", "http:"].includes(url.protocol) &&
|
|
101
|
+
!url.username &&
|
|
102
|
+
!url.password
|
|
103
|
+
? url.href
|
|
104
|
+
: undefined;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** The selected team's key from the receipt, only when it is plain enough to print in a command. */
|
|
111
|
+
export function onboardTeamKey(journal) {
|
|
112
|
+
const step = journal?.steps.find((row) => row.id === "linear.team");
|
|
113
|
+
const key = step?.state === "done" ? step.evidence?.teamKey : undefined;
|
|
114
|
+
return typeof key === "string" && /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(key)
|
|
115
|
+
? key
|
|
116
|
+
: undefined;
|
|
117
|
+
}
|
|
118
|
+
/** One step's next action. `waitsFor` names the unfinished step a prerequisite wait is blocked on. */
|
|
119
|
+
export function onboardReasonText(step, context = {}) {
|
|
120
|
+
const reason = step.reason ?? "";
|
|
121
|
+
if (reason === "workflow_mapping_unverified")
|
|
122
|
+
return `This team's workflow mapping is not verified yet. Run catalyst team adopt ${onboardTeamKey(context.journal) ?? "<TEAM KEY>"} to preview the adoption plan. Run it again with the --yes --plan-hash value it prints. Then run catalyst onboard.`;
|
|
123
|
+
if (reason === "account_enrollment_required") {
|
|
124
|
+
const where = context.baseUrl
|
|
125
|
+
? `at ${normalizeBaseUrl(context.baseUrl)}/settings/coding-accounts, the Settings → AI accounts page`
|
|
126
|
+
: "in the web app on the Settings → AI accounts page";
|
|
127
|
+
return `No coding account is enrolled. A workspace owner or administrator adds one ${where}. A Claude account needs its email and the token printed by \`claude setup-token\`. Then run catalyst onboard.`;
|
|
128
|
+
}
|
|
129
|
+
if (reason === "cloud_capability_unavailable") {
|
|
130
|
+
// The capability guard records the web page in evidence.path only when it printed it.
|
|
131
|
+
const page = webUrl(step.evidence?.path);
|
|
132
|
+
return page
|
|
133
|
+
? `This setup step is not available on this server yet. Continue in the web app at ${page}, then run catalyst onboard.`
|
|
134
|
+
: "This setup step is not available on this server yet. Run catalyst onboard after the server update.";
|
|
135
|
+
}
|
|
136
|
+
if (reason === "prerequisite_not_ready" && context.waitsFor)
|
|
137
|
+
return `Runs after "${context.waitsFor}".`;
|
|
138
|
+
return REASONS[reason] ?? reason.replaceAll("_", " ");
|
|
139
|
+
}
|
package/dist/onboard-personal.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { finishOnTheWeb } from "./consent-browser.js";
|
|
1
2
|
import { loadConfig, normalizeBaseUrl, } from "./config.js";
|
|
2
3
|
import { pollConsent } from "./onboard-consent.js";
|
|
3
4
|
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
|
|
@@ -198,11 +199,15 @@ export function personalConsentAdapter(options) {
|
|
|
198
199
|
const name = options.provider === "linear" ? "Linear" : "GitHub";
|
|
199
200
|
ctx.stderr(`Approve your personal ${name} connection in the browser.`);
|
|
200
201
|
// Signed continuation credentials belong only to the browser, never transcript or journal.
|
|
202
|
+
let browserUnavailable = false;
|
|
201
203
|
try {
|
|
202
|
-
options.openBrowser(url);
|
|
204
|
+
await options.openBrowser(url, signal);
|
|
203
205
|
}
|
|
204
206
|
catch {
|
|
205
|
-
|
|
207
|
+
if (signal?.aborted)
|
|
208
|
+
return waiting("interrupted");
|
|
209
|
+
browserUnavailable = true;
|
|
210
|
+
ctx.stderr(finishOnTheWeb(cfg.baseUrl, "connected-accounts", `connect your ${name} account`));
|
|
206
211
|
}
|
|
207
212
|
let latest = waiting("personal_status_unavailable");
|
|
208
213
|
const run = () => pollConsent({
|
|
@@ -213,6 +218,8 @@ export function personalConsentAdapter(options) {
|
|
|
213
218
|
latest = await inspect(ctx, journal, pollSignal);
|
|
214
219
|
if (latest.reason === "cloud_capability_unavailable")
|
|
215
220
|
return { outcome: "waiting", reason: latest.reason };
|
|
221
|
+
if (latest.reason === "personal_login_refresh_required")
|
|
222
|
+
return { outcome: "waiting", reason: latest.reason };
|
|
216
223
|
if (latest.state === "done")
|
|
217
224
|
return { outcome: "connected" };
|
|
218
225
|
if (latest.state === "refused" || latest.state === "failed")
|
|
@@ -225,7 +232,11 @@ export function personalConsentAdapter(options) {
|
|
|
225
232
|
const result = options.wait
|
|
226
233
|
? await options.wait(`Waiting for your ${name} approval`, run)
|
|
227
234
|
: await run();
|
|
228
|
-
|
|
235
|
+
if (result.state === "done")
|
|
236
|
+
return latest;
|
|
237
|
+
return browserUnavailable && result.reason === "consent_timeout"
|
|
238
|
+
? waiting("personal_browser_unavailable")
|
|
239
|
+
: result;
|
|
229
240
|
},
|
|
230
241
|
};
|
|
231
242
|
}
|