@ottocode/sdk 0.1.304 → 0.1.305

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.304",
3
+ "version": "0.1.305",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -105,7 +105,7 @@
105
105
  "@ai-sdk/provider": "3.0.10",
106
106
  "@ai-sdk/provider-utils": "4.0.27",
107
107
  "@ai-sdk/xai": "3.0.93",
108
- "@ff-labs/fff-bun": "0.9.3",
108
+ "@ff-labs/fff-bun": "0.9.4",
109
109
  "@modelcontextprotocol/sdk": "1.27.1",
110
110
  "@openauthjs/openauth": "0.4.3",
111
111
  "@openrouter/ai-sdk-provider": "1.5.4",
@@ -1,5 +1,5 @@
1
1
  import { join } from 'node:path';
2
- import { spawn, execSync } from 'node:child_process';
2
+ import { spawn, execFileSync } from 'node:child_process';
3
3
  import { homedir } from 'node:os';
4
4
  import {
5
5
  clearCachedBinaries,
@@ -10,7 +10,10 @@ import { fileExists, isExecutable } from './bin-manager/filesystem.ts';
10
10
  import { getAgiBinDir, getBinaryFileName } from './bin-manager/paths.ts';
11
11
  import { extractFromVendor } from './bin-manager/vendor.ts';
12
12
 
13
- let cachedLoginPath: string | null = null;
13
+ let cachedLoginPath: {
14
+ key: string;
15
+ path: string | null;
16
+ } | null = null;
14
17
 
15
18
  export { getAgiBinDir } from './bin-manager/paths.ts';
16
19
 
@@ -64,15 +67,61 @@ export function clearBinaryCache(): void {
64
67
  clearCachedBinaries();
65
68
  }
66
69
 
67
- function getLoginShellPath(): string | null {
68
- if (cachedLoginPath !== null) return cachedLoginPath;
70
+ export function getUserShell(): string {
71
+ if (process.platform === 'win32') return process.env.COMSPEC || 'cmd.exe';
72
+ return process.env.SHELL || '/bin/bash';
73
+ }
74
+
75
+ function getShellRcBootstrap(shell: string): string {
76
+ const shellName = shell.split('/').pop() || '';
77
+ if (shellName.includes('zsh')) {
78
+ return 'if [ -f "$HOME/.zshrc" ]; then . "$HOME/.zshrc"; fi';
79
+ }
80
+ if (shellName.includes('bash')) {
81
+ return 'if [ -f "$HOME/.bashrc" ]; then . "$HOME/.bashrc"; fi';
82
+ }
83
+ return '';
84
+ }
85
+
86
+ function getInteractiveShellFlag(shell: string): string {
87
+ const shellName = shell.split('/').pop() || '';
88
+ if (shellName.includes('bash')) return '-ic';
89
+ return '-ilc';
90
+ }
69
91
 
92
+ export function getShellExecutionConfig(cmd: string): {
93
+ command: string;
94
+ args: string[];
95
+ env: NodeJS.ProcessEnv;
96
+ } {
97
+ const env = { ...process.env, PATH: getAugmentedPath() };
70
98
  if (process.platform === 'win32') {
71
- cachedLoginPath = process.env.PATH || '';
72
- return cachedLoginPath;
99
+ return {
100
+ command: getUserShell(),
101
+ args: ['/d', '/s', '/c', cmd],
102
+ env,
103
+ };
73
104
  }
74
105
 
106
+ const command = getUserShell();
107
+ return {
108
+ command,
109
+ args: [getInteractiveShellFlag(command), 'eval "$OTTO_SHELL_COMMAND"'],
110
+ env: { ...env, OTTO_SHELL_COMMAND: cmd },
111
+ };
112
+ }
113
+
114
+ function getLoginShellPath(): string | null {
75
115
  const home = process.env.HOME || homedir();
116
+ const userShell = getUserShell();
117
+ const cacheKey = [home, userShell, process.env.PATH || ''].join('\0');
118
+ if (cachedLoginPath?.key === cacheKey) return cachedLoginPath.path;
119
+
120
+ if (process.platform === 'win32') {
121
+ cachedLoginPath = { key: cacheKey, path: process.env.PATH || '' };
122
+ return cachedLoginPath.path;
123
+ }
124
+
76
125
  const shellCandidates = [
77
126
  process.env.SHELL,
78
127
  '/bin/zsh',
@@ -82,21 +131,28 @@ function getLoginShellPath(): string | null {
82
131
 
83
132
  for (const shell of shellCandidates) {
84
133
  try {
85
- const result = execSync(`${shell} -ilc 'echo "___PATH___:$PATH"'`, {
134
+ const rcBootstrap = getShellRcBootstrap(shell);
135
+ const pathCommand = `${rcBootstrap ? `${rcBootstrap}\n` : ''}echo "___PATH___:$PATH"`;
136
+ const result = execFileSync(shell, ['-ilc', pathCommand], {
86
137
  timeout: 5000,
87
138
  stdio: ['ignore', 'pipe', 'ignore'],
88
- env: { HOME: home, USER: process.env.USER || '', SHELL: shell },
139
+ env: {
140
+ ...process.env,
141
+ HOME: home,
142
+ USER: process.env.USER || '',
143
+ SHELL: shell,
144
+ },
89
145
  });
90
146
  const output = result.toString();
91
147
  const match = output.match(/___PATH___:(.*)/);
92
148
  if (match?.[1]?.trim()) {
93
- cachedLoginPath = match[1].trim();
94
- return cachedLoginPath;
149
+ cachedLoginPath = { key: cacheKey, path: match[1].trim() };
150
+ return cachedLoginPath.path;
95
151
  }
96
152
  } catch {}
97
153
  }
98
154
 
99
- cachedLoginPath = null;
155
+ cachedLoginPath = { key: cacheKey, path: null };
100
156
  return null;
101
157
  }
102
158
 
@@ -3,7 +3,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import { spawn } from 'node:child_process';
4
4
  import { z } from 'zod/v3';
5
5
  import DESCRIPTION from './shell.txt' with { type: 'text' };
6
- import { getAugmentedPath } from '../bin-manager.ts';
6
+ import { getShellExecutionConfig } from '../bin-manager.ts';
7
7
  import { createToolError, type ToolResponse } from '../error.ts';
8
8
  import { injectCoAuthorIntoGitCommit } from './git-identity.ts';
9
9
 
@@ -47,17 +47,6 @@ export type ShellOutputMode = 'auto' | 'full' | 'tail';
47
47
  const DEFAULT_TAIL_LINES = 100;
48
48
  const DEFAULT_MAX_OUTPUT_BYTES = 128_000;
49
49
 
50
- function looksLikeRepositorySearchCommand(cmd: string): boolean {
51
- const normalized = cmd.replace(/\s+/g, ' ').trim();
52
- return (
53
- /(^|[;&|()]\s*)(rg|ripgrep)(\s|$)/.test(normalized) ||
54
- /(^|[;&|()]\s*)grep\s+.*\s(-R|-r|--recursive)(\s|$)/.test(normalized) ||
55
- /(^|[;&|()]\s*)find\s+(\.|\.\/|\$PWD|\S*\/).*(-name|-iname|-path|-type)/.test(
56
- normalized,
57
- )
58
- );
59
- }
60
-
61
50
  type CompactTextResult = {
62
51
  text: string;
63
52
  truncated: boolean;
@@ -147,7 +136,9 @@ const shellInputSchema = z
147
136
  .object({
148
137
  cmd: z
149
138
  .string()
150
- .describe('Non-interactive shell command to run (bash -c <cmd>)'),
139
+ .describe(
140
+ 'Non-interactive shell command to run using the user shell with login/interactive startup loaded',
141
+ ),
151
142
  cwd: z
152
143
  .string()
153
144
  .default('.')
@@ -231,18 +222,6 @@ export function buildShellTool(projectRoot: string): {
231
222
  });
232
223
  }
233
224
 
234
- if (looksLikeRepositorySearchCommand(cmd)) {
235
- return createToolError(
236
- 'This looks like repository discovery. Use the search tool for content/code search or glob for filename/path discovery.',
237
- 'validation',
238
- {
239
- cmd,
240
- suggestion:
241
- 'Use search for file contents, or glob for file and path discovery.',
242
- },
243
- );
244
- }
245
-
246
225
  const absCwd = resolveSafePath(projectRoot, cwd || '.');
247
226
  const finalCmd = injectCoAuthorIntoGitCommit(cmd);
248
227
  const shellExecutor = shellExecutorContext.getStore();
@@ -261,11 +240,11 @@ export function buildShellTool(projectRoot: string): {
261
240
  ) as AsyncIterable<ShellStreamChunk> | ShellResult;
262
241
  }
263
242
 
264
- const proc = spawn(finalCmd, {
243
+ const shellConfig = getShellExecutionConfig(finalCmd);
244
+ const proc = spawn(shellConfig.command, shellConfig.args, {
265
245
  cwd: absCwd,
266
- shell: true,
267
246
  stdio: ['ignore', 'pipe', 'pipe'],
268
- env: { ...process.env, PATH: getAugmentedPath() },
247
+ env: shellConfig.env,
269
248
  detached: true,
270
249
  });
271
250
 
@@ -1,4 +1,4 @@
1
- - Execute a non-interactive shell command using `bash -lc`
1
+ - Execute a non-interactive shell command using the user's shell with login/interactive startup loaded
2
2
  - Returns `stdout`, `stderr`, and `exitCode`
3
3
  - `cwd` is relative to the project root and sandboxed within it
4
4