@beastmode-develeap/beastmode 0.1.36 → 0.1.38
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/dist/index.js +276 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7318,7 +7318,13 @@ function generateComposeYaml(tag) {
|
|
|
7318
7318
|
- ./config:/app/config
|
|
7319
7319
|
- ./runs:/app/runs
|
|
7320
7320
|
- ./daemon/logs:/app/daemon/logs:ro
|
|
7321
|
-
|
|
7321
|
+
# Claude credentials for the chat feature. Target /home/appuser/.claude
|
|
7322
|
+
# matches the ui image's USER appuser (cli/Dockerfile sets HOME to
|
|
7323
|
+
# /home/appuser); the entrypoint restores .claude.json from the
|
|
7324
|
+
# newest backup in this dir at startup so chat works across container
|
|
7325
|
+
# restarts. Pre-2026-04-15 this used /root/.claude, which silently
|
|
7326
|
+
# broke chat because appuser couldn't read those files.
|
|
7327
|
+
- \${HOME}/.claude:/home/appuser/.claude:ro
|
|
7322
7328
|
depends_on:
|
|
7323
7329
|
board:
|
|
7324
7330
|
condition: service_healthy
|
|
@@ -7337,6 +7343,15 @@ function generateComposeYaml(tag) {
|
|
|
7337
7343
|
- BEASTMODE_ROOT=/app
|
|
7338
7344
|
- BEASTMODE_BOARD_URL=http://board:8080
|
|
7339
7345
|
volumes:
|
|
7346
|
+
# Factory state (projects, extensions, deploy strategies). Shared
|
|
7347
|
+
# with the ui container so Settings UI edits AND daemon-side
|
|
7348
|
+
# template seeding (from project-templates/) both land in the same
|
|
7349
|
+
# bind-mounted location. Pre-2026-04-15 this mount was missing
|
|
7350
|
+
# from the daemon service, which silently caused the daemon's
|
|
7351
|
+
# template-seeding logic to write to ephemeral in-container
|
|
7352
|
+
# storage \u2014 the seeded files were invisible to the ui and lost
|
|
7353
|
+
# on restart.
|
|
7354
|
+
- ./.beastmode:/app/.beastmode
|
|
7340
7355
|
# Daemon config files. Mounted from the host so Settings UI writes
|
|
7341
7356
|
# (to beastmode.docker.json) are picked up on daemon restart without
|
|
7342
7357
|
# rebuilding the image.
|
|
@@ -7384,7 +7399,25 @@ function step(n, total, text) {
|
|
|
7384
7399
|
}
|
|
7385
7400
|
|
|
7386
7401
|
// src/cli/commands/init.ts
|
|
7387
|
-
var initCommand = new Command("init").description("Create a new BeastMode factory").argument("[name]", "Factory name (default: current directory name)").option("--project <path>", "Path to target project").option("--preset <preset>", "Pipeline preset (full, lean, prototype, infra, docs)").option("--backend <backend>", "Task backend (beastmode-board, github-issues)").option("--deploy <target>", "Deploy target (pr-only, vercel, aws-ecs, ...)").option("--methodology <name>", "Development methodology plugin").option("--plugin <name>", "Install plugin (repeatable)", collect, []).option("--from <template>", "Import config from template file").option("--ui", "Open web wizard instead of CLI").option("--yes", "Accept all defaults (non-interactive)").option(
|
|
7402
|
+
var initCommand = new Command("init").description("Create a new BeastMode factory").argument("[name]", "Factory name (default: current directory name)").option("--project <path>", "Path to target project").option("--preset <preset>", "Pipeline preset (full, lean, prototype, infra, docs)").option("--backend <backend>", "Task backend (beastmode-board, github-issues)").option("--deploy <target>", "Deploy target (pr-only, vercel, aws-ecs, ...)").option("--methodology <name>", "Development methodology plugin").option("--plugin <name>", "Install plugin (repeatable)", collect, []).option("--from <template>", "Import config from template file").option("--ui", "Open web wizard instead of CLI").option("--yes", "Accept all defaults (non-interactive)").option(
|
|
7403
|
+
"--project-github-token <token>",
|
|
7404
|
+
"GitHub PAT for the daemon's project operations (commit/push/PR/merge against PROJECT_DIR's git remote \u2014 needs `repo` scope). Alias for the legacy --github-token flag."
|
|
7405
|
+
).option(
|
|
7406
|
+
"--project-github-token-file <path>",
|
|
7407
|
+
"Read project GitHub PAT from file (safer than --project-github-token)"
|
|
7408
|
+
).option(
|
|
7409
|
+
"--github-token <token>",
|
|
7410
|
+
"Deprecated alias for --project-github-token. Kept for backward compat."
|
|
7411
|
+
).option(
|
|
7412
|
+
"--github-token-file <path>",
|
|
7413
|
+
"Deprecated alias for --project-github-token-file. Kept for backward compat."
|
|
7414
|
+
).option(
|
|
7415
|
+
"--ghcr-pull-token <token>",
|
|
7416
|
+
"GitHub PAT for pulling private beastmode images from ghcr.io/develeap/beastmode/* \u2014 needs `read:packages` scope. May be the same as --project-github-token but should normally be a separate dedicated token (Gap 16)."
|
|
7417
|
+
).option(
|
|
7418
|
+
"--ghcr-pull-token-file <path>",
|
|
7419
|
+
"Read GHCR pull token from file (safer than --ghcr-pull-token)"
|
|
7420
|
+
).option("--board-password <password>", "Board UI password (avoids interactive prompt)").option("--board-password-file <path>", "Read board password from file").action(async (name, opts) => {
|
|
7388
7421
|
try {
|
|
7389
7422
|
await runInit(name, opts);
|
|
7390
7423
|
} catch (err) {
|
|
@@ -7699,23 +7732,44 @@ async function runImageModeInit(name, opts) {
|
|
|
7699
7732
|
const factoryName = name || ".";
|
|
7700
7733
|
const targetDir = resolve5(factoryName === "." ? "." : factoryName);
|
|
7701
7734
|
step(1, totalSteps, "Credentials");
|
|
7702
|
-
let
|
|
7703
|
-
if (!
|
|
7735
|
+
let projectGithubToken = opts.projectGithubToken || (opts.projectGithubTokenFile ? readSecretFile(opts.projectGithubTokenFile) : "") || opts.githubToken || (opts.githubTokenFile ? readSecretFile(opts.githubTokenFile) : "") || process.env.PROJECT_GITHUB_TOKEN || process.env.GITHUB_TOKEN || "";
|
|
7736
|
+
if (!projectGithubToken && !opts.yes) {
|
|
7737
|
+
const answer = await inquirer.prompt([
|
|
7738
|
+
{
|
|
7739
|
+
type: "password",
|
|
7740
|
+
name: "token",
|
|
7741
|
+
message: "PROJECT_GITHUB_TOKEN (PAT for project commits/pushes/PRs \u2014 `repo` scope, or `public_repo` for public-only):",
|
|
7742
|
+
mask: "*"
|
|
7743
|
+
}
|
|
7744
|
+
]);
|
|
7745
|
+
projectGithubToken = answer.token || "";
|
|
7746
|
+
}
|
|
7747
|
+
if (projectGithubToken) {
|
|
7748
|
+
success("PROJECT_GITHUB_TOKEN set");
|
|
7749
|
+
} else {
|
|
7750
|
+
warn("PROJECT_GITHUB_TOKEN not set \u2014 daemon's git operations will fail until added to .env");
|
|
7751
|
+
}
|
|
7752
|
+
let ghcrPullToken = opts.ghcrPullToken || (opts.ghcrPullTokenFile ? readSecretFile(opts.ghcrPullTokenFile) : "") || process.env.GHCR_PULL_TOKEN || "";
|
|
7753
|
+
if (!ghcrPullToken && !opts.yes) {
|
|
7704
7754
|
const answer = await inquirer.prompt([
|
|
7705
7755
|
{
|
|
7706
7756
|
type: "password",
|
|
7707
7757
|
name: "token",
|
|
7708
|
-
message: "
|
|
7758
|
+
message: "GHCR_PULL_TOKEN (PAT for pulling private beastmode images from ghcr.io/develeap/beastmode/* \u2014 `read:packages` scope. Leave empty to fall back to the project token):",
|
|
7709
7759
|
mask: "*"
|
|
7710
7760
|
}
|
|
7711
7761
|
]);
|
|
7712
|
-
|
|
7762
|
+
ghcrPullToken = answer.token || "";
|
|
7713
7763
|
}
|
|
7714
|
-
if (
|
|
7715
|
-
|
|
7764
|
+
if (!ghcrPullToken && projectGithubToken) {
|
|
7765
|
+
ghcrPullToken = projectGithubToken;
|
|
7766
|
+
info("GHCR_PULL_TOKEN: falling back to PROJECT_GITHUB_TOKEN (you can split them later in .env)");
|
|
7767
|
+
} else if (ghcrPullToken) {
|
|
7768
|
+
success("GHCR_PULL_TOKEN set");
|
|
7716
7769
|
} else {
|
|
7717
|
-
warn("
|
|
7770
|
+
warn("GHCR_PULL_TOKEN not set \u2014 `docker compose pull` will fail for private images. Add to .env later.");
|
|
7718
7771
|
}
|
|
7772
|
+
const githubToken = projectGithubToken;
|
|
7719
7773
|
let uiPassword = opts.boardPassword || (opts.boardPasswordFile ? readSecretFile(opts.boardPasswordFile) : "") || "";
|
|
7720
7774
|
if (!uiPassword && !opts.yes) {
|
|
7721
7775
|
const { password } = await inquirer.prompt([
|
|
@@ -7772,12 +7826,15 @@ async function runImageModeInit(name, opts) {
|
|
|
7772
7826
|
}
|
|
7773
7827
|
step(2, totalSteps, "Docker Registry");
|
|
7774
7828
|
if (!isGhcrAuthenticated()) {
|
|
7775
|
-
|
|
7776
|
-
|
|
7829
|
+
const tokenForLogin = ghcrPullToken || projectGithubToken;
|
|
7830
|
+
if (tokenForLogin && loginToGhcr(tokenForLogin)) {
|
|
7831
|
+
success(
|
|
7832
|
+
`Authenticated to ghcr.io (via ${ghcrPullToken ? "GHCR_PULL_TOKEN" : "PROJECT_GITHUB_TOKEN fallback"})`
|
|
7833
|
+
);
|
|
7777
7834
|
} else {
|
|
7778
7835
|
info("Not logged in to ghcr.io \u2014 images may still pull if public");
|
|
7779
7836
|
info(" To log in manually: docker login ghcr.io -u <your-github-username>");
|
|
7780
|
-
if (!opts.yes && !opts.githubToken) {
|
|
7837
|
+
if (!opts.yes && !opts.githubToken && !opts.projectGithubToken && !opts.ghcrPullToken) {
|
|
7781
7838
|
const { retry } = await inquirer.prompt([
|
|
7782
7839
|
{
|
|
7783
7840
|
type: "confirm",
|
|
@@ -7802,23 +7859,44 @@ async function runImageModeInit(name, opts) {
|
|
|
7802
7859
|
success("docker-compose.yml");
|
|
7803
7860
|
const envLines = [
|
|
7804
7861
|
"# BeastMode environment \u2014 DO NOT COMMIT",
|
|
7805
|
-
|
|
7862
|
+
"",
|
|
7863
|
+
"# \u2500\u2500 Project credentials (Gap 16 \u2014 two-PAT model) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
7864
|
+
"# PROJECT_GITHUB_TOKEN: used by the daemon and Claude to commit /",
|
|
7865
|
+
"# push / open PRs / merge / review against the target project repo.",
|
|
7866
|
+
"# Needs `repo` scope for private repos, `public_repo` for public.",
|
|
7867
|
+
"# Aliased to GITHUB_TOKEN for backward compat with the gh CLI and",
|
|
7868
|
+
"# any code that still reads GITHUB_TOKEN.",
|
|
7869
|
+
`PROJECT_GITHUB_TOKEN=${projectGithubToken}`,
|
|
7870
|
+
`GITHUB_TOKEN=${projectGithubToken}`,
|
|
7871
|
+
"",
|
|
7872
|
+
"# GHCR_PULL_TOKEN: used by `docker compose pull` to fetch the",
|
|
7873
|
+
"# beastmode factory images from ghcr.io/develeap/beastmode/*.",
|
|
7874
|
+
"# Maintainer-provided (shared across a team) or your own PAT with",
|
|
7875
|
+
"# `read:packages` scope added. Rotates rarely. May be the same as",
|
|
7876
|
+
"# PROJECT_GITHUB_TOKEN but is normally a separate dedicated token.",
|
|
7877
|
+
`GHCR_PULL_TOKEN=${ghcrPullToken}`,
|
|
7878
|
+
"",
|
|
7806
7879
|
`BEASTMODE_UI_PASSWORD=${uiPassword}`,
|
|
7807
7880
|
"",
|
|
7808
|
-
"# Project
|
|
7881
|
+
"# \u2500\u2500 Project location \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
7882
|
+
"# Required. The daemon's git operations target the git remote of",
|
|
7883
|
+
"# this path. The daemon auto-resolves the github repo from",
|
|
7884
|
+
"# `git -C $PROJECT_DIR remote get-url origin` at startup.",
|
|
7809
7885
|
`PROJECT_DIR=${projectPath}`,
|
|
7810
7886
|
"",
|
|
7811
|
-
"# Optional
|
|
7887
|
+
"# \u2500\u2500 Optional \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
7888
|
+
"# Uncomment for faster direct Anthropic API calls (bypasses the",
|
|
7889
|
+
"# Claude Code subscription auth via ~/.claude.json).",
|
|
7812
7890
|
"# ANTHROPIC_API_KEY=sk-ant-...",
|
|
7813
7891
|
"",
|
|
7814
|
-
"#
|
|
7892
|
+
"# Last-resort override if the daemon's project_repo auto-resolution",
|
|
7815
7893
|
"# (git remote get-url origin) doesn't work for your setup.",
|
|
7816
7894
|
"# The daemon handles this automatically in >99% of cases.",
|
|
7817
7895
|
"# PROJECT_REPO=owner/repo",
|
|
7818
7896
|
""
|
|
7819
7897
|
];
|
|
7820
7898
|
writeFileSync13(join15(targetDir, ".env"), envLines.join("\n"), "utf-8");
|
|
7821
|
-
success(`.env (PROJECT_DIR=${projectPath})`);
|
|
7899
|
+
success(`.env (PROJECT_DIR=${projectPath}, two-PAT model)`);
|
|
7822
7900
|
for (const dir of ["data", "runs", "daemon/logs", ".beastmode", "config"]) {
|
|
7823
7901
|
mkdirSync12(join15(targetDir, dir), { recursive: true });
|
|
7824
7902
|
}
|
|
@@ -8725,6 +8803,185 @@ function checkGithubToken(env) {
|
|
|
8725
8803
|
}
|
|
8726
8804
|
return { label: "GITHUB_TOKEN / GH_TOKEN", status: "pass", detail: "set" };
|
|
8727
8805
|
}
|
|
8806
|
+
async function checkProjectGithubToken(env, factoryDir) {
|
|
8807
|
+
const token = env.PROJECT_GITHUB_TOKEN || env.GITHUB_TOKEN;
|
|
8808
|
+
if (!token) {
|
|
8809
|
+
return {
|
|
8810
|
+
label: "PROJECT_GITHUB_TOKEN",
|
|
8811
|
+
status: "fail",
|
|
8812
|
+
detail: "not set in env (or factory .env)",
|
|
8813
|
+
fix: "Add PROJECT_GITHUB_TOKEN=ghp_... to .env (needs `repo` scope for private, `public_repo` for public)"
|
|
8814
|
+
};
|
|
8815
|
+
}
|
|
8816
|
+
let ownerRepo = null;
|
|
8817
|
+
if (factoryDir) {
|
|
8818
|
+
const envPath = join22(factoryDir, ".env");
|
|
8819
|
+
if (existsSync24(envPath)) {
|
|
8820
|
+
try {
|
|
8821
|
+
const content = readFileSync21(envPath, "utf-8");
|
|
8822
|
+
const dirLine = content.split("\n").find(
|
|
8823
|
+
(l) => l.trim().startsWith("PROJECT_DIR=") && !l.trim().startsWith("#")
|
|
8824
|
+
);
|
|
8825
|
+
if (dirLine) {
|
|
8826
|
+
const projectDir = dirLine.split("=").slice(1).join("=").trim();
|
|
8827
|
+
if (projectDir && existsSync24(join22(projectDir, ".git"))) {
|
|
8828
|
+
const remote = tryExec(
|
|
8829
|
+
`git -C "${projectDir}" remote get-url origin 2>/dev/null`
|
|
8830
|
+
);
|
|
8831
|
+
if (remote) {
|
|
8832
|
+
const m = remote.match(
|
|
8833
|
+
/(?:https?:\/\/[^/]+\/|git@[^:]+:)([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/
|
|
8834
|
+
);
|
|
8835
|
+
if (m) ownerRepo = `${m[1]}/${m[2]}`;
|
|
8836
|
+
}
|
|
8837
|
+
}
|
|
8838
|
+
}
|
|
8839
|
+
} catch {
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
8842
|
+
}
|
|
8843
|
+
const apiPath = ownerRepo ? `/repos/${ownerRepo}` : "/user";
|
|
8844
|
+
const result = await new Promise((resolveProm) => {
|
|
8845
|
+
const req = https.request(
|
|
8846
|
+
{
|
|
8847
|
+
hostname: "api.github.com",
|
|
8848
|
+
path: apiPath,
|
|
8849
|
+
method: "GET",
|
|
8850
|
+
headers: {
|
|
8851
|
+
"User-Agent": "beastmode-doctor",
|
|
8852
|
+
Authorization: `Bearer ${token}`,
|
|
8853
|
+
Accept: "application/vnd.github+json"
|
|
8854
|
+
},
|
|
8855
|
+
timeout: 8e3
|
|
8856
|
+
},
|
|
8857
|
+
(res) => {
|
|
8858
|
+
let data = "";
|
|
8859
|
+
res.on("data", (chunk) => {
|
|
8860
|
+
data += chunk;
|
|
8861
|
+
});
|
|
8862
|
+
res.on("end", () => {
|
|
8863
|
+
let canPush = null;
|
|
8864
|
+
try {
|
|
8865
|
+
const parsed = JSON.parse(data);
|
|
8866
|
+
if (parsed.permissions && typeof parsed.permissions.push === "boolean") {
|
|
8867
|
+
canPush = parsed.permissions.push;
|
|
8868
|
+
}
|
|
8869
|
+
} catch {
|
|
8870
|
+
}
|
|
8871
|
+
resolveProm({ status: res.statusCode ?? 0, canPush });
|
|
8872
|
+
});
|
|
8873
|
+
}
|
|
8874
|
+
);
|
|
8875
|
+
req.on("error", () => resolveProm({ status: 0, canPush: null }));
|
|
8876
|
+
req.on("timeout", () => {
|
|
8877
|
+
req.destroy();
|
|
8878
|
+
resolveProm({ status: 0, canPush: null });
|
|
8879
|
+
});
|
|
8880
|
+
req.end();
|
|
8881
|
+
});
|
|
8882
|
+
if (result.status === 0) {
|
|
8883
|
+
return {
|
|
8884
|
+
label: "PROJECT_GITHUB_TOKEN",
|
|
8885
|
+
status: "warn",
|
|
8886
|
+
detail: "set but api.github.com unreachable (network issue?)"
|
|
8887
|
+
};
|
|
8888
|
+
}
|
|
8889
|
+
if (result.status === 401 || result.status === 403) {
|
|
8890
|
+
return {
|
|
8891
|
+
label: "PROJECT_GITHUB_TOKEN",
|
|
8892
|
+
status: "fail",
|
|
8893
|
+
detail: `invalid (HTTP ${result.status}${ownerRepo ? ` for ${ownerRepo}` : ""})`,
|
|
8894
|
+
fix: "Check the token at https://github.com/settings/tokens \u2014 needs `repo` scope (or `public_repo` for public repos only)"
|
|
8895
|
+
};
|
|
8896
|
+
}
|
|
8897
|
+
if (result.status === 404 && ownerRepo) {
|
|
8898
|
+
return {
|
|
8899
|
+
label: "PROJECT_GITHUB_TOKEN",
|
|
8900
|
+
status: "fail",
|
|
8901
|
+
detail: `cannot access ${ownerRepo} (HTTP 404 \u2014 repo missing or token has no read permission)`,
|
|
8902
|
+
fix: "Verify the token has access to the target repo + the repo URL is correct in PROJECT_DIR"
|
|
8903
|
+
};
|
|
8904
|
+
}
|
|
8905
|
+
if (result.status >= 200 && result.status < 300) {
|
|
8906
|
+
if (ownerRepo && result.canPush === true) {
|
|
8907
|
+
return {
|
|
8908
|
+
label: "PROJECT_GITHUB_TOKEN",
|
|
8909
|
+
status: "pass",
|
|
8910
|
+
detail: `valid + write access to ${ownerRepo}`
|
|
8911
|
+
};
|
|
8912
|
+
}
|
|
8913
|
+
if (ownerRepo && result.canPush === false) {
|
|
8914
|
+
return {
|
|
8915
|
+
label: "PROJECT_GITHUB_TOKEN",
|
|
8916
|
+
status: "fail",
|
|
8917
|
+
detail: `valid but NO write access to ${ownerRepo}`,
|
|
8918
|
+
fix: "The token's user is not a collaborator on the target repo OR the token lacks `repo` scope"
|
|
8919
|
+
};
|
|
8920
|
+
}
|
|
8921
|
+
return {
|
|
8922
|
+
label: "PROJECT_GITHUB_TOKEN",
|
|
8923
|
+
status: "pass",
|
|
8924
|
+
detail: ownerRepo ? `valid (couldn't determine permissions \u2014 repo response had no permissions field)` : `valid (skipped per-repo check \u2014 no PROJECT_DIR resolvable)`
|
|
8925
|
+
};
|
|
8926
|
+
}
|
|
8927
|
+
return {
|
|
8928
|
+
label: "PROJECT_GITHUB_TOKEN",
|
|
8929
|
+
status: "warn",
|
|
8930
|
+
detail: `HTTP ${result.status}`
|
|
8931
|
+
};
|
|
8932
|
+
}
|
|
8933
|
+
function checkGhcrPullToken(env) {
|
|
8934
|
+
const token = env.GHCR_PULL_TOKEN;
|
|
8935
|
+
if (!token) {
|
|
8936
|
+
if (env.GITHUB_TOKEN || env.PROJECT_GITHUB_TOKEN) {
|
|
8937
|
+
return {
|
|
8938
|
+
label: "GHCR_PULL_TOKEN",
|
|
8939
|
+
status: "warn",
|
|
8940
|
+
detail: "not set; falling back to PROJECT_GITHUB_TOKEN (works if it has read:packages scope)",
|
|
8941
|
+
fix: "Set GHCR_PULL_TOKEN=ghp_... in .env for a dedicated read-only token (read:packages scope)"
|
|
8942
|
+
};
|
|
8943
|
+
}
|
|
8944
|
+
return {
|
|
8945
|
+
label: "GHCR_PULL_TOKEN",
|
|
8946
|
+
status: "fail",
|
|
8947
|
+
detail: "not set \u2014 `docker compose pull` cannot fetch private beastmode images",
|
|
8948
|
+
fix: "Set GHCR_PULL_TOKEN=ghp_... in .env (read:packages scope)"
|
|
8949
|
+
};
|
|
8950
|
+
}
|
|
8951
|
+
const loggedIn = tryExec("docker login ghcr.io --get-login 2>/dev/null");
|
|
8952
|
+
if (!loggedIn) {
|
|
8953
|
+
return {
|
|
8954
|
+
label: "GHCR_PULL_TOKEN",
|
|
8955
|
+
status: "warn",
|
|
8956
|
+
detail: "set in env but docker is not logged in to ghcr.io",
|
|
8957
|
+
fix: "Run `docker login ghcr.io -u <github-username> --password-stdin <<< $GHCR_PULL_TOKEN`"
|
|
8958
|
+
};
|
|
8959
|
+
}
|
|
8960
|
+
const inspect = tryExec(
|
|
8961
|
+
"docker manifest inspect ghcr.io/develeap/beastmode/board:latest 2>&1",
|
|
8962
|
+
15e3
|
|
8963
|
+
);
|
|
8964
|
+
if (inspect && (inspect.includes("schemaVersion") || inspect.includes("mediaType"))) {
|
|
8965
|
+
return {
|
|
8966
|
+
label: "GHCR_PULL_TOKEN",
|
|
8967
|
+
status: "pass",
|
|
8968
|
+
detail: `valid (logged in as ${loggedIn}, can pull beastmode images)`
|
|
8969
|
+
};
|
|
8970
|
+
}
|
|
8971
|
+
if (inspect && inspect.includes("denied")) {
|
|
8972
|
+
return {
|
|
8973
|
+
label: "GHCR_PULL_TOKEN",
|
|
8974
|
+
status: "fail",
|
|
8975
|
+
detail: "logged in but lacks read:packages scope (denied)",
|
|
8976
|
+
fix: "Generate a new PAT at https://github.com/settings/tokens with `read:packages` scope"
|
|
8977
|
+
};
|
|
8978
|
+
}
|
|
8979
|
+
return {
|
|
8980
|
+
label: "GHCR_PULL_TOKEN",
|
|
8981
|
+
status: "warn",
|
|
8982
|
+
detail: `logged in as ${loggedIn} but image inspect didn't return a manifest`
|
|
8983
|
+
};
|
|
8984
|
+
}
|
|
8728
8985
|
function checkDocker() {
|
|
8729
8986
|
const out = tryExec("docker info --format '{{.ServerVersion}}' 2>/dev/null");
|
|
8730
8987
|
if (out) {
|
|
@@ -9359,6 +9616,8 @@ var doctorCommand = new Command11("doctor").description("Health check \u2014 val
|
|
|
9359
9616
|
const checks = [];
|
|
9360
9617
|
checks.push(...await checkClaudeAuth(env.ANTHROPIC_API_KEY));
|
|
9361
9618
|
checks.push(checkGithubToken(env));
|
|
9619
|
+
checks.push(await checkProjectGithubToken(env, hasFactory ? factoryDir : null));
|
|
9620
|
+
checks.push(checkGhcrPullToken(env));
|
|
9362
9621
|
checks.push(checkDocker());
|
|
9363
9622
|
checks.push(checkDockerComposeVersion());
|
|
9364
9623
|
checks.push(checkGhcrAuth());
|