@hncode/hncode 0.4.1 → 0.5.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 (65) hide show
  1. package/README.md +3 -16
  2. package/README_CN.md +1 -15
  3. package/bin/hncode +22 -9
  4. package/package.json +3 -2
  5. package/src/agent.js +145 -33
  6. package/src/bootstrap.js +56 -0
  7. package/src/btw-panel.js +224 -0
  8. package/src/cache.js +3 -2
  9. package/src/cell-buffer.js +173 -0
  10. package/src/ci.js +4 -1
  11. package/src/clipboard.js +202 -43
  12. package/src/colors.js +175 -30
  13. package/src/config.js +376 -34
  14. package/src/context-bar.js +237 -0
  15. package/src/doctor.js +320 -0
  16. package/src/experiments.js +122 -0
  17. package/src/file-history.js +112 -0
  18. package/src/file-tree.js +255 -0
  19. package/src/flush-tick.js +56 -0
  20. package/src/fork.js +44 -0
  21. package/src/fx.js +148 -0
  22. package/src/index.js +123 -11
  23. package/src/keybindings.js +229 -0
  24. package/src/llm.js +355 -34
  25. package/src/lsp.js +401 -0
  26. package/src/notebook.js +232 -0
  27. package/src/output-styles.js +183 -0
  28. package/src/patch.js +248 -0
  29. package/src/permissions.js +130 -1
  30. package/src/plan.js +3 -2
  31. package/src/plugin.js +691 -43
  32. package/src/prompt-presets.js +1 -1
  33. package/src/registry-browser.js +317 -0
  34. package/src/remote.js +289 -0
  35. package/src/rollout.js +151 -0
  36. package/src/row-cache.js +143 -0
  37. package/src/schedule.js +294 -0
  38. package/src/session-approvals.js +86 -0
  39. package/src/session.js +178 -8
  40. package/src/shell-safety.js +473 -0
  41. package/src/side-thread.js +240 -0
  42. package/src/skills.js +27 -0
  43. package/src/stream-buffer.js +350 -0
  44. package/src/subagent-control.js +160 -0
  45. package/src/tasks-browser.js +54 -11
  46. package/src/teleport.js +260 -0
  47. package/src/term-caps.js +411 -0
  48. package/src/term.js +283 -8
  49. package/src/tools/agent-control.js +98 -0
  50. package/src/tools/agent-swarm.js +11 -2
  51. package/src/tools/agent.js +35 -1
  52. package/src/tools/bash.js +17 -3
  53. package/src/tools/diagnostics.js +175 -0
  54. package/src/tools/edit.js +25 -10
  55. package/src/tools/grep.js +62 -20
  56. package/src/tools/ignore.js +38 -14
  57. package/src/tools/index.js +10 -0
  58. package/src/tools/notebook.js +113 -0
  59. package/src/tools/read-media-file.js +10 -3
  60. package/src/tools/shell.js +134 -0
  61. package/src/tui.js +16682 -11665
  62. package/src/web-public/app.css +22 -9
  63. package/src/web-public/app.js +107 -54
  64. package/src/web-public/home.css +3 -3
  65. package/src/web-public/login.css +1 -1
@@ -0,0 +1,134 @@
1
+ // Shell discovery for the Bash tool.
2
+ //
3
+ // The tool used to hardcode `spawn('pwsh', …)`, which relies on `pwsh` being on
4
+ // PATH. On a Windows box with only the built-in Windows PowerShell that name does
5
+ // not resolve at all, so the spawn failed with ENOENT and the whole tool was dead —
6
+ // no fallback, and nothing in the message said why. It only ever worked where the
7
+ // user had installed PowerShell 7 and added it to PATH by hand.
8
+ //
9
+ // Mirrors Claude Code's findPowerShell (src/utils/shell/powershellDetection.ts):
10
+ // prefer pwsh (Core 7+), fall back to powershell (Desktop 5.1), and cache the
11
+ // answer — the probe shells out, so it must not run per command.
12
+ //
13
+ // The EDITION matters beyond picking a binary: 7+ supports the pipeline chain
14
+ // operators (`&&`, `||`) and 5.1 does not, so the prompt's syntax guidance depends
15
+ // on which one was found.
16
+
17
+ import cp from 'node:child_process';
18
+ import fs from 'node:fs';
19
+
20
+ // The two PowerShell families, by the name they are invoked as.
21
+ export const SHELL_CANDIDATES = ['pwsh', 'powershell'];
22
+
23
+ // Last-resort Windows fallback. A Windows box always has cmd.exe (%COMSPEC%),
24
+ // so when neither PowerShell exists the Bash tool must still run SOMETHING
25
+ // rather than failing outright. `cmd` is not a PowerShell, so the syntax
26
+ // guidance and the spawn args differ (see cmdSpawnArgs).
27
+ export function findCmdExe() {
28
+ const fromEnv = process.env.COMSPEC || process.env.ComSpec;
29
+ if (fromEnv) {
30
+ try { if (fs.existsSync(fromEnv)) return fromEnv; } catch { /* fall through to the PATH probe */ }
31
+ }
32
+ if (process.platform === 'win32') {
33
+ return whichSync('cmd') || whichSync('cmd.exe') || 'cmd.exe';
34
+ }
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * Resolve an executable name through PATH without spawning it.
40
+ *
41
+ * `where` on Windows / `which` on POSIX are the platform's own resolvers, so the
42
+ * answer matches what a shell would do. Returns the first absolute path or null.
43
+ */
44
+ function whichSync(name) {
45
+ const probe = process.platform === 'win32' ? 'where' : 'which';
46
+ try {
47
+ const out = cp.execFileSync(probe, [name], {
48
+ stdio: ['ignore', 'pipe', 'ignore'],
49
+ encoding: 'utf8',
50
+ windowsHide: true,
51
+ });
52
+ // `where` can print several matches; take the first non-empty line.
53
+ const first = String(out).split(/\r?\n/).map((l) => l.trim()).filter(Boolean)[0];
54
+ return first || null;
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Find a usable shell: pwsh, then the built-in powershell, then (on Windows)
62
+ * cmd.exe as a last resort. Returns { path, name, edition } or null.
63
+ *
64
+ * `edition` drives syntax guidance — see the header. It is inferred from the NAME
65
+ * rather than by running the binary (a spawn per probe is not worth it, and
66
+ * PowerShell 6, also named `pwsh`, is EOL and not a realistic target, so `pwsh`
67
+ * safely implies 7+). `edition: 'cmd'` means neither PowerShell exists and the
68
+ * Bash tool is running through cmd.exe: it still works, but the command syntax is
69
+ * cmd's, not PowerShell's.
70
+ */
71
+ export function detectShell() {
72
+ for (const name of SHELL_CANDIDATES) {
73
+ const path = whichSync(name);
74
+ if (path) {
75
+ return { path, name, edition: name === 'pwsh' ? 'core' : 'desktop' };
76
+ }
77
+ }
78
+ // No PowerShell at all: on Windows, guarantee cmd.exe so the tool is not dead.
79
+ const cmd = findCmdExe();
80
+ if (cmd) return { path: cmd, name: 'cmd', edition: 'cmd' };
81
+ return null;
82
+ }
83
+
84
+ let cached; // undefined = not probed yet; null = probed, none found
85
+ let cachedEnvKey; // re-probe if PATH changes (a /env PATH=… must take effect)
86
+
87
+ /**
88
+ * Cached detectShell(). The probe spawns a process, so it runs once per PATH.
89
+ * Re-probing when PATH changes means an in-session PATH fix (`/env PATH=…`, which
90
+ * is exactly what a user with a stripped PATH would do) can rescue the tool.
91
+ */
92
+ export function getShell() {
93
+ const key = String(process.env.PATH || '');
94
+ if (cached === undefined || cachedEnvKey !== key) {
95
+ cached = detectShell();
96
+ cachedEnvKey = key;
97
+ }
98
+ return cached;
99
+ }
100
+
101
+ /** Test seam: forget the cached probe. */
102
+ export function resetShellCache() {
103
+ cached = undefined;
104
+ cachedEnvKey = undefined;
105
+ }
106
+
107
+ /**
108
+ * The command + args to run `command` through the detected shell.
109
+ *
110
+ * `-NoProfile` and `-NonInteractive` are not cosmetic: a user profile can print a
111
+ * banner into stdout (polluting the captured result) and can take seconds to load
112
+ * on every single tool call, and `-NonInteractive` stops a command that prompts
113
+ * from hanging the tool forever with no way to answer.
114
+ *
115
+ * When neither PowerShell exists, `edition === 'cmd'` selects cmd.exe args instead
116
+ * (see detectShell). Throws a message naming the fix only when no shell at all
117
+ * was found, instead of letting the caller hit a bare ENOENT.
118
+ */
119
+ export function shellSpawnArgs(command) {
120
+ const shell = getShell();
121
+ if (!shell) {
122
+ throw new Error(
123
+ 'No shell found (tried pwsh, powershell, and cmd.exe). '
124
+ + 'Install PowerShell 7+ or add it to PATH, then retry.',
125
+ );
126
+ }
127
+ if (shell.edition === 'cmd') {
128
+ // cmd.exe: `/d` skips AutoRun commands, `/s` makes `/c` quoting behave
129
+ // predictably. The command is passed as ONE argv element, so Node does the
130
+ // quoting and we do not wrap it ourselves.
131
+ return { bin: shell.path, args: ['/d', '/s', '/c', command], shell };
132
+ }
133
+ return { bin: shell.path, args: ['-NoProfile', '-NonInteractive', '-Command', command], shell };
134
+ }