create-expert 0.0.8 → 0.0.10

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/cli.js DELETED
@@ -1,800 +0,0 @@
1
- #!/usr/bin/env node
2
- #!/usr/bin/env node
3
-
4
- // bin/cli.ts
5
- import { spawn } from "child_process";
6
- import { existsSync, readFileSync, writeFileSync } from "fs";
7
- import { join } from "path";
8
- import { Command } from "commander";
9
- import { config } from "dotenv";
10
-
11
- // src/lib/agents-md-template.ts
12
- function generateAgentsMd(options) {
13
- const { provider, model, runtime } = options;
14
- const isNonDefaultRuntime = runtime && runtime !== "default";
15
- const runtimeSection = isNonDefaultRuntime ? `runtime = "${runtime}"` : "";
16
- return `# AGENTS.md
17
-
18
- ## What is Perstack
19
-
20
- Perstack is a package manager and runtime for agent-first development. It enables you to define, test, and share modular AI agents called "Experts".
21
-
22
- Key concepts:
23
- - **Experts**: Modular micro-agents defined in TOML
24
- - **Runtime**: Executes Experts with isolation, observability, and sandbox support
25
- - **Registry**: Public registry for sharing and reusing Experts
26
-
27
- ## Project Configuration
28
-
29
- This project uses:
30
- - Provider: ${provider}
31
- - Model: ${model}
32
- ${isNonDefaultRuntime ? `- Runtime: ${runtime}` : "- Runtime: docker (default)"}
33
-
34
- ## CLI Reference
35
-
36
- ### Running Experts
37
-
38
- **\`perstack start\`** - Interactive workbench for developing and testing Experts
39
- \`\`\`bash
40
- perstack start [expertKey] [query]
41
- \`\`\`
42
-
43
- **\`perstack run\`** - Headless execution for production and automation
44
- \`\`\`bash
45
- perstack run <expertKey> <query> [options]
46
- \`\`\`
47
-
48
- ### Common Options
49
-
50
- | Option | Description | Default |
51
- |--------|-------------|---------|
52
- | \`--provider <provider>\` | LLM provider | \`anthropic\` |
53
- | \`--model <model>\` | Model name | \`claude-sonnet-4-5\` |
54
- | \`--max-steps <n>\` | Maximum steps | unlimited |
55
- | \`--runtime <runtime>\` | Execution runtime | \`docker\` |
56
-
57
- ### Available Runtimes
58
-
59
- - \`docker\` \u2014 Containerized runtime with network isolation (default)
60
- - \`local\` \u2014 Built-in runtime without isolation
61
- - \`cursor\` \u2014 Cursor CLI (experimental)
62
- - \`claude-code\` \u2014 Claude Code CLI (experimental)
63
- - \`gemini\` \u2014 Gemini CLI (experimental)
64
-
65
- ## perstack.toml Format
66
-
67
- \`\`\`toml
68
- model = "${model}"
69
- ${runtimeSection}
70
-
71
- [provider]
72
- providerName = "${provider}"
73
-
74
- [experts."my-expert"]
75
- version = "1.0.0"
76
- description = "Brief description"
77
- instruction = """
78
- Detailed instructions for the expert.
79
- """
80
-
81
- [experts."my-expert".skills."@perstack/base"]
82
- type = "mcpStdioSkill"
83
- command = "npx"
84
- packageName = "@perstack/base"
85
- \`\`\`
86
-
87
- ## Best Practices for Creating Experts
88
-
89
- ### 1. Do One Thing Well
90
-
91
- Bad:
92
- \`\`\`toml
93
- [experts."assistant"]
94
- description = "Handles inquiries, reports, meetings, and expenses"
95
- \`\`\`
96
-
97
- Good:
98
- \`\`\`toml
99
- [experts."customer-support"]
100
- description = "Answers customer questions about products and orders"
101
- \`\`\`
102
-
103
- ### 2. Trust the LLM, Define Domain Knowledge
104
-
105
- Bad (procedural):
106
- \`\`\`toml
107
- instruction = """
108
- 1. First, greet the customer
109
- 2. Ask for their order number
110
- 3. Look up the order
111
- """
112
- \`\`\`
113
-
114
- Good (declarative):
115
- \`\`\`toml
116
- instruction = """
117
- You are a customer support specialist.
118
-
119
- Key policies:
120
- - Orders ship within 2 business days
121
- - Free returns within 30 days
122
- - VIP customers get priority handling
123
-
124
- Tone: Friendly but professional.
125
- """
126
- \`\`\`
127
-
128
- ### 3. Let Them Collaborate
129
-
130
- Use delegation for complex workflows:
131
- \`\`\`toml
132
- [experts."coordinator"]
133
- delegates = ["researcher", "writer", "reviewer"]
134
-
135
- [experts."researcher"]
136
- description = "Gathers information from various sources"
137
- \`\`\`
138
-
139
- ### 4. Keep It Verifiable
140
-
141
- Write clear, predictable instructions:
142
- \`\`\`toml
143
- instruction = """
144
- Approval rules:
145
- - Under $100: Auto-approve with receipt
146
- - $100-$500: Approve if business purpose is clear
147
- - Over $500: Flag for manager review
148
- """
149
- \`\`\`
150
-
151
- ### 5. Ship Early
152
-
153
- Start minimal, expand based on real usage. Don't over-engineer.
154
-
155
- ## Finding Skills (MCP Servers)
156
-
157
- Skills extend Experts with external capabilities via MCP (Model Context Protocol).
158
-
159
- ### MCP Registry
160
-
161
- Search for MCP servers at: https://registry.modelcontextprotocol.io
162
-
163
- **API Reference:**
164
- \`\`\`bash
165
- # List all servers
166
- curl "https://registry.modelcontextprotocol.io/v0.1/servers"
167
-
168
- # Search by name
169
- curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=filesystem"
170
-
171
- # Get specific server
172
- curl "https://registry.modelcontextprotocol.io/v0.1/servers/{serverName}/versions/{version}"
173
- \`\`\`
174
-
175
- ### Using MCP Skills
176
-
177
- **npm packages (most common):**
178
- \`\`\`toml
179
- [experts."my-expert".skills."web-search"]
180
- type = "mcpStdioSkill"
181
- command = "npx"
182
- packageName = "exa-mcp-server"
183
- requiredEnv = ["EXA_API_KEY"]
184
- pick = ["web_search_exa"] # Optional: whitelist tools
185
- \`\`\`
186
-
187
- **Remote servers (SSE):**
188
- \`\`\`toml
189
- [experts."my-expert".skills."remote-api"]
190
- type = "mcpSseSkill"
191
- endpoint = "https://api.example.com/mcp"
192
- \`\`\`
193
-
194
- ### Built-in Base Skill
195
-
196
- \`@perstack/base\` provides essential tools:
197
- - File operations: \`readTextFile\`, \`writeTextFile\`, \`editTextFile\`, etc.
198
- - Shell execution: \`exec\`
199
- - Control flow: \`attemptCompletion\`, \`think\`, \`todo\`
200
-
201
- \`\`\`toml
202
- [experts."my-expert".skills."@perstack/base"]
203
- type = "mcpStdioSkill"
204
- command = "npx"
205
- packageName = "@perstack/base"
206
- \`\`\`
207
-
208
- ## Testing Experts
209
-
210
- 1. Start with \`perstack start\` for interactive development
211
- 2. Test happy path scenarios first
212
- 3. Test edge cases and error scenarios
213
- 4. Verify outputs match expectations
214
- 5. Use \`--max-steps\` to limit runaway executions
215
-
216
- ## Project Files
217
-
218
- - \`perstack.toml\` - Expert definitions and runtime config
219
- - \`AGENTS.md\` - This file, for AI agent context
220
- - \`.env\` - Environment variables (API keys)
221
- - \`perstack/\` - Execution history (auto-managed)
222
- `;
223
- }
224
-
225
- // src/lib/create-expert-toml.ts
226
- function generateCreateExpertToml(options) {
227
- const runtimeLine = options.runtime && options.runtime !== "default" ? `runtime = "${options.runtime}"
228
- ` : "";
229
- return `model = "${options.model}"
230
- ${runtimeLine}
231
- [provider]
232
- providerName = "${options.provider}"
233
-
234
- [experts."create-expert"]
235
- version = "1.0.0"
236
- description = "Creates and tests new Perstack Experts based on user requirements"
237
- instruction = """
238
- You are an Expert creator for Perstack. Your job is to create well-designed Experts based on user requirements.
239
-
240
- ## Your Workflow
241
-
242
- 1. **Understand Requirements**: Analyze what the user wants the Expert to do
243
- 2. **Design the Expert**: Plan the structure, skills needed, and delegation patterns
244
- 3. **Implement**: Write the Expert definition in perstack.toml
245
- 4. **Test**: Run the Expert with realistic test cases
246
- 5. **Iterate**: Fix issues and improve based on test results
247
-
248
- ## Design Principles
249
-
250
- 1. **Do One Thing Well**: Each Expert should focus on a single responsibility
251
- 2. **Define Domain Knowledge**: Write declarative instructions with policies, not procedures
252
- 3. **Use Delegation**: Break complex tasks into collaborating Experts
253
- 4. **Keep It Verifiable**: Write clear, predictable behavior
254
- 5. **Start Simple**: Begin minimal, expand based on real needs
255
-
256
- ## Expert Structure
257
-
258
- [experts."expert-name"]
259
- version = "1.0.0"
260
- description = "Brief description visible to delegators"
261
- instruction = "Domain knowledge, policies, and constraints."
262
- delegates = ["other-expert"]
263
-
264
- [experts."expert-name".skills."skill-name"]
265
- type = "mcpStdioSkill"
266
- command = "npx"
267
- packageName = "package-name"
268
-
269
- ## Finding Skills
270
-
271
- Search MCP Registry for available skills:
272
- - Browse: https://registry.modelcontextprotocol.io
273
- - API: GET /v0.1/servers?search=<query>
274
-
275
- Common skills:
276
- - \`@perstack/base\` - File operations, shell, control flow
277
- - \`exa-mcp-server\` - Web search (requires EXA_API_KEY)
278
- - \`@anthropic/mcp-server-memory\` - Persistent memory
279
-
280
- ## Testing Protocol
281
-
282
- For each Expert you create:
283
-
284
- 1. **Happy Path Tests**
285
- - Test the primary use case with normal inputs
286
- - Verify the output matches expectations
287
-
288
- 2. **Edge Case Tests**
289
- - Empty or minimal inputs
290
- - Unusual but valid inputs
291
- - Boundary conditions
292
-
293
- 3. **Error Handling Tests**
294
- - Invalid inputs
295
- - Missing required data
296
- - Skill failures
297
-
298
- Use \`npx -y perstack run\` to execute tests:
299
- \`\`\`bash
300
- npx -y perstack run expert-name "test query"
301
- \`\`\`
302
-
303
- ## Iteration Process
304
-
305
- After each test:
306
- 1. Review the output
307
- 2. Identify issues or improvements
308
- 3. Update the Expert definition
309
- 4. Re-run tests to verify fixes
310
-
311
- Continue until all tests pass and the Expert behaves as expected.
312
- """
313
-
314
- [experts."create-expert".skills."@perstack/base"]
315
- type = "mcpStdioSkill"
316
- command = "npx"
317
- packageName = "@perstack/base"
318
- `;
319
- }
320
-
321
- // src/lib/detect-llm.ts
322
- var LLM_CONFIGS = {
323
- anthropic: {
324
- envVar: "ANTHROPIC_API_KEY",
325
- displayName: "Anthropic (Claude)",
326
- defaultModel: "claude-sonnet-4-5"
327
- },
328
- openai: { envVar: "OPENAI_API_KEY", displayName: "OpenAI", defaultModel: "gpt-4o" },
329
- google: {
330
- envVar: "GOOGLE_GENERATIVE_AI_API_KEY",
331
- displayName: "Google (Gemini)",
332
- defaultModel: "gemini-2.5-pro"
333
- }
334
- };
335
- function detectLLM(provider) {
336
- const config2 = LLM_CONFIGS[provider];
337
- return {
338
- provider,
339
- envVar: config2.envVar,
340
- available: Boolean(process.env[config2.envVar]),
341
- displayName: config2.displayName,
342
- defaultModel: config2.defaultModel
343
- };
344
- }
345
- function detectAllLLMs() {
346
- return Object.keys(LLM_CONFIGS).map(detectLLM);
347
- }
348
- function getDefaultModel(provider) {
349
- return LLM_CONFIGS[provider].defaultModel;
350
- }
351
-
352
- // src/lib/detect-runtime.ts
353
- import { execSync } from "child_process";
354
- function checkCommand(command) {
355
- try {
356
- const result = execSync(`${command} --version`, {
357
- encoding: "utf-8",
358
- stdio: ["pipe", "pipe", "pipe"],
359
- timeout: 5e3
360
- });
361
- const firstLine = result.trim().split("\n")[0];
362
- return { available: true, version: firstLine };
363
- } catch {
364
- return { available: false };
365
- }
366
- }
367
- function detectCursor() {
368
- const result = checkCommand("cursor");
369
- return { type: "cursor", ...result };
370
- }
371
- function detectClaudeCode() {
372
- const result = checkCommand("claude");
373
- return { type: "claude-code", ...result };
374
- }
375
- function detectGemini() {
376
- const result = checkCommand("gemini");
377
- return { type: "gemini", ...result };
378
- }
379
- function detectAllRuntimes() {
380
- return [detectCursor(), detectClaudeCode(), detectGemini()];
381
- }
382
-
383
- // src/tui/wizard/render.tsx
384
- import { render } from "ink";
385
-
386
- // src/tui/wizard/app.tsx
387
- import { Box, Text, useApp, useInput } from "ink";
388
- import { useEffect, useState } from "react";
389
- import { jsx, jsxs } from "react/jsx-runtime";
390
- function SelectableList({
391
- items,
392
- selectedIndex,
393
- renderItem
394
- }) {
395
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
396
- const isSelected = index === selectedIndex;
397
- if (renderItem) {
398
- return /* @__PURE__ */ jsx(Box, { children: renderItem(item, isSelected) }, item.key);
399
- }
400
- return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: item.disabled ? "gray" : isSelected ? "cyan" : "white", children: [
401
- isSelected ? "\u276F " : " ",
402
- item.label,
403
- item.disabled ? " (not available)" : ""
404
- ] }) }, item.key);
405
- }) });
406
- }
407
- function TextInputComponent({
408
- value,
409
- onChange,
410
- onSubmit,
411
- placeholder,
412
- isSecret
413
- }) {
414
- useInput((input, key) => {
415
- if (key.return) {
416
- onSubmit();
417
- } else if (key.backspace || key.delete) {
418
- onChange(value.slice(0, -1));
419
- } else if (!key.ctrl && !key.meta && input) {
420
- onChange(value + input);
421
- }
422
- });
423
- const displayValue = isSecret ? "\u2022".repeat(value.length) : value;
424
- return /* @__PURE__ */ jsxs(Box, { children: [
425
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: displayValue || /* @__PURE__ */ jsx(Text, { color: "gray", children: placeholder }) }),
426
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u2588" })
427
- ] });
428
- }
429
- function getRuntimeDisplayName(type) {
430
- switch (type) {
431
- case "cursor":
432
- return "Cursor";
433
- case "claude-code":
434
- return "Claude Code";
435
- case "gemini":
436
- return "Gemini CLI";
437
- }
438
- }
439
- function App({ llms, runtimes, onComplete, isImprovement, improvementTarget }) {
440
- const { exit } = useApp();
441
- const [step, setStep] = useState("detecting");
442
- const [selectedIndex, setSelectedIndex] = useState(0);
443
- const [result, setResult] = useState({});
444
- const [apiKeyInput, setApiKeyInput] = useState("");
445
- const [expertDescInput, setExpertDescInput] = useState(improvementTarget || "");
446
- const availableLLMs = llms.filter((l) => l.available);
447
- const availableRuntimes = runtimes.filter((r) => r.available);
448
- const runtimeOptions = [
449
- { key: "default", type: "default", label: "Default (built-in)" },
450
- ...availableRuntimes.map((r) => ({
451
- key: r.type,
452
- type: r.type,
453
- label: `${getRuntimeDisplayName(r.type)}${r.version ? ` (${r.version})` : ""}`,
454
- version: r.version
455
- }))
456
- ];
457
- const llmOptionsWithOther = [
458
- ...llms.map((l) => ({
459
- key: l.provider,
460
- label: `${l.displayName}${l.available ? " \u2713" : ""}`,
461
- provider: l.provider,
462
- available: l.available,
463
- defaultModel: l.defaultModel
464
- })),
465
- {
466
- key: "other",
467
- label: "Other (configure new provider)",
468
- provider: null,
469
- available: false,
470
- defaultModel: ""
471
- }
472
- ];
473
- useEffect(() => {
474
- if (step === "detecting") {
475
- const timer = setTimeout(() => {
476
- if (isImprovement) {
477
- const llm = availableLLMs[0];
478
- if (llm) {
479
- setResult({
480
- runtime: "default",
481
- provider: llm.provider,
482
- model: llm.defaultModel
483
- });
484
- setStep("input-expert-description");
485
- } else {
486
- setStep("select-runtime");
487
- }
488
- } else {
489
- setStep("select-runtime");
490
- }
491
- }, 500);
492
- return () => clearTimeout(timer);
493
- }
494
- return void 0;
495
- }, [step, isImprovement, availableLLMs]);
496
- useInput((_, key) => {
497
- if (key.escape) {
498
- exit();
499
- return;
500
- }
501
- if (step === "input-api-key" || step === "input-expert-description") {
502
- return;
503
- }
504
- if (key.upArrow) {
505
- setSelectedIndex((prev) => Math.max(0, prev - 1));
506
- } else if (key.downArrow) {
507
- setSelectedIndex((prev) => {
508
- const maxIndex = getMaxIndex();
509
- return Math.min(maxIndex, prev + 1);
510
- });
511
- } else if (key.return) {
512
- handleSelect();
513
- }
514
- });
515
- function getMaxIndex() {
516
- switch (step) {
517
- case "select-runtime":
518
- return runtimeOptions.length - 1;
519
- case "select-llm":
520
- return llmOptionsWithOther.length - 1;
521
- case "select-provider":
522
- return llms.length - 1;
523
- default:
524
- return 0;
525
- }
526
- }
527
- function handleSelect() {
528
- switch (step) {
529
- case "select-runtime": {
530
- const selected = runtimeOptions[selectedIndex];
531
- if (!selected) break;
532
- if (selected.type === "default") {
533
- setResult({ runtime: "default" });
534
- if (availableLLMs.length > 0) {
535
- setStep("select-llm");
536
- } else {
537
- setStep("select-provider");
538
- }
539
- } else {
540
- setResult({ runtime: selected.type });
541
- setStep("input-expert-description");
542
- }
543
- setSelectedIndex(0);
544
- break;
545
- }
546
- case "select-llm": {
547
- const selected = llmOptionsWithOther[selectedIndex];
548
- if (!selected) break;
549
- if (selected.key === "other") {
550
- setStep("select-provider");
551
- } else if (selected.available && selected.provider) {
552
- setResult((prev) => ({
553
- ...prev,
554
- provider: selected.provider,
555
- model: selected.defaultModel
556
- }));
557
- setStep("input-expert-description");
558
- } else if (selected.provider) {
559
- setResult((prev) => ({ ...prev, provider: selected.provider }));
560
- setStep("input-api-key");
561
- }
562
- setSelectedIndex(0);
563
- break;
564
- }
565
- case "select-provider": {
566
- const selected = llms[selectedIndex];
567
- if (!selected) break;
568
- setResult((prev) => ({ ...prev, provider: selected.provider }));
569
- setStep("input-api-key");
570
- setSelectedIndex(0);
571
- break;
572
- }
573
- }
574
- }
575
- function handleApiKeySubmit() {
576
- if (apiKeyInput.trim()) {
577
- const selectedLlm = llms.find((l) => l.provider === result.provider) ?? llms[0];
578
- if (!selectedLlm) return;
579
- setResult((prev) => ({
580
- ...prev,
581
- provider: selectedLlm.provider,
582
- model: selectedLlm.defaultModel,
583
- apiKey: apiKeyInput.trim()
584
- }));
585
- setStep("input-expert-description");
586
- }
587
- }
588
- function handleExpertDescSubmit() {
589
- if (expertDescInput.trim()) {
590
- const finalResult = {
591
- runtime: result.runtime || "default",
592
- provider: result.provider,
593
- model: result.model,
594
- apiKey: result.apiKey,
595
- expertDescription: expertDescInput.trim()
596
- };
597
- onComplete(finalResult);
598
- setStep("done");
599
- exit();
600
- }
601
- }
602
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
603
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "\u{1F680} Create Expert Wizard" }) }),
604
- step === "detecting" && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Detecting available runtimes..." }) }),
605
- step === "select-runtime" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
606
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { children: "Select a runtime:" }) }),
607
- /* @__PURE__ */ jsx(
608
- SelectableList,
609
- {
610
- items: runtimeOptions.map((r) => ({ key: r.key, label: r.label })),
611
- selectedIndex
612
- }
613
- ),
614
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u2191\u2193 to move, Enter to select, Esc to exit" }) })
615
- ] }),
616
- step === "select-llm" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
617
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { children: "Select an LLM provider:" }) }),
618
- /* @__PURE__ */ jsx(
619
- SelectableList,
620
- {
621
- items: llmOptionsWithOther.map((l) => ({ key: l.key, label: l.label })),
622
- selectedIndex
623
- }
624
- ),
625
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u2191\u2193 to move, Enter to select" }) })
626
- ] }),
627
- step === "select-provider" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
628
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u26A0 No LLM API keys found." }) }),
629
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { children: "Perstack requires an API key from one of these providers:" }) }),
630
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, children: llms.map((l) => /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
631
- "\u2022 ",
632
- l.displayName,
633
- " (",
634
- l.envVar,
635
- ")"
636
- ] }, l.provider)) }),
637
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { children: "Select a provider to configure:" }) }),
638
- /* @__PURE__ */ jsx(
639
- SelectableList,
640
- {
641
- items: llms.map((l) => ({ key: l.provider, label: l.displayName })),
642
- selectedIndex
643
- }
644
- ),
645
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u2191\u2193 to move, Enter to select" }) })
646
- ] }),
647
- step === "input-api-key" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
648
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
649
- "Enter your ",
650
- llms.find((l) => l.provider === result.provider)?.displayName || "API",
651
- " ",
652
- "key:"
653
- ] }) }),
654
- /* @__PURE__ */ jsx(
655
- TextInputComponent,
656
- {
657
- value: apiKeyInput,
658
- onChange: setApiKeyInput,
659
- onSubmit: handleApiKeySubmit,
660
- placeholder: "sk-...",
661
- isSecret: true
662
- }
663
- ),
664
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Type your API key and press Enter" }) })
665
- ] }),
666
- step === "input-expert-description" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
667
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, children: isImprovement ? "What improvements do you want?" : "What kind of Expert do you want to create?" }) }),
668
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Describe the Expert's purpose, capabilities, or domain knowledge." }) }),
669
- /* @__PURE__ */ jsx(
670
- TextInputComponent,
671
- {
672
- value: expertDescInput,
673
- onChange: setExpertDescInput,
674
- onSubmit: handleExpertDescSubmit,
675
- placeholder: "e.g., A code reviewer that checks for TypeScript best practices"
676
- }
677
- ),
678
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Type your description and press Enter" }) })
679
- ] }),
680
- step === "done" && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713 Configuration complete! Starting Expert creation..." }) })
681
- ] });
682
- }
683
-
684
- // src/tui/wizard/render.tsx
685
- import { jsx as jsx2 } from "react/jsx-runtime";
686
- async function renderWizard(options) {
687
- return new Promise((resolve) => {
688
- let result = null;
689
- const { waitUntilExit } = render(
690
- /* @__PURE__ */ jsx2(
691
- App,
692
- {
693
- llms: options.llms,
694
- runtimes: options.runtimes,
695
- isImprovement: options.isImprovement,
696
- improvementTarget: options.improvementTarget,
697
- onComplete: (wizardResult) => {
698
- result = wizardResult;
699
- }
700
- }
701
- )
702
- );
703
- waitUntilExit().then(() => resolve(result));
704
- });
705
- }
706
-
707
- // bin/cli.ts
708
- config();
709
- function getEnvVarName(provider) {
710
- switch (provider) {
711
- case "anthropic":
712
- return "ANTHROPIC_API_KEY";
713
- case "openai":
714
- return "OPENAI_API_KEY";
715
- case "google":
716
- return "GOOGLE_GENERATIVE_AI_API_KEY";
717
- }
718
- }
719
- var program = new Command().name("create-expert").description("Create Perstack Experts interactively").version("0.0.1").argument("[expertName]", "Expert name to improve (for improvement mode)").argument("[improvements]", "Improvement description (for improvement mode)").option("--cwd <path>", "Working directory", process.cwd()).action(async (expertName, improvements, options) => {
720
- const cwd = options?.cwd || process.cwd();
721
- const isImprovement = Boolean(expertName);
722
- const improvementTarget = improvements || "";
723
- const perstackTomlPath = join(cwd, "perstack.toml");
724
- const agentsMdPath = join(cwd, "AGENTS.md");
725
- const envPath = join(cwd, ".env");
726
- const llms = detectAllLLMs();
727
- const runtimes = detectAllRuntimes();
728
- const wizardResult = await renderWizard({
729
- llms,
730
- runtimes,
731
- isImprovement,
732
- improvementTarget
733
- });
734
- if (!wizardResult) {
735
- console.log("Wizard cancelled.");
736
- process.exit(0);
737
- }
738
- if (wizardResult.apiKey && wizardResult.provider) {
739
- const envVarName = getEnvVarName(wizardResult.provider);
740
- const envContent = `${envVarName}=${wizardResult.apiKey}
741
- `;
742
- if (existsSync(envPath)) {
743
- const existing = readFileSync(envPath, "utf-8");
744
- const hasEnvVar = new RegExp(`^${envVarName}=`, "m").test(existing);
745
- if (!hasEnvVar) {
746
- writeFileSync(envPath, `${existing}
747
- ${envContent}`);
748
- console.log(`\u2713 Added ${envVarName} to .env`);
749
- }
750
- } else {
751
- writeFileSync(envPath, envContent);
752
- console.log(`\u2713 Created .env with ${envVarName}`);
753
- }
754
- process.env[envVarName] = wizardResult.apiKey;
755
- }
756
- const isDefaultRuntime = wizardResult.runtime === "default";
757
- if (!isImprovement) {
758
- if (isDefaultRuntime) {
759
- const provider = wizardResult.provider || "anthropic";
760
- const model = wizardResult.model || getDefaultModel(provider);
761
- const agentsMd = generateAgentsMd({ provider, model });
762
- writeFileSync(agentsMdPath, agentsMd);
763
- console.log("\u2713 Created AGENTS.md");
764
- const createExpertToml = generateCreateExpertToml({ provider, model });
765
- writeFileSync(perstackTomlPath, createExpertToml);
766
- console.log("\u2713 Created perstack.toml with create-expert Expert");
767
- } else {
768
- const provider = wizardResult.provider || "anthropic";
769
- const model = wizardResult.model || getDefaultModel(provider);
770
- const agentsMd = generateAgentsMd({
771
- provider,
772
- model,
773
- runtime: wizardResult.runtime
774
- });
775
- writeFileSync(agentsMdPath, agentsMd);
776
- console.log("\u2713 Created AGENTS.md");
777
- const createExpertToml = generateCreateExpertToml({
778
- provider,
779
- model,
780
- runtime: wizardResult.runtime
781
- });
782
- writeFileSync(perstackTomlPath, createExpertToml);
783
- console.log("\u2713 Created perstack.toml with create-expert Expert");
784
- }
785
- }
786
- const expertDescription = wizardResult.expertDescription || "";
787
- const query = isImprovement ? `Improve the Expert "${expertName}": ${expertDescription}` : `Create a new Expert based on these requirements: ${expertDescription}`;
788
- const runtimeArg = isDefaultRuntime ? [] : ["--runtime", wizardResult.runtime];
789
- const args = ["perstack", "start", "create-expert", query, ...runtimeArg];
790
- const proc = spawn("npx", args, {
791
- cwd,
792
- env: process.env,
793
- stdio: "inherit"
794
- });
795
- proc.on("exit", (code) => {
796
- process.exit(code || 0);
797
- });
798
- });
799
- program.parse();
800
- //# sourceMappingURL=cli.js.map