@cydm/pie 1.0.7 → 1.0.9

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,36 @@
1
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+
3
+ // src/project-root.ts
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ function hasGitBoundary(directory) {
7
+ const gitPath = path.join(directory, ".git");
8
+ try {
9
+ const stat = fs.statSync(gitPath);
10
+ return stat.isDirectory() || stat.isFile();
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+ function resolveCliProjectRoot(cwd) {
16
+ const resolvedCwd = path.resolve(cwd || process.cwd());
17
+ const filesystemRoot = path.parse(resolvedCwd).root;
18
+ let current = resolvedCwd;
19
+ while (true) {
20
+ if (hasGitBoundary(current)) {
21
+ return current;
22
+ }
23
+ if (current === filesystemRoot) {
24
+ return resolvedCwd;
25
+ }
26
+ const parent = path.dirname(current);
27
+ if (parent === current) {
28
+ return resolvedCwd;
29
+ }
30
+ current = parent;
31
+ }
32
+ }
33
+
34
+ export {
35
+ resolveCliProjectRoot
36
+ };
@@ -4,19 +4,130 @@ import {
4
4
  createCodeIntelTool,
5
5
  createLightweightCodeIntelProvider,
6
6
  createSharedWebFetchTool,
7
+ createSharedWebResearchTool,
7
8
  createSharedWebSearchTool,
8
9
  interpretShellExit,
9
10
  requestInteraction
10
- } from "./chunk-BHNULR7U.js";
11
+ } from "./chunk-EJGQAAKS.js";
11
12
  import {
12
13
  Type
13
14
  } from "./chunk-A5JSJAPK.js";
14
15
 
16
+ // src/config.ts
17
+ import { existsSync, mkdirSync, readFileSync, renameSync } from "fs";
18
+ import { homedir } from "os";
19
+ import { dirname, join } from "path";
20
+ import { fileURLToPath } from "url";
21
+ var __filename = fileURLToPath(import.meta.url);
22
+ var __dirname = dirname(__filename);
23
+ var isBunRuntime = !!process.versions.bun;
24
+ function getPackageDir() {
25
+ const envDir = process.env.PIE_PACKAGE_DIR;
26
+ if (envDir) {
27
+ if (envDir === "~") return homedir();
28
+ if (envDir.startsWith("~/")) return homedir() + envDir.slice(1);
29
+ return envDir;
30
+ }
31
+ let dir = __dirname;
32
+ while (dir !== dirname(dir)) {
33
+ if (existsSync(join(dir, "package.json"))) {
34
+ return dir;
35
+ }
36
+ dir = dirname(dir);
37
+ }
38
+ return __dirname;
39
+ }
40
+ function getThemesDir() {
41
+ const packageDir = getPackageDir();
42
+ const srcOrDist = existsSync(join(packageDir, "src")) ? "src" : "dist";
43
+ return join(packageDir, srcOrDist, "theme");
44
+ }
45
+ function getBuiltinDir() {
46
+ const packageDir = getPackageDir();
47
+ const distBuiltinDir = join(packageDir, "dist", "builtin");
48
+ if (existsSync(distBuiltinDir)) {
49
+ return distBuiltinDir;
50
+ }
51
+ return join(packageDir, "builtin");
52
+ }
53
+ function getPackageJsonPath() {
54
+ return join(getPackageDir(), "package.json");
55
+ }
56
+ var pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
57
+ var CONFIG_DIR_NAME = ".pie";
58
+ var VERSION = pkg.version;
59
+ var ENV_AGENT_DIR = "PIE_CODING_AGENT_DIR";
60
+ function getConfigDir() {
61
+ const envDir = process.env[ENV_AGENT_DIR];
62
+ if (envDir) {
63
+ if (envDir === "~") return homedir();
64
+ if (envDir.startsWith("~/")) return homedir() + envDir.slice(1);
65
+ return envDir;
66
+ }
67
+ return join(homedir(), CONFIG_DIR_NAME);
68
+ }
69
+ function getAgentDir() {
70
+ return getConfigDir();
71
+ }
72
+ function getCustomThemesDir() {
73
+ return join(getConfigDir(), "themes");
74
+ }
75
+ function getSettingsPath() {
76
+ return join(getConfigDir(), "settings.json");
77
+ }
78
+ function getSessionsDir() {
79
+ return join(getConfigDir(), "sessions");
80
+ }
81
+ function getLogsDir() {
82
+ return join(getConfigDir(), "logs");
83
+ }
84
+ function getRuntimeLogPath() {
85
+ return join(getLogsDir(), "pie-cli.log");
86
+ }
87
+ function migrateConfigFromAgentDir() {
88
+ const oldAgentDir = join(homedir(), CONFIG_DIR_NAME, "agent");
89
+ const newConfigDir = getConfigDir();
90
+ if (!existsSync(oldAgentDir)) {
91
+ return;
92
+ }
93
+ if (!existsSync(newConfigDir)) {
94
+ mkdirSync(newConfigDir, { recursive: true });
95
+ }
96
+ const filesToMigrate = [
97
+ "keybindings.json",
98
+ "settings.json",
99
+ "pie-debug.log"
100
+ ];
101
+ for (const file of filesToMigrate) {
102
+ const oldPath = join(oldAgentDir, file);
103
+ const newPath = file === "pie-debug.log" ? getRuntimeLogPath() : join(newConfigDir, file);
104
+ if (existsSync(oldPath) && !existsSync(newPath)) {
105
+ try {
106
+ if (file === "pie-debug.log") {
107
+ mkdirSync(getLogsDir(), { recursive: true });
108
+ }
109
+ renameSync(oldPath, newPath);
110
+ console.log(`[Config] Migrated ${file} to ${newPath}`);
111
+ } catch (e) {
112
+ }
113
+ }
114
+ }
115
+ const oldThemesDir = join(oldAgentDir, "themes");
116
+ const newThemesDir = getCustomThemesDir();
117
+ if (existsSync(oldThemesDir) && !existsSync(newThemesDir)) {
118
+ try {
119
+ renameSync(oldThemesDir, newThemesDir);
120
+ console.log(`[Config] Migrated themes to ${newThemesDir}`);
121
+ } catch (e) {
122
+ }
123
+ }
124
+ }
125
+
15
126
  // src/capabilities/bash/index.ts
16
127
  import { spawn } from "child_process";
17
- import { createWriteStream, existsSync, statSync, writeFileSync } from "fs";
128
+ import { createWriteStream, existsSync as existsSync2, statSync, writeFileSync } from "fs";
18
129
  import { tmpdir } from "os";
19
- import { join } from "path";
130
+ import { join as join2 } from "path";
20
131
  import { randomBytes } from "crypto";
21
132
 
22
133
  // src/capabilities/bash/truncate.ts
@@ -170,7 +281,7 @@ async function requestHighRiskConfirmation(request) {
170
281
  return response.type === "confirm" && response.confirmed === true;
171
282
  }
172
283
  function validateWorkingDirectory(cwd) {
173
- if (!existsSync(cwd)) {
284
+ if (!existsSync2(cwd)) {
174
285
  throw new Error(`Invalid bash cwd: ${cwd} does not exist. Use the current workspace path or pass an existing directory in cwd.`);
175
286
  }
176
287
  let stat;
@@ -189,11 +300,11 @@ function normalizeWorkingDirectory(cwd, defaultCwd) {
189
300
  }
190
301
  function getTempFilePath() {
191
302
  const id = randomBytes(8).toString("hex");
192
- return join(tmpdir(), `pie-bash-${id}.log`);
303
+ return join2(tmpdir(), `pie-bash-${id}.log`);
193
304
  }
194
305
  function finishTempFileStream(stream) {
195
306
  if (!stream) return Promise.resolve();
196
- return new Promise((resolve2, reject) => {
307
+ return new Promise((resolve3, reject) => {
197
308
  const onError = (error) => {
198
309
  stream.off("error", onError);
199
310
  reject(error);
@@ -201,7 +312,7 @@ function finishTempFileStream(stream) {
201
312
  stream.once("error", onError);
202
313
  stream.end(() => {
203
314
  stream.off("error", onError);
204
- resolve2();
315
+ resolve3();
205
316
  });
206
317
  });
207
318
  }
@@ -278,7 +389,7 @@ function createBashTool(defaultCwd, options) {
278
389
  }
279
390
  }
280
391
  const timeoutMs = (timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1e3;
281
- return new Promise((resolve2, reject) => {
392
+ return new Promise((resolve3, reject) => {
282
393
  if (signal?.aborted) {
283
394
  reject(new Error("Operation aborted"));
284
395
  return;
@@ -443,7 +554,7 @@ function createBashTool(defaultCwd, options) {
443
554
  [Recovery hint: ${recoveryHint}]`;
444
555
  }
445
556
  const content = [{ type: "text", text: outputText }];
446
- resolve2({ content, details, isError: timedOut || semantic.isError });
557
+ resolve3({ content, details, isError: timedOut || semantic.isError });
447
558
  } catch (error) {
448
559
  reject(error);
449
560
  }
@@ -653,9 +764,150 @@ function createCodeIntelTool2(options) {
653
764
  });
654
765
  }
655
766
 
767
+ // src/capabilities/rendered-page-reader.ts
768
+ import { spawn as spawn2 } from "node:child_process";
769
+ import { createRequire } from "node:module";
770
+ var require2 = createRequire(import.meta.url);
771
+ function resolvePlaywrightCli() {
772
+ return require2.resolve("@playwright/cli/playwright-cli.js");
773
+ }
774
+ function runPlaywright(args, timeoutMs = 6e4) {
775
+ return new Promise((resolve3, reject) => {
776
+ const child = spawn2(process.execPath, [resolvePlaywrightCli(), ...args], {
777
+ stdio: ["ignore", "pipe", "pipe"],
778
+ shell: false
779
+ });
780
+ let stdout = "";
781
+ let stderr = "";
782
+ const timer = setTimeout(() => {
783
+ child.kill("SIGTERM");
784
+ reject(new Error(`Playwright CLI timed out after ${Math.ceil(timeoutMs / 1e3)} seconds`));
785
+ }, timeoutMs);
786
+ child.stdout.setEncoding("utf8");
787
+ child.stderr.setEncoding("utf8");
788
+ child.stdout.on("data", (chunk) => {
789
+ stdout += chunk;
790
+ });
791
+ child.stderr.on("data", (chunk) => {
792
+ stderr += chunk;
793
+ });
794
+ child.on("error", (error) => {
795
+ clearTimeout(timer);
796
+ reject(error);
797
+ });
798
+ child.on("close", (code) => {
799
+ clearTimeout(timer);
800
+ if (code === 0) resolve3(stdout.trim());
801
+ else reject(new Error((stderr || stdout || `Playwright CLI exited with ${code}`).trim()));
802
+ });
803
+ });
804
+ }
805
+ function linesOf(output, max = 50) {
806
+ return output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, max);
807
+ }
808
+ function parseScreenshotPath(output) {
809
+ const match = output.match(/\(([^)]+\.png)\)/i) || output.match(/([^\s]+\.png)/i);
810
+ return match?.[1];
811
+ }
812
+ function parsePageInfo(raw) {
813
+ const trimmed = raw.trim();
814
+ if (!trimmed) return {};
815
+ try {
816
+ const decoded = trimmed.startsWith('"') ? JSON.parse(trimmed) : trimmed;
817
+ const parsed = typeof decoded === "string" ? JSON.parse(decoded) : decoded;
818
+ if (!parsed || typeof parsed !== "object") return {};
819
+ return {
820
+ finalUrl: typeof parsed.href === "string" ? parsed.href : void 0,
821
+ title: typeof parsed.title === "string" ? parsed.title : void 0,
822
+ text: typeof parsed.text === "string" ? parsed.text : void 0
823
+ };
824
+ } catch {
825
+ return {};
826
+ }
827
+ }
828
+ function createCliRenderedPageReader() {
829
+ return async ({ url }) => {
830
+ const session = `pie-web-research-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
831
+ try {
832
+ await runPlaywright([`-s=${session}`, "open", "about:blank"]);
833
+ await runPlaywright([`-s=${session}`, "goto", url]);
834
+ const [snapshot, pageInfoRaw, screenshotOutput, consoleOutput, networkOutput] = await Promise.all([
835
+ runPlaywright([`-s=${session}`, "snapshot"]),
836
+ runPlaywright([`-s=${session}`, "--raw", "eval", "() => JSON.stringify({ title: document.title, href: location.href, text: document.body ? document.body.innerText : '' })"]),
837
+ runPlaywright([`-s=${session}`, "--raw", "screenshot"]).catch((error) => `screenshot_failed: ${error instanceof Error ? error.message : String(error)}`),
838
+ runPlaywright([`-s=${session}`, "--raw", "console"]).catch((error) => `console_failed: ${error instanceof Error ? error.message : String(error)}`),
839
+ runPlaywright([`-s=${session}`, "--raw", "network"]).catch((error) => `network_failed: ${error instanceof Error ? error.message : String(error)}`)
840
+ ]);
841
+ const pageInfo = parsePageInfo(pageInfoRaw);
842
+ const text = pageInfo.text?.trim() || snapshot.trim();
843
+ if (!text) {
844
+ throw new Error("Playwright snapshot returned no text.");
845
+ }
846
+ return {
847
+ url,
848
+ finalUrl: pageInfo.finalUrl || url,
849
+ title: pageInfo.title,
850
+ text,
851
+ citationAnchors: [{ url: pageInfo.finalUrl || url, title: pageInfo.title, startOffset: 0, endOffset: Math.min(120, text.length) }],
852
+ screenshotPath: parseScreenshotPath(screenshotOutput),
853
+ consoleMessages: linesOf(consoleOutput),
854
+ networkRequests: linesOf(networkOutput)
855
+ };
856
+ } finally {
857
+ runPlaywright([`-s=${session}`, "close"], 1e4).catch(() => void 0);
858
+ }
859
+ };
860
+ }
861
+
862
+ // src/capabilities/web-research-health.ts
863
+ import fs2 from "node:fs";
864
+ import path2 from "node:path";
865
+ function isRecord(value) {
866
+ return !!value && typeof value === "object" && !Array.isArray(value);
867
+ }
868
+ function parseHealthEntry(value) {
869
+ if (!isRecord(value)) return void 0;
870
+ const routeKey = typeof value.routeKey === "string" ? value.routeKey : "";
871
+ if (!routeKey) return void 0;
872
+ return {
873
+ routeKey,
874
+ attempts: Number(value.attempts) || 0,
875
+ successes: Number(value.successes) || 0,
876
+ failures: Number(value.failures) || 0,
877
+ lowQuality: Number(value.lowQuality) || 0,
878
+ rateLimited: Number(value.rateLimited) || 0,
879
+ timeouts: Number(value.timeouts) || 0,
880
+ totalDurationMs: Number(value.totalDurationMs) || 0,
881
+ totalSources: Number(value.totalSources) || 0,
882
+ updatedAt: Number(value.updatedAt) || 0
883
+ };
884
+ }
885
+ function createCliWebResearchHealthStore(configDir) {
886
+ const filePath = path2.join(configDir, "web-research-health.json");
887
+ return {
888
+ loadProviderHealth: () => {
889
+ try {
890
+ const parsed = JSON.parse(fs2.readFileSync(filePath, "utf8"));
891
+ if (!Array.isArray(parsed)) return [];
892
+ return parsed.map(parseHealthEntry).filter((entry) => !!entry);
893
+ } catch {
894
+ return [];
895
+ }
896
+ },
897
+ saveProviderHealth: (entries) => {
898
+ try {
899
+ fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
900
+ fs2.writeFileSync(filePath, JSON.stringify(entries, null, 2));
901
+ } catch {
902
+ }
903
+ }
904
+ };
905
+ }
906
+
656
907
  // src/capabilities/index.ts
657
908
  function createCliHostCapabilities(cwd, options = {}) {
658
909
  const bashTool = createBashTool(cwd);
910
+ const healthStore = createCliWebResearchHealthStore(options.configDir ?? getConfigDir());
659
911
  const webSearchTool = createSharedWebSearchTool({
660
912
  getModel: options.getModel ?? (() => void 0),
661
913
  getApiKey: options.getApiKey ?? (() => void 0),
@@ -667,6 +919,25 @@ function createCliHostCapabilities(cwd, options = {}) {
667
919
  getModel: options.getModel,
668
920
  getApiKey: options.getApiKey
669
921
  });
922
+ const webResearchTool = createSharedWebResearchTool({
923
+ getModel: options.getModel ?? (() => void 0),
924
+ getApiKey: options.getApiKey ?? (() => void 0),
925
+ getMode: options.getWebSearchMode,
926
+ resolveToolModel: options.resolveToolModel,
927
+ resolveToolModelCandidates: (purpose) => options.resolveToolModelCandidates?.(purpose),
928
+ timeoutMs: 6e4,
929
+ searchAttemptTimeoutMs: 45e3,
930
+ fetchAttemptTimeoutMs: 3e4,
931
+ browserAttemptTimeoutMs: 45e3,
932
+ getCurrentDateContext: () => ({
933
+ currentDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
934
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
935
+ }),
936
+ webFetchTool,
937
+ renderedPageReader: createCliRenderedPageReader(),
938
+ loadProviderHealth: healthStore.loadProviderHealth,
939
+ saveProviderHealth: healthStore.saveProviderHealth
940
+ });
670
941
  const codeIntelTool = createCodeIntelTool2({ projectRoot: cwd });
671
942
  const capabilities = [
672
943
  {
@@ -674,6 +945,11 @@ function createCliHostCapabilities(cwd, options = {}) {
674
945
  description: "Node/TUI host capability for executing shell commands in the local workspace.",
675
946
  tools: [bashTool]
676
947
  },
948
+ {
949
+ id: "web_research",
950
+ description: "End-to-end web research that searches, fetches credible sources, and returns citations.",
951
+ tools: [webResearchTool]
952
+ },
677
953
  {
678
954
  id: "web_search",
679
955
  description: "Provider-native web search for current information outside the local workspace.",
@@ -697,5 +973,17 @@ function createCliHostCapabilities(cwd, options = {}) {
697
973
  }
698
974
 
699
975
  export {
976
+ getPackageDir,
977
+ getThemesDir,
978
+ getBuiltinDir,
979
+ getConfigDir,
980
+ getAgentDir,
981
+ getCustomThemesDir,
982
+ getSettingsPath,
983
+ getSessionsDir,
984
+ getLogsDir,
985
+ getRuntimeLogPath,
986
+ migrateConfigFromAgentDir,
987
+ createCliWebResearchHealthStore,
700
988
  createCliHostCapabilities
701
989
  };