agent-takkub 1.0.6 → 1.0.8
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.
|
Binary file
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Ensure the npm global bin dir stays on the user's persistent PATH.
|
|
3
|
+
//
|
|
4
|
+
// Field incident 2026-07-04: a Node update dropped %APPDATA%\npm from the user
|
|
5
|
+
// PATH — `claude`, `takkub`, and `agent-takkub` all became "command not found"
|
|
6
|
+
// and the user had to hand-edit the registry. This module makes the install
|
|
7
|
+
// self-healing:
|
|
8
|
+
// • win32 — appends the dir to HKCU\Environment Path via .NET RegistryKey,
|
|
9
|
+
// PRESERVING the value kind (REG_SZ vs REG_EXPAND_SZ, read with
|
|
10
|
+
// DoNotExpandEnvironmentNames so %VAR% entries survive verbatim),
|
|
11
|
+
// then broadcasts WM_SETTINGCHANGE so new shells see it.
|
|
12
|
+
// • darwin/linux — appends a marker-guarded export block to ~/.zshrc (and
|
|
13
|
+
// ~/.bashrc when present). Idempotent via the marker.
|
|
14
|
+
// Best-effort by design: any failure only prints a manual-fix hint — it never
|
|
15
|
+
// fails the install.
|
|
16
|
+
|
|
17
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const MARKER = '# >>> agent-takkub PATH >>>';
|
|
23
|
+
|
|
24
|
+
function npmGlobalBinDir() {
|
|
25
|
+
// Node ≥18 refuses to spawn .cmd shims without a shell (CVE-2024-27980
|
|
26
|
+
// hardening) — route through cmd.exe explicitly on Windows.
|
|
27
|
+
const r =
|
|
28
|
+
process.platform === 'win32'
|
|
29
|
+
? spawnSync('cmd.exe', ['/d', '/s', '/c', 'npm prefix -g'], {
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
timeout: 30000,
|
|
32
|
+
windowsHide: true,
|
|
33
|
+
})
|
|
34
|
+
: spawnSync('npm', ['prefix', '-g'], { encoding: 'utf8', timeout: 30000 });
|
|
35
|
+
const prefix = (r.stdout || '').trim();
|
|
36
|
+
if (r.status !== 0 || !prefix) return null;
|
|
37
|
+
return process.platform === 'win32' ? prefix : path.join(prefix, 'bin');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalize(p) {
|
|
41
|
+
return path.normalize(p.trim()).replace(/[\\/]+$/, '').toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function dirOnPath(dir, pathValue) {
|
|
45
|
+
const want = normalize(dir);
|
|
46
|
+
return pathValue
|
|
47
|
+
.split(path.delimiter)
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.some((p) => normalize(p) === want);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// PowerShell one-shot: read raw user Path (unexpanded), append if missing with
|
|
53
|
+
// the same value kind, broadcast WM_SETTINGCHANGE. Prints ADDED/PRESENT.
|
|
54
|
+
function winEnsure(binDir) {
|
|
55
|
+
const script = `
|
|
56
|
+
$bin = ${JSON.stringify(binDir)}
|
|
57
|
+
$key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true)
|
|
58
|
+
$raw = ''
|
|
59
|
+
$kind = [Microsoft.Win32.RegistryValueKind]::ExpandString
|
|
60
|
+
if ($key.GetValueNames() -contains 'Path') {
|
|
61
|
+
$raw = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
|
|
62
|
+
$kind = $key.GetValueKind('Path')
|
|
63
|
+
}
|
|
64
|
+
$parts = @($raw -split ';' | Where-Object { $_ } | ForEach-Object { $_.TrimEnd('\\').ToLower() })
|
|
65
|
+
$expanded = @($parts | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_) })
|
|
66
|
+
$want = $bin.TrimEnd('\\').ToLower()
|
|
67
|
+
if (($parts -contains $want) -or ($expanded -contains $want)) {
|
|
68
|
+
Write-Output 'PRESENT'
|
|
69
|
+
} else {
|
|
70
|
+
$new = if ($raw) { $raw.TrimEnd(';') + ';' + $bin } else { $bin }
|
|
71
|
+
$key.SetValue('Path', $new, $kind)
|
|
72
|
+
$sig = '[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);'
|
|
73
|
+
$w32 = Add-Type -MemberDefinition $sig -Name 'PathBroadcast' -Namespace 'Win32' -PassThru
|
|
74
|
+
[UIntPtr]$res = [UIntPtr]::Zero
|
|
75
|
+
$w32::SendMessageTimeout([IntPtr]0xFFFF, 0x1A, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref]$res) | Out-Null
|
|
76
|
+
Write-Output 'ADDED'
|
|
77
|
+
}
|
|
78
|
+
$key.Close()
|
|
79
|
+
`;
|
|
80
|
+
const out = execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
81
|
+
encoding: 'utf8',
|
|
82
|
+
timeout: 30000,
|
|
83
|
+
}).trim();
|
|
84
|
+
return out.includes('ADDED');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function posixEnsure(binDir) {
|
|
88
|
+
if (dirOnPath(binDir, process.env.PATH || '')) return false;
|
|
89
|
+
const block = `\n${MARKER}\nexport PATH="$PATH:${binDir}"\n# <<< agent-takkub PATH <<<\n`;
|
|
90
|
+
const rcs = [path.join(os.homedir(), '.zshrc')];
|
|
91
|
+
const bashrc = path.join(os.homedir(), '.bashrc');
|
|
92
|
+
if (fs.existsSync(bashrc)) rcs.push(bashrc);
|
|
93
|
+
let added = false;
|
|
94
|
+
for (const rc of rcs) {
|
|
95
|
+
const existing = fs.existsSync(rc) ? fs.readFileSync(rc, 'utf8') : '';
|
|
96
|
+
if (existing.includes(MARKER)) continue;
|
|
97
|
+
fs.writeFileSync(rc, existing + block);
|
|
98
|
+
added = true;
|
|
99
|
+
}
|
|
100
|
+
return added;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Returns true when it CHANGED something (caller prints the restart hint).
|
|
104
|
+
function ensureGlobalBinOnPath() {
|
|
105
|
+
try {
|
|
106
|
+
const binDir = npmGlobalBinDir();
|
|
107
|
+
if (!binDir) return false;
|
|
108
|
+
if (process.platform === 'win32') return winEnsure(binDir);
|
|
109
|
+
return posixEnsure(binDir);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.log(
|
|
112
|
+
`[agent-takkub] PATH check skipped (${e && e.message}) — if 'takkub' is not found later, run: takkub doctor --fix`
|
|
113
|
+
);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = { ensureGlobalBinOnPath, npmGlobalBinDir, dirOnPath };
|
|
@@ -89,6 +89,21 @@ function main() {
|
|
|
89
89
|
|
|
90
90
|
const claudeOk = ensureClaudeCli(env.claudeCli.present);
|
|
91
91
|
|
|
92
|
+
// Keep the npm global bin dir on the persistent PATH — otherwise a broken
|
|
93
|
+
// PATH makes claude/takkub/agent-takkub "command not found" in new shells
|
|
94
|
+
// (field incident 2026-07-04). Best-effort; never fails the install.
|
|
95
|
+
let pathAdded = false;
|
|
96
|
+
try {
|
|
97
|
+
pathAdded = require('./pathfix').ensureGlobalBinOnPath();
|
|
98
|
+
if (pathAdded) {
|
|
99
|
+
console.log(
|
|
100
|
+
'[agent-takkub] ✓ npm global bin dir added to your PATH (open a NEW terminal to use takkub/claude).'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
} catch (_e) {
|
|
104
|
+
/* pathfix already printed its own hint */
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
console.log(`\n[agent-takkub] ✓ cockpit ready (isolated in ${agentTakkubHome()}).`);
|
|
93
108
|
try {
|
|
94
109
|
const sc = require('./shortcut').create();
|