@theholocron/cli 2.0.0-alpha.72 → 2.0.0-alpha.74
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/cli.mjs +156 -20
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -483,9 +483,12 @@ var PluginLoader = class {
|
|
|
483
483
|
throw new LoaderError(`failed to import \`${tuple.packageName}\` for capability \`${key}\`: ${err instanceof Error ? err.message : String(err)}`);
|
|
484
484
|
});
|
|
485
485
|
if (isPluginModule(mod)) {
|
|
486
|
+
const effectiveToken = this.context.cliTokens?.[tuple.provider] ?? this.context.cliToken;
|
|
486
487
|
const factory = mod.createPlugin({
|
|
487
488
|
...this.projectDefaults(),
|
|
488
489
|
...this.context,
|
|
490
|
+
...effectiveToken !== void 0 ? { cliToken: effectiveToken } : {},
|
|
491
|
+
cliTokens: void 0,
|
|
489
492
|
...tuple.options
|
|
490
493
|
}).capabilities[key];
|
|
491
494
|
if (typeof factory !== "function") throw new LoaderError(`\`${tuple.packageName}\` does not implement the \`${key}\` capability`);
|
|
@@ -913,7 +916,7 @@ var lint_default$1 = "name: Lint\n\non: # yamllint disable-line rule:truthy\n w
|
|
|
913
916
|
var release_default$1 = "name: Release\n\n# Semantic-release with OIDC Trusted Publishing — no NPM_TOKEN required.\n# The calling repo must have a .releaserc.json that configures branches,\n# plugins, and any publish options. npm@11+ is installed to support OIDC.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n run-build:\n description: Run `pnpm build` before releasing\n type: boolean\n required: false\n default: true\n secrets:\n SYNC_TOKEN:\n description: >\n Optional PAT with Contents write access. Required when the default\n branch is protected by a ruleset — github.token cannot push through\n rulesets, but a PAT with admin bypass can. Falls back to github.token.\n required: false\n\njobs:\n release:\n name: Semantic release\n permissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n # Do not cancel in-progress releases — a partial release is worse than a slow one.\n concurrency:\n group: release-${{ github.ref }}\n cancel-in-progress: false\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n # Use SYNC_TOKEN when available — git push (tags, release commits)\n # uses the checkout credential, not GITHUB_TOKEN env var. The\n # built-in github.token cannot push through branch protection rulesets.\n token: ${{ secrets.SYNC_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Configure git identity\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n git config --global user.name \"$GIT_NAME\"\n git config --global user.email \"$GIT_EMAIL\"\n git config --global format.signoff true\n env:\n GH_TOKEN: ${{ secrets.SYNC_TOKEN || github.token }}\n\n - run: npm install -g npm@11 sigstore\n name: Upgrade npm for OIDC support\n # sigstore is required by libnpmpublish/provenance.js at module parse\n # time — before any config takes effect. Some npm 11.x builds stopped\n # bundling it; installing it globally into the same prefix ensures it\n # resolves regardless of npm version. (Discovered 2026-07-09.)\n\n - run: pnpm build\n name: Build\n if: ${{ inputs.run-build == true }}\n\n - run: npx semantic-release\n name: Release\n env:\n # Prefer SYNC_TOKEN (a PAT with Contents write) when available —\n # the built-in github.token cannot push to protected default branches\n # because rulesets block non-PAT pushes. Falls back to github.token\n # for repos without branch protection.\n GITHUB_TOKEN: ${{ secrets.SYNC_TOKEN || github.token }}\n NPM_CONFIG_PROVENANCE: true\n";
|
|
914
917
|
//#endregion
|
|
915
918
|
//#region src/templates/workflows/review.yml
|
|
916
|
-
var review_default$1 = "name: Review\n\n# ReviewDog is the annotation layer — posts inline PR diff annotations.\n# Runs on pull_request only: inline annotations require PR context,\n# and branch protection ensures all changes go through PRs anyway.\n# super-linter (lint.yml) is the CI gate covering push + PR events.\n# Gitleaks and YAML are intentionally duplicated: super-linter gates\n# merges; ReviewDog surfaces exact line annotations in the PR diff.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n\nconcurrency:\n group: review-${{ github.workflow }}-${{ github.ref }}\n cancel-in-progress: true\n\njobs:\n reviewdog:\n name: Review PRs\n runs-on: ubuntu-latest\n timeout-minutes: 20\n permissions:\n contents: read\n checks: write\n pull-requests: write\n\n steps:\n - name: Checkout repository\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n\n - name: Setup\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n uses: theholocron/.github/.github/actions/setup@main\n\n - name: Install ReviewDog\n uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1\n with:\n reviewdog_version: latest\n\n # Detect which tools are relevant for this repo, excluding node_modules.\n # hashFiles('**/*') recurses into node_modules/.pnpm and produces false\n # positives for repos that don't own those file types.\n # -print -quit stops find after the first match without a pipe, avoiding\n # the SIGPIPE/pipefail exit-141 that find|head-1 triggers under\n # GitHub Actions' default bash --noprofile --norc -e -o pipefail mode.\n - name: Detect project features\n id: detect\n shell: bash\n run: |\n has() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n has_ext() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n { { has 'eslint.config.js' || has 'eslint.config.mjs' || has 'eslint.config.cjs' || \\\n has 'eslint.config.ts' || has '.eslintrc' || has '.eslintrc.js' || \\\n has '.eslintrc.cjs' || has '.eslintrc.json' || has '.eslintrc.yaml' || \\\n has '.eslintrc.yml'; } && grep -qF '\"eslint\":' package.json 2>/dev/null; } && echo \"eslint=true\" >> \"$GITHUB_OUTPUT\" || echo \"eslint=false\" >> \"$GITHUB_OUTPUT\"\n { has 'tsconfig.json' && grep -qF '\"typescript\":' package.json 2>/dev/null; } && echo \"tsconfig=true\" >> \"$GITHUB_OUTPUT\" || echo \"tsconfig=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.sh' && echo \"shell=true\" >> \"$GITHUB_OUTPUT\" || echo \"shell=false\" >> \"$GITHUB_OUTPUT\"\n has 'Dockerfile' || has_ext '*.Dockerfile' || has 'Containerfile' && \\\n echo \"docker=true\" >> \"$GITHUB_OUTPUT\" || echo \"docker=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '.env*' && echo \"dotenv=true\" >> \"$GITHUB_OUTPUT\" || echo \"dotenv=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.md' && echo \"markdown=true\" >> \"$GITHUB_OUTPUT\" || echo \"markdown=false\" >> \"$GITHUB_OUTPUT\"\n\n #\n # Always applicable\n #\n\n - name: Gitleaks (secrets)\n uses: reviewdog/action-gitleaks@2b7b5685e3e3eecddab5d30cfa04f18123031421 # v1\n with:\n reporter: github-pr-check\n gitleaks_flags: --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}\n\n - name: YamlLint\n if: ${{ hashFiles('yamllint.config.yml') != '' }}\n uses: reviewdog/action-yamllint@b5f7217d8c815ae374d1d55840d5e569d82f01f0 # v1\n with:\n reporter: github-pr-check\n yamllint_flags: -c ${{ github.workspace }}/yamllint.config.yml ${{ github.workspace }}\n\n - name: ActionLint (GitHub Actions)\n if: ${{ hashFiles('.github/workflows/*.yml', '.github/workflows/*.yaml') != '' }}\n uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1\n with:\n reporter: github-pr-check\n\n #\n # TypeScript / JavaScript\n #\n\n - name: ESLint\n if: steps.detect.outputs.eslint == 'true'\n uses: reviewdog/action-eslint@556a3fdaf8b4201d4d74d406013386aa4f7dab96 # v1.34.0\n with:\n reporter: github-pr-check\n eslint_flags: .\n\n - name: TypeScript\n if: steps.detect.outputs.tsconfig == 'true'\n uses: EPMatt/reviewdog-action-tsc@63d923a3c5b4497671940b8874f58a404e2351b5 # v1\n with:\n reporter: github-pr-check\n\n #\n # Shell\n #\n\n - name: ShellCheck\n if: steps.detect.outputs.shell == 'true'\n uses: reviewdog/action-shellcheck@4c07458293ac342d477251099501a718ae5ef86e # v1\n with:\n reporter: github-pr-check\n fail_level: none\n\n #\n # Docker\n #\n\n - name: Hadolint\n if: steps.detect.outputs.docker == 'true'\n uses: reviewdog/action-hadolint@1b2cfa6ba72072ad35158d7ff3aa49bbdc03506d # v1\n with:\n reporter: github-pr-check\n fail_level: none\n\n #\n # Environment files\n #\n\n - name: dotenv-linter\n if: steps.detect.outputs.dotenv == 'true'\n uses: dotenv-linter/action-dotenv-linter@afde61cfda2ecffe7bea35837b6f20b956c88689 # v3.0.0\n with:\n reporter: github-code-suggestions\n\n #\n # Documentation\n #\n\n - name: Alex (inclusive language)\n if: steps.detect.outputs.markdown == 'true'\n uses: reviewdog/action-alex@347481655add010a2ae302df34b57c9bcfa0d6e4 # v1\n with:\n reporter: github-pr-check\n";
|
|
919
|
+
var review_default$1 = "name: Review\n\n# ReviewDog is the annotation layer — posts inline PR diff annotations.\n# Runs on pull_request only: inline annotations require PR context,\n# and branch protection ensures all changes go through PRs anyway.\n# super-linter (lint.yml) is the CI gate covering push + PR events.\n# Gitleaks and YAML are intentionally duplicated: super-linter gates\n# merges; ReviewDog surfaces exact line annotations in the PR diff.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n\nconcurrency:\n group: review-${{ github.workflow }}-${{ github.ref }}\n cancel-in-progress: true\n\njobs:\n reviewdog:\n name: Review PRs\n runs-on: ubuntu-latest\n timeout-minutes: 20\n permissions:\n contents: read\n checks: write\n pull-requests: write\n\n steps:\n - name: Checkout repository\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n\n - name: Setup\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n uses: theholocron/.github/.github/actions/setup@main\n\n - name: Install ReviewDog\n uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1\n with:\n reviewdog_version: latest\n\n # Detect which tools are relevant for this repo, excluding node_modules.\n # hashFiles('**/*') recurses into node_modules/.pnpm and produces false\n # positives for repos that don't own those file types.\n # -print -quit stops find after the first match without a pipe, avoiding\n # the SIGPIPE/pipefail exit-141 that find|head-1 triggers under\n # GitHub Actions' default bash --noprofile --norc -e -o pipefail mode.\n - name: Detect project features\n id: detect\n shell: bash\n run: |\n has() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n has_ext() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n { { has 'eslint.config.js' || has 'eslint.config.mjs' || has 'eslint.config.cjs' || \\\n has 'eslint.config.ts' || has '.eslintrc' || has '.eslintrc.js' || \\\n has '.eslintrc.cjs' || has '.eslintrc.json' || has '.eslintrc.yaml' || \\\n has '.eslintrc.yml'; } && grep -qF '\"eslint\":' package.json 2>/dev/null; } && echo \"eslint=true\" >> \"$GITHUB_OUTPUT\" || echo \"eslint=false\" >> \"$GITHUB_OUTPUT\"\n { has 'tsconfig.json' && grep -qF '\"typescript\":' package.json 2>/dev/null; } && echo \"tsconfig=true\" >> \"$GITHUB_OUTPUT\" || echo \"tsconfig=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.sh' && echo \"shell=true\" >> \"$GITHUB_OUTPUT\" || echo \"shell=false\" >> \"$GITHUB_OUTPUT\"\n has 'Dockerfile' || has_ext '*.Dockerfile' || has 'Containerfile' && \\\n echo \"docker=true\" >> \"$GITHUB_OUTPUT\" || echo \"docker=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '.env*' && echo \"dotenv=true\" >> \"$GITHUB_OUTPUT\" || echo \"dotenv=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.md' && echo \"markdown=true\" >> \"$GITHUB_OUTPUT\" || echo \"markdown=false\" >> \"$GITHUB_OUTPUT\"\n\n #\n # Always applicable\n #\n\n - name: Gitleaks (secrets)\n uses: reviewdog/action-gitleaks@2b7b5685e3e3eecddab5d30cfa04f18123031421 # v1\n with:\n reporter: github-pr-check\n tool_name: \"Review / gitleaks\"\n gitleaks_flags: --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}\n\n - name: YamlLint\n if: ${{ hashFiles('yamllint.config.yml') != '' }}\n uses: reviewdog/action-yamllint@b5f7217d8c815ae374d1d55840d5e569d82f01f0 # v1\n with:\n reporter: github-pr-check\n tool_name: \"Review / yamllint\"\n yamllint_flags: -c ${{ github.workspace }}/yamllint.config.yml ${{ github.workspace }}\n\n - name: ActionLint (GitHub Actions)\n if: ${{ hashFiles('.github/workflows/*.yml', '.github/workflows/*.yaml') != '' }}\n uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1\n with:\n reporter: github-pr-check\n tool_name: \"Review / actionlint\"\n\n #\n # TypeScript / JavaScript\n #\n\n - name: ESLint\n if: steps.detect.outputs.eslint == 'true'\n uses: reviewdog/action-eslint@556a3fdaf8b4201d4d74d406013386aa4f7dab96 # v1.34.0\n with:\n reporter: github-pr-check\n tool_name: \"Review / eslint\"\n eslint_flags: .\n\n - name: TypeScript\n if: steps.detect.outputs.tsconfig == 'true'\n uses: EPMatt/reviewdog-action-tsc@63d923a3c5b4497671940b8874f58a404e2351b5 # v1\n with:\n reporter: github-pr-check\n tool_name: \"Review / tsc\"\n\n #\n # Shell\n #\n\n - name: ShellCheck\n if: steps.detect.outputs.shell == 'true'\n uses: reviewdog/action-shellcheck@4c07458293ac342d477251099501a718ae5ef86e # v1\n with:\n reporter: github-pr-check\n tool_name: \"Review / shellcheck\"\n fail_level: none\n\n #\n # Docker\n #\n\n - name: Hadolint\n if: steps.detect.outputs.docker == 'true'\n uses: reviewdog/action-hadolint@1b2cfa6ba72072ad35158d7ff3aa49bbdc03506d # v1\n with:\n reporter: github-pr-check\n tool_name: \"Review / hadolint\"\n fail_level: none\n\n #\n # Environment files\n #\n\n - name: dotenv-linter\n if: steps.detect.outputs.dotenv == 'true'\n uses: dotenv-linter/action-dotenv-linter@afde61cfda2ecffe7bea35837b6f20b956c88689 # v3.0.0\n with:\n reporter: github-code-suggestions\n\n #\n # Documentation\n #\n\n - name: Alex (inclusive language)\n if: steps.detect.outputs.markdown == 'true'\n uses: reviewdog/action-alex@347481655add010a2ae302df34b57c9bcfa0d6e4 # v1\n with:\n reporter: github-pr-check\n tool_name: \"Review / alex\"\n";
|
|
917
920
|
//#endregion
|
|
918
921
|
//#region src/templates/workflows/stale.yml
|
|
919
922
|
var stale_default$1 = "name: Stale\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n days-before-stale:\n description: Days of inactivity before an issue is marked stale\n type: number\n required: false\n default: 30\n days-before-close:\n description: Days of inactivity after stale label before closing\n type: number\n required: false\n default: 5\n\njobs:\n stale:\n name: Mark stale issues and pull requests\n permissions:\n contents: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0\n name: Run Stale\n with:\n close-issue-message: >\n This issue was closed because it has been stalled for\n ${{ inputs.days-before-close }} days with no activity.\n days-before-close: ${{ inputs.days-before-close }}\n days-before-stale: ${{ inputs.days-before-stale }}\n exempt-all-pr-milestones: true\n stale-issue-label: wontfix\n stale-issue-message: >\n This issue is stale because it has been open ${{ inputs.days-before-stale }}\n days with no activity. Remove the stale label or comment, or this will be\n closed in ${{ inputs.days-before-close }} days.\n stale-pr-label: wontfix\n stale-pr-message: >\n This PR is stale because it has been open ${{ inputs.days-before-stale }}\n days with no activity. Remove the stale label or comment, or this will be\n closed in ${{ inputs.days-before-close }} days.\n";
|
|
@@ -1430,7 +1433,7 @@ async function runNpmPublishInitial(input = {}) {
|
|
|
1430
1433
|
const dryRun = input.dryRun ?? false;
|
|
1431
1434
|
const otp = input.otp;
|
|
1432
1435
|
const env = input.env ?? process.env;
|
|
1433
|
-
const exec = input.exec ?? defaultExec$
|
|
1436
|
+
const exec = input.exec ?? defaultExec$2;
|
|
1434
1437
|
const publishArgs = [
|
|
1435
1438
|
"-r",
|
|
1436
1439
|
"--filter=./packages/*",
|
|
@@ -1506,7 +1509,7 @@ function printNextSteps$1(print, env) {
|
|
|
1506
1509
|
print(" https://www.npmjs.com/settings/~/tokens");
|
|
1507
1510
|
}
|
|
1508
1511
|
}
|
|
1509
|
-
const defaultExec$
|
|
1512
|
+
const defaultExec$2 = async (cmd, args, opts) => {
|
|
1510
1513
|
const result = spawnSync(cmd, args, {
|
|
1511
1514
|
cwd: opts.cwd,
|
|
1512
1515
|
encoding: "utf8",
|
|
@@ -2485,7 +2488,7 @@ function runPluginCreate(input) {
|
|
|
2485
2488
|
filesWritten.push(resolvedPath);
|
|
2486
2489
|
}
|
|
2487
2490
|
if (!input.dryRun && !input.noVerify) {
|
|
2488
|
-
const execFn = input.exec ?? defaultExec;
|
|
2491
|
+
const execFn = input.exec ?? defaultExec$1;
|
|
2489
2492
|
const pkg = `@theholocron/holocron-plugin-${inputs.slug}`;
|
|
2490
2493
|
print("");
|
|
2491
2494
|
print(" Verifying scaffold…");
|
|
@@ -2572,7 +2575,7 @@ function defaultWrite(filepath, content) {
|
|
|
2572
2575
|
mkdirSync(path.dirname(filepath), { recursive: true });
|
|
2573
2576
|
writeFileSync(filepath, content, "utf8");
|
|
2574
2577
|
}
|
|
2575
|
-
function defaultExec(cmd, args, opts) {
|
|
2578
|
+
function defaultExec$1(cmd, args, opts) {
|
|
2576
2579
|
execFileSync(cmd, args, {
|
|
2577
2580
|
cwd: opts.cwd,
|
|
2578
2581
|
stdio: opts.stdio
|
|
@@ -3608,6 +3611,23 @@ function formatStep(step) {
|
|
|
3608
3611
|
}
|
|
3609
3612
|
//#endregion
|
|
3610
3613
|
//#region src/commands/skills.ts
|
|
3614
|
+
/**
|
|
3615
|
+
* `holocron skills` — install, remove, and update agent skills from @theholocron/skills.
|
|
3616
|
+
*
|
|
3617
|
+
* Unlike `holocron setup`, this command is purely local (no GitHub token
|
|
3618
|
+
* required). It reads `agent` and `skills` from the config and installs
|
|
3619
|
+
* the listed skills into `.agents/skills/<name>/` with a symlink at the
|
|
3620
|
+
* agent-specific path (e.g. `.claude/skills/<name>` for Claude Code).
|
|
3621
|
+
*
|
|
3622
|
+
* `holocron skills remove [name]` and `holocron skills update [name]` delegate
|
|
3623
|
+
* to the corresponding `npx skills` subcommands from the upstream skills CLI.
|
|
3624
|
+
*/
|
|
3625
|
+
const defaultExec = (cmd, args, opts) => {
|
|
3626
|
+
return { exitCode: spawnSync(cmd, args, {
|
|
3627
|
+
cwd: opts.cwd,
|
|
3628
|
+
stdio: "inherit"
|
|
3629
|
+
}).status ?? -1 };
|
|
3630
|
+
};
|
|
3611
3631
|
async function runSkillsInstall(input) {
|
|
3612
3632
|
const print = input.print ?? ((line) => console.log(line));
|
|
3613
3633
|
const config = input.loaded.resolved;
|
|
@@ -3631,6 +3651,36 @@ async function runSkillsInstall(input) {
|
|
|
3631
3651
|
print(` ✗ ${err instanceof Error ? err.message : String(err)}`);
|
|
3632
3652
|
}
|
|
3633
3653
|
}
|
|
3654
|
+
function runSkillsRemove(input) {
|
|
3655
|
+
const { dryRun, repoRoot } = input.context;
|
|
3656
|
+
const exec = input.exec ?? defaultExec;
|
|
3657
|
+
const args = [
|
|
3658
|
+
"skills",
|
|
3659
|
+
"remove",
|
|
3660
|
+
...input.names ?? []
|
|
3661
|
+
];
|
|
3662
|
+
if (dryRun) {
|
|
3663
|
+
console.log(`Would run: npx ${args.join(" ")}`);
|
|
3664
|
+
return { status: "dry-run" };
|
|
3665
|
+
}
|
|
3666
|
+
const { exitCode } = exec("npx", args, { cwd: repoRoot });
|
|
3667
|
+
return { status: exitCode === 0 ? "ok" : "fail" };
|
|
3668
|
+
}
|
|
3669
|
+
function runSkillsUpdate(input) {
|
|
3670
|
+
const { dryRun, repoRoot } = input.context;
|
|
3671
|
+
const exec = input.exec ?? defaultExec;
|
|
3672
|
+
const args = [
|
|
3673
|
+
"skills",
|
|
3674
|
+
"update",
|
|
3675
|
+
...input.name ? [input.name] : []
|
|
3676
|
+
];
|
|
3677
|
+
if (dryRun) {
|
|
3678
|
+
console.log(`Would run: npx ${args.join(" ")}`);
|
|
3679
|
+
return { status: "dry-run" };
|
|
3680
|
+
}
|
|
3681
|
+
const { exitCode } = exec("npx", args, { cwd: repoRoot });
|
|
3682
|
+
return { status: exitCode === 0 ? "ok" : "fail" };
|
|
3683
|
+
}
|
|
3634
3684
|
//#endregion
|
|
3635
3685
|
//#region src/commands/sync.ts
|
|
3636
3686
|
const SYNC_STEPS = [
|
|
@@ -4025,15 +4075,68 @@ async function fileExists(path) {
|
|
|
4025
4075
|
}
|
|
4026
4076
|
}
|
|
4027
4077
|
//#endregion
|
|
4078
|
+
//#region src/token-args.ts
|
|
4079
|
+
var TokenParseError = class extends Error {
|
|
4080
|
+
name = "TokenParseError";
|
|
4081
|
+
};
|
|
4082
|
+
/**
|
|
4083
|
+
* Converts raw --token CLI values into a typed result.
|
|
4084
|
+
*
|
|
4085
|
+
* Bare form: --token ghp_xxx → { cliToken: "ghp_xxx" }
|
|
4086
|
+
* Keyed form: --token github=ghp_xxx → { cliTokens: { github: "ghp_xxx" } }
|
|
4087
|
+
* Mixed: --token github=ghp_xxx --token v_yyy
|
|
4088
|
+
* → { cliToken: "v_yyy", cliTokens: { github: "ghp_xxx" } }
|
|
4089
|
+
*
|
|
4090
|
+
* Values may contain "=" (e.g. base64 strings) — only the first "=" is treated as a separator.
|
|
4091
|
+
*/
|
|
4092
|
+
function parseTokenArgs(tokens) {
|
|
4093
|
+
if (tokens.length === 0) return {};
|
|
4094
|
+
const cliTokens = {};
|
|
4095
|
+
const bare = [];
|
|
4096
|
+
for (const raw of tokens) {
|
|
4097
|
+
const eqIdx = raw.indexOf("=");
|
|
4098
|
+
if (eqIdx === -1) {
|
|
4099
|
+
bare.push(raw);
|
|
4100
|
+
continue;
|
|
4101
|
+
}
|
|
4102
|
+
const vendor = raw.slice(0, eqIdx);
|
|
4103
|
+
const value = raw.slice(eqIdx + 1);
|
|
4104
|
+
if (vendor.trim() === "") throw new TokenParseError(`invalid --token value "${raw}": vendor name must not be empty`);
|
|
4105
|
+
if (/\s/.test(vendor)) throw new TokenParseError(`invalid --token value "${raw}": vendor name must not contain whitespace`);
|
|
4106
|
+
if (value === "") throw new TokenParseError(`invalid --token value "${raw}": token value must not be empty`);
|
|
4107
|
+
cliTokens[vendor] = value;
|
|
4108
|
+
}
|
|
4109
|
+
if (bare.length > 1) throw new TokenParseError(`only one bare --token value is allowed; got ${bare.length.toString()} — use vendor=value form for multiple tokens`);
|
|
4110
|
+
const result = {};
|
|
4111
|
+
if (bare.length === 1) result.cliToken = bare[0];
|
|
4112
|
+
if (Object.keys(cliTokens).length > 0) result.cliTokens = cliTokens;
|
|
4113
|
+
return result;
|
|
4114
|
+
}
|
|
4115
|
+
//#endregion
|
|
4028
4116
|
//#region src/cli.ts
|
|
4029
4117
|
const { version: CLI_VERSION } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
4118
|
+
/** Parses --token values and returns the context spread, or null on parse error (exits with code 1). */
|
|
4119
|
+
function tokenContext(rawTokens) {
|
|
4120
|
+
if (!rawTokens?.length) return {};
|
|
4121
|
+
try {
|
|
4122
|
+
return parseTokenArgs(rawTokens);
|
|
4123
|
+
} catch (err) {
|
|
4124
|
+
if (err instanceof TokenParseError) {
|
|
4125
|
+
console.error(`--token: ${err.message}`);
|
|
4126
|
+
process.exitCode = 1;
|
|
4127
|
+
return null;
|
|
4128
|
+
}
|
|
4129
|
+
throw err;
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4030
4132
|
await yargs(hideBin(process.argv)).scriptName("holocron").usage("$0 <command> [options]").option("dry-run", {
|
|
4031
4133
|
type: "boolean",
|
|
4032
4134
|
default: false,
|
|
4033
4135
|
describe: "Print what would be mutated without calling capability mutators. Commands branch on this; read-only commands ignore it."
|
|
4034
4136
|
}).option("token", {
|
|
4035
4137
|
type: "string",
|
|
4036
|
-
|
|
4138
|
+
array: true,
|
|
4139
|
+
describe: "Vendor token override for plugins. Bare form: --token <value> (fallback for all plugins, single-plugin commands). Keyed form: --token vendor=value (targets a specific provider; repeat for each). Example: --token github=ghp_xxx --token vercel=v_yyy"
|
|
4037
4140
|
}).option("cwd", {
|
|
4038
4141
|
type: "string",
|
|
4039
4142
|
default: process.cwd(),
|
|
@@ -4044,41 +4147,64 @@ await yargs(hideBin(process.argv)).scriptName("holocron").usage("$0 <command> [o
|
|
|
4044
4147
|
type: "string",
|
|
4045
4148
|
describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
|
|
4046
4149
|
}), async (argv) => {
|
|
4150
|
+
const tokens = tokenContext(argv.token);
|
|
4151
|
+
if (!tokens) return;
|
|
4047
4152
|
if ((await runDoctor({
|
|
4048
4153
|
loaded: await loadConfig(argv.cwd),
|
|
4049
4154
|
context: {
|
|
4050
4155
|
repoRoot: argv.cwd,
|
|
4051
4156
|
dryRun: argv.dryRun,
|
|
4052
4157
|
...argv.repo ? { repo: argv.repo } : {},
|
|
4053
|
-
...
|
|
4158
|
+
...tokens
|
|
4054
4159
|
}
|
|
4055
4160
|
})).summary.fail > 0) process.exitCode = 1;
|
|
4056
4161
|
}).command("setup", "Apply infra setup actions across every configured capability", (y) => y.option("repo", {
|
|
4057
4162
|
type: "string",
|
|
4058
4163
|
describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
|
|
4059
4164
|
}), async (argv) => {
|
|
4165
|
+
const tokens = tokenContext(argv.token);
|
|
4166
|
+
if (!tokens) return;
|
|
4060
4167
|
if ((await runSetup({
|
|
4061
4168
|
loaded: await loadConfig(argv.cwd),
|
|
4062
4169
|
context: {
|
|
4063
4170
|
repoRoot: argv.cwd,
|
|
4064
4171
|
dryRun: argv.dryRun,
|
|
4065
4172
|
...argv.repo ? { repo: argv.repo } : {},
|
|
4066
|
-
...
|
|
4173
|
+
...tokens
|
|
4067
4174
|
}
|
|
4068
4175
|
})).summary.fail > 0) process.exitCode = 1;
|
|
4069
|
-
}).command("skills
|
|
4070
|
-
|
|
4071
|
-
choices: ["install"],
|
|
4072
|
-
describe: "install — copy skills from @theholocron/skills into .agents/ with agent symlinks"
|
|
4073
|
-
}), async (argv) => {
|
|
4074
|
-
if (argv.action === "install") await runSkillsInstall({
|
|
4176
|
+
}).command("skills", "Manage agent skills from the @theholocron/skills registry", (y) => y.command("install", "Copy skills from @theholocron/skills into .agents/ with agent symlinks", () => {}, async (argv) => {
|
|
4177
|
+
await runSkillsInstall({
|
|
4075
4178
|
loaded: await loadConfig(argv.cwd),
|
|
4076
4179
|
context: {
|
|
4077
4180
|
repoRoot: argv.cwd,
|
|
4078
4181
|
dryRun: argv.dryRun
|
|
4079
4182
|
}
|
|
4080
4183
|
});
|
|
4081
|
-
}).command("
|
|
4184
|
+
}).command("remove [names..]", "Remove installed skills via npx skills remove", (yy) => yy.positional("names", {
|
|
4185
|
+
type: "string",
|
|
4186
|
+
array: true,
|
|
4187
|
+
describe: "Skill name(s) to remove (omit to remove all installed skills)"
|
|
4188
|
+
}), (argv) => {
|
|
4189
|
+
if (runSkillsRemove({
|
|
4190
|
+
context: {
|
|
4191
|
+
repoRoot: argv.cwd,
|
|
4192
|
+
dryRun: argv.dryRun
|
|
4193
|
+
},
|
|
4194
|
+
...argv.names?.length ? { names: argv.names } : {}
|
|
4195
|
+
}).status === "fail") process.exitCode = 1;
|
|
4196
|
+
}).command("update [name]", "Update installed skills to their latest upstream versions via npx skills update", (yy) => yy.positional("name", {
|
|
4197
|
+
type: "string",
|
|
4198
|
+
describe: "Skill name to update (omit to update all installed skills)"
|
|
4199
|
+
}), (argv) => {
|
|
4200
|
+
if (runSkillsUpdate({
|
|
4201
|
+
context: {
|
|
4202
|
+
repoRoot: argv.cwd,
|
|
4203
|
+
dryRun: argv.dryRun
|
|
4204
|
+
},
|
|
4205
|
+
...argv.name ? { name: argv.name } : {}
|
|
4206
|
+
}).status === "fail") process.exitCode = 1;
|
|
4207
|
+
}).demandCommand(1, "Run `holocron skills --help` to see available skills subcommands."), () => {}).command("secret set <name> [value]", "Set a single secret via the configured `secrets` capability", (y) => y.positional("name", {
|
|
4082
4208
|
type: "string",
|
|
4083
4209
|
demandOption: true,
|
|
4084
4210
|
describe: "Secret name (e.g., NPM_TOKEN)"
|
|
@@ -4097,6 +4223,8 @@ await yargs(hideBin(process.argv)).scriptName("holocron").usage("$0 <command> [o
|
|
|
4097
4223
|
default: "repo",
|
|
4098
4224
|
describe: "Scope: \"repo\" (default), \"env=<name>\", or \"org=<name>\""
|
|
4099
4225
|
}), async (argv) => {
|
|
4226
|
+
const tokens = tokenContext(argv.token);
|
|
4227
|
+
if (!tokens) return;
|
|
4100
4228
|
const scopeArg = argv.scope;
|
|
4101
4229
|
const scope = parseScope(scopeArg);
|
|
4102
4230
|
if ((await runSecretSet({
|
|
@@ -4104,7 +4232,7 @@ await yargs(hideBin(process.argv)).scriptName("holocron").usage("$0 <command> [o
|
|
|
4104
4232
|
context: {
|
|
4105
4233
|
repoRoot: argv.cwd,
|
|
4106
4234
|
dryRun: argv.dryRun,
|
|
4107
|
-
...
|
|
4235
|
+
...tokens
|
|
4108
4236
|
},
|
|
4109
4237
|
name: argv.name,
|
|
4110
4238
|
...argv.value ? { value: argv.value } : {},
|
|
@@ -4124,12 +4252,14 @@ await yargs(hideBin(process.argv)).scriptName("holocron").usage("$0 <command> [o
|
|
|
4124
4252
|
default: ["production", "preview"],
|
|
4125
4253
|
describe: "Deployment targets to sync to. Defaults to production + preview."
|
|
4126
4254
|
}), async (argv) => {
|
|
4255
|
+
const tokens = tokenContext(argv.token);
|
|
4256
|
+
if (!tokens) return;
|
|
4127
4257
|
if ((await runSecretsSync({
|
|
4128
4258
|
loaded: await loadConfig(argv.cwd),
|
|
4129
4259
|
context: {
|
|
4130
4260
|
repoRoot: argv.cwd,
|
|
4131
4261
|
dryRun: argv.dryRun,
|
|
4132
|
-
...
|
|
4262
|
+
...tokens
|
|
4133
4263
|
},
|
|
4134
4264
|
environmentId: argv.environmentId,
|
|
4135
4265
|
...argv.projectId ? { projectId: argv.projectId } : {},
|
|
@@ -4148,12 +4278,14 @@ await yargs(hideBin(process.argv)).scriptName("holocron").usage("$0 <command> [o
|
|
|
4148
4278
|
choices: ["production", "staging"],
|
|
4149
4279
|
describe: "Named environment to deploy into. Omit for a branch preview."
|
|
4150
4280
|
}), async (argv) => {
|
|
4281
|
+
const tokens = tokenContext(argv.token);
|
|
4282
|
+
if (!tokens) return;
|
|
4151
4283
|
if ((await runDeploy({
|
|
4152
4284
|
loaded: await loadConfig(argv.cwd),
|
|
4153
4285
|
context: {
|
|
4154
4286
|
repoRoot: argv.cwd,
|
|
4155
4287
|
dryRun: argv.dryRun,
|
|
4156
|
-
...
|
|
4288
|
+
...tokens
|
|
4157
4289
|
},
|
|
4158
4290
|
projectId: argv.projectId,
|
|
4159
4291
|
branch: argv.branch,
|
|
@@ -4191,13 +4323,15 @@ await yargs(hideBin(process.argv)).scriptName("holocron").usage("$0 <command> [o
|
|
|
4191
4323
|
type: "string",
|
|
4192
4324
|
describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
|
|
4193
4325
|
}), async (argv) => {
|
|
4326
|
+
const tokens = tokenContext(argv.token);
|
|
4327
|
+
if (!tokens) return;
|
|
4194
4328
|
if ((await runSync({
|
|
4195
4329
|
loaded: await loadConfig(argv.cwd),
|
|
4196
4330
|
context: {
|
|
4197
4331
|
repoRoot: argv.cwd,
|
|
4198
4332
|
dryRun: argv.dryRun,
|
|
4199
4333
|
...argv.repo ? { repo: argv.repo } : {},
|
|
4200
|
-
...
|
|
4334
|
+
...tokens
|
|
4201
4335
|
},
|
|
4202
4336
|
...argv.steps && argv.steps.length > 0 ? { steps: argv.steps } : {}
|
|
4203
4337
|
})).summary.fail > 0) process.exitCode = 1;
|
|
@@ -4220,7 +4354,9 @@ await yargs(hideBin(process.argv)).scriptName("holocron").usage("$0 <command> [o
|
|
|
4220
4354
|
describe: "Write generated files to this local directory instead of pushing (for validation)"
|
|
4221
4355
|
}), async (argv) => {
|
|
4222
4356
|
const outputDir = argv["output-dir"];
|
|
4223
|
-
const
|
|
4357
|
+
const parsed = tokenContext(argv.token);
|
|
4358
|
+
if (!parsed) return;
|
|
4359
|
+
const token = outputDir ? "no-token-needed" : parsed.cliTokens?.["github"] ?? parsed.cliToken ?? process.env.GITHUB_TOKEN ?? process.env.HOLOCRON_GITHUB_TOKEN;
|
|
4224
4360
|
if (!token) {
|
|
4225
4361
|
console.error("sync-github: GitHub token required — pass --token or set GITHUB_TOKEN");
|
|
4226
4362
|
process.exitCode = 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.74",
|
|
4
4
|
"description": "The Holocron CLI — a pluggable, capability-based orchestrator for spinning up and operating software projects.",
|
|
5
5
|
"homepage": "https://github.com/theholocron/holocron/tree/main/packages/cli#readme",
|
|
6
6
|
"bugs": "https://github.com/theholocron/holocron/issues",
|