javi-forge 1.37.0 → 1.38.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.
Files changed (42) hide show
  1. package/README.md +5 -5
  2. package/assets/claude-hooks/javi-forge-skillguard-pre-tool-use.mjs +13 -11
  3. package/assets/claude-hooks/manifest.json +35 -1
  4. package/assets/hooks/manifest.json +6 -2
  5. package/assets/hooks/pre-push +206 -8
  6. package/ci-local/README.md +3 -6
  7. package/ci-local/ci-local.ps1 +36 -10
  8. package/ci-local/ci-local.sh +26 -9
  9. package/ci-local/install.ps1 +36 -14
  10. package/ci-local/install.sh +27 -10
  11. package/ci-local/lib/common.sh +3 -7
  12. package/ci-local/uninstall.ps1 +50 -0
  13. package/ci-local/uninstall.sh +38 -3
  14. package/dist/cli/bootstrap.d.ts +12 -0
  15. package/dist/cli/bootstrap.js +12 -0
  16. package/dist/cli/dispatch/simple-renderers.d.ts +8 -1
  17. package/dist/cli/dispatch/simple-renderers.js +11 -2
  18. package/dist/cli/main.d.ts +2 -0
  19. package/dist/cli/main.js +85 -0
  20. package/dist/commands/claude-hooks.d.ts +1 -0
  21. package/dist/commands/claude-hooks.js +12 -3
  22. package/dist/commands/codex-hooks.d.ts +1 -0
  23. package/dist/commands/codex-hooks.js +12 -3
  24. package/dist/commands/doctor.d.ts +16 -1
  25. package/dist/commands/doctor.js +54 -35
  26. package/dist/commands/init.d.ts +4 -1
  27. package/dist/commands/init.js +2 -2
  28. package/dist/index.js +4 -81
  29. package/dist/lib/claude-hook-manager.d.ts +11 -6
  30. package/dist/lib/claude-hook-manager.js +28 -21
  31. package/dist/lib/codex-hook-manager.d.ts +10 -4
  32. package/dist/lib/codex-hook-manager.js +44 -13
  33. package/dist/lib/platform-support.d.ts +18 -14
  34. package/dist/lib/platform-support.js +19 -17
  35. package/dist/lib/secure-fs-posix.d.ts +8 -13
  36. package/dist/lib/secure-fs-posix.js +11 -52
  37. package/dist/lib/secure-fs-transaction.d.ts +2 -3
  38. package/dist/types/index.d.ts +7 -2
  39. package/dist/ui/Doctor.d.ts +2 -0
  40. package/dist/ui/Doctor.js +13 -0
  41. package/lib/common.sh +3 -7
  42. package/package.json +7 -2
@@ -7,7 +7,7 @@
7
7
  # .\install.ps1
8
8
  #
9
9
  # Behaviour MUST match install.sh. Diff against the bash version on every
10
- # change. Test on real Windows + WSL + macOS pwsh before tagging a release.
10
+ # change. Test on supported Windows and WSL hosts before tagging a release.
11
11
  # =============================================================================
12
12
 
13
13
  #Requires -Version 7.2
@@ -15,19 +15,36 @@
15
15
  param()
16
16
 
17
17
  function Invoke-CiLocalMain {
18
- param([Parameter(Mandatory)][string]$Platform)
19
-
20
- if ($Platform -eq 'Darwin') {
21
- Write-Host 'macOS is deprecated and unsupported for new CI-Local install/startup. Pin a supported release or migrate. Existing installed guards are not removed; Darwin code removal is planned separately for 2.0.'
18
+ param(
19
+ [Parameter(Mandatory)][string]$Platform,
20
+ [ref]$ExitCode
21
+ )
22
+
23
+ if ($Platform -notin @('Linux', 'Windows')) {
24
+ Write-Host 'unsupported-platform: javi-forge supports Linux and Windows only.'
25
+ if ($PSBoundParameters.ContainsKey('ExitCode')) {
26
+ $ExitCode.Value = 1
27
+ return
28
+ }
22
29
  return 1
23
30
  }
24
31
 
25
- Invoke-CiLocalStartupBody -Platform $Platform
32
+ $bodyExitCode = 0
33
+ & ${function:Invoke-CiLocalStartupBody} -Platform $Platform -ExitCode ([ref]$bodyExitCode)
34
+ if ($PSBoundParameters.ContainsKey('ExitCode')) {
35
+ $ExitCode.Value = $bodyExitCode
36
+ } elseif ($bodyExitCode -ne 0) {
37
+ return $bodyExitCode
38
+ }
26
39
  }
27
40
 
28
41
  function Invoke-CiLocalStartupBody {
29
- param([string]$Platform)
42
+ param(
43
+ [string]$Platform,
44
+ [ref]$ExitCode
45
+ )
30
46
 
47
+ $ExitCode.Value = 0
31
48
  # Defense-in-depth: #Requires is parsed by some hosts AFTER the script body.
32
49
  # 7.2 is the minimum because FileSystemInfo.ResolveLinkTarget($true) is .NET 6+,
33
50
  # which shipped with pwsh 7.2. Earlier pwsh 7.x runs on .NET 5 and the method
@@ -159,9 +176,11 @@ try {
159
176
 
160
177
  $jfVersionOk = $true
161
178
  $jfVersionOutput = ''
179
+ $jfVersionExitCode = 1
162
180
  try {
163
181
  $jfVersionOutput = (& javi-forge --version 2>&1) -join "`n"
164
- if ($LASTEXITCODE -ne 0) { $jfVersionOk = $false }
182
+ $jfVersionExitCode = $LASTEXITCODE
183
+ if ($jfVersionExitCode -ne 0) { $jfVersionOk = $false }
165
184
  } catch {
166
185
  $jfVersionOk = $false
167
186
  $jfVersionOutput = $_.Exception.Message
@@ -177,7 +196,8 @@ try {
177
196
  Write-Host "ERROR: javi-forge found at $jfPath but '--version' failed" -ForegroundColor Red
178
197
  Write-Host "Output: $jfVersionOutput" -ForegroundColor Yellow
179
198
  Write-Host 'Reinstall: npm install -g javi-forge' -ForegroundColor Cyan
180
- exit 1
199
+ $ExitCode.Value = $jfVersionExitCode
200
+ return
181
201
  }
182
202
 
183
203
  # Reject UNC / remote paths outright — a javi-forge symlinked to
@@ -297,12 +317,13 @@ try {
297
317
  & git config core.hooksPath $hooksRel
298
318
  if ($LASTEXITCODE -ne 0) {
299
319
  Write-Host 'ERROR: failed to set core.hooksPath. Are you inside a git repo?' -ForegroundColor Red
300
- exit 1
320
+ $ExitCode.Value = $LASTEXITCODE
321
+ return
301
322
  }
302
323
  Write-Host "hooksPath = $hooksRel" -ForegroundColor Green
303
324
 
304
325
  # Make hooks executable. Different OS, different model:
305
- # - macOS / Linux / WSL: chmod 0755 — POSIX bits, what git expects.
326
+ # - Linux / WSL: chmod 0755 — POSIX bits, what git expects.
306
327
  # - Windows native NTFS: chmod is a no-op; lock the ACL via icacls so
307
328
  # only the current user can rewrite the hooks between commits.
308
329
  if (Test-CommandExists -Name 'chmod') {
@@ -382,9 +403,10 @@ try {
382
403
  }
383
404
 
384
405
  if ($MyInvocation.InvocationName -ne '.') {
385
- $platform = if ($IsMacOS) { 'Darwin' } else { 'Windows' }
386
- $exitCode = & ${function:Invoke-CiLocalMain} -Platform $platform
387
- if ($exitCode -is [int] -and $exitCode -ne 0) {
406
+ $platform = if ($IsWindows) { 'Windows' } elseif ($IsLinux) { 'Linux' } else { 'unsupported' }
407
+ $exitCode = 0
408
+ & ${function:Invoke-CiLocalMain} -Platform $platform -ExitCode ([ref]$exitCode)
409
+ if ($exitCode -ne 0) {
388
410
  exit $exitCode
389
411
  }
390
412
  }
@@ -3,22 +3,40 @@
3
3
  # CI-LOCAL: Installation Script
4
4
  # =============================================================================
5
5
 
6
- function ci_local_main() {
6
+
7
+ CI_LOCAL_ENTRYPOINT_SOURCE="${BASH_SOURCE[0]}"
8
+ if [[ "$CI_LOCAL_ENTRYPOINT_SOURCE" == /* ]]; then
9
+ CI_LOCAL_ENTRYPOINT_PATH="$CI_LOCAL_ENTRYPOINT_SOURCE"
10
+ else
11
+ CI_LOCAL_ENTRYPOINT_PATH="$PWD/$CI_LOCAL_ENTRYPOINT_SOURCE"
12
+ fi
13
+ CI_LOCAL_ENTRYPOINT_DIR="${CI_LOCAL_ENTRYPOINT_PATH%/*}"
14
+
15
+ function ci_local_normalize_platform() {
7
16
  local platform="${1:?platform required}"
8
17
 
9
- if [ "$platform" = "Darwin" ]; then
10
- printf '%s\n' 'macOS is deprecated and unsupported for new CI-Local install/startup. Pin a supported release or migrate. Existing installed guards are not removed; Darwin code removal is planned separately for 2.0.'
11
- return 1
12
- fi
18
+ case "$platform" in
19
+ MINGW*_NT-*|MSYS*|CYGWIN*) printf '%s\n' 'Windows' ;;
20
+ *) printf '%s\n' "$platform" ;;
21
+ esac
22
+ }
13
23
 
14
- ci_local_startup_body "$platform"
24
+ function ci_local_main() {
25
+ local platform
26
+ platform="$(ci_local_normalize_platform "${1:?platform required}")"
27
+ shift
28
+
29
+ case "$platform" in
30
+ Linux|Windows) ci_local_startup_body "$@" ;;
31
+ *) printf '%s\n' 'unsupported-platform: javi-forge supports Linux and Windows only.'; return 1 ;;
32
+ esac
15
33
  }
16
34
 
17
35
  function ci_local_startup_body() {
18
36
  set -e
19
37
 
20
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
21
- PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
38
+ SCRIPT_DIR="$CI_LOCAL_ENTRYPOINT_DIR"
39
+ PROJECT_DIR="${SCRIPT_DIR%/*}"
22
40
 
23
41
  # ─── Required tools (fail-closed) ──────────────────────────────────
24
42
  # realpath is load-bearing for the symlink-traversal checks below. If it's
@@ -27,7 +45,6 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
27
45
  if ! command -v realpath >/dev/null 2>&1; then
28
46
  printf 'ERROR: realpath is required for ci-local install (symlink checks).\n' >&2
29
47
  printf ' Linux/WSL: install coreutils\n' >&2
30
- printf ' macOS: brew install coreutils, then add gnubin to PATH\n' >&2
31
48
  exit 1
32
49
  fi
33
50
 
@@ -208,5 +225,5 @@ echo -e ""
208
225
  }
209
226
 
210
227
  if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
211
- ci_local_main "$(/usr/bin/uname -s)"
228
+ ci_local_main "$(/usr/bin/uname -s)" "$@"
212
229
  fi
@@ -39,16 +39,12 @@ log_info() { echo -e " ${CYAN}[INFO]${NC} $1"; }
39
39
  log_step() { echo -e "${YELLOW}$1${NC}"; }
40
40
 
41
41
  # =============================================================================
42
- # sed_inplace - Portable sed -i (works on both GNU and BSD/macOS sed)
42
+ # sed_inplace - GNU sed -i for supported Linux/WSL hosts
43
43
  # =============================================================================
44
44
  # Usage: sed_inplace "s/foo/bar/" file.txt
45
45
  # =============================================================================
46
46
  sed_inplace() {
47
- if sed --version 2>/dev/null | grep -q GNU; then
48
- sed -i "$@"
49
- else
50
- sed -i '' "$@"
51
- fi
47
+ sed -i "$@"
52
48
  }
53
49
 
54
50
  # =============================================================================
@@ -100,7 +96,7 @@ detect_stack() {
100
96
  STACK_TYPE="java-gradle"
101
97
  BUILD_TOOL="gradle"
102
98
 
103
- # Detect Java version from build files (compatible with macOS and Linux)
99
+ # Detect Java version from build files
104
100
  if [[ -f "$project_dir/build.gradle.kts" ]]; then
105
101
  JAVA_VERSION=$(grep -E 'languageVersion\s*=\s*JavaLanguageVersion\.of\(' "$project_dir/build.gradle.kts" 2>/dev/null | grep -o '[0-9]\+' | head -1 || echo "21")
106
102
  elif [[ -f "$project_dir/build.gradle" ]]; then
@@ -16,6 +16,42 @@ param(
16
16
  [switch]$Purge
17
17
  )
18
18
 
19
+
20
+ function Invoke-CiLocalMain {
21
+ param(
22
+ [Parameter(Mandatory)][string]$Platform,
23
+ [switch]$RestoreBackups,
24
+ [switch]$Purge,
25
+ [ref]$ExitCode
26
+ )
27
+
28
+ if ($Platform -notin @('Linux', 'Windows')) {
29
+ Write-Host 'unsupported-platform: javi-forge supports Linux and Windows only.'
30
+ if ($PSBoundParameters.ContainsKey('ExitCode')) {
31
+ $ExitCode.Value = 1
32
+ return
33
+ }
34
+ return 1
35
+ }
36
+
37
+ $bodyExitCode = 0
38
+ & ${function:Invoke-CiLocalStartupBody} -Platform $Platform -RestoreBackups:$RestoreBackups -Purge:$Purge -ExitCode ([ref]$bodyExitCode)
39
+ if ($PSBoundParameters.ContainsKey('ExitCode')) {
40
+ $ExitCode.Value = $bodyExitCode
41
+ } elseif ($bodyExitCode -ne 0) {
42
+ return $bodyExitCode
43
+ }
44
+ }
45
+
46
+ function Invoke-CiLocalStartupBody {
47
+ param(
48
+ [Parameter(Mandatory)][string]$Platform,
49
+ [switch]$RestoreBackups,
50
+ [switch]$Purge,
51
+ [ref]$ExitCode
52
+ )
53
+
54
+ $ExitCode.Value = 0
19
55
  Set-StrictMode -Version Latest
20
56
  $ErrorActionPreference = 'Stop'
21
57
 
@@ -32,6 +68,10 @@ try {
32
68
  $current = (& git config --get core.hooksPath 2>$null) -join ''
33
69
  if ($current) {
34
70
  & git config --unset core.hooksPath
71
+ if ($LASTEXITCODE -ne 0) {
72
+ $ExitCode.Value = $LASTEXITCODE
73
+ return
74
+ }
35
75
  Write-Host "Removed hooksPath = $current" -ForegroundColor Green
36
76
  } else {
37
77
  Write-Host 'No core.hooksPath was set; nothing to remove' -ForegroundColor Yellow
@@ -83,3 +123,13 @@ try {
83
123
  } finally {
84
124
  Pop-Location
85
125
  }
126
+ }
127
+
128
+ if ($MyInvocation.InvocationName -ne '.') {
129
+ $platform = if ($IsWindows) { 'Windows' } elseif ($IsLinux) { 'Linux' } else { 'unsupported' }
130
+ $exitCode = 0
131
+ & ${function:Invoke-CiLocalMain} -Platform $platform -RestoreBackups:$RestoreBackups -Purge:$Purge -ExitCode ([ref]$exitCode)
132
+ if ($exitCode -ne 0) {
133
+ exit $exitCode
134
+ }
135
+ }
@@ -13,10 +13,40 @@
13
13
  # ./uninstall.sh --purge # also rm -rf ci-local/ + lib/common.sh
14
14
  # =============================================================================
15
15
 
16
- set -e
17
16
 
18
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
19
- PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
17
+ CI_LOCAL_ENTRYPOINT_SOURCE="${BASH_SOURCE[0]}"
18
+ if [[ "$CI_LOCAL_ENTRYPOINT_SOURCE" == /* ]]; then
19
+ CI_LOCAL_ENTRYPOINT_PATH="$CI_LOCAL_ENTRYPOINT_SOURCE"
20
+ else
21
+ CI_LOCAL_ENTRYPOINT_PATH="$PWD/$CI_LOCAL_ENTRYPOINT_SOURCE"
22
+ fi
23
+ CI_LOCAL_ENTRYPOINT_DIR="${CI_LOCAL_ENTRYPOINT_PATH%/*}"
24
+
25
+ function ci_local_normalize_platform() {
26
+ local platform="${1:?platform required}"
27
+
28
+ case "$platform" in
29
+ MINGW*_NT-*|MSYS*|CYGWIN*) printf '%s\n' 'Windows' ;;
30
+ *) printf '%s\n' "$platform" ;;
31
+ esac
32
+ }
33
+
34
+ function ci_local_main() {
35
+ local platform
36
+ platform="$(ci_local_normalize_platform "${1:?platform required}")"
37
+ shift
38
+
39
+ case "$platform" in
40
+ Linux|Windows) ci_local_startup_body "$@" ;;
41
+ *) printf '%s\n' 'unsupported-platform: javi-forge supports Linux and Windows only.'; return 1 ;;
42
+ esac
43
+ }
44
+
45
+ function ci_local_startup_body() {
46
+ set -e
47
+
48
+ SCRIPT_DIR="$CI_LOCAL_ENTRYPOINT_DIR"
49
+ PROJECT_DIR="${SCRIPT_DIR%/*}"
20
50
 
21
51
  # Source colors if available; degrade gracefully if not.
22
52
  if [ -f "$SCRIPT_DIR/../lib/common.sh" ]; then
@@ -93,3 +123,8 @@ fi
93
123
  echo -e ""
94
124
  echo -e "${GREEN}Uninstall complete!${NC}"
95
125
  echo -e ""
126
+ }
127
+
128
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
129
+ ci_local_main "$(/usr/bin/uname -s)" "$@"
130
+ fi
@@ -0,0 +1,12 @@
1
+ export type BootstrapResult = {
2
+ state: "supported";
3
+ exitCode: 0;
4
+ } | {
5
+ state: "unsupported-platform";
6
+ exitCode: 1;
7
+ };
8
+ export interface SupportedCli {
9
+ runCli: () => Promise<void>;
10
+ }
11
+ export declare function bootstrapCli(platform: string, loadSupportedCli: () => Promise<SupportedCli>, refuse: (message: string) => void): Promise<BootstrapResult>;
12
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { resolvePlatformSupport } from "../lib/platform-support.js";
2
+ export async function bootstrapCli(platform, loadSupportedCli, refuse) {
3
+ const refusal = resolvePlatformSupport(platform);
4
+ if (refusal) {
5
+ refuse(`${refusal.refusalCode}: ${refusal.guidance}`);
6
+ return { state: "unsupported-platform", exitCode: 1 };
7
+ }
8
+ const supportedCli = await loadSupportedCli();
9
+ await supportedCli.runCli();
10
+ return { state: "supported", exitCode: 0 };
11
+ }
12
+ //# sourceMappingURL=bootstrap.js.map
@@ -7,8 +7,15 @@
7
7
  * Ink + React are already eagerly imported at the entrypoint, so importing the
8
8
  * concrete UI components at the top of this file does NOT worsen cold-start.
9
9
  */
10
+ import { render } from "ink";
10
11
  import type { CLI, RendererCtx } from "./types.js";
11
- export declare function handleDoctor(_cli: CLI, ctx: RendererCtx): void;
12
+ export interface RendererDeps {
13
+ platform?: string;
14
+ render?: typeof render;
15
+ error?: (message: string) => void;
16
+ setExitCode?: (code: number) => void;
17
+ }
18
+ export declare function handleDoctor(_cli: CLI, ctx: RendererCtx, deps?: RendererDeps): void;
12
19
  export declare function handleAnalyze(cli: CLI, ctx: RendererCtx): void;
13
20
  export declare function handleLlmsTxt(cli: CLI, ctx: RendererCtx): void;
14
21
  export declare function handlePlugin(cli: CLI, ctx: RendererCtx): void;
@@ -17,8 +17,17 @@ import Doctor from "../../ui/Doctor.js";
17
17
  import LlmsTxt from "../../ui/LlmsTxt.js";
18
18
  import Plugin from "../../ui/Plugin.js";
19
19
  import { VALID_CI, VALID_MEMORY, VALID_STACKS } from "../validators.js";
20
- export function handleDoctor(_cli, ctx) {
21
- render(React.createElement(CIContextProvider, { isCI: ctx.isCI },
20
+ export function handleDoctor(_cli, ctx, deps = {}) {
21
+ const platformSupport = resolvePlatformSupport(deps.platform ?? process.platform);
22
+ if (platformSupport) {
23
+ (deps.error ?? console.error)(`${platformSupport.refusalCode}: ${platformSupport.guidance}`);
24
+ (deps.setExitCode ??
25
+ ((code) => {
26
+ process.exitCode = code;
27
+ }))(1);
28
+ return;
29
+ }
30
+ (deps.render ?? render)(React.createElement(CIContextProvider, { isCI: ctx.isCI },
22
31
  React.createElement(Doctor, null)), { stdin: ctx.inkStdin });
23
32
  }
24
33
  export function handleAnalyze(cli, ctx) {
@@ -0,0 +1,2 @@
1
+ export declare function runCli(): Promise<void>;
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1,85 @@
1
+ import { createRequire } from "node:module";
2
+ import meow from "meow";
3
+ import { handleCi } from "./dispatch/ci.js";
4
+ import { handleHooks } from "./dispatch/hooks.js";
5
+ import { handleSecurity } from "./dispatch/security.js";
6
+ import { handleAnalyze, handleDoctor, handleInitDefault, handleLlmsTxt, handlePlugin, } from "./dispatch/simple-renderers.js";
7
+ import { handleSkillPublish } from "./dispatch/skill-publish.js";
8
+ import { handleSkillsCmd } from "./dispatch/skills-cmd.js";
9
+ import { handleTdd } from "./dispatch/tdd.js";
10
+ import { handleWorkflow } from "./dispatch/workflow.js";
11
+ import { FLAGS_SCHEMA, HELP_TEXT } from "./help.js";
12
+ import { createInkStdin, detectCI, setupUpdateNotifier } from "./runtime.js";
13
+ export async function runCli() {
14
+ // Check for updates in background (non-blocking, cached 24h)
15
+ const _require = createRequire(import.meta.url);
16
+ const pkg = _require("../../package.json");
17
+ setupUpdateNotifier(pkg);
18
+ const cli = meow(HELP_TEXT, {
19
+ importMeta: import.meta,
20
+ flags: FLAGS_SCHEMA,
21
+ // Handle --help manually so `ci --help` can show ci-specific usage instead
22
+ // of the global banner (meow's autoHelp would print + exit before dispatch).
23
+ autoHelp: false,
24
+ });
25
+ const subcommand = cli.input[0] ?? "init";
26
+ // Global --help: every command except `ci` and `hooks` shows the global banner
27
+ // here. Those two own their per-command help inside their handlers.
28
+ if (cli.flags.help && subcommand !== "ci" && subcommand !== "hooks") {
29
+ console.log(HELP_TEXT);
30
+ process.exit(0);
31
+ }
32
+ const isCI = detectCI(cli.flags);
33
+ const inkStdin = createInkStdin();
34
+ switch (subcommand) {
35
+ case "tdd": {
36
+ await handleTdd(cli);
37
+ break;
38
+ }
39
+ case "ci": {
40
+ await handleCi(cli, { inkStdin, isCI });
41
+ break;
42
+ }
43
+ case "hooks": {
44
+ await handleHooks(cli);
45
+ break;
46
+ }
47
+ case "doctor": {
48
+ handleDoctor(cli, { inkStdin, isCI });
49
+ break;
50
+ }
51
+ case "analyze": {
52
+ handleAnalyze(cli, { inkStdin, isCI });
53
+ break;
54
+ }
55
+ case "workflow": {
56
+ await handleWorkflow(cli);
57
+ break;
58
+ }
59
+ case "llms-txt": {
60
+ handleLlmsTxt(cli, { inkStdin, isCI });
61
+ break;
62
+ }
63
+ case "plugin": {
64
+ handlePlugin(cli, { inkStdin, isCI });
65
+ break;
66
+ }
67
+ case "skills": {
68
+ await handleSkillsCmd(cli, { inkStdin, isCI });
69
+ break;
70
+ }
71
+ case "skill": {
72
+ await handleSkillPublish(cli);
73
+ break;
74
+ }
75
+ case "security": {
76
+ await handleSecurity(cli);
77
+ break;
78
+ }
79
+ default: {
80
+ handleInitDefault(cli, { inkStdin, isCI });
81
+ break;
82
+ }
83
+ }
84
+ }
85
+ //# sourceMappingURL=main.js.map
@@ -17,6 +17,7 @@ import { doctorClaudePreToolUse, installClaudePreToolUse, repairClaudePreToolUse
17
17
  export type ClaudeHookSub = "install" | "doctor" | "repair";
18
18
  /** Injectable seams for tests; each defaults to the real implementation. */
19
19
  export interface ClaudeHookCmdDeps {
20
+ platform?: string;
20
21
  install?: typeof installClaudePreToolUse;
21
22
  doctor?: typeof doctorClaudePreToolUse;
22
23
  repair?: typeof repairClaudePreToolUse;
@@ -14,6 +14,7 @@
14
14
  * current.
15
15
  */
16
16
  import { doctorClaudePreToolUse, installClaudePreToolUse, repairClaudePreToolUse, } from "../lib/claude-hook-manager.js";
17
+ import { resolvePlatformSupport } from "../lib/platform-support.js";
17
18
  import { remediationForMessage } from "../lib/secure-refusal-remediation.js";
18
19
  /**
19
20
  * Warnings are NON-BLOCKING notices, so they go to stdout under their own
@@ -57,10 +58,13 @@ function renderMutation(verb, result, log, logError) {
57
58
  renderWarnings(result.warnings, log);
58
59
  return 1;
59
60
  }
60
- function renderDoctor(report, log) {
61
+ function renderDoctor(result, log) {
62
+ if ("state" in result) {
63
+ log(`${result.platformSupport.refusalCode}: ${result.platformSupport.guidance}`);
64
+ return 1;
65
+ }
66
+ const report = result;
61
67
  log(`doctor claude: ${report.healthy ? "healthy" : "unhealthy"}`);
62
- if (report.platformSupport)
63
- log(` platform-support: ${report.platformSupport.state} — ${report.platformSupport.guidance}`);
64
68
  log(` settings: ${report.settings.state} — ${report.settings.detail}`);
65
69
  log(` asset: ${report.asset.state} — ${report.asset.detail}`);
66
70
  log(` node: ${report.node.version ?? "unavailable"} (min-satisfied: ${report.node.satisfiesMinimum})`);
@@ -124,6 +128,11 @@ function renderDoctor(report, log) {
124
128
  export async function runClaudeHookCommand(sub, projectDir, opts, deps = {}) {
125
129
  const log = deps.log ?? ((m) => console.log(m));
126
130
  const logError = deps.logError ?? ((m) => console.error(m));
131
+ const platformSupport = resolvePlatformSupport(deps.platform ?? process.platform);
132
+ if (platformSupport) {
133
+ logError(`${platformSupport.refusalCode}: ${platformSupport.guidance}`);
134
+ return 1;
135
+ }
127
136
  const install = deps.install ?? installClaudePreToolUse;
128
137
  const doctor = deps.doctor ?? doctorClaudePreToolUse;
129
138
  const repair = deps.repair ?? repairClaudePreToolUse;
@@ -13,6 +13,7 @@
13
13
  import { doctorCodexPreToolUse, installCodexPreToolUse, repairCodexPreToolUse } from "../lib/codex-hook-manager.js";
14
14
  export type CodexHookSub = "install" | "doctor" | "repair";
15
15
  export interface CodexHookCmdDeps {
16
+ platform?: string;
16
17
  install?: typeof installCodexPreToolUse;
17
18
  doctor?: typeof doctorCodexPreToolUse;
18
19
  repair?: typeof repairCodexPreToolUse;
@@ -11,6 +11,7 @@
11
11
  * install correctly reports blocked until the user grants trust.
12
12
  */
13
13
  import { doctorCodexPreToolUse, installCodexPreToolUse, repairCodexPreToolUse, } from "../lib/codex-hook-manager.js";
14
+ import { resolvePlatformSupport } from "../lib/platform-support.js";
14
15
  function renderWarnings(warnings, log) {
15
16
  if (warnings.length === 0)
16
17
  return;
@@ -42,10 +43,13 @@ function renderMutation(verb, result, log, logError) {
42
43
  renderWarnings(result.warnings, log);
43
44
  return 1;
44
45
  }
45
- function renderDoctor(report, log) {
46
+ function renderDoctor(result, log) {
47
+ if ("state" in result) {
48
+ log(`${result.platformSupport.refusalCode}: ${result.platformSupport.guidance}`);
49
+ return 1;
50
+ }
51
+ const report = result;
46
52
  log(`doctor codex: ${report.healthy ? "healthy" : "unhealthy"}`);
47
- if (report.platformSupport)
48
- log(` platform-support: ${report.platformSupport.state} — ${report.platformSupport.guidance}`);
49
53
  log(` hooks.json: ${report.hooksJson.state}`);
50
54
  log(` config: [features] hooks=${report.config.featuresHooks} (readable: ${report.config.readable})`);
51
55
  log(` trust: ${report.trust.state}`);
@@ -88,6 +92,11 @@ function renderDoctor(report, log) {
88
92
  export async function runCodexHookCommand(sub, _cwd, opts, deps = {}) {
89
93
  const log = deps.log ?? ((m) => console.log(m));
90
94
  const logError = deps.logError ?? ((m) => console.error(m));
95
+ const platformSupport = resolvePlatformSupport(deps.platform ?? process.platform);
96
+ if (platformSupport) {
97
+ logError(`${platformSupport.refusalCode}: ${platformSupport.guidance}`);
98
+ return 1;
99
+ }
91
100
  const install = deps.install ?? installCodexPreToolUse;
92
101
  const doctor = deps.doctor ?? doctorCodexPreToolUse;
93
102
  const repair = deps.repair ?? repairCodexPreToolUse;
@@ -1,5 +1,20 @@
1
+ import fs from "fs-extra";
2
+ import { detectStack } from "../lib/common.js";
3
+ import { refreshContextDir } from "../lib/context.js";
4
+ import { execFileAsync } from "../lib/exec.js";
5
+ import { listInstalledPlugins } from "../lib/plugin.js";
1
6
  import type { DoctorResult } from "../types/index.js";
2
7
  export type CheckStatus = "ok" | "fail" | "skip";
8
+ type DoctorFilesystem = Pick<typeof fs, "pathExists" | "readJson" | "readdir">;
9
+ export interface DoctorDeps {
10
+ platform?: string;
11
+ cwd?: () => string;
12
+ filesystem?: DoctorFilesystem;
13
+ exec?: typeof execFileAsync;
14
+ stackDetector?: typeof detectStack;
15
+ pluginLister?: typeof listInstalledPlugins;
16
+ contextRefresher?: typeof refreshContextDir;
17
+ }
3
18
  interface RemoteInfo {
4
19
  host: "github" | "gitlab" | "other";
5
20
  owner: string;
@@ -10,6 +25,6 @@ export declare function parseRemote(url: string): RemoteInfo | null;
10
25
  /**
11
26
  * Run comprehensive health checks for the project and framework.
12
27
  */
13
- export declare function runDoctor(projectDir?: string): Promise<DoctorResult>;
28
+ export declare function runDoctor(projectDir?: string, deps?: DoctorDeps): Promise<DoctorResult>;
14
29
  export {};
15
30
  //# sourceMappingURL=doctor.d.ts.map