@jaypie/mcp 0.2.3 → 0.2.4
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/index.js +1 -1
- package/package.json +1 -1
- package/prompts/Jaypie_CDK_Constructs_and_Patterns.md +51 -0
- package/prompts/Jaypie_DynamoDB_Package.md +547 -0
- package/prompts/Jaypie_Express_Package.md +91 -0
- package/prompts/Jaypie_Init_Lambda_Package.md +134 -1
- package/prompts/Jaypie_Llm_Calls.md +77 -0
- package/prompts/Jaypie_Llm_Tools.md +26 -3
- package/prompts/Jaypie_Vocabulary_Commander.md +411 -0
- package/prompts/Jaypie_Vocabulary_LLM.md +312 -0
- package/prompts/Jaypie_Vocabulary_Lambda.md +310 -0
- package/prompts/Jaypie_Vocabulary_MCP.md +296 -0
- package/prompts/Jaypie_Vocabulary_Package.md +141 -183
|
@@ -361,4 +361,137 @@ Deploy using AWS CDK or other deployment tool. The Lambda handler will be refere
|
|
|
361
361
|
- Use double quotes, trailing commas, semicolons
|
|
362
362
|
- Alphabetize imports and properties
|
|
363
363
|
- Define constants for hard-coded values at file top
|
|
364
|
-
- Never throw vanilla Error; use errors from `@jaypie/errors`
|
|
364
|
+
- Never throw vanilla Error; use errors from `@jaypie/errors`
|
|
365
|
+
|
|
366
|
+
## Streaming Lambda Functions
|
|
367
|
+
|
|
368
|
+
Use `lambdaStreamHandler` for AWS Lambda Response Streaming. This enables real-time streaming responses for LLM interactions, large file processing, and SSE endpoints.
|
|
369
|
+
|
|
370
|
+
### Lambda Streaming Setup
|
|
371
|
+
|
|
372
|
+
Create a streaming Lambda handler with `awslambda.streamifyResponse`:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// src/streamWorker.ts
|
|
376
|
+
import { log } from "@jaypie/core";
|
|
377
|
+
import { lambdaStreamHandler, createLambdaStream, Llm } from "jaypie";
|
|
378
|
+
import type { StreamHandlerContext } from "@jaypie/lambda";
|
|
379
|
+
|
|
380
|
+
export interface StreamWorkerEvent {
|
|
381
|
+
prompt?: string;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const streamWorker = lambdaStreamHandler(
|
|
385
|
+
async (event: StreamWorkerEvent, context: StreamHandlerContext) => {
|
|
386
|
+
log.trace("streamWorker: start");
|
|
387
|
+
|
|
388
|
+
const llm = new Llm("anthropic");
|
|
389
|
+
const stream = llm.stream(event.prompt || "Hello");
|
|
390
|
+
|
|
391
|
+
// createLambdaStream pipes LLM chunks as SSE events
|
|
392
|
+
await createLambdaStream(stream, context.responseStream);
|
|
393
|
+
|
|
394
|
+
log.trace("streamWorker: complete");
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "streamWorker",
|
|
398
|
+
contentType: "text/event-stream",
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// Wrap with AWS streamifyResponse
|
|
403
|
+
declare const awslambda: { streamifyResponse: <T>(handler: T) => T };
|
|
404
|
+
export const handler = awslambda.streamifyResponse(streamWorker);
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Manual Stream Writing
|
|
408
|
+
|
|
409
|
+
Write directly to the response stream for custom SSE events:
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
import { lambdaStreamHandler } from "jaypie";
|
|
413
|
+
import type { StreamHandlerContext } from "@jaypie/lambda";
|
|
414
|
+
|
|
415
|
+
const manualStreamHandler = lambdaStreamHandler(
|
|
416
|
+
async (event: unknown, context: StreamHandlerContext) => {
|
|
417
|
+
const { responseStream } = context;
|
|
418
|
+
|
|
419
|
+
// Write SSE events directly
|
|
420
|
+
responseStream.write("event: start\ndata: {\"status\": \"processing\"}\n\n");
|
|
421
|
+
|
|
422
|
+
// Process data in chunks
|
|
423
|
+
for (const item of items) {
|
|
424
|
+
const result = await process(item);
|
|
425
|
+
responseStream.write(`event: data\ndata: ${JSON.stringify(result)}\n\n`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
responseStream.write("event: done\ndata: {\"status\": \"complete\"}\n\n");
|
|
429
|
+
// Handler automatically calls responseStream.end()
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "manualStream",
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Stream Handler Options
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import type { LambdaStreamHandlerOptions } from "@jaypie/lambda";
|
|
441
|
+
|
|
442
|
+
const options: LambdaStreamHandlerOptions = {
|
|
443
|
+
name: "myStreamHandler", // Handler name for logging
|
|
444
|
+
contentType: "text/event-stream", // Response content type (default)
|
|
445
|
+
chaos: "low", // Chaos testing level
|
|
446
|
+
secrets: ["API_KEY"], // AWS secrets to load into process.env
|
|
447
|
+
setup: [], // Setup function(s)
|
|
448
|
+
teardown: [], // Teardown function(s)
|
|
449
|
+
validate: [], // Validation function(s)
|
|
450
|
+
throw: false, // Re-throw errors instead of SSE error
|
|
451
|
+
unavailable: false, // Return 503 if true
|
|
452
|
+
};
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Stream Handler Types
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
import type {
|
|
459
|
+
LambdaStreamHandlerOptions,
|
|
460
|
+
StreamHandlerContext,
|
|
461
|
+
ResponseStream,
|
|
462
|
+
AwsStreamingHandler,
|
|
463
|
+
} from "@jaypie/lambda";
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### CDK Configuration for Streaming
|
|
467
|
+
|
|
468
|
+
Enable Lambda Response Streaming via Function URL in CDK:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { JaypieLambda } from "@jaypie/constructs";
|
|
472
|
+
import { FunctionUrlAuthType, InvokeMode } from "aws-cdk-lib/aws-lambda";
|
|
473
|
+
|
|
474
|
+
const streamingLambda = new JaypieLambda(this, "StreamingFunction", {
|
|
475
|
+
code: "dist",
|
|
476
|
+
handler: "streamWorker.handler",
|
|
477
|
+
timeout: Duration.minutes(5),
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Add Function URL with streaming enabled
|
|
481
|
+
streamingLambda.addFunctionUrl({
|
|
482
|
+
authType: FunctionUrlAuthType.NONE, // or AWS_IAM for auth
|
|
483
|
+
invokeMode: InvokeMode.RESPONSE_STREAM,
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Error Handling in Streams
|
|
488
|
+
|
|
489
|
+
Errors are formatted as SSE error events:
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
// Jaypie errors written as:
|
|
493
|
+
// event: error
|
|
494
|
+
// data: {"errors":[{"status":500,"title":"Internal Error"}]}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Set `throw: true` to re-throw errors instead of writing to stream.
|
|
@@ -312,6 +312,60 @@ type LlmStreamChunk =
|
|
|
312
312
|
| LlmStreamChunkError; // { type: "error", error: { status, title, detail? } }
|
|
313
313
|
```
|
|
314
314
|
|
|
315
|
+
### Streaming to Express
|
|
316
|
+
|
|
317
|
+
Use `createExpressStream` to pipe LLM streams to Express responses:
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
import { expressStreamHandler, Llm, createExpressStream } from "jaypie";
|
|
321
|
+
|
|
322
|
+
const chatRoute = expressStreamHandler(async (req, res) => {
|
|
323
|
+
const llm = new Llm("anthropic");
|
|
324
|
+
const stream = llm.stream(req.body.prompt);
|
|
325
|
+
await createExpressStream(stream, res);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
app.post("/chat", chatRoute);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Streaming to Lambda
|
|
332
|
+
|
|
333
|
+
Use `createLambdaStream` with Lambda Response Streaming:
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
import { lambdaStreamHandler, Llm, createLambdaStream } from "jaypie";
|
|
337
|
+
|
|
338
|
+
const handler = awslambda.streamifyResponse(
|
|
339
|
+
lambdaStreamHandler(async (event, context) => {
|
|
340
|
+
const llm = new Llm("openai");
|
|
341
|
+
const stream = llm.stream(event.prompt);
|
|
342
|
+
await createLambdaStream(stream, context.responseStream);
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### JaypieStream Wrapper
|
|
348
|
+
|
|
349
|
+
Use `JaypieStream` or `createJaypieStream` for fluent piping:
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
import { createJaypieStream, Llm } from "jaypie";
|
|
353
|
+
|
|
354
|
+
const llm = new Llm("gemini");
|
|
355
|
+
const stream = createJaypieStream(llm.stream("Hello"));
|
|
356
|
+
|
|
357
|
+
// Pipe to Express
|
|
358
|
+
await stream.toExpress(res);
|
|
359
|
+
|
|
360
|
+
// Or pipe to Lambda
|
|
361
|
+
await stream.toLambda(responseStream);
|
|
362
|
+
|
|
363
|
+
// Or iterate manually
|
|
364
|
+
for await (const chunk of stream) {
|
|
365
|
+
console.log(chunk);
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
315
369
|
## Hooks
|
|
316
370
|
|
|
317
371
|
Use hooks to intercept and observe the LLM lifecycle:
|
|
@@ -365,6 +419,29 @@ const result = await llm.operate("Roll dice and check weather", {
|
|
|
365
419
|
});
|
|
366
420
|
```
|
|
367
421
|
|
|
422
|
+
### Zod Schema Support
|
|
423
|
+
|
|
424
|
+
Tool parameters can be defined using Zod schemas instead of JSON Schema:
|
|
425
|
+
|
|
426
|
+
```javascript
|
|
427
|
+
import { z } from "zod/v4";
|
|
428
|
+
import { Llm, Toolkit } from "jaypie";
|
|
429
|
+
|
|
430
|
+
const weatherTool = {
|
|
431
|
+
name: "get_weather",
|
|
432
|
+
description: "Get weather for a city",
|
|
433
|
+
parameters: z.object({
|
|
434
|
+
city: z.string().describe("City name"),
|
|
435
|
+
unit: z.enum(["celsius", "fahrenheit"]),
|
|
436
|
+
}),
|
|
437
|
+
type: "function",
|
|
438
|
+
call: async ({ city, unit }) => ({ city, temp: 72, unit }),
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const toolkit = new Toolkit([weatherTool]);
|
|
442
|
+
// Zod schemas are automatically converted to JSON Schema
|
|
443
|
+
```
|
|
444
|
+
|
|
368
445
|
## Footnotes
|
|
369
446
|
|
|
370
447
|
Llm.operate(input, options)
|
|
@@ -16,10 +16,12 @@ Create and integrate tools that enable LLMs to perform specific functions beyond
|
|
|
16
16
|
Implement the `LlmTool` interface:
|
|
17
17
|
|
|
18
18
|
```typescript
|
|
19
|
+
import { z } from "zod/v4";
|
|
20
|
+
|
|
19
21
|
interface LlmTool {
|
|
20
22
|
description: string;
|
|
21
23
|
name: string;
|
|
22
|
-
parameters: JsonObject;
|
|
24
|
+
parameters: JsonObject | z.ZodType; // JSON Schema or Zod schema
|
|
23
25
|
type: "function" | string;
|
|
24
26
|
call: (args?: JsonObject) => Promise<AnyValue> | AnyValue;
|
|
25
27
|
}
|
|
@@ -28,11 +30,11 @@ interface LlmTool {
|
|
|
28
30
|
Properties:
|
|
29
31
|
- `description`: Clear explanation of tool functionality
|
|
30
32
|
- `name`: Unique identifier
|
|
31
|
-
- `parameters`: JSON Schema defining input parameters
|
|
33
|
+
- `parameters`: JSON Schema or Zod schema defining input parameters
|
|
32
34
|
- `type`: Usually "function" (OpenAI convention)
|
|
33
35
|
- `call`: Implementation function executed on invocation
|
|
34
36
|
|
|
35
|
-
## Example: Dice Roller
|
|
37
|
+
## Example: Dice Roller (JSON Schema)
|
|
36
38
|
|
|
37
39
|
```typescript
|
|
38
40
|
import { LlmTool } from "../types/LlmTool.interface.js";
|
|
@@ -84,6 +86,27 @@ export const roll: LlmTool = {
|
|
|
84
86
|
};
|
|
85
87
|
```
|
|
86
88
|
|
|
89
|
+
## Example: Weather Tool (Zod Schema)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { z } from "zod/v4";
|
|
93
|
+
import { LlmTool } from "jaypie";
|
|
94
|
+
|
|
95
|
+
export const getWeather: LlmTool = {
|
|
96
|
+
description: "Get current weather for a city",
|
|
97
|
+
name: "get_weather",
|
|
98
|
+
parameters: z.object({
|
|
99
|
+
city: z.string().describe("City name"),
|
|
100
|
+
unit: z.enum(["celsius", "fahrenheit"]).describe("Temperature unit"),
|
|
101
|
+
}),
|
|
102
|
+
type: "function",
|
|
103
|
+
call: async ({ city, unit }) => {
|
|
104
|
+
// Implementation here
|
|
105
|
+
return { city, temperature: 72, unit };
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
87
110
|
## Best Practices
|
|
88
111
|
|
|
89
112
|
### Input Validation
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Commander.js CLI integration with serviceHandler callbacks (onMessage, onComplete, onError, onFatal)
|
|
3
|
+
include: "**/cli/**"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Jaypie Vocabulary Commander Adapter
|
|
7
|
+
|
|
8
|
+
The Commander adapter (`@jaypie/vocabulary/commander`) integrates Jaypie service handlers with Commander.js CLI applications, providing automatic option generation, type coercion, and callback hooks for progress reporting.
|
|
9
|
+
|
|
10
|
+
**See also:** [Jaypie_Vocabulary_Package.md](Jaypie_Vocabulary_Package.md) for core serviceHandler documentation.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @jaypie/vocabulary commander
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Command } from "commander";
|
|
22
|
+
import { serviceHandler } from "@jaypie/vocabulary";
|
|
23
|
+
import { registerServiceCommand } from "@jaypie/vocabulary/commander";
|
|
24
|
+
|
|
25
|
+
const handler = serviceHandler({
|
|
26
|
+
alias: "greet",
|
|
27
|
+
description: "Greet a user",
|
|
28
|
+
input: {
|
|
29
|
+
userName: { type: String, flag: "user", letter: "u" },
|
|
30
|
+
loud: { type: Boolean, letter: "l", default: false },
|
|
31
|
+
},
|
|
32
|
+
service: ({ loud, userName }) => {
|
|
33
|
+
const greeting = `Hello, ${userName}!`;
|
|
34
|
+
console.log(loud ? greeting.toUpperCase() : greeting);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const program = new Command();
|
|
39
|
+
registerServiceCommand({ handler, program });
|
|
40
|
+
program.parse();
|
|
41
|
+
// Usage: greet --user Alice -l
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## registerServiceCommand
|
|
45
|
+
|
|
46
|
+
The primary function for registering a service handler as a Commander command.
|
|
47
|
+
|
|
48
|
+
### Options
|
|
49
|
+
|
|
50
|
+
| Option | Type | Description |
|
|
51
|
+
|--------|------|-------------|
|
|
52
|
+
| `handler` | `ServiceHandlerFunction` | Required. The service handler to register |
|
|
53
|
+
| `program` | `Command` | Required. Commander program or command |
|
|
54
|
+
| `name` | `string` | Override command name (default: handler.alias) |
|
|
55
|
+
| `description` | `string` | Override description (default: handler.description) |
|
|
56
|
+
| `exclude` | `string[]` | Field names to exclude from CLI options |
|
|
57
|
+
| `onComplete` | `OnCompleteCallback` | Called with handler's return value on success |
|
|
58
|
+
| `onError` | `OnErrorCallback` | Receives errors reported via `context.onError()` |
|
|
59
|
+
| `onFatal` | `OnFatalCallback` | Receives fatal errors (thrown or via `context.onFatal()`) |
|
|
60
|
+
| `onMessage` | `OnMessageCallback` | Receives messages from `context.sendMessage` |
|
|
61
|
+
| `overrides` | `Record<string, override>` | Per-field option overrides |
|
|
62
|
+
|
|
63
|
+
## Callback Hooks
|
|
64
|
+
|
|
65
|
+
### onMessage
|
|
66
|
+
|
|
67
|
+
Receives progress messages sent by the service via `context.sendMessage`:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { Command } from "commander";
|
|
71
|
+
import { serviceHandler } from "@jaypie/vocabulary";
|
|
72
|
+
import { registerServiceCommand } from "@jaypie/vocabulary/commander";
|
|
73
|
+
|
|
74
|
+
const handler = serviceHandler({
|
|
75
|
+
alias: "process",
|
|
76
|
+
input: { jobId: { type: String, letter: "j" } },
|
|
77
|
+
service: async ({ jobId }, context) => {
|
|
78
|
+
// Send progress messages during execution
|
|
79
|
+
context?.sendMessage?.({ content: `Starting job ${jobId}` });
|
|
80
|
+
|
|
81
|
+
// Simulate work
|
|
82
|
+
await doStep1();
|
|
83
|
+
context?.sendMessage?.({ content: "Step 1 complete", level: "debug" });
|
|
84
|
+
|
|
85
|
+
await doStep2();
|
|
86
|
+
context?.sendMessage?.({ content: "Step 2 complete", level: "debug" });
|
|
87
|
+
|
|
88
|
+
context?.sendMessage?.({ content: "Job finished!", level: "info" });
|
|
89
|
+
return { jobId, status: "complete" };
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const program = new Command();
|
|
94
|
+
registerServiceCommand({
|
|
95
|
+
handler,
|
|
96
|
+
program,
|
|
97
|
+
onMessage: (msg) => {
|
|
98
|
+
// msg: { content: string, level?: "trace"|"debug"|"info"|"warn"|"error" }
|
|
99
|
+
const level = msg.level || "info";
|
|
100
|
+
console[level](msg.content);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
program.parse();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Important:** Errors in `onMessage` are swallowed to ensure messaging failures never halt service execution.
|
|
107
|
+
|
|
108
|
+
### onComplete
|
|
109
|
+
|
|
110
|
+
Called with the handler's return value on successful completion:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
registerServiceCommand({
|
|
114
|
+
handler,
|
|
115
|
+
program,
|
|
116
|
+
onComplete: (response) => {
|
|
117
|
+
// Called after service completes successfully
|
|
118
|
+
console.log("Result:", JSON.stringify(response, null, 2));
|
|
119
|
+
|
|
120
|
+
// Common patterns:
|
|
121
|
+
// - Save results to file
|
|
122
|
+
// - Display formatted output
|
|
123
|
+
// - Trigger follow-up actions
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### onError
|
|
129
|
+
|
|
130
|
+
Receives errors that the service explicitly reports via `context.onError()`. Use this for recoverable errors that don't halt execution:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
registerServiceCommand({
|
|
134
|
+
handler,
|
|
135
|
+
program,
|
|
136
|
+
onError: (error) => {
|
|
137
|
+
// Log recoverable errors
|
|
138
|
+
console.warn("Warning:", error.message);
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### onFatal
|
|
144
|
+
|
|
145
|
+
Receives fatal errors - either thrown errors or errors reported via `context.onFatal()`. Any error that escapes the service (is thrown) is treated as fatal:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
registerServiceCommand({
|
|
149
|
+
handler,
|
|
150
|
+
program,
|
|
151
|
+
onFatal: (error) => {
|
|
152
|
+
console.error("Fatal error:", error.message);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Error handling priority:**
|
|
159
|
+
1. If `onFatal` is provided, thrown errors go to `onFatal`
|
|
160
|
+
2. If only `onError` is provided, thrown errors fall back to `onError`
|
|
161
|
+
3. If neither is provided, errors are re-thrown
|
|
162
|
+
|
|
163
|
+
## Complete Example with All Callbacks
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { Command } from "commander";
|
|
167
|
+
import { serviceHandler } from "@jaypie/vocabulary";
|
|
168
|
+
import { registerServiceCommand } from "@jaypie/vocabulary/commander";
|
|
169
|
+
|
|
170
|
+
const evaluateHandler = serviceHandler({
|
|
171
|
+
alias: "evaluate",
|
|
172
|
+
description: "Run an evaluation job",
|
|
173
|
+
input: {
|
|
174
|
+
jobId: {
|
|
175
|
+
type: String,
|
|
176
|
+
flag: "job",
|
|
177
|
+
letter: "j",
|
|
178
|
+
description: "Job identifier",
|
|
179
|
+
},
|
|
180
|
+
priority: {
|
|
181
|
+
type: [1, 2, 3, 4, 5],
|
|
182
|
+
default: 3,
|
|
183
|
+
letter: "p",
|
|
184
|
+
description: "Job priority (1-5)",
|
|
185
|
+
},
|
|
186
|
+
tags: {
|
|
187
|
+
type: [String],
|
|
188
|
+
letter: "t",
|
|
189
|
+
required: false,
|
|
190
|
+
description: "Tags to apply",
|
|
191
|
+
},
|
|
192
|
+
dryRun: {
|
|
193
|
+
type: Boolean,
|
|
194
|
+
letter: "d",
|
|
195
|
+
default: false,
|
|
196
|
+
description: "Run without executing",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
service: async ({ dryRun, jobId, priority, tags }, context) => {
|
|
200
|
+
context?.sendMessage?.({ content: `Initializing job ${jobId}...` });
|
|
201
|
+
|
|
202
|
+
if (dryRun) {
|
|
203
|
+
context?.sendMessage?.({ content: "Dry run mode - skipping execution", level: "warn" });
|
|
204
|
+
return { jobId, status: "dry-run", skipped: true };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Handle recoverable errors without throwing
|
|
208
|
+
try {
|
|
209
|
+
await riskyOperation();
|
|
210
|
+
} catch (err) {
|
|
211
|
+
context?.onError?.(err); // Reports error but continues
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
context?.sendMessage?.({ content: `Running with priority ${priority}` });
|
|
215
|
+
|
|
216
|
+
if (tags?.length) {
|
|
217
|
+
context?.sendMessage?.({ content: `Applying tags: ${tags.join(", ")}`, level: "debug" });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Simulate processing
|
|
221
|
+
for (let i = 1; i <= 5; i++) {
|
|
222
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
223
|
+
context?.sendMessage?.({ content: `Progress: ${i * 20}%`, level: "debug" });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
context?.sendMessage?.({ content: "Evaluation complete!" });
|
|
227
|
+
return { jobId, status: "complete", results: 42 };
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const program = new Command();
|
|
232
|
+
program.version("1.0.0").description("Evaluation CLI");
|
|
233
|
+
|
|
234
|
+
registerServiceCommand({
|
|
235
|
+
handler: evaluateHandler,
|
|
236
|
+
program,
|
|
237
|
+
onMessage: (msg) => {
|
|
238
|
+
const prefix = msg.level === "warn" ? "WARNING: " :
|
|
239
|
+
msg.level === "error" ? "ERROR: " : "";
|
|
240
|
+
console.log(`${prefix}${msg.content}`);
|
|
241
|
+
},
|
|
242
|
+
onComplete: (response) => {
|
|
243
|
+
console.log("\n--- Results ---");
|
|
244
|
+
console.log(JSON.stringify(response, null, 2));
|
|
245
|
+
},
|
|
246
|
+
onError: (error) => {
|
|
247
|
+
// Recoverable errors reported via context.onError()
|
|
248
|
+
console.warn("Warning:", error.message);
|
|
249
|
+
},
|
|
250
|
+
onFatal: (error) => {
|
|
251
|
+
// Fatal errors (thrown or via context.onFatal())
|
|
252
|
+
console.error("\nFatal:", error.message);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
program.parse();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Usage:
|
|
261
|
+
```bash
|
|
262
|
+
# Basic execution
|
|
263
|
+
cli evaluate --job abc123
|
|
264
|
+
|
|
265
|
+
# With all options
|
|
266
|
+
cli evaluate -j abc123 -p 1 -t urgent -t critical --dry-run
|
|
267
|
+
|
|
268
|
+
# Help
|
|
269
|
+
cli evaluate --help
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Input Flag and Letter Properties
|
|
273
|
+
|
|
274
|
+
Define CLI flags directly in input definitions:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
input: {
|
|
278
|
+
userName: {
|
|
279
|
+
type: String,
|
|
280
|
+
flag: "user", // Long flag: --user (instead of --user-name)
|
|
281
|
+
letter: "u", // Short flag: -u
|
|
282
|
+
description: "User name to greet",
|
|
283
|
+
},
|
|
284
|
+
verbose: {
|
|
285
|
+
type: Boolean,
|
|
286
|
+
letter: "v", // -v
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
// Generates: --user <userName>, -u and --verbose, -v
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Naming Convention
|
|
293
|
+
|
|
294
|
+
| Property | CLI Flag | Example |
|
|
295
|
+
|----------|----------|---------|
|
|
296
|
+
| `userName` (camelCase) | `--user-name` | Default behavior |
|
|
297
|
+
| `flag: "user"` | `--user` | Override long flag |
|
|
298
|
+
| `letter: "u"` | `-u` | Short flag |
|
|
299
|
+
|
|
300
|
+
## Boolean Flag Behavior
|
|
301
|
+
|
|
302
|
+
Commander.js automatically handles boolean flags:
|
|
303
|
+
- `--verbose` sets to `true`
|
|
304
|
+
- `--no-verbose` sets to `false` (for flags with `default: true`)
|
|
305
|
+
|
|
306
|
+
## Manual Integration
|
|
307
|
+
|
|
308
|
+
For more control, use `createCommanderOptions` and `parseCommanderOptions`:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import { Command } from "commander";
|
|
312
|
+
import { serviceHandler } from "@jaypie/vocabulary";
|
|
313
|
+
import {
|
|
314
|
+
createCommanderOptions,
|
|
315
|
+
parseCommanderOptions,
|
|
316
|
+
} from "@jaypie/vocabulary/commander";
|
|
317
|
+
|
|
318
|
+
const handler = serviceHandler({
|
|
319
|
+
input: {
|
|
320
|
+
userName: { type: String, description: "User name" },
|
|
321
|
+
maxRetries: { type: Number, default: 3 },
|
|
322
|
+
verbose: { type: Boolean },
|
|
323
|
+
},
|
|
324
|
+
service: (input) => console.log(input),
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const program = new Command();
|
|
328
|
+
|
|
329
|
+
// Create options from handler input
|
|
330
|
+
const { options } = createCommanderOptions(handler.input, {
|
|
331
|
+
exclude: ["internalField"],
|
|
332
|
+
overrides: {
|
|
333
|
+
userName: { short: "u", description: "Override description" },
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
options.forEach((opt) => program.addOption(opt));
|
|
337
|
+
|
|
338
|
+
// Wire up action
|
|
339
|
+
program.action(async (opts) => {
|
|
340
|
+
const input = parseCommanderOptions(opts, { input: handler.input });
|
|
341
|
+
await handler(input);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
program.parse();
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## TypeScript Types
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import type {
|
|
351
|
+
CommanderOptionOverride,
|
|
352
|
+
CreateCommanderOptionsConfig,
|
|
353
|
+
CreateCommanderOptionsResult,
|
|
354
|
+
OnCompleteCallback,
|
|
355
|
+
OnErrorCallback,
|
|
356
|
+
OnFatalCallback,
|
|
357
|
+
OnMessageCallback,
|
|
358
|
+
ParseCommanderOptionsConfig,
|
|
359
|
+
RegisterServiceCommandConfig,
|
|
360
|
+
RegisterServiceCommandResult,
|
|
361
|
+
} from "@jaypie/vocabulary/commander";
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Callback Type Definitions
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// Message received from context.sendMessage
|
|
368
|
+
interface Message {
|
|
369
|
+
content: string;
|
|
370
|
+
level?: "trace" | "debug" | "info" | "warn" | "error";
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// onMessage callback
|
|
374
|
+
type OnMessageCallback = (message: Message) => void | Promise<void>;
|
|
375
|
+
|
|
376
|
+
// onComplete callback - receives handler return value
|
|
377
|
+
type OnCompleteCallback<T = unknown> = (response: T) => void | Promise<void>;
|
|
378
|
+
|
|
379
|
+
// onError callback - receives errors from context.onError()
|
|
380
|
+
type OnErrorCallback = (error: unknown) => void | Promise<void>;
|
|
381
|
+
|
|
382
|
+
// onFatal callback - receives fatal errors (thrown or context.onFatal())
|
|
383
|
+
type OnFatalCallback = (error: unknown) => void | Promise<void>;
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Exports
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// @jaypie/vocabulary/commander
|
|
390
|
+
export { createCommanderOptions } from "./createCommanderOptions.js";
|
|
391
|
+
export { parseCommanderOptions } from "./parseCommanderOptions.js";
|
|
392
|
+
export { registerServiceCommand } from "./registerServiceCommand.js";
|
|
393
|
+
|
|
394
|
+
export type {
|
|
395
|
+
CommanderOptionOverride,
|
|
396
|
+
CreateCommanderOptionsConfig,
|
|
397
|
+
CreateCommanderOptionsResult,
|
|
398
|
+
OnCompleteCallback,
|
|
399
|
+
OnErrorCallback,
|
|
400
|
+
OnFatalCallback,
|
|
401
|
+
OnMessageCallback,
|
|
402
|
+
ParseCommanderOptionsConfig,
|
|
403
|
+
RegisterServiceCommandConfig,
|
|
404
|
+
RegisterServiceCommandResult,
|
|
405
|
+
} from "./types.js";
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Related
|
|
409
|
+
|
|
410
|
+
- [Jaypie_Commander_CLI_Package.md](Jaypie_Commander_CLI_Package.md) - Setting up a Commander CLI project from scratch
|
|
411
|
+
- [Jaypie_Vocabulary_Package.md](Jaypie_Vocabulary_Package.md) - Core serviceHandler and type coercion
|