@aexol/spectral 0.1.1 → 0.1.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.
@@ -48,8 +48,11 @@
48
48
  * conversations within a single WS connection work normally.
49
49
  */
50
50
  import { AuthStorage, createAgentSession, DefaultResourceLoader, ModelRegistry, SessionManager, } from "@mariozechner/pi-coding-agent";
51
- import { createRequire } from "node:module";
51
+ import { createJiti } from "@mariozechner/jiti";
52
52
  import { randomUUID } from "node:crypto";
53
+ import { existsSync, readFileSync } from "node:fs";
54
+ import { dirname, resolve } from "node:path";
55
+ import { fileURLToPath } from "node:url";
53
56
  import aexolMcpExtension from "../extensions/aexol-mcp.js";
54
57
  import { fetchAllowedModels as defaultFetchAllowedModels, } from "../relay/models-fetch.js";
55
58
  /**
@@ -87,6 +90,41 @@ function extractTextFromContent(content) {
87
90
  }
88
91
  return out;
89
92
  }
93
+ /**
94
+ * Resolve the entry point of the pi-mcp-adapter extension (index.ts)
95
+ * by walking up from this file's location through node_modules.
96
+ * Returns the absolute path, or null if the package is not installed.
97
+ */
98
+ function resolveMcpAdapterEntry() {
99
+ const __dirname = dirname(fileURLToPath(import.meta.url));
100
+ const rel = "node_modules/pi-mcp-adapter/package.json";
101
+ const root = "/";
102
+ let dir = __dirname;
103
+ for (let i = 0; i < 20; i++) {
104
+ const pkgPath = resolve(dir, rel);
105
+ try {
106
+ const raw = readFileSync(pkgPath, "utf8");
107
+ const pkg = JSON.parse(raw);
108
+ const extRel = pkg.pi?.extensions?.[0];
109
+ if (extRel) {
110
+ return resolve(dirname(pkgPath), extRel);
111
+ }
112
+ // Package found but no pi.extensions — try index.ts as fallback
113
+ const indexTs = resolve(dirname(pkgPath), "index.ts");
114
+ if (existsSync(indexTs))
115
+ return indexTs;
116
+ break;
117
+ }
118
+ catch {
119
+ // package.json not readable at this level, keep walking up
120
+ }
121
+ const parent = dirname(dir);
122
+ if (parent === dir || parent === root)
123
+ break;
124
+ dir = parent;
125
+ }
126
+ return null;
127
+ }
90
128
  export class PiBridge {
91
129
  session;
92
130
  unsubscribe;
@@ -117,23 +155,25 @@ export class PiBridge {
117
155
  async start() {
118
156
  if (this.disposed)
119
157
  throw new Error("PiBridge already disposed");
120
- // Build extension factories. Always include the Aexol MCP extension;
121
- // dynamically load the pi-mcp-adapter (standard MCP servers) with
122
- // graceful degradation when the package is not installed.
123
158
  const extensionFactories = [aexolMcpExtension];
124
- // Use createRequire to avoid TypeScript walking into pi-mcp-adapter's
125
- // raw .ts sources (the package doesn't ship .d.ts). The extension factory
126
- // is the default export: `export default function mcpAdapter(pi)`.
127
- try {
128
- const piMcpRequire = createRequire(import.meta.url);
129
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
130
- const mcpAdapterExtension = piMcpRequire("pi-mcp-adapter");
131
- if (typeof mcpAdapterExtension === "function") {
132
- extensionFactories.push(mcpAdapterExtension);
133
- console.info("[PiBridge] Standard MCP adapter loaded (pi-mcp-adapter)");
159
+ // Load pi-mcp-adapter via jiti so tsc never crawls its .ts files in
160
+ // node_modules. The static `import` was causing tsc to type-check
161
+ // pi-mcp-adapter's source and fail the build on its type errors.
162
+ // jiti is the same loader pi uses internally for all extensions.
163
+ const mcpAdapterPath = resolveMcpAdapterEntry();
164
+ if (mcpAdapterPath) {
165
+ try {
166
+ const jiti = createJiti(import.meta.url, { moduleCache: false });
167
+ const mcpAdapterFactory = await jiti.import(mcpAdapterPath, { default: true });
168
+ if (typeof mcpAdapterFactory === "function") {
169
+ extensionFactories.push(async (pi) => mcpAdapterFactory(pi));
170
+ }
171
+ }
172
+ catch {
173
+ console.info("[PiBridge] pi-mcp-adapter not found; standard MCP servers disabled.");
134
174
  }
135
175
  }
136
- catch {
176
+ else {
137
177
  console.info("[PiBridge] pi-mcp-adapter not found; standard MCP servers disabled.");
138
178
  }
139
179
  // ResourceLoader with extensions wired in via factories.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Always-on coding agent for Aexol — branded pi wrapper with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -51,6 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@inquirer/prompts": "^7.2.0",
54
+ "@mariozechner/jiti": "^2.6.5",
54
55
  "@mariozechner/pi-coding-agent": "^0.70.2",
55
56
  "better-sqlite3": "^12.9.0",
56
57
  "pi-mcp-adapter": "^2.5.4",