@funkai/cli 0.1.4 → 0.2.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/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +49 -0
- package/dist/index.mjs +376 -3237
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/src/commands/generate.ts +1 -1
- package/src/commands/prompts/create.ts +1 -0
- package/src/commands/prompts/generate.ts +37 -28
- package/src/commands/prompts/lint.ts +26 -17
- package/src/commands/prompts/setup.ts +53 -27
- package/src/commands/validate.ts +1 -1
- package/src/lib/prompts/__tests__/flatten.test.ts +29 -27
- package/src/lib/prompts/__tests__/frontmatter.test.ts +17 -15
- package/src/lib/prompts/codegen.ts +37 -37
- package/src/lib/prompts/extract-variables.ts +20 -9
- package/src/lib/prompts/flatten.ts +62 -16
- package/src/lib/prompts/frontmatter.ts +97 -53
- package/src/lib/prompts/lint.ts +18 -23
- package/src/lib/prompts/paths.ts +24 -10
- package/src/lib/prompts/pipeline.ts +5 -4
package/src/lib/prompts/paths.ts
CHANGED
|
@@ -1,35 +1,45 @@
|
|
|
1
1
|
import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { basename, extname, join, resolve } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
|
|
4
6
|
import { FRONTMATTER_RE, NAME_RE } from "./frontmatter.js";
|
|
5
7
|
|
|
6
8
|
const MAX_DEPTH = 5;
|
|
7
9
|
const PROMPT_EXT = ".prompt";
|
|
8
10
|
|
|
11
|
+
/** A `.prompt` file discovered during directory scanning. */
|
|
9
12
|
export interface DiscoveredPrompt {
|
|
10
13
|
readonly name: string;
|
|
11
14
|
readonly filePath: string;
|
|
12
15
|
}
|
|
13
16
|
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Private
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
14
21
|
/**
|
|
15
22
|
* Extract the `name` field from YAML frontmatter.
|
|
16
23
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
24
|
+
* Uses a proper YAML parser to handle quoted values and edge cases.
|
|
25
|
+
*
|
|
26
|
+
* @private
|
|
19
27
|
*/
|
|
20
28
|
function extractName(content: string): string | undefined {
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
29
|
+
const fmMatch = content.match(FRONTMATTER_RE);
|
|
30
|
+
if (!fmMatch) {
|
|
23
31
|
return undefined;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
try {
|
|
35
|
+
const parsed = parseYaml(fmMatch[1]) as Record<string, unknown> | null;
|
|
36
|
+
if (parsed !== null && parsed !== undefined && typeof parsed.name === "string") {
|
|
37
|
+
return parsed.name;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
} catch {
|
|
29
41
|
return undefined;
|
|
30
42
|
}
|
|
31
|
-
|
|
32
|
-
return nameLine.slice("name:".length).trim();
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
/**
|
|
@@ -37,6 +47,8 @@ function extractName(content: string): string | undefined {
|
|
|
37
47
|
*
|
|
38
48
|
* If the file is named `prompt.prompt`, uses the parent directory name.
|
|
39
49
|
* Otherwise uses the file stem (e.g. `my-agent.prompt` -> `my-agent`).
|
|
50
|
+
*
|
|
51
|
+
* @private
|
|
40
52
|
*/
|
|
41
53
|
function deriveNameFromPath(filePath: string): string {
|
|
42
54
|
const stem = basename(filePath, PROMPT_EXT);
|
|
@@ -48,6 +60,8 @@ function deriveNameFromPath(filePath: string): string {
|
|
|
48
60
|
|
|
49
61
|
/**
|
|
50
62
|
* Recursively scan a directory for `.prompt` files.
|
|
63
|
+
*
|
|
64
|
+
* @private
|
|
51
65
|
*/
|
|
52
66
|
function scanDirectory(dir: string, depth: number): DiscoveredPrompt[] {
|
|
53
67
|
if (depth > MAX_DEPTH) {
|
|
@@ -102,7 +116,7 @@ function scanDirectory(dir: string, depth: number): DiscoveredPrompt[] {
|
|
|
102
116
|
* @returns Sorted, deduplicated list of discovered prompts.
|
|
103
117
|
* @throws If duplicate prompt names are found across roots.
|
|
104
118
|
*/
|
|
105
|
-
export function discoverPrompts(roots: readonly string[]): DiscoveredPrompt[] {
|
|
119
|
+
export function discoverPrompts(roots: readonly string[]): readonly DiscoveredPrompt[] {
|
|
106
120
|
const all = roots.flatMap((root) => scanDirectory(resolve(root), 0));
|
|
107
121
|
|
|
108
122
|
const byName = Map.groupBy(all, (prompt) => prompt.name);
|
|
@@ -14,6 +14,7 @@ import { discoverPrompts } from "./paths.js";
|
|
|
14
14
|
/**
|
|
15
15
|
* Resolve the list of partial directories to search.
|
|
16
16
|
*
|
|
17
|
+
* @private
|
|
17
18
|
* @param customDir - Custom partials directory path.
|
|
18
19
|
* @returns Array of directories to search for partials.
|
|
19
20
|
*/
|
|
@@ -55,8 +56,8 @@ export function runLintPipeline(options: LintPipelineOptions): LintPipelineResul
|
|
|
55
56
|
const results = discovered.map((d) => {
|
|
56
57
|
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: reading discovered prompt file
|
|
57
58
|
const raw = readFileSync(d.filePath, "utf8");
|
|
58
|
-
const frontmatter = parseFrontmatter(raw, d.filePath);
|
|
59
|
-
const template = flattenPartials(clean(raw), partialsDirs);
|
|
59
|
+
const frontmatter = parseFrontmatter({ content: raw, filePath: d.filePath });
|
|
60
|
+
const template = flattenPartials({ template: clean(raw), partialsDirs });
|
|
60
61
|
const templateVars = extractVariables(template);
|
|
61
62
|
return lintPrompt(frontmatter.name, d.filePath, frontmatter.schema, templateVars);
|
|
62
63
|
});
|
|
@@ -98,8 +99,8 @@ export function runGeneratePipeline(options: GeneratePipelineOptions): GenerateP
|
|
|
98
99
|
const processed = discovered.map((d) => {
|
|
99
100
|
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: reading discovered prompt file
|
|
100
101
|
const raw = readFileSync(d.filePath, "utf8");
|
|
101
|
-
const frontmatter = parseFrontmatter(raw, d.filePath);
|
|
102
|
-
const template = flattenPartials(clean(raw), partialsDirs);
|
|
102
|
+
const frontmatter = parseFrontmatter({ content: raw, filePath: d.filePath });
|
|
103
|
+
const template = flattenPartials({ template: clean(raw), partialsDirs });
|
|
103
104
|
const templateVars = extractVariables(template);
|
|
104
105
|
|
|
105
106
|
return {
|