@funkai/cli 0.1.1 → 0.1.3
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/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +22 -0
- package/dist/index.mjs +13118 -9981
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -7
- package/src/commands/generate.ts +4 -62
- package/src/commands/prompts/create.ts +1 -1
- package/src/commands/prompts/generate.ts +75 -48
- package/src/commands/prompts/lint.ts +62 -44
- package/src/commands/prompts/setup.ts +2 -7
- package/src/commands/validate.ts +4 -27
- package/src/index.ts +36 -2
- package/src/lib/prompts/codegen.ts +12 -16
- package/src/lib/prompts/flatten.ts +2 -7
- package/src/lib/prompts/frontmatter.ts +28 -33
- package/src/lib/prompts/lint.ts +8 -8
- package/src/lib/prompts/paths.ts +5 -5
- package/src/lib/prompts/pipeline.ts +7 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funkai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CLI for the funkai AI SDK framework",
|
|
6
6
|
"keywords": [
|
|
@@ -26,18 +26,18 @@
|
|
|
26
26
|
},
|
|
27
27
|
"type": "module",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@kidd-cli/core": "^0.
|
|
29
|
+
"@kidd-cli/core": "^0.8.2",
|
|
30
30
|
"liquidjs": "^10.25.0",
|
|
31
31
|
"ts-pattern": "^5.9.0",
|
|
32
32
|
"yaml": "^2.8.2",
|
|
33
33
|
"zod": "^4.3.6",
|
|
34
|
-
"@funkai/prompts": "0.
|
|
34
|
+
"@funkai/prompts": "0.2.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@kidd-cli/cli": "^0.
|
|
37
|
+
"@kidd-cli/cli": "^0.4.5",
|
|
38
38
|
"@types/node": "^25.5.0",
|
|
39
39
|
"@vitest/coverage-v8": "^4.1.0",
|
|
40
|
-
"tsdown": "^0.21.
|
|
40
|
+
"tsdown": "^0.21.3",
|
|
41
41
|
"typescript": "^5.9.3",
|
|
42
42
|
"vitest": "^4.1.0"
|
|
43
43
|
},
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
"dev": "kidd dev",
|
|
49
49
|
"build": "kidd build",
|
|
50
50
|
"typecheck": "tsc --noEmit",
|
|
51
|
-
"test": "vitest run"
|
|
52
|
-
"test:coverage": "vitest run --coverage"
|
|
51
|
+
"test": "vitest run"
|
|
53
52
|
}
|
|
54
53
|
}
|
package/src/commands/generate.ts
CHANGED
|
@@ -1,78 +1,20 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
|
|
4
1
|
import { command } from "@kidd-cli/core";
|
|
5
|
-
import { match } from "ts-pattern";
|
|
6
|
-
import { z } from "zod";
|
|
7
2
|
|
|
8
|
-
import {
|
|
9
|
-
import { hasLintErrors } from "@/lib/prompts/lint.js";
|
|
10
|
-
import { runGeneratePipeline } from "@/lib/prompts/pipeline.js";
|
|
3
|
+
import { generateArgs, handleGenerate } from "./prompts/generate.js";
|
|
11
4
|
|
|
12
5
|
export default command({
|
|
13
6
|
description: "Run all code generation across the funkai SDK",
|
|
14
|
-
|
|
15
|
-
out: z.string().describe("Output directory for generated files"),
|
|
16
|
-
roots: z.array(z.string()).describe("Root directories to scan for .prompt files"),
|
|
17
|
-
partials: z.string().optional().describe("Custom partials directory"),
|
|
18
|
-
silent: z.boolean().default(false).describe("Suppress output except errors"),
|
|
19
|
-
}),
|
|
7
|
+
options: generateArgs,
|
|
20
8
|
handler(ctx) {
|
|
21
|
-
const {
|
|
9
|
+
const { silent } = ctx.args;
|
|
22
10
|
|
|
23
11
|
// --- Prompts codegen ---
|
|
24
12
|
if (!silent) {
|
|
25
13
|
ctx.logger.info("Running prompts code generation...");
|
|
26
14
|
}
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!silent) {
|
|
31
|
-
ctx.logger.info(`Found ${discovered} prompt(s)`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
for (const prompt of prompts) {
|
|
35
|
-
if (!silent) {
|
|
36
|
-
const varCount = prompt.schema.length;
|
|
37
|
-
const varList = match(varCount > 0)
|
|
38
|
-
.with(true, () => ` (${prompt.schema.map((v) => v.name).join(", ")})`)
|
|
39
|
-
.otherwise(() => "");
|
|
40
|
-
ctx.logger.step(`${prompt.name}${varList}`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (const result of lintResults) {
|
|
45
|
-
for (const diag of result.diagnostics) {
|
|
46
|
-
if (diag.level === "error") {
|
|
47
|
-
ctx.logger.error(diag.message);
|
|
48
|
-
} else {
|
|
49
|
-
ctx.logger.warn(diag.message);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (hasLintErrors([...lintResults])) {
|
|
55
|
-
ctx.fail("Lint errors found. Fix them before generating.");
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const outDir = resolve(out);
|
|
59
|
-
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: output directory from CLI config
|
|
60
|
-
mkdirSync(outDir, { recursive: true });
|
|
61
|
-
|
|
62
|
-
for (const prompt of prompts) {
|
|
63
|
-
const content = generatePromptModule(prompt);
|
|
64
|
-
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: writing generated module to output directory
|
|
65
|
-
writeFileSync(resolve(outDir, `${prompt.name}.ts`), content, "utf-8");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const registryContent = generateRegistry([...prompts]);
|
|
69
|
-
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: writing generated registry to output directory
|
|
70
|
-
writeFileSync(resolve(outDir, "index.ts"), registryContent, "utf-8");
|
|
16
|
+
handleGenerate(ctx.args, ctx.logger, ctx.fail);
|
|
71
17
|
|
|
72
18
|
// --- Future: agents codegen ---
|
|
73
|
-
|
|
74
|
-
if (!silent) {
|
|
75
|
-
ctx.logger.success(`Generated ${prompts.length} prompt module(s) + registry → ${outDir}`);
|
|
76
|
-
}
|
|
77
19
|
},
|
|
78
20
|
});
|
|
@@ -13,7 +13,7 @@ name: ${name}
|
|
|
13
13
|
|
|
14
14
|
export default command({
|
|
15
15
|
description: "Create a new .prompt file",
|
|
16
|
-
|
|
16
|
+
options: z.object({
|
|
17
17
|
name: z.string().describe("Prompt name (kebab-case)"),
|
|
18
18
|
out: z.string().optional().describe("Output directory (defaults to cwd)"),
|
|
19
19
|
partial: z.boolean().default(false).describe("Create as a partial in .prompts/partials/"),
|
|
@@ -2,70 +2,97 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { command } from "@kidd-cli/core";
|
|
5
|
-
import { match } from "ts-pattern";
|
|
6
5
|
import { z } from "zod";
|
|
7
6
|
|
|
8
7
|
import { generatePromptModule, generateRegistry } from "@/lib/prompts/codegen.js";
|
|
9
8
|
import { hasLintErrors } from "@/lib/prompts/lint.js";
|
|
10
9
|
import { runGeneratePipeline } from "@/lib/prompts/pipeline.js";
|
|
11
10
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
export const generateArgs = z.object({
|
|
12
|
+
out: z.string().describe("Output directory for generated files"),
|
|
13
|
+
roots: z.array(z.string()).describe("Root directories to scan for .prompt files"),
|
|
14
|
+
partials: z.string().optional().describe("Custom partials directory"),
|
|
15
|
+
silent: z.boolean().default(false).describe("Suppress output except errors"),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export type GenerateArgs = z.infer<typeof generateArgs>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Shared handler for prompts code generation.
|
|
22
|
+
*
|
|
23
|
+
* @param args - Parsed CLI arguments.
|
|
24
|
+
* @param logger - Logger instance from the command context.
|
|
25
|
+
* @param fail - Failure callback from the command context.
|
|
26
|
+
*/
|
|
27
|
+
export function handleGenerate(
|
|
28
|
+
args: {
|
|
29
|
+
readonly out: string;
|
|
30
|
+
readonly roots: readonly string[];
|
|
31
|
+
readonly partials?: string;
|
|
32
|
+
readonly silent: boolean;
|
|
33
|
+
},
|
|
34
|
+
logger: {
|
|
35
|
+
info: (msg: string) => void;
|
|
36
|
+
step: (msg: string) => void;
|
|
37
|
+
error: (msg: string) => void;
|
|
38
|
+
warn: (msg: string) => void;
|
|
39
|
+
success: (msg: string) => void;
|
|
40
|
+
},
|
|
41
|
+
fail: (msg: string) => never,
|
|
42
|
+
): void {
|
|
43
|
+
const { out, roots, partials, silent } = args;
|
|
44
|
+
|
|
45
|
+
const { discovered, lintResults, prompts } = runGeneratePipeline({ roots, out, partials });
|
|
22
46
|
|
|
23
|
-
|
|
47
|
+
if (!silent) {
|
|
48
|
+
logger.info(`Found ${discovered} prompt(s)`);
|
|
49
|
+
}
|
|
24
50
|
|
|
25
|
-
|
|
26
|
-
|
|
51
|
+
if (!silent) {
|
|
52
|
+
for (const prompt of prompts) {
|
|
53
|
+
const varList =
|
|
54
|
+
prompt.schema.length > 0 ? ` (${prompt.schema.map((v) => v.name).join(", ")})` : "";
|
|
55
|
+
logger.step(`${prompt.name}${varList}`);
|
|
27
56
|
}
|
|
57
|
+
}
|
|
28
58
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
59
|
+
for (const result of lintResults) {
|
|
60
|
+
for (const diag of result.diagnostics) {
|
|
61
|
+
if (diag.level === "error") {
|
|
62
|
+
logger.error(diag.message);
|
|
63
|
+
} else {
|
|
64
|
+
logger.warn(diag.message);
|
|
65
|
+
}
|
|
37
66
|
}
|
|
67
|
+
}
|
|
38
68
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (diag.level === "error") {
|
|
43
|
-
ctx.logger.error(diag.message);
|
|
44
|
-
} else {
|
|
45
|
-
ctx.logger.warn(diag.message);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
69
|
+
if (hasLintErrors(lintResults)) {
|
|
70
|
+
fail("Lint errors found. Fix them before generating.");
|
|
71
|
+
}
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
73
|
+
const outDir = resolve(out);
|
|
74
|
+
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: output directory from CLI config
|
|
75
|
+
mkdirSync(outDir, { recursive: true });
|
|
52
76
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
for (const prompt of prompts) {
|
|
78
|
+
const content = generatePromptModule(prompt);
|
|
79
|
+
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: writing generated module to output directory
|
|
80
|
+
writeFileSync(resolve(outDir, `${prompt.name}.ts`), content, "utf-8");
|
|
81
|
+
}
|
|
56
82
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
writeFileSync(resolve(outDir, `${prompt.name}.ts`), content, "utf-8");
|
|
61
|
-
});
|
|
83
|
+
const registryContent = generateRegistry(prompts);
|
|
84
|
+
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: writing generated registry to output directory
|
|
85
|
+
writeFileSync(resolve(outDir, "index.ts"), registryContent, "utf-8");
|
|
62
86
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
if (!silent) {
|
|
88
|
+
logger.success(`Generated ${prompts.length} prompt module(s) + registry → ${outDir}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
66
91
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
92
|
+
export default command({
|
|
93
|
+
description: "Generate TypeScript modules from .prompt files",
|
|
94
|
+
options: generateArgs,
|
|
95
|
+
handler(ctx) {
|
|
96
|
+
handleGenerate(ctx.args, ctx.logger, ctx.fail);
|
|
70
97
|
},
|
|
71
98
|
});
|
|
@@ -1,57 +1,75 @@
|
|
|
1
1
|
import { command } from "@kidd-cli/core";
|
|
2
|
-
import { match } from "ts-pattern";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
|
|
5
4
|
import { hasLintErrors } from "@/lib/prompts/lint.js";
|
|
6
5
|
import { runLintPipeline } from "@/lib/prompts/pipeline.js";
|
|
7
6
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
silent: z.boolean().default(false).describe("Suppress output except errors"),
|
|
14
|
-
}),
|
|
15
|
-
handler(ctx) {
|
|
16
|
-
const { roots, partials, silent } = ctx.args;
|
|
7
|
+
export const lintArgs = z.object({
|
|
8
|
+
roots: z.array(z.string()).describe("Root directories to scan for .prompt files"),
|
|
9
|
+
partials: z.string().optional().describe("Custom partials directory"),
|
|
10
|
+
silent: z.boolean().default(false).describe("Suppress output except errors"),
|
|
11
|
+
});
|
|
17
12
|
|
|
18
|
-
|
|
13
|
+
export type LintArgs = z.infer<typeof lintArgs>;
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Shared handler for prompts lint/validation.
|
|
17
|
+
*
|
|
18
|
+
* @param args - Parsed CLI arguments.
|
|
19
|
+
* @param logger - Logger instance from the command context.
|
|
20
|
+
* @param fail - Failure callback from the command context.
|
|
21
|
+
*/
|
|
22
|
+
export function handleLint(
|
|
23
|
+
args: { readonly roots: readonly string[]; readonly partials?: string; readonly silent: boolean },
|
|
24
|
+
logger: {
|
|
25
|
+
info: (msg: string) => void;
|
|
26
|
+
error: (msg: string) => void;
|
|
27
|
+
warn: (msg: string) => void;
|
|
28
|
+
},
|
|
29
|
+
fail: (msg: string) => never,
|
|
30
|
+
): void {
|
|
31
|
+
const { roots, partials, silent } = args;
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const errorCount = diagnostics.filter((d) => d.level === "error").length;
|
|
35
|
-
const warnCount = diagnostics.filter((d) => d.level !== "error").length;
|
|
36
|
-
|
|
37
|
-
if (!silent) {
|
|
38
|
-
const summary = [
|
|
39
|
-
`${discovered} prompt(s) linted`,
|
|
40
|
-
match(errorCount > 0)
|
|
41
|
-
.with(true, () => `${errorCount} error(s)`)
|
|
42
|
-
.otherwise(() => undefined),
|
|
43
|
-
match(warnCount > 0)
|
|
44
|
-
.with(true, () => `${warnCount} warning(s)`)
|
|
45
|
-
.otherwise(() => undefined),
|
|
46
|
-
]
|
|
47
|
-
.filter(Boolean)
|
|
48
|
-
.join(", ");
|
|
49
|
-
|
|
50
|
-
ctx.logger.info(summary);
|
|
51
|
-
}
|
|
33
|
+
const { discovered, results } = runLintPipeline({ roots, partials });
|
|
34
|
+
|
|
35
|
+
if (!silent) {
|
|
36
|
+
logger.info(`Linting ${discovered} prompt(s)...`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const diagnostics = results.flatMap((result) => result.diagnostics);
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
for (const diag of diagnostics) {
|
|
42
|
+
if (diag.level === "error") {
|
|
43
|
+
logger.error(diag.message);
|
|
44
|
+
} else {
|
|
45
|
+
logger.warn(diag.message);
|
|
55
46
|
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const errorCount = diagnostics.filter((d) => d.level === "error").length;
|
|
50
|
+
const warnCount = diagnostics.filter((d) => d.level !== "error").length;
|
|
51
|
+
|
|
52
|
+
if (!silent) {
|
|
53
|
+
const summary = [
|
|
54
|
+
`${discovered} prompt(s) linted`,
|
|
55
|
+
errorCount > 0 ? `${errorCount} error(s)` : undefined,
|
|
56
|
+
warnCount > 0 ? `${warnCount} warning(s)` : undefined,
|
|
57
|
+
]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(", ");
|
|
60
|
+
|
|
61
|
+
logger.info(summary);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (hasLintErrors(results)) {
|
|
65
|
+
fail("Lint errors found.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default command({
|
|
70
|
+
description: "Validate .prompt files for schema/template mismatches",
|
|
71
|
+
options: lintArgs,
|
|
72
|
+
handler(ctx) {
|
|
73
|
+
handleLint(ctx.args, ctx.logger, ctx.fail);
|
|
56
74
|
},
|
|
57
75
|
});
|
|
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { command } from "@kidd-cli/core";
|
|
5
|
-
import { match } from "ts-pattern";
|
|
6
5
|
|
|
7
6
|
const VSCODE_DIR = ".vscode";
|
|
8
7
|
const SETTINGS_FILE = "settings.json";
|
|
@@ -76,14 +75,10 @@ export default command({
|
|
|
76
75
|
|
|
77
76
|
if (shouldGitignore) {
|
|
78
77
|
const gitignorePath = resolve(GITIGNORE_FILE);
|
|
79
|
-
const existing =
|
|
80
|
-
.with(true, () => readFileSync(gitignorePath, "utf-8"))
|
|
81
|
-
.otherwise(() => "");
|
|
78
|
+
const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : "";
|
|
82
79
|
|
|
83
80
|
if (!existing.includes(GITIGNORE_ENTRY)) {
|
|
84
|
-
const separator =
|
|
85
|
-
.with(true, () => "\n")
|
|
86
|
-
.otherwise(() => "");
|
|
81
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
87
82
|
const block = `${separator}\n# Generated prompt client (created by \`funkai prompts generate\`)\n${GITIGNORE_ENTRY}\n`;
|
|
88
83
|
writeFileSync(gitignorePath, existing + block, "utf-8");
|
|
89
84
|
ctx.logger.success(`Added ${GITIGNORE_ENTRY} to ${gitignorePath}`);
|
package/src/commands/validate.ts
CHANGED
|
@@ -1,45 +1,22 @@
|
|
|
1
1
|
import { command } from "@kidd-cli/core";
|
|
2
|
-
import { match } from "ts-pattern";
|
|
3
|
-
import { z } from "zod";
|
|
4
2
|
|
|
5
|
-
import {
|
|
6
|
-
import { runLintPipeline } from "@/lib/prompts/pipeline.js";
|
|
3
|
+
import { handleLint, lintArgs } from "./prompts/lint.js";
|
|
7
4
|
|
|
8
5
|
export default command({
|
|
9
6
|
description: "Run all validations across the funkai SDK",
|
|
10
|
-
|
|
11
|
-
roots: z.array(z.string()).describe("Root directories to scan for .prompt files"),
|
|
12
|
-
partials: z.string().optional().describe("Custom partials directory"),
|
|
13
|
-
silent: z.boolean().default(false).describe("Suppress output except errors"),
|
|
14
|
-
}),
|
|
7
|
+
options: lintArgs,
|
|
15
8
|
handler(ctx) {
|
|
16
|
-
const {
|
|
9
|
+
const { silent } = ctx.args;
|
|
17
10
|
|
|
18
11
|
// --- Prompts validation ---
|
|
19
12
|
if (!silent) {
|
|
20
13
|
ctx.logger.info("Running prompts validation...");
|
|
21
14
|
}
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!silent) {
|
|
26
|
-
ctx.logger.info(`Found ${discovered} prompt(s)`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const diagnostics = results.flatMap((result) => result.diagnostics);
|
|
30
|
-
|
|
31
|
-
for (const diag of diagnostics) {
|
|
32
|
-
match(diag.level)
|
|
33
|
-
.with("error", () => ctx.logger.error(diag.message))
|
|
34
|
-
.otherwise(() => ctx.logger.warn(diag.message));
|
|
35
|
-
}
|
|
16
|
+
handleLint(ctx.args, ctx.logger, ctx.fail);
|
|
36
17
|
|
|
37
18
|
// --- Future: agents validation ---
|
|
38
19
|
|
|
39
|
-
if (hasLintErrors([...results])) {
|
|
40
|
-
ctx.fail("Validation errors found.");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
20
|
if (!silent) {
|
|
44
21
|
ctx.logger.success("All validations passed.");
|
|
45
22
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
import { cli, command } from "@kidd-cli/core";
|
|
4
|
+
|
|
5
|
+
import agentsValidate from "@/commands/agents/validate.js";
|
|
6
|
+
import generate from "@/commands/generate.js";
|
|
7
|
+
import promptsCreate from "@/commands/prompts/create.js";
|
|
8
|
+
import promptsGenerate from "@/commands/prompts/generate.js";
|
|
9
|
+
import promptsLint from "@/commands/prompts/lint.js";
|
|
10
|
+
import promptsSetup from "@/commands/prompts/setup.js";
|
|
11
|
+
import setup from "@/commands/setup.js";
|
|
12
|
+
import validate from "@/commands/validate.js";
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const packageJson = require("../package.json") as { readonly version: string };
|
|
2
16
|
|
|
3
17
|
await cli({
|
|
4
18
|
description: "CLI for the funkai AI SDK framework",
|
|
5
19
|
name: "funkai",
|
|
6
|
-
version:
|
|
20
|
+
version: packageJson.version,
|
|
21
|
+
commands: {
|
|
22
|
+
generate,
|
|
23
|
+
setup,
|
|
24
|
+
validate,
|
|
25
|
+
agents: command({
|
|
26
|
+
description: "Agent-related commands",
|
|
27
|
+
commands: {
|
|
28
|
+
validate: agentsValidate,
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
prompts: command({
|
|
32
|
+
description: "Prompt-related commands",
|
|
33
|
+
commands: {
|
|
34
|
+
create: promptsCreate,
|
|
35
|
+
generate: promptsGenerate,
|
|
36
|
+
lint: promptsLint,
|
|
37
|
+
setup: promptsSetup,
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
7
41
|
});
|
|
@@ -6,11 +6,11 @@ import type { SchemaVariable } from "./frontmatter.js";
|
|
|
6
6
|
* Fully parsed prompt ready for code generation.
|
|
7
7
|
*/
|
|
8
8
|
export interface ParsedPrompt {
|
|
9
|
-
name: string;
|
|
10
|
-
group?: string;
|
|
11
|
-
schema: SchemaVariable[];
|
|
12
|
-
template: string;
|
|
13
|
-
sourcePath: string;
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly group?: string;
|
|
11
|
+
readonly schema: readonly SchemaVariable[];
|
|
12
|
+
readonly template: string;
|
|
13
|
+
readonly sourcePath: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -57,7 +57,7 @@ function escapeTemplateLiteral(str: string): string {
|
|
|
57
57
|
*
|
|
58
58
|
* @private
|
|
59
59
|
*/
|
|
60
|
-
function generateSchemaExpression(vars: SchemaVariable[]): string {
|
|
60
|
+
function generateSchemaExpression(vars: readonly SchemaVariable[]): string {
|
|
61
61
|
if (vars.length === 0) {
|
|
62
62
|
return "z.object({})";
|
|
63
63
|
}
|
|
@@ -65,9 +65,7 @@ function generateSchemaExpression(vars: SchemaVariable[]): string {
|
|
|
65
65
|
const fields = vars
|
|
66
66
|
.map((v) => {
|
|
67
67
|
const base = "z.string()";
|
|
68
|
-
const expr =
|
|
69
|
-
.with(true, () => base)
|
|
70
|
-
.otherwise(() => `${base}.optional()`);
|
|
68
|
+
const expr = v.required ? base : `${base}.optional()`;
|
|
71
69
|
return ` ${v.name}: ${expr},`;
|
|
72
70
|
})
|
|
73
71
|
.join("\n");
|
|
@@ -95,16 +93,14 @@ const HEADER = [
|
|
|
95
93
|
export function generatePromptModule(prompt: ParsedPrompt): string {
|
|
96
94
|
const escaped = escapeTemplateLiteral(prompt.template);
|
|
97
95
|
const schemaExpr = generateSchemaExpression(prompt.schema);
|
|
98
|
-
const groupValue =
|
|
99
|
-
.with(true, () => `'${prompt.group}' as const`)
|
|
100
|
-
.otherwise(() => "undefined");
|
|
96
|
+
const groupValue = prompt.group != null ? `'${prompt.group}' as const` : "undefined";
|
|
101
97
|
|
|
102
98
|
const lines: string[] = [
|
|
103
99
|
HEADER,
|
|
104
100
|
`// Source: ${prompt.sourcePath}`,
|
|
105
101
|
"",
|
|
106
102
|
"import { z } from 'zod'",
|
|
107
|
-
"import {
|
|
103
|
+
"import { liquidEngine } from '@funkai/prompts/runtime'",
|
|
108
104
|
"",
|
|
109
105
|
`const schema = ${schemaExpr}`,
|
|
110
106
|
"",
|
|
@@ -119,7 +115,7 @@ export function generatePromptModule(prompt: ParsedPrompt): string {
|
|
|
119
115
|
...match(prompt.schema.length)
|
|
120
116
|
.with(0, () => [
|
|
121
117
|
" render(variables?: undefined): string {",
|
|
122
|
-
" return
|
|
118
|
+
" return liquidEngine.parseAndRenderSync(template, {})",
|
|
123
119
|
" },",
|
|
124
120
|
" validate(variables?: undefined): Variables {",
|
|
125
121
|
" return schema.parse(variables ?? {})",
|
|
@@ -127,7 +123,7 @@ export function generatePromptModule(prompt: ParsedPrompt): string {
|
|
|
127
123
|
])
|
|
128
124
|
.otherwise(() => [
|
|
129
125
|
" render(variables: Variables): string {",
|
|
130
|
-
" return
|
|
126
|
+
" return liquidEngine.parseAndRenderSync(template, schema.parse(variables))",
|
|
131
127
|
" },",
|
|
132
128
|
" validate(variables: unknown): Variables {",
|
|
133
129
|
" return schema.parse(variables)",
|
|
@@ -214,7 +210,7 @@ function serializeTree(node: TreeNode, indent: number): string[] {
|
|
|
214
210
|
* Prompts are organized into a nested object structure based on their
|
|
215
211
|
* `group` field, with each `/`-separated segment becoming a nesting level.
|
|
216
212
|
*/
|
|
217
|
-
export function generateRegistry(prompts: ParsedPrompt[]): string {
|
|
213
|
+
export function generateRegistry(prompts: readonly ParsedPrompt[]): string {
|
|
218
214
|
const sorted = [...prompts].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
219
215
|
|
|
220
216
|
const imports = sorted
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Liquid } from "liquidjs";
|
|
2
|
-
import { match } from "ts-pattern";
|
|
3
2
|
|
|
4
3
|
// oxlint-disable-next-line security/detect-unsafe-regex -- template parsing, not adversarial input
|
|
5
4
|
const RENDER_TAG_RE = /\{%-?\s*render\s+'([^']+)'(?:\s*,\s*(.*?))?\s*-?%\}/g;
|
|
@@ -40,12 +39,8 @@ function parseParams(raw: string, partialName: string): Record<string, string> {
|
|
|
40
39
|
*/
|
|
41
40
|
function parseRenderTags(template: string): RenderTag[] {
|
|
42
41
|
return [...template.matchAll(RENDER_TAG_RE)].map((m) => {
|
|
43
|
-
const rawParams =
|
|
44
|
-
|
|
45
|
-
.otherwise(() => "");
|
|
46
|
-
const params = match(rawParams.length > 0)
|
|
47
|
-
.with(true, () => parseParams(rawParams, m[1]))
|
|
48
|
-
.otherwise(() => ({}));
|
|
42
|
+
const rawParams = m[2] != null ? m[2].trim() : "";
|
|
43
|
+
const params = rawParams.length > 0 ? parseParams(rawParams, m[1]) : {};
|
|
49
44
|
|
|
50
45
|
return { fullMatch: m[0], partialName: m[1], params };
|
|
51
46
|
});
|