@aiviatic/kindling 0.1.1 → 0.1.3

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.
@@ -10,7 +10,7 @@
10
10
  $ErrorActionPreference = 'Stop'
11
11
 
12
12
  $KindlingNodeVersion = '24.16.0' # == pins.node
13
- $KindlingVersion = '0.1.1' # == pins.kindling
13
+ $KindlingVersion = '0.1.3' # == pins.kindling
14
14
  $NodeFloorMajor = 20
15
15
 
16
16
  # --- Inline helpers (were bootstrap/lib/common.ps1; inlined for the file-less delivery) ----------
@@ -39,19 +39,24 @@ Say " - This window already runs with -ExecutionPolicy Bypass for THIS process o
39
39
  # $NodeExe stays $null when a system Node is reused (launch via PATH); the portable-install path
40
40
  # sets it to the absolute node.exe so the launch never depends on PATH (clean-runtime rule, AR6).
41
41
  $NodeExe = $null
42
+ $arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
43
+ $distName = "node-v$KindlingNodeVersion-win-$arch"
44
+ $nodeRoot = Join-Path $env:LOCALAPPDATA 'kindling\node'
45
+ $portableNode = Join-Path $nodeRoot "$distName\node.exe"
42
46
  if (Test-NodeOk $NodeFloorMajor) {
43
47
  Say "Node is already installed - reusing it."
48
+ } elseif (Test-Path $portableNode) {
49
+ # Portable Node from a previous run is already extracted here - reuse it, don't re-download the
50
+ # 30 MB. (The portable dir is never persisted to PATH, so Test-NodeOk alone can't see it.)
51
+ Say "Node is already installed - reusing it."
52
+ $NodeExe = $portableNode
44
53
  } else {
45
54
  Say "Setting up Node - the engine your project runs on. This downloads about 30 MB, one time."
46
55
  # Portable Node: download the pinned Windows zip from nodejs.org, VERIFY its SHA-256 against Node's
47
56
  # published SHASUMS256.txt before touching it, extract, and point the launch at the absolute
48
57
  # node.exe (clean-runtime rule AR6 — never rely on a mutated PATH). Mirrors node-windows.ts.
49
- # NOTE: newly implemented; validate on real Windows (proxy/TLS-interception, extract, non-admin exec).
50
58
  $ProgressPreference = 'SilentlyContinue' # a visible progress bar makes Invoke-WebRequest ~10x slower
51
59
  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
52
- $arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
53
- $distName = "node-v$KindlingNodeVersion-win-$arch"
54
- $nodeRoot = Join-Path $env:LOCALAPPDATA 'kindling\node'
55
60
  $zipPath = Join-Path $nodeRoot "$distName.zip"
56
61
  $baseUrl = "https://nodejs.org/dist/v$KindlingNodeVersion"
57
62
  New-Item -ItemType Directory -Force -Path $nodeRoot | Out-Null
@@ -73,13 +78,61 @@ if (Test-NodeOk $NodeFloorMajor) {
73
78
  Say "Node is ready."
74
79
  }
75
80
 
76
- # --- Git (portable) -----------------------------------------------------------
81
+ # --- Git (pinned, portable MinGit) -------------------------------------------
82
+ # $GitCmdDir stays $null when a system Git is reused (already on PATH); the portable path sets it to
83
+ # MinGit's cmd\ dir, prepended to PATH at launch so the engine can `git init` the new project.
84
+ $GitCmdDir = $null
85
+ $gitRoot = Join-Path $env:LOCALAPPDATA 'kindling\git'
86
+ $portableGitCmd = Join-Path $gitRoot 'cmd'
77
87
  if (Test-Cmd 'git') {
78
88
  Say "Git is already installed - reusing it."
89
+ } elseif (Test-Path (Join-Path $portableGitCmd 'git.exe')) {
90
+ # Portable MinGit from a previous run is already extracted here - reuse it, don't re-download.
91
+ Say "Git is already installed - reusing it."
92
+ $GitCmdDir = $portableGitCmd
79
93
  } else {
80
- Say "Setting up Git - it keeps the history of your project."
81
- # PortableGit download/extract deferred (needs pinned version + release URL); resolved at rehearsal.
94
+ Say "Setting up Git - it keeps the history of your project. This downloads about 35 MB, one time."
95
+ # Portable MinGit (the ZIP build made for bundling): download the pinned release, VERIFY its SHA-256
96
+ # before touching it, extract, and expose cmd\ on PATH. Mirrors the Node block. Pinned version +
97
+ # hash below MUST be bumped together (git-for-windows publishes the digest on each release asset).
98
+ $ProgressPreference = 'SilentlyContinue'
99
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
100
+ $gitVersion = '2.55.0.2'
101
+ $gitZip = Join-Path $env:LOCALAPPDATA 'kindling\mingit.zip'
102
+ $gitUrl = "https://github.com/git-for-windows/git/releases/download/v2.55.0.windows.2/MinGit-$gitVersion-64-bit.zip"
103
+ $gitSha = 'e3ea2944cea4b3fabcd69c7c1669ef69b1b66c05ac7806d81224d0abad2dec31'
104
+ New-Item -ItemType Directory -Force -Path $gitRoot | Out-Null
105
+ try {
106
+ Invoke-WebRequest -Uri $gitUrl -OutFile $gitZip -UseBasicParsing
107
+ $actual = (Get-FileHash -Path $gitZip -Algorithm SHA256).Hash.ToLower()
108
+ if ($actual -ne $gitSha) {
109
+ throw "downloaded Git failed its integrity check (expected '$gitSha', got '$actual')"
110
+ }
111
+ Expand-Archive -Path $gitZip -DestinationPath $gitRoot -Force
112
+ } catch {
113
+ throw "Couldn't set up Git ($($_.Exception.Message)). Check your internet connection, then run this again - it's safe to re-run."
114
+ }
115
+ $GitCmdDir = $portableGitCmd
116
+ if (-not (Test-Path (Join-Path $GitCmdDir 'git.exe'))) { throw "Git was downloaded but git.exe wasn't found at $GitCmdDir." }
117
+ Say "Git is ready."
118
+ }
119
+
120
+ # --- Persist the portable runtime on the USER PATH ---------------------------
121
+ # So the tools actually WORK in a normal terminal afterward (not just during this run): node/npm/npx
122
+ # live in the Node dir, git in MinGit's cmd\, and any globally-installed agent CLI (claude/codex)
123
+ # gets its shim written INTO the Node dir (npm's global prefix) - so putting the Node dir on PATH
124
+ # covers those too. Only the PORTABLE paths need this; a reused system Node/Git is already on PATH.
125
+ # User-scope (no admin), idempotent, prepended so the pinned runtime wins. Future terminals pick it up.
126
+ function Add-UserPath([string]$dir) {
127
+ if (-not $dir -or -not (Test-Path $dir)) { return }
128
+ $cur = [Environment]::GetEnvironmentVariable('Path', 'User')
129
+ $parts = @(($cur -split ';') | Where-Object { $_ -ne '' })
130
+ if ($parts -notcontains $dir) {
131
+ [Environment]::SetEnvironmentVariable('Path', ((@($dir) + $parts) -join ';'), 'User')
132
+ }
82
133
  }
134
+ if ($NodeExe) { Add-UserPath (Split-Path $NodeExe) }
135
+ if ($GitCmdDir) { Add-UserPath $GitCmdDir }
83
136
 
84
137
  # --- Launch Kindling (clean-runtime: absolute node when portable, else npx on PATH) -----------
85
138
  Say "Starting Kindling..."
@@ -91,6 +144,9 @@ Say "Starting Kindling..."
91
144
  $launchDir = Join-Path $env:LOCALAPPDATA 'kindling'
92
145
  New-Item -ItemType Directory -Force -Path $launchDir | Out-Null
93
146
  Set-Location -LiteralPath $launchDir
147
+ # Portable Git on PATH (the engine spawns `git` by name to scaffold the project's history). Applies
148
+ # to both launch branches; the provisioned Node dir is added inside the portable-Node branch below.
149
+ if ($GitCmdDir) { $env:Path = "$GitCmdDir;$env:Path" }
94
150
  if ($null -eq $NodeExe) {
95
151
  & npx -y "@aiviatic/kindling@$KindlingVersion"
96
152
  } else {
@@ -7,7 +7,7 @@
7
7
  set -euo pipefail
8
8
 
9
9
  KINDLING_NODE_VERSION="24.16.0" # == pins.node
10
- KINDLING_VERSION="0.1.1" # == pins.kindling
10
+ KINDLING_VERSION="0.1.3" # == pins.kindling
11
11
  NVM_VERSION="v0.40.3" # == NVM_VERSION (engine/provision/node-unix.ts)
12
12
  NODE_FLOOR_MAJOR=20
13
13
 
@@ -40,75 +40,75 @@ var pins = Object.freeze({
40
40
  node: "24.16.0",
41
41
  bmad: "6.9.0",
42
42
  // frozen for the Cohort #1 cycle (matches this repo's BMad install; tools + install flags verified vs the real 6.9.0 CLI 2026-07-02); bump between cohorts
43
- kindling: "0.1.1"
43
+ kindling: "0.1.3"
44
44
  });
45
45
 
46
46
  // engine/messages.ts
47
47
  var stepMessages = {
48
- [StepId.ProvisionNode]: "Setting up Node \u2014 the engine your project runs on.",
49
- [StepId.ProvisionGit]: "Setting up Git \u2014 it keeps the history of your project safe.",
50
- [StepId.ProvisionXcodeClt]: "macOS is installing some developer tools. A system dialog popped up \u2014 click Install. This is completely normal and usually takes about 5 minutes. You can grab a coffee.",
48
+ [StepId.ProvisionNode]: "Setting up Node - the engine your project runs on.",
49
+ [StepId.ProvisionGit]: "Setting up Git - it keeps the history of your project safe.",
50
+ [StepId.ProvisionXcodeClt]: "macOS is installing some developer tools. A system dialog popped up: click Install. This is completely normal and usually takes about 5 minutes. You can grab a coffee.",
51
51
  [StepId.ScaffoldGitInit]: "Creating your project folder and starting its history.",
52
- [StepId.InstallBmad]: "Installing BMad \u2014 the toolkit that powers your project. This is a sizeable download, so it can take a couple of minutes. Nothing is stuck; hang tight.",
52
+ [StepId.InstallBmad]: "Installing BMad - the toolkit that powers your project. This is a sizeable download, so it can take a couple of minutes. Nothing is stuck; hang tight.",
53
53
  [StepId.InstallAgentCli]: "Installing your AI coding assistant so it\u2019s ready to run right after setup.",
54
54
  [StepId.FinalizeSelfCheck]: "Double-checking everything is in place."
55
55
  };
56
56
  var agentCliMessages = {
57
57
  working: (name) => `Installing ${name} so you can start right away.`,
58
58
  done: (name) => `${name} is installed and ready to run.`,
59
- skipped: (name) => `${name} is already installed \u2014 reusing it.`,
60
- failed: (name) => `${name} didn\u2019t finish installing \u2014 your project is still set up and ready. You can press Retry, or install it yourself later.`,
59
+ skipped: (name) => `${name} is already installed, reusing it.`,
60
+ failed: (name) => `${name} didn\u2019t finish installing - your project is still set up and ready. You can press Retry, or install it yourself later.`,
61
61
  manualInstall: (pkg) => `To install it yourself later, run: npm install -g ${pkg}`
62
62
  };
63
63
  var scaffoldMessages = {
64
64
  done: "Your project folder is ready.",
65
- skipped: "Project already set up \u2014 nothing to do.",
66
- blocked: "This folder already has files in it. Kindling won\u2019t change anything without your OK \u2014 pick an empty folder, or confirm before continuing.",
65
+ skipped: "Project already set up, nothing to do.",
66
+ blocked: "This folder already has files in it. Kindling won\u2019t change anything without your OK, pick an empty folder, or confirm before continuing.",
67
67
  failed: "Setting up your project folder ran into a problem. Check the details, then press Retry."
68
68
  };
69
69
  var provisionMessages = {
70
- nodePresent: "Node is already installed \u2014 reusing it.",
71
- nodeQueued: "Node needs setting up \u2014 the engine your project runs on.",
72
- gitPresent: "Git is already installed \u2014 reusing it.",
73
- gitQueued: "Git needs setting up \u2014 it keeps the history of your project.",
70
+ nodePresent: "Node is already installed, reusing it.",
71
+ nodeQueued: "Node needs setting up - the engine your project runs on.",
72
+ gitPresent: "Git is already installed, reusing it.",
73
+ gitQueued: "Git needs setting up - it keeps the history of your project.",
74
74
  gitInstalled: "Git is set up.",
75
- xcodeWaiting: "Still installing developer tools\u2026 the macOS dialog is doing its thing. This can take a few minutes \u2014 hang tight, nothing is stuck.",
76
- xcodeDone: "Developer tools are ready \u2014 Git is set up.",
75
+ xcodeWaiting: "Still installing developer tools\u2026 the macOS dialog is doing its thing. This can take a few minutes, hang tight, nothing is stuck.",
76
+ xcodeDone: "Developer tools are ready - Git is set up.",
77
77
  xcodeTimeout: "The developer-tools install is taking longer than expected. If the macOS dialog is still open, let it finish, then press Retry.",
78
78
  xcodeInstallFailed: "We couldn\u2019t start the developer-tools install. Make sure you\u2019re connected, then press Retry.",
79
- gitInstallFailed: "Setting up Git ran into a problem \u2014 it may need permission to install. Check the details, then press Retry."
79
+ gitInstallFailed: "Setting up Git ran into a problem, it may need permission to install. Check the details, then press Retry."
80
80
  };
81
81
  var selfCheckMessages = {
82
- done: "Everything checks out \u2014 you\u2019re ready.",
83
- failed: "Some checks didn\u2019t pass \u2014 see the readiness details."
82
+ done: "Everything checks out, you\u2019re ready.",
83
+ failed: "Some checks didn\u2019t pass, see the readiness details."
84
84
  };
85
85
  var installMessages = {
86
- notPinned: "BMad isn\u2019t pinned to a version yet \u2014 set the cohort version before installing.",
86
+ notPinned: "BMad isn\u2019t pinned to a version yet, set the cohort version before installing.",
87
87
  done: (version) => `BMad ${version} installed.`
88
88
  };
89
89
  var errorMessages = {
90
- [ErrorCode.ExecFailed]: "Something needs a quick fix \u2014 a step did not finish. Check the next step below, then press Retry.",
90
+ [ErrorCode.ExecFailed]: "Something needs a quick fix: a step did not finish. Check the next step below, then press Retry.",
91
91
  [ErrorCode.NetworkLost]: "We lost the connection. Reconnect to the internet, then press Retry.",
92
- [ErrorCode.BmadInstallFailed]: "BMad didn\u2019t finish installing. The details are below \u2014 press Retry.",
93
- [ErrorCode.AgentCliInstallFailed]: "Your AI coding assistant didn\u2019t finish installing \u2014 your project is still ready. Press Retry, or install it yourself later.",
94
- [ErrorCode.ExecPolicyBlocked]: "Windows blocked the script because it\u2019s unsigned \u2014 that\u2019s expected, safe, and reversible.",
95
- [ErrorCode.SmartScreenBlocked]: "Windows SmartScreen (or your antivirus) paused the script \u2014 that\u2019s expected, safe, and reversible.",
92
+ [ErrorCode.BmadInstallFailed]: "BMad didn\u2019t finish installing. The details are below, press Retry.",
93
+ [ErrorCode.AgentCliInstallFailed]: "Your AI coding assistant didn\u2019t finish installing - your project is still ready. Press Retry, or install it yourself later.",
94
+ [ErrorCode.ExecPolicyBlocked]: "Windows blocked the script because it\u2019s unsigned, that\u2019s expected, safe, and reversible.",
95
+ [ErrorCode.SmartScreenBlocked]: "Windows SmartScreen (or your antivirus) paused the script, that\u2019s expected, safe, and reversible.",
96
96
  [ErrorCode.ProjectConflict]: "That folder already has files in it that Kindling didn\u2019t create."
97
97
  };
98
98
  var recoveryGuidance = {
99
99
  [ErrorCode.ExecFailed]: {
100
100
  title: "Something needs a quick fix.",
101
- detail: "A step didn\u2019t finish. This usually clears up on a second try \u2014 press Retry.",
101
+ detail: "A step didn\u2019t finish. This usually clears up on a second try, press Retry.",
102
102
  recovery: "retry"
103
103
  },
104
104
  [ErrorCode.NetworkLost]: {
105
105
  title: "We lost the connection.",
106
- detail: "A download was interrupted. Reconnect to the internet, then press Retry \u2014 Kindling resumes where it left off.",
106
+ detail: "A download was interrupted. Reconnect to the internet, then press Retry, Kindling resumes where it left off.",
107
107
  recovery: "retry"
108
108
  },
109
109
  [ErrorCode.BmadInstallFailed]: {
110
110
  title: "BMad didn\u2019t install.",
111
- detail: "The install step didn\u2019t finish. This is usually temporary \u2014 press Retry. The step details below show what happened.",
111
+ detail: "The install step didn\u2019t finish. This is usually temporary, press Retry. The step details below show what happened.",
112
112
  recovery: "retry"
113
113
  },
114
114
  [ErrorCode.AgentCliInstallFailed]: {
@@ -116,18 +116,18 @@ var recoveryGuidance = {
116
116
  // concrete, copy-pasteable `npm install -g <package>` line ships in the failed step's own
117
117
  // message (agentCliMessages.manualInstall) rather than in this static, error-code-keyed entry.
118
118
  title: "Your AI assistant didn\u2019t install.",
119
- detail: "Your project is set up and ready either way \u2014 this step is optional. Press Retry to try again, or install it yourself later using the command shown in the step details below.",
119
+ detail: "Your project is set up and ready either way, this step is optional. Press Retry to try again, or install it yourself later using the command shown in the step details below.",
120
120
  recovery: "retry"
121
121
  },
122
122
  [ErrorCode.ExecPolicyBlocked]: {
123
123
  title: "Something needs a quick fix.",
124
- detail: "Windows blocked the script because it\u2019s unsigned \u2014 that\u2019s expected, and it\u2019s safe and reversible. Run this one line in PowerShell, then come back and press Retry:",
124
+ detail: "Windows blocked the script because it\u2019s unsigned, that\u2019s expected, and it\u2019s safe and reversible. Run this one line in PowerShell, then come back and press Retry:",
125
125
  fixCommand: "Set-ExecutionPolicy -Scope Process Bypass",
126
126
  recovery: "retry"
127
127
  },
128
128
  [ErrorCode.SmartScreenBlocked]: {
129
129
  title: "Windows asked you to confirm.",
130
- detail: "SmartScreen or your antivirus paused the script \u2014 this is expected and safe. Choose \u201CMore info\u201D, then \u201CRun anyway\u201D, and press Retry. Nothing was changed on your computer.",
130
+ detail: "SmartScreen or your antivirus paused the script, this is expected and safe. Choose \u201CMore info\u201D, then \u201CRun anyway\u201D, and press Retry. Nothing was changed on your computer.",
131
131
  recovery: "retry"
132
132
  },
133
133
  [ErrorCode.ProjectConflict]: {
@@ -304,14 +304,30 @@ async function runBmadInstall(opts) {
304
304
  const alreadyInstalled = opts.bmadAlreadyInstalled ?? defaultBmadInstalled;
305
305
  action = await alreadyInstalled(opts.config.projectDir) ? "update" : "install";
306
306
  }
307
- const args = [`bmad-method@${versionTag}`, ...composeInstallArgs(opts.config, action)];
307
+ const args = [
308
+ ...opts.npxPrefixArgs ?? [],
309
+ `bmad-method@${versionTag}`,
310
+ ...composeInstallArgs(opts.config, action)
311
+ ];
308
312
  result = await exec2(npx, args);
309
313
  } catch (err) {
310
- emit(Status.Failed, errorMessages[ErrorCode.BmadInstallFailed], "error", ErrorCode.BmadInstallFailed);
314
+ const detail = err instanceof Error ? err.message : String(err);
315
+ emit(
316
+ Status.Failed,
317
+ `${errorMessages[ErrorCode.BmadInstallFailed]} (${detail})`,
318
+ "error",
319
+ ErrorCode.BmadInstallFailed
320
+ );
311
321
  throw err;
312
322
  }
313
323
  if (result.code !== 0) {
314
- emit(Status.Failed, errorMessages[ErrorCode.BmadInstallFailed], "error", ErrorCode.BmadInstallFailed);
324
+ const raw = (result.stderr.trim() ? result.stderr : result.stdout).trim();
325
+ const detail = raw.slice(-600).trim();
326
+ const humanMessage = detail ? `${errorMessages[ErrorCode.BmadInstallFailed]}
327
+
328
+ Details:
329
+ ${detail}` : errorMessages[ErrorCode.BmadInstallFailed];
330
+ emit(Status.Failed, humanMessage, "error", ErrorCode.BmadInstallFailed);
315
331
  return { ok: false, bmadVersion: versionTag };
316
332
  }
317
333
  emit(Status.Done, installMessages.done(versionTag));
@@ -331,6 +347,15 @@ async function probeVersion(exec2, cmd) {
331
347
  return null;
332
348
  }
333
349
  }
350
+ async function probeCliVersion(exec2, bin, isWindows) {
351
+ try {
352
+ const r = isWindows ? await exec2("cmd", ["/c", bin, "--version"]) : await exec2(bin, ["--version"]);
353
+ const out = r.stdout.trim() || r.stderr.trim();
354
+ return r.code === 0 && out ? out : null;
355
+ } catch {
356
+ return null;
357
+ }
358
+ }
334
359
 
335
360
  // engine/orchestrate/agent-cli.ts
336
361
  var AGENT_CLI_TABLE = {
@@ -346,7 +371,7 @@ function eligibleAgentClis(installCli) {
346
371
  async function installAgentCli(opts) {
347
372
  const exec2 = opts.exec ?? exec;
348
373
  const npm = opts.npmCommand ?? "npm";
349
- const resolveBin = opts.resolveBin ?? ((bin) => bin);
374
+ const isWindows = opts.isWindows ?? false;
350
375
  const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
351
376
  const emit = (status, humanMessage, level = "info", errorCode) => {
352
377
  opts.emitter.emit({
@@ -365,7 +390,7 @@ async function installAgentCli(opts) {
365
390
  const failed = [];
366
391
  const eligible = eligibleAgentClis(opts.config.installCli);
367
392
  for (const { id, pkg, bin, name } of eligible) {
368
- if (await probeVersion(exec2, resolveBin(bin)) !== null) {
393
+ if (await probeCliVersion(exec2, bin, isWindows) !== null) {
369
394
  emit(Status.Skipped, agentCliMessages.skipped(name));
370
395
  skipped.push(id);
371
396
  continue;
@@ -373,29 +398,61 @@ async function installAgentCli(opts) {
373
398
  emit(Status.Working, agentCliMessages.working(name));
374
399
  const failMessage = `${agentCliMessages.failed(name)} ${agentCliMessages.manualInstall(pkg)}`;
375
400
  try {
376
- const result = await exec2(npm, ["install", "-g", pkg]);
401
+ const result = await exec2(npm, [...opts.npmPrefixArgs ?? [], "install", "-g", pkg]);
377
402
  if (result.code === 0) {
378
403
  emit(Status.Done, agentCliMessages.done(name));
379
404
  installed.push(id);
380
405
  } else {
381
- emit(Status.Failed, failMessage, "error", ErrorCode.AgentCliInstallFailed);
406
+ const raw = (result.stderr.trim() ? result.stderr : result.stdout).trim();
407
+ const detail = raw.slice(-600).trim();
408
+ emit(
409
+ Status.Failed,
410
+ detail ? `${failMessage}
411
+
412
+ Details:
413
+ ${detail}` : failMessage,
414
+ "error",
415
+ ErrorCode.AgentCliInstallFailed
416
+ );
382
417
  failed.push(id);
383
418
  }
384
- } catch {
385
- emit(Status.Failed, failMessage, "error", ErrorCode.AgentCliInstallFailed);
419
+ } catch (err) {
420
+ const detail = err instanceof Error ? err.message : String(err);
421
+ emit(
422
+ Status.Failed,
423
+ `${failMessage} (${detail})`,
424
+ "error",
425
+ ErrorCode.AgentCliInstallFailed
426
+ );
386
427
  failed.push(id);
387
428
  }
388
429
  }
389
430
  return { ok: true, installed, skipped, failed };
390
431
  }
391
432
 
433
+ // engine/orchestrate/launch.ts
434
+ import { dirname, join as join3 } from "path";
435
+ function npxCliPath(nodeExe) {
436
+ return join3(dirname(nodeExe), "node_modules", "npm", "bin", "npx-cli.js");
437
+ }
438
+ function npmCliPath(nodeExe) {
439
+ return join3(dirname(nodeExe), "node_modules", "npm", "bin", "npm-cli.js");
440
+ }
441
+ function composeLaunchCommand({ nodeExe, kindlingVersion }) {
442
+ const spec = `@aiviatic/kindling@${kindlingVersion}`;
443
+ if (nodeExe === null) {
444
+ return { cmd: "npx", args: ["-y", spec] };
445
+ }
446
+ return { cmd: nodeExe, args: [npxCliPath(nodeExe), "-y", spec] };
447
+ }
448
+
392
449
  // engine/bmad-manifest.ts
393
450
  import { readFile } from "fs/promises";
394
- import { join as join3 } from "path";
451
+ import { join as join4 } from "path";
395
452
  import { load } from "js-yaml";
396
453
  async function readInstalledBmadVersion(projectDir) {
397
454
  try {
398
- const raw = await readFile(join3(projectDir, "_bmad", "_config", "manifest.yaml"), "utf8");
455
+ const raw = await readFile(join4(projectDir, "_bmad", "_config", "manifest.yaml"), "utf8");
399
456
  const doc = load(raw);
400
457
  if (typeof doc !== "object" || doc === null) return null;
401
458
  const installation = doc.installation;
@@ -455,7 +512,6 @@ import { randomUUID as randomUUID4 } from "crypto";
455
512
  async function runSelfCheck(opts) {
456
513
  const exec2 = opts.exec ?? exec;
457
514
  const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
458
- const resolveBin = opts.resolveBin ?? ((bin) => bin);
459
515
  const readInstalledBmadVersion2 = opts.readInstalledBmadVersion ?? readInstalledBmadVersion;
460
516
  const platform = opts.platform ?? {
461
517
  os: process.platform,
@@ -480,9 +536,10 @@ async function runSelfCheck(opts) {
480
536
  const nodeVersion = await probeVersion(exec2, opts.node ?? "node");
481
537
  const gitVersion = await probeVersion(exec2, opts.git ?? "git");
482
538
  const nodeMajor = parseMajor(nodeVersion);
539
+ const isWindows = platform.os === "win32";
483
540
  const cli = [];
484
541
  for (const c of opts.agentClis ?? []) {
485
- const present = await probeVersion(exec2, resolveBin(c.bin)) !== null;
542
+ const present = await probeCliVersion(exec2, c.bin, isWindows) !== null;
486
543
  cli.push({ id: c.id, name: c.name, bin: c.bin, pkg: c.pkg, present });
487
544
  }
488
545
  const installedVersion = await readInstalledBmadVersion2(opts.projectDir);
@@ -625,14 +682,14 @@ async function provisionGitUnix(opts) {
625
682
 
626
683
  // engine/log.ts
627
684
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
628
- import { join as join4 } from "path";
685
+ import { join as join5 } from "path";
629
686
  import { homedir } from "os";
630
687
  import { randomUUID as randomUUID7 } from "crypto";
631
688
  function defaultLogDir() {
632
689
  if (process.platform === "win32" && process.env.LOCALAPPDATA) {
633
- return join4(process.env.LOCALAPPDATA, "kindling", "logs");
690
+ return join5(process.env.LOCALAPPDATA, "kindling", "logs");
634
691
  }
635
- return join4(homedir(), ".kindling", "logs");
692
+ return join5(homedir(), ".kindling", "logs");
636
693
  }
637
694
  function render(entry, timestamp) {
638
695
  const lines = [
@@ -644,7 +701,7 @@ function render(entry, timestamp) {
644
701
  entry.error,
645
702
  ``,
646
703
  `Event log:`,
647
- ...entry.events.map((e) => ` [${e.status}] ${e.step} \u2014 ${e.humanMessage}`),
704
+ ...entry.events.map((e) => ` [${e.status}] ${e.step} - ${e.humanMessage}`),
648
705
  ``
649
706
  ];
650
707
  return lines.join("\n");
@@ -655,7 +712,7 @@ async function writeFailureLog(entry, opts = {}) {
655
712
  const safeStamp = timestamp.replace(/[:.]/g, "-");
656
713
  const suffix = randomUUID7().slice(0, 8);
657
714
  await mkdir2(dir, { recursive: true });
658
- const path = join4(dir, `kindling-report-${safeStamp}-${suffix}.log`);
715
+ const path = join5(dir, `kindling-report-${safeStamp}-${suffix}.log`);
659
716
  await writeFile2(path, render(entry, timestamp));
660
717
  return path;
661
718
  }
@@ -733,7 +790,11 @@ var Engine = class {
733
790
  {
734
791
  id: StepId.InstallBmad,
735
792
  run: async () => {
736
- const result = await this.deps.runBmadInstall({ config: this.config, emitter: this.emitter });
793
+ const result = await this.deps.runBmadInstall({
794
+ config: this.config,
795
+ emitter: this.emitter,
796
+ ...this.deps.platform === "win32" ? { npxCommand: process.execPath, npxPrefixArgs: [npxCliPath(process.execPath)] } : {}
797
+ });
737
798
  this.bmadInstalled = result.ok;
738
799
  return result.ok;
739
800
  }
@@ -748,7 +809,14 @@ var Engine = class {
748
809
  // CLI-presence guidance (retry() skips the already-completed self-check). See deferred-work.md.
749
810
  id: StepId.InstallAgentCli,
750
811
  run: async () => {
751
- await this.deps.installAgentCli({ config: this.config, emitter: this.emitter });
812
+ await this.deps.installAgentCli({
813
+ config: this.config,
814
+ emitter: this.emitter,
815
+ // Windows: the idempotent-skip probe must go through `cmd /c <bin> --version` so the
816
+ // installed `claude.cmd` shim is detected (Node won't spawn `.cmd` with shell:false).
817
+ isWindows: this.deps.platform === "win32",
818
+ ...this.deps.platform === "win32" ? { npmCommand: process.execPath, npmPrefixArgs: [npmCliPath(process.execPath)] } : {}
819
+ });
752
820
  return true;
753
821
  }
754
822
  },
@@ -872,6 +940,8 @@ export {
872
940
  AGENT_CLI_TABLE,
873
941
  eligibleAgentClis,
874
942
  installAgentCli,
943
+ npxCliPath,
944
+ composeLaunchCommand,
875
945
  readInstalledBmadVersion,
876
946
  SCHEMA_VERSION,
877
947
  NODE_FLOOR_MAJOR,
@@ -887,4 +957,4 @@ export {
887
957
  writeFailureLog,
888
958
  Engine
889
959
  };
890
- //# sourceMappingURL=chunk-6VTQGOJR.js.map
960
+ //# sourceMappingURL=chunk-G2SKKJWZ.js.map