thinkwell 0.3.2 → 0.4.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/bin/thinkwell CHANGED
@@ -3,36 +3,54 @@
3
3
  /**
4
4
  * Thinkwell CLI launcher
5
5
  *
6
- * This Node.js script detects if Bun is available and delegates to it
7
- * with the @thinkwell/bun-plugin preloaded.
6
+ * This Node.js script serves as the entry point for the npm distribution.
7
+ * It uses Node.js with the pkg-style loader for script execution, requiring
8
+ * Node 24+ for native TypeScript support via --experimental-transform-types.
9
+ *
10
+ * The pkg binary distribution uses a separate entry point (main-pkg.cjs)
11
+ * which is compiled directly into the binary.
8
12
  */
9
13
 
10
- import { execSync, spawn } from "node:child_process";
11
- import { dirname, resolve } from "node:path";
14
+ import { spawn, spawnSync } from "node:child_process";
15
+ import { dirname, resolve, isAbsolute } from "node:path";
12
16
  import { fileURLToPath } from "node:url";
13
17
  import { existsSync } from "node:fs";
14
18
 
15
19
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
20
 
17
- // Check if Bun is available and return version or null
18
- function getBunVersion() {
19
- try {
20
- const version = execSync("bun --version", { encoding: "utf-8" }).trim();
21
- return version;
22
- } catch {
23
- return null;
24
- }
25
- }
21
+ // ============================================================================
22
+ // Re-exec with Proper Node Flags
23
+ // ============================================================================
24
+
25
+ // Check if we need to re-exec with proper flags
26
+ // We use an env var to prevent infinite re-exec loops
27
+ const REEXEC_FLAG = "__THINKWELL_REEXEC__";
28
+
29
+ if (!process.env[REEXEC_FLAG]) {
30
+ // Re-exec with experimental transform types and warning suppression
31
+ const result = spawnSync(process.execPath, [
32
+ "--experimental-transform-types",
33
+ "--disable-warning=ExperimentalWarning",
34
+ fileURLToPath(import.meta.url),
35
+ ...process.argv.slice(2),
36
+ ], {
37
+ stdio: "inherit",
38
+ env: { ...process.env, [REEXEC_FLAG]: "1" },
39
+ });
26
40
 
27
- // Get the path to the plugin
28
- function getPluginPath() {
29
- // When installed via npm, the plugin is in our node_modules
30
- return resolve(__dirname, "../node_modules/@thinkwell/bun-plugin/dist/index.js");
41
+ process.exit(result.status ?? 1);
31
42
  }
32
43
 
33
- // Get the path to our node_modules (where thinkwell packages are installed)
34
- function getNodeModulesPath() {
35
- return resolve(__dirname, "../node_modules");
44
+ // Minimum Node.js version for native TypeScript support
45
+ const MIN_NODE_VERSION = 24;
46
+
47
+ // ============================================================================
48
+ // Path Helpers
49
+ // ============================================================================
50
+
51
+ // Get the path to the init command script
52
+ function getInitCommandPath() {
53
+ return resolve(__dirname, "../dist/cli/init-command.js");
36
54
  }
37
55
 
38
56
  // Get the path to the types command script
@@ -40,18 +58,61 @@ function getTypesCommandPath() {
40
58
  return resolve(__dirname, "../dist/cli/types-command.js");
41
59
  }
42
60
 
43
- // Validate that required files exist for commands that require Bun
61
+ // Get the path to the bundled CLI loader
62
+ function getLoaderPath() {
63
+ return resolve(__dirname, "../dist-pkg/cli-loader.cjs");
64
+ }
65
+
66
+ // Get paths to the bundled thinkwell packages
67
+ function getBundledPackagesPath() {
68
+ return {
69
+ thinkwell: resolve(__dirname, "../dist-pkg/thinkwell.cjs"),
70
+ acp: resolve(__dirname, "../dist-pkg/acp.cjs"),
71
+ protocol: resolve(__dirname, "../dist-pkg/protocol.cjs"),
72
+ };
73
+ }
74
+
75
+ // ============================================================================
76
+ // Runtime Detection
77
+ // ============================================================================
78
+
79
+ // Get Node.js major version
80
+ function getNodeMajorVersion() {
81
+ const match = process.version.match(/^v(\d+)/);
82
+ return match ? parseInt(match[1], 10) : 0;
83
+ }
84
+
85
+ // ============================================================================
86
+ // Validation
87
+ // ============================================================================
88
+
89
+ // Validate Node.js version
90
+ function validateNodeVersion() {
91
+ const nodeMajor = getNodeMajorVersion();
92
+ if (nodeMajor < MIN_NODE_VERSION) {
93
+ console.error(`Error: thinkwell requires Node.js ${MIN_NODE_VERSION} or later.`);
94
+ console.error(`Current version: ${process.version}`);
95
+ console.error("");
96
+ console.error("Node.js 24+ is required for native TypeScript support.");
97
+ console.error("Please upgrade Node.js: https://nodejs.org/");
98
+ process.exit(1);
99
+ }
100
+ }
101
+
102
+ // Validate that required files exist for script execution
44
103
  function validateInstallation() {
45
- const pluginPath = getPluginPath();
46
- const typesCommandPath = getTypesCommandPath();
104
+ const loaderPath = getLoaderPath();
105
+ const bundledPaths = getBundledPackagesPath();
47
106
  const errors = [];
48
107
 
49
- if (!existsSync(pluginPath)) {
50
- errors.push(`Plugin not found: ${pluginPath}`);
108
+ if (!existsSync(loaderPath)) {
109
+ errors.push(`Loader not found: ${loaderPath}`);
51
110
  }
52
111
 
53
- if (!existsSync(typesCommandPath)) {
54
- errors.push(`Types command not found: ${typesCommandPath}`);
112
+ for (const [name, path] of Object.entries(bundledPaths)) {
113
+ if (!existsSync(path)) {
114
+ errors.push(`Bundled ${name} not found: ${path}`);
115
+ }
55
116
  }
56
117
 
57
118
  if (errors.length > 0) {
@@ -75,6 +136,17 @@ function validateInitCommand() {
75
136
  }
76
137
  }
77
138
 
139
+ // Validate types command is available
140
+ function validateTypesCommand() {
141
+ const typesCommandPath = getTypesCommandPath();
142
+ if (!existsSync(typesCommandPath)) {
143
+ console.error("Error: thinkwell installation appears to be corrupted.");
144
+ console.error(` - Types command not found: ${typesCommandPath}`);
145
+ console.error("\nTry reinstalling with: npm install thinkwell");
146
+ process.exit(1);
147
+ }
148
+ }
149
+
78
150
  // Parse version from package.json
79
151
  async function getVersion() {
80
152
  const packagePath = resolve(__dirname, "../package.json");
@@ -82,12 +154,10 @@ async function getVersion() {
82
154
  return pkg.version;
83
155
  }
84
156
 
85
- // Get the path to the init command script
86
- function getInitCommandPath() {
87
- return resolve(__dirname, "../dist/cli/init-command.js");
88
- }
157
+ // ============================================================================
158
+ // Help
159
+ // ============================================================================
89
160
 
90
- // Show help message
91
161
  function showHelp() {
92
162
  console.log(`
93
163
  thinkwell - Run TypeScript scripts with automatic schema generation
@@ -119,10 +189,132 @@ For more information, visit: https://github.com/dherman/thinkwell
119
189
  `);
120
190
  }
121
191
 
192
+ // ============================================================================
193
+ // Bundled Module Registration
194
+ // ============================================================================
195
+
196
+ /**
197
+ * Register bundled thinkwell packages to global.__bundled__.
198
+ *
199
+ * For the npm distribution, we load the pre-bundled CJS packages from dist-pkg/.
200
+ * This mirrors what main-pkg.cjs does for the pkg binary distribution.
201
+ */
202
+ async function registerBundledModules() {
203
+ const bundledPaths = getBundledPackagesPath();
204
+
205
+ try {
206
+ // Dynamic import the bundled CJS packages
207
+ const thinkwell = await import(bundledPaths.thinkwell);
208
+ const acpModule = await import(bundledPaths.acp);
209
+ const protocolModule = await import(bundledPaths.protocol);
210
+
211
+ // Register to global.__bundled__ for the loader to access
212
+ global.__bundled__ = {
213
+ thinkwell: thinkwell.default || thinkwell,
214
+ "@thinkwell/acp": acpModule.default || acpModule,
215
+ "@thinkwell/protocol": protocolModule.default || protocolModule,
216
+ };
217
+ } catch (error) {
218
+ console.error("Error: Failed to load bundled modules.");
219
+ console.error("");
220
+ if (process.env.DEBUG) {
221
+ console.error("Debug info:");
222
+ console.error(` ${error.message}`);
223
+ console.error(error.stack);
224
+ }
225
+ console.error("Try reinstalling with: npm install thinkwell");
226
+ process.exit(1);
227
+ }
228
+ }
229
+
230
+ // ============================================================================
231
+ // Script Execution
232
+ // ============================================================================
233
+
234
+ /**
235
+ * Run a user script using the CLI loader.
236
+ */
237
+ async function runUserScript(scriptPath, args) {
238
+ // Resolve the script path
239
+ const resolvedPath = isAbsolute(scriptPath)
240
+ ? scriptPath
241
+ : resolve(process.cwd(), scriptPath);
242
+
243
+ // Check if the script file exists
244
+ if (!existsSync(resolvedPath)) {
245
+ console.error(`Error: Script not found: ${scriptPath}`);
246
+ console.error("");
247
+ console.error("Make sure the file exists and the path is correct.");
248
+ process.exit(1);
249
+ }
250
+
251
+ // Import the loader
252
+ const loaderPath = getLoaderPath();
253
+ const { runScript } = await import(loaderPath);
254
+
255
+ try {
256
+ await runScript(resolvedPath, args);
257
+ } catch (error) {
258
+ // Handle common error cases with helpful messages
259
+ if (error.message && error.message.includes("Cannot find module")) {
260
+ console.error(`Error: ${error.message}`);
261
+ console.error("");
262
+ console.error("Make sure the module is installed in your project's node_modules.");
263
+ process.exit(1);
264
+ }
265
+
266
+ if (error.message && error.message.includes("Cannot find package")) {
267
+ console.error(`Error: ${error.message}`);
268
+ console.error("");
269
+ console.error("Run 'npm install' or 'pnpm install' to install dependencies.");
270
+ process.exit(1);
271
+ }
272
+
273
+ // Re-throw other errors
274
+ throw error;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Run the types command.
280
+ *
281
+ * The types command uses the same loader infrastructure to process
282
+ * TypeScript files and generate .d.ts declarations.
283
+ */
284
+ async function runTypesCommand(args) {
285
+ validateTypesCommand();
286
+ const typesCommandPath = getTypesCommandPath();
287
+
288
+ // Spawn Node.js with the types command
289
+ const child = spawn(process.execPath, [
290
+ "--experimental-transform-types",
291
+ "--disable-warning=ExperimentalWarning",
292
+ typesCommandPath,
293
+ ...args,
294
+ ], {
295
+ stdio: "inherit",
296
+ env: process.env,
297
+ });
298
+
299
+ child.on("error", (err) => {
300
+ console.error(`Error: Failed to run types command.`);
301
+ console.error(` ${err.message}`);
302
+ process.exit(1);
303
+ });
304
+
305
+ child.on("exit", (code) => {
306
+ process.exit(code ?? 0);
307
+ });
308
+ }
309
+
310
+ // ============================================================================
311
+ // Main Entry Point
312
+ // ============================================================================
313
+
122
314
  async function main() {
123
315
  const args = process.argv.slice(2);
124
316
 
125
- // Handle "init" subcommand first - does NOT require Bun
317
+ // Handle "init" subcommand first - does NOT require bundled modules
126
318
  // Must be before global --help so "init --help" shows init-specific help
127
319
  if (args[0] === "init") {
128
320
  validateInitCommand();
@@ -148,56 +340,16 @@ async function main() {
148
340
  process.exit(0);
149
341
  }
150
342
 
151
- // All commands below require Bun
152
- const bunVersion = getBunVersion();
153
- if (!bunVersion) {
154
- console.error("Error: Bun is required to run thinkwell scripts.");
155
- console.error("");
156
- console.error("The thinkwell runtime uses Bun for TypeScript execution and schema");
157
- console.error("generation. This also enables features like compiled executables.");
158
- console.error("");
159
- console.error("To install Bun:");
160
- console.error(" curl -fsSL https://bun.sh/install | bash");
161
- console.error("");
162
- console.error("Or via Homebrew:");
163
- console.error(" brew install oven-sh/bun/bun");
164
- console.error("");
165
- console.error("For more information: https://bun.sh");
166
- process.exit(1);
167
- }
168
-
169
- // Validate installation
170
- validateInstallation();
171
-
172
343
  // Handle "types" subcommand
173
344
  if (args[0] === "types") {
174
- const typesArgs = args.slice(1);
175
- const typesCommandPath = getTypesCommandPath();
176
- const bunArgs = [typesCommandPath, ...typesArgs];
177
-
178
- const child = spawn("bun", bunArgs, {
179
- stdio: "inherit",
180
- env: process.env,
181
- });
182
-
183
- child.on("error", (err) => {
184
- console.error(`Error: Failed to execute 'bun' command.`);
185
- console.error(` ${err.message}`);
186
- if (err.code === "ENOENT") {
187
- console.error("");
188
- console.error("Bun was detected but cannot be executed.");
189
- console.error("Ensure 'bun' is in your PATH and has execute permissions.");
190
- }
191
- process.exit(1);
192
- });
193
-
194
- child.on("exit", (code) => {
195
- process.exit(code ?? 0);
196
- });
197
-
345
+ await runTypesCommand(args.slice(1));
198
346
  return;
199
347
  }
200
348
 
349
+ // All script execution requires Node 24+ and proper installation
350
+ validateNodeVersion();
351
+ validateInstallation();
352
+
201
353
  // Handle "run" subcommand - just strip it
202
354
  const runArgs = args[0] === "run" ? args.slice(1) : args;
203
355
 
@@ -205,53 +357,24 @@ async function main() {
205
357
  if (runArgs.length === 0) {
206
358
  console.error("Error: No script provided.");
207
359
  console.error("");
208
- console.error("Usage: thinkwell run <script.ts> [args...]");
360
+ console.error("Usage: thinkwell <script.ts> [args...]");
209
361
  process.exit(1);
210
362
  }
211
363
 
212
- // Check if the script file exists
213
- const scriptPath = runArgs[0];
214
- if (!scriptPath.startsWith("-") && !existsSync(scriptPath)) {
215
- // Try resolving relative to cwd
216
- const resolvedPath = resolve(process.cwd(), scriptPath);
217
- if (!existsSync(resolvedPath)) {
218
- console.error(`Error: Script not found: ${scriptPath}`);
219
- console.error("");
220
- console.error("Make sure the file exists and the path is correct.");
221
- process.exit(1);
222
- }
223
- }
224
-
225
- // Delegate to bun with plugin preloaded
226
- const pluginPath = getPluginPath();
227
- const nodeModulesPath = getNodeModulesPath();
228
- const bunArgs = ["--preload", pluginPath, ...runArgs];
364
+ // Register bundled modules before loading user scripts
365
+ await registerBundledModules();
229
366
 
230
- // Add our node_modules to NODE_PATH so Bun can find thinkwell packages
231
- // regardless of where the script is located
232
- const nodePath = process.env.NODE_PATH
233
- ? `${nodeModulesPath}:${process.env.NODE_PATH}`
234
- : nodeModulesPath;
235
-
236
- const child = spawn("bun", bunArgs, {
237
- stdio: "inherit",
238
- env: { ...process.env, NODE_PATH: nodePath },
239
- });
240
-
241
- child.on("error", (err) => {
242
- console.error(`Error: Failed to execute 'bun' command.`);
243
- console.error(` ${err.message}`);
244
- if (err.code === "ENOENT") {
245
- console.error("");
246
- console.error("Bun was detected but cannot be executed.");
247
- console.error("Ensure 'bun' is in your PATH and has execute permissions.");
248
- }
249
- process.exit(1);
250
- });
251
-
252
- child.on("exit", (code) => {
253
- process.exit(code ?? 0);
254
- });
367
+ // Run the user script
368
+ const scriptPath = runArgs[0];
369
+ const scriptArgs = runArgs.slice(1);
370
+ await runUserScript(scriptPath, scriptArgs);
255
371
  }
256
372
 
257
- main();
373
+ main().catch((error) => {
374
+ console.error("Unexpected error:");
375
+ console.error(` ${error.message || error}`);
376
+ if (process.env.DEBUG) {
377
+ console.error(error.stack);
378
+ }
379
+ process.exit(1);
380
+ });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Custom script loader for the pkg-compiled binary.
3
+ *
4
+ * This module provides the runtime infrastructure for loading and executing
5
+ * user scripts in the pkg binary. It handles:
6
+ *
7
+ * 1. **Module Resolution**: Routes imports to the appropriate source:
8
+ * - `thinkwell:*` imports → bundled packages via `global.__bundled__`
9
+ * - thinkwell packages → bundled packages via `global.__bundled__`
10
+ * - External packages → user's node_modules via `require.resolve()`
11
+ *
12
+ * 2. **Import Transformation**: Rewrites user script imports before execution:
13
+ * - `import { Agent } from "thinkwell:agent"` → bundled thinkwell
14
+ * - `import { Agent } from "thinkwell"` → bundled thinkwell
15
+ *
16
+ * 3. **@JSONSchema Processing**: Generates JSON schemas for marked types and
17
+ * injects namespace declarations with SchemaProvider implementations.
18
+ *
19
+ * 4. **Script Loading**: Uses Node's require() with temp files for transformed scripts
20
+ *
21
+ * Unlike the Bun plugin which runs at bundle time, this loader operates at
22
+ * runtime when the user script is executed.
23
+ */
24
+ /**
25
+ * Global registry for bundled modules.
26
+ * This is populated by main-pkg.cjs before loading user scripts.
27
+ */
28
+ declare global {
29
+ var __bundled__: Record<string, unknown> | undefined;
30
+ }
31
+ /**
32
+ * Initialize the bundled module registry.
33
+ *
34
+ * This should be called from main-pkg.cjs with the bundled package exports.
35
+ * The registry is used by createCustomRequire to route thinkwell imports.
36
+ *
37
+ * @param modules - Map of package names to their exports
38
+ */
39
+ export declare function initializeBundledRegistry(modules: Record<string, unknown>): void;
40
+ /**
41
+ * Strip shebang line from source if present.
42
+ *
43
+ * Shebangs are valid for executable scripts but not valid JS/TS syntax.
44
+ * We need to strip them before Module._compile processes the source.
45
+ *
46
+ * @param source - The script source code
47
+ * @returns Tuple of [shebang line or empty string, rest of source]
48
+ */
49
+ export declare function extractShebang(source: string): [string, string];
50
+ /**
51
+ * Transform thinkwell:* imports to use bundled packages.
52
+ *
53
+ * Rewrites import specifiers from the thinkwell:* URI scheme to
54
+ * their corresponding npm package names.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * // Input:
59
+ * import { Agent } from "thinkwell:agent";
60
+ *
61
+ * // Output:
62
+ * import { Agent } from "thinkwell";
63
+ * ```
64
+ *
65
+ * @param source - The script source code
66
+ * @returns The source with thinkwell:* imports rewritten
67
+ */
68
+ export declare function rewriteThinkwellImports(source: string): string;
69
+ /**
70
+ * Transform imports to use the bundled module registry.
71
+ *
72
+ * In the pkg binary, we can't rely on Node's normal module resolution
73
+ * for bundled packages (they're in the /snapshot/ virtual filesystem).
74
+ * This transform rewrites imports to use global.__bundled__ directly.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // Input:
79
+ * import { Agent } from "thinkwell";
80
+ *
81
+ * // Output:
82
+ * const { Agent } = global.__bundled__["thinkwell"];
83
+ * ```
84
+ *
85
+ * @param source - The script source code
86
+ * @returns The source with bundled package imports transformed
87
+ */
88
+ export declare function transformVirtualImports(source: string): string;
89
+ /**
90
+ * Create a custom require function that routes imports appropriately.
91
+ *
92
+ * This function creates a require implementation that:
93
+ * 1. Checks bundled modules first (thinkwell packages)
94
+ * 2. Falls back to require.resolve from the script's directory
95
+ * 3. Falls back to global require for built-in modules
96
+ *
97
+ * @param scriptPath - Absolute path to the user script
98
+ * @returns A require function bound to the script's directory
99
+ */
100
+ export declare function createCustomRequire(scriptPath: string): NodeJS.Require;
101
+ /**
102
+ * Load and execute a user script with custom module resolution.
103
+ *
104
+ * This function handles two loading strategies:
105
+ *
106
+ * 1. **Direct require()** - For scripts that don't use thinkwell imports.
107
+ * This leverages Node's --experimental-strip-types for TypeScript support.
108
+ *
109
+ * 2. **Transform and compile** - For scripts that use thinkwell:* imports
110
+ * or import from bundled packages. The script is transformed and written
111
+ * to a temp file, then required (which applies type stripping).
112
+ *
113
+ * @param scriptPath - Absolute path to the script to load
114
+ * @returns The module exports from the script
115
+ * @throws Error if the script cannot be loaded or executed
116
+ */
117
+ export declare function loadScript(scriptPath: string): unknown;
118
+ /**
119
+ * Load and execute a user script, handling both sync and async exports.
120
+ *
121
+ * After loading, this function checks if the script exports a main function
122
+ * or default export and executes it if present.
123
+ *
124
+ * @param scriptPath - Absolute path to the script to load
125
+ * @param args - Additional arguments to pass to the script's argv
126
+ * @returns Promise that resolves when script execution completes
127
+ */
128
+ export declare function runScript(scriptPath: string, args?: string[]): Promise<void>;
129
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/cli/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAqCH;;;GAGG;AACH,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACtD;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAEN;AASD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAS/D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAgB9D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAqC9D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,OAAO,CAqDhB;AAuDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAgEtD;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAC7B,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,MAAM,EAAO,GAClB,OAAO,CAAC,IAAI,CAAC,CAyCf"}