@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.
Files changed (2) hide show
  1. package/dist/cli.mjs +156 -20
  2. 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$1;
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$1 = async (cmd, args, opts) => {
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
- describe: "Vendor token passed to plugins as `cliToken`, taking precedence over env vars. Unambiguous for single-plugin commands; for multi-plugin flows pass per-vendor env vars instead."
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
- ...argv.token ? { cliToken: argv.token } : {}
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
- ...argv.token ? { cliToken: argv.token } : {}
4173
+ ...tokens
4067
4174
  }
4068
4175
  })).summary.fail > 0) process.exitCode = 1;
4069
- }).command("skills <action>", "Manage agent skills from the @theholocron/skills registry", (y) => y.positional("action", {
4070
- type: "string",
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("secret set <name> [value]", "Set a single secret via the configured `secrets` capability", (y) => y.positional("name", {
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
- ...argv.token ? { cliToken: argv.token } : {}
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
- ...argv.token ? { cliToken: argv.token } : {}
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
- ...argv.token ? { cliToken: argv.token } : {}
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
- ...argv.token ? { cliToken: argv.token } : {}
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 token = outputDir ? "no-token-needed" : argv.token ?? process.env.GITHUB_TOKEN ?? process.env.HOLOCRON_GITHUB_TOKEN;
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.72",
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",