skedyul 0.2.139 → 0.2.142

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/dist/.build-stamp CHANGED
@@ -1 +1 @@
1
- 1770350396098
1
+ 1770543492164
@@ -41,6 +41,7 @@ const link_1 = require("../utils/link");
41
41
  const config_1 = require("../utils/config");
42
42
  const tunnel_1 = require("../utils/tunnel");
43
43
  const readline = __importStar(require("readline"));
44
+ const z = __importStar(require("zod"));
44
45
  /**
45
46
  * Prompt the user for input
46
47
  */
@@ -95,12 +96,14 @@ async function promptInput(question, hidden = false) {
95
96
  }
96
97
  /**
97
98
  * Check if required env vars are configured, prompt if missing.
98
- * After collecting env vars, calls the install API to run the install workflow.
99
+ * Returns the env and whether the install workflow needs to run.
100
+ * The install workflow is deferred until the server is up and endpoint is registered,
101
+ * so the Temporal workflow can reach the app's /install handler.
99
102
  */
100
- async function ensureEnvConfigured(workplaceSubdomain, existingEnv, linkConfig, credentials) {
103
+ async function ensureEnvConfigured(workplaceSubdomain, existingEnv) {
101
104
  const installConfig = await (0, config_1.loadInstallConfig)();
102
105
  if (!installConfig?.env) {
103
- return existingEnv;
106
+ return { env: existingEnv, needsInstall: false };
104
107
  }
105
108
  const envFields = Object.entries(installConfig.env);
106
109
  const missingRequired = [];
@@ -111,7 +114,7 @@ async function ensureEnvConfigured(workplaceSubdomain, existingEnv, linkConfig,
111
114
  }
112
115
  }
113
116
  if (missingRequired.length === 0) {
114
- return existingEnv;
117
+ return { env: existingEnv, needsInstall: false };
115
118
  }
116
119
  // Prompt for missing env vars
117
120
  console.log(`\n⚠ Missing required environment variables for ${workplaceSubdomain}:`);
@@ -138,20 +141,7 @@ async function ensureEnvConfigured(workplaceSubdomain, existingEnv, linkConfig,
138
141
  // Save the updated env locally
139
142
  (0, link_1.saveEnvFile)(workplaceSubdomain, newEnv);
140
143
  console.log(`\n✓ Saved to .skedyul/env/${workplaceSubdomain}.env`);
141
- // Call the install API to run the install workflow with the new env vars
142
- console.log(`\nRunning installation workflow...`);
143
- try {
144
- await (0, auth_1.callCliApi)({ serverUrl: linkConfig.serverUrl, token: credentials.token }, '/install', {
145
- appVersionId: linkConfig.appVersionId,
146
- env: newEnv,
147
- });
148
- console.log(` ✓ Installation completed`);
149
- }
150
- catch (error) {
151
- console.error(` ⚠ Installation workflow failed: ${error instanceof Error ? error.message : String(error)}`);
152
- console.error(` (You can continue with local testing, but some features may not work)`);
153
- }
154
- return newEnv;
144
+ return { env: newEnv, needsInstall: true };
155
145
  }
156
146
  function printHelp() {
157
147
  console.log(`
@@ -203,6 +193,29 @@ Endpoints:
203
193
  `);
204
194
  }
205
195
  const net = __importStar(require("net"));
196
+ /**
197
+ * Convert a Zod schema to a JSON Schema representation for serialization.
198
+ * Raw Zod instances contain internal properties (functions, symbols) that
199
+ * can't be stored in the database, so we convert them first.
200
+ */
201
+ function safeInputSchemaToJson(schema) {
202
+ if (!schema)
203
+ return undefined;
204
+ // Check if it's a Zod schema (has _def property or is ZodType instance)
205
+ if (schema instanceof z.ZodType) {
206
+ try {
207
+ return z.toJSONSchema(schema, { unrepresentable: 'any' });
208
+ }
209
+ catch {
210
+ return undefined;
211
+ }
212
+ }
213
+ // Already a plain object (JSON Schema), return as-is
214
+ if (typeof schema === 'object') {
215
+ return schema;
216
+ }
217
+ return undefined;
218
+ }
206
219
  const HEARTBEAT_INTERVAL_MS = 30 * 1000; // 30 seconds
207
220
  const DEFAULT_PORT = 60000;
208
221
  const MAX_PORT_ATTEMPTS = 100;
@@ -289,6 +302,7 @@ async function serveCommand(args) {
289
302
  let credentials = null;
290
303
  let tunnel = null;
291
304
  let heartbeatInterval = null;
305
+ let envResult = { env: {}, needsInstall: false };
292
306
  if (isLinked && workplaceSubdomain) {
293
307
  // Check authentication
294
308
  credentials = (0, auth_1.getCredentials)();
@@ -356,7 +370,8 @@ async function serveCommand(args) {
356
370
  console.log(` AppVersion: ${linkConfig.appVersionHandle}`);
357
371
  // Load env vars for this workplace, prompt for missing required vars
358
372
  let linkedEnv = (0, link_1.loadEnvFile)(workplaceSubdomain);
359
- linkedEnv = await ensureEnvConfigured(workplaceSubdomain, linkedEnv, linkConfig, credentials);
373
+ envResult = await ensureEnvConfigured(workplaceSubdomain, linkedEnv);
374
+ linkedEnv = envResult.env;
360
375
  const envCount = Object.keys(linkedEnv).length;
361
376
  if (envCount > 0) {
362
377
  console.log(`\nLoading env from .skedyul/env/${workplaceSubdomain}.env`);
@@ -380,11 +395,11 @@ async function serveCommand(args) {
380
395
  if (isLinked) {
381
396
  const appConfig = await (0, config_1.loadAppConfig)();
382
397
  const installConfig = await (0, config_1.loadInstallConfig)();
383
- // Build tool list with metadata
398
+ // Build tool list with metadata (convert Zod schemas to JSON Schema for serialization)
384
399
  const tools = Object.entries(registry).map(([name, tool]) => ({
385
400
  name,
386
401
  description: tool.description,
387
- inputSchema: tool.inputSchema,
402
+ inputSchema: safeInputSchemaToJson(tool.inputSchema),
388
403
  }));
389
404
  executableConfig = {
390
405
  name: appConfig?.name,
@@ -400,6 +415,18 @@ async function serveCommand(args) {
400
415
  console.log(` Tools: ${tools.length}`);
401
416
  console.log(` Install env: ${installConfig?.env ? Object.keys(installConfig.env).length : 0} variables`);
402
417
  }
418
+ // Load install handler if available
419
+ let installHandler;
420
+ try {
421
+ const handler = await (0, config_1.loadInstallHandler)();
422
+ if (handler) {
423
+ installHandler = handler;
424
+ console.log(`\n✓ Loaded install handler`);
425
+ }
426
+ }
427
+ catch (error) {
428
+ console.warn(`Could not load install handler: ${error instanceof Error ? error.message : String(error)}`);
429
+ }
403
430
  // Create server
404
431
  const server = (0, server_1.createSkedyulServer)({
405
432
  computeLayer: 'dedicated',
@@ -408,6 +435,7 @@ async function serveCommand(args) {
408
435
  version: serverVersion,
409
436
  },
410
437
  defaultPort: port,
438
+ hooks: installHandler ? { install: installHandler } : undefined,
411
439
  }, registry);
412
440
  // Start listening
413
441
  const dedicatedServer = server;
@@ -474,18 +502,52 @@ async function serveCommand(args) {
474
502
  // Register endpoint with Skedyul
475
503
  console.log(`\nRegistering endpoint with Skedyul...`);
476
504
  try {
477
- await (0, auth_1.callCliApi)({ serverUrl: linkConfig.serverUrl, token: credentials.token }, '/register-endpoint', {
505
+ const registerUrl = `${linkConfig.serverUrl}/api/cli/register-endpoint`;
506
+ const registerBody = {
478
507
  appVersionId: linkConfig.appVersionId,
479
508
  invokeEndpoint,
480
509
  config: executableConfig,
510
+ };
511
+ const registerResponse = await fetch(registerUrl, {
512
+ method: 'POST',
513
+ headers: {
514
+ 'Content-Type': 'application/json',
515
+ 'Authorization': `Bearer ${credentials.token}`,
516
+ },
517
+ body: JSON.stringify(registerBody),
481
518
  });
482
- console.log(` ✓ Registered as invokeEndpoint for ${linkConfig.appVersionHandle}`);
483
- console.log(` ✓ Synced ${toolCount} tools to Skedyul`);
519
+ if (!registerResponse.ok) {
520
+ const responseText = await registerResponse.text();
521
+ console.error(`Failed to register endpoint (HTTP ${registerResponse.status}):`);
522
+ // Show first 500 chars of response body for debugging
523
+ console.error(` Response: ${responseText.substring(0, 500)}`);
524
+ }
525
+ else {
526
+ console.log(` ✓ Registered as invokeEndpoint for ${linkConfig.appVersionHandle}`);
527
+ console.log(` ✓ Synced ${toolCount} tools to Skedyul`);
528
+ }
484
529
  }
485
530
  catch (error) {
486
531
  console.error(`Failed to register endpoint: ${error instanceof Error ? error.message : String(error)}`);
487
532
  // Continue anyway, might be a temporary issue
488
533
  }
534
+ // Run deferred install workflow now that the server is up and endpoint is registered.
535
+ // The Temporal workflow calls the app's /install handler via HTTP, so the server
536
+ // must be reachable first.
537
+ if (envResult.needsInstall) {
538
+ console.log(`\nRunning installation workflow...`);
539
+ try {
540
+ await (0, auth_1.callCliApi)({ serverUrl: linkConfig.serverUrl, token: credentials.token }, '/install', {
541
+ appVersionId: linkConfig.appVersionId,
542
+ env: envResult.env,
543
+ });
544
+ console.log(` ✓ Installation completed`);
545
+ }
546
+ catch (error) {
547
+ console.error(` ⚠ Installation workflow failed: ${error instanceof Error ? error.message : String(error)}`);
548
+ console.error(` (You can continue with local testing, but some features may not work)`);
549
+ }
550
+ }
489
551
  // Start heartbeat (also syncs config on each heartbeat for hot-reload)
490
552
  const sendHeartbeat = async () => {
491
553
  try {
@@ -495,7 +557,7 @@ async function serveCommand(args) {
495
557
  const freshTools = Object.entries(registry).map(([name, tool]) => ({
496
558
  name,
497
559
  description: tool.description,
498
- inputSchema: tool.inputSchema,
560
+ inputSchema: safeInputSchemaToJson(tool.inputSchema),
499
561
  }));
500
562
  const freshConfig = {
501
563
  name: freshAppConfig?.name,
@@ -28,4 +28,9 @@ export interface InstallConfigData {
28
28
  onUninstall?: string;
29
29
  }
30
30
  export declare function loadInstallConfig(projectDir?: string, debug?: boolean): Promise<InstallConfigData | null>;
31
+ /**
32
+ * Find and load the install handler from the project directory.
33
+ * Returns the default export (install handler function) or null if not found.
34
+ */
35
+ export declare function loadInstallHandler(projectDir?: string): Promise<((ctx: unknown) => Promise<unknown>) | null>;
31
36
  export declare function findRegistryPath(projectDir?: string): string | null;
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.findConfigFile = findConfigFile;
37
37
  exports.loadAppConfig = loadAppConfig;
38
38
  exports.loadInstallConfig = loadInstallConfig;
39
+ exports.loadInstallHandler = loadInstallHandler;
39
40
  exports.findRegistryPath = findRegistryPath;
40
41
  const fs = __importStar(require("fs"));
41
42
  const path = __importStar(require("path"));
@@ -316,6 +317,50 @@ function parseInstallConfigFromSource(content) {
316
317
  }
317
318
  }
318
319
  // ─────────────────────────────────────────────────────────────────────────────
320
+ // Install Handler Loading
321
+ // ─────────────────────────────────────────────────────────────────────────────
322
+ const INSTALL_HANDLER_PATHS = [
323
+ // Source files (dev mode)
324
+ 'src/install.ts',
325
+ 'src/install.js',
326
+ // Compiled output (production)
327
+ 'dist/install.js',
328
+ 'build/install.js',
329
+ ];
330
+ /**
331
+ * Find and load the install handler from the project directory.
332
+ * Returns the default export (install handler function) or null if not found.
333
+ */
334
+ async function loadInstallHandler(projectDir) {
335
+ const dir = projectDir ?? process.cwd();
336
+ for (const handlerPath of INSTALL_HANDLER_PATHS) {
337
+ const fullPath = path.join(dir, handlerPath);
338
+ if (!fs.existsSync(fullPath)) {
339
+ continue;
340
+ }
341
+ try {
342
+ let module;
343
+ if (fullPath.endsWith('.ts')) {
344
+ // Use tsx loader for TypeScript files
345
+ const { loadTypeScriptFile } = await Promise.resolve().then(() => __importStar(require('../utils')));
346
+ module = (await loadTypeScriptFile(fullPath));
347
+ }
348
+ else {
349
+ module = await Promise.resolve(`${fullPath}`).then(s => __importStar(require(s)));
350
+ }
351
+ const handler = module.default;
352
+ if (typeof handler === 'function') {
353
+ return handler;
354
+ }
355
+ }
356
+ catch (error) {
357
+ console.warn(`[loadInstallHandler] Failed to load ${handlerPath}: ${error instanceof Error ? error.message : String(error)}`);
358
+ continue;
359
+ }
360
+ }
361
+ return null;
362
+ }
363
+ // ─────────────────────────────────────────────────────────────────────────────
319
364
  // Registry Path Detection
320
365
  // ─────────────────────────────────────────────────────────────────────────────
321
366
  // Check source files first (for dev), then compiled output
@@ -15,6 +15,10 @@ export declare function parseEnvFlags(args: string[]): Record<string, string>;
15
15
  * Load environment variables from a .env file
16
16
  */
17
17
  export declare function loadEnvFile(filePath: string): Record<string, string>;
18
+ /**
19
+ * Load a TypeScript file using tsx's require hook
20
+ */
21
+ export declare function loadTypeScriptFile(absolutePath: string): Promise<unknown>;
18
22
  /**
19
23
  * Load a registry from a JS/TS file
20
24
  */
package/dist/cli/utils.js CHANGED
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.parseArgs = parseArgs;
37
37
  exports.parseEnvFlags = parseEnvFlags;
38
38
  exports.loadEnvFile = loadEnvFile;
39
+ exports.loadTypeScriptFile = loadTypeScriptFile;
39
40
  exports.loadRegistry = loadRegistry;
40
41
  exports.formatJson = formatJson;
41
42
  const fs = __importStar(require("fs"));
package/dist/server.js CHANGED
@@ -257,13 +257,17 @@ async function handleCoreMethod(method, params) {
257
257
  };
258
258
  }
259
259
  function buildToolMetadata(registry) {
260
- return Object.values(registry).map((tool) => ({
261
- name: tool.name,
262
- displayName: tool.label || tool.name,
263
- description: tool.description,
264
- inputSchema: getJsonSchemaFromToolSchema(tool.inputSchema),
265
- outputSchema: getJsonSchemaFromToolSchema(tool.outputSchema),
266
- }));
260
+ return Object.values(registry).map((tool) => {
261
+ const timeout = typeof tool.timeout === 'number' && tool.timeout > 0 ? tool.timeout : 10000;
262
+ return {
263
+ name: tool.name,
264
+ displayName: tool.label || tool.name,
265
+ description: tool.description,
266
+ inputSchema: getJsonSchemaFromToolSchema(tool.inputSchema),
267
+ outputSchema: getJsonSchemaFromToolSchema(tool.outputSchema),
268
+ timeout, // Default to 10 seconds
269
+ };
270
+ });
267
271
  }
268
272
  function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNames) {
269
273
  let requestCount = 0;
@@ -909,8 +913,12 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
909
913
  '',
910
914
  };
911
915
  try {
916
+ const installHook = config.hooks.install;
917
+ const installHandler = typeof installHook === 'function'
918
+ ? installHook
919
+ : installHook.handler;
912
920
  const result = await (0, client_1.runWithConfig)(installRequestConfig, async () => {
913
- return await config.hooks.install(installContext);
921
+ return await installHandler(installContext);
914
922
  });
915
923
  sendJSON(res, 200, {
916
924
  env: result.env ?? {},
@@ -973,8 +981,12 @@ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer
973
981
  apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? '',
974
982
  };
975
983
  try {
984
+ const provisionHook = config.hooks.provision;
985
+ const provisionHandler = typeof provisionHook === 'function'
986
+ ? provisionHook
987
+ : provisionHook.handler;
976
988
  const result = await (0, client_1.runWithConfig)(provisionRequestConfig, async () => {
977
- return await config.hooks.provision(provisionContext);
989
+ return await provisionHandler(provisionContext);
978
990
  });
979
991
  sendJSON(res, 200, result);
980
992
  }
@@ -1441,8 +1453,12 @@ function createServerlessInstance(config, tools, callTool, state, mcpServer, reg
1441
1453
  '',
1442
1454
  };
1443
1455
  try {
1456
+ const installHook = config.hooks.install;
1457
+ const installHandler = typeof installHook === 'function'
1458
+ ? installHook
1459
+ : installHook.handler;
1444
1460
  const result = await (0, client_1.runWithConfig)(installRequestConfig, async () => {
1445
- return await config.hooks.install(installContext);
1461
+ return await installHandler(installContext);
1446
1462
  });
1447
1463
  return createResponse(200, { env: result.env ?? {}, redirect: result.redirect }, headers);
1448
1464
  }
package/dist/types.d.ts CHANGED
@@ -131,6 +131,8 @@ export interface ToolDefinition<Input = unknown, Output = unknown, InputSchema e
131
131
  inputSchema: ToolSchema<InputSchema>;
132
132
  handler: ToolHandler<Input, Output>;
133
133
  outputSchema?: ToolSchema<OutputSchema>;
134
+ /** Timeout in milliseconds. Defaults to 10000 (10 seconds) if not specified. */
135
+ timeout?: number;
134
136
  [key: string]: unknown;
135
137
  }
136
138
  export interface ToolRegistryEntry {
@@ -150,6 +152,8 @@ export interface ToolMetadata {
150
152
  description: string;
151
153
  inputSchema?: Record<string, unknown>;
152
154
  outputSchema?: Record<string, unknown>;
155
+ /** Timeout in milliseconds. Defaults to 10000 (10 seconds) if not specified. */
156
+ timeout?: number;
153
157
  }
154
158
  export interface HealthStatus {
155
159
  status: 'running';
@@ -177,9 +181,17 @@ export interface CorsOptions {
177
181
  */
178
182
  export interface ServerHooks {
179
183
  /** Called during app installation to validate/normalize env and perform setup */
180
- install?: InstallHandler;
184
+ install?: InstallHandler | {
185
+ handler: InstallHandler;
186
+ /** Timeout in milliseconds. Defaults to 60000 (1 minute) if not specified. */
187
+ timeout?: number;
188
+ };
181
189
  /** Called after app version provisioning to set up version-level resources */
182
- provision?: ProvisionHandler;
190
+ provision?: ProvisionHandler | {
191
+ handler: ProvisionHandler;
192
+ /** Timeout in milliseconds. Defaults to 300000 (5 minutes) if not specified. */
193
+ timeout?: number;
194
+ };
183
195
  }
184
196
  export interface SkedyulServerConfig {
185
197
  computeLayer: ComputeLayer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skedyul",
3
- "version": "0.2.139",
3
+ "version": "0.2.142",
4
4
  "description": "The Skedyul SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",