@catalyst-cloud/cli 0.13.1 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/dist/args.js +291 -69
- package/dist/capabilities.js +2 -0
- package/dist/cli.js +109 -17
- package/dist/config.js +3 -1
- package/dist/env/draft.js +134 -0
- package/dist/env/legacy.js +54 -0
- package/dist/env/parse-env.js +50 -0
- package/dist/env/settings-toml.js +66 -0
- package/dist/env.js +126 -26
- package/dist/legacy.js +16 -17
- package/dist/onboard-consent.js +65 -0
- package/dist/onboard.js +836 -0
- package/dist/project.js +75 -13
- package/dist/ready.js +11 -1
- package/dist/secret.js +24 -3
- package/dist/var.js +118 -0
- package/package.json +3 -2
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +1 -1
- package/skills/catalyst-onboard/references/declaring-a-repository.md +19 -12
- package/skills/catalyst-onboard/references/reading-ready.md +1 -1
- package/skills/catalyst-onboard/references/the-one-path.md +4 -4
- package/skills/catalyst-onboard/references/what-the-browser-owns.md +2 -2
- package/skills/catalyst-onboard/scripts/lib/project-coverage.mjs +53 -0
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +885 -196
- 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 +4 -4
- package/skills/what-this-repo-needs/scripts/check.mjs +12 -13
- package/skills/whats-happening/SKILL.md +19 -35
- package/skills/whats-happening/references/coding-accounts.md +6 -19
- package/skills/whats-happening/references/routing-work.md +4 -15
- package/skills/whats-happening/references/settings-and-where-they-live.md +15 -18
- package/skills/whats-happening/references/stages-and-mapping.md +12 -18
- package/skills/whats-happening/references/status-reply.md +11 -29
- package/skills/whats-happening/references/the-ladder.md +15 -25
- package/skills/whats-happening/references/what-runs-next.md +7 -26
- package/skills/whats-happening/references/when-a-phase-fails.md +16 -22
- package/skills/whats-happening/references/why-is-it-stuck.md +38 -48
- package/dist/env/declaration-rules.js +0 -163
package/dist/env.js
CHANGED
|
@@ -2,17 +2,103 @@
|
|
|
2
2
|
// validator. Unlike every other verb in this file's siblings, `cmdEnv` never calls `requireConfig` or
|
|
3
3
|
// `apiClient` — it needs neither a login nor a network call, which is the whole point of the feature:
|
|
4
4
|
// a person reviews what a repository declares without connecting anything first.
|
|
5
|
-
import { readFileSync, statSync } from "node:fs";
|
|
6
|
-
import { resolve } from "node:path";
|
|
7
|
-
import { positionals } from "./args.js";
|
|
8
|
-
import {
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { flagBool, flagString, positionals } from "./args.js";
|
|
8
|
+
import { validateSettingsToml } from "./env/settings-toml.js";
|
|
9
|
+
import { convertLegacyEnvironmentJson } from "./env/legacy.js";
|
|
10
|
+
import { draftEnvironment } from "./env/draft.js";
|
|
9
11
|
import { inventoryRepo } from "./env/inventory.js";
|
|
10
|
-
import { inventoryToJson,
|
|
12
|
+
import { inventoryToJson, renderInventory } from "./env/render.js";
|
|
11
13
|
import { UsageError } from "./errors.js";
|
|
12
14
|
export async function cmdEnv(args, ctx, deps = {}) {
|
|
13
15
|
const [sub, ...rest] = positionals(args);
|
|
14
16
|
if (sub === undefined)
|
|
15
|
-
throw new UsageError("env needs a subcommand: inventory | check");
|
|
17
|
+
throw new UsageError("env needs a subcommand: inventory | check | migrate | draft");
|
|
18
|
+
if (sub === "draft") {
|
|
19
|
+
if (rest.length > 0)
|
|
20
|
+
throw new UsageError("env draft takes no positional arguments; use --root DIR");
|
|
21
|
+
const root = resolve(flagString(args, "root") ?? ".");
|
|
22
|
+
const target = join(root, ".catalyst/catalyst.toml");
|
|
23
|
+
const draft = draftEnvironment(root, deps);
|
|
24
|
+
if (draft.state === "invalid") {
|
|
25
|
+
if (args.json)
|
|
26
|
+
ctx.stdout(JSON.stringify(draft));
|
|
27
|
+
else
|
|
28
|
+
for (const error of draft.errors)
|
|
29
|
+
ctx.stderr(error);
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
const summary = {
|
|
33
|
+
state: "valid",
|
|
34
|
+
names: draft.names,
|
|
35
|
+
secretNames: draft.secretNames,
|
|
36
|
+
setupCount: draft.setup.length,
|
|
37
|
+
verifyCount: draft.verify.length,
|
|
38
|
+
sources: draft.sources,
|
|
39
|
+
destination: ".catalyst/catalyst.toml",
|
|
40
|
+
toml: draft.toml,
|
|
41
|
+
};
|
|
42
|
+
const write = flagBool(args, "write");
|
|
43
|
+
if (write && existsSync(target)) {
|
|
44
|
+
ctx.stderr(`${summary.destination} already exists; no file was written. Review the draft with --diff and merge it by hand.`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
if (write) {
|
|
48
|
+
const checked = validateSettingsToml(draft.toml);
|
|
49
|
+
if (checked.state !== "valid") {
|
|
50
|
+
ctx.stderr(`draft failed local validation: ${checked.state === "invalid" ? checked.errors.join("; ") : "no environment table"}`);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
if (deps.writeFile)
|
|
54
|
+
deps.writeFile(target, draft.toml);
|
|
55
|
+
else {
|
|
56
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
57
|
+
writeFileSync(target, draft.toml, { encoding: "utf8", flag: "wx" });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (args.json)
|
|
61
|
+
ctx.stdout(JSON.stringify({ ...summary, written: write }));
|
|
62
|
+
else if (flagBool(args, "diff")) {
|
|
63
|
+
ctx.stdout(`--- /dev/null\n+++ b/${summary.destination} (draft)\n@@\n${draft.toml.split("\n").filter(Boolean).map((line) => `+${line}`).join("\n")}`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
ctx.stdout(`Drafted ${summary.destination} from ${draft.sources.length ? draft.sources.join(", ") : "no environment sources"}.`);
|
|
67
|
+
ctx.stdout(`Names: ${draft.names.length} (${draft.secretNames.length} secret; all optional)`);
|
|
68
|
+
ctx.stdout(`Setup: ${draft.setup.length}; verify: ${draft.verify.length}`);
|
|
69
|
+
ctx.stdout(draft.toml);
|
|
70
|
+
}
|
|
71
|
+
if (write && !args.json)
|
|
72
|
+
ctx.stdout(`Wrote ${summary.destination}; review and commit it through your repository's pull request.`);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
if (sub === "migrate") {
|
|
76
|
+
if (rest.length > 1)
|
|
77
|
+
throw new UsageError(`env migrate takes at most one file (got an extra "${rest[1]}")`);
|
|
78
|
+
const file = rest[0] ?? "catalyst.env.json";
|
|
79
|
+
let text;
|
|
80
|
+
try {
|
|
81
|
+
text = readFileSync(file, "utf8");
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
ctx.stderr(`could not read legacy declaration ${file}`);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
const result = convertLegacyEnvironmentJson(text);
|
|
88
|
+
if (result.state === "invalid") {
|
|
89
|
+
if (args.json)
|
|
90
|
+
ctx.stdout(JSON.stringify(result));
|
|
91
|
+
else
|
|
92
|
+
for (const error of result.errors)
|
|
93
|
+
ctx.stderr(error);
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
if (args.json)
|
|
97
|
+
ctx.stdout(JSON.stringify(result));
|
|
98
|
+
else
|
|
99
|
+
ctx.stdout(result.toml);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
16
102
|
if (sub === "inventory") {
|
|
17
103
|
if (rest.length > 1)
|
|
18
104
|
throw new UsageError(`env inventory takes at most one path (got an extra "${rest[1]}")`);
|
|
@@ -40,37 +126,51 @@ export async function cmdEnv(args, ctx, deps = {}) {
|
|
|
40
126
|
return 0;
|
|
41
127
|
}
|
|
42
128
|
if (sub === "check") {
|
|
43
|
-
const file = rest[0];
|
|
44
|
-
if (!file)
|
|
45
|
-
throw new UsageError("env check needs a file: catalyst env check <path to catalyst.env.json>");
|
|
129
|
+
const file = rest[0] ?? ".catalyst/catalyst.toml";
|
|
46
130
|
if (rest.length > 1)
|
|
47
131
|
throw new UsageError(`env check takes exactly one file (got an extra "${rest[1]}")`);
|
|
132
|
+
if (file.endsWith("catalyst.env.json")) {
|
|
133
|
+
const message = "catalyst.env.json is a legacy declaration and is no longer read; validate .catalyst/catalyst.toml instead";
|
|
134
|
+
if (args.json)
|
|
135
|
+
ctx.stdout(JSON.stringify({ state: "invalid", errors: [message] }));
|
|
136
|
+
else
|
|
137
|
+
ctx.stderr(message);
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
48
140
|
let text;
|
|
49
141
|
try {
|
|
50
142
|
text = readFileSync(file, "utf8");
|
|
51
143
|
}
|
|
52
144
|
catch (err) {
|
|
145
|
+
if (rest[0] === undefined && !existsSync("catalyst.env.json")) {
|
|
146
|
+
if (args.json)
|
|
147
|
+
ctx.stdout(JSON.stringify({ state: "no-source", errors: [] }));
|
|
148
|
+
else
|
|
149
|
+
ctx.stdout("no .catalyst/catalyst.toml found in this repository");
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
if (rest[0] === undefined && existsSync("catalyst.env.json")) {
|
|
153
|
+
const message = "No .catalyst/catalyst.toml found; this repository has a legacy catalyst.env.json, which is no longer read. Convert it to the [environment] table in .catalyst/catalyst.toml.";
|
|
154
|
+
if (args.json)
|
|
155
|
+
ctx.stdout(JSON.stringify({ state: "invalid", errors: [message] }));
|
|
156
|
+
else
|
|
157
|
+
ctx.stderr(message);
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
53
160
|
ctx.stderr(`could not read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
54
161
|
return 1;
|
|
55
162
|
}
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
doc = JSON.parse(text);
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
// C-4/S-2: JSON.parse's own message embeds the first ten characters of the input, so
|
|
62
|
-
// forwarding it prints content from the file — on a verb documented as never printing a value.
|
|
63
|
-
ctx.stderr(`${file} is not valid JSON`);
|
|
64
|
-
return 1;
|
|
65
|
-
}
|
|
66
|
-
const errors = validateDeclaration(doc);
|
|
67
|
-
const state = errors.length === 0 ? "valid" : "invalid";
|
|
163
|
+
const result = validateSettingsToml(text);
|
|
68
164
|
if (args.json)
|
|
69
|
-
ctx.stdout(JSON.stringify(
|
|
165
|
+
ctx.stdout(JSON.stringify(result));
|
|
166
|
+
else if (result.state === "valid")
|
|
167
|
+
ctx.stdout(`valid — ${file} contains ${result.variableNames.length} environment variable names`);
|
|
168
|
+
else if (result.state === "no-source")
|
|
169
|
+
ctx.stdout(`no [environment] table in ${file}`);
|
|
70
170
|
else
|
|
71
|
-
for (const
|
|
72
|
-
ctx.
|
|
73
|
-
return state === "
|
|
171
|
+
for (const error of result.errors)
|
|
172
|
+
ctx.stderr(`${file}: ${error}`);
|
|
173
|
+
return result.state === "invalid" ? 1 : 0;
|
|
74
174
|
}
|
|
75
|
-
throw new UsageError(`unknown env subcommand "${sub}": inventory | check`);
|
|
175
|
+
throw new UsageError(`unknown env subcommand "${sub}": inventory | check | migrate | draft`);
|
|
76
176
|
}
|
package/dist/legacy.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
//
|
|
7
7
|
// The list is fixed on purpose. A prefix match would one day catch a current job (the housekeeping
|
|
8
8
|
// job, the sandbox credit guard, jobs a person made); this matches names the old runtime installed,
|
|
9
|
-
// read at the source commit below, and nothing else.
|
|
10
|
-
import { existsSync, lstatSync, readFileSync, readdirSync,
|
|
9
|
+
// read at the source commit below, and nothing else. Shared data folders are always kept. The historical --data flag cannot remove them.
|
|
10
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { flagBool, positionals } from "./args.js";
|
|
@@ -47,8 +47,8 @@ const OLD_ROLE_PREFIX = "com.catalyst.role.";
|
|
|
47
47
|
export const OLD_USER_UNITS = ["catalyst.service", "catalyst-monitor.service"];
|
|
48
48
|
/** Command files the old runtime put on the person's PATH. `~/.local/bin/catalyst` is NOT one: that
|
|
49
49
|
* name belongs to the current CLI wherever npm put it. */
|
|
50
|
-
export const OLD_BINS = [".
|
|
51
|
-
/**
|
|
50
|
+
export const OLD_BINS = [".local/bin/catalyst-events", ".local/bin/catalyst-filter", ".local/bin/catalyst-hud", ".local/bin/catalyst-monitor", ".local/bin/catalyst-myown"];
|
|
51
|
+
/** Shared roots also hold current skills, logs, hosts and seats. Never remove these directories. */
|
|
52
52
|
export const OLD_DATA_DIRS = [".catalyst", ".config/catalyst", ".local/state/catalyst", ".local/state/catalyst-fleet-runner", ".local/state/catalyst-ledger"];
|
|
53
53
|
const RECOMMEND = "Remove them: the cloud runtime replaced them, and left in place they can start old jobs or shadow current commands.";
|
|
54
54
|
function readJson(path) {
|
|
@@ -138,11 +138,10 @@ function remove(item, run, platform, uid) {
|
|
|
138
138
|
unlinkSync(item.path);
|
|
139
139
|
return null;
|
|
140
140
|
}
|
|
141
|
-
//
|
|
142
|
-
if (lstatSync(item.path).isDirectory())
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
unlinkSync(item.path);
|
|
141
|
+
// CTC-4496: even explicit --data cannot delete shared roots. Unknown directories stay too.
|
|
142
|
+
if (item.data || lstatSync(item.path).isDirectory())
|
|
143
|
+
return "directory kept; shared data is protected";
|
|
144
|
+
unlinkSync(item.path);
|
|
146
145
|
return null;
|
|
147
146
|
}
|
|
148
147
|
catch (err) {
|
|
@@ -171,10 +170,10 @@ export async function cmdLegacy(args, ctx, deps = {}) {
|
|
|
171
170
|
}
|
|
172
171
|
const listLines = [
|
|
173
172
|
`Leftovers of the old local Catalyst runtime on this machine (${found.length}):`,
|
|
174
|
-
...found.map((f) => ` ${f.kind}: ${f.name}${f.kind === "job" ? ` — ${f.path}` : f.data ? " (kept
|
|
173
|
+
...found.map((f) => ` ${f.kind}: ${f.name}${f.kind === "job" ? ` — ${f.path}` : f.data ? " (kept: shared current state)" : ""}`),
|
|
175
174
|
];
|
|
176
175
|
if (!wantRemove) {
|
|
177
|
-
emit({ sourceCommit: LEGACY_SOURCE_COMMIT, found, removed: [], remaining: found }, [...listLines, RECOMMEND, "Run: catalyst legacy --remove (
|
|
176
|
+
emit({ sourceCommit: LEGACY_SOURCE_COMMIT, found, removed: [], remaining: found }, [...listLines, RECOMMEND, "Run: catalyst legacy --remove (all data folders are kept)"]);
|
|
178
177
|
return 1;
|
|
179
178
|
}
|
|
180
179
|
// the one question, only on a terminal; --yes answers it without asking
|
|
@@ -182,13 +181,13 @@ export async function cmdLegacy(args, ctx, deps = {}) {
|
|
|
182
181
|
if (!go) {
|
|
183
182
|
const tty = (deps.isTty ?? stdinIsTty)();
|
|
184
183
|
if (!tty) {
|
|
185
|
-
emit({ sourceCommit: LEGACY_SOURCE_COMMIT, found, removed: [], remaining: found, asked: false }, [...listLines, RECOMMEND, "nothing removed: no terminal to ask on; run catalyst legacy --remove --yes to remove these without a question (
|
|
184
|
+
emit({ sourceCommit: LEGACY_SOURCE_COMMIT, found, removed: [], remaining: found, asked: false }, [...listLines, RECOMMEND, "nothing removed: no terminal to ask on; run catalyst legacy --remove --yes to remove these without a question (all data folders are kept)"]);
|
|
186
185
|
return 1;
|
|
187
186
|
}
|
|
188
187
|
for (const l of listLines)
|
|
189
188
|
ctx.stdout(l);
|
|
190
189
|
ctx.stdout(RECOMMEND);
|
|
191
|
-
const answer = (await (deps.prompt ?? ((q) => promptSecret(q)))(`Remove ${found.length} item${found.length === 1 ? "" : "s"}
|
|
190
|
+
const answer = (await (deps.prompt ?? ((q) => promptSecret(q)))(`Remove ${found.length} item${found.length === 1 ? "" : "s"}, keeping all data folders? [y/N] `)).trim().toLowerCase();
|
|
192
191
|
go = answer === "y" || answer === "yes";
|
|
193
192
|
if (!go) {
|
|
194
193
|
emit({ sourceCommit: LEGACY_SOURCE_COMMIT, found, removed: [], remaining: found, asked: true }, ["nothing removed; run catalyst legacy --remove again when ready"]);
|
|
@@ -200,9 +199,9 @@ export async function cmdLegacy(args, ctx, deps = {}) {
|
|
|
200
199
|
const kept = [];
|
|
201
200
|
const lines = [];
|
|
202
201
|
for (const item of found) {
|
|
203
|
-
if (item.data
|
|
202
|
+
if (item.data) {
|
|
204
203
|
kept.push(item);
|
|
205
|
-
lines.push(`kept: ${item.kind} ${item.name} (
|
|
204
|
+
lines.push(`kept: ${item.kind} ${item.name} (shared current state; always kept)`);
|
|
206
205
|
continue;
|
|
207
206
|
}
|
|
208
207
|
const why = remove(item, run, platform, uid);
|
|
@@ -216,8 +215,8 @@ export async function cmdLegacy(args, ctx, deps = {}) {
|
|
|
216
215
|
}
|
|
217
216
|
}
|
|
218
217
|
// re-check from the same list, so the report says what the machine holds now, not what was attempted
|
|
219
|
-
const remaining = findLegacy(ctx.home, platform).filter((f) => !
|
|
220
|
-
const keptNote = kept.length > 0 ? "
|
|
218
|
+
const remaining = findLegacy(ctx.home, platform).filter((f) => !f.data);
|
|
219
|
+
const keptNote = kept.length > 0 ? "; all shared data folders were kept" : "";
|
|
221
220
|
lines.push(remaining.length === 0 ? `re-checked: nothing of the old runtime remains${keptNote}` : `re-checked: ${remaining.length} item${remaining.length === 1 ? "" : "s"} of the old runtime remain${remaining.length === 1 ? "s" : ""}${keptNote}: ${remaining.map((r) => `${r.kind} ${r.name}`).join(", ")}`);
|
|
222
221
|
emit({ sourceCommit: LEGACY_SOURCE_COMMIT, found, removed, remaining: [...remaining, ...kept], kept, failed }, lines);
|
|
223
222
|
return remaining.length === 0 ? 0 : 1;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { CliError, UsageError } from "./errors.js";
|
|
3
|
+
/** Bound callbacks too: a transport that forgets to honor abort cannot hold setup forever. */
|
|
4
|
+
function bounded(work, signal) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const abort = () => reject(new Error("consent_deadline"));
|
|
7
|
+
if (signal.aborted) {
|
|
8
|
+
reject(new Error("consent_deadline"));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
12
|
+
work.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function safeReason(reason, fallback) {
|
|
16
|
+
return reason && /^[a-z][a-z0-9_]{1,63}$/.test(reason) ? reason : fallback;
|
|
17
|
+
}
|
|
18
|
+
/** Poll live consent every two seconds for at most ten minutes, using actual elapsed time. */
|
|
19
|
+
export async function pollConsent(options) {
|
|
20
|
+
const timeoutMs = options.timeoutMs ?? 600_000;
|
|
21
|
+
const intervalMs = options.intervalMs ?? 2_000;
|
|
22
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 600_000 ||
|
|
23
|
+
!Number.isSafeInteger(intervalMs) || intervalMs <= 0)
|
|
24
|
+
throw new UsageError("consent timeout must be 1 to 600000 milliseconds, with a positive poll interval");
|
|
25
|
+
const now = options.now ?? (() => performance.now());
|
|
26
|
+
const started = now();
|
|
27
|
+
const deadline = new AbortController();
|
|
28
|
+
const timer = setTimeout(() => deadline.abort(), timeoutMs);
|
|
29
|
+
const signal = options.signal ? AbortSignal.any([options.signal, deadline.signal]) : deadline.signal;
|
|
30
|
+
const elapsedMs = () => Math.max(0, Math.round(now() - started));
|
|
31
|
+
const waiting = () => ({ state: "waiting", reason: options.signal?.aborted ? "interrupted" : "consent_timeout", elapsedMs: elapsedMs() });
|
|
32
|
+
const sleep = options.sleep ?? ((ms) => new Promise(resolve => setTimeout(resolve, ms)));
|
|
33
|
+
try {
|
|
34
|
+
while (!signal.aborted && elapsedMs() < timeoutMs) {
|
|
35
|
+
let status;
|
|
36
|
+
try {
|
|
37
|
+
status = await bounded(options.readStatus(signal), signal);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (signal.aborted)
|
|
41
|
+
return waiting();
|
|
42
|
+
if (error instanceof CliError && (error.status === 401 || error.status === 403))
|
|
43
|
+
return { state: "refused", reason: "consent_identity_refused", elapsedMs: elapsedMs() };
|
|
44
|
+
return { state: "failed", reason: "consent_status_failed", elapsedMs: elapsedMs() };
|
|
45
|
+
}
|
|
46
|
+
if (elapsedMs() >= timeoutMs || signal.aborted)
|
|
47
|
+
return waiting();
|
|
48
|
+
if (status.outcome === "connected")
|
|
49
|
+
return { state: "done", elapsedMs: elapsedMs() };
|
|
50
|
+
if (status.outcome === "refused" || status.outcome === "failed")
|
|
51
|
+
return { state: status.outcome, reason: safeReason(status.reason, `consent_${status.outcome}`), elapsedMs: elapsedMs() };
|
|
52
|
+
const remaining = timeoutMs - elapsedMs();
|
|
53
|
+
try {
|
|
54
|
+
await bounded(sleep(Math.min(intervalMs, remaining)), signal);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return waiting();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return waiting();
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
}
|
|
65
|
+
}
|