weifuwu 0.12.0 → 0.13.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.
package/cli.ts ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, writeFile, copyFile, readdir } from 'node:fs/promises'
3
+ import { homedir } from 'node:os'
4
+ import { join, dirname, resolve } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ const __filename = fileURLToPath(import.meta.url)
8
+ const __dirname = dirname(__filename)
9
+
10
+ const pkgRoot = resolve(__dirname, '..')
11
+
12
+ async function cmdSkill() {
13
+ const targetDir = join(homedir(), '.agents', 'skills', 'weifuwu')
14
+ const docsTarget = join(targetDir, 'docs')
15
+
16
+ await mkdir(docsTarget, { recursive: true })
17
+ await copyFile(join(pkgRoot, 'README.md'), join(targetDir, 'SKILL.md'))
18
+
19
+ const docDir = join(pkgRoot, 'docs')
20
+ const entries = await readdir(docDir)
21
+ for (const entry of entries) {
22
+ if (entry.endsWith('.md')) {
23
+ await copyFile(join(docDir, entry), join(docsTarget, entry))
24
+ }
25
+ }
26
+
27
+ console.log('✅ Installed weifuwu skill to ~/.agents/skills/weifuwu/')
28
+ }
29
+
30
+ async function cmdInit(name: string) {
31
+ const targetDir = resolve(process.cwd(), name)
32
+ await mkdir(targetDir, { recursive: true })
33
+
34
+ await writeFile(join(targetDir, 'package.json'), JSON.stringify({
35
+ name,
36
+ type: 'module',
37
+ scripts: {
38
+ dev: 'node --watch app.ts',
39
+ start: 'node app.ts',
40
+ },
41
+ dependencies: {
42
+ weifuwu: 'latest',
43
+ },
44
+ }, null, 2) + '\n')
45
+
46
+ await writeFile(join(targetDir, 'tsconfig.json'), JSON.stringify({
47
+ compilerOptions: {
48
+ target: 'ESNext',
49
+ module: 'NodeNext',
50
+ moduleResolution: 'NodeNext',
51
+ strict: true,
52
+ jsx: 'react-jsx',
53
+ skipLibCheck: true,
54
+ },
55
+ include: ['*.ts'],
56
+ }, null, 2) + '\n')
57
+
58
+ await writeFile(join(targetDir, '.gitignore'), 'node_modules\ndist\n.env\n.sessions\n')
59
+
60
+ await writeFile(join(targetDir, '.env'), 'PORT=3000\n')
61
+
62
+ await writeFile(join(targetDir, 'app.ts'), [
63
+ "import { serve, loadEnv } from 'weifuwu'",
64
+ '',
65
+ "loadEnv()",
66
+ "const port = Number(process.env.PORT) || 3000",
67
+ '',
68
+ "serve((req, ctx) => new Response('Hello, Weifuwu!'), { port })",
69
+ '',
70
+ ].join('\n'))
71
+
72
+ console.log(`✅ Created ${name}/ — cd ${name} && npm install && npm run dev`)
73
+ }
74
+
75
+ const cmd = process.argv[2]
76
+
77
+ if (cmd === 'skill') {
78
+ cmdSkill().catch(console.error)
79
+ } else if (cmd === 'init') {
80
+ const name = process.argv[3]
81
+ if (!name) {
82
+ console.error('Usage: npx weifuwu init <name>')
83
+ process.exit(1)
84
+ }
85
+ cmdInit(name).catch(console.error)
86
+ } else {
87
+ console.log('\nUsage:\n npx weifuwu init <name> Create a new weifuwu project\n npx weifuwu skill Install weifuwu skill to ~/.agents/skills/\n')
88
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+
3
+ // cli.ts
4
+ import { mkdir, writeFile, copyFile, readdir } from "node:fs/promises";
5
+ import { homedir } from "node:os";
6
+ import { join, dirname, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ var __filename = fileURLToPath(import.meta.url);
9
+ var __dirname = dirname(__filename);
10
+ var pkgRoot = resolve(__dirname, "..");
11
+ async function cmdSkill() {
12
+ const targetDir = join(homedir(), ".agents", "skills", "weifuwu");
13
+ const docsTarget = join(targetDir, "docs");
14
+ await mkdir(docsTarget, { recursive: true });
15
+ await copyFile(join(pkgRoot, "README.md"), join(targetDir, "SKILL.md"));
16
+ const docDir = join(pkgRoot, "docs");
17
+ const entries = await readdir(docDir);
18
+ for (const entry of entries) {
19
+ if (entry.endsWith(".md")) {
20
+ await copyFile(join(docDir, entry), join(docsTarget, entry));
21
+ }
22
+ }
23
+ console.log("\u2705 Installed weifuwu skill to ~/.agents/skills/weifuwu/");
24
+ }
25
+ async function cmdInit(name) {
26
+ const targetDir = resolve(process.cwd(), name);
27
+ await mkdir(targetDir, { recursive: true });
28
+ await writeFile(join(targetDir, "package.json"), JSON.stringify({
29
+ name,
30
+ type: "module",
31
+ scripts: {
32
+ dev: "node --watch app.ts",
33
+ start: "node app.ts"
34
+ },
35
+ dependencies: {
36
+ weifuwu: "latest"
37
+ }
38
+ }, null, 2) + "\n");
39
+ await writeFile(join(targetDir, "tsconfig.json"), JSON.stringify({
40
+ compilerOptions: {
41
+ target: "ESNext",
42
+ module: "NodeNext",
43
+ moduleResolution: "NodeNext",
44
+ strict: true,
45
+ jsx: "react-jsx",
46
+ skipLibCheck: true
47
+ },
48
+ include: ["*.ts"]
49
+ }, null, 2) + "\n");
50
+ await writeFile(join(targetDir, ".gitignore"), "node_modules\ndist\n.env\n.sessions\n");
51
+ await writeFile(join(targetDir, ".env"), "PORT=3000\n");
52
+ await writeFile(join(targetDir, "app.ts"), [
53
+ "import { serve, loadEnv } from 'weifuwu'",
54
+ "",
55
+ "loadEnv()",
56
+ "const port = Number(process.env.PORT) || 3000",
57
+ "",
58
+ "serve((req, ctx) => new Response('Hello, Weifuwu!'), { port })",
59
+ ""
60
+ ].join("\n"));
61
+ console.log(`\u2705 Created ${name}/ \u2014 cd ${name} && npm install && npm run dev`);
62
+ }
63
+ var cmd = process.argv[2];
64
+ if (cmd === "skill") {
65
+ cmdSkill().catch(console.error);
66
+ } else if (cmd === "init") {
67
+ const name = process.argv[3];
68
+ if (!name) {
69
+ console.error("Usage: npx weifuwu init <name>");
70
+ process.exit(1);
71
+ }
72
+ cmdInit(name).catch(console.error);
73
+ } else {
74
+ console.log("\nUsage:\n npx weifuwu init <name> Create a new weifuwu project\n npx weifuwu skill Install weifuwu skill to ~/.agents/skills/\n");
75
+ }
package/dist/env.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function loadEnv(path?: string): void;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { Context, Handler, Middleware, ErrorHandler } from './types.ts';
2
+ export { loadEnv } from './env.ts';
2
3
  export { serve, createTestServer } from './serve.ts';
3
4
  export type { ServeOptions, Server } from './serve.ts';
4
5
  export { Router } from './router.ts';
package/dist/index.js CHANGED
@@ -1,3 +1,31 @@
1
+ // env.ts
2
+ import { readFileSync, existsSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ function loadEnv(path2) {
5
+ const filePath = resolve(process.cwd(), path2 ?? ".env");
6
+ if (!existsSync(filePath)) return;
7
+ const content = readFileSync(filePath, "utf-8");
8
+ for (const line of content.split("\n")) {
9
+ const trimmed = line.trim();
10
+ if (!trimmed || trimmed.startsWith("#")) continue;
11
+ const eqIdx = trimmed.indexOf("=");
12
+ if (eqIdx === -1) continue;
13
+ const key = trimmed.slice(0, eqIdx).trim();
14
+ if (!key) continue;
15
+ if (process.env[key] !== void 0) continue;
16
+ let value = trimmed.slice(eqIdx + 1).trim();
17
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
18
+ value = value.slice(1, -1);
19
+ } else {
20
+ const commentIdx = value.indexOf(" #");
21
+ if (commentIdx !== -1) {
22
+ value = value.slice(0, commentIdx).trimEnd();
23
+ }
24
+ }
25
+ process.env[key] = value;
26
+ }
27
+ }
28
+
1
29
  // serve.ts
2
30
  import http from "node:http";
3
31
  async function readBody(req, maxSize) {
@@ -334,13 +362,13 @@ var Router = class _Router {
334
362
  const mw = match.middlewares[index++];
335
363
  return mw(innerReq, ctx2, dispatch);
336
364
  }
337
- return await new Promise((resolve10) => {
365
+ return await new Promise((resolve11) => {
338
366
  try {
339
367
  upgradeSocket(router.wss, req, socket, head, match.handler, ctx2);
340
- resolve10(new Response(null, { status: 101 }));
368
+ resolve11(new Response(null, { status: 101 }));
341
369
  } catch {
342
370
  socket.destroy();
343
- resolve10(new Response("WebSocket upgrade failed", { status: 500 }));
371
+ resolve11(new Response("WebSocket upgrade failed", { status: 500 }));
344
372
  }
345
373
  });
346
374
  };
@@ -530,8 +558,8 @@ function sendHttpResponseOnSocket(socket, response) {
530
558
  import { createElement } from "react";
531
559
  import { renderToReadableStream } from "react-dom/server";
532
560
  import * as esbuild from "esbuild";
533
- import { readdirSync, statSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
534
- import { join, relative, resolve, sep, dirname, basename } from "node:path";
561
+ import { readdirSync, statSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
562
+ import { join, relative, resolve as resolve2, sep, dirname, basename } from "node:path";
535
563
  import { pathToFileURL } from "node:url";
536
564
  import { createHash } from "node:crypto";
537
565
  import vm from "node:vm";
@@ -577,17 +605,17 @@ function resolveAliases() {
577
605
  if (_alias) return _alias;
578
606
  const configFiles = ["tsconfig.json", "jsconfig.json"];
579
607
  for (const file of configFiles) {
580
- const p = resolve(file);
581
- if (existsSync(p)) {
608
+ const p = resolve2(file);
609
+ if (existsSync2(p)) {
582
610
  try {
583
- const config = JSON.parse(readFileSync(p, "utf-8"));
611
+ const config = JSON.parse(readFileSync2(p, "utf-8"));
584
612
  const paths = config.compilerOptions?.paths;
585
613
  if (paths) {
586
614
  const alias = {};
587
615
  for (const [key, values] of Object.entries(paths)) {
588
616
  const cleanKey = key.replace("/*", "");
589
617
  const val = values[0]?.replace("/*", "");
590
- if (val) alias[cleanKey] = resolve(dirname(p), val);
618
+ if (val) alias[cleanKey] = resolve2(dirname(p), val);
591
619
  }
592
620
  _alias = alias;
593
621
  return alias;
@@ -639,9 +667,9 @@ function scanPages(dir) {
639
667
  const pagePath = join(current, "page.tsx");
640
668
  const tsPagePath = join(current, "page.ts");
641
669
  let entryPath = "";
642
- if (existsSync(pagePath)) {
670
+ if (existsSync2(pagePath)) {
643
671
  entryPath = pagePath;
644
- } else if (existsSync(tsPagePath)) {
672
+ } else if (existsSync2(tsPagePath)) {
645
673
  entryPath = tsPagePath;
646
674
  }
647
675
  if (entryPath) {
@@ -650,8 +678,8 @@ function scanPages(dir) {
650
678
  relPath = relPath.replace(/^page\.tsx?$/, "");
651
679
  const route = filePathToRoute(relPath);
652
680
  const layouts = resolveLayouts(current, dir);
653
- const loadPath = existsSync(join(current, "load.ts")) ? join(current, "load.ts") : void 0;
654
- const rPath = existsSync(join(current, "route.ts")) ? join(current, "route.ts") : void 0;
681
+ const loadPath = existsSync2(join(current, "load.ts")) ? join(current, "load.ts") : void 0;
682
+ const rPath = existsSync2(join(current, "route.ts")) ? join(current, "route.ts") : void 0;
655
683
  pages.push({
656
684
  route,
657
685
  entryPath,
@@ -661,7 +689,7 @@ function scanPages(dir) {
661
689
  });
662
690
  } else {
663
691
  const rPath = join(current, "route.ts");
664
- if (existsSync(rPath)) {
692
+ if (existsSync2(rPath)) {
665
693
  let relPath = relative(dir, rPath).replace(sep, "/");
666
694
  relPath = relPath.replace(/\/route\.tsx?$/, "");
667
695
  const route = filePathToRoute(relPath);
@@ -690,7 +718,7 @@ function resolveLayouts(dir, pagesDir) {
690
718
  let current = dir;
691
719
  while (current.startsWith(pagesDir)) {
692
720
  const p = join(current, "layout.tsx");
693
- if (existsSync(p)) {
721
+ if (existsSync2(p)) {
694
722
  layouts.push(p);
695
723
  }
696
724
  const parent = dirname(current);
@@ -750,8 +778,8 @@ var TsxInstance = class {
750
778
  clientBuildParams = /* @__PURE__ */ new Map();
751
779
  clientRouteLog = /* @__PURE__ */ new Set();
752
780
  constructor(options) {
753
- this.uiDir = resolve(options.dir);
754
- this.pagesDir = existsSync(join(this.uiDir, "pages")) ? join(this.uiDir, "pages") : this.uiDir;
781
+ this.uiDir = resolve2(options.dir);
782
+ this.pagesDir = existsSync2(join(this.uiDir, "pages")) ? join(this.uiDir, "pages") : this.uiDir;
755
783
  this.outDir = join(this.uiDir, ".weifuwu", "ssr");
756
784
  this.router = new Router();
757
785
  }
@@ -766,7 +794,7 @@ var TsxInstance = class {
766
794
  if (p.routePath) allFiles.add(p.routePath);
767
795
  }
768
796
  const nfPath = join(this.pagesDir, "not-found.tsx");
769
- const hasNotFound = existsSync(nfPath);
797
+ const hasNotFound = existsSync2(nfPath);
770
798
  if (hasNotFound) {
771
799
  allFiles.add(nfPath);
772
800
  const rootLayouts = resolveLayouts(this.pagesDir, this.pagesDir);
@@ -899,14 +927,14 @@ ${body}`;
899
927
  } catch {
900
928
  return "";
901
929
  }
902
- const inputFile = resolve(this.uiDir, "app.css");
903
- if (!existsSync(inputFile)) {
930
+ const inputFile = resolve2(this.uiDir, "app.css");
931
+ if (!existsSync2(inputFile)) {
904
932
  mkdirSync(this.uiDir, { recursive: true });
905
933
  writeFileSync(inputFile, '@import "tailwindcss"\n', "utf-8");
906
934
  console.log("\u2139 weifuwu/tsx: created " + relative(process.cwd(), inputFile));
907
935
  }
908
936
  try {
909
- let src = readFileSync(inputFile, "utf-8");
937
+ let src = readFileSync2(inputFile, "utf-8");
910
938
  const sourceRel = relative(this.uiDir, this.pagesDir) || ".";
911
939
  const sourcePath = sourceRel === "." ? "./" : `./${sourceRel}/`;
912
940
  src = `@source "${sourcePath}";
@@ -924,7 +952,7 @@ ${src}`;
924
952
  headers: { "content-type": "text/css; charset=utf-8" }
925
953
  }));
926
954
  if (isDev) {
927
- const inputFile = resolve(this.uiDir, "app.css");
955
+ const inputFile = resolve2(this.uiDir, "app.css");
928
956
  chokidar.watch(inputFile, { persistent: false }).on("change", async () => {
929
957
  this.compiledTailwindCss = "";
930
958
  await this.compileTailwind();
@@ -1073,7 +1101,7 @@ ${body}`;
1073
1101
  timeout = null;
1074
1102
  const files = [...pending];
1075
1103
  pending.clear();
1076
- const exists = files.filter((f) => existsSync(f));
1104
+ const exists = files.filter((f) => existsSync2(f));
1077
1105
  const allKnown = exists.every(
1078
1106
  (f) => this.pageModules.has(f) || this.layoutModules.has(f) || this.loadModules.has(f) || this.routeModules.has(f)
1079
1107
  );
@@ -1138,7 +1166,7 @@ ${body}`;
1138
1166
  if (p.routePath) freshFiles.add(p.routePath);
1139
1167
  }
1140
1168
  const nfPath = join(this.pagesDir, "not-found.tsx");
1141
- if (existsSync(nfPath)) {
1169
+ if (existsSync2(nfPath)) {
1142
1170
  freshFiles.add(nfPath);
1143
1171
  const rootLayouts = resolveLayouts(this.pagesDir, this.pagesDir);
1144
1172
  for (const lp of rootLayouts) freshFiles.add(lp);
@@ -1376,9 +1404,9 @@ function auth(options) {
1376
1404
  // static.ts
1377
1405
  import { createHash as createHash2 } from "node:crypto";
1378
1406
  import { open, realpath } from "node:fs/promises";
1379
- import { extname, resolve as resolve2, normalize, sep as sep2 } from "node:path";
1407
+ import { extname, resolve as resolve3, normalize, sep as sep2 } from "node:path";
1380
1408
  function serveStatic(root, options) {
1381
- const rootDir = resolve2(root);
1409
+ const rootDir = resolve3(root);
1382
1410
  const opts = options ?? {};
1383
1411
  return async (req, ctx) => {
1384
1412
  const relativePath = ctx.params["*"] ?? new URL(req.url).pathname.slice(1);
@@ -1386,7 +1414,7 @@ function serveStatic(root, options) {
1386
1414
  if (decoded.includes("..") || decoded.includes("\0")) {
1387
1415
  return new Response("Forbidden", { status: 403 });
1388
1416
  }
1389
- let filePath = normalize(resolve2(rootDir, decoded));
1417
+ let filePath = normalize(resolve3(rootDir, decoded));
1390
1418
  if (!filePath.startsWith(rootDir + sep2) && filePath !== rootDir) {
1391
1419
  return new Response("Forbidden", { status: 403 });
1392
1420
  }
@@ -1402,7 +1430,7 @@ function serveStatic(root, options) {
1402
1430
  if (stat.isDirectory()) {
1403
1431
  await fileHandle.close();
1404
1432
  const indexFile = opts.index ?? "index.html";
1405
- filePath = resolve2(filePath, indexFile);
1433
+ filePath = resolve3(filePath, indexFile);
1406
1434
  if (!filePath.startsWith(rootDir + sep2)) {
1407
1435
  return new Response("Forbidden", { status: 403 });
1408
1436
  }
@@ -5106,14 +5134,14 @@ function forkApp(opts) {
5106
5134
  return { child, port: opts.port };
5107
5135
  }
5108
5136
  function stopProcess(mp, timeout = 1e4) {
5109
- return new Promise((resolve10) => {
5137
+ return new Promise((resolve11) => {
5110
5138
  const timer = setTimeout(() => {
5111
5139
  mp.child.kill("SIGKILL");
5112
- resolve10();
5140
+ resolve11();
5113
5141
  }, timeout);
5114
5142
  mp.child.on("exit", () => {
5115
5143
  clearTimeout(timer);
5116
- resolve10();
5144
+ resolve11();
5117
5145
  });
5118
5146
  mp.child.kill("SIGTERM");
5119
5147
  });
@@ -5672,10 +5700,10 @@ function createBashTool(ctx) {
5672
5700
  return { stdout: "", stderr: "Command denied: potentially dangerous command", exitCode: 1 };
5673
5701
  }
5674
5702
  const cwd = workdir ? `${ctx.workspace}/${workdir}` : ctx.workspace;
5675
- return new Promise((resolve10) => {
5703
+ return new Promise((resolve11) => {
5676
5704
  const child = exec(command, { cwd, timeout: timeout * 1e3, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
5677
5705
  const truncated = stdout.length > 1e6 || stderr.length > 1e6;
5678
- resolve10({
5706
+ resolve11({
5679
5707
  stdout: stdout.slice(0, 1e6),
5680
5708
  stderr: stderr.slice(0, 1e6),
5681
5709
  exitCode: error?.code ?? 0,
@@ -5691,8 +5719,8 @@ function createBashTool(ctx) {
5691
5719
  // opencode/tools/read.ts
5692
5720
  import { tool as tool3 } from "ai";
5693
5721
  import { z as z6 } from "zod";
5694
- import { readFileSync as readFileSync2 } from "node:fs";
5695
- import { resolve as resolve3 } from "node:path";
5722
+ import { readFileSync as readFileSync3 } from "node:fs";
5723
+ import { resolve as resolve4 } from "node:path";
5696
5724
  function createReadTool(ctx) {
5697
5725
  return tool3({
5698
5726
  description: "Read file contents. Supports offset and limit for reading specific line ranges.",
@@ -5702,11 +5730,11 @@ function createReadTool(ctx) {
5702
5730
  limit: z6.number().optional().describe("Number of lines to read")
5703
5731
  }),
5704
5732
  execute: async ({ path: path2, offset, limit }) => {
5705
- const resolved = resolve3(ctx.workspace, path2);
5733
+ const resolved = resolve4(ctx.workspace, path2);
5706
5734
  if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
5707
5735
  return { error: "Path not allowed", content: null, totalLines: 0 };
5708
5736
  }
5709
- const content = readFileSync2(resolved, "utf-8");
5737
+ const content = readFileSync3(resolved, "utf-8");
5710
5738
  const lines = content.split("\n");
5711
5739
  const totalLines = lines.length;
5712
5740
  if (offset !== void 0) {
@@ -5734,7 +5762,7 @@ function createReadTool(ctx) {
5734
5762
  import { tool as tool4 } from "ai";
5735
5763
  import { z as z7 } from "zod";
5736
5764
  import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
5737
- import { resolve as resolve4, dirname as dirname2 } from "node:path";
5765
+ import { resolve as resolve5, dirname as dirname2 } from "node:path";
5738
5766
  function createWriteTool(ctx) {
5739
5767
  return tool4({
5740
5768
  description: "Create or overwrite a file. Parent directories are created automatically.",
@@ -5743,7 +5771,7 @@ function createWriteTool(ctx) {
5743
5771
  content: z7.string().describe("File content")
5744
5772
  }),
5745
5773
  execute: async ({ path: path2, content }) => {
5746
- const resolved = resolve4(ctx.workspace, path2);
5774
+ const resolved = resolve5(ctx.workspace, path2);
5747
5775
  if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
5748
5776
  return { error: "Path not allowed" };
5749
5777
  }
@@ -5757,8 +5785,8 @@ function createWriteTool(ctx) {
5757
5785
  // opencode/tools/edit.ts
5758
5786
  import { tool as tool5 } from "ai";
5759
5787
  import { z as z8 } from "zod";
5760
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
5761
- import { resolve as resolve5 } from "node:path";
5788
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
5789
+ import { resolve as resolve6 } from "node:path";
5762
5790
  function createEditTool(ctx) {
5763
5791
  return tool5({
5764
5792
  description: "Perform exact string replacements in a file. If oldString appears multiple times, provide more surrounding context.",
@@ -5769,11 +5797,11 @@ function createEditTool(ctx) {
5769
5797
  replaceAll: z8.boolean().default(false).describe("Replace all occurrences")
5770
5798
  }),
5771
5799
  execute: async ({ path: path2, oldString, newString, replaceAll }) => {
5772
- const resolved = resolve5(ctx.workspace, path2);
5800
+ const resolved = resolve6(ctx.workspace, path2);
5773
5801
  if (!isPathAllowed(resolved, ctx.workspace, ctx.permissions)) {
5774
5802
  return { error: "Path not allowed" };
5775
5803
  }
5776
- const content = readFileSync3(resolved, "utf-8");
5804
+ const content = readFileSync4(resolved, "utf-8");
5777
5805
  if (replaceAll) {
5778
5806
  if (!content.includes(oldString)) {
5779
5807
  return { error: "oldString not found in file", replaced: 0 };
@@ -5802,8 +5830,8 @@ function createEditTool(ctx) {
5802
5830
  import { tool as tool6 } from "ai";
5803
5831
  import { z as z9 } from "zod";
5804
5832
  import { execSync as execSync2 } from "node:child_process";
5805
- import { resolve as resolve6 } from "node:path";
5806
- import { existsSync as existsSync2 } from "node:fs";
5833
+ import { resolve as resolve7 } from "node:path";
5834
+ import { existsSync as existsSync3 } from "node:fs";
5807
5835
  function createGrepTool(ctx) {
5808
5836
  return tool6({
5809
5837
  description: "Search file contents using regex. Supports file type filtering and context lines.",
@@ -5814,10 +5842,10 @@ function createGrepTool(ctx) {
5814
5842
  context: z9.number().default(0).describe("Number of context lines before and after each match")
5815
5843
  }),
5816
5844
  execute: async ({ pattern, include, path: path2, context }) => {
5817
- const searchDir = path2 ? resolve6(ctx.workspace, path2) : ctx.workspace;
5845
+ const searchDir = path2 ? resolve7(ctx.workspace, path2) : ctx.workspace;
5818
5846
  const contextArg = context > 0 ? `-C ${context}` : "";
5819
5847
  let cmd;
5820
- if (existsSync2("/usr/bin/rg") || existsSync2("/usr/local/bin/rg")) {
5848
+ if (existsSync3("/usr/bin/rg") || existsSync3("/usr/local/bin/rg")) {
5821
5849
  cmd = `rg -n ${contextArg} ${include ? `-g '${include}'` : ""} '${pattern.replace(/'/g, "'\\''")}' '${searchDir}'`;
5822
5850
  } else {
5823
5851
  cmd = `grep -rn ${contextArg} ${include ? `--include='${include}'` : ""} '${pattern.replace(/'/g, "'\\''")}' '${searchDir}'`;
@@ -5840,7 +5868,7 @@ function createGrepTool(ctx) {
5840
5868
  import { tool as tool7 } from "ai";
5841
5869
  import { z as z10 } from "zod";
5842
5870
  import { execSync as execSync3 } from "node:child_process";
5843
- import { resolve as resolve7 } from "node:path";
5871
+ import { resolve as resolve8 } from "node:path";
5844
5872
  function createGlobTool(ctx) {
5845
5873
  return tool7({
5846
5874
  description: "Find files matching a glob pattern.",
@@ -5849,7 +5877,7 @@ function createGlobTool(ctx) {
5849
5877
  path: z10.string().optional().describe("Subdirectory relative to workspace")
5850
5878
  }),
5851
5879
  execute: async ({ pattern, path: path2 }) => {
5852
- const searchDir = path2 ? resolve7(ctx.workspace, path2) : ctx.workspace;
5880
+ const searchDir = path2 ? resolve8(ctx.workspace, path2) : ctx.workspace;
5853
5881
  try {
5854
5882
  const stdout = execSync3(`find '${searchDir}' -name '${pattern.replace(/'/g, "'\\''")}' -not -path '*/node_modules/*' 2>/dev/null | head -200`, {
5855
5883
  timeout: 1e4,
@@ -5903,7 +5931,7 @@ function createQuestionTool(ctx) {
5903
5931
  options: z12.array(z12.string()).optional().describe("Optional multiple choice options")
5904
5932
  }),
5905
5933
  execute: async ({ question, options }, { toolCallId }) => {
5906
- return new Promise((resolve10, reject) => {
5934
+ return new Promise((resolve11, reject) => {
5907
5935
  const timeout = setTimeout(() => {
5908
5936
  ctx.pendingQuestions.delete(toolCallId);
5909
5937
  reject(new Error("Question timed out"));
@@ -5911,7 +5939,7 @@ function createQuestionTool(ctx) {
5911
5939
  ctx.pendingQuestions.set(toolCallId, {
5912
5940
  resolve: (answer) => {
5913
5941
  clearTimeout(timeout);
5914
- resolve10(answer);
5942
+ resolve11(answer);
5915
5943
  },
5916
5944
  reject: (err) => {
5917
5945
  clearTimeout(timeout);
@@ -6194,7 +6222,7 @@ function createWSHandler2(deps) {
6194
6222
  import { readFile } from "node:fs/promises";
6195
6223
  import { glob } from "node:fs/promises";
6196
6224
  import { homedir } from "node:os";
6197
- import { resolve as resolve8 } from "node:path";
6225
+ import { resolve as resolve9 } from "node:path";
6198
6226
  import { parse as parseYaml } from "yaml";
6199
6227
  var SEARCH_DIRS = [
6200
6228
  (ws) => `${ws}/.opencode/skills`,
@@ -6232,7 +6260,7 @@ async function scanDir(dir) {
6232
6260
  try {
6233
6261
  const files = [];
6234
6262
  for await (const entry of glob("*/SKILL.md", { cwd: dir })) {
6235
- const skill = await parseSkillFile(resolve8(dir, entry));
6263
+ const skill = await parseSkillFile(resolve9(dir, entry));
6236
6264
  if (skill) files.push(skill);
6237
6265
  }
6238
6266
  return files;
@@ -6316,17 +6344,17 @@ function health(options) {
6316
6344
 
6317
6345
  // i18n.ts
6318
6346
  import { readFile as readFile2 } from "node:fs/promises";
6319
- import { existsSync as existsSync3 } from "node:fs";
6320
- import { join as join4, resolve as resolve9 } from "node:path";
6347
+ import { existsSync as existsSync4 } from "node:fs";
6348
+ import { join as join4, resolve as resolve10 } from "node:path";
6321
6349
  function i18n(options) {
6322
- const dir = resolve9(options.dir);
6350
+ const dir = resolve10(options.dir);
6323
6351
  const defaultLocale = options.defaultLocale ?? "en";
6324
6352
  const cache = /* @__PURE__ */ new Map();
6325
6353
  async function load(locale) {
6326
6354
  const cached = cache.get(locale);
6327
6355
  if (cached) return cached;
6328
6356
  const filePath = join4(dir, `${locale}.json`);
6329
- if (!existsSync3(filePath)) return {};
6357
+ if (!existsSync4(filePath)) return {};
6330
6358
  try {
6331
6359
  const content = await readFile2(filePath, "utf-8");
6332
6360
  const data = JSON.parse(content);
@@ -6412,6 +6440,7 @@ export {
6412
6440
  graphql,
6413
6441
  health,
6414
6442
  i18n,
6443
+ loadEnv,
6415
6444
  logger,
6416
6445
  mailer,
6417
6446
  messager,
package/docs/agent.md ADDED
@@ -0,0 +1,44 @@
1
+ # AI Agent
2
+
3
+ > [Home](../README.md) → AI Agent
4
+
5
+ ## AI Agent
6
+
7
+ Server-side AI agents with OpenAI-compatible API. Built-in chat, tool-use (tool-calling), and knowledge (RAG) types. Works out of the box with Ollama or any OpenAI-compatible provider.
8
+
9
+ ```ts
10
+ import { agent } from 'weifuwu'
11
+
12
+ const agents = agent({ pg })
13
+
14
+ await agents.migrate()
15
+ app.use('/api', agents.router())
16
+ ```
17
+
18
+ | Type | Description | Execution |
19
+ |------|-------------|-----------|
20
+ | `chat` | Pure conversation | `streamText()` / `generateText()` |
21
+ | `tool-use` | Tool-calling agent | `streamText({ tools })` |
22
+
23
+ ### Knowledge (RAG)
24
+
25
+ Add documents to any agent — `searchKnowledge` tool auto-injected:
26
+
27
+ ```ts
28
+ await agents.addKnowledge(agentId, 'Title', 'Document content...')
29
+ // The agent automatically calls searchKnowledge when answering
30
+ ```
31
+
32
+ ### Streaming
33
+
34
+ ```http
35
+ POST /agents/:id/run { input: "hello", stream: true }
36
+ → event-stream (fullStream SSE: text-delta, tool-call, tool-result, finish)
37
+ ```
38
+
39
+ ### Programmatic API
40
+
41
+ ```ts
42
+ const result = await agents.run(agentId, { input: 'hello', stream: false })
43
+ // { output: "Hello!", elapsed: 1234 }
44
+ ```