agent-sh 0.12.11 → 0.12.13

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.
@@ -16,6 +16,7 @@ import { createToolProtocol } from "./tool-protocol.js";
16
16
  // Core tool factories
17
17
  import { createBashTool } from "./tools/bash.js";
18
18
  import { createPwshTool } from "./tools/pwsh.js";
19
+ import { findBash } from "../executor.js";
19
20
  import { createReadFileTool } from "./tools/read-file.js";
20
21
  import { createWriteFileTool } from "./tools/write-file.js";
21
22
  import { createEditFileTool } from "./tools/edit-file.js";
@@ -606,7 +607,9 @@ export class AgentLoop {
606
607
  }
607
608
  return env;
608
609
  };
609
- this.toolRegistry.register(createBashTool({ getCwd, getEnv, bus: this.bus }));
610
+ if (findBash() !== null) {
611
+ this.toolRegistry.register(createBashTool({ getCwd, getEnv, bus: this.bus }));
612
+ }
610
613
  if (process.platform === "win32") {
611
614
  this.toolRegistry.register(createPwshTool({ getCwd, getEnv, bus: this.bus }));
612
615
  }
@@ -9,7 +9,9 @@ export declare function discoverGlobalSkills(): Skill[];
9
9
  export declare function invalidateGlobalSkillsCache(): void;
10
10
  /**
11
11
  * Discover project-level skills from .agents/skills/ in cwd hierarchy.
12
- * Scans from cwd up to git root.
12
+ * Walks from cwd up to $HOME (or filesystem root if cwd is outside HOME).
13
+ * Git boundaries are ignored — nested repos under a skills-bearing parent
14
+ * would otherwise hide the parent's skills.
13
15
  */
14
16
  export declare function discoverProjectSkills(cwd: string): Skill[];
15
17
  /**
@@ -93,22 +93,6 @@ function scanDir(dir) {
93
93
  }
94
94
  return skills;
95
95
  }
96
- /** Find the git root from a directory. */
97
- function findGitRoot(dir) {
98
- let current = path.resolve(dir);
99
- while (true) {
100
- try {
101
- fs.accessSync(path.join(current, ".git"));
102
- return current;
103
- }
104
- catch {
105
- const parent = path.dirname(current);
106
- if (parent === current)
107
- return null;
108
- current = parent;
109
- }
110
- }
111
- }
112
96
  /** Expand ~ to home directory. */
113
97
  function expandHome(p) {
114
98
  if (p.startsWith("~/") || p === "~") {
@@ -146,16 +130,18 @@ export function invalidateGlobalSkillsCache() {
146
130
  }
147
131
  /**
148
132
  * Discover project-level skills from .agents/skills/ in cwd hierarchy.
149
- * Scans from cwd up to git root.
133
+ * Walks from cwd up to $HOME (or filesystem root if cwd is outside HOME).
134
+ * Git boundaries are ignored — nested repos under a skills-bearing parent
135
+ * would otherwise hide the parent's skills.
150
136
  */
151
137
  export function discoverProjectSkills(cwd) {
152
138
  const seen = new Set();
153
139
  const skills = [];
154
- const gitRoot = findGitRoot(cwd);
140
+ const home = path.resolve(os.homedir());
155
141
  let current = path.resolve(cwd);
156
142
  while (true) {
157
143
  addUnique(skills, scanDir(path.join(current, ".agents", "skills")), seen);
158
- if (gitRoot && current === gitRoot)
144
+ if (current === home)
159
145
  break;
160
146
  const parent = path.dirname(current);
161
147
  if (parent === current)
@@ -470,6 +470,7 @@ export class DeferredLookupProtocol {
470
470
  return [
471
471
  {
472
472
  name: "load_tool",
473
+ displayName: "Load tools",
473
474
  description: "Load extension tool schemas so you can call them natively on the next turn.",
474
475
  input_schema: {
475
476
  type: "object",
@@ -483,6 +484,11 @@ export class DeferredLookupProtocol {
483
484
  required: ["names"],
484
485
  },
485
486
  showOutput: false,
487
+ getDisplayInfo: () => ({ kind: "read" }),
488
+ formatCall: (args) => {
489
+ const names = Array.isArray(args.names) ? args.names : [];
490
+ return names.join(", ");
491
+ },
486
492
  async execute(args) {
487
493
  const names = Array.isArray(args.names) ? args.names : [];
488
494
  if (names.length === 0) {
@@ -1,4 +1,8 @@
1
1
  import { type ChildProcess } from "node:child_process";
2
+ /** Resolve a usable bash binary, or null if none is on PATH.
3
+ * Unix: `/bin/bash` (canonical, present on every Linux/macOS install).
4
+ * Windows: probe via `where bash` so Git Bash users keep working. */
5
+ export declare function findBash(): string | null;
2
6
  export interface ExecutorSession {
3
7
  id: string;
4
8
  command: string;
package/dist/executor.js CHANGED
@@ -1,5 +1,20 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawn, spawnSync } from "node:child_process";
2
2
  import { stripAnsi } from "./utils/ansi.js";
3
+ let cachedBashPath;
4
+ /** Resolve a usable bash binary, or null if none is on PATH.
5
+ * Unix: `/bin/bash` (canonical, present on every Linux/macOS install).
6
+ * Windows: probe via `where bash` so Git Bash users keep working. */
7
+ export function findBash() {
8
+ if (cachedBashPath !== undefined)
9
+ return cachedBashPath;
10
+ if (process.platform !== "win32") {
11
+ cachedBashPath = "/bin/bash";
12
+ return cachedBashPath;
13
+ }
14
+ const r = spawnSync("where", ["bash"], { encoding: "utf-8" });
15
+ cachedBashPath = r.status === 0 ? r.stdout.split(/\r?\n/)[0].trim() || null : null;
16
+ return cachedBashPath;
17
+ }
3
18
  const DEFAULT_TIMEOUT = 60_000;
4
19
  const DEFAULT_MAX_OUTPUT = 256 * 1024; // 256KB
5
20
  /**
@@ -29,9 +44,12 @@ export function executeCommand(opts) {
29
44
  if (v !== undefined)
30
45
  env[k] = v;
31
46
  }
47
+ const bashPath = findBash();
32
48
  let child;
33
49
  try {
34
- child = spawn("/bin/bash", ["-c", opts.command], {
50
+ if (!bashPath)
51
+ throw new Error("bash not found on PATH");
52
+ child = spawn(bashPath, ["-c", opts.command], {
35
53
  stdio: ["ignore", "pipe", "pipe"],
36
54
  cwd: opts.cwd,
37
55
  env,
@@ -61,10 +61,10 @@ export function renderToolCall(tool, width, cwd = process.cwd()) {
61
61
  const lines = [];
62
62
  // Build a compact detail string to append after the title
63
63
  let detail = "";
64
- if (mode === "full" && tool.displayDetail) {
64
+ if (tool.displayDetail) {
65
65
  detail = tool.displayDetail;
66
66
  }
67
- else if (mode === "full") {
67
+ else {
68
68
  if (tool.command) {
69
69
  detail = `$ ${tool.command}`;
70
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.12.11",
3
+ "version": "0.12.13",
4
4
  "description": "A shell-first terminal where AI is one keystroke away",
5
5
  "type": "module",
6
6
  "main": "dist/core.js",