@yuaone/tools 0.1.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 (87) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +15 -0
  3. package/dist/__tests__/file-edit.test.d.ts +8 -0
  4. package/dist/__tests__/file-edit.test.d.ts.map +1 -0
  5. package/dist/__tests__/file-edit.test.js +125 -0
  6. package/dist/__tests__/file-edit.test.js.map +1 -0
  7. package/dist/__tests__/registry.test.d.ts +7 -0
  8. package/dist/__tests__/registry.test.d.ts.map +1 -0
  9. package/dist/__tests__/registry.test.js +83 -0
  10. package/dist/__tests__/registry.test.js.map +1 -0
  11. package/dist/__tests__/validators.test.d.ts +8 -0
  12. package/dist/__tests__/validators.test.d.ts.map +1 -0
  13. package/dist/__tests__/validators.test.js +189 -0
  14. package/dist/__tests__/validators.test.js.map +1 -0
  15. package/dist/base-tool.d.ts +45 -0
  16. package/dist/base-tool.d.ts.map +1 -0
  17. package/dist/base-tool.js +87 -0
  18. package/dist/base-tool.js.map +1 -0
  19. package/dist/browser-tool.d.ts +39 -0
  20. package/dist/browser-tool.d.ts.map +1 -0
  21. package/dist/browser-tool.js +518 -0
  22. package/dist/browser-tool.js.map +1 -0
  23. package/dist/code-search.d.ts +42 -0
  24. package/dist/code-search.d.ts.map +1 -0
  25. package/dist/code-search.js +298 -0
  26. package/dist/code-search.js.map +1 -0
  27. package/dist/design-tools.d.ts +70 -0
  28. package/dist/design-tools.d.ts.map +1 -0
  29. package/dist/design-tools.js +471 -0
  30. package/dist/design-tools.js.map +1 -0
  31. package/dist/dev-server-manager.d.ts +32 -0
  32. package/dist/dev-server-manager.d.ts.map +1 -0
  33. package/dist/dev-server-manager.js +183 -0
  34. package/dist/dev-server-manager.js.map +1 -0
  35. package/dist/file-edit.d.ts +19 -0
  36. package/dist/file-edit.d.ts.map +1 -0
  37. package/dist/file-edit.js +217 -0
  38. package/dist/file-edit.js.map +1 -0
  39. package/dist/file-read.d.ts +19 -0
  40. package/dist/file-read.d.ts.map +1 -0
  41. package/dist/file-read.js +142 -0
  42. package/dist/file-read.js.map +1 -0
  43. package/dist/file-write.d.ts +18 -0
  44. package/dist/file-write.d.ts.map +1 -0
  45. package/dist/file-write.js +139 -0
  46. package/dist/file-write.js.map +1 -0
  47. package/dist/git-ops.d.ts +25 -0
  48. package/dist/git-ops.d.ts.map +1 -0
  49. package/dist/git-ops.js +219 -0
  50. package/dist/git-ops.js.map +1 -0
  51. package/dist/glob.d.ts +18 -0
  52. package/dist/glob.d.ts.map +1 -0
  53. package/dist/glob.js +91 -0
  54. package/dist/glob.js.map +1 -0
  55. package/dist/grep.d.ts +19 -0
  56. package/dist/grep.d.ts.map +1 -0
  57. package/dist/grep.js +177 -0
  58. package/dist/grep.js.map +1 -0
  59. package/dist/index.d.ts +27 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +29 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/security-scan.d.ts +62 -0
  64. package/dist/security-scan.d.ts.map +1 -0
  65. package/dist/security-scan.js +445 -0
  66. package/dist/security-scan.js.map +1 -0
  67. package/dist/shell-exec.d.ts +20 -0
  68. package/dist/shell-exec.d.ts.map +1 -0
  69. package/dist/shell-exec.js +206 -0
  70. package/dist/shell-exec.js.map +1 -0
  71. package/dist/test-run.d.ts +51 -0
  72. package/dist/test-run.d.ts.map +1 -0
  73. package/dist/test-run.js +359 -0
  74. package/dist/test-run.js.map +1 -0
  75. package/dist/tool-registry.d.ts +70 -0
  76. package/dist/tool-registry.d.ts.map +1 -0
  77. package/dist/tool-registry.js +181 -0
  78. package/dist/tool-registry.js.map +1 -0
  79. package/dist/types.d.ts +137 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +8 -0
  82. package/dist/types.js.map +1 -0
  83. package/dist/validators.d.ts +57 -0
  84. package/dist/validators.d.ts.map +1 -0
  85. package/dist/validators.js +218 -0
  86. package/dist/validators.js.map +1 -0
  87. package/package.json +42 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * @yuaone/tools — DevServerManager
3
+ *
4
+ * Detects framework, starts/stops dev servers, detects port from stdout.
5
+ */
6
+ import { spawn } from "node:child_process";
7
+ import { readFile } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+ import { createServer } from "node:net";
10
+ import { EventEmitter } from "node:events";
11
+ const FRAMEWORK_DETECTORS = [
12
+ { key: "next", framework: "nextjs", defaultCommand: "next dev" },
13
+ { key: "vite", framework: "vite", defaultCommand: "vite" },
14
+ { key: "react-scripts", framework: "cra", defaultCommand: "react-scripts start" },
15
+ { key: "astro", framework: "astro", defaultCommand: "astro dev" },
16
+ { key: "svelte", framework: "svelte", defaultCommand: "vite" },
17
+ ];
18
+ const PORT_PATTERNS = [
19
+ /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::\]):(\d+)/,
20
+ /port\s+(\d+)/i,
21
+ /listening\s+(?:on\s+)?(?:port\s+)?(\d+)/i,
22
+ ];
23
+ export class DevServerManager extends EventEmitter {
24
+ process = null;
25
+ state = null;
26
+ async detectFramework(workDir) {
27
+ try {
28
+ const raw = await readFile(join(workDir, "package.json"), "utf8");
29
+ const pkg = JSON.parse(raw);
30
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
31
+ for (const detector of FRAMEWORK_DETECTORS) {
32
+ if (allDeps[detector.key]) {
33
+ const devScript = pkg.scripts?.dev ?? detector.defaultCommand;
34
+ return { framework: detector.framework, devCommand: devScript };
35
+ }
36
+ }
37
+ if (pkg.scripts?.dev) {
38
+ return { framework: "unknown", devCommand: pkg.scripts.dev };
39
+ }
40
+ return { framework: "unknown", devCommand: "npm run dev" };
41
+ }
42
+ catch {
43
+ return { framework: "unknown", devCommand: "npm run dev" };
44
+ }
45
+ }
46
+ async isPortInUse(port) {
47
+ return new Promise((resolve) => {
48
+ const server = createServer();
49
+ server.once("error", () => resolve(true));
50
+ server.once("listening", () => {
51
+ server.close();
52
+ resolve(false);
53
+ });
54
+ server.listen(port, "127.0.0.1");
55
+ });
56
+ }
57
+ async start(workDir, options) {
58
+ const { framework, devCommand } = await this.detectFramework(workDir);
59
+ const command = options?.command ?? devCommand;
60
+ const timeout = options?.timeout ?? 30_000;
61
+ const expectedPort = options?.port ?? 3000;
62
+ if (await this.isPortInUse(expectedPort)) {
63
+ const state = {
64
+ framework,
65
+ command,
66
+ url: `http://localhost:${expectedPort}`,
67
+ port: expectedPort,
68
+ pid: 0,
69
+ managed: false,
70
+ };
71
+ this.state = state;
72
+ this.emit("started", state);
73
+ return state;
74
+ }
75
+ let pm = "npm";
76
+ try {
77
+ await readFile(join(workDir, "pnpm-lock.yaml"), "utf8");
78
+ pm = "pnpm";
79
+ }
80
+ catch {
81
+ try {
82
+ await readFile(join(workDir, "yarn.lock"), "utf8");
83
+ pm = "yarn";
84
+ }
85
+ catch {
86
+ // npm default
87
+ }
88
+ }
89
+ const fullCommand = command.startsWith(pm) ? command : `${pm} run dev`;
90
+ return new Promise((resolve, reject) => {
91
+ const [cmd, ...args] = fullCommand.split(/\s+/);
92
+ this.process = spawn(cmd, args, {
93
+ cwd: workDir,
94
+ stdio: ["pipe", "pipe", "pipe"],
95
+ env: { ...process.env, FORCE_COLOR: "0" },
96
+ shell: true,
97
+ });
98
+ let resolved = false;
99
+ const timer = setTimeout(() => {
100
+ if (!resolved) {
101
+ resolved = true;
102
+ const state = {
103
+ framework,
104
+ command: fullCommand,
105
+ url: `http://localhost:${expectedPort}`,
106
+ port: expectedPort,
107
+ pid: this.process?.pid ?? 0,
108
+ managed: true,
109
+ };
110
+ this.state = state;
111
+ this.emit("started", state);
112
+ resolve(state);
113
+ }
114
+ }, timeout);
115
+ const handleOutput = (data) => {
116
+ const text = data.toString();
117
+ this.emit("stdout", text);
118
+ if (resolved)
119
+ return;
120
+ for (const pattern of PORT_PATTERNS) {
121
+ const match = text.match(pattern);
122
+ if (match) {
123
+ resolved = true;
124
+ clearTimeout(timer);
125
+ const port = parseInt(match[1], 10);
126
+ const state = {
127
+ framework,
128
+ command: fullCommand,
129
+ url: `http://localhost:${port}`,
130
+ port,
131
+ pid: this.process?.pid ?? 0,
132
+ managed: true,
133
+ };
134
+ this.state = state;
135
+ this.emit("started", state);
136
+ resolve(state);
137
+ return;
138
+ }
139
+ }
140
+ };
141
+ this.process.stdout?.on("data", handleOutput);
142
+ this.process.stderr?.on("data", (data) => {
143
+ this.emit("stderr", data.toString());
144
+ handleOutput(data);
145
+ });
146
+ this.process.on("error", (err) => {
147
+ clearTimeout(timer);
148
+ if (!resolved) {
149
+ resolved = true;
150
+ reject(err);
151
+ }
152
+ this.emit("error", err);
153
+ });
154
+ this.process.on("exit", () => {
155
+ this.emit("stopped");
156
+ });
157
+ });
158
+ }
159
+ async stop() {
160
+ if (this.process && this.state?.managed) {
161
+ this.process.kill("SIGTERM");
162
+ await new Promise((resolve) => {
163
+ const timer = setTimeout(() => {
164
+ this.process?.kill("SIGKILL");
165
+ resolve();
166
+ }, 3000);
167
+ this.process?.on("exit", () => {
168
+ clearTimeout(timer);
169
+ resolve();
170
+ });
171
+ });
172
+ }
173
+ this.process = null;
174
+ this.state = null;
175
+ }
176
+ getState() {
177
+ return this.state;
178
+ }
179
+ isRunning() {
180
+ return this.state !== null;
181
+ }
182
+ }
183
+ //# sourceMappingURL=dev-server-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-server-manager.js","sourceRoot":"","sources":["../src/dev-server-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,mBAAmB,GAIpB;IACH,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE;IAChE,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE;IAC1D,EAAE,GAAG,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE;IACjF,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE;IACjE,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE;CAC/D,CAAC;AAEF,MAAM,aAAa,GAAG;IACpB,+DAA+D;IAC/D,eAAe;IACf,0CAA0C;CAC3C,CAAC;AAUF,MAAM,OAAO,gBAAiB,SAAQ,YAAY;IACxC,OAAO,GAAwB,IAAI,CAAC;IACpC,KAAK,GAA0B,IAAI,CAAC;IAE5C,KAAK,CAAC,eAAe,CAAC,OAAe;QAInC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;YAClE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM,OAAO,GAAG,EAAE,GAAG,GAAG,CAAC,YAAY,EAAE,GAAG,GAAG,CAAC,eAAe,EAAE,CAAC;YAEhE,KAAK,MAAM,QAAQ,IAAI,mBAAmB,EAAE,CAAC;gBAC3C,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC1B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,QAAQ,CAAC,cAAc,CAAC;oBAC9D,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;gBAClE,CAAC;YACH,CAAC;YAED,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC;gBACrB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;YAC/D,CAAC;YAED,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAAY;QAC5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;gBAC5B,MAAM,CAAC,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK,CACT,OAAe,EACf,OAA+D;QAE/D,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,UAAU,CAAC;QAC/C,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC;QAC3C,MAAM,YAAY,GAAG,OAAO,EAAE,IAAI,IAAI,IAAI,CAAC;QAE3C,IAAI,MAAM,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC;YACzC,MAAM,KAAK,GAAmB;gBAC5B,SAAS;gBACT,OAAO;gBACP,GAAG,EAAE,oBAAoB,YAAY,EAAE;gBACvC,IAAI,EAAE,YAAY;gBAClB,GAAG,EAAE,CAAC;gBACN,OAAO,EAAE,KAAK;aACf,CAAC;YACF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YAC5B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,EAAE,GAAG,KAAK,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC,CAAC;YACxD,EAAE,GAAG,MAAM,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;gBACnD,EAAE,GAAG,MAAM,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,cAAc;YAChB,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC;QAEvE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAChD,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;gBAC9B,GAAG,EAAE,OAAO;gBACZ,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;gBAC/B,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE;gBACzC,KAAK,EAAE,IAAI;aACZ,CAAC,CAAC;YAEH,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,QAAQ,GAAG,IAAI,CAAC;oBAChB,MAAM,KAAK,GAAmB;wBAC5B,SAAS;wBACT,OAAO,EAAE,WAAW;wBACpB,GAAG,EAAE,oBAAoB,YAAY,EAAE;wBACvC,IAAI,EAAE,YAAY;wBAClB,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;wBAC3B,OAAO,EAAE,IAAI;qBACd,CAAC;oBACF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;oBACnB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;oBAC5B,OAAO,CAAC,KAAK,CAAC,CAAC;gBACjB,CAAC;YACH,CAAC,EAAE,OAAO,CAAC,CAAC;YAEZ,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE;gBACpC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC7B,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;gBAE1B,IAAI,QAAQ;oBAAE,OAAO;gBAErB,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;oBACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBAClC,IAAI,KAAK,EAAE,CAAC;wBACV,QAAQ,GAAG,IAAI,CAAC;wBAChB,YAAY,CAAC,KAAK,CAAC,CAAC;wBACpB,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;wBACpC,MAAM,KAAK,GAAmB;4BAC5B,SAAS;4BACT,OAAO,EAAE,WAAW;4BACpB,GAAG,EAAE,oBAAoB,IAAI,EAAE;4BAC/B,IAAI;4BACJ,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;4BAC3B,OAAO,EAAE,IAAI;yBACd,CAAC;wBACF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;wBACnB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;wBAC5B,OAAO,CAAC,KAAK,CAAC,CAAC;wBACf,OAAO;oBACT,CAAC;gBACH,CAAC;YACH,CAAC,CAAC;YAEF,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;YAC9C,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC/C,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACrC,YAAY,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC/B,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,QAAQ,GAAG,IAAI,CAAC;oBAChB,MAAM,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACvB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;YACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC5B,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;oBAC9B,OAAO,EAAE,CAAC;gBACZ,CAAC,EAAE,IAAI,CAAC,CAAC;gBACT,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;oBAC5B,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @yuaone/tools — file_edit tool
3
+ *
4
+ * Performs exact string replacement in files.
5
+ * - old_string must exist in file
6
+ * - Ambiguity check: if old_string matches multiple times and replace_all=false → error
7
+ * - Generates unified diff preview
8
+ * - Fuzzy match suggestion on failure
9
+ */
10
+ import type { ParameterDef, RiskLevel, ToolResult } from './types.js';
11
+ import { BaseTool } from './base-tool.js';
12
+ export declare class FileEditTool extends BaseTool {
13
+ readonly name = "file_edit";
14
+ readonly description: string;
15
+ readonly riskLevel: RiskLevel;
16
+ readonly parameters: Record<string, ParameterDef>;
17
+ execute(args: Record<string, unknown>, workDir: string): Promise<ToolResult>;
18
+ }
19
+ //# sourceMappingURL=file-edit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-edit.d.ts","sourceRoot":"","sources":["../src/file-edit.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,qBAAa,YAAa,SAAQ,QAAQ;IACxC,QAAQ,CAAC,IAAI,eAAe;IAC5B,QAAQ,CAAC,WAAW,SAEyE;IAC7F,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAY;IAEzC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAsB/C;IAEI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;CAoGnF"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @yuaone/tools — file_edit tool
3
+ *
4
+ * Performs exact string replacement in files.
5
+ * - old_string must exist in file
6
+ * - Ambiguity check: if old_string matches multiple times and replace_all=false → error
7
+ * - Generates unified diff preview
8
+ * - Fuzzy match suggestion on failure
9
+ */
10
+ import { readFile, stat, open as fsOpen } from 'node:fs/promises';
11
+ import { constants as fsConstants } from 'node:fs';
12
+ import { BaseTool } from './base-tool.js';
13
+ import { isSensitiveFile } from './validators.js';
14
+ export class FileEditTool extends BaseTool {
15
+ name = 'file_edit';
16
+ description = 'Edit a file by replacing an exact string match. ' +
17
+ 'Fails if old_string is not found or is ambiguous (multiple matches without replace_all).';
18
+ riskLevel = 'medium';
19
+ parameters = {
20
+ path: {
21
+ type: 'string',
22
+ description: 'Relative path from project root',
23
+ required: true,
24
+ },
25
+ old_string: {
26
+ type: 'string',
27
+ description: 'Exact string to find and replace',
28
+ required: true,
29
+ },
30
+ new_string: {
31
+ type: 'string',
32
+ description: 'Replacement string',
33
+ required: true,
34
+ },
35
+ replace_all: {
36
+ type: 'boolean',
37
+ description: 'Replace all occurrences (default: false)',
38
+ required: false,
39
+ default: false,
40
+ },
41
+ };
42
+ async execute(args, workDir) {
43
+ const toolCallId = args._toolCallId ?? '';
44
+ const path = args.path;
45
+ const oldString = args.old_string;
46
+ const newString = args.new_string;
47
+ const replaceAll = args.replace_all ?? false;
48
+ if (!path)
49
+ return this.fail(toolCallId, 'Missing required parameter: path');
50
+ if (oldString === undefined)
51
+ return this.fail(toolCallId, 'Missing required parameter: old_string');
52
+ if (newString === undefined)
53
+ return this.fail(toolCallId, 'Missing required parameter: new_string');
54
+ let resolvedPath;
55
+ try {
56
+ resolvedPath = this.validatePath(path, workDir);
57
+ }
58
+ catch (err) {
59
+ return this.fail(toolCallId, err.message);
60
+ }
61
+ // Sensitive file check
62
+ if (isSensitiveFile(path)) {
63
+ return this.fail(toolCallId, `Sensitive file detected: "${path}". ` +
64
+ 'Editing credential/secret files is blocked for security.');
65
+ }
66
+ // Check file exists
67
+ try {
68
+ const s = await stat(resolvedPath);
69
+ if (s.isDirectory()) {
70
+ return this.fail(toolCallId, `Path is a directory: ${path}`);
71
+ }
72
+ }
73
+ catch {
74
+ return this.fail(toolCallId, `File not found: ${path}`);
75
+ }
76
+ // Read file
77
+ let content;
78
+ try {
79
+ content = await readFile(resolvedPath, 'utf-8');
80
+ }
81
+ catch (err) {
82
+ return this.fail(toolCallId, `Failed to read file: ${err.message}`);
83
+ }
84
+ // Count occurrences
85
+ const occurrences = countOccurrences(content, oldString);
86
+ if (occurrences === 0) {
87
+ // Try fuzzy match suggestion
88
+ const suggestion = findFuzzyMatch(content, oldString);
89
+ const msg = suggestion
90
+ ? `old_string not found in ${path}. Did you mean:\n${suggestion}`
91
+ : `old_string not found in ${path}. Verify the exact content to replace.`;
92
+ return this.fail(toolCallId, msg);
93
+ }
94
+ if (occurrences > 1 && !replaceAll) {
95
+ return this.fail(toolCallId, `old_string matches ${occurrences} times in ${path}. ` +
96
+ 'Set replace_all=true to replace all occurrences, ' +
97
+ 'or provide a more specific old_string with surrounding context.');
98
+ }
99
+ // Perform replacement
100
+ const newContent = replaceAll
101
+ ? content.split(oldString).join(newString)
102
+ : content.replace(oldString, newString);
103
+ const replacements = replaceAll ? occurrences : 1;
104
+ // Generate preview (unified diff style)
105
+ const preview = generatePreview(content, newContent, path);
106
+ // Write file using O_NOFOLLOW to prevent symlink TOCTOU attacks
107
+ try {
108
+ const fh = await fsOpen(resolvedPath, fsConstants.O_WRONLY | fsConstants.O_TRUNC | fsConstants.O_NOFOLLOW);
109
+ try {
110
+ await fh.writeFile(newContent, 'utf-8');
111
+ }
112
+ finally {
113
+ await fh.close();
114
+ }
115
+ }
116
+ catch (err) {
117
+ const msg = err.code === 'ELOOP'
118
+ ? `Refusing to edit through symlink: ${path}`
119
+ : `Failed to write file: ${err.message}`;
120
+ return this.fail(toolCallId, msg);
121
+ }
122
+ return this.ok(toolCallId, `${replacements} replacement(s) in ${path}\n\n${preview}`, { replacements, preview });
123
+ }
124
+ }
125
+ function countOccurrences(content, search) {
126
+ let count = 0;
127
+ let pos = 0;
128
+ while (true) {
129
+ const idx = content.indexOf(search, pos);
130
+ if (idx === -1)
131
+ break;
132
+ count++;
133
+ pos = idx + search.length;
134
+ }
135
+ return count;
136
+ }
137
+ /**
138
+ * Generate a unified-diff-style preview showing changed lines.
139
+ */
140
+ function generatePreview(oldContent, newContent, filePath) {
141
+ const oldLines = oldContent.split('\n');
142
+ const newLines = newContent.split('\n');
143
+ const diffs = [`--- a/${filePath}`, `+++ b/${filePath}`];
144
+ // Simple line-by-line diff with context
145
+ const contextSize = 3;
146
+ const changedLineIndices = new Set();
147
+ // Find changed ranges
148
+ const maxLen = Math.max(oldLines.length, newLines.length);
149
+ for (let i = 0; i < maxLen; i++) {
150
+ if (oldLines[i] !== newLines[i]) {
151
+ for (let c = Math.max(0, i - contextSize); c <= Math.min(maxLen - 1, i + contextSize); c++) {
152
+ changedLineIndices.add(c);
153
+ }
154
+ }
155
+ }
156
+ if (changedLineIndices.size === 0)
157
+ return 'No changes detected.';
158
+ // Build hunks
159
+ const sortedIndices = [...changedLineIndices].sort((a, b) => a - b);
160
+ if (sortedIndices.length === 0)
161
+ return 'No changes detected.';
162
+ let hunkStart = sortedIndices[0];
163
+ let hunkLines = [];
164
+ for (const idx of sortedIndices) {
165
+ const oldLine = idx < oldLines.length ? oldLines[idx] : undefined;
166
+ const newLine = idx < newLines.length ? newLines[idx] : undefined;
167
+ if (oldLine === newLine) {
168
+ hunkLines.push(` ${oldLine ?? ''}`);
169
+ }
170
+ else {
171
+ if (oldLine !== undefined)
172
+ hunkLines.push(`-${oldLine}`);
173
+ if (newLine !== undefined)
174
+ hunkLines.push(`+${newLine}`);
175
+ }
176
+ }
177
+ diffs.push(`@@ -${hunkStart + 1} @@`);
178
+ diffs.push(...hunkLines);
179
+ // Limit preview size
180
+ const result = diffs.join('\n');
181
+ if (result.length > 2000) {
182
+ return result.slice(0, 2000) + '\n... (preview truncated)';
183
+ }
184
+ return result;
185
+ }
186
+ /**
187
+ * Try to find a fuzzy match in the content when exact match fails.
188
+ * Returns a suggestion string or null.
189
+ */
190
+ function findFuzzyMatch(content, search) {
191
+ // Trim whitespace and try again
192
+ const trimmed = search.trim();
193
+ if (trimmed !== search && content.includes(trimmed)) {
194
+ return `"${trimmed}" (whitespace-trimmed version found)`;
195
+ }
196
+ // Try case-insensitive match
197
+ const lowerContent = content.toLowerCase();
198
+ const lowerSearch = search.toLowerCase();
199
+ const idx = lowerContent.indexOf(lowerSearch);
200
+ if (idx !== -1) {
201
+ const found = content.slice(idx, idx + search.length);
202
+ return `"${found}" (case-insensitive match found)`;
203
+ }
204
+ // Try first line match
205
+ const firstLine = search.split('\n')[0].trim();
206
+ if (firstLine.length > 10) {
207
+ const lineIdx = content.indexOf(firstLine);
208
+ if (lineIdx !== -1) {
209
+ const lineStart = content.lastIndexOf('\n', lineIdx) + 1;
210
+ const lineEnd = content.indexOf('\n', lineIdx + firstLine.length);
211
+ const contextEnd = lineEnd === -1 ? content.length : lineEnd;
212
+ return `First line found at offset ${lineIdx}. Context:\n${content.slice(lineStart, contextEnd)}`;
213
+ }
214
+ }
215
+ return null;
216
+ }
217
+ //# sourceMappingURL=file-edit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-edit.js","sourceRoot":"","sources":["../src/file-edit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @yuaone/tools — file_read tool
3
+ *
4
+ * Reads a file with optional offset/limit.
5
+ * - 50KB size limit (auto-split guidance on overflow)
6
+ * - UTF-8 decoding
7
+ * - Binary file detection and rejection
8
+ * - Image file base64 conversion
9
+ */
10
+ import type { ParameterDef, RiskLevel, ToolResult } from './types.js';
11
+ import { BaseTool } from './base-tool.js';
12
+ export declare class FileReadTool extends BaseTool {
13
+ readonly name = "file_read";
14
+ readonly description: string;
15
+ readonly riskLevel: RiskLevel;
16
+ readonly parameters: Record<string, ParameterDef>;
17
+ execute(args: Record<string, unknown>, workDir: string): Promise<ToolResult>;
18
+ }
19
+ //# sourceMappingURL=file-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-read.d.ts","sourceRoot":"","sources":["../src/file-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK1C,qBAAa,YAAa,SAAQ,QAAQ;IACxC,QAAQ,CAAC,IAAI,eAAe;IAC5B,QAAQ,CAAC,WAAW,SAE0B;IAC9C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAS;IAEtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAgB/C;IAEI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;CAkHnF"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @yuaone/tools — file_read tool
3
+ *
4
+ * Reads a file with optional offset/limit.
5
+ * - 50KB size limit (auto-split guidance on overflow)
6
+ * - UTF-8 decoding
7
+ * - Binary file detection and rejection
8
+ * - Image file base64 conversion
9
+ */
10
+ import { readFile, stat, open as fsOpen } from 'node:fs/promises';
11
+ import { constants as fsConstants } from 'node:fs';
12
+ import { BaseTool } from './base-tool.js';
13
+ import { isBinaryFile, isImageFile, detectLanguage } from './validators.js';
14
+ const MAX_FILE_SIZE = 50_000; // 50KB
15
+ export class FileReadTool extends BaseTool {
16
+ name = 'file_read';
17
+ description = 'Read a file from the project. Returns content with line numbers. ' +
18
+ 'Use offset/limit for large files (>50KB).';
19
+ riskLevel = 'low';
20
+ parameters = {
21
+ path: {
22
+ type: 'string',
23
+ description: 'Relative path from project root',
24
+ required: true,
25
+ },
26
+ offset: {
27
+ type: 'number',
28
+ description: 'Start line number (1-based)',
29
+ required: false,
30
+ },
31
+ limit: {
32
+ type: 'number',
33
+ description: 'Number of lines to read',
34
+ required: false,
35
+ },
36
+ };
37
+ async execute(args, workDir) {
38
+ const toolCallId = args._toolCallId ?? '';
39
+ const path = args.path;
40
+ if (!path) {
41
+ return this.fail(toolCallId, 'Missing required parameter: path');
42
+ }
43
+ let resolvedPath;
44
+ try {
45
+ resolvedPath = this.validatePath(path, workDir);
46
+ }
47
+ catch (err) {
48
+ return this.fail(toolCallId, err.message);
49
+ }
50
+ // Check if file exists and get size
51
+ let fileStat;
52
+ try {
53
+ fileStat = await stat(resolvedPath);
54
+ }
55
+ catch {
56
+ return this.fail(toolCallId, `File not found: ${path}`);
57
+ }
58
+ if (fileStat.isDirectory()) {
59
+ return this.fail(toolCallId, `Path is a directory, not a file: ${path}`);
60
+ }
61
+ // Image file → base64 (uses O_NOFOLLOW to prevent symlink TOCTOU)
62
+ if (isImageFile(resolvedPath)) {
63
+ try {
64
+ const fh = await fsOpen(resolvedPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
65
+ try {
66
+ const buf = await fh.readFile();
67
+ const b64 = buf.toString('base64');
68
+ const language = detectLanguage(resolvedPath);
69
+ return this.ok(toolCallId, `[base64 image: ${language}]\n${b64}`, {
70
+ totalLines: 0,
71
+ language,
72
+ truncated: false,
73
+ });
74
+ }
75
+ finally {
76
+ await fh.close();
77
+ }
78
+ }
79
+ catch (err) {
80
+ const msg = err.code === 'ELOOP'
81
+ ? `Refusing to read through symlink: ${path}`
82
+ : `Failed to read image: ${err.message}`;
83
+ return this.fail(toolCallId, msg);
84
+ }
85
+ }
86
+ // Binary file → reject
87
+ if (await isBinaryFile(resolvedPath)) {
88
+ return this.fail(toolCallId, `Binary file detected: ${path}. Use a specialized tool or download it directly.`);
89
+ }
90
+ // Check size limit (without offset/limit)
91
+ const offset = args.offset ?? undefined;
92
+ const limit = args.limit ?? undefined;
93
+ if (fileStat.size > MAX_FILE_SIZE && offset === undefined && limit === undefined) {
94
+ const totalLines = await countLines(resolvedPath);
95
+ return this.fail(toolCallId, `File is ${fileStat.size} bytes (limit: ${MAX_FILE_SIZE}). ` +
96
+ `Total lines: ${totalLines}. ` +
97
+ `Use offset and limit parameters to read in chunks.`);
98
+ }
99
+ // Read file using O_NOFOLLOW to atomically prevent symlink TOCTOU attacks
100
+ try {
101
+ const fh = await fsOpen(resolvedPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
102
+ let raw;
103
+ try {
104
+ const buf = await fh.readFile();
105
+ raw = buf.toString('utf-8');
106
+ }
107
+ finally {
108
+ await fh.close();
109
+ }
110
+ const allLines = raw.split('\n');
111
+ const totalLines = allLines.length;
112
+ // Apply offset/limit
113
+ const startLine = offset !== undefined ? Math.max(1, offset) : 1;
114
+ const endLine = limit !== undefined ? startLine + limit - 1 : totalLines;
115
+ const selectedLines = allLines.slice(startLine - 1, endLine);
116
+ // Format with line numbers
117
+ const numbered = selectedLines
118
+ .map((line, i) => `${String(startLine + i).padStart(6, ' ')}\t${line}`)
119
+ .join('\n');
120
+ const truncated = endLine < totalLines || startLine > 1;
121
+ const language = detectLanguage(resolvedPath);
122
+ // Check output size after formatting
123
+ const output = this.truncateOutput(numbered);
124
+ return this.ok(toolCallId, output, {
125
+ totalLines,
126
+ language,
127
+ truncated,
128
+ });
129
+ }
130
+ catch (err) {
131
+ const msg = err.code === 'ELOOP'
132
+ ? `Refusing to read through symlink: ${path}`
133
+ : `Failed to read file: ${err.message}`;
134
+ return this.fail(toolCallId, msg);
135
+ }
136
+ }
137
+ }
138
+ async function countLines(filePath) {
139
+ const content = await readFile(filePath, 'utf-8');
140
+ return content.split('\n').length;
141
+ }
142
+ //# sourceMappingURL=file-read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-read.js","sourceRoot":"","sources":["../src/file-read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @yuaone/tools — file_write tool
3
+ *
4
+ * Writes content to a file.
5
+ * - Auto-creates directories (mkdir -p)
6
+ * - Backs up existing files before overwrite (.yuan-backup)
7
+ * - Detects and warns about sensitive files (.env, credentials, etc.)
8
+ */
9
+ import type { ParameterDef, RiskLevel, ToolResult } from './types.js';
10
+ import { BaseTool } from './base-tool.js';
11
+ export declare class FileWriteTool extends BaseTool {
12
+ readonly name = "file_write";
13
+ readonly description: string;
14
+ readonly riskLevel: RiskLevel;
15
+ readonly parameters: Record<string, ParameterDef>;
16
+ execute(args: Record<string, unknown>, workDir: string): Promise<ToolResult>;
17
+ }
18
+ //# sourceMappingURL=file-write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-write.d.ts","sourceRoot":"","sources":["../src/file-write.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,qBAAa,aAAc,SAAQ,QAAQ;IACzC,QAAQ,CAAC,IAAI,gBAAgB;IAC7B,QAAQ,CAAC,WAAW,SAE0B;IAC9C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAU;IAEvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAiB/C;IAEI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;CA4GnF"}