@catalyst-cloud/cli 0.8.0
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 +75 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/bin/catalyst-skills.js +8 -0
- package/bin/catalyst.js +5 -0
- package/bin/launch.js +154 -0
- package/dist/args.js +280 -0
- package/dist/ask.js +161 -0
- package/dist/browser.js +20 -0
- package/dist/cli.js +397 -0
- package/dist/config.js +241 -0
- package/dist/contract-types.js +4 -0
- package/dist/contract.js +184 -0
- package/dist/detach.js +10 -0
- package/dist/environment.js +207 -0
- package/dist/errors.js +27 -0
- package/dist/events.js +106 -0
- package/dist/execution.js +451 -0
- package/dist/oauth.js +300 -0
- package/dist/pagination.js +76 -0
- package/dist/prompt.js +35 -0
- package/dist/published.js +79 -0
- package/dist/query.js +248 -0
- package/dist/ready.js +380 -0
- package/dist/release.js +142 -0
- package/dist/replica.js +614 -0
- package/dist/runtime-store.js +135 -0
- package/dist/runtime-verb.js +66 -0
- package/dist/runtime.js +87 -0
- package/dist/sdk.js +29 -0
- package/dist/secret.js +190 -0
- package/dist/semver.js +18 -0
- package/dist/skill-shape.js +189 -0
- package/dist/skills.js +129 -0
- package/dist/transport.js +205 -0
- package/dist/ts-deps-loader.js +113 -0
- package/dist/watch/consumer.js +141 -0
- package/dist/watch/cursor-file.js +62 -0
- package/dist/watch.js +175 -0
- package/dist/write.js +224 -0
- package/package.json +60 -0
- package/skills/catalyst-github/SKILL.md +35 -0
- package/skills/catalyst-github/agents/openai.yaml +6 -0
- package/skills/catalyst-github/agents/portability.yaml +4 -0
- package/skills/catalyst-github/references/is-it-mergeable.md +57 -0
- package/skills/catalyst-github/references/what-a-pr-accumulates.md +61 -0
- package/skills/catalyst-github/scripts/is-it-mergeable.mjs +124 -0
- package/skills/catalyst-github/scripts/lib/cli.mjs +103 -0
- package/skills/catalyst-github/scripts/lib/credential.mjs +29 -0
- package/skills/catalyst-github/scripts/lib/pull.mjs +82 -0
- package/skills/catalyst-github/scripts/read-pr.mjs +97 -0
- package/skills/catalyst-linear/SKILL.md +43 -0
- package/skills/catalyst-linear/agents/openai.yaml +6 -0
- package/skills/catalyst-linear/agents/portability.yaml +5 -0
- package/skills/catalyst-linear/references/reading-a-ticket.md +52 -0
- package/skills/catalyst-linear/references/what-a-ticket-accumulates.md +53 -0
- package/skills/catalyst-linear/references/writing-to-linear.md +43 -0
- package/skills/catalyst-linear/scripts/comment.mjs +59 -0
- package/skills/catalyst-linear/scripts/create-ticket.mjs +44 -0
- package/skills/catalyst-linear/scripts/label.mjs +48 -0
- package/skills/catalyst-linear/scripts/lib/cli.mjs +164 -0
- package/skills/catalyst-linear/scripts/lib/credential.mjs +29 -0
- package/skills/catalyst-linear/scripts/move.mjs +41 -0
- package/skills/catalyst-linear/scripts/read-ticket.mjs +93 -0
- package/skills/catalyst-linear/scripts/search.mjs +49 -0
- package/skills/catalyst-onboard/SKILL.md +57 -0
- package/skills/catalyst-onboard/agents/openai.yaml +6 -0
- package/skills/catalyst-onboard/agents/portability.yaml +5 -0
- package/skills/catalyst-onboard/references/declaring-a-repository.md +23 -0
- package/skills/catalyst-onboard/references/skill-sources.md +35 -0
- package/skills/catalyst-onboard/references/the-one-path.md +149 -0
- package/skills/catalyst-onboard/references/what-a-phase-needs.md +46 -0
- package/skills/catalyst-onboard/references/what-the-browser-owns.md +50 -0
- package/skills/catalyst-onboard/references/who-fixes-what.md +44 -0
- package/skills/catalyst-onboard/scripts/lib/cli.mjs +117 -0
- package/skills/catalyst-onboard/scripts/lib/credential.mjs +29 -0
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +345 -0
- package/skills/catalyst-setup/SKILL.md +36 -0
- package/skills/catalyst-setup/agents/openai.yaml +6 -0
- package/skills/catalyst-setup/agents/portability.yaml +4 -0
- package/skills/catalyst-setup/references/what-each-check-means.md +88 -0
- package/skills/catalyst-setup/scripts/check.mjs +75 -0
- package/skills/catalyst-setup/scripts/lib/cli.mjs +103 -0
- package/skills/catalyst-setup/scripts/lib/credential.mjs +29 -0
- package/skills/catalyst-setup/scripts/replica-status.mjs +46 -0
- package/skills/connect-me/SKILL.md +63 -0
- package/skills/connect-me/agents/openai.yaml +6 -0
- package/skills/connect-me/agents/portability.yaml +5 -0
- package/skills/connect-me/references/keeping-the-replica-running.md +88 -0
- package/skills/connect-me/scripts/lib/cli.mjs +185 -0
- package/skills/connect-me/scripts/lib/credential.mjs +29 -0
- package/skills/connect-me/scripts/verify-connection.mjs +68 -0
- package/skills/how-catalyst-works/SKILL.md +43 -0
- package/skills/how-catalyst-works/agents/openai.yaml +6 -0
- package/skills/how-catalyst-works/agents/portability.yaml +4 -0
- package/skills/how-catalyst-works/references/coding-accounts.md +51 -0
- package/skills/how-catalyst-works/references/stages-and-mapping.md +56 -0
- package/skills/how-catalyst-works/references/the-ladder.md +41 -0
- package/skills/how-catalyst-works/references/what-catalyst-is.md +30 -0
- package/skills/how-catalyst-works/references/what-runs-next.md +77 -0
- package/skills/how-catalyst-works/references/when-a-phase-fails.md +57 -0
- package/skills/how-catalyst-works/scripts/explain-ticket.mjs +41 -0
- package/skills/how-catalyst-works/scripts/lib/cli.mjs +164 -0
- package/skills/how-catalyst-works/scripts/lib/credential.mjs +29 -0
- package/skills/how-catalyst-works/scripts/show-my-map.mjs +94 -0
- package/skills/how-catalyst-works/scripts/whats-running.mjs +65 -0
- package/skills/run-this-project/SKILL.md +45 -0
- package/skills/run-this-project/agents/openai.yaml +6 -0
- package/skills/run-this-project/agents/portability.yaml +5 -0
- package/skills/run-this-project/assets/stall-policy.json +15 -0
- package/skills/run-this-project/references/making-work-ready.md +60 -0
- package/skills/run-this-project/references/reacting-to-events.md +76 -0
- package/skills/run-this-project/references/stalls-and-escalation.md +63 -0
- package/skills/run-this-project/scripts/lib/cli.mjs +185 -0
- package/skills/run-this-project/scripts/lib/credential.mjs +29 -0
- package/skills/run-this-project/scripts/make-ready.mjs +64 -0
- package/skills/run-this-project/scripts/scope-status.mjs +0 -0
- package/skills/run-this-project/scripts/watch-scope.mjs +61 -0
- package/skills/unstick/SKILL.md +41 -0
- package/skills/unstick/agents/openai.yaml +6 -0
- package/skills/unstick/agents/portability.yaml +5 -0
- package/skills/unstick/references/playbook.md +51 -0
- package/skills/unstick/scripts/lib/cli.mjs +135 -0
- package/skills/unstick/scripts/lib/credential.mjs +29 -0
- package/skills/unstick/scripts/unstick.mjs +57 -0
- package/skills/what-needs-me/SKILL.md +41 -0
- package/skills/what-needs-me/agents/openai.yaml +6 -0
- package/skills/what-needs-me/agents/portability.yaml +5 -0
- package/skills/what-needs-me/references/raising-a-decision.md +41 -0
- package/skills/what-needs-me/references/reading-the-inbox.md +38 -0
- package/skills/what-needs-me/references/settling-an-answer.md +37 -0
- package/skills/what-needs-me/scripts/inbox.mjs +56 -0
- package/skills/what-needs-me/scripts/lib/cli.mjs +135 -0
- package/skills/what-needs-me/scripts/lib/credential.mjs +29 -0
- package/skills/what-needs-me/scripts/raise.mjs +53 -0
- package/skills/what-needs-me/scripts/settle.mjs +73 -0
- package/skills/whats-happening/SKILL.md +43 -0
- package/skills/whats-happening/agents/openai.yaml +6 -0
- package/skills/whats-happening/agents/portability.yaml +4 -0
- package/skills/whats-happening/assets/status-reply.json +77 -0
- package/skills/whats-happening/references/reading-the-board.md +43 -0
- package/skills/whats-happening/references/reprioritising.md +37 -0
- package/skills/whats-happening/references/routing-work.md +36 -0
- package/skills/whats-happening/references/status-reply.md +34 -0
- package/skills/whats-happening/references/why-is-it-stuck.md +62 -0
- package/skills/whats-happening/scripts/explain.mjs +28 -0
- package/skills/whats-happening/scripts/lib/cli.mjs +135 -0
- package/skills/whats-happening/scripts/lib/credential.mjs +29 -0
- package/skills/whats-happening/scripts/snapshot.mjs +149 -0
- package/vendor/README.md +9 -0
- package/vendor/paths/index.d.ts +85 -0
- package/vendor/paths/index.js +148 -0
- package/vendor/paths/legacy-installer.d.ts +36 -0
- package/vendor/paths/legacy-installer.js +154 -0
- package/vendor/paths/node.d.ts +18 -0
- package/vendor/paths/node.js +102 -0
- package/vendor/paths/provenance.json +17 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// lib/cli.mjs — the one way a skill script reaches Catalyst Cloud: spawn the catalyst-skills CLI.
|
|
3
|
+
// The CLI holds the SDK and the key; this file holds neither. It reads customer.json only
|
|
4
|
+
// to learn where the CLI lives, and it exits 2 with one line when the machine is not connected.
|
|
5
|
+
// Run any script beside this one with --help; this file is a library and is never run directly.
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { CONNECT_COMMAND, hasCredential } from "./credential.mjs";
|
|
10
|
+
|
|
11
|
+
export const PACKAGE = "@catalyst-cloud/catalyst-skills";
|
|
12
|
+
export const NOT_CONFIGURED_EXIT = 2;
|
|
13
|
+
|
|
14
|
+
export function configPath() {
|
|
15
|
+
const home = process.env.CATALYST_SKILLS_HOME ?? process.env.HOME ?? "/";
|
|
16
|
+
return join(home, ".config", "catalyst-cloud", "customer.json");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The connected-machine config, or exit 2 with the one line that says how to connect. */
|
|
20
|
+
export function loadConfig() {
|
|
21
|
+
const path = configPath();
|
|
22
|
+
if (!existsSync(path)) notConfigured(`no config at ${path}`);
|
|
23
|
+
try {
|
|
24
|
+
const cfg = JSON.parse(readFileSync(path, "utf8"));
|
|
25
|
+
if (!hasCredential(cfg)) notConfigured(`config at ${path} has no key or login`);
|
|
26
|
+
return cfg;
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (err && err.exitCode === NOT_CONFIGURED_EXIT) throw err;
|
|
29
|
+
notConfigured(`config at ${path} is unreadable`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function notConfigured(why) {
|
|
34
|
+
process.stderr.write(`not connected (${why}) — run: ${CONNECT_COMMAND}\n`);
|
|
35
|
+
process.exit(NOT_CONFIGURED_EXIT);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Run one catalyst-skills verb and return {code, stdout, stderr}. Uses the CLI path the login
|
|
40
|
+
* recorded when it still exists, else `npx <package>`. A CLI exit of 2 with a "not joined" or
|
|
41
|
+
* "not connected" line is turned into this script's own exit 2, so every caller sees one contract.
|
|
42
|
+
*/
|
|
43
|
+
export function runCli(args, opts = {}) {
|
|
44
|
+
const cfg = loadConfig();
|
|
45
|
+
const useRecorded = typeof cfg.cliPath === "string" && existsSync(cfg.cliPath);
|
|
46
|
+
const cmd = useRecorded ? process.execPath : "npx";
|
|
47
|
+
const argv = useRecorded ? [cfg.cliPath, ...args] : [PACKAGE, ...args];
|
|
48
|
+
const res = spawnSync(cmd, argv, {
|
|
49
|
+
input: opts.stdin,
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
52
|
+
shell: !useRecorded && process.platform === "win32",
|
|
53
|
+
env: process.env,
|
|
54
|
+
});
|
|
55
|
+
if (res.error) {
|
|
56
|
+
process.stderr.write(`could not run ${cmd}: ${res.error.message}\n`);
|
|
57
|
+
process.exit(NOT_CONFIGURED_EXIT);
|
|
58
|
+
}
|
|
59
|
+
const stdout = res.stdout ?? "";
|
|
60
|
+
const stderr = res.stderr ?? "";
|
|
61
|
+
if (res.status === NOT_CONFIGURED_EXIT && /not (joined|connected|configured)/i.test(stderr)) {
|
|
62
|
+
process.stderr.write(stderr);
|
|
63
|
+
process.exit(NOT_CONFIGURED_EXIT);
|
|
64
|
+
}
|
|
65
|
+
return { code: res.status ?? 1, stdout, stderr };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Run a verb and require success; on failure print the CLI's own stderr and exit with its code (or 1). */
|
|
69
|
+
export function mustRun(args, opts = {}) {
|
|
70
|
+
const r = runCli(args, opts);
|
|
71
|
+
if (r.code !== 0) {
|
|
72
|
+
if (r.stderr) process.stderr.write(r.stderr.endsWith("\n") ? r.stderr : `${r.stderr}\n`);
|
|
73
|
+
if (r.stdout && !opts.quiet) process.stdout.write(r.stdout.endsWith("\n") ? r.stdout : `${r.stdout}\n`);
|
|
74
|
+
process.exit(r.code === 0 ? 1 : r.code);
|
|
75
|
+
}
|
|
76
|
+
return r;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Parse the CLI's --json stdout; a non-JSON answer is a failed check, exit 1. */
|
|
80
|
+
export function parseJson(stdout, what = "output") {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(stdout.trim());
|
|
83
|
+
} catch {
|
|
84
|
+
process.stderr.write(`could not parse ${what} as JSON: ${stdout.trim().slice(0, 200)}\n`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Minimal flag parsing for the scripts: --name value, --name=value, --flag, repeated flags collect. */
|
|
90
|
+
export function parseFlags(argv, spec) {
|
|
91
|
+
const flags = {};
|
|
92
|
+
const positionals = [];
|
|
93
|
+
for (let i = 0; i < argv.length; i++) {
|
|
94
|
+
const a = argv[i];
|
|
95
|
+
if (a === "--help" || a === "-h") return { help: true, flags, positionals };
|
|
96
|
+
if (a.startsWith("--")) {
|
|
97
|
+
let name = a.slice(2);
|
|
98
|
+
let value;
|
|
99
|
+
const eq = name.indexOf("=");
|
|
100
|
+
if (eq !== -1) {
|
|
101
|
+
value = name.slice(eq + 1);
|
|
102
|
+
name = name.slice(0, eq);
|
|
103
|
+
}
|
|
104
|
+
const s = spec[name];
|
|
105
|
+
if (!s) {
|
|
106
|
+
process.stderr.write(`unknown option --${name} (try --help)\n`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
if (s.value) {
|
|
110
|
+
if (value === undefined) value = argv[++i];
|
|
111
|
+
if (value === undefined || value === "") {
|
|
112
|
+
process.stderr.write(`--${name} needs a value\n`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
if (s.repeat) (flags[name] ??= []).push(value);
|
|
116
|
+
else flags[name] = value;
|
|
117
|
+
} else flags[name] = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
positionals.push(a);
|
|
121
|
+
}
|
|
122
|
+
return { help: false, flags, positionals };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function printHelp(usage, spec, notes = []) {
|
|
126
|
+
const lines = [`Usage: ${usage}`, ""];
|
|
127
|
+
const names = Object.keys(spec);
|
|
128
|
+
if (names.length) {
|
|
129
|
+
lines.push("Options:");
|
|
130
|
+
for (const n of names) lines.push(` --${n}${spec[n].value ? " <value>" : ""} ${spec[n].help}`);
|
|
131
|
+
lines.push("");
|
|
132
|
+
}
|
|
133
|
+
lines.push(...notes, "", "Exit codes: 0 ok · 1 the check failed or the arguments were wrong · 2 this machine is not connected to a tenant");
|
|
134
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
135
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// lib/credential.mjs — is this machine connected? The ONE place a skill script decides it, vendored
|
|
3
|
+
// byte-identical into every skill's scripts/lib/ from skill-lib/credential.mjs at the package root
|
|
4
|
+
// (`npm run skill-lib:sync`; a test fails on any drift). Skills install one directory at a time, so
|
|
5
|
+
// each carries its own copy. This file is a library — run a sibling script with --help for usage.
|
|
6
|
+
//
|
|
7
|
+
// customer.json carries exactly one credential: a personal key (`key`), or the keyless login's
|
|
8
|
+
// session (`auth`, the recommended rail). A script never reads either for its value: it spawns the
|
|
9
|
+
// catalyst-skills CLI, which authenticates with whichever is present and refreshes a login's token
|
|
10
|
+
// itself. A new credential kind lands here, once.
|
|
11
|
+
|
|
12
|
+
/** The command that connects this machine, as every not-connected line names it. */
|
|
13
|
+
export const CONNECT_COMMAND =
|
|
14
|
+
"npx @catalyst-cloud/catalyst-skills login (or, with a personal key: CATALYST_CLOUD_TOKEN=<your personal key> npx @catalyst-cloud/catalyst-skills login)";
|
|
15
|
+
|
|
16
|
+
/** True when `cfg` (parsed customer.json) holds a usable credential of either kind. Never throws. */
|
|
17
|
+
export function hasCredential(cfg) {
|
|
18
|
+
if (cfg === null || typeof cfg !== "object") return false;
|
|
19
|
+
const key = cfg["key"];
|
|
20
|
+
if (typeof key === "string" && key !== "") return true;
|
|
21
|
+
const login = cfg["auth"];
|
|
22
|
+
return (
|
|
23
|
+
login !== null &&
|
|
24
|
+
typeof login === "object" &&
|
|
25
|
+
login["kind"] === "oauth" &&
|
|
26
|
+
typeof login["refreshToken"] === "string" &&
|
|
27
|
+
login["refreshToken"] !== ""
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// snapshot.mjs — one call that answers "where are we?": the tenant contract (trimmed to what a
|
|
3
|
+
// status reply needs), what is running, what is queued, what is waiting on a human, and whether the
|
|
4
|
+
// local replica is fresh. Prints ONE JSON document on stdout and the source line on stderr.
|
|
5
|
+
import { mustRun, parseFlags, parseJson, printHelp, runCli } from "./lib/cli.mjs";
|
|
6
|
+
|
|
7
|
+
const SPEC = {
|
|
8
|
+
team: { value: true, help: "limit the queue and the board to this team key" },
|
|
9
|
+
board: { value: false, help: "also include open tickets grouped by stage, in the contract's slot order" },
|
|
10
|
+
limit: { value: true, help: "board: max tickets to read (default 200)" },
|
|
11
|
+
"full-contract": { value: false, help: "include the whole contract instead of the trimmed tenant block" },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const { help, flags } = parseFlags(process.argv.slice(2), SPEC);
|
|
15
|
+
if (help) {
|
|
16
|
+
printHelp("node scripts/snapshot.mjs [--team K] [--board] [--limit N] [--full-contract] [--help]", SPEC, [
|
|
17
|
+
"Runs, in order: catalyst-skills contract, running, queue, ask list, replica status (and query issues with --board).",
|
|
18
|
+
"Output: one JSON document {takenAt, source, tenant, running, queue, waitingOnHuman, board?, errors?}.",
|
|
19
|
+
"A section the cloud refused is reported under errors and the rest still prints; exit 1 in that case.",
|
|
20
|
+
"Every tenant fact (stage names, thresholds, teams) comes from the contract in this output, never from prose.",
|
|
21
|
+
]);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const errors = {};
|
|
26
|
+
const section = (name, args) => {
|
|
27
|
+
const r = runCli(args);
|
|
28
|
+
if (r.code !== 0) {
|
|
29
|
+
errors[name] = (r.stderr || r.stdout || `exit ${r.code}`).trim().split("\n").at(-1);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return parseJson(r.stdout, name);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const contract = parseJson(mustRun(["contract", "--json"], { quiet: true }).stdout, "contract");
|
|
36
|
+
// `replica status` exits 0 fresh, 1 stale, 3 absent — all three are verdicts with JSON on stdout,
|
|
37
|
+
// not failures; only a non-JSON answer is an error.
|
|
38
|
+
const replica = (() => {
|
|
39
|
+
const r = runCli(["replica", "status", "--json"]);
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(r.stdout.trim());
|
|
42
|
+
} catch {
|
|
43
|
+
errors.replica = (r.stderr || r.stdout || `exit ${r.code}`).trim().split("\n").at(-1);
|
|
44
|
+
return { verdict: "unknown", cursor: null, heartbeatAgeMs: null };
|
|
45
|
+
}
|
|
46
|
+
})();
|
|
47
|
+
const running = section("running", ["running", "--json"]);
|
|
48
|
+
const queue = section("queue", flags.team ? ["queue", "--team", flags.team, "--json"] : ["queue", "--json"]);
|
|
49
|
+
const asks = section("waitingOnHuman", ["ask", "list", "--json"]);
|
|
50
|
+
|
|
51
|
+
const out = {
|
|
52
|
+
takenAt: new Date().toISOString(),
|
|
53
|
+
source: {
|
|
54
|
+
reads: "api",
|
|
55
|
+
replica: replica.verdict,
|
|
56
|
+
replicaCursor: replica.cursor ?? null,
|
|
57
|
+
replicaHeartbeatAgeMs: replica.heartbeatAgeMs ?? null,
|
|
58
|
+
},
|
|
59
|
+
tenant: flags["full-contract"] ? contract : trimContract(contract, flags.team),
|
|
60
|
+
running,
|
|
61
|
+
queue,
|
|
62
|
+
waitingOnHuman: asks,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (flags.board) {
|
|
66
|
+
const args = ["query", "issues", "--limit", String(flags.limit ?? 200), "--json"];
|
|
67
|
+
if (flags.team) args.push("--team", flags.team);
|
|
68
|
+
const r = runCli(args);
|
|
69
|
+
const sourceLine = (r.stderr.match(/^source: .*$/m) ?? [null])[0];
|
|
70
|
+
// A board read is a window (`--limit` stays the default), and a window the cloud cut short must
|
|
71
|
+
// say so rather than reading as the whole team.
|
|
72
|
+
const truncatedLine = (r.stderr.match(/^truncated at .*$/m) ?? [null])[0];
|
|
73
|
+
if (r.code !== 0) errors.board = (r.stderr || `exit ${r.code}`).trim().split("\n").at(-1);
|
|
74
|
+
else {
|
|
75
|
+
const rows = parseJson(r.stdout, "issues");
|
|
76
|
+
out.board = groupByStage(rows, contract, flags.team);
|
|
77
|
+
if (sourceLine) out.source.board = sourceLine.replace(/^source: /, "");
|
|
78
|
+
if (truncatedLine) out.source.boardTruncated = truncatedLine;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (Object.keys(errors).length) out.errors = errors;
|
|
83
|
+
process.stderr.write(`source: api for running/queue/asks; replica ${replica.verdict}${replica.cursor != null ? ` (cursor ${replica.cursor})` : ""}${out.source.board ? `; board from ${out.source.board}` : ""}\n`);
|
|
84
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
85
|
+
process.exit(Object.keys(errors).length ? 1 : 0);
|
|
86
|
+
|
|
87
|
+
/** The contract facts a status reply needs, and nothing a reply should not paste. */
|
|
88
|
+
function trimContract(doc, teamKey) {
|
|
89
|
+
const teams = (doc.teams ?? []).filter((t) => !teamKey || String(t.key ?? "").toUpperCase() === teamKey.toUpperCase());
|
|
90
|
+
return {
|
|
91
|
+
contractVersion: doc.contractVersion,
|
|
92
|
+
account: { slug: doc.account?.slug, name: doc.account?.name },
|
|
93
|
+
slots: doc.slots,
|
|
94
|
+
ladder: { phases: doc.ladder?.phases, intakeEnabled: doc.ladder?.intakeEnabled, keying: doc.ladder?.keying },
|
|
95
|
+
thresholds: doc.thresholds,
|
|
96
|
+
mergeDefaultPolicy: doc.merge?.defaultPolicy,
|
|
97
|
+
teams: teams.map((t) => ({
|
|
98
|
+
key: t.key,
|
|
99
|
+
name: t.name,
|
|
100
|
+
workflowMode: t.workflowMode,
|
|
101
|
+
readiness: t.readiness?.status,
|
|
102
|
+
stages: Object.fromEntries(
|
|
103
|
+
(doc.slots ?? Object.keys(t.stages ?? {}))
|
|
104
|
+
.filter((slot) => t.stages?.[slot])
|
|
105
|
+
.map((slot) => [slot, { name: t.stages[slot].name, type: t.stages[slot].type, stateStillExists: t.stages[slot].stateStillExists }]),
|
|
106
|
+
),
|
|
107
|
+
labels: {
|
|
108
|
+
ask: (t.labels?.ask ?? []).map((l) => l.name),
|
|
109
|
+
hold: (t.labels?.hold ?? []).map((l) => l.name),
|
|
110
|
+
release: (t.labels?.release ?? []).map((l) => l.name),
|
|
111
|
+
},
|
|
112
|
+
})),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Open tickets by stage name, ordered by the contract's slot order for the ticket's team; unmapped
|
|
117
|
+
* state names follow, alphabetically. Terminal-typed states are dropped so the board is work only. */
|
|
118
|
+
function groupByStage(rows, doc, teamKey) {
|
|
119
|
+
const order = [];
|
|
120
|
+
const seen = new Set();
|
|
121
|
+
const terminal = new Set();
|
|
122
|
+
for (const t of doc.teams ?? []) {
|
|
123
|
+
if (teamKey && String(t.key ?? "").toUpperCase() !== teamKey.toUpperCase()) continue;
|
|
124
|
+
for (const slot of doc.slots ?? []) {
|
|
125
|
+
const s = t.stages?.[slot];
|
|
126
|
+
if (!s?.name) continue;
|
|
127
|
+
if (slot === "done" || slot === "canceled" || ["completed", "canceled", "cancelled"].includes(String(s.type ?? "").toLowerCase())) terminal.add(s.name);
|
|
128
|
+
else if (!seen.has(s.name)) {
|
|
129
|
+
seen.add(s.name);
|
|
130
|
+
order.push(s.name);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const byStage = {};
|
|
135
|
+
for (const r of rows) {
|
|
136
|
+
const state = String(r.state ?? "unknown");
|
|
137
|
+
if (terminal.has(state)) continue;
|
|
138
|
+
(byStage[state] ??= []).push({
|
|
139
|
+
identifier: r.identifier,
|
|
140
|
+
title: r.title,
|
|
141
|
+
priority: r.priority ?? null,
|
|
142
|
+
assignee: r.assignee_name ?? r.assignee ?? null,
|
|
143
|
+
updatedAt: r.updated_at ?? null,
|
|
144
|
+
projectId: r.project_id ?? null,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const extra = Object.keys(byStage).filter((s) => !seen.has(s)).sort();
|
|
148
|
+
return { stageOrder: [...order, ...extra], byStage, terminalStagesDropped: [...terminal] };
|
|
149
|
+
}
|
package/vendor/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Temporary paths runtime
|
|
2
|
+
|
|
3
|
+
`paths/` contains the generated JavaScript and declarations from `@catalyst-cloud/paths`, catalyst-cloud commit `c391358d06720985126c15b7fa9d9d6ad1878211`, PR 6059. This avoids a separate implementation of the version 1 machine path contract while that package is private. Replace the imports with the published package when publication is approved.
|
|
4
|
+
|
|
5
|
+
Run `CATALYST_PATHS_SOURCE=/path/to/catalyst-cloud node scripts/vendor-paths.mjs` to regenerate from that exact Git object. Add `--check` to compare a fresh compilation with the checked-in files. Without a source checkout, `npm run paths:check` validates each generated file against the recorded hashes. The generator uses this repository's locked TypeScript compiler.
|
|
6
|
+
|
|
7
|
+
Replica selection is `--db` where accepted, `CATALYST_REPLICA_DB`, then the machine manifest's `replicaDb`. Before a machine manifest exists, the CLI retains its saved customer.json path or legacy default. A manifest without replicaDb leaves replication optional and unconfigured. A malformed or missing explicit manifest fails instead of selecting another database.
|
|
8
|
+
|
|
9
|
+
The reader derives the lock from the selected database on each status check. No existing database is copied, moved, opened for writing, or reseeded by path resolution. Setup must import a discovered populated replica into the manifest before activating it. This PR does not install a manifest or start a writer.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Pure POSIX path contract. No process globals or filesystem imports. */
|
|
2
|
+
export declare const PATH_ROLES: {
|
|
3
|
+
readonly repoRoot: {
|
|
4
|
+
readonly variable: "CATALYST_REPO_ROOT";
|
|
5
|
+
readonly mode: "0755";
|
|
6
|
+
};
|
|
7
|
+
readonly worktrees: {
|
|
8
|
+
readonly variable: "CATALYST_WORKTREES_DIR";
|
|
9
|
+
readonly mode: "0755";
|
|
10
|
+
};
|
|
11
|
+
readonly logs: {
|
|
12
|
+
readonly variable: "CATALYST_LOGS_DIR";
|
|
13
|
+
readonly mode: "0755";
|
|
14
|
+
};
|
|
15
|
+
readonly events: {
|
|
16
|
+
readonly variable: "CATALYST_EVENTS_DIR";
|
|
17
|
+
readonly mode: "0755";
|
|
18
|
+
};
|
|
19
|
+
readonly config: {
|
|
20
|
+
readonly variable: "CATALYST_CONFIG_DIR";
|
|
21
|
+
readonly mode: "0700";
|
|
22
|
+
};
|
|
23
|
+
readonly cache: {
|
|
24
|
+
readonly variable: "CATALYST_CACHE_DIR";
|
|
25
|
+
readonly mode: "0755";
|
|
26
|
+
};
|
|
27
|
+
readonly state: {
|
|
28
|
+
readonly variable: "CATALYST_STATE_DIR";
|
|
29
|
+
readonly mode: "0755";
|
|
30
|
+
};
|
|
31
|
+
readonly skills: {
|
|
32
|
+
readonly variable: "CATALYST_SKILLS_DIR";
|
|
33
|
+
readonly mode: "0755";
|
|
34
|
+
};
|
|
35
|
+
readonly thoughtsRepo: {
|
|
36
|
+
readonly variable: "CATALYST_THOUGHTS_REPO";
|
|
37
|
+
readonly mode: "0755";
|
|
38
|
+
};
|
|
39
|
+
readonly replicaDb: {
|
|
40
|
+
readonly variable: "CATALYST_REPLICA_DB";
|
|
41
|
+
readonly mode: "0600";
|
|
42
|
+
};
|
|
43
|
+
readonly artifacts: {
|
|
44
|
+
readonly variable: "CATALYST_ARTIFACT_DIR";
|
|
45
|
+
readonly mode: "0700";
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export type PathRole = keyof typeof PATH_ROLES;
|
|
49
|
+
export type MachineRole = Exclude<PathRole, "artifacts">;
|
|
50
|
+
export type Environment = Readonly<Record<string, string | undefined>>;
|
|
51
|
+
export type PathOverrides = Readonly<Partial<Record<PathRole, string>>>;
|
|
52
|
+
/** Mandatory v1 fields for setup/installer composers; optional roles have no setup default. */
|
|
53
|
+
export declare const REQUIRED_MACHINE_PATH_ROLES: readonly ["repoRoot", "worktrees", "logs", "events", "config", "cache", "state", "skills"];
|
|
54
|
+
export type MachinePathValues = Record<(typeof REQUIRED_MACHINE_PATH_ROLES)[number], string> & {
|
|
55
|
+
replicaDb?: string;
|
|
56
|
+
thoughtsRepo?: string;
|
|
57
|
+
};
|
|
58
|
+
export type PathProvenance = "explicit" | "environment" | "imported" | "default";
|
|
59
|
+
export interface MachinePaths {
|
|
60
|
+
readonly version: 1;
|
|
61
|
+
readonly paths: MachinePathValues;
|
|
62
|
+
readonly provenance: Partial<Record<MachineRole, PathProvenance>>;
|
|
63
|
+
}
|
|
64
|
+
export type Profile = "workstation" | "container";
|
|
65
|
+
export interface ResolveOptions {
|
|
66
|
+
readonly profile?: Profile;
|
|
67
|
+
readonly overrides?: PathOverrides;
|
|
68
|
+
readonly env?: Environment;
|
|
69
|
+
/** Explicitly selected record; the Node adapter owns implicit discovery. */
|
|
70
|
+
readonly machine?: MachinePaths;
|
|
71
|
+
}
|
|
72
|
+
export declare function absolutePath(value: unknown, name: string): string;
|
|
73
|
+
/** Reject extra fields, including credentials and per-run artifacts. */
|
|
74
|
+
export declare function parseMachinePaths(value: unknown): MachinePaths;
|
|
75
|
+
export declare function resolveCatalystPath(role: string, options?: ResolveOptions): string;
|
|
76
|
+
export interface ProposalOptions {
|
|
77
|
+
readonly env: Environment;
|
|
78
|
+
readonly overrides?: PathOverrides;
|
|
79
|
+
readonly existing?: MachinePaths;
|
|
80
|
+
/** Existing locations, never relocated contents. */
|
|
81
|
+
readonly discovered?: Partial<Record<MachineRole, string>>;
|
|
82
|
+
}
|
|
83
|
+
/** Setup only. Runtime never calls this and this function never writes. */
|
|
84
|
+
export declare function proposeMachinePaths(options: ProposalOptions): MachinePaths;
|
|
85
|
+
export { CATALYST_DIRECTORY_ROLES as LEGACY_DIRECTORY_ROLES, CREATABLE_DIRECTORY_ROLES as LEGACY_CREATABLE_DIRECTORY_ROLES, LEGACY_BASE_SHELL, resolveWorkstationDir, type CatalystDirectoryRole, type DirectoryBase, } from "./legacy-installer.js";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/** Pure POSIX path contract. No process globals or filesystem imports. */
|
|
2
|
+
export const PATH_ROLES = {
|
|
3
|
+
repoRoot: { variable: "CATALYST_REPO_ROOT", mode: "0755" },
|
|
4
|
+
worktrees: { variable: "CATALYST_WORKTREES_DIR", mode: "0755" },
|
|
5
|
+
logs: { variable: "CATALYST_LOGS_DIR", mode: "0755" },
|
|
6
|
+
events: { variable: "CATALYST_EVENTS_DIR", mode: "0755" },
|
|
7
|
+
config: { variable: "CATALYST_CONFIG_DIR", mode: "0700" },
|
|
8
|
+
cache: { variable: "CATALYST_CACHE_DIR", mode: "0755" },
|
|
9
|
+
state: { variable: "CATALYST_STATE_DIR", mode: "0755" },
|
|
10
|
+
skills: { variable: "CATALYST_SKILLS_DIR", mode: "0755" },
|
|
11
|
+
thoughtsRepo: { variable: "CATALYST_THOUGHTS_REPO", mode: "0755" },
|
|
12
|
+
replicaDb: { variable: "CATALYST_REPLICA_DB", mode: "0600" },
|
|
13
|
+
artifacts: { variable: "CATALYST_ARTIFACT_DIR", mode: "0700" },
|
|
14
|
+
};
|
|
15
|
+
/** Mandatory v1 fields for setup/installer composers; optional roles have no setup default. */
|
|
16
|
+
export const REQUIRED_MACHINE_PATH_ROLES = [
|
|
17
|
+
"repoRoot",
|
|
18
|
+
"worktrees",
|
|
19
|
+
"logs",
|
|
20
|
+
"events",
|
|
21
|
+
"config",
|
|
22
|
+
"cache",
|
|
23
|
+
"state",
|
|
24
|
+
"skills",
|
|
25
|
+
];
|
|
26
|
+
export function absolutePath(value, name) {
|
|
27
|
+
if (typeof value !== "string" || !value.startsWith("/") || value.includes("\0")) {
|
|
28
|
+
throw new Error(`paths: ${name} must be an absolute POSIX path`);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function isRole(role) {
|
|
33
|
+
return Object.hasOwn(PATH_ROLES, role);
|
|
34
|
+
}
|
|
35
|
+
function object(value, name) {
|
|
36
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
37
|
+
throw new Error(`paths: ${name} must be an object`);
|
|
38
|
+
return Object.fromEntries(Object.entries(value));
|
|
39
|
+
}
|
|
40
|
+
function keys(value, allowed, name) {
|
|
41
|
+
for (const key of Object.keys(value))
|
|
42
|
+
if (!allowed.includes(key))
|
|
43
|
+
throw new Error(`paths: unknown ${name} field ${key}`);
|
|
44
|
+
}
|
|
45
|
+
/** Reject extra fields, including credentials and per-run artifacts. */
|
|
46
|
+
export function parseMachinePaths(value) {
|
|
47
|
+
const record = object(value, "machine record");
|
|
48
|
+
keys(record, ["version", "paths", "provenance"], "machine record");
|
|
49
|
+
if (record.version !== 1)
|
|
50
|
+
throw new Error(`paths: unsupported machine record version ${String(record.version)}`);
|
|
51
|
+
const paths = object(record.paths, "paths");
|
|
52
|
+
const provenance = object(record.provenance, "provenance");
|
|
53
|
+
const roles = Object.keys(PATH_ROLES).filter((role) => role !== "artifacts");
|
|
54
|
+
keys(paths, roles, "paths");
|
|
55
|
+
keys(provenance, roles, "provenance");
|
|
56
|
+
const validated = {
|
|
57
|
+
repoRoot: absolutePath(paths.repoRoot, "repoRoot"),
|
|
58
|
+
worktrees: absolutePath(paths.worktrees, "worktrees"),
|
|
59
|
+
logs: absolutePath(paths.logs, "logs"),
|
|
60
|
+
events: absolutePath(paths.events, "events"),
|
|
61
|
+
config: absolutePath(paths.config, "config"),
|
|
62
|
+
cache: absolutePath(paths.cache, "cache"),
|
|
63
|
+
state: absolutePath(paths.state, "state"),
|
|
64
|
+
skills: absolutePath(paths.skills, "skills"),
|
|
65
|
+
};
|
|
66
|
+
if (Object.hasOwn(paths, "replicaDb"))
|
|
67
|
+
validated.replicaDb = absolutePath(paths.replicaDb, "replicaDb");
|
|
68
|
+
if (Object.hasOwn(paths, "thoughtsRepo"))
|
|
69
|
+
validated.thoughtsRepo = absolutePath(paths.thoughtsRepo, "thoughtsRepo");
|
|
70
|
+
const sources = {};
|
|
71
|
+
for (const [role, source] of Object.entries(provenance)) {
|
|
72
|
+
if (!isRole(role) || role === "artifacts" || !Object.hasOwn(validated, role))
|
|
73
|
+
throw new Error(`paths: provenance has no path for ${role}`);
|
|
74
|
+
if (source !== "explicit" &&
|
|
75
|
+
source !== "environment" &&
|
|
76
|
+
source !== "imported" &&
|
|
77
|
+
source !== "default")
|
|
78
|
+
throw new Error(`paths: invalid provenance for ${role}`);
|
|
79
|
+
sources[role] = source;
|
|
80
|
+
}
|
|
81
|
+
return { version: 1, paths: validated, provenance: sources };
|
|
82
|
+
}
|
|
83
|
+
export function resolveCatalystPath(role, options = {}) {
|
|
84
|
+
if (!isRole(role))
|
|
85
|
+
throw new Error(`paths: unknown role ${role}`);
|
|
86
|
+
const variable = PATH_ROLES[role].variable;
|
|
87
|
+
const explicit = options.overrides?.[role];
|
|
88
|
+
if (explicit !== undefined)
|
|
89
|
+
return absolutePath(explicit, role);
|
|
90
|
+
const environment = options.env?.[variable];
|
|
91
|
+
if (environment !== undefined)
|
|
92
|
+
return absolutePath(environment, variable);
|
|
93
|
+
if (options.machine && role !== "artifacts") {
|
|
94
|
+
const path = parseMachinePaths(options.machine).paths[role];
|
|
95
|
+
if (path !== undefined)
|
|
96
|
+
return path;
|
|
97
|
+
}
|
|
98
|
+
throw new Error(`paths: ${variable} is not set; declare ${role} explicitly or run paths configure`);
|
|
99
|
+
}
|
|
100
|
+
/** Setup only. Runtime never calls this and this function never writes. */
|
|
101
|
+
export function proposeMachinePaths(options) {
|
|
102
|
+
const existing = options.existing && parseMachinePaths(options.existing);
|
|
103
|
+
const paths = {};
|
|
104
|
+
const provenance = {};
|
|
105
|
+
const env = options.env;
|
|
106
|
+
const home = () => absolutePath(env.HOME, "HOME");
|
|
107
|
+
const base = (variable, fallback) => absolutePath(env[variable] ?? fallback(), variable);
|
|
108
|
+
const defaults = {
|
|
109
|
+
repoRoot: () => `${base("CATALYST_HOME", () => `${home()}/catalyst`)}/repos`,
|
|
110
|
+
worktrees: () => `${base("CATALYST_HOME", () => `${home()}/catalyst`)}/wt`,
|
|
111
|
+
logs: () => `${base("XDG_STATE_HOME", () => `${home()}/.local/state`)}/catalyst/logs`,
|
|
112
|
+
events: () => `${base("XDG_STATE_HOME", () => `${home()}/.local/state`)}/catalyst/events`,
|
|
113
|
+
config: () => `${base("XDG_CONFIG_HOME", () => `${home()}/.config`)}/catalyst`,
|
|
114
|
+
cache: () => `${base("XDG_CACHE_HOME", () => `${home()}/.cache`)}/catalyst`,
|
|
115
|
+
state: () => `${base("XDG_STATE_HOME", () => `${home()}/.local/state`)}/catalyst`,
|
|
116
|
+
skills: () => `${home()}/.agents/skills`,
|
|
117
|
+
};
|
|
118
|
+
for (const role of Object.keys(PATH_ROLES)) {
|
|
119
|
+
if (!isRole(role) || role === "artifacts")
|
|
120
|
+
continue;
|
|
121
|
+
const explicit = options.overrides?.[role];
|
|
122
|
+
const environment = env[PATH_ROLES[role].variable];
|
|
123
|
+
const accepted = existing?.paths[role];
|
|
124
|
+
const imported = options.discovered?.[role];
|
|
125
|
+
const value = explicit ??
|
|
126
|
+
environment ??
|
|
127
|
+
accepted ??
|
|
128
|
+
imported ??
|
|
129
|
+
(role === "replicaDb" || role === "thoughtsRepo" ? undefined : defaults[role]());
|
|
130
|
+
if (value === undefined)
|
|
131
|
+
continue;
|
|
132
|
+
paths[role] = absolutePath(value, role);
|
|
133
|
+
const source = explicit !== undefined
|
|
134
|
+
? "explicit"
|
|
135
|
+
: environment !== undefined
|
|
136
|
+
? "environment"
|
|
137
|
+
: accepted !== undefined
|
|
138
|
+
? existing?.provenance[role]
|
|
139
|
+
: imported !== undefined
|
|
140
|
+
? "imported"
|
|
141
|
+
: "default";
|
|
142
|
+
if (source !== undefined)
|
|
143
|
+
provenance[role] = source;
|
|
144
|
+
}
|
|
145
|
+
return parseMachinePaths({ version: 1, paths, provenance });
|
|
146
|
+
}
|
|
147
|
+
// Transitional installer adapter only. Runtime callers must use resolveCatalystPath.
|
|
148
|
+
export { CATALYST_DIRECTORY_ROLES as LEGACY_DIRECTORY_ROLES, CREATABLE_DIRECTORY_ROLES as LEGACY_CREATABLE_DIRECTORY_ROLES, LEGACY_BASE_SHELL, resolveWorkstationDir, } from "./legacy-installer.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Where a role's directory lives when no explicit environment variable overrides it. */
|
|
2
|
+
export type DirectoryBase = "catalyst-home" | "xdg-state" | "xdg-config" | "xdg-cache" | "xdg-data" | "none";
|
|
3
|
+
export interface CatalystDirectoryRole {
|
|
4
|
+
/** CTC-2548's role id, verbatim. */
|
|
5
|
+
readonly role: string;
|
|
6
|
+
/** CTC-2548's canonical environment variable for this role, verbatim. */
|
|
7
|
+
readonly variable: string;
|
|
8
|
+
/** "none" ⇒ this role has no default and always refuses without the explicit variable. */
|
|
9
|
+
readonly base: DirectoryBase;
|
|
10
|
+
/** Path segments appended to the resolved base, in order. */
|
|
11
|
+
readonly segments: readonly string[];
|
|
12
|
+
/** false ⇒ setup never creates this directory (artifacts is minted per run, not provisioned). */
|
|
13
|
+
readonly creatable: boolean;
|
|
14
|
+
/** 0700 for anything holding a credential or machine state; 0755 otherwise. */
|
|
15
|
+
readonly mode: "0700" | "0755";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The ten roles, in CTC-2548's own order. `artifacts` is last-but-one and is the one role with
|
|
19
|
+
* `base: "none"` — it is minted per run by the caller (`CATALYST_ARTIFACT_DIR`) and this script
|
|
20
|
+
* must never invent a default for it.
|
|
21
|
+
*/
|
|
22
|
+
export declare const CATALYST_DIRECTORY_ROLES: readonly CatalystDirectoryRole[];
|
|
23
|
+
/** The roles setup actually creates, in table order. Never hand-counted (CTC-2552 was exactly that bug). */
|
|
24
|
+
export declare const CREATABLE_DIRECTORY_ROLES: readonly CatalystDirectoryRole[];
|
|
25
|
+
type Env = Record<string, string | undefined>;
|
|
26
|
+
/** The shell expression for a base's default (mirrors {@link baseDefault}, rendered instead of resolved). */
|
|
27
|
+
export declare const LEGACY_BASE_SHELL: Record<DirectoryBase, string | null>;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve one role's directory from `env`, the same precedence a shell caller gets from the
|
|
30
|
+
* rendered block: explicit variable → base default → a NAMED refusal, never a relative guess
|
|
31
|
+
* (the house shape: apps/host-sync/src/cli.ts:801-812).
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveWorkstationDir(role: string, env: Env): string | {
|
|
34
|
+
readonly refused: string;
|
|
35
|
+
};
|
|
36
|
+
export {};
|