clayton-forge 0.1.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/dist/commands/new.d.ts +2 -0
- package/dist/commands/new.js +286 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +121 -0
- package/dist/commands/test.d.ts +2 -0
- package/dist/commands/test.js +136 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +134 -0
- package/package.json +26 -0
- package/package.json:Zone.Identifier +0 -0
- package/src/commands/new.ts +281 -0
- package/src/commands/new.ts:Zone.Identifier +0 -0
- package/src/commands/run.ts +97 -0
- package/src/commands/run.ts:Zone.Identifier +0 -0
- package/src/commands/test.ts +122 -0
- package/src/commands/test.ts:Zone.Identifier +0 -0
- package/src/index.ts +115 -0
- package/src/index.ts:Zone.Identifier +0 -0
- package/tsconfig.json +9 -0
- package/tsconfig.json:Zone.Identifier +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const new_1 = require("./commands/new");
|
|
43
|
+
const run_1 = require("./commands/run");
|
|
44
|
+
const test_1 = require("./commands/test");
|
|
45
|
+
const pkg = require("../package.json");
|
|
46
|
+
// ─────────────────────────────────────────────
|
|
47
|
+
// Banner
|
|
48
|
+
// ─────────────────────────────────────────────
|
|
49
|
+
function printBanner() {
|
|
50
|
+
console.log("");
|
|
51
|
+
console.log(chalk_1.default.bold.cyan(" CLAYTON FORGE") + chalk_1.default.gray(` v${pkg.version}`));
|
|
52
|
+
console.log(chalk_1.default.gray(" Build AI agents with confidence."));
|
|
53
|
+
console.log(chalk_1.default.gray(" https://github.com/devclaytonn10/clayton-forge"));
|
|
54
|
+
console.log("");
|
|
55
|
+
}
|
|
56
|
+
// ─────────────────────────────────────────────
|
|
57
|
+
// CLI
|
|
58
|
+
// ─────────────────────────────────────────────
|
|
59
|
+
const program = new commander_1.Command();
|
|
60
|
+
program
|
|
61
|
+
.name("cforge")
|
|
62
|
+
.description("Clayton Forge — AI agent platform CLI")
|
|
63
|
+
.version(pkg.version, "-v, --version", "Output the current version")
|
|
64
|
+
.addHelpText("before", "\n CLAYTON FORGE — Build AI agents with confidence.\n");
|
|
65
|
+
// ─────────────────────────────────────────────
|
|
66
|
+
// cforge new [name]
|
|
67
|
+
// ─────────────────────────────────────────────
|
|
68
|
+
program
|
|
69
|
+
.command("new [name]")
|
|
70
|
+
.description("Create a new agent with an interactive wizard")
|
|
71
|
+
.action(async (name) => {
|
|
72
|
+
await (0, new_1.commandNew)(name ? undefined : undefined);
|
|
73
|
+
});
|
|
74
|
+
// ─────────────────────────────────────────────
|
|
75
|
+
// cforge run [agent-dir] [input]
|
|
76
|
+
// ─────────────────────────────────────────────
|
|
77
|
+
program
|
|
78
|
+
.command("run [agent-dir]")
|
|
79
|
+
.description("Execute an agent")
|
|
80
|
+
.option("-i, --input <text>", "Input text (skips interactive prompt for single-variable agents)")
|
|
81
|
+
.action(async (agentDir, options) => {
|
|
82
|
+
await (0, run_1.commandRun)(agentDir, options?.input);
|
|
83
|
+
});
|
|
84
|
+
// ─────────────────────────────────────────────
|
|
85
|
+
// cforge test [agent-dir]
|
|
86
|
+
// ─────────────────────────────────────────────
|
|
87
|
+
program
|
|
88
|
+
.command("test [agent-dir]")
|
|
89
|
+
.description("Run the agent's test suite")
|
|
90
|
+
.action(async (agentDir) => {
|
|
91
|
+
await (0, test_1.commandTest)(agentDir);
|
|
92
|
+
});
|
|
93
|
+
// ─────────────────────────────────────────────
|
|
94
|
+
// cforge validate
|
|
95
|
+
// ─────────────────────────────────────────────
|
|
96
|
+
program
|
|
97
|
+
.command("validate [agent-dir]")
|
|
98
|
+
.description("Validate agent.yaml without running the agent")
|
|
99
|
+
.action(async (agentDir) => {
|
|
100
|
+
const { parseAgentFile, findAgentFile } = await Promise.resolve().then(() => __importStar(require("@clayton-forge/core")));
|
|
101
|
+
const path = require("path");
|
|
102
|
+
const fs = require("fs");
|
|
103
|
+
const searchDir = agentDir ? path.resolve(agentDir) : process.cwd();
|
|
104
|
+
const agentPath = fs.existsSync(path.join(searchDir, "agent.yaml"))
|
|
105
|
+
? path.join(searchDir, "agent.yaml")
|
|
106
|
+
: findAgentFile(searchDir);
|
|
107
|
+
if (!agentPath) {
|
|
108
|
+
console.error(chalk_1.default.red("✗ No agent.yaml found."));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const agent = parseAgentFile(agentPath);
|
|
113
|
+
console.log(chalk_1.default.green(`\n ✓ Valid agent.yaml`));
|
|
114
|
+
console.log(chalk_1.default.gray(` Name: ${agent.name} v${agent.version}`));
|
|
115
|
+
console.log(chalk_1.default.gray(` Type: ${agent.type}`));
|
|
116
|
+
console.log(chalk_1.default.gray(` LLM: ${agent.llm.provider}${agent.llm.model ? ` / ${agent.llm.model}` : ""}`));
|
|
117
|
+
console.log("");
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
console.error(chalk_1.default.red(`\n ✗ ${err instanceof Error ? err.message : err}`));
|
|
121
|
+
console.log("");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
// ─────────────────────────────────────────────
|
|
126
|
+
// Default: banner + help
|
|
127
|
+
// ─────────────────────────────────────────────
|
|
128
|
+
program.addHelpCommand(true);
|
|
129
|
+
if (process.argv.length <= 2) {
|
|
130
|
+
printBanner();
|
|
131
|
+
program.help();
|
|
132
|
+
}
|
|
133
|
+
program.parse(process.argv);
|
|
134
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clayton-forge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Clayton Forge CLI — build AI agents with confidence",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cforge": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"clean": "rm -rf dist"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@clayton-forge/core": "*",
|
|
16
|
+
"chalk": "^4.1.2",
|
|
17
|
+
"commander": "^12.1.0",
|
|
18
|
+
"inquirer": "^8.2.7",
|
|
19
|
+
"ora": "^5.4.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/inquirer": "^8.2.13",
|
|
23
|
+
"@types/node": "^20.14.0",
|
|
24
|
+
"typescript": "^5.4.5"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
|
|
7
|
+
// ─────────────────────────────────────────────
|
|
8
|
+
// Tipos locais (para evitar import circular durante build inicial)
|
|
9
|
+
// ─────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
interface WizardAnswers {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
type: string;
|
|
15
|
+
llm_provider: string;
|
|
16
|
+
llm_model: string;
|
|
17
|
+
output_format: string;
|
|
18
|
+
trust_level: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────
|
|
22
|
+
// Helpers de display
|
|
23
|
+
// ─────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function header() {
|
|
26
|
+
console.log("");
|
|
27
|
+
console.log(chalk.bold.white(" ╔═══════════════════════════════════╗"));
|
|
28
|
+
console.log(chalk.bold.white(" ║") + chalk.bold.cyan(" CLAYTON FORGE — new ") + chalk.bold.white("║"));
|
|
29
|
+
console.log(chalk.bold.white(" ╚═══════════════════════════════════╝"));
|
|
30
|
+
console.log("");
|
|
31
|
+
console.log(chalk.gray(" Let's create your agent. Answer a few questions."));
|
|
32
|
+
console.log(chalk.gray(" Press Ctrl+C at any time to cancel.\n"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─────────────────────────────────────────────
|
|
36
|
+
// Wizard
|
|
37
|
+
// ─────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
export async function commandNew(targetDir?: string): Promise<void> {
|
|
40
|
+
header();
|
|
41
|
+
|
|
42
|
+
const answers = await inquirer.prompt<WizardAnswers>([
|
|
43
|
+
{
|
|
44
|
+
type: "input",
|
|
45
|
+
name: "name",
|
|
46
|
+
message: chalk.white("Agent name") + chalk.gray(" (kebab-case, e.g. invoice-classifier):"),
|
|
47
|
+
validate: (v: string) =>
|
|
48
|
+
/^[a-z][a-z0-9-]*$/.test(v) || "Use lowercase letters, numbers, and hyphens only",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: "input",
|
|
52
|
+
name: "description",
|
|
53
|
+
message: chalk.white("What does this agent do?"),
|
|
54
|
+
validate: (v: string) => v.trim().length > 5 || "Please write a short description",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: "list",
|
|
58
|
+
name: "type",
|
|
59
|
+
message: chalk.white("Agent type:"),
|
|
60
|
+
choices: [
|
|
61
|
+
{ name: "reactive — receives input → processes → responds", value: "reactive" },
|
|
62
|
+
{ name: "proactive — acts on triggers without waiting for input", value: "proactive" },
|
|
63
|
+
{ name: "pipeline — sequential steps, each feeding the next", value: "pipeline" },
|
|
64
|
+
{ name: "orchestrator— coordinates other agents", value: "orchestrator" },
|
|
65
|
+
{ name: "worker — executed by an orchestrator", value: "worker" },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: "list",
|
|
70
|
+
name: "llm_provider",
|
|
71
|
+
message: chalk.white("LLM provider:"),
|
|
72
|
+
choices: [
|
|
73
|
+
{ name: "anthropic (Claude — requires ANTHROPIC_API_KEY)", value: "anthropic" },
|
|
74
|
+
{ name: "openai (GPT — requires OPENAI_API_KEY)", value: "openai" },
|
|
75
|
+
{ name: "ollama (local — requires Ollama running)", value: "ollama" },
|
|
76
|
+
{ name: "lmstudio (local — requires LM Studio running)", value: "lmstudio" },
|
|
77
|
+
{ name: "openai-compatible (any OpenAI-compatible API)", value: "openai-compatible" },
|
|
78
|
+
{ name: "none (rule-based, no LLM)", value: "none" },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "input",
|
|
83
|
+
name: "llm_model",
|
|
84
|
+
message: chalk.white("Model name:"),
|
|
85
|
+
when: (a: WizardAnswers) => a.llm_provider !== "none",
|
|
86
|
+
default: (a: WizardAnswers) => {
|
|
87
|
+
const defaults: Record<string, string> = {
|
|
88
|
+
anthropic: "claude-sonnet-4-6",
|
|
89
|
+
openai: "gpt-4o-mini",
|
|
90
|
+
ollama: "llama3",
|
|
91
|
+
lmstudio: "local-model",
|
|
92
|
+
"openai-compatible": "default",
|
|
93
|
+
};
|
|
94
|
+
return defaults[a.llm_provider] ?? "";
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: "list",
|
|
99
|
+
name: "output_format",
|
|
100
|
+
message: chalk.white("Output format:"),
|
|
101
|
+
choices: [
|
|
102
|
+
{ name: "text — plain text response", value: "text" },
|
|
103
|
+
{ name: "json — structured JSON (add output schema)", value: "json" },
|
|
104
|
+
{ name: "markdown — formatted markdown", value: "markdown" },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: "list",
|
|
109
|
+
name: "trust_level",
|
|
110
|
+
message: chalk.white("Trust level:"),
|
|
111
|
+
choices: [
|
|
112
|
+
{ name: "0 — sandboxed (no external access)", value: 0 },
|
|
113
|
+
{ name: "1 — read-only (reads data, no writes)", value: 1 },
|
|
114
|
+
{ name: "2 — write (reads and writes)", value: 2 },
|
|
115
|
+
{ name: "3 — admin (full control)", value: 3 },
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
// Destino
|
|
121
|
+
const projectName = answers.name;
|
|
122
|
+
const dest = targetDir ? path.resolve(targetDir, projectName) : path.resolve(process.cwd(), projectName);
|
|
123
|
+
|
|
124
|
+
// Verificar se pasta já existe
|
|
125
|
+
if (fs.existsSync(dest)) {
|
|
126
|
+
console.log(chalk.red(`\n✗ Directory already exists: ${dest}`));
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const spinner = ora("Creating your agent...").start();
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
// Criar pasta
|
|
134
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
135
|
+
|
|
136
|
+
// Gerar agent.yaml
|
|
137
|
+
const apiKeyVar = getApiKeyVar(answers.llm_provider);
|
|
138
|
+
const agentYaml = buildAgentYaml(answers, apiKeyVar);
|
|
139
|
+
fs.writeFileSync(path.join(dest, "agent.yaml"), agentYaml);
|
|
140
|
+
|
|
141
|
+
// Gerar agent.test.yaml
|
|
142
|
+
const testYaml = buildTestYaml(answers.name);
|
|
143
|
+
fs.writeFileSync(path.join(dest, "agent.test.yaml"), testYaml);
|
|
144
|
+
|
|
145
|
+
// Gerar README.md
|
|
146
|
+
const readme = buildReadme(answers);
|
|
147
|
+
fs.writeFileSync(path.join(dest, "README.md"), readme);
|
|
148
|
+
|
|
149
|
+
// Gerar .env.example
|
|
150
|
+
if (apiKeyVar) {
|
|
151
|
+
fs.writeFileSync(path.join(dest, ".env.example"), `${apiKeyVar}=your-api-key-here\n`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
spinner.succeed(chalk.green("Agent created!"));
|
|
155
|
+
|
|
156
|
+
// Summary
|
|
157
|
+
console.log("");
|
|
158
|
+
console.log(chalk.bold(" 📁 " + dest));
|
|
159
|
+
console.log(chalk.gray(" ├── agent.yaml"));
|
|
160
|
+
console.log(chalk.gray(" ├── agent.test.yaml"));
|
|
161
|
+
console.log(chalk.gray(" ├── README.md"));
|
|
162
|
+
if (apiKeyVar) console.log(chalk.gray(" └── .env.example"));
|
|
163
|
+
console.log("");
|
|
164
|
+
console.log(chalk.bold.cyan(" Next steps:"));
|
|
165
|
+
console.log(chalk.white(` $ cd ${projectName}`));
|
|
166
|
+
if (apiKeyVar) console.log(chalk.white(` $ cp .env.example .env # add your API key`));
|
|
167
|
+
console.log(chalk.white(` $ cforge run # execute the agent`));
|
|
168
|
+
console.log(chalk.white(` $ cforge test # run tests`));
|
|
169
|
+
console.log("");
|
|
170
|
+
} catch (err) {
|
|
171
|
+
spinner.fail("Failed to create agent");
|
|
172
|
+
console.error(err);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─────────────────────────────────────────────
|
|
178
|
+
// Builders
|
|
179
|
+
// ─────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
function getApiKeyVar(provider: string): string | null {
|
|
182
|
+
const map: Record<string, string> = {
|
|
183
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
184
|
+
openai: "OPENAI_API_KEY",
|
|
185
|
+
};
|
|
186
|
+
return map[provider] ?? null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function buildAgentYaml(answers: WizardAnswers, apiKeyVar: string | null): string {
|
|
190
|
+
const lines = [
|
|
191
|
+
`name: "${answers.name}"`,
|
|
192
|
+
`version: "1.0.0"`,
|
|
193
|
+
`description: "${answers.description}"`,
|
|
194
|
+
`type: ${answers.type}`,
|
|
195
|
+
`trust_level: ${answers.trust_level}`,
|
|
196
|
+
``,
|
|
197
|
+
`llm:`,
|
|
198
|
+
` provider: ${answers.llm_provider}`,
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
if (answers.llm_provider !== "none") {
|
|
202
|
+
lines.push(` model: ${answers.llm_model}`);
|
|
203
|
+
if (apiKeyVar) {
|
|
204
|
+
lines.push(` api_key: "\${${apiKeyVar}}"`);
|
|
205
|
+
}
|
|
206
|
+
lines.push(` temperature: 0.1`);
|
|
207
|
+
lines.push(` max_tokens: 1000`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
lines.push(``);
|
|
211
|
+
lines.push(`prompt:`);
|
|
212
|
+
lines.push(` system: |`);
|
|
213
|
+
lines.push(` You are ${answers.description}.`);
|
|
214
|
+
lines.push(` Be concise, accurate, and helpful.`);
|
|
215
|
+
lines.push(` user_template: "{{input}}"`);
|
|
216
|
+
lines.push(` variables:`);
|
|
217
|
+
lines.push(` - name: input`);
|
|
218
|
+
lines.push(` required: true`);
|
|
219
|
+
lines.push(` description: "The user's input"`);
|
|
220
|
+
lines.push(``);
|
|
221
|
+
lines.push(`output:`);
|
|
222
|
+
lines.push(` format: ${answers.output_format}`);
|
|
223
|
+
lines.push(``);
|
|
224
|
+
lines.push(`tests:`);
|
|
225
|
+
lines.push(` - file: agent.test.yaml`);
|
|
226
|
+
lines.push(``);
|
|
227
|
+
|
|
228
|
+
return lines.join("\n");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function buildTestYaml(agentName: string): string {
|
|
232
|
+
return [
|
|
233
|
+
`agent: "${agentName}"`,
|
|
234
|
+
``,
|
|
235
|
+
`cases:`,
|
|
236
|
+
` - name: "basic test"`,
|
|
237
|
+
` input:`,
|
|
238
|
+
` input: "Hello, world!"`,
|
|
239
|
+
` expected_contains:`,
|
|
240
|
+
` - "Hello" # adjust to match your agent's expected output`,
|
|
241
|
+
` timeout_ms: 30000`,
|
|
242
|
+
``,
|
|
243
|
+
].join("\n");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function buildReadme(answers: WizardAnswers): string {
|
|
247
|
+
return [
|
|
248
|
+
`# ${answers.name}`,
|
|
249
|
+
``,
|
|
250
|
+
`> ${answers.description}`,
|
|
251
|
+
``,
|
|
252
|
+
`## Overview`,
|
|
253
|
+
``,
|
|
254
|
+
`| Field | Value |`,
|
|
255
|
+
`|-------|-------|`,
|
|
256
|
+
`| **Type** | ${answers.type} |`,
|
|
257
|
+
`| **LLM** | ${answers.llm_provider}${answers.llm_model ? ` / ${answers.llm_model}` : ""} |`,
|
|
258
|
+
`| **Output** | ${answers.output_format} |`,
|
|
259
|
+
`| **Trust Level** | ${answers.trust_level} |`,
|
|
260
|
+
``,
|
|
261
|
+
`## Usage`,
|
|
262
|
+
``,
|
|
263
|
+
`\`\`\`bash`,
|
|
264
|
+
`cforge run # execute the agent`,
|
|
265
|
+
`cforge test # run test suite`,
|
|
266
|
+
`\`\`\``,
|
|
267
|
+
``,
|
|
268
|
+
`## Configuration`,
|
|
269
|
+
``,
|
|
270
|
+
`Edit \`agent.yaml\` to customize the agent's behavior.`,
|
|
271
|
+
``,
|
|
272
|
+
`## Tests`,
|
|
273
|
+
``,
|
|
274
|
+
`Edit \`agent.test.yaml\` to add test cases.`,
|
|
275
|
+
``,
|
|
276
|
+
`---`,
|
|
277
|
+
``,
|
|
278
|
+
`Built with [Clayton Forge](https://github.com/devclaytonn10/clayton-forge)`,
|
|
279
|
+
``,
|
|
280
|
+
].join("\n");
|
|
281
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as readline from "readline";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
|
|
5
|
+
// ─────────────────────────────────────────────
|
|
6
|
+
// cforge run [agent-dir]
|
|
7
|
+
// ─────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export async function commandRun(agentDir?: string, inputArg?: string): Promise<void> {
|
|
10
|
+
const { parseAgentFile, findAgentFile, executeAgent } = await import("@clayton-forge/core");
|
|
11
|
+
|
|
12
|
+
// 1. Find agent.yaml
|
|
13
|
+
const searchDir = agentDir ? require("path").resolve(agentDir) : process.cwd();
|
|
14
|
+
const agentPath =
|
|
15
|
+
require("fs").existsSync(require("path").join(searchDir, "agent.yaml"))
|
|
16
|
+
? require("path").join(searchDir, "agent.yaml")
|
|
17
|
+
: findAgentFile(searchDir);
|
|
18
|
+
|
|
19
|
+
if (!agentPath) {
|
|
20
|
+
console.error(chalk.red("✗ No agent.yaml found. Run this command inside an agent directory."));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 2. Parse
|
|
25
|
+
let agent: Awaited<ReturnType<typeof parseAgentFile>>;
|
|
26
|
+
try {
|
|
27
|
+
agent = parseAgentFile(agentPath);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error(chalk.red("✗ Invalid agent.yaml:"), err instanceof Error ? err.message : err);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log("");
|
|
34
|
+
console.log(chalk.bold.cyan(` ▶ ${agent.name}`) + chalk.gray(` v${agent.version}`));
|
|
35
|
+
console.log(chalk.gray(` ${agent.description ?? ""}`));
|
|
36
|
+
console.log(chalk.gray(` Provider: ${agent.llm.provider}${agent.llm.model ? ` / ${agent.llm.model}` : ""}`));
|
|
37
|
+
console.log("");
|
|
38
|
+
|
|
39
|
+
// 3. Collect input
|
|
40
|
+
const requiredVars = agent.prompt.variables.filter((v) => v.required);
|
|
41
|
+
const variables: Record<string, string> = {};
|
|
42
|
+
|
|
43
|
+
if (inputArg && requiredVars.length <= 1) {
|
|
44
|
+
// Se só tem uma variável obrigatória, usa o argumento direto
|
|
45
|
+
const varName = requiredVars[0]?.name ?? "input";
|
|
46
|
+
variables[varName] = inputArg;
|
|
47
|
+
} else {
|
|
48
|
+
// Interactive input
|
|
49
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
50
|
+
const question = (prompt: string): Promise<string> =>
|
|
51
|
+
new Promise((resolve) => rl.question(prompt, resolve));
|
|
52
|
+
|
|
53
|
+
for (const variable of agent.prompt.variables) {
|
|
54
|
+
if (variable.required || !variable.default) {
|
|
55
|
+
const defaultHint = variable.default ? chalk.gray(` (default: ${variable.default})`) : "";
|
|
56
|
+
const descHint = variable.description ? chalk.gray(` — ${variable.description}`) : "";
|
|
57
|
+
const value = await question(` ${chalk.white(variable.name)}${descHint}${defaultHint}: `);
|
|
58
|
+
variables[variable.name] = value || variable.default || "";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
rl.close();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log("");
|
|
65
|
+
|
|
66
|
+
// 4. Execute
|
|
67
|
+
const spinner = ora("Running agent...").start();
|
|
68
|
+
const result = await executeAgent(agent, { variables });
|
|
69
|
+
|
|
70
|
+
if (result.success) {
|
|
71
|
+
spinner.succeed(chalk.green(`Done`) + chalk.gray(` (${result.duration_ms}ms)`));
|
|
72
|
+
console.log("");
|
|
73
|
+
console.log(chalk.bold(" Output:"));
|
|
74
|
+
console.log("");
|
|
75
|
+
|
|
76
|
+
// Format output based on type
|
|
77
|
+
const lines = result.output.split("\n");
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
console.log(" " + line);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (result.llm_response?.usage) {
|
|
83
|
+
const { input_tokens, output_tokens } = result.llm_response.usage;
|
|
84
|
+
console.log("");
|
|
85
|
+
console.log(
|
|
86
|
+
chalk.gray(` Tokens: ${input_tokens ?? "?"} in / ${output_tokens ?? "?"} out`)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
spinner.fail(chalk.red("Execution failed"));
|
|
91
|
+
console.log("");
|
|
92
|
+
console.error(chalk.red(" Error:"), result.error);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log("");
|
|
97
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
|
|
6
|
+
// ─────────────────────────────────────────────
|
|
7
|
+
// cforge test [agent-dir]
|
|
8
|
+
// ─────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export async function commandTest(agentDir?: string): Promise<void> {
|
|
11
|
+
const { parseAgentFile, parseTestFile, runTestSuite, findAgentFile } =
|
|
12
|
+
await import("@clayton-forge/core");
|
|
13
|
+
|
|
14
|
+
// 1. Find agent.yaml
|
|
15
|
+
const searchDir = agentDir ? path.resolve(agentDir) : process.cwd();
|
|
16
|
+
const agentYamlPath = fs.existsSync(path.join(searchDir, "agent.yaml"))
|
|
17
|
+
? path.join(searchDir, "agent.yaml")
|
|
18
|
+
: findAgentFile(searchDir);
|
|
19
|
+
|
|
20
|
+
if (!agentYamlPath) {
|
|
21
|
+
console.error(chalk.red("✗ No agent.yaml found."));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 2. Parse agent
|
|
26
|
+
let agent: Awaited<ReturnType<typeof parseAgentFile>>;
|
|
27
|
+
try {
|
|
28
|
+
agent = parseAgentFile(agentYamlPath);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(chalk.red("✗ Invalid agent.yaml:"), err instanceof Error ? err.message : err);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 3. Find test files
|
|
35
|
+
const agentDir2 = path.dirname(agentYamlPath);
|
|
36
|
+
const testFiles = (agent.tests ?? []).map((t) => path.resolve(agentDir2, t.file));
|
|
37
|
+
|
|
38
|
+
// Fallback: tenta agent.test.yaml se não há tests declarados
|
|
39
|
+
if (testFiles.length === 0) {
|
|
40
|
+
const fallback = path.join(agentDir2, "agent.test.yaml");
|
|
41
|
+
if (fs.existsSync(fallback)) testFiles.push(fallback);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (testFiles.length === 0) {
|
|
45
|
+
console.error(chalk.red("✗ No test files found. Create agent.test.yaml first."));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log("");
|
|
50
|
+
console.log(chalk.bold.cyan(` ⚗ Testing: ${agent.name}`) + chalk.gray(` v${agent.version}`));
|
|
51
|
+
console.log(chalk.gray(` Provider: ${agent.llm.provider}${agent.llm.model ? ` / ${agent.llm.model}` : ""}`));
|
|
52
|
+
console.log("");
|
|
53
|
+
|
|
54
|
+
let totalPassed = 0;
|
|
55
|
+
let totalFailed = 0;
|
|
56
|
+
|
|
57
|
+
for (const testFile of testFiles) {
|
|
58
|
+
// 4. Parse test suite
|
|
59
|
+
let suite: Awaited<ReturnType<typeof parseTestFile>>;
|
|
60
|
+
try {
|
|
61
|
+
suite = parseTestFile(testFile);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error(chalk.red(`✗ Invalid test file ${testFile}:`), err instanceof Error ? err.message : err);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(chalk.gray(` File: ${path.basename(testFile)}`));
|
|
68
|
+
console.log(chalk.gray(` Cases: ${suite.cases.length}`));
|
|
69
|
+
console.log("");
|
|
70
|
+
|
|
71
|
+
// 5. Run
|
|
72
|
+
const spinner = ora("Running tests...").start();
|
|
73
|
+
const summary = await runTestSuite(agent, suite);
|
|
74
|
+
spinner.stop();
|
|
75
|
+
|
|
76
|
+
// 6. Display results
|
|
77
|
+
for (const result of summary.results) {
|
|
78
|
+
if (result.passed) {
|
|
79
|
+
console.log(
|
|
80
|
+
chalk.green(" ✓ PASS") +
|
|
81
|
+
chalk.white(` ${result.name}`) +
|
|
82
|
+
chalk.gray(` (${result.duration_ms}ms)`)
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
console.log(
|
|
86
|
+
chalk.red(" ✗ FAIL") +
|
|
87
|
+
chalk.white(` ${result.name}`) +
|
|
88
|
+
chalk.gray(` (${result.duration_ms}ms)`)
|
|
89
|
+
);
|
|
90
|
+
for (const failure of result.failures) {
|
|
91
|
+
console.log(chalk.red(" ↳ " + failure));
|
|
92
|
+
}
|
|
93
|
+
if (result.error) {
|
|
94
|
+
console.log(chalk.red(" Error: " + result.error));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
totalPassed += summary.passed;
|
|
100
|
+
totalFailed += summary.failed;
|
|
101
|
+
|
|
102
|
+
console.log("");
|
|
103
|
+
console.log(
|
|
104
|
+
chalk.gray(" ─────────────────────────────────────")
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const statusColor = summary.failed === 0 ? chalk.green : chalk.red;
|
|
108
|
+
console.log(
|
|
109
|
+
statusColor(
|
|
110
|
+
` ${summary.passed}/${summary.total} passed`
|
|
111
|
+
) +
|
|
112
|
+
chalk.gray(` · ${summary.duration_ms}ms`)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log("");
|
|
117
|
+
|
|
118
|
+
// Final exit code
|
|
119
|
+
if (totalFailed > 0) {
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
Binary file
|