@theholocron/cli 3.1.1 → 3.3.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/dist/cli.mjs CHANGED
@@ -17,6 +17,7 @@ import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink,
17
17
  import { pathToFileURL } from "node:url";
18
18
  import { createGitHubClient } from "@theholocron/github-client";
19
19
  import { promisify } from "node:util";
20
+ import * as Sentry from "@sentry/node";
20
21
  //#region src/env.ts
21
22
  function createEnvLookup(source = process.env) {
22
23
  return {
@@ -3838,7 +3839,7 @@ var greetings_default = "name: Greetings\n\non: # yamllint disable-line rule:tru
3838
3839
  var lint_default = "name: Lint\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n prettier-config:\n type: string\n required: false\n default: prettier.config.js\n yaml-config:\n type: string\n required: false\n default: yamllint.config.yml\n enable-auto-commit:\n description: Auto-commit super-linter fixes via GPG-signed commit\n type: boolean\n required: false\n default: false\n secrets:\n SUPER_LINTER_GPG_PRIVATE_KEY:\n required: false\n SUPER_LINTER_GPG_PASSPHRASE:\n required: false\n\njobs:\n super-lint:\n name: Lint entire codebase\n permissions:\n contents: write\n statuses: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n env:\n GPG_KEY_SET: ${{ secrets.SUPER_LINTER_GPG_PRIVATE_KEY != '' }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n token: ${{ github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n\n - uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0\n name: Run Super Linter\n env:\n GITHUB_TOKEN: ${{ github.token }}\n DEFAULT_BRANCH: ${{ github.event.pull_request.base.ref || github.event.repository.default_branch }}\n ANNOTATE_ONLY: true\n DISABLE_COMMENTS: false\n IGNORE_GITIGNORED_FILES: true\n LINTER_RULES_PATH: /\n EDITORCONFIG_FILE_NAME: \".editorconfig-checker.json\"\n FIX_ENV: true\n FIX_GRAPHQL_PRETTIER: true\n FIX_HTML_PRETTIER: true\n FIX_JAVASCRIPT_PRETTIER: true\n FIX_JSX_PRETTIER: true\n FIX_MARKDOWN_PRETTIER: true\n FIX_TSX: true\n FIX_TYPESCRIPT_PRETTIER: true\n PRETTIER_CONFIG: ${{ inputs.prettier-config }}\n VALIDATE_DOCKERFILE: true\n VALIDATE_EDITORCONFIG: true\n VALIDATE_ENV: true\n VALIDATE_GIT_COMMITLINT: true\n VALIDATE_GIT_MERGE_CONFLICT_MARKERS: true\n VALIDATE_GITHUB_ACTIONS: true\n VALIDATE_GITLEAKS: true\n VALIDATE_GRAPHQL_PRETTIER: true\n VALIDATE_HTML_PRETTIER: true\n VALIDATE_JAVASCRIPT_PRETTIER: true\n VALIDATE_JSX_PRETTIER: true\n VALIDATE_MARKDOWN_PRETTIER: true\n VALIDATE_TSX: true\n VALIDATE_TYPESCRIPT_PRETTIER: true\n VALIDATE_YAML: true\n YAML_CONFIG_FILE: ${{ inputs.yaml-config }}\n\n - uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0\n name: Import GPG Key\n # Conditions mirror auto-commit exactly — no point importing GPG if the\n # commit step will be skipped (fork PR, default branch, or secret unset).\n if: >\n inputs.enable-auto-commit == true &&\n github.event.pull_request != null &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.ref_name != github.event.repository.default_branch &&\n env.GPG_KEY_SET == 'true'\n with:\n git_user_signingkey: true\n git_commit_gpgsign: true\n GPG_PRIVATE_KEY: ${{ secrets.SUPER_LINTER_GPG_PRIVATE_KEY }}\n PASSPHRASE: ${{ secrets.SUPER_LINTER_GPG_PASSPHRASE }}\n\n - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0\n name: Commit and push linting fixes\n if: >\n inputs.enable-auto-commit == true &&\n github.event.pull_request != null &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.ref_name != github.event.repository.default_branch &&\n env.GPG_KEY_SET == 'true'\n with:\n branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }}\n commit_message: \"chore: fix linting issues\\n\\nSigned-off-by: super-linter <super-linter@super-linter.dev>\"\n commit_options: \"--no-verify\"\n commit_user_name: super-linter\n commit_user_email: super-linter@super-linter.dev\n";
3839
3840
  //#endregion
3840
3841
  //#region src/templates/workflows/release.yml
3841
- var release_default = "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 HOLOCRON_RELEASE_TOKEN:\n description: >\n Fine-grained PAT (Contents + Issues + Pull requests: write) owned by\n an admin. Required when the default branch is protected by a ruleset —\n github.token cannot push through rulesets, but an admin PAT can.\n Takes priority over HOLOCRON_SYNC_TOKEN. Falls back to github.token.\n required: false\n HOLOCRON_SYNC_TOKEN:\n description: >\n Legacy alias for HOLOCRON_RELEASE_TOKEN — kept for backward compatibility.\n Prefer HOLOCRON_RELEASE_TOKEN for new repos.\n required: false\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to github.token.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when\n HOLOCRON_READ_TOKEN is not set.\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 HOLOCRON_RELEASE_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.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_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.HOLOCRON_READ_TOKEN || secrets.GH_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 HOLOCRON_RELEASE_TOKEN (fine-grained PAT, Contents+Issues+PRs write,\n # owned by an admin with ruleset bypass) so @semantic-release/git can\n # push the version-bump commit through branch protection. Falls back to\n # HOLOCRON_SYNC_TOKEN (legacy) then github.token for unprotected repos.\n GITHUB_TOKEN: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n NPM_CONFIG_PROVENANCE: true\n";
3842
+ var release_default = "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 sentry-project:\n description: >\n Sentry project slug for sourcemap upload and release creation after\n publishing. Omit to skip the Sentry release step entirely.\n type: string\n required: false\n default: \"\"\n secrets:\n HOLOCRON_RELEASE_TOKEN:\n description: >\n Fine-grained PAT (Contents + Issues + Pull requests: write) owned by\n an admin. Required when the default branch is protected by a ruleset —\n github.token cannot push through rulesets, but an admin PAT can.\n Takes priority over HOLOCRON_SYNC_TOKEN. Falls back to github.token.\n required: false\n HOLOCRON_SYNC_TOKEN:\n description: >\n Legacy alias for HOLOCRON_RELEASE_TOKEN — kept for backward compatibility.\n Prefer HOLOCRON_RELEASE_TOKEN for new repos.\n required: false\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to github.token.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when\n HOLOCRON_READ_TOKEN is not set.\n required: false\n SENTRY_AUTH_TOKEN:\n description: >\n Sentry auth token for sourcemap upload and release creation.\n Required when sentry-project is set. Use the org-level secret.\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 HOLOCRON_RELEASE_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.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_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.HOLOCRON_READ_TOKEN || secrets.GH_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 HOLOCRON_RELEASE_TOKEN (fine-grained PAT, Contents+Issues+PRs write,\n # owned by an admin with ruleset bypass) so @semantic-release/git can\n # push the version-bump commit through branch protection. Falls back to\n # HOLOCRON_SYNC_TOKEN (legacy) then github.token for unprotected repos.\n GITHUB_TOKEN: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n NPM_CONFIG_PROVENANCE: true\n\n - name: Get release version\n id: release_version\n if: ${{ inputs.sentry-project != '' }}\n run: |\n TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo \"\")\n if [ -n \"$TAG\" ]; then\n echo \"release=${{ inputs.sentry-project }}@${TAG#v}\" >> \"$GITHUB_OUTPUT\"\n fi\n\n - name: Create Sentry release\n if: ${{ inputs.sentry-project != '' && steps.release_version.outputs.release != '' }}\n uses: getsentry/action-release@v3\n env:\n SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n SENTRY_ORG: theholocron\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n with:\n environment: production\n version: ${{ steps.release_version.outputs.release }}\n sourcemaps: \"**/dist\"\n";
3842
3843
  //#endregion
3843
3844
  //#region src/templates/workflows/review.yml
3844
3845
  var review_default = "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 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-annotations\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-annotations\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-annotations\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-annotations\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-annotations\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-annotations\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-annotations\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-annotations\n tool_name: \"Review / alex\"\n";
@@ -4477,6 +4478,58 @@ async function fileExists(path) {
4477
4478
  }
4478
4479
  }
4479
4480
  //#endregion
4481
+ //#region src/telemetry.ts
4482
+ const DSN = "https://95cbb72ad5636c94e119a5405ee8f55f@o4508238154104832.ingest.us.sentry.io/4511810950791168";
4483
+ function isEnabled() {
4484
+ return !process.env.NO_HOLOCRON_TELEMETRY && true;
4485
+ }
4486
+ function init(version) {
4487
+ if (!isEnabled()) return;
4488
+ Sentry.init({
4489
+ dsn: DSN,
4490
+ release: `holocron@${version}`,
4491
+ environment: process.env.CI ? "ci" : "local",
4492
+ tracesSampleRate: 1,
4493
+ beforeSend: scrubError
4494
+ });
4495
+ Sentry.startSession();
4496
+ Sentry.setTag("os", process.platform);
4497
+ Sentry.setTag("node", process.version);
4498
+ Sentry.setTag("ci", String(Boolean(process.env.CI)));
4499
+ }
4500
+ function startCommand(name) {
4501
+ if (!isEnabled()) return () => {};
4502
+ Sentry.setTag("command", name);
4503
+ const span = Sentry.startInactiveSpan({
4504
+ name,
4505
+ op: "holocron.command",
4506
+ forceTransaction: true
4507
+ });
4508
+ return (ok) => {
4509
+ span.setStatus({ code: ok ? 1 : 2 });
4510
+ span.end();
4511
+ };
4512
+ }
4513
+ function captureException(err) {
4514
+ if (!isEnabled()) return;
4515
+ Sentry.captureException(err);
4516
+ }
4517
+ function endSession() {
4518
+ if (!isEnabled()) return;
4519
+ Sentry.endSession();
4520
+ }
4521
+ async function flush() {
4522
+ if (!isEnabled()) return;
4523
+ await Sentry.close(2e3);
4524
+ }
4525
+ const TOKEN_RE = /\b(ghp_|ghs_|glpat-|xoxb-|xoxp-|npm_|sk-|[A-Z][A-Z0-9_]{2,}_TOKEN[=\s])[^\s"]*/g;
4526
+ function redact(raw) {
4527
+ return raw.replace(TOKEN_RE, "[REDACTED]");
4528
+ }
4529
+ function scrubError(event, _hint) {
4530
+ return JSON.parse(redact(JSON.stringify(event)));
4531
+ }
4532
+ //#endregion
4480
4533
  //#region src/token-args.ts
4481
4534
  var TokenParseError = class extends Error {
4482
4535
  name = "TokenParseError";
@@ -4651,463 +4704,475 @@ function tokenContext(rawTokens) {
4651
4704
  throw err;
4652
4705
  }
4653
4706
  }
4707
+ init(CLI_VERSION);
4654
4708
  const updateCheckPromise = checkForUpdates(CLI_VERSION);
4655
- await yargs(hideBin(process.argv)).scriptName("").usage("holocron <command> [options]").option("dry-run", {
4656
- type: "boolean",
4657
- default: false,
4658
- describe: "Print what would be mutated without calling capability mutators. Commands branch on this; read-only commands ignore it."
4659
- }).option("token", {
4660
- type: "string",
4661
- array: true,
4662
- 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"
4663
- }).option("cwd", {
4664
- type: "string",
4665
- default: process.cwd(),
4666
- describe: "Directory to search for holocron.config.json"
4667
- }).command("version", "Print the CLI version", () => {}, () => {
4668
- console.log(`holocron ${CLI_VERSION}`);
4669
- }).command("clone", "Clone all repos in a GitHub org as siblings under a single directory", (y) => y.option("org", {
4670
- type: "string",
4671
- demandOption: true,
4672
- describe: "GitHub org to clone (e.g., theholocron)"
4673
- }).option("dir", {
4674
- type: "string",
4675
- describe: "Parent directory to clone into (default: ~/Code/<org>)"
4676
- }), async (argv) => {
4677
- const tokens = tokenContext(argv.token);
4678
- if (!tokens) return;
4679
- let token;
4680
- try {
4681
- token = resolveCloneToken({ cliToken: tokens.cliTokens?.["github"] ?? tokens.cliToken });
4682
- } catch (err) {
4683
- console.error(`clone: ${err instanceof AuthError ? err.message : String(err)}`);
4684
- process.exitCode = 1;
4685
- return;
4686
- }
4687
- if ((await runClone({
4688
- org: argv.org,
4689
- token,
4690
- dryRun: argv.dryRun,
4691
- ...argv.dir ? { dir: argv.dir } : {}
4692
- })).status === "fail") process.exitCode = 1;
4693
- }).command("doctor", "Load the config and run a smoke check against every provider", (y) => y.option("repo", {
4694
- type: "string",
4695
- describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
4696
- }), async (argv) => {
4697
- const tokens = tokenContext(argv.token);
4698
- if (!tokens) return;
4699
- if ((await runDoctor({
4700
- loaded: await loadConfig(argv.cwd),
4701
- context: {
4702
- repoRoot: argv.cwd,
4703
- dryRun: argv.dryRun,
4704
- ...argv.repo ? { repo: argv.repo } : {},
4705
- ...tokens
4709
+ let finishCommand = () => {};
4710
+ try {
4711
+ await yargs(hideBin(process.argv)).middleware((argv) => {
4712
+ finishCommand = startCommand(argv._.slice(0, 2).join(" ") || "unknown");
4713
+ }).scriptName("").usage("holocron <command> [options]").option("dry-run", {
4714
+ type: "boolean",
4715
+ default: false,
4716
+ describe: "Print what would be mutated without calling capability mutators. Commands branch on this; read-only commands ignore it."
4717
+ }).option("token", {
4718
+ type: "string",
4719
+ array: true,
4720
+ 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"
4721
+ }).option("cwd", {
4722
+ type: "string",
4723
+ default: process.cwd(),
4724
+ describe: "Directory to search for holocron.config.json"
4725
+ }).command("version", "Print the CLI version", () => {}, () => {
4726
+ console.log(`holocron ${CLI_VERSION}`);
4727
+ }).command("clone", "Clone all repos in a GitHub org as siblings under a single directory", (y) => y.option("org", {
4728
+ type: "string",
4729
+ demandOption: true,
4730
+ describe: "GitHub org to clone (e.g., theholocron)"
4731
+ }).option("dir", {
4732
+ type: "string",
4733
+ describe: "Parent directory to clone into (default: ~/Code/<org>)"
4734
+ }), async (argv) => {
4735
+ const tokens = tokenContext(argv.token);
4736
+ if (!tokens) return;
4737
+ let token;
4738
+ try {
4739
+ token = resolveCloneToken({ cliToken: tokens.cliTokens?.["github"] ?? tokens.cliToken });
4740
+ } catch (err) {
4741
+ console.error(`clone: ${err instanceof AuthError ? err.message : String(err)}`);
4742
+ process.exitCode = 1;
4743
+ return;
4706
4744
  }
4707
- })).summary.fail > 0) process.exitCode = 1;
4708
- }).command("setup", "Apply infra setup actions across every configured capability", (y) => y.option("repo", {
4709
- type: "string",
4710
- describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
4711
- }), async (argv) => {
4712
- const tokens = tokenContext(argv.token);
4713
- if (!tokens) return;
4714
- if ((await runSetup({
4715
- loaded: await loadConfig(argv.cwd),
4716
- context: {
4717
- repoRoot: argv.cwd,
4745
+ if ((await runClone({
4746
+ org: argv.org,
4747
+ token,
4718
4748
  dryRun: argv.dryRun,
4719
- ...argv.repo ? { repo: argv.repo } : {},
4720
- ...tokens
4721
- }
4722
- })).summary.fail > 0) process.exitCode = 1;
4723
- }).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) => {
4724
- await runSkillsInstall({
4725
- loaded: await loadConfig(argv.cwd),
4726
- context: {
4727
- repoRoot: argv.cwd,
4728
- dryRun: argv.dryRun
4729
- }
4730
- });
4731
- }).command("remove [names..]", "Remove installed skills via npx skills remove", (yy) => yy.positional("names", {
4732
- type: "string",
4733
- array: true,
4734
- describe: "Skill name(s) to remove (omit to remove all installed skills)"
4735
- }), (argv) => {
4736
- if (runSkillsRemove({
4737
- context: {
4738
- repoRoot: argv.cwd,
4739
- dryRun: argv.dryRun
4740
- },
4741
- ...argv.names?.length ? { names: argv.names } : {}
4742
- }).status === "fail") process.exitCode = 1;
4743
- }).command("update [name]", "Update installed skills to their latest upstream versions via npx skills update", (yy) => yy.positional("name", {
4744
- type: "string",
4745
- describe: "Skill name to update (omit to update all installed skills)"
4746
- }), (argv) => {
4747
- if (runSkillsUpdate({
4748
- context: {
4749
- repoRoot: argv.cwd,
4749
+ ...argv.dir ? { dir: argv.dir } : {}
4750
+ })).status === "fail") process.exitCode = 1;
4751
+ }).command("doctor", "Load the config and run a smoke check against every provider", (y) => y.option("repo", {
4752
+ type: "string",
4753
+ describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
4754
+ }), async (argv) => {
4755
+ const tokens = tokenContext(argv.token);
4756
+ if (!tokens) return;
4757
+ if ((await runDoctor({
4758
+ loaded: await loadConfig(argv.cwd),
4759
+ context: {
4760
+ repoRoot: argv.cwd,
4761
+ dryRun: argv.dryRun,
4762
+ ...argv.repo ? { repo: argv.repo } : {},
4763
+ ...tokens
4764
+ }
4765
+ })).summary.fail > 0) process.exitCode = 1;
4766
+ }).command("setup", "Apply infra setup actions across every configured capability", (y) => y.option("repo", {
4767
+ type: "string",
4768
+ describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
4769
+ }), async (argv) => {
4770
+ const tokens = tokenContext(argv.token);
4771
+ if (!tokens) return;
4772
+ if ((await runSetup({
4773
+ loaded: await loadConfig(argv.cwd),
4774
+ context: {
4775
+ repoRoot: argv.cwd,
4776
+ dryRun: argv.dryRun,
4777
+ ...argv.repo ? { repo: argv.repo } : {},
4778
+ ...tokens
4779
+ }
4780
+ })).summary.fail > 0) process.exitCode = 1;
4781
+ }).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) => {
4782
+ await runSkillsInstall({
4783
+ loaded: await loadConfig(argv.cwd),
4784
+ context: {
4785
+ repoRoot: argv.cwd,
4786
+ dryRun: argv.dryRun
4787
+ }
4788
+ });
4789
+ }).command("remove [names..]", "Remove installed skills via npx skills remove", (yy) => yy.positional("names", {
4790
+ type: "string",
4791
+ array: true,
4792
+ describe: "Skill name(s) to remove (omit to remove all installed skills)"
4793
+ }), (argv) => {
4794
+ if (runSkillsRemove({
4795
+ context: {
4796
+ repoRoot: argv.cwd,
4797
+ dryRun: argv.dryRun
4798
+ },
4799
+ ...argv.names?.length ? { names: argv.names } : {}
4800
+ }).status === "fail") process.exitCode = 1;
4801
+ }).command("update [name]", "Update installed skills to their latest upstream versions via npx skills update", (yy) => yy.positional("name", {
4802
+ type: "string",
4803
+ describe: "Skill name to update (omit to update all installed skills)"
4804
+ }), (argv) => {
4805
+ if (runSkillsUpdate({
4806
+ context: {
4807
+ repoRoot: argv.cwd,
4808
+ dryRun: argv.dryRun
4809
+ },
4810
+ ...argv.name ? { name: argv.name } : {}
4811
+ }).status === "fail") process.exitCode = 1;
4812
+ }).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", {
4813
+ type: "string",
4814
+ demandOption: true,
4815
+ describe: "Secret name (e.g., NPM_TOKEN)"
4816
+ }).positional("value", {
4817
+ type: "string",
4818
+ describe: "Secret value (positional). If omitted, sources from --from-stdin, --from-env, or env var matching <name>."
4819
+ }).option("from-stdin", {
4820
+ type: "boolean",
4821
+ default: false,
4822
+ describe: "Read the secret value from stdin"
4823
+ }).option("from-env", {
4824
+ type: "string",
4825
+ describe: "Read the secret value from the named env var (otherwise: env var matching <name>)"
4826
+ }).option("scope", {
4827
+ type: "string",
4828
+ default: "repo",
4829
+ describe: "Scope: \"repo\" (default), \"env=<name>\", or \"org=<name>\""
4830
+ }), async (argv) => {
4831
+ const tokens = tokenContext(argv.token);
4832
+ if (!tokens) return;
4833
+ const scopeArg = argv.scope;
4834
+ const scope = parseScope(scopeArg);
4835
+ if ((await runSecretSet({
4836
+ loaded: await loadConfig(argv.cwd),
4837
+ context: {
4838
+ repoRoot: argv.cwd,
4839
+ dryRun: argv.dryRun,
4840
+ ...tokens
4841
+ },
4842
+ name: argv.name,
4843
+ ...argv.value ? { value: argv.value } : {},
4844
+ ...argv.fromStdin ? { fromStdin: true } : {},
4845
+ ...argv.fromEnv ? { fromEnv: argv.fromEnv } : {},
4846
+ scope
4847
+ })).status === "fail") process.exitCode = 1;
4848
+ }).command("secrets sync <environmentId>", "Read a vault environment + fan KEY=VALUEs out to secrets + deployment env vars", (y) => y.positional("environmentId", {
4849
+ type: "string",
4850
+ demandOption: true,
4851
+ describe: "Vault environment id to read (1P Environment id, etc.)"
4852
+ }).option("project-id", {
4853
+ type: "string",
4854
+ describe: "Deployment project id (e.g., Vercel prj_*). Required when deployment is loaded."
4855
+ }).option("target", {
4856
+ type: "array",
4857
+ default: ["production", "preview"],
4858
+ describe: "Deployment targets to sync to. Defaults to production + preview."
4859
+ }), async (argv) => {
4860
+ const tokens = tokenContext(argv.token);
4861
+ if (!tokens) return;
4862
+ if ((await runSecretsSync({
4863
+ loaded: await loadConfig(argv.cwd),
4864
+ context: {
4865
+ repoRoot: argv.cwd,
4866
+ dryRun: argv.dryRun,
4867
+ ...tokens
4868
+ },
4869
+ environmentId: argv.environmentId,
4870
+ ...argv.projectId ? { projectId: argv.projectId } : {},
4871
+ targets: argv.target
4872
+ })).summary.fail > 0) process.exitCode = 1;
4873
+ }).command("deploy <branch>", "Trigger a deployment via the configured `deployment` capability", (y) => y.positional("branch", {
4874
+ type: "string",
4875
+ demandOption: true,
4876
+ describe: "Git branch to deploy"
4877
+ }).option("project-id", {
4878
+ type: "string",
4879
+ demandOption: true,
4880
+ describe: "Deployment project id (e.g., Vercel prj_*)"
4881
+ }).option("target", {
4882
+ type: "string",
4883
+ choices: ["production", "staging"],
4884
+ describe: "Named environment to deploy into. Omit for a branch preview."
4885
+ }), async (argv) => {
4886
+ const tokens = tokenContext(argv.token);
4887
+ if (!tokens) return;
4888
+ if ((await runDeploy({
4889
+ loaded: await loadConfig(argv.cwd),
4890
+ context: {
4891
+ repoRoot: argv.cwd,
4892
+ dryRun: argv.dryRun,
4893
+ ...tokens
4894
+ },
4895
+ projectId: argv.projectId,
4896
+ branch: argv.branch,
4897
+ ...argv.target ? { target: argv.target } : {}
4898
+ })).status === "fail") process.exitCode = 1;
4899
+ }).command("npm", "npm-related monorepo utilities", (y) => y.command("bump-versions <new-version>", "Bump all non-private package versions in lockstep (semantic-release prepareCmd)", (yy) => yy.positional("new-version", {
4900
+ type: "string",
4901
+ demandOption: true,
4902
+ describe: "Version to set (e.g., 4.2.0 or 2.0.0-alpha.1)"
4903
+ }), async (argv) => {
4904
+ if ((await runNpmBumpVersions({
4905
+ version: argv.newVersion,
4906
+ cwd: argv.cwd,
4750
4907
  dryRun: argv.dryRun
4751
- },
4752
- ...argv.name ? { name: argv.name } : {}
4753
- }).status === "fail") process.exitCode = 1;
4754
- }).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", {
4755
- type: "string",
4756
- demandOption: true,
4757
- describe: "Secret name (e.g., NPM_TOKEN)"
4758
- }).positional("value", {
4759
- type: "string",
4760
- describe: "Secret value (positional). If omitted, sources from --from-stdin, --from-env, or env var matching <name>."
4761
- }).option("from-stdin", {
4762
- type: "boolean",
4763
- default: false,
4764
- describe: "Read the secret value from stdin"
4765
- }).option("from-env", {
4766
- type: "string",
4767
- describe: "Read the secret value from the named env var (otherwise: env var matching <name>)"
4768
- }).option("scope", {
4769
- type: "string",
4770
- default: "repo",
4771
- describe: "Scope: \"repo\" (default), \"env=<name>\", or \"org=<name>\""
4772
- }), async (argv) => {
4773
- const tokens = tokenContext(argv.token);
4774
- if (!tokens) return;
4775
- const scopeArg = argv.scope;
4776
- const scope = parseScope(scopeArg);
4777
- if ((await runSecretSet({
4778
- loaded: await loadConfig(argv.cwd),
4779
- context: {
4780
- repoRoot: argv.cwd,
4781
- dryRun: argv.dryRun,
4782
- ...tokens
4783
- },
4784
- name: argv.name,
4785
- ...argv.value ? { value: argv.value } : {},
4786
- ...argv.fromStdin ? { fromStdin: true } : {},
4787
- ...argv.fromEnv ? { fromEnv: argv.fromEnv } : {},
4788
- scope
4789
- })).status === "fail") process.exitCode = 1;
4790
- }).command("secrets sync <environmentId>", "Read a vault environment + fan KEY=VALUEs out to secrets + deployment env vars", (y) => y.positional("environmentId", {
4791
- type: "string",
4792
- demandOption: true,
4793
- describe: "Vault environment id to read (1P Environment id, etc.)"
4794
- }).option("project-id", {
4795
- type: "string",
4796
- describe: "Deployment project id (e.g., Vercel prj_*). Required when deployment is loaded."
4797
- }).option("target", {
4798
- type: "array",
4799
- default: ["production", "preview"],
4800
- describe: "Deployment targets to sync to. Defaults to production + preview."
4801
- }), async (argv) => {
4802
- const tokens = tokenContext(argv.token);
4803
- if (!tokens) return;
4804
- if ((await runSecretsSync({
4805
- loaded: await loadConfig(argv.cwd),
4806
- context: {
4807
- repoRoot: argv.cwd,
4808
- dryRun: argv.dryRun,
4809
- ...tokens
4810
- },
4811
- environmentId: argv.environmentId,
4812
- ...argv.projectId ? { projectId: argv.projectId } : {},
4813
- targets: argv.target
4814
- })).summary.fail > 0) process.exitCode = 1;
4815
- }).command("deploy <branch>", "Trigger a deployment via the configured `deployment` capability", (y) => y.positional("branch", {
4816
- type: "string",
4817
- demandOption: true,
4818
- describe: "Git branch to deploy"
4819
- }).option("project-id", {
4820
- type: "string",
4821
- demandOption: true,
4822
- describe: "Deployment project id (e.g., Vercel prj_*)"
4823
- }).option("target", {
4824
- type: "string",
4825
- choices: ["production", "staging"],
4826
- describe: "Named environment to deploy into. Omit for a branch preview."
4827
- }), async (argv) => {
4828
- const tokens = tokenContext(argv.token);
4829
- if (!tokens) return;
4830
- if ((await runDeploy({
4831
- loaded: await loadConfig(argv.cwd),
4832
- context: {
4833
- repoRoot: argv.cwd,
4834
- dryRun: argv.dryRun,
4835
- ...tokens
4836
- },
4837
- projectId: argv.projectId,
4838
- branch: argv.branch,
4839
- ...argv.target ? { target: argv.target } : {}
4840
- })).status === "fail") process.exitCode = 1;
4841
- }).command("npm", "npm-related monorepo utilities", (y) => y.command("bump-versions <new-version>", "Bump all non-private package versions in lockstep (semantic-release prepareCmd)", (yy) => yy.positional("new-version", {
4842
- type: "string",
4843
- demandOption: true,
4844
- describe: "Version to set (e.g., 4.2.0 or 2.0.0-alpha.1)"
4845
- }), async (argv) => {
4846
- if ((await runNpmBumpVersions({
4847
- version: argv.newVersion,
4848
- cwd: argv.cwd,
4849
- dryRun: argv.dryRun
4850
- })).status === "fail") process.exitCode = 1;
4851
- }).command("publish-initial", "One-shot bootstrap publish for trusted-publishing-eligible packages", (yy) => yy.option("tag", {
4852
- type: "string",
4853
- default: "alpha",
4854
- describe: "npm distribution tag (defaults to alpha)"
4855
- }).option("otp", {
4856
- type: "string",
4857
- describe: "One-time password from your authenticator (required if npm needs 2FA for writes)"
4858
- }), async (argv) => {
4859
- if ((await runNpmPublishInitial({
4860
- cwd: argv.cwd,
4861
- tag: argv.tag,
4862
- dryRun: argv.dryRun,
4863
- ...argv.otp ? { otp: argv.otp } : {}
4864
- })).status === "fail") process.exitCode = 1;
4865
- }).demandCommand(1, "Run `holocron npm --help` to see available npm subcommands."), () => {}).command("sync [steps..]", "Sync state from config to the provider and local files (labels, properties, topics, keywords, description)", (y) => y.positional("steps", {
4866
- type: "string",
4867
- array: true,
4868
- describe: "Steps to run: labels, properties, topics, keywords, description (default: all)"
4869
- }).option("repo", {
4870
- type: "string",
4871
- describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
4872
- }), async (argv) => {
4873
- const tokens = tokenContext(argv.token);
4874
- if (!tokens) return;
4875
- if ((await runSync({
4876
- loaded: await loadConfig(argv.cwd),
4877
- context: {
4878
- repoRoot: argv.cwd,
4908
+ })).status === "fail") process.exitCode = 1;
4909
+ }).command("publish-initial", "One-shot bootstrap publish for trusted-publishing-eligible packages", (yy) => yy.option("tag", {
4910
+ type: "string",
4911
+ default: "alpha",
4912
+ describe: "npm distribution tag (defaults to alpha)"
4913
+ }).option("otp", {
4914
+ type: "string",
4915
+ describe: "One-time password from your authenticator (required if npm needs 2FA for writes)"
4916
+ }), async (argv) => {
4917
+ if ((await runNpmPublishInitial({
4918
+ cwd: argv.cwd,
4919
+ tag: argv.tag,
4879
4920
  dryRun: argv.dryRun,
4880
- ...argv.repo ? { repo: argv.repo } : {},
4881
- ...tokens
4882
- },
4883
- ...argv.steps && argv.steps.length > 0 ? { steps: argv.steps } : {}
4884
- })).summary.fail > 0) process.exitCode = 1;
4885
- }).command("sync-github", "Sync workflow templates and composite actions to theholocron/.github via the GitHub API", (y) => y.option("repo", {
4886
- type: "string",
4887
- default: "theholocron/.github",
4888
- describe: "Target org/repo (default: theholocron/.github)"
4889
- }).option("branch", {
4890
- type: "string",
4891
- describe: "Push to this branch instead of the default branch (enables PR-based workflow for protected repos)"
4892
- }).option("pr", {
4893
- type: "boolean",
4894
- default: false,
4895
- describe: "Open a PR after pushing to --branch (no-op without --branch)"
4896
- }).option("message", {
4897
- type: "string",
4898
- describe: "Commit message (default: chore: sync from theholocron/holocron)"
4899
- }).option("output-dir", {
4900
- type: "string",
4901
- describe: "Write generated files to this local directory instead of pushing (for validation)"
4902
- }), async (argv) => {
4903
- const outputDir = argv["output-dir"];
4904
- const parsed = tokenContext(argv.token);
4905
- if (!parsed) return;
4906
- let token;
4907
- if (outputDir) token = "no-token-needed";
4908
- else try {
4909
- token = resolveSyncToken({ cliToken: parsed.cliTokens?.["github"] ?? parsed.cliToken });
4910
- } catch (err) {
4911
- console.error(`sync-github: ${err instanceof AuthError ? err.message : String(err)}`);
4912
- process.exitCode = 1;
4913
- return;
4914
- }
4915
- if ((await runSyncGithub({
4916
- token,
4917
- repo: argv.repo,
4918
- dryRun: argv.dryRun,
4919
- ...argv.branch ? { branch: argv.branch } : {},
4920
- ...argv.pr ? { createPr: true } : {},
4921
- ...argv.message ? { message: argv.message } : {},
4922
- ...outputDir ? { outputDir } : {}
4923
- })).status === "fail") process.exitCode = 1;
4924
- }).command("config show", "Print the resolved holocron config", () => {}, async (argv) => {
4925
- const loaded = await loadConfig(argv.cwd);
4926
- console.log(JSON.stringify(loaded.resolved, null, 2));
4927
- }).command("new [type] [name]", "Scaffold a new repo from a GitHub template (e.g. cli, react, nextjs, node, monorepo, base)", (y) => y.positional("type", {
4928
- type: "string",
4929
- describe: "Template type — maps to theholocron/<type>-template (e.g. cli, react, nextjs, node, monorepo, base)"
4930
- }).positional("name", {
4931
- type: "string",
4932
- describe: "New repo name (kebab-case, e.g. my-tool)"
4933
- }).option("description", {
4934
- type: "string",
4935
- describe: "Short description — replaces <description> placeholders in the template"
4936
- }).option("org", {
4937
- type: "string",
4938
- default: "theholocron",
4939
- describe: "GitHub org that owns the template and will own the new repo"
4940
- }).option("verify", {
4941
- type: "boolean",
4942
- default: true,
4943
- describe: "Run pnpm install after bootstrapping (default true; --no-verify skips)"
4944
- }), async (argv) => {
4945
- try {
4946
- let type = argv.type;
4947
- let name = argv.name;
4948
- let description = argv.description;
4949
- if (!type || !name || description === void 0) {
4950
- const rl = createInterface({
4951
- input: stdin,
4952
- output: stdout
4953
- });
4954
- const ask = (question) => new Promise((resolve) => rl.question(` ${question} `, (answer) => resolve(answer.trim())));
4955
- try {
4956
- if (!type) {
4957
- console.log(" Known types: base, cli, monorepo, nextjs, node, react");
4958
- type = await ask("Template type:");
4959
- }
4960
- if (!name) name = await ask("Repo name (kebab-case):");
4961
- if (description === void 0) description = await ask("Short description (Enter to skip):");
4962
- } finally {
4963
- rl.close();
4964
- }
4965
- }
4966
- if (!type) {
4967
- console.error("new: template type is required");
4968
- process.exitCode = 1;
4969
- return;
4970
- }
4971
- if (!name) {
4972
- console.error("new: repo name is required");
4921
+ ...argv.otp ? { otp: argv.otp } : {}
4922
+ })).status === "fail") process.exitCode = 1;
4923
+ }).demandCommand(1, "Run `holocron npm --help` to see available npm subcommands."), () => {}).command("sync [steps..]", "Sync state from config to the provider and local files (labels, properties, topics, keywords, description)", (y) => y.positional("steps", {
4924
+ type: "string",
4925
+ array: true,
4926
+ describe: "Steps to run: labels, properties, topics, keywords, description (default: all)"
4927
+ }).option("repo", {
4928
+ type: "string",
4929
+ describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
4930
+ }), async (argv) => {
4931
+ const tokens = tokenContext(argv.token);
4932
+ if (!tokens) return;
4933
+ if ((await runSync({
4934
+ loaded: await loadConfig(argv.cwd),
4935
+ context: {
4936
+ repoRoot: argv.cwd,
4937
+ dryRun: argv.dryRun,
4938
+ ...argv.repo ? { repo: argv.repo } : {},
4939
+ ...tokens
4940
+ },
4941
+ ...argv.steps && argv.steps.length > 0 ? { steps: argv.steps } : {}
4942
+ })).summary.fail > 0) process.exitCode = 1;
4943
+ }).command("sync-github", "Sync workflow templates and composite actions to theholocron/.github via the GitHub API", (y) => y.option("repo", {
4944
+ type: "string",
4945
+ default: "theholocron/.github",
4946
+ describe: "Target org/repo (default: theholocron/.github)"
4947
+ }).option("branch", {
4948
+ type: "string",
4949
+ describe: "Push to this branch instead of the default branch (enables PR-based workflow for protected repos)"
4950
+ }).option("pr", {
4951
+ type: "boolean",
4952
+ default: false,
4953
+ describe: "Open a PR after pushing to --branch (no-op without --branch)"
4954
+ }).option("message", {
4955
+ type: "string",
4956
+ describe: "Commit message (default: chore: sync from theholocron/holocron)"
4957
+ }).option("output-dir", {
4958
+ type: "string",
4959
+ describe: "Write generated files to this local directory instead of pushing (for validation)"
4960
+ }), async (argv) => {
4961
+ const outputDir = argv["output-dir"];
4962
+ const parsed = tokenContext(argv.token);
4963
+ if (!parsed) return;
4964
+ let token;
4965
+ if (outputDir) token = "no-token-needed";
4966
+ else try {
4967
+ token = resolveSyncToken({ cliToken: parsed.cliTokens?.["github"] ?? parsed.cliToken });
4968
+ } catch (err) {
4969
+ console.error(`sync-github: ${err instanceof AuthError ? err.message : String(err)}`);
4973
4970
  process.exitCode = 1;
4974
4971
  return;
4975
4972
  }
4976
- if ((await runNew({
4977
- type,
4978
- name,
4979
- ...description ? { description } : {},
4980
- org: argv.org,
4973
+ if ((await runSyncGithub({
4974
+ token,
4975
+ repo: argv.repo,
4981
4976
  dryRun: argv.dryRun,
4982
- noVerify: !argv.verify,
4983
- cwd: argv.cwd
4977
+ ...argv.branch ? { branch: argv.branch } : {},
4978
+ ...argv.pr ? { createPr: true } : {},
4979
+ ...argv.message ? { message: argv.message } : {},
4980
+ ...outputDir ? { outputDir } : {}
4984
4981
  })).status === "fail") process.exitCode = 1;
4985
- } catch (err) {
4986
- if (err instanceof NewError) {
4987
- console.error(`new: ${err.message}`);
4988
- process.exitCode = 1;
4989
- return;
4982
+ }).command("config show", "Print the resolved holocron config", () => {}, async (argv) => {
4983
+ const loaded = await loadConfig(argv.cwd);
4984
+ console.log(JSON.stringify(loaded.resolved, null, 2));
4985
+ }).command("new [type] [name]", "Scaffold a new repo from a GitHub template (e.g. cli, react, nextjs, node, monorepo, base)", (y) => y.positional("type", {
4986
+ type: "string",
4987
+ describe: "Template type — maps to theholocron/<type>-template (e.g. cli, react, nextjs, node, monorepo, base)"
4988
+ }).positional("name", {
4989
+ type: "string",
4990
+ describe: "New repo name (kebab-case, e.g. my-tool)"
4991
+ }).option("description", {
4992
+ type: "string",
4993
+ describe: "Short description — replaces <description> placeholders in the template"
4994
+ }).option("org", {
4995
+ type: "string",
4996
+ default: "theholocron",
4997
+ describe: "GitHub org that owns the template and will own the new repo"
4998
+ }).option("verify", {
4999
+ type: "boolean",
5000
+ default: true,
5001
+ describe: "Run pnpm install after bootstrapping (default true; --no-verify skips)"
5002
+ }), async (argv) => {
5003
+ try {
5004
+ let type = argv.type;
5005
+ let name = argv.name;
5006
+ let description = argv.description;
5007
+ if (!type || !name || description === void 0) {
5008
+ const rl = createInterface({
5009
+ input: stdin,
5010
+ output: stdout
5011
+ });
5012
+ const ask = (question) => new Promise((resolve) => rl.question(` ${question} `, (answer) => resolve(answer.trim())));
5013
+ try {
5014
+ if (!type) {
5015
+ console.log(" Known types: base, cli, monorepo, nextjs, node, react");
5016
+ type = await ask("Template type:");
5017
+ }
5018
+ if (!name) name = await ask("Repo name (kebab-case):");
5019
+ if (description === void 0) description = await ask("Short description (Enter to skip):");
5020
+ } finally {
5021
+ rl.close();
5022
+ }
5023
+ }
5024
+ if (!type) {
5025
+ console.error("new: template type is required");
5026
+ process.exitCode = 1;
5027
+ return;
5028
+ }
5029
+ if (!name) {
5030
+ console.error("new: repo name is required");
5031
+ process.exitCode = 1;
5032
+ return;
5033
+ }
5034
+ if ((await runNew({
5035
+ type,
5036
+ name,
5037
+ ...description ? { description } : {},
5038
+ org: argv.org,
5039
+ dryRun: argv.dryRun,
5040
+ noVerify: !argv.verify,
5041
+ cwd: argv.cwd
5042
+ })).status === "fail") process.exitCode = 1;
5043
+ } catch (err) {
5044
+ if (err instanceof NewError) {
5045
+ console.error(`new: ${err.message}`);
5046
+ process.exitCode = 1;
5047
+ return;
5048
+ }
5049
+ throw err;
4990
5050
  }
4991
- throw err;
4992
- }
4993
- }).command("plugin create <slug> <vendor>", "Scaffold a new @theholocron/holocron-plugin-<slug> package", (y) => y.positional("slug", {
4994
- type: "string",
4995
- demandOption: true,
4996
- describe: "Package slug (kebab-case)"
4997
- }).positional("vendor", {
4998
- type: "string",
4999
- demandOption: true,
5000
- describe: "Vendor display name (PascalCase)"
5001
- }).option("capability", {
5002
- type: "string",
5003
- describe: "Capability key: source|ci|secrets|environments|issues|deployment|storage|auth|vault|dns|tooling|notifications|analytics|observability"
5004
- }).option("token-env", {
5005
- type: "string",
5006
- describe: "Holocron env var name (defaults to HOLOCRON_<VENDOR>_TOKEN)"
5007
- }).option("vendor-env", {
5008
- type: "string",
5009
- describe: "Vendor-native env var name"
5010
- }).option("base-url", {
5011
- type: "string",
5012
- describe: "REST base URL"
5013
- }).option("verify", {
5014
- type: "boolean",
5015
- default: true,
5016
- describe: "Run post-scaffold pnpm install + typecheck + lint + test (default true; --no-verify skips)"
5017
- }), async (argv) => {
5018
- try {
5019
- const capabilityKeys = Object.keys(CARDINALITY).join(", ");
5020
- const needsPrompt = !argv.capability || !argv.vendorEnv || !argv.baseUrl;
5021
- let capability;
5022
- let vendorEnv;
5023
- let baseUrl;
5024
- if (needsPrompt) {
5025
- const rl = createInterface({
5026
- input: stdin,
5027
- output: stdout
5028
- });
5029
- const ask = (question) => new Promise((resolve) => rl.question(` ${question} `, (answer) => resolve(answer.trim())));
5030
- try {
5031
- if (!argv.capability) {
5032
- console.log(` Available capabilities: ${capabilityKeys}`);
5033
- capability = await ask("Capability:");
5034
- } else capability = argv.capability;
5035
- vendorEnv = argv.vendorEnv ? argv.vendorEnv : await ask(`Vendor-native env var for the ${argv.vendor} token (e.g. MYVENDOR_API_KEY):`);
5036
- baseUrl = argv.baseUrl ? argv.baseUrl : await ask(`REST base URL for the ${argv.vendor} API (e.g. https://api.myvendor.com):`);
5037
- } finally {
5038
- rl.close();
5051
+ }).command("plugin create <slug> <vendor>", "Scaffold a new @theholocron/holocron-plugin-<slug> package", (y) => y.positional("slug", {
5052
+ type: "string",
5053
+ demandOption: true,
5054
+ describe: "Package slug (kebab-case)"
5055
+ }).positional("vendor", {
5056
+ type: "string",
5057
+ demandOption: true,
5058
+ describe: "Vendor display name (PascalCase)"
5059
+ }).option("capability", {
5060
+ type: "string",
5061
+ describe: "Capability key: source|ci|secrets|environments|issues|deployment|storage|auth|vault|dns|tooling|notifications|analytics|observability"
5062
+ }).option("token-env", {
5063
+ type: "string",
5064
+ describe: "Holocron env var name (defaults to HOLOCRON_<VENDOR>_TOKEN)"
5065
+ }).option("vendor-env", {
5066
+ type: "string",
5067
+ describe: "Vendor-native env var name"
5068
+ }).option("base-url", {
5069
+ type: "string",
5070
+ describe: "REST base URL"
5071
+ }).option("verify", {
5072
+ type: "boolean",
5073
+ default: true,
5074
+ describe: "Run post-scaffold pnpm install + typecheck + lint + test (default true; --no-verify skips)"
5075
+ }), async (argv) => {
5076
+ try {
5077
+ const capabilityKeys = Object.keys(CARDINALITY).join(", ");
5078
+ const needsPrompt = !argv.capability || !argv.vendorEnv || !argv.baseUrl;
5079
+ let capability;
5080
+ let vendorEnv;
5081
+ let baseUrl;
5082
+ if (needsPrompt) {
5083
+ const rl = createInterface({
5084
+ input: stdin,
5085
+ output: stdout
5086
+ });
5087
+ const ask = (question) => new Promise((resolve) => rl.question(` ${question} `, (answer) => resolve(answer.trim())));
5088
+ try {
5089
+ if (!argv.capability) {
5090
+ console.log(` Available capabilities: ${capabilityKeys}`);
5091
+ capability = await ask("Capability:");
5092
+ } else capability = argv.capability;
5093
+ vendorEnv = argv.vendorEnv ? argv.vendorEnv : await ask(`Vendor-native env var for the ${argv.vendor} token (e.g. MYVENDOR_API_KEY):`);
5094
+ baseUrl = argv.baseUrl ? argv.baseUrl : await ask(`REST base URL for the ${argv.vendor} API (e.g. https://api.myvendor.com):`);
5095
+ } finally {
5096
+ rl.close();
5097
+ }
5098
+ } else {
5099
+ capability = argv.capability;
5100
+ vendorEnv = argv.vendorEnv;
5101
+ baseUrl = argv.baseUrl;
5039
5102
  }
5040
- } else {
5041
- capability = argv.capability;
5042
- vendorEnv = argv.vendorEnv;
5043
- baseUrl = argv.baseUrl;
5103
+ if (runPluginCreate({
5104
+ slug: argv.slug,
5105
+ vendorName: argv.vendor,
5106
+ capability,
5107
+ vendorEnv,
5108
+ baseUrl,
5109
+ ...argv.tokenEnv ? { tokenEnv: argv.tokenEnv } : {},
5110
+ dryRun: argv.dryRun,
5111
+ noVerify: !argv.verify,
5112
+ cwd: argv.cwd
5113
+ }).status === "fail") process.exitCode = 1;
5114
+ } catch (err) {
5115
+ if (err instanceof PluginCreateError) {
5116
+ console.error(`plugin create: ${err.message}`);
5117
+ process.exitCode = 1;
5118
+ return;
5119
+ }
5120
+ throw err;
5044
5121
  }
5045
- if (runPluginCreate({
5046
- slug: argv.slug,
5047
- vendorName: argv.vendor,
5048
- capability,
5049
- vendorEnv,
5050
- baseUrl,
5051
- ...argv.tokenEnv ? { tokenEnv: argv.tokenEnv } : {},
5122
+ }).command("upgrade", "Upgrade toolchain version pins across the repo", (y) => y.command("node <to>", "Scan the repo and update every Node.js version pin to a new major", (yy) => yy.positional("to", {
5123
+ type: "number",
5124
+ demandOption: true,
5125
+ describe: "Target Node.js major version (e.g., 22)"
5126
+ }).option("from", {
5127
+ type: "number",
5128
+ describe: "Current major version to replace. Auto-detected from .nvmrc / engines.node when omitted."
5129
+ }), async (argv) => {
5130
+ let extra = [];
5131
+ try {
5132
+ const raw = readFileSync(join(argv.cwd, "holocron.config.json"), "utf8");
5133
+ const upgradeNode = JSON.parse(raw).upgrade?.node;
5134
+ if (Array.isArray(upgradeNode?.extra)) extra = upgradeNode.extra;
5135
+ } catch {}
5136
+ const report = await runUpgradeNode({
5137
+ to: argv.to,
5138
+ ...argv.from != null ? { from: argv.from } : {},
5139
+ cwd: argv.cwd,
5052
5140
  dryRun: argv.dryRun,
5053
- noVerify: !argv.verify,
5054
- cwd: argv.cwd
5055
- }).status === "fail") process.exitCode = 1;
5056
- } catch (err) {
5057
- if (err instanceof PluginCreateError) {
5058
- console.error(`plugin create: ${err.message}`);
5141
+ extra
5142
+ });
5143
+ if (report.status === "fail") {
5144
+ if (report.message) console.error(`upgrade node: ${report.message}`);
5059
5145
  process.exitCode = 1;
5060
- return;
5061
5146
  }
5062
- throw err;
5063
- }
5064
- }).command("upgrade", "Upgrade toolchain version pins across the repo", (y) => y.command("node <to>", "Scan the repo and update every Node.js version pin to a new major", (yy) => yy.positional("to", {
5065
- type: "number",
5066
- demandOption: true,
5067
- describe: "Target Node.js major version (e.g., 22)"
5068
- }).option("from", {
5069
- type: "number",
5070
- describe: "Current major version to replace. Auto-detected from .nvmrc / engines.node when omitted."
5071
- }), async (argv) => {
5072
- let extra = [];
5073
- try {
5074
- const raw = readFileSync(join(argv.cwd, "holocron.config.json"), "utf8");
5075
- const upgradeNode = JSON.parse(raw).upgrade?.node;
5076
- if (Array.isArray(upgradeNode?.extra)) extra = upgradeNode.extra;
5077
- } catch {}
5078
- const report = await runUpgradeNode({
5079
- to: argv.to,
5080
- ...argv.from != null ? { from: argv.from } : {},
5081
- cwd: argv.cwd,
5082
- dryRun: argv.dryRun,
5083
- extra
5084
- });
5085
- if (report.status === "fail") {
5086
- if (report.message) console.error(`upgrade node: ${report.message}`);
5087
- process.exitCode = 1;
5088
- }
5089
- }).demandCommand(1, "Run `holocron upgrade --help` to see available upgrade subcommands."), () => {}).command("auth <subcommand>", "Manage bootstrap credentials in the OS keyring", (y) => y.command("set <provider> [value]", "Verify + store a bootstrap token for a provider", (yy) => yy.positional("provider", {
5090
- type: "string",
5091
- demandOption: true
5092
- }).positional("value", { type: "string" }), async (argv) => {
5093
- if ((await runAuthSet({
5094
- provider: argv.provider,
5095
- ...argv.value ? { positional: argv.value } : {}
5096
- })).status === "fail") process.exitCode = 1;
5097
- }).command("unset <provider>", "Remove a stored bootstrap token", (yy) => yy.positional("provider", {
5098
- type: "string",
5099
- demandOption: true
5100
- }), (argv) => {
5101
- runAuthUnset({ provider: argv.provider });
5102
- }).command("check <provider>", "Re-verify a stored bootstrap token", (yy) => yy.positional("provider", {
5103
- type: "string",
5104
- demandOption: true
5105
- }), async (argv) => {
5106
- if ((await runAuthCheck({ provider: argv.provider })).status === "fail") process.exitCode = 1;
5107
- }).command("list", "List every provider with a stored bootstrap token", () => {}, async () => {
5108
- await runAuthList();
5109
- }).demandCommand(1, "Run `holocron auth --help` to see available auth subcommands."), () => {}).demandCommand(1, "Run `holocron --help` to see available commands.").strict().help().parse();
5147
+ }).demandCommand(1, "Run `holocron upgrade --help` to see available upgrade subcommands."), () => {}).command("auth <subcommand>", "Manage bootstrap credentials in the OS keyring", (y) => y.command("set <provider> [value]", "Verify + store a bootstrap token for a provider", (yy) => yy.positional("provider", {
5148
+ type: "string",
5149
+ demandOption: true
5150
+ }).positional("value", { type: "string" }), async (argv) => {
5151
+ if ((await runAuthSet({
5152
+ provider: argv.provider,
5153
+ ...argv.value ? { positional: argv.value } : {}
5154
+ })).status === "fail") process.exitCode = 1;
5155
+ }).command("unset <provider>", "Remove a stored bootstrap token", (yy) => yy.positional("provider", {
5156
+ type: "string",
5157
+ demandOption: true
5158
+ }), (argv) => {
5159
+ runAuthUnset({ provider: argv.provider });
5160
+ }).command("check <provider>", "Re-verify a stored bootstrap token", (yy) => yy.positional("provider", {
5161
+ type: "string",
5162
+ demandOption: true
5163
+ }), async (argv) => {
5164
+ if ((await runAuthCheck({ provider: argv.provider })).status === "fail") process.exitCode = 1;
5165
+ }).command("list", "List every provider with a stored bootstrap token", () => {}, async () => {
5166
+ await runAuthList();
5167
+ }).demandCommand(1, "Run `holocron auth --help` to see available auth subcommands."), () => {}).demandCommand(1, "Run `holocron --help` to see available commands.").strict().help().parse();
5168
+ } catch (err) {
5169
+ captureException(err);
5170
+ if (!process.exitCode) process.exitCode = 1;
5171
+ }
5172
+ finishCommand(!process.exitCode);
5173
+ endSession();
5110
5174
  (await updateCheckPromise)?.();
5175
+ await flush();
5111
5176
  /**
5112
5177
  * Parse `--scope` strings: `repo` | `env=NAME` | `org=NAME`.
5113
5178
  */
@@ -5125,3 +5190,5 @@ function parseScope(s) {
5125
5190
  }
5126
5191
  //#endregion
5127
5192
  export {};
5193
+
5194
+ //# sourceMappingURL=cli.mjs.map