ryeos-code 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,246 @@
1
+ # rye:signed:2026-02-25T00:02:15Z:a23a1a0375614da586cad7d7dcaa6e27ebe4ddb16efad18e250643bd760bbf66:9SsOH5ZdKvZbv8v4xiFxMBRk4Q3ELA3x1TrMEtzITvI9e4ldiSZVsQbkk5ATYN5EegNOV3G4pvA_zBRKhANFBA==:9fbfabe975fa5a7f
2
+
3
+ """Git operations - status, add, commit, diff, log, branch, checkout, stash, reset, tag."""
4
+
5
+ import argparse
6
+ import json
7
+ import subprocess
8
+ from pathlib import Path
9
+
10
+ __version__ = "1.0.0"
11
+ __tool_type__ = "python"
12
+ __executor_id__ = "rye/core/runtimes/python/script"
13
+ __category__ = "rye/code/git"
14
+ __tool_description__ = "Git operations - status, add, commit, diff, log, branch, checkout, stash, reset, tag"
15
+
16
+ CONFIG_SCHEMA = {
17
+ "type": "object",
18
+ "properties": {
19
+ "action": {
20
+ "type": "string",
21
+ "enum": ["status", "add", "commit", "diff", "log", "branch", "checkout", "stash", "reset", "tag"],
22
+ "description": "Git action to perform",
23
+ },
24
+ "args": {
25
+ "type": "array",
26
+ "items": {"type": "string"},
27
+ "default": [],
28
+ "description": "Arguments for the action (file paths, branch names, etc.)",
29
+ },
30
+ "message": {
31
+ "type": "string",
32
+ "description": "Commit/tag message (required for commit action)",
33
+ },
34
+ "flags": {
35
+ "type": "object",
36
+ "default": {},
37
+ "description": "Flags to pass (e.g. { staged: true, amend: true, create: true })",
38
+ },
39
+ "working_dir": {
40
+ "type": "string",
41
+ "description": "Working directory (relative to project root or absolute)",
42
+ },
43
+ "timeout": {
44
+ "type": "integer",
45
+ "default": 30,
46
+ "description": "Timeout in seconds",
47
+ },
48
+ },
49
+ "required": ["action"],
50
+ }
51
+
52
+ MAX_OUTPUT_BYTES = 51200
53
+ DEFAULT_TIMEOUT = 30
54
+
55
+
56
+ def truncate_output(output: str, max_bytes: int) -> tuple[str, bool]:
57
+ encoded = output.encode("utf-8", errors="replace")
58
+ if len(encoded) <= max_bytes:
59
+ return output, False
60
+
61
+ truncated_bytes = encoded[:max_bytes]
62
+ truncated_str = truncated_bytes.decode("utf-8", errors="replace")
63
+
64
+ truncation_msg = f"\n... [output truncated, {len(encoded)} bytes total]"
65
+ return truncated_str + truncation_msg, True
66
+
67
+
68
+ def build_command(params: dict) -> list[str]:
69
+ action = params["action"]
70
+ args = params.get("args", [])
71
+ flags = params.get("flags", {})
72
+ message = params.get("message")
73
+
74
+ match action:
75
+ case "status":
76
+ cmd = ["git", "status", "--porcelain"]
77
+ if flags.get("long"):
78
+ cmd = ["git", "status"]
79
+ return cmd + args
80
+
81
+ case "add":
82
+ if not args:
83
+ raise ValueError("add requires explicit file paths")
84
+ blocked = {"-A", "--all", "."}
85
+ for a in args:
86
+ if a in blocked:
87
+ raise ValueError("Use explicit file paths instead of '-A', '--all', or '.'")
88
+ return ["git", "add"] + args
89
+
90
+ case "commit":
91
+ if not message:
92
+ raise ValueError("commit requires a message")
93
+ cmd = ["git", "commit", "-m", message]
94
+ if flags.get("no_verify"):
95
+ cmd.append("--no-verify")
96
+ if flags.get("amend"):
97
+ cmd.append("--amend")
98
+ return cmd
99
+
100
+ case "diff":
101
+ cmd = ["git", "diff"]
102
+ if flags.get("staged") or flags.get("cached"):
103
+ cmd.append("--staged")
104
+ return cmd + args
105
+
106
+ case "log":
107
+ max_count = flags.get("max_count", 20)
108
+ cmd = ["git", "log", "--oneline", f"-n{max_count}"]
109
+ if flags.get("format"):
110
+ cmd = ["git", "log", f"--format={flags['format']}", f"-n{max_count}"]
111
+ return cmd + args
112
+
113
+ case "branch":
114
+ if flags.get("delete") and args:
115
+ return ["git", "branch", "-d", args[0]]
116
+ if flags.get("list") or not args:
117
+ cmd = ["git", "branch"]
118
+ if flags.get("all"):
119
+ cmd.append("-a")
120
+ return cmd
121
+ return ["git", "branch", args[0]]
122
+
123
+ case "checkout":
124
+ if not args:
125
+ raise ValueError("checkout requires a branch or file path")
126
+ cmd = ["git", "checkout"]
127
+ if flags.get("create"):
128
+ cmd.append("-b")
129
+ return cmd + args
130
+
131
+ case "stash":
132
+ sub = args[0] if args else "push"
133
+ valid = {"push", "pop", "list", "drop", "apply"}
134
+ if sub not in valid:
135
+ raise ValueError(f"Invalid stash subcommand: {sub}. Valid: {', '.join(sorted(valid))}")
136
+ cmd = ["git", "stash", sub]
137
+ return cmd + args[1:]
138
+
139
+ case "reset":
140
+ cmd = ["git", "reset"]
141
+ if flags.get("hard"):
142
+ cmd.append("--hard")
143
+ elif flags.get("soft"):
144
+ cmd.append("--soft")
145
+ elif flags.get("mixed"):
146
+ cmd.append("--mixed")
147
+ return cmd + args
148
+
149
+ case "tag":
150
+ if flags.get("list") or (flags.get("delete") is None and not args):
151
+ return ["git", "tag", "--list"]
152
+ if flags.get("delete") and args:
153
+ return ["git", "tag", "-d", args[0]]
154
+ if not args:
155
+ raise ValueError("tag requires a tag name")
156
+ cmd = ["git", "tag"]
157
+ if flags.get("message"):
158
+ cmd.extend(["-a", args[0], "-m", flags["message"]])
159
+ else:
160
+ cmd.append(args[0])
161
+ return cmd
162
+
163
+ case _:
164
+ raise ValueError(f"Unknown action: {action}")
165
+
166
+
167
+ def execute(params: dict, project_path: str) -> dict:
168
+ project = Path(project_path).resolve()
169
+ timeout = params.get("timeout", DEFAULT_TIMEOUT)
170
+ working_dir = params.get("working_dir")
171
+
172
+ if working_dir:
173
+ work_path = Path(working_dir)
174
+ if not work_path.is_absolute():
175
+ work_path = project / work_path
176
+ work_path = work_path.resolve()
177
+
178
+ if not work_path.is_relative_to(project):
179
+ return {
180
+ "success": False,
181
+ "error": "Working directory is outside the project workspace",
182
+ }
183
+
184
+ if not work_path.exists():
185
+ return {
186
+ "success": False,
187
+ "error": f"Working directory not found: {work_path}",
188
+ }
189
+ else:
190
+ work_path = project
191
+
192
+ try:
193
+ cmd = build_command(params)
194
+ except ValueError as e:
195
+ return {"success": False, "error": str(e)}
196
+
197
+ try:
198
+ result = subprocess.run(
199
+ cmd,
200
+ capture_output=True,
201
+ text=True,
202
+ cwd=str(work_path),
203
+ timeout=timeout,
204
+ )
205
+
206
+ stdout = result.stdout or ""
207
+ stderr = result.stderr or ""
208
+
209
+ stdout, stdout_truncated = truncate_output(stdout, MAX_OUTPUT_BYTES)
210
+ stderr, stderr_truncated = truncate_output(stderr, MAX_OUTPUT_BYTES)
211
+
212
+ success = result.returncode == 0
213
+
214
+ output_parts = []
215
+ if stdout:
216
+ output_parts.append(stdout)
217
+ if stderr:
218
+ output_parts.append(f"[stderr]\n{stderr}")
219
+
220
+ combined_output = "\n".join(output_parts)
221
+
222
+ return {
223
+ "success": success,
224
+ "output": combined_output,
225
+ "stdout": stdout,
226
+ "stderr": stderr,
227
+ "exit_code": result.returncode,
228
+ "truncated": stdout_truncated or stderr_truncated,
229
+ }
230
+ except subprocess.TimeoutExpired:
231
+ return {
232
+ "success": False,
233
+ "error": f"Command timed out after {timeout} seconds",
234
+ "timeout": timeout,
235
+ }
236
+ except Exception as e:
237
+ return {"success": False, "error": str(e)}
238
+
239
+
240
+ if __name__ == "__main__":
241
+ parser = argparse.ArgumentParser()
242
+ parser.add_argument("--params", required=True)
243
+ parser.add_argument("--project-path", required=True)
244
+ args = parser.parse_args()
245
+ result = execute(json.loads(args.params), args.project_path)
246
+ print(json.dumps(result))
@@ -0,0 +1,438 @@
1
+ // rye:signed:2026-02-25T00:02:14Z:86f089cf310d3ff22be3f65d2768cea1e3c568955b4276605c3911d67bdd8b9e:U6Mjpa8FTWO519-6nqdILwMJIlNu_D6gKS2Uu3AgJmS4mWgI_DWOH-jtNWW8A_WSpRJbXqRiH88cA9CVaeXrAQ==:9fbfabe975fa5a7f
2
+ // rye:unsigned
3
+ import { parseArgs } from "node:util";
4
+ import { spawn } from "node:child_process";
5
+ import { resolve, isAbsolute, extname, relative } from "node:path";
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { pathToFileURL, fileURLToPath } from "node:url";
8
+ import { execSync } from "node:child_process";
9
+ import {
10
+ createMessageConnection,
11
+ StreamMessageReader,
12
+ StreamMessageWriter,
13
+ } from "vscode-jsonrpc/node.js";
14
+
15
+ export const __version__ = "1.0.0";
16
+ export const __tool_type__ = "javascript";
17
+ export const __executor_id__ = "rye/core/runtimes/node/node";
18
+ export const __category__ = "rye/code/lsp";
19
+ export const __tool_description__ =
20
+ "LSP client — go to definition, find references, hover, document symbols, and more via language servers";
21
+
22
+ const OPERATIONS = [
23
+ "goToDefinition",
24
+ "findReferences",
25
+ "hover",
26
+ "documentSymbol",
27
+ "workspaceSymbol",
28
+ "goToImplementation",
29
+ "prepareCallHierarchy",
30
+ "incomingCalls",
31
+ "outgoingCalls",
32
+ ] as const;
33
+
34
+ export const CONFIG_SCHEMA = {
35
+ type: "object",
36
+ properties: {
37
+ operation: {
38
+ type: "string",
39
+ enum: [...OPERATIONS],
40
+ description: "LSP operation to perform",
41
+ },
42
+ file_path: {
43
+ type: "string",
44
+ description: "Path to the file",
45
+ },
46
+ line: {
47
+ type: "integer",
48
+ minimum: 1,
49
+ description: "Line number (1-based)",
50
+ },
51
+ character: {
52
+ type: "integer",
53
+ minimum: 1,
54
+ description: "Character offset (1-based)",
55
+ },
56
+ timeout: {
57
+ type: "integer",
58
+ default: 15,
59
+ description: "Timeout in seconds",
60
+ },
61
+ },
62
+ required: ["operation", "file_path", "line", "character"],
63
+ };
64
+
65
+ const DEFAULT_TIMEOUT = 15;
66
+
67
+ const LANGUAGE_EXTENSIONS: Record<string, string> = {
68
+ ".py": "python",
69
+ ".js": "javascript",
70
+ ".jsx": "javascriptreact",
71
+ ".ts": "typescript",
72
+ ".tsx": "typescriptreact",
73
+ ".mjs": "javascript",
74
+ ".cjs": "javascript",
75
+ ".mts": "typescript",
76
+ ".cts": "typescript",
77
+ ".go": "go",
78
+ ".rs": "rust",
79
+ ".rb": "ruby",
80
+ ".java": "java",
81
+ ".c": "c",
82
+ ".cpp": "cpp",
83
+ ".h": "c",
84
+ ".hpp": "cpp",
85
+ ".vue": "vue",
86
+ ".svelte": "svelte",
87
+ };
88
+
89
+ interface ServerConfig {
90
+ id: string;
91
+ extensions: string[];
92
+ command: string[];
93
+ }
94
+
95
+ const SERVERS: ServerConfig[] = [
96
+ {
97
+ id: "typescript",
98
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
99
+ command: ["typescript-language-server", "--stdio"],
100
+ },
101
+ {
102
+ id: "pyright",
103
+ extensions: [".py"],
104
+ command: ["pyright-langserver", "--stdio"],
105
+ },
106
+ {
107
+ id: "gopls",
108
+ extensions: [".go"],
109
+ command: ["gopls", "serve"],
110
+ },
111
+ {
112
+ id: "rust-analyzer",
113
+ extensions: [".rs"],
114
+ command: ["rust-analyzer"],
115
+ },
116
+ ];
117
+
118
+ interface Params {
119
+ operation: (typeof OPERATIONS)[number];
120
+ file_path: string;
121
+ line: number;
122
+ character: number;
123
+ timeout?: number;
124
+ }
125
+
126
+ interface Result {
127
+ success: boolean;
128
+ output?: string;
129
+ error?: string;
130
+ operation?: string;
131
+ server?: string;
132
+ results?: unknown[];
133
+ }
134
+
135
+ function findServer(filePath: string): ServerConfig | null {
136
+ const ext = extname(filePath).toLowerCase();
137
+ for (const server of SERVERS) {
138
+ if (!server.extensions.includes(ext)) continue;
139
+ try {
140
+ execSync(`which ${server.command[0]}`, {
141
+ encoding: "utf-8",
142
+ stdio: ["pipe", "pipe", "pipe"],
143
+ });
144
+ return server;
145
+ } catch {
146
+ continue;
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+
152
+ function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
153
+ return new Promise((resolve, reject) => {
154
+ const timer = setTimeout(
155
+ () => reject(new Error(`Timed out after ${ms}ms`)),
156
+ ms,
157
+ );
158
+ promise.then(
159
+ (v) => {
160
+ clearTimeout(timer);
161
+ resolve(v);
162
+ },
163
+ (e) => {
164
+ clearTimeout(timer);
165
+ reject(e);
166
+ },
167
+ );
168
+ });
169
+ }
170
+
171
+ async function execute(params: Params, projectPath: string): Promise<Result> {
172
+ const project = resolve(projectPath);
173
+ const operation = params.operation;
174
+ const timeout = (params.timeout ?? DEFAULT_TIMEOUT) * 1000;
175
+
176
+ let filePath = params.file_path;
177
+ if (!isAbsolute(filePath)) filePath = resolve(project, filePath);
178
+
179
+ if (!existsSync(filePath)) {
180
+ return { success: false, error: `File not found: ${filePath}` };
181
+ }
182
+
183
+ const server = findServer(filePath);
184
+ if (!server) {
185
+ return {
186
+ success: false,
187
+ error: `No LSP server available for ${extname(filePath)} files`,
188
+ };
189
+ }
190
+
191
+ const proc = spawn(server.command[0], server.command.slice(1), {
192
+ cwd: project,
193
+ stdio: ["pipe", "pipe", "pipe"],
194
+ });
195
+
196
+ const connection = createMessageConnection(
197
+ new StreamMessageReader(proc.stdout),
198
+ new StreamMessageWriter(proc.stdin),
199
+ );
200
+
201
+ // Suppress unhandled notifications
202
+ connection.onRequest("window/workDoneProgress/create", () => null);
203
+ connection.onRequest("workspace/configuration", () => [{}]);
204
+ connection.onRequest("client/registerCapability", () => {});
205
+ connection.onRequest("client/unregisterCapability", () => {});
206
+ connection.onRequest("workspace/workspaceFolders", () => [
207
+ { name: "workspace", uri: pathToFileURL(project).href },
208
+ ]);
209
+
210
+ connection.listen();
211
+
212
+ try {
213
+ // Initialize
214
+ await withTimeout(
215
+ connection.sendRequest("initialize", {
216
+ rootUri: pathToFileURL(project).href,
217
+ processId: proc.pid,
218
+ workspaceFolders: [
219
+ { name: "workspace", uri: pathToFileURL(project).href },
220
+ ],
221
+ capabilities: {
222
+ workspace: {
223
+ configuration: true,
224
+ didChangeWatchedFiles: { dynamicRegistration: true },
225
+ },
226
+ textDocument: {
227
+ synchronization: { didOpen: true, didChange: true },
228
+ publishDiagnostics: { versionSupport: true },
229
+ },
230
+ },
231
+ }),
232
+ timeout,
233
+ );
234
+
235
+ await connection.sendNotification("initialized", {});
236
+
237
+ // Open file
238
+ const ext = extname(filePath);
239
+ const languageId = LANGUAGE_EXTENSIONS[ext] ?? "plaintext";
240
+ const text = readFileSync(filePath, "utf-8");
241
+ const uri = pathToFileURL(filePath).href;
242
+
243
+ await connection.sendNotification("textDocument/didOpen", {
244
+ textDocument: { uri, languageId, version: 0, text },
245
+ });
246
+
247
+ // Brief delay for server to process
248
+ await new Promise((r) => setTimeout(r, 500));
249
+
250
+ const position = {
251
+ line: params.line - 1,
252
+ character: params.character - 1,
253
+ };
254
+ const textDocument = { uri };
255
+
256
+ // Execute operation
257
+ let results: unknown[];
258
+ switch (operation) {
259
+ case "goToDefinition":
260
+ results = await withTimeout(
261
+ connection
262
+ .sendRequest("textDocument/definition", { textDocument, position })
263
+ .then(normalize),
264
+ timeout,
265
+ );
266
+ break;
267
+ case "findReferences":
268
+ results = await withTimeout(
269
+ connection
270
+ .sendRequest("textDocument/references", {
271
+ textDocument,
272
+ position,
273
+ context: { includeDeclaration: true },
274
+ })
275
+ .then(normalize),
276
+ timeout,
277
+ );
278
+ break;
279
+ case "hover":
280
+ results = await withTimeout(
281
+ connection
282
+ .sendRequest("textDocument/hover", { textDocument, position })
283
+ .then((r) => (r ? [r] : [])),
284
+ timeout,
285
+ );
286
+ break;
287
+ case "documentSymbol":
288
+ results = await withTimeout(
289
+ connection
290
+ .sendRequest("textDocument/documentSymbol", { textDocument })
291
+ .then(normalize),
292
+ timeout,
293
+ );
294
+ break;
295
+ case "workspaceSymbol":
296
+ results = await withTimeout(
297
+ connection
298
+ .sendRequest("workspace/symbol", { query: "" })
299
+ .then(normalize)
300
+ .then((r: any[]) => r.slice(0, 20)),
301
+ timeout,
302
+ );
303
+ break;
304
+ case "goToImplementation":
305
+ results = await withTimeout(
306
+ connection
307
+ .sendRequest("textDocument/implementation", {
308
+ textDocument,
309
+ position,
310
+ })
311
+ .then(normalize),
312
+ timeout,
313
+ );
314
+ break;
315
+ case "prepareCallHierarchy":
316
+ results = await withTimeout(
317
+ connection
318
+ .sendRequest("textDocument/prepareCallHierarchy", {
319
+ textDocument,
320
+ position,
321
+ })
322
+ .then(normalize),
323
+ timeout,
324
+ );
325
+ break;
326
+ case "incomingCalls": {
327
+ const items: any[] = await withTimeout(
328
+ connection
329
+ .sendRequest("textDocument/prepareCallHierarchy", {
330
+ textDocument,
331
+ position,
332
+ })
333
+ .catch(() => []),
334
+ timeout,
335
+ );
336
+ if (!items?.length) {
337
+ results = [];
338
+ } else {
339
+ results = await withTimeout(
340
+ connection
341
+ .sendRequest("callHierarchy/incomingCalls", { item: items[0] })
342
+ .then(normalize),
343
+ timeout,
344
+ );
345
+ }
346
+ break;
347
+ }
348
+ case "outgoingCalls": {
349
+ const items2: any[] = await withTimeout(
350
+ connection
351
+ .sendRequest("textDocument/prepareCallHierarchy", {
352
+ textDocument,
353
+ position,
354
+ })
355
+ .catch(() => []),
356
+ timeout,
357
+ );
358
+ if (!items2?.length) {
359
+ results = [];
360
+ } else {
361
+ results = await withTimeout(
362
+ connection
363
+ .sendRequest("callHierarchy/outgoingCalls", { item: items2[0] })
364
+ .then(normalize),
365
+ timeout,
366
+ );
367
+ }
368
+ break;
369
+ }
370
+ default:
371
+ return { success: false, error: `Unknown operation: ${operation}` };
372
+ }
373
+
374
+ // Convert file URIs to relative paths
375
+ const cleaned = JSON.parse(
376
+ JSON.stringify(results, (key, value) => {
377
+ if (
378
+ key === "uri" &&
379
+ typeof value === "string" &&
380
+ value.startsWith("file://")
381
+ ) {
382
+ try {
383
+ return relative(project, fileURLToPath(value));
384
+ } catch {
385
+ return value;
386
+ }
387
+ }
388
+ return value;
389
+ }),
390
+ );
391
+
392
+ const output =
393
+ cleaned.length === 0
394
+ ? `No results found for ${operation}`
395
+ : JSON.stringify(cleaned, null, 2);
396
+
397
+ const relPath = relative(project, filePath);
398
+ return {
399
+ success: true,
400
+ output,
401
+ operation,
402
+ server: server.id,
403
+ results: cleaned,
404
+ };
405
+ } catch (e: any) {
406
+ return {
407
+ success: false,
408
+ error: e.message ?? String(e),
409
+ operation,
410
+ server: server.id,
411
+ };
412
+ } finally {
413
+ connection.end();
414
+ connection.dispose();
415
+ proc.kill();
416
+ }
417
+ }
418
+
419
+ function normalize(result: unknown): unknown[] {
420
+ if (!result) return [];
421
+ return Array.isArray(result) ? result.filter(Boolean) : [result];
422
+ }
423
+
424
+ // CLI entry point
425
+ const { values } = parseArgs({
426
+ options: {
427
+ params: { type: "string" },
428
+ "project-path": { type: "string" },
429
+ },
430
+ });
431
+
432
+ if (values.params && values["project-path"]) {
433
+ const result = await execute(
434
+ JSON.parse(values.params) as Params,
435
+ values["project-path"],
436
+ );
437
+ console.log(JSON.stringify(result));
438
+ }