@aexol/spectral 0.0.8 → 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,7 +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 { createJiti } from "@mariozechner/jiti";
51
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";
52
56
  import aexolMcpExtension from "../extensions/aexol-mcp.js";
53
57
  import { fetchAllowedModels as defaultFetchAllowedModels, } from "../relay/models-fetch.js";
54
58
  /**
@@ -86,6 +90,41 @@ function extractTextFromContent(content) {
86
90
  }
87
91
  return out;
88
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
+ }
89
128
  export class PiBridge {
90
129
  session;
91
130
  unsubscribe;
@@ -116,13 +155,34 @@ export class PiBridge {
116
155
  async start() {
117
156
  if (this.disposed)
118
157
  throw new Error("PiBridge already disposed");
119
- // ResourceLoader with the Aexol MCP extension wired in via factory.
120
- // The extension's signature `(pi: ExtensionAPI) => Promise<void>` matches
121
- // the ExtensionFactory type exactly, so we can pass it directly.
158
+ const extensionFactories = [aexolMcpExtension];
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.");
174
+ }
175
+ }
176
+ else {
177
+ console.info("[PiBridge] pi-mcp-adapter not found; standard MCP servers disabled.");
178
+ }
179
+ // ResourceLoader with extensions wired in via factories.
180
+ // Each factory's signature `(pi: ExtensionAPI) => Promise<void>` matches
181
+ // the ExtensionFactory type exactly, so we can pass them directly.
122
182
  const resourceLoader = new DefaultResourceLoader({
123
183
  cwd: this.opts.cwd,
124
184
  agentDir: this.opts.agentDir ?? `${process.env.HOME ?? ""}/.pi/agent`,
125
- extensionFactories: [aexolMcpExtension],
185
+ extensionFactories,
126
186
  // Skip on-disk extension/skill discovery so the server is self-contained.
127
187
  noExtensions: false,
128
188
  noSkills: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.0.8",
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",