@aexol/spectral 0.8.0 → 0.8.2

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 (43) hide show
  1. package/dist/extensions/kanban-bridge.js +668 -0
  2. package/dist/extensions/spectral-vision-fallback.js +3 -2
  3. package/dist/mcp/init.js +1 -9
  4. package/dist/memory/index.js +2 -0
  5. package/dist/memory/tools/write-project-observation.js +60 -0
  6. package/dist/relay/auto-research.js +34 -0
  7. package/dist/sdk/ai/env-api-keys.js +9 -49
  8. package/dist/sdk/ai/utils/oauth/anthropic.js +1 -1
  9. package/dist/sdk/ai/utils/oauth/openai-codex.js +1 -1
  10. package/dist/sdk/coding-agent/config.js +2 -69
  11. package/dist/sdk/coding-agent/core/extensions/loader.js +2 -35
  12. package/dist/sdk/coding-agent/core/extensions/runner.js +1 -2
  13. package/dist/sdk/coding-agent/core/model-resolver-utils.js +8 -0
  14. package/dist/sdk/coding-agent/core/model-resolver.js +1 -1
  15. package/dist/sdk/coding-agent/core/resource-loader.js +1 -1
  16. package/dist/sdk/coding-agent/core/settings-manager.js +1 -170
  17. package/dist/sdk/coding-agent/core/system-prompt.js +3 -1
  18. package/dist/sdk/coding-agent/core/theme.js +202 -0
  19. package/dist/sdk/coding-agent/core/tools/bash.js +17 -18
  20. package/dist/sdk/coding-agent/core/tools/edit.js +7 -8
  21. package/dist/sdk/coding-agent/core/tools/find.js +9 -13
  22. package/dist/sdk/coding-agent/core/tools/grep.js +10 -14
  23. package/dist/sdk/coding-agent/core/tools/ls.js +9 -10
  24. package/dist/sdk/coding-agent/core/tools/read.js +15 -25
  25. package/dist/sdk/coding-agent/{modes/interactive/components/diff.js → core/tools/render-diff.js} +18 -31
  26. package/dist/sdk/coding-agent/core/tools/write.js +10 -11
  27. package/dist/sdk/coding-agent/index.js +7 -5
  28. package/dist/sdk/coding-agent/modes/index.js +0 -1
  29. package/dist/sdk/coding-agent/modes/rpc/rpc-mode.js +2 -2
  30. package/dist/sdk/coding-agent/utils/photon.js +2 -10
  31. package/dist/sdk/coding-agent/utils/pi-user-agent.js +1 -2
  32. package/dist/server/agent-bridge.js +2 -1
  33. package/package.json +1 -1
  34. package/dist/sdk/coding-agent/bun/cli.js +0 -7
  35. package/dist/sdk/coding-agent/bun/restore-sandbox-env.js +0 -31
  36. package/dist/sdk/coding-agent/cli/args.js +0 -340
  37. package/dist/sdk/coding-agent/cli/file-processor.js +0 -82
  38. package/dist/sdk/coding-agent/cli/initial-message.js +0 -21
  39. package/dist/sdk/coding-agent/core/footer-data-provider.js +0 -309
  40. package/dist/sdk/coding-agent/modes/interactive/components/keybinding-hints.js +0 -35
  41. package/dist/sdk/coding-agent/modes/interactive/components/visual-truncate.js +0 -26
  42. package/dist/sdk/coding-agent/modes/interactive/interactive-mode.js +0 -3
  43. package/dist/sdk/coding-agent/modes/interactive/theme/theme.js +0 -1022
package/dist/mcp/init.js CHANGED
@@ -208,15 +208,7 @@ export function flushMetadataCache(state) {
208
208
  }
209
209
  }
210
210
  }
211
- function safeFg(ui, color, text) {
212
- try {
213
- const styled = ui?.theme?.fg?.(color, text);
214
- if (styled)
215
- return styled;
216
- }
217
- catch {
218
- // fall through to plain text
219
- }
211
+ function safeFg(_ui, _color, text) {
220
212
  return text;
221
213
  }
222
214
  export function updateStatusBar(state) {
@@ -6,6 +6,7 @@ import { registerObserverTrigger } from "./hooks/observer-trigger.js";
6
6
  import { Runtime } from "./runtime.js";
7
7
  import { registerRecallTool } from "./tools/recall-observation.js";
8
8
  import { registerReadProjectObservationsTool } from "./tools/read-project-observations.js";
9
+ import { registerWriteProjectObservationTool } from "./tools/write-project-observation.js";
9
10
  export default function observationalMemory(pi) {
10
11
  const runtime = new Runtime();
11
12
  // Log extension load so we can confirm it's running in serve mode.
@@ -17,4 +18,5 @@ export default function observationalMemory(pi) {
17
18
  registerViewCommand(pi, runtime);
18
19
  registerRecallTool(pi);
19
20
  registerReadProjectObservationsTool(pi);
21
+ registerWriteProjectObservationTool(pi);
20
22
  }
@@ -0,0 +1,60 @@
1
+ import { Type } from "../../sdk/ai/index.js";
2
+ import { defineTool } from "../../sdk/coding-agent/index.js";
3
+ import { getProjectObsStore } from "../project-observations-store.js";
4
+ import { hashId } from "../ids.js";
5
+ export const WRITE_PROJECT_OBSERVATION_TOOL_NAME = "write_project_observation";
6
+ export const writeProjectObservationTool = defineTool({
7
+ name: WRITE_PROJECT_OBSERVATION_TOOL_NAME,
8
+ label: "Write project observation",
9
+ description: "Write a durable, cross-session observation about this project. " +
10
+ "Use this to persist discovered conventions, architectural rules, " +
11
+ "gotchas, and directory purposes. Observations are deduplicated by " +
12
+ "content hash — writing the same fact twice is harmless (idempotent). " +
13
+ "Future sessions can discover these via read_project_observations.",
14
+ promptSnippet: "Use write_project_observation(content, relevance) to persist project-level knowledge.",
15
+ promptGuidelines: [
16
+ "Use for STABLE facts: conventions, architecture decisions, gotchas, file roles.",
17
+ "Use relevance='critical' for NEVER-EDIT rules (auto-generated files, invariant constraints).",
18
+ "Use relevance='high' for important conventions the agent must follow.",
19
+ "Use relevance='medium' for useful patterns and directory purposes.",
20
+ "Content must be a single, self-contained sentence — no markdown, no lists.",
21
+ "Deduplication is automatic — same content = same ID → INSERT OR REPLACE.",
22
+ ],
23
+ parameters: Type.Object({
24
+ content: Type.String({
25
+ minLength: 1,
26
+ description: "A single self-contained sentence describing the fact. No markdown, no lists.",
27
+ }),
28
+ relevance: Type.Union([
29
+ Type.Literal("low"),
30
+ Type.Literal("medium"),
31
+ Type.Literal("high"),
32
+ Type.Literal("critical"),
33
+ ], { description: "Importance: critical > high > medium > low" }),
34
+ }),
35
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
36
+ const store = getProjectObsStore();
37
+ if (!store) {
38
+ return {
39
+ content: [{ type: "text", text: "Project observations store is not available." }],
40
+ details: { status: "store_unavailable" },
41
+ };
42
+ }
43
+ const projectId = store.getProjectByCwd(ctx.cwd);
44
+ if (!projectId) {
45
+ return {
46
+ content: [{ type: "text", text: `No project found for current working directory: ${ctx.cwd}` }],
47
+ details: { status: "no_project" },
48
+ };
49
+ }
50
+ const id = hashId(params.content);
51
+ store.insertProjectObservations(projectId, "auto-research", [{ id, content: params.content, relevance: params.relevance }], Date.now());
52
+ return {
53
+ content: [{ type: "text", text: `Observation [${id}] recorded (relevance: ${params.relevance}).` }],
54
+ details: { id, status: "ok" },
55
+ };
56
+ },
57
+ });
58
+ export function registerWriteProjectObservationTool(pi) {
59
+ pi.registerTool(writeProjectObservationTool);
60
+ }
@@ -532,6 +532,40 @@ function buildAutoResearchTask(projectPath, projectName, preRunContext = null) {
532
532
  "",
533
533
  "Start by collecting project context, then generate the most impactful extensions",
534
534
  "and update AGENTS.md when done.",
535
+ "",
536
+ "### 6. Persist Key Discoveries as Durable Observations",
537
+ "",
538
+ "As you analyze the project, you will discover conventions, rules, and patterns",
539
+ "that future agent sessions should know. Write these as durable cross-session",
540
+ "observations using the `write_project_observation` tool:",
541
+ "",
542
+ "```",
543
+ "write_project_observation({",
544
+ " content: \"backend/src/models.ts is auto-generated by Axolotl — never edit by hand\",",
545
+ " relevance: \"critical\"",
546
+ "})",
547
+ "```",
548
+ "",
549
+ "**What to capture:**",
550
+ "",
551
+ "- **NEVER-EDIT markers** (relevance: critical): Auto-generated files that must",
552
+ " be regenerated instead of edited. Seen a comment like 'DO NOT EDIT'? Persist it.",
553
+ "- **Import/macro conventions** (relevance: high): Rules like \"all Deno imports",
554
+ " MUST use .ts extension\" or \"use workspace imports, not relative paths\".",
555
+ "- **Code-generation commands** (relevance: high): The exact shell commands to",
556
+ " regenerate generated files (e.g. 'cd backend && deno task axolotl:build').",
557
+ "- **Directory roles** (relevance: medium): What each top-level directory contains",
558
+ " (e.g. 'packages/parser/ is zero-dependency — never add imports here').",
559
+ "- **Runtime boundaries** (relevance: medium): Which packages use Deno vs Node.",
560
+ "- **Database/API patterns** (relevance: medium): How the backend is structured.",
561
+ "",
562
+ "**Guidelines:**",
563
+ "",
564
+ "- Write 5–10 observations covering the most important discoveries.",
565
+ "- Each observation is a single self-contained sentence — no markdown, no lists.",
566
+ "- Focus on facts that would PREVENT MISTAKES or SAVE TIME in future sessions.",
567
+ "- Deduplication is automatic by content hash — writing the same fact twice is harmless.",
568
+ "- Future sessions retrieve these via `read_project_observations(\"query\")`.",
535
569
  ].join("\n");
536
570
  }
537
571
  // ---------------------------------------------------------------------------
@@ -6,8 +6,8 @@ const dynamicImport = (specifier) => import(specifier);
6
6
  const NODE_FS_SPECIFIER = "node:" + "fs";
7
7
  const NODE_OS_SPECIFIER = "node:" + "os";
8
8
  const NODE_PATH_SPECIFIER = "node:" + "path";
9
- // Eagerly load in Node.js/Bun environment only
10
- if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
9
+ // Eagerly load in Node.js environment only
10
+ if (typeof process !== "undefined" && process.versions?.node) {
11
11
  dynamicImport(NODE_FS_SPECIFIER).then((m) => {
12
12
  _existsSync = m.existsSync;
13
13
  });
@@ -18,38 +18,6 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
18
18
  _join = m.join;
19
19
  });
20
20
  }
21
- let _procEnvCache = null;
22
- /**
23
- * Fallback for https://github.com/oven-sh/bun/issues/27802
24
- * Bun compiled binaries have an empty `process.env` inside sandbox
25
- * environments on Linux. We can recover the env from `/proc/self/environ`.
26
- */
27
- function getProcEnv(key) {
28
- if (!process.versions?.bun)
29
- return undefined;
30
- if (typeof process === "undefined")
31
- return undefined;
32
- // If process.env already has entries, the bug is not triggered.
33
- if (Object.keys(process.env).length > 0)
34
- return undefined;
35
- if (_procEnvCache === null) {
36
- _procEnvCache = new Map();
37
- try {
38
- const { readFileSync } = require("node:fs");
39
- const data = readFileSync("/proc/self/environ", "utf-8");
40
- for (const entry of data.split("\0")) {
41
- const idx = entry.indexOf("=");
42
- if (idx > 0) {
43
- _procEnvCache.set(entry.slice(0, idx), entry.slice(idx + 1));
44
- }
45
- }
46
- }
47
- catch {
48
- // /proc/self/environ may not be readable.
49
- }
50
- }
51
- return _procEnvCache.get(key);
52
- }
53
21
  let cachedVertexAdcCredentialsExists = null;
54
22
  function hasVertexAdcCredentials() {
55
23
  if (cachedVertexAdcCredentialsExists === null) {
@@ -57,7 +25,7 @@ function hasVertexAdcCredentials() {
57
25
  // return false WITHOUT caching so the next call retries once they're ready.
58
26
  // Only cache false permanently in a browser environment where fs is never available.
59
27
  if (!_existsSync || !_homedir || !_join) {
60
- const isNode = typeof process !== "undefined" && (process.versions?.node || process.versions?.bun);
28
+ const isNode = typeof process !== "undefined" && process.versions?.node;
61
29
  if (!isNode) {
62
30
  // Definitively in a browser — safe to cache false permanently
63
31
  cachedVertexAdcCredentialsExists = false;
@@ -65,7 +33,7 @@ function hasVertexAdcCredentials() {
65
33
  return false;
66
34
  }
67
35
  // Check GOOGLE_APPLICATION_CREDENTIALS env var first (standard way)
68
- const gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS || getProcEnv("GOOGLE_APPLICATION_CREDENTIALS");
36
+ const gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
69
37
  if (gacPath) {
70
38
  cachedVertexAdcCredentialsExists = _existsSync(gacPath);
71
39
  }
@@ -121,23 +89,21 @@ export function findEnvKeys(provider) {
121
89
  const envVars = getApiKeyEnvVars(provider);
122
90
  if (!envVars)
123
91
  return undefined;
124
- const found = envVars.filter((envVar) => !!process.env[envVar] || !!getProcEnv(envVar));
92
+ const found = envVars.filter((envVar) => !!process.env[envVar]);
125
93
  return found.length > 0 ? found : undefined;
126
94
  }
127
95
  export function getEnvApiKey(provider) {
128
96
  const envKeys = findEnvKeys(provider);
129
97
  if (envKeys?.[0]) {
130
- return process.env[envKeys[0]] || getProcEnv(envKeys[0]);
98
+ return process.env[envKeys[0]];
131
99
  }
132
100
  // Vertex AI supports either an explicit API key or Application Default Credentials.
133
101
  // Auth is configured via `gcloud auth application-default login`.
134
102
  if (provider === "google-vertex") {
135
103
  const hasCredentials = hasVertexAdcCredentials();
136
104
  const hasProject = !!(process.env.GOOGLE_CLOUD_PROJECT ||
137
- process.env.GCLOUD_PROJECT ||
138
- getProcEnv("GOOGLE_CLOUD_PROJECT") ||
139
- getProcEnv("GCLOUD_PROJECT"));
140
- const hasLocation = !!(process.env.GOOGLE_CLOUD_LOCATION || getProcEnv("GOOGLE_CLOUD_LOCATION"));
105
+ process.env.GCLOUD_PROJECT);
106
+ const hasLocation = !!(process.env.GOOGLE_CLOUD_LOCATION);
141
107
  if (hasCredentials && hasProject && hasLocation) {
142
108
  return "<authenticated>";
143
109
  }
@@ -155,13 +121,7 @@ export function getEnvApiKey(provider) {
155
121
  process.env.AWS_BEARER_TOKEN_BEDROCK ||
156
122
  process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ||
157
123
  process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI ||
158
- process.env.AWS_WEB_IDENTITY_TOKEN_FILE ||
159
- getProcEnv("AWS_PROFILE") ||
160
- (getProcEnv("AWS_ACCESS_KEY_ID") && getProcEnv("AWS_SECRET_ACCESS_KEY")) ||
161
- getProcEnv("AWS_BEARER_TOKEN_BEDROCK") ||
162
- getProcEnv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") ||
163
- getProcEnv("AWS_CONTAINER_CREDENTIALS_FULL_URI") ||
164
- getProcEnv("AWS_WEB_IDENTITY_TOKEN_FILE")) {
124
+ process.env.AWS_WEB_IDENTITY_TOKEN_FILE) {
165
125
  return "<authenticated>";
166
126
  }
167
127
  }
@@ -21,7 +21,7 @@ async function getNodeApis() {
21
21
  if (nodeApis)
22
22
  return nodeApis;
23
23
  if (!nodeApisPromise) {
24
- if (typeof process === "undefined" || (!process.versions?.node && !process.versions?.bun)) {
24
+ if (typeof process === "undefined" || !process.versions?.node) {
25
25
  throw new Error("Anthropic OAuth is only available in Node.js environments");
26
26
  }
27
27
  nodeApisPromise = import("node:http").then((httpModule) => ({
@@ -7,7 +7,7 @@
7
7
  // NEVER convert to top-level imports - breaks browser/Vite builds
8
8
  let _randomBytes = null;
9
9
  let _http = null;
10
- if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
10
+ if (typeof process !== "undefined" && process.versions?.node) {
11
11
  import("node:crypto").then((m) => {
12
12
  _randomBytes = m.randomBytes;
13
13
  });
@@ -9,13 +9,6 @@ import { normalizePath } from "./utils/paths.js";
9
9
  // =============================================================================
10
10
  const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = dirname(__filename);
12
- /**
13
- * Detect if we're running as a Bun compiled binary.
14
- * Bun binaries have import.meta.url containing "$bunfs", "~BUN", or "%7EBUN" (Bun's virtual filesystem path)
15
- */
16
- export const isBunBinary = import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN");
17
- /** Detect if Bun is the runtime (compiled binary or bun run) */
18
- export const isBunRuntime = !!process.versions.bun;
19
12
  function makeSelfUpdateCommand(installStep, uninstallStep) {
20
13
  if (!uninstallStep)
21
14
  return installStep;
@@ -33,9 +26,6 @@ function makeSelfUpdateCommandStep(command, args) {
33
26
  };
34
27
  }
35
28
  export function detectInstallMethod() {
36
- if (isBunBinary) {
37
- return "bun-binary";
38
- }
39
29
  const resolvedPath = `${__dirname}\0${process.execPath || ""}`.toLowerCase().replace(/\\/g, "/");
40
30
  if (resolvedPath.includes("/pnpm/") || resolvedPath.includes("/.pnpm/")) {
41
31
  return "pnpm";
@@ -43,9 +33,6 @@ export function detectInstallMethod() {
43
33
  if (resolvedPath.includes("/yarn/") || resolvedPath.includes("/.yarn/")) {
44
34
  return "yarn";
45
35
  }
46
- if (isBunRuntime || resolvedPath.includes("/install/global/node_modules/")) {
47
- return "bun";
48
- }
49
36
  if (resolvedPath.includes("/npm/") || resolvedPath.includes("/node_modules/")) {
50
37
  return "npm";
51
38
  }
@@ -74,8 +61,6 @@ function getInferredNpmInstall() {
74
61
  }
75
62
  function getSelfUpdateCommandForMethod(method, installedPackageName, updatePackageName = installedPackageName, npmCommand) {
76
63
  switch (method) {
77
- case "bun-binary":
78
- return undefined;
79
64
  case "pnpm":
80
65
  return makeSelfUpdateCommand(makeSelfUpdateCommandStep("pnpm", ["install", "-g", "--ignore-scripts", updatePackageName]), updatePackageName === installedPackageName
81
66
  ? undefined
@@ -84,10 +69,6 @@ function getSelfUpdateCommandForMethod(method, installedPackageName, updatePacka
84
69
  return makeSelfUpdateCommand(makeSelfUpdateCommandStep("yarn", ["global", "add", "--ignore-scripts", updatePackageName]), updatePackageName === installedPackageName
85
70
  ? undefined
86
71
  : makeSelfUpdateCommandStep("yarn", ["global", "remove", installedPackageName]));
87
- case "bun":
88
- return makeSelfUpdateCommand(makeSelfUpdateCommandStep("bun", ["install", "-g", "--ignore-scripts", updatePackageName]), updatePackageName === installedPackageName
89
- ? undefined
90
- : makeSelfUpdateCommandStep("bun", ["uninstall", "-g", installedPackageName]));
91
72
  case "npm": {
92
73
  const [command = "npm", ...npmArgs] = npmCommand ?? [];
93
74
  const inferred = npmCommand?.length ? undefined : getInferredNpmInstall();
@@ -126,16 +107,6 @@ function getGlobalPackageRoots(method, _packageName, npmCommand) {
126
107
  case "npm": {
127
108
  const configured = !!npmCommand?.length;
128
109
  const [command = "npm", ...npmArgs] = npmCommand ?? [];
129
- if (configured && command === "bun") {
130
- const bunBin = readCommandOutput(command, [...npmArgs, "pm", "bin", "-g"], {
131
- requireSuccess: true,
132
- });
133
- const roots = [join(homedir(), ".bun", "install", "global", "node_modules")];
134
- if (bunBin) {
135
- roots.push(join(dirname(bunBin), "install", "global", "node_modules"));
136
- }
137
- return roots;
138
- }
139
110
  const root = readCommandOutput(command, [...npmArgs, "root", "-g"], {
140
111
  requireSuccess: configured,
141
112
  });
@@ -150,15 +121,6 @@ function getGlobalPackageRoots(method, _packageName, npmCommand) {
150
121
  const dir = readCommandOutput("yarn", ["global", "dir"]);
151
122
  return dir ? [dir, join(dir, "node_modules")] : [];
152
123
  }
153
- case "bun": {
154
- const bunBin = readCommandOutput("bun", ["pm", "bin", "-g"]);
155
- const roots = [join(homedir(), ".bun", "install", "global", "node_modules")];
156
- if (bunBin) {
157
- roots.push(join(dirname(bunBin), "install", "global", "node_modules"));
158
- }
159
- return roots;
160
- }
161
- case "bun-binary":
162
124
  case "unknown":
163
125
  return [];
164
126
  }
@@ -229,9 +191,6 @@ export function getSelfUpdateCommand(packageName, npmCommand, updatePackageName
229
191
  }
230
192
  export function getSelfUpdateUnavailableInstruction(packageName, npmCommand, updatePackageName = packageName) {
231
193
  const method = detectInstallMethod();
232
- if (method === "bun-binary") {
233
- return `Download from: https://github.com/earendil-works/pi-mono/releases/latest`;
234
- }
235
194
  const command = getSelfUpdateCommandForMethod(method, packageName, updatePackageName, npmCommand);
236
195
  if (command) {
237
196
  if (isManagedByGlobalPackageManager(method, packageName, npmCommand) && !isSelfUpdatePathWritable()) {
@@ -254,7 +213,6 @@ export function getUpdateInstruction(packageName) {
254
213
  // =============================================================================
255
214
  /**
256
215
  * Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
257
- * - For Bun binary: returns the directory containing the executable
258
216
  * - For Node.js (dist/): returns __dirname (the dist/ directory)
259
217
  * - For tsx (src/): returns parent directory (the package root)
260
218
  */
@@ -264,10 +222,6 @@ export function getPackageDir() {
264
222
  if (envDir) {
265
223
  return normalizePath(envDir);
266
224
  }
267
- if (isBunBinary) {
268
- // Bun binary: process.execPath points to the compiled executable
269
- return dirname(process.execPath);
270
- }
271
225
  // Node.js: walk up from __dirname until we find package.json
272
226
  let dir = __dirname;
273
227
  while (dir !== dirname(dir)) {
@@ -281,14 +235,10 @@ export function getPackageDir() {
281
235
  }
282
236
  /**
283
237
  * Get path to built-in themes directory (shipped with package)
284
- * - For Bun binary: theme/ next to executable
285
238
  * - For Node.js (dist/): dist/modes/interactive/theme/
286
239
  * - For tsx (src/): src/modes/interactive/theme/
287
240
  */
288
241
  export function getThemesDir() {
289
- if (isBunBinary) {
290
- return join(getPackageDir(), "theme");
291
- }
292
242
  // Theme is in modes/interactive/theme/ relative to src/ or dist/
293
243
  const packageDir = getPackageDir();
294
244
  const srcOrDist = existsSync(join(packageDir, "src")) ? "src" : "dist";
@@ -296,7 +246,8 @@ export function getThemesDir() {
296
246
  }
297
247
  /**
298
248
  * Get path to HTML export template directory (shipped with package)
299
- * - For Bun binary: export-html/ next to executable
249
+ * - For Node.js (dist/): returns __dirname (the dist/ directory)
250
+ * - For tsx (src/): returns parent directory (the package root)
300
251
  /** Get path to package.json */
301
252
  export function getPackageJsonPath() {
302
253
  return join(getPackageDir(), "package.json");
@@ -317,24 +268,6 @@ export function getExamplesPath() {
317
268
  export function getChangelogPath() {
318
269
  return resolve(join(getPackageDir(), "CHANGELOG.md"));
319
270
  }
320
- /**
321
- * Get path to built-in interactive assets directory.
322
- * - For Bun binary: assets/ next to executable
323
- * - For Node.js (dist/): dist/modes/interactive/assets/
324
- * - For tsx (src/): src/modes/interactive/assets/
325
- */
326
- export function getInteractiveAssetsDir() {
327
- if (isBunBinary) {
328
- return join(getPackageDir(), "assets");
329
- }
330
- const packageDir = getPackageDir();
331
- const srcOrDist = existsSync(join(packageDir, "src")) ? "src" : "dist";
332
- return join(packageDir, srcOrDist, "modes", "interactive", "assets");
333
- }
334
- /** Get path to a bundled interactive asset */
335
- export function getBundledInteractiveAssetPath(name) {
336
- return join(getInteractiveAssetsDir(), name);
337
- }
338
271
  const pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
339
272
  const spectralConfigName = pkg.spectralConfig?.name;
340
273
  export const PACKAGE_NAME = pkg.name || "index.ts";
@@ -6,45 +6,15 @@ import * as fs from "node:fs";
6
6
  import { createRequire } from "node:module";
7
7
  import * as path from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import * as _bundledPiAgentCore from "../../../agent-core/index.js";
10
- import * as _bundledPiAi from "../../../ai/index.js";
11
- import * as _bundledPiAiOauth from "../../../ai/oauth.js";
12
9
  import { createJiti } from "@mariozechner/jiti";
13
- // Static imports of packages that extensions may use.
14
- // These MUST be static so Bun bundles them into the compiled binary.
15
- // The virtualModules option then makes them available to extensions.
16
- import * as _bundledTypebox from "typebox";
17
- import * as _bundledTypeboxCompile from "typebox/compile";
18
- import * as _bundledTypeboxValue from "typebox/value";
19
- import { CONFIG_DIR_NAME, getAgentDir, isBunBinary } from "../../config.js";
20
- // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,
21
- // avoiding a circular dependency. Extensions can import from ../../index.ts.
22
- import * as _bundledPiCodingAgent from "../../index.js";
10
+ import { CONFIG_DIR_NAME, getAgentDir } from "../../config.js";
23
11
  import { resolvePath } from "../../utils/paths.js";
24
12
  import { createEventBus } from "../event-bus.js";
25
13
  import { execCommand } from "../exec.js";
26
14
  import { createSyntheticSourceInfo } from "../source-info.js";
27
- /** Modules available to extensions via virtualModules (for compiled Bun binary) */
28
- const VIRTUAL_MODULES = {
29
- typebox: _bundledTypebox,
30
- "typebox/compile": _bundledTypeboxCompile,
31
- "typebox/value": _bundledTypeboxValue,
32
- "@sinclair/typebox": _bundledTypebox,
33
- "@sinclair/typebox/compile": _bundledTypeboxCompile,
34
- "@sinclair/typebox/value": _bundledTypeboxValue,
35
- "../../../agent-core/index.ts": _bundledPiAgentCore,
36
- "../../../ai/index.ts": _bundledPiAi,
37
- "../../../ai/oauth.ts": _bundledPiAiOauth,
38
- "../../index.ts": _bundledPiCodingAgent,
39
- "@mariozechner/pi-agent": _bundledPiAgentCore,
40
- "@mariozechner/pi-ai": _bundledPiAi,
41
- "@mariozechner/pi-ai/oauth": _bundledPiAiOauth,
42
- "@mariozechner/pi-coding-agent": _bundledPiCodingAgent,
43
- };
44
15
  const require = createRequire(import.meta.url);
45
16
  /**
46
17
  * Get aliases for jiti (used in Node.js/development mode).
47
- * In Bun binary mode, virtualModules is used instead.
48
18
  */
49
19
  let _aliases = null;
50
20
  function getAliases() {
@@ -263,10 +233,7 @@ function createExtensionAPI(extension, runtime, cwd, eventBus) {
263
233
  async function loadExtensionModule(extensionPath) {
264
234
  const jiti = createJiti(import.meta.url, {
265
235
  moduleCache: false,
266
- // In Bun binary: use virtualModules for bundled packages (no filesystem resolution)
267
- // Also disable tryNative so jiti handles ALL imports (not just the entry point)
268
- // In Node.js/dev: use aliases to resolve to node_modules paths
269
- ...(isBunBinary ? { virtualModules: VIRTUAL_MODULES, tryNative: false } : { alias: getAliases() }),
236
+ alias: getAliases(),
270
237
  });
271
238
  const module = await jiti.import(extensionPath, { default: true });
272
239
  const factory = module;
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Extension runner - executes extensions and manages their lifecycle.
3
3
  */
4
- import { theme } from "../../modes/interactive/theme/theme.js";
5
4
  // Extension shortcuts compete with canonical keybinding ids from keybindings.json.
6
5
  // Only editor-global shortcuts are reserved here. Picker-specific bindings are not.
7
6
  const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
@@ -80,7 +79,7 @@ const noOpUIContext = {
80
79
  setEditorComponent: () => { },
81
80
  getEditorComponent: () => undefined,
82
81
  get theme() {
83
- return theme;
82
+ return {};
84
83
  },
85
84
  getAllThemes: () => [],
86
85
  getTheme: () => undefined,
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared utility for validating thinking level strings.
3
+ * Extracted from cli/args.ts (TUI-only, now deleted).
4
+ */
5
+ const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
6
+ export function isValidThinkingLevel(level) {
7
+ return VALID_THINKING_LEVELS.includes(level);
8
+ }
@@ -4,7 +4,7 @@
4
4
  import { modelsAreEqual } from "../../ai/index.js";
5
5
  import chalk from "chalk";
6
6
  import { minimatch } from "minimatch";
7
- import { isValidThinkingLevel } from "../cli/args.js";
7
+ import { isValidThinkingLevel } from "./model-resolver-utils.js";
8
8
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
9
9
  /** Default model IDs for each known provider */
10
10
  export const defaultModelPerProvider = {
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import { join, resolve, sep } from "node:path";
3
3
  import chalk from "chalk";
4
4
  import { CONFIG_DIR_NAME } from "../config.js";
5
- import { loadThemeFromPath } from "../modes/interactive/theme/theme.js";
5
+ import { loadThemeFromPath } from "./theme.js";
6
6
  import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.js";
7
7
  import { createEventBus } from "./event-bus.js";
8
8
  import { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from "./extensions/loader.js";