@oh-my-pi/pi-coding-agent 14.3.0 → 14.4.1

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 (120) hide show
  1. package/CHANGELOG.md +98 -1
  2. package/package.json +7 -7
  3. package/src/autoresearch/prompt.md +1 -1
  4. package/src/commit/agentic/prompts/analyze-file.md +1 -1
  5. package/src/config/model-registry.ts +67 -15
  6. package/src/config/prompt-templates.ts +5 -5
  7. package/src/config/settings-schema.ts +4 -4
  8. package/src/cursor.ts +3 -8
  9. package/src/discovery/helpers.ts +3 -3
  10. package/src/edit/diff.ts +50 -47
  11. package/src/edit/index.ts +86 -57
  12. package/src/edit/line-hash.ts +743 -24
  13. package/src/edit/modes/apply-patch.ts +0 -9
  14. package/src/edit/modes/atom.ts +893 -0
  15. package/src/edit/modes/chunk.ts +14 -24
  16. package/src/edit/modes/hashline.ts +193 -146
  17. package/src/edit/modes/patch.ts +5 -9
  18. package/src/edit/modes/replace.ts +6 -11
  19. package/src/edit/renderer.ts +14 -10
  20. package/src/edit/streaming.ts +50 -16
  21. package/src/exec/bash-executor.ts +2 -4
  22. package/src/export/html/template.generated.ts +1 -1
  23. package/src/export/html/template.js +4 -12
  24. package/src/extensibility/custom-tools/types.ts +2 -0
  25. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  26. package/src/internal-urls/docs-index.generated.ts +2 -2
  27. package/src/lsp/defaults.json +142 -652
  28. package/src/lsp/index.ts +1 -1
  29. package/src/mcp/render.ts +1 -8
  30. package/src/modes/components/assistant-message.ts +4 -0
  31. package/src/modes/components/diff.ts +23 -14
  32. package/src/modes/components/footer.ts +21 -16
  33. package/src/modes/components/session-selector.ts +3 -3
  34. package/src/modes/components/settings-defs.ts +6 -1
  35. package/src/modes/components/todo-reminder.ts +1 -8
  36. package/src/modes/components/tool-execution.ts +1 -4
  37. package/src/modes/controllers/selector-controller.ts +1 -1
  38. package/src/modes/print-mode.ts +8 -0
  39. package/src/prompts/agents/librarian.md +1 -1
  40. package/src/prompts/agents/reviewer.md +4 -4
  41. package/src/prompts/ci-green-request.md +1 -1
  42. package/src/prompts/review-request.md +1 -1
  43. package/src/prompts/system/subagent-system-prompt.md +3 -3
  44. package/src/prompts/system/subagent-yield-reminder.md +11 -0
  45. package/src/prompts/system/system-prompt.md +3 -0
  46. package/src/prompts/tools/ask.md +3 -2
  47. package/src/prompts/tools/ast-edit.md +16 -20
  48. package/src/prompts/tools/ast-grep.md +19 -24
  49. package/src/prompts/tools/atom.md +87 -0
  50. package/src/prompts/tools/chunk-edit.md +37 -161
  51. package/src/prompts/tools/debug.md +4 -5
  52. package/src/prompts/tools/exit-plan-mode.md +4 -5
  53. package/src/prompts/tools/find.md +4 -8
  54. package/src/prompts/tools/github.md +18 -0
  55. package/src/prompts/tools/grep.md +4 -5
  56. package/src/prompts/tools/hashline.md +22 -89
  57. package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
  58. package/src/prompts/tools/inspect-image.md +6 -6
  59. package/src/prompts/tools/lsp.md +1 -1
  60. package/src/prompts/tools/patch.md +12 -19
  61. package/src/prompts/tools/python.md +3 -2
  62. package/src/prompts/tools/read-chunk.md +2 -3
  63. package/src/prompts/tools/read.md +2 -2
  64. package/src/prompts/tools/ssh.md +8 -17
  65. package/src/prompts/tools/todo-write.md +54 -41
  66. package/src/sdk.ts +14 -9
  67. package/src/session/agent-session.ts +25 -2
  68. package/src/session/session-manager.ts +4 -1
  69. package/src/task/executor.ts +43 -48
  70. package/src/task/render.ts +11 -13
  71. package/src/tools/ask.ts +7 -7
  72. package/src/tools/ast-edit.ts +45 -41
  73. package/src/tools/ast-grep.ts +77 -85
  74. package/src/tools/bash.ts +8 -9
  75. package/src/tools/browser.ts +32 -30
  76. package/src/tools/calculator.ts +4 -4
  77. package/src/tools/cancel-job.ts +1 -1
  78. package/src/tools/checkpoint.ts +2 -2
  79. package/src/tools/debug.ts +41 -37
  80. package/src/tools/exit-plan-mode.ts +1 -1
  81. package/src/tools/find.ts +4 -4
  82. package/src/tools/gh-renderer.ts +12 -4
  83. package/src/tools/gh.ts +509 -697
  84. package/src/tools/grep.ts +116 -131
  85. package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
  86. package/src/tools/index.ts +14 -32
  87. package/src/tools/inspect-image.ts +3 -3
  88. package/src/tools/json-tree.ts +114 -114
  89. package/src/tools/match-line-format.ts +8 -7
  90. package/src/tools/notebook.ts +8 -7
  91. package/src/tools/poll-tool.ts +2 -1
  92. package/src/tools/python.ts +9 -23
  93. package/src/tools/read.ts +32 -25
  94. package/src/tools/render-mermaid.ts +1 -1
  95. package/src/tools/render-utils.ts +18 -0
  96. package/src/tools/renderers.ts +2 -2
  97. package/src/tools/report-tool-issue.ts +3 -2
  98. package/src/tools/resolve.ts +1 -1
  99. package/src/tools/review.ts +12 -10
  100. package/src/tools/search-tool-bm25.ts +2 -4
  101. package/src/tools/ssh.ts +4 -4
  102. package/src/tools/todo-write.ts +172 -147
  103. package/src/tools/vim.ts +14 -15
  104. package/src/tools/write.ts +4 -4
  105. package/src/tools/{submit-result.ts → yield.ts} +11 -13
  106. package/src/utils/edit-mode.ts +2 -1
  107. package/src/utils/file-display-mode.ts +10 -5
  108. package/src/utils/git.ts +9 -5
  109. package/src/utils/shell-snapshot.ts +2 -3
  110. package/src/vim/render.ts +4 -4
  111. package/src/prompts/system/subagent-submit-reminder.md +0 -11
  112. package/src/prompts/tools/gh-issue-view.md +0 -11
  113. package/src/prompts/tools/gh-pr-checkout.md +0 -12
  114. package/src/prompts/tools/gh-pr-diff.md +0 -12
  115. package/src/prompts/tools/gh-pr-push.md +0 -12
  116. package/src/prompts/tools/gh-pr-view.md +0 -11
  117. package/src/prompts/tools/gh-repo-view.md +0 -11
  118. package/src/prompts/tools/gh-run-watch.md +0 -12
  119. package/src/prompts/tools/gh-search-issues.md +0 -11
  120. package/src/prompts/tools/gh-search-prs.md +0 -11
package/src/tools/read.ts CHANGED
@@ -8,7 +8,7 @@ import type { Component } from "@oh-my-pi/pi-tui";
8
8
  import { Text } from "@oh-my-pi/pi-tui";
9
9
  import { getRemoteDir, prompt, readImageMetadata, untilAborted } from "@oh-my-pi/pi-utils";
10
10
  import { type Static, Type } from "@sinclair/typebox";
11
- import { computeLineHash } from "../edit/line-hash";
11
+ import { formatHashLines } from "../edit/line-hash";
12
12
  import {
13
13
  type ChunkReadTarget,
14
14
  formatChunkedRead,
@@ -89,19 +89,7 @@ function isRemoteMountPath(absolutePath: string): boolean {
89
89
 
90
90
  function prependLineNumbers(text: string, startNum: number): string {
91
91
  const textLines = text.split("\n");
92
- const lastLineNum = startNum + textLines.length - 1;
93
- const padWidth = String(lastLineNum).length;
94
- return textLines
95
- .map((line, i) => {
96
- const lineNum = String(startNum + i).padStart(padWidth, " ");
97
- return `${lineNum}|${line}`;
98
- })
99
- .join("\n");
100
- }
101
-
102
- function prependHashLines(text: string, startNum: number): string {
103
- const textLines = text.split("\n");
104
- return textLines.map((line, i) => `${startNum + i}#${computeLineHash(startNum + i, line)}:${line}`).join("\n");
92
+ return textLines.map((line, i) => `${startNum + i}|${line}`).join("\n");
105
93
  }
106
94
 
107
95
  function formatTextWithMode(
@@ -110,7 +98,7 @@ function formatTextWithMode(
110
98
  shouldAddHashLines: boolean,
111
99
  shouldAddLineNumbers: boolean,
112
100
  ): string {
113
- if (shouldAddHashLines) return prependHashLines(text, startNum);
101
+ if (shouldAddHashLines) return formatHashLines(text, startNum);
114
102
  if (shouldAddLineNumbers) return prependLineNumbers(text, startNum);
115
103
  return text;
116
104
  }
@@ -369,9 +357,9 @@ function prependSuffixResolutionNotice(text: string, suffixResolution?: { from:
369
357
  }
370
358
 
371
359
  const readSchema = Type.Object({
372
- path: Type.String({ description: "Path or URL to read" }),
373
- sel: Type.Optional(Type.String({ description: "Selector: chunk path, L10-L50, or raw" })),
374
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds", default: 20 })),
360
+ path: Type.String({ description: "path or url", examples: ["src/foo.ts", "https://example.com"] }),
361
+ sel: Type.Optional(Type.String({ description: "line range or mode", examples: ["L50", "L50-L120", "raw"] })),
362
+ timeout: Type.Optional(Type.Number({ description: "timeout in seconds", default: 20 })),
375
363
  });
376
364
 
377
365
  export type ReadToolInput = Static<typeof readSchema>;
@@ -389,6 +377,10 @@ export interface ReadToolDetails {
389
377
  method?: string;
390
378
  notes?: string[];
391
379
  meta?: OutputMeta;
380
+ /** Raw text + start line for user-visible TUI rendering, set when content is text-like.
381
+ * Mirrors the same lines the model receives but without hashline/line-number prefixes,
382
+ * so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
383
+ displayContent?: { text: string; startLine: number };
392
384
  }
393
385
 
394
386
  type ReadParams = ReadToolInput;
@@ -599,9 +591,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
599
591
  sourceInternal?: string;
600
592
  entityLabel: string;
601
593
  ignoreResultLimits?: boolean;
594
+ raw?: boolean;
602
595
  },
603
596
  ): AgentToolResult<ReadToolDetails> {
604
- const displayMode = resolveFileDisplayMode(this.session);
597
+ const displayMode = resolveFileDisplayMode(this.session, { raw: options.raw });
605
598
  const details = options.details ?? {};
606
599
  const allLines = text.split("\n");
607
600
  const totalLines = allLines.length;
@@ -640,8 +633,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
640
633
 
641
634
  const shouldAddHashLines = displayMode.hashLines;
642
635
  const shouldAddLineNumbers = shouldAddHashLines ? false : displayMode.lineNumbers;
643
- const formatText = (content: string, startNum: number): string =>
644
- formatTextWithMode(content, startNum, shouldAddHashLines, shouldAddLineNumbers);
636
+ const formatText = (content: string, startNum: number): string => {
637
+ details.displayContent = { text: content, startLine: startNum };
638
+ return formatTextWithMode(content, startNum, shouldAddHashLines, shouldAddLineNumbers);
639
+ };
645
640
 
646
641
  let outputText: string;
647
642
  let truncationInfo:
@@ -743,6 +738,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
743
738
  limit: number | undefined,
744
739
  resolvedArchivePath: ResolvedArchiveReadPath,
745
740
  signal?: AbortSignal,
741
+ options?: { raw?: boolean },
746
742
  ): Promise<AgentToolResult<ReadToolDetails>> {
747
743
  throwIfAborted(signal);
748
744
  const archive = await openArchive(resolvedArchivePath.absolutePath);
@@ -787,6 +783,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
787
783
  details,
788
784
  sourcePath: resolvedArchivePath.absolutePath,
789
785
  entityLabel: "archive entry",
786
+ raw: options?.raw,
790
787
  });
791
788
  const firstText = result.content.find((content): content is TextContent => content.type === "text");
792
789
  if (firstText) {
@@ -978,7 +975,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
978
975
  const archivePath = await this.#resolveArchiveReadPath(localReadPath, signal);
979
976
  if (archivePath) {
980
977
  const { offset, limit } = selToOffsetLimit(parsed);
981
- return this.#readArchive(readPath, offset, limit, archivePath, signal);
978
+ return this.#readArchive(readPath, offset, limit, archivePath, signal, { raw: parsed.kind === "raw" });
982
979
  }
983
980
 
984
981
  const sqlitePath = await this.#resolveSqliteReadPath(readPath, signal);
@@ -1247,9 +1244,12 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1247
1244
  firstLineExceedsLimit,
1248
1245
  };
1249
1246
 
1250
- const shouldAddHashLines = displayMode.hashLines;
1251
- const shouldAddLineNumbers = shouldAddHashLines ? false : displayMode.lineNumbers;
1247
+ const isRawMode = parsed.kind === "raw";
1248
+ const shouldAddHashLines = !isRawMode && displayMode.hashLines;
1249
+ const shouldAddLineNumbers = isRawMode ? false : shouldAddHashLines ? false : displayMode.lineNumbers;
1250
+ let capturedDisplayContent: { text: string; startLine: number } | undefined;
1252
1251
  const formatText = (text: string, startNum: number): string => {
1252
+ capturedDisplayContent = { text, startLine: startNum };
1253
1253
  return formatTextWithMode(text, startNum, shouldAddHashLines, shouldAddLineNumbers);
1254
1254
  };
1255
1255
 
@@ -1300,6 +1300,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1300
1300
  sourcePath = absolutePath;
1301
1301
  }
1302
1302
 
1303
+ if (capturedDisplayContent) {
1304
+ details.displayContent = capturedDisplayContent;
1305
+ }
1306
+
1303
1307
  content = [{ type: "text", text: outputText }];
1304
1308
  }
1305
1309
 
@@ -1494,7 +1498,10 @@ export const readToolRenderer = {
1494
1498
  }
1495
1499
 
1496
1500
  const details = result.details;
1497
- const contentText = result.content?.find(c => c.type === "text")?.text ?? "";
1501
+ const rawText = result.content?.find(c => c.type === "text")?.text ?? "";
1502
+ // Prefer structured `displayContent` from details when available so the TUI
1503
+ // shows clean file content (no model-only hashline anchors) without parsing the formatted text.
1504
+ const contentText = details?.displayContent?.text ?? rawText;
1498
1505
  const imageContent = result.content?.find(c => c.type === "image");
1499
1506
  const rawPath = args?.file_path || args?.path || "";
1500
1507
  const filePath = shortenPath(rawPath);
@@ -5,7 +5,7 @@ import renderMermaidDescription from "../prompts/tools/render-mermaid.md" with {
5
5
  import type { ToolSession } from "./index";
6
6
 
7
7
  const renderMermaidSchema = Type.Object({
8
- mermaid: Type.String({ description: "Mermaid graph source text" }),
8
+ mermaid: Type.String({ description: "mermaid source", examples: ["graph TD; A-->B"] }),
9
9
  config: Type.Optional(
10
10
  Type.Object({
11
11
  useAscii: Type.Optional(Type.Boolean()),
@@ -182,6 +182,24 @@ export function formatEmptyMessage(message: string, theme: Theme): string {
182
182
  return `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", message)}`;
183
183
  }
184
184
 
185
+ // =============================================================================
186
+ // Code Frame Formatting
187
+ // =============================================================================
188
+
189
+ export type CodeFrameMarker = "" | " " | "*" | "+" | "-" | ">";
190
+
191
+ export function formatCodeFrameLine(
192
+ marker: CodeFrameMarker,
193
+ lineNumber: string | number,
194
+ content: string,
195
+ lineNumberWidth: number,
196
+ ): string {
197
+ const markerText = marker.trim();
198
+ const lineNumberText = String(lineNumber).trim();
199
+ const gutterText = markerText && lineNumberText ? `${markerText}${lineNumberText}` : lineNumberText || markerText;
200
+ return `${gutterText.padStart(lineNumberWidth + 1, " ")}│${content}`;
201
+ }
202
+
185
203
  // =============================================================================
186
204
  // Tool UI Helpers
187
205
  // =============================================================================
@@ -17,7 +17,7 @@ import { bashToolRenderer } from "./bash";
17
17
  import { calculatorToolRenderer } from "./calculator";
18
18
  import { debugToolRenderer } from "./debug";
19
19
  import { findToolRenderer } from "./find";
20
- import { ghRunWatchToolRenderer } from "./gh-renderer";
20
+ import { githubToolRenderer } from "./gh-renderer";
21
21
  import { grepToolRenderer } from "./grep";
22
22
  import { inspectImageToolRenderer } from "./inspect-image-renderer";
23
23
  import { notebookToolRenderer } from "./notebook";
@@ -63,7 +63,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
63
63
  ssh: sshToolRenderer as ToolRenderer,
64
64
  task: taskToolRenderer as ToolRenderer,
65
65
  todo_write: todoWriteToolRenderer as ToolRenderer,
66
- gh_run_watch: ghRunWatchToolRenderer as ToolRenderer,
66
+ github: githubToolRenderer as ToolRenderer,
67
67
  web_search: webSearchToolRenderer as ToolRenderer,
68
68
  write: writeToolRenderer as ToolRenderer,
69
69
  };
@@ -14,8 +14,8 @@ import type { Settings } from "..";
14
14
  import type { ToolSession } from "./index";
15
15
 
16
16
  const ReportToolIssueParams = Type.Object({
17
- tool: Type.String({ description: "Name of the tool that behaved unexpectedly" }),
18
- report: Type.String({ description: "Description of what was unexpected about the tool's behavior" }),
17
+ tool: Type.String({ description: "tool name", examples: ["bash", "read"] }),
18
+ report: Type.String({ description: "unexpected behavior" }),
19
19
  });
20
20
 
21
21
  export function isAutoQaEnabled(settings?: Settings): boolean {
@@ -57,6 +57,7 @@ export function createReportToolIssueTool(session: ToolSession): AgentTool {
57
57
  return {
58
58
  name: "report_tool_issue",
59
59
  label: "Report Tool Issue",
60
+ strict: false,
60
61
  description: "Report unexpected tool behavior for automated QA tracking.",
61
62
  parameters: ReportToolIssueParams,
62
63
  async execute(_toolCallId, rawParams) {
@@ -13,7 +13,7 @@ import { ToolError } from "./tool-errors";
13
13
 
14
14
  const resolveSchema = Type.Object({
15
15
  action: Type.Union([Type.Literal("apply"), Type.Literal("discard")]),
16
- reason: Type.String({ description: "Why you're applying or discarding" }),
16
+ reason: Type.String({ description: "reason for action", examples: ["approved by user"] }),
17
17
  });
18
18
 
19
19
  type ResolveParams = Static<typeof resolveSchema>;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Used by the reviewer agent to report findings in a structured way.
5
5
  * Hidden by default - only enabled when explicitly listed in agent's tools.
6
- * Reviewers finish via `submit_result` tool with SubmitReviewDetails schema.
6
+ * Reviewers finish via `yield` tool with SubmitReviewDetails schema.
7
7
  */
8
8
  // ─────────────────────────────────────────────────────────────────────────────
9
9
  // Subprocess tool handlers - registered for extraction/rendering in task tool
@@ -55,22 +55,24 @@ function getPriorityDisplay(
55
55
  // report_finding schema
56
56
  const ReportFindingParams = Type.Object({
57
57
  title: Type.String({
58
- description: "≤80 chars, imperative, prefixed with [P0-P3]. E.g., '[P1] Un-padding slices along wrong dimension'",
58
+ description: "prefixed imperative title",
59
+ examples: ["[P1] un-padding wrong dimension"],
59
60
  }),
60
61
  body: Type.String({
61
- description: "Markdown explaining why this is a problem. One paragraph max.",
62
+ description: "problem explanation",
62
63
  }),
63
64
  priority: StringEnum(["P0", "P1", "P2", "P3"], {
64
- description: "0=P0 (critical), 1=P1 (urgent), 2=P2 (normal), 3=P3 (low)",
65
+ description: "priority 0-3",
65
66
  }),
66
67
  confidence: Type.Number({
67
68
  minimum: 0,
68
69
  maximum: 1,
69
- description: "Confidence score 0.0-1.0",
70
+ description: "confidence score",
71
+ examples: [0.0, 0.5, 1.0],
70
72
  }),
71
- file_path: Type.String({ description: "Absolute path to the file" }),
72
- line_start: Type.Number({ description: "Start line of the issue" }),
73
- line_end: Type.Number({ description: "End line of the issue" }),
73
+ file_path: Type.String({ description: "file path" }),
74
+ line_start: Type.Number({ description: "start line" }),
75
+ line_end: Type.Number({ description: "end line" }),
74
76
  });
75
77
 
76
78
  interface ReportFindingDetails {
@@ -131,7 +133,7 @@ export function parseReportFindingDetails(value: unknown): ReportFindingDetails
131
133
  export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> = {
132
134
  name: "report_finding",
133
135
  label: "Report Finding",
134
- description: "Report a code review finding. Use this for each issue found. Call submit_result when done.",
136
+ description: "Report a code review finding. Use this for each issue found. Call yield when done.",
135
137
  parameters: ReportFindingParams,
136
138
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
137
139
  const { title, body, priority, confidence, file_path, line_start, line_end } = params;
@@ -186,7 +188,7 @@ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFind
186
188
  },
187
189
  };
188
190
 
189
- /** SubmitReviewDetails - used for rendering review results from submit_result tool */
191
+ /** SubmitReviewDetails - used for rendering review results from yield tool */
190
192
  export interface SubmitReviewDetails {
191
193
  overall_correctness: "correct" | "incorrect";
192
194
  explanation: string;
@@ -25,10 +25,8 @@ const MATCH_LABEL_LEN = 72;
25
25
  const MATCH_DESCRIPTION_LEN = 96;
26
26
 
27
27
  const searchToolBm25Schema = Type.Object({
28
- query: Type.String({ description: "Search query for hidden MCP tool metadata" }),
29
- limit: Type.Optional(
30
- Type.Integer({ description: "Max matching tools to activate and return (default 8)", minimum: 1 }),
31
- ),
28
+ query: Type.String({ description: "mcp search query", examples: ["kubernetes pod", "image processing"] }),
29
+ limit: Type.Optional(Type.Integer({ description: "max matches", minimum: 1 })),
32
30
  });
33
31
 
34
32
  type SearchToolBm25Params = Static<typeof searchToolBm25Schema>;
package/src/tools/ssh.ts CHANGED
@@ -22,10 +22,10 @@ import { toolResult } from "./tool-result";
22
22
  import { clampTimeout } from "./tool-timeouts";
23
23
 
24
24
  const sshSchema = Type.Object({
25
- host: Type.String({ description: "Host name from managed SSH config or discovered ssh.json files" }),
26
- command: Type.String({ description: "Command to execute on the remote host" }),
27
- cwd: Type.Optional(Type.String({ description: "Remote working directory (optional)" })),
28
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds", default: 60 })),
25
+ host: Type.String({ description: "ssh host", examples: ["my-server", "prod-1"] }),
26
+ command: Type.String({ description: "remote command", examples: ["ls -la", "uptime"] }),
27
+ cwd: Type.Optional(Type.String({ description: "remote working directory", examples: ["/var/log"] })),
28
+ timeout: Type.Optional(Type.Number({ description: "timeout in seconds", default: 60 })),
29
29
  });
30
30
 
31
31
  export interface SSHToolDetails {