@funkai/cli 0.2.0 → 0.3.1
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 +39 -0
- package/README.md +11 -11
- package/dist/index.mjs +640 -267
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -4
- package/src/commands/generate.ts +23 -1
- package/src/commands/prompts/create.ts +33 -2
- package/src/commands/prompts/generate.ts +97 -12
- package/src/commands/prompts/lint.ts +69 -7
- package/src/commands/prompts/setup.ts +103 -95
- package/src/commands/setup.ts +151 -4
- package/src/commands/validate.ts +20 -2
- package/src/config.ts +28 -0
- package/src/index.ts +4 -0
- package/src/lib/prompts/__tests__/lint.test.ts +36 -24
- package/src/lib/prompts/codegen.ts +112 -43
- package/src/lib/prompts/flatten.ts +10 -5
- package/src/lib/prompts/frontmatter.ts +24 -8
- package/src/lib/prompts/lint.ts +31 -10
- package/src/lib/prompts/paths.ts +71 -18
- package/src/lib/prompts/pipeline.ts +112 -14
- package/tsconfig.json +11 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funkai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CLI for the funkai AI SDK framework",
|
|
6
6
|
"keywords": [
|
|
@@ -31,15 +31,18 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@kidd-cli/core": "^0.10.0",
|
|
33
33
|
"es-toolkit": "^1.45.1",
|
|
34
|
-
"liquidjs": "^10.25.
|
|
34
|
+
"liquidjs": "^10.25.1",
|
|
35
|
+
"picomatch": "^4.0.3",
|
|
35
36
|
"ts-pattern": "^5.9.0",
|
|
36
|
-
"yaml": "^2.8.
|
|
37
|
+
"yaml": "^2.8.3",
|
|
37
38
|
"zod": "^4.3.6",
|
|
38
|
-
"@funkai/
|
|
39
|
+
"@funkai/config": "0.2.0",
|
|
40
|
+
"@funkai/prompts": "0.4.1"
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"@kidd-cli/cli": "^0.4.9",
|
|
42
44
|
"@types/node": "^25.5.0",
|
|
45
|
+
"@types/picomatch": "^4.0.2",
|
|
43
46
|
"@vitest/coverage-v8": "^4.1.0",
|
|
44
47
|
"tsdown": "^0.21.4",
|
|
45
48
|
"typescript": "^5.9.3",
|
package/src/commands/generate.ts
CHANGED
|
@@ -1,19 +1,41 @@
|
|
|
1
1
|
import { command } from "@kidd-cli/core";
|
|
2
2
|
|
|
3
3
|
import { generateArgs, handleGenerate } from "./prompts/generate.js";
|
|
4
|
+
import { getConfig } from "@/config.js";
|
|
4
5
|
|
|
5
6
|
export default command({
|
|
6
7
|
description: "Run all code generation across the funkai SDK",
|
|
7
8
|
options: generateArgs,
|
|
8
9
|
handler(ctx) {
|
|
9
10
|
const { silent } = ctx.args;
|
|
11
|
+
const config = getConfig(ctx);
|
|
10
12
|
|
|
11
13
|
// --- Prompts codegen ---
|
|
12
14
|
if (!silent) {
|
|
13
15
|
ctx.logger.info("Running prompts code generation...");
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
const generateHandleArgs: {
|
|
19
|
+
silent: boolean;
|
|
20
|
+
out?: string;
|
|
21
|
+
includes?: readonly string[];
|
|
22
|
+
partials?: string;
|
|
23
|
+
} = { silent: ctx.args.silent };
|
|
24
|
+
if (ctx.args.out !== undefined) {
|
|
25
|
+
generateHandleArgs.out = ctx.args.out;
|
|
26
|
+
}
|
|
27
|
+
if (ctx.args.includes !== undefined) {
|
|
28
|
+
generateHandleArgs.includes = ctx.args.includes;
|
|
29
|
+
}
|
|
30
|
+
if (ctx.args.partials !== undefined) {
|
|
31
|
+
generateHandleArgs.partials = ctx.args.partials;
|
|
32
|
+
}
|
|
33
|
+
handleGenerate({
|
|
34
|
+
args: generateHandleArgs,
|
|
35
|
+
config: config.prompts,
|
|
36
|
+
logger: ctx.logger,
|
|
37
|
+
fail: ctx.fail,
|
|
38
|
+
});
|
|
17
39
|
|
|
18
40
|
// --- Future: agents codegen ---
|
|
19
41
|
},
|
|
@@ -5,6 +5,8 @@ import { command } from "@kidd-cli/core";
|
|
|
5
5
|
import { match, P } from "ts-pattern";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
|
+
import { getConfig } from "@/config.js";
|
|
9
|
+
|
|
8
10
|
/** @private */
|
|
9
11
|
const createTemplate = (name: string) => `---
|
|
10
12
|
name: ${name}
|
|
@@ -16,15 +18,44 @@ export default command({
|
|
|
16
18
|
description: "Create a new .prompt file",
|
|
17
19
|
options: z.object({
|
|
18
20
|
name: z.string().describe("Prompt name (kebab-case)"),
|
|
19
|
-
out: z
|
|
21
|
+
out: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Output directory (defaults to first root in config or cwd)"),
|
|
20
25
|
partial: z.boolean().default(false).describe("Create as a partial in .prompts/partials/"),
|
|
21
26
|
}),
|
|
22
27
|
handler(ctx) {
|
|
23
28
|
const { name, out, partial } = ctx.args;
|
|
29
|
+
const config = getConfig(ctx);
|
|
30
|
+
const promptsConfig = config.prompts;
|
|
31
|
+
const firstInclude = match(promptsConfig)
|
|
32
|
+
.with({ includes: P.array(P.string).select() }, (includes) => {
|
|
33
|
+
if (includes.length > 0) {
|
|
34
|
+
// Extract the static base directory from the first include pattern
|
|
35
|
+
const [pattern] = includes;
|
|
36
|
+
if (pattern === undefined) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const parts = pattern.split("/");
|
|
40
|
+
const staticParts = parts.filter((p) => !p.includes("*") && !p.includes("?"));
|
|
41
|
+
if (staticParts.length > 0) {
|
|
42
|
+
return staticParts.join("/");
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
})
|
|
48
|
+
.otherwise(() => undefined);
|
|
49
|
+
|
|
24
50
|
const dir = match({ partial, out })
|
|
25
51
|
.with({ partial: true }, () => resolve(".prompts/partials"))
|
|
26
52
|
.with({ out: P.string }, ({ out: outDir }) => resolve(outDir))
|
|
27
|
-
.otherwise(() =>
|
|
53
|
+
.otherwise(() => {
|
|
54
|
+
if (firstInclude) {
|
|
55
|
+
return resolve(firstInclude);
|
|
56
|
+
}
|
|
57
|
+
return process.cwd();
|
|
58
|
+
});
|
|
28
59
|
const filePath = resolve(dir, `${name}.prompt`);
|
|
29
60
|
|
|
30
61
|
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: user-provided CLI argument for prompt file creation
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
|
|
4
|
+
import type { FunkaiConfig, PromptGroup } from "@funkai/config";
|
|
4
5
|
import { command } from "@kidd-cli/core";
|
|
5
6
|
import { match } from "ts-pattern";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
+
import { getConfig } from "@/config.js";
|
|
10
|
+
import { generatePromptModule, generateRegistry, toFileSlug } from "@/lib/prompts/codegen.js";
|
|
9
11
|
import { hasLintErrors } from "@/lib/prompts/lint.js";
|
|
10
12
|
import { runGeneratePipeline } from "@/lib/prompts/pipeline.js";
|
|
11
13
|
|
|
12
14
|
/** Zod schema for the `prompts generate` CLI arguments. */
|
|
13
15
|
export const generateArgs = z.object({
|
|
14
|
-
out: z.string().describe("Output directory for generated files"),
|
|
15
|
-
|
|
16
|
+
out: z.string().optional().describe("Output directory for generated files"),
|
|
17
|
+
includes: z.array(z.string()).optional().describe("Glob patterns to scan for .prompt files"),
|
|
16
18
|
partials: z.string().optional().describe("Custom partials directory"),
|
|
17
19
|
silent: z.boolean().default(false).describe("Suppress output except errors"),
|
|
18
20
|
});
|
|
@@ -25,11 +27,12 @@ export type GenerateArgs = z.infer<typeof generateArgs>;
|
|
|
25
27
|
*/
|
|
26
28
|
export interface HandleGenerateParams {
|
|
27
29
|
readonly args: {
|
|
28
|
-
readonly out
|
|
29
|
-
readonly
|
|
30
|
+
readonly out?: string;
|
|
31
|
+
readonly includes?: readonly string[];
|
|
30
32
|
readonly partials?: string;
|
|
31
33
|
readonly silent: boolean;
|
|
32
34
|
};
|
|
35
|
+
readonly config?: FunkaiConfig["prompts"];
|
|
33
36
|
readonly logger: {
|
|
34
37
|
info: (msg: string) => void;
|
|
35
38
|
step: (msg: string) => void;
|
|
@@ -41,14 +44,74 @@ export interface HandleGenerateParams {
|
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
/**
|
|
44
|
-
*
|
|
47
|
+
* Resolve generate args by merging CLI flags with config defaults.
|
|
45
48
|
*
|
|
46
|
-
* @param
|
|
49
|
+
* @param args - CLI arguments (take precedence).
|
|
50
|
+
* @param config - Prompts config from funkai.config.ts (fallback).
|
|
51
|
+
* @param fail - Error handler for missing required values.
|
|
52
|
+
* @returns Resolved args with required fields guaranteed.
|
|
47
53
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
function resolveGenerateArgs(
|
|
55
|
+
args: HandleGenerateParams["args"],
|
|
56
|
+
config: FunkaiConfig["prompts"],
|
|
57
|
+
fail: (msg: string) => never,
|
|
58
|
+
): {
|
|
59
|
+
readonly out: string;
|
|
60
|
+
readonly includes: readonly string[];
|
|
61
|
+
readonly excludes: readonly string[];
|
|
62
|
+
readonly partials?: string;
|
|
63
|
+
readonly silent: boolean;
|
|
64
|
+
} {
|
|
65
|
+
const out = args.out ?? (config && config.out);
|
|
66
|
+
const includes = args.includes ?? (config && config.includes) ?? ["./**"];
|
|
67
|
+
const excludes = (config && config.excludes) ?? [];
|
|
68
|
+
const partials = args.partials ?? (config && config.partials);
|
|
69
|
+
|
|
70
|
+
if (!out) {
|
|
71
|
+
fail("Missing --out flag. Provide it via CLI or set prompts.out in funkai.config.ts.");
|
|
72
|
+
}
|
|
50
73
|
|
|
51
|
-
const
|
|
74
|
+
const resolved: {
|
|
75
|
+
out: string;
|
|
76
|
+
includes: readonly string[];
|
|
77
|
+
excludes: readonly string[];
|
|
78
|
+
silent: boolean;
|
|
79
|
+
partials?: string;
|
|
80
|
+
} = {
|
|
81
|
+
out,
|
|
82
|
+
includes,
|
|
83
|
+
excludes,
|
|
84
|
+
silent: args.silent,
|
|
85
|
+
};
|
|
86
|
+
if (partials !== undefined) {
|
|
87
|
+
resolved.partials = partials;
|
|
88
|
+
}
|
|
89
|
+
return resolved;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Shared handler for prompts code generation.
|
|
94
|
+
*
|
|
95
|
+
* @param params - Handler context with args, config, logger, and fail callback.
|
|
96
|
+
*/
|
|
97
|
+
export function handleGenerate({ args, config, logger, fail }: HandleGenerateParams): void {
|
|
98
|
+
const { out, includes, excludes, partials, silent } = resolveGenerateArgs(args, config, fail);
|
|
99
|
+
|
|
100
|
+
const configGroups = config && config.groups;
|
|
101
|
+
const pipelineOptions: {
|
|
102
|
+
includes: readonly string[];
|
|
103
|
+
excludes: readonly string[];
|
|
104
|
+
out: string;
|
|
105
|
+
partials?: string;
|
|
106
|
+
groups?: readonly PromptGroup[];
|
|
107
|
+
} = { includes, excludes, out };
|
|
108
|
+
if (partials !== undefined) {
|
|
109
|
+
pipelineOptions.partials = partials;
|
|
110
|
+
}
|
|
111
|
+
if (configGroups !== undefined) {
|
|
112
|
+
pipelineOptions.groups = configGroups;
|
|
113
|
+
}
|
|
114
|
+
const { discovered, lintResults, prompts } = runGeneratePipeline(pipelineOptions);
|
|
52
115
|
|
|
53
116
|
if (!silent) {
|
|
54
117
|
logger.info(`Found ${discovered} prompt(s)`);
|
|
@@ -82,8 +145,9 @@ export function handleGenerate({ args, logger, fail }: HandleGenerateParams): vo
|
|
|
82
145
|
|
|
83
146
|
for (const prompt of prompts) {
|
|
84
147
|
const content = generatePromptModule(prompt);
|
|
148
|
+
const fileSlug = toFileSlug(prompt.name, prompt.group);
|
|
85
149
|
// oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: writing generated module to output directory
|
|
86
|
-
writeFileSync(resolve(outDir, `${
|
|
150
|
+
writeFileSync(resolve(outDir, `${fileSlug}.ts`), content, "utf8");
|
|
87
151
|
}
|
|
88
152
|
|
|
89
153
|
const registryContent = generateRegistry(prompts);
|
|
@@ -107,6 +171,27 @@ export default command({
|
|
|
107
171
|
description: "Generate TypeScript modules from .prompt files",
|
|
108
172
|
options: generateArgs,
|
|
109
173
|
handler(ctx) {
|
|
110
|
-
|
|
174
|
+
const config = getConfig(ctx);
|
|
175
|
+
const generateArgs2: {
|
|
176
|
+
silent: boolean;
|
|
177
|
+
out?: string;
|
|
178
|
+
includes?: readonly string[];
|
|
179
|
+
partials?: string;
|
|
180
|
+
} = { silent: ctx.args.silent };
|
|
181
|
+
if (ctx.args.out !== undefined) {
|
|
182
|
+
generateArgs2.out = ctx.args.out;
|
|
183
|
+
}
|
|
184
|
+
if (ctx.args.includes !== undefined) {
|
|
185
|
+
generateArgs2.includes = ctx.args.includes;
|
|
186
|
+
}
|
|
187
|
+
if (ctx.args.partials !== undefined) {
|
|
188
|
+
generateArgs2.partials = ctx.args.partials;
|
|
189
|
+
}
|
|
190
|
+
handleGenerate({
|
|
191
|
+
args: generateArgs2,
|
|
192
|
+
config: config.prompts,
|
|
193
|
+
logger: ctx.logger,
|
|
194
|
+
fail: ctx.fail,
|
|
195
|
+
});
|
|
111
196
|
},
|
|
112
197
|
});
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import type { FunkaiConfig } from "@funkai/config";
|
|
1
2
|
import { command } from "@kidd-cli/core";
|
|
2
3
|
import { match } from "ts-pattern";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
|
|
6
|
+
import { getConfig } from "@/config.js";
|
|
5
7
|
import { hasLintErrors } from "@/lib/prompts/lint.js";
|
|
6
8
|
import { runLintPipeline } from "@/lib/prompts/pipeline.js";
|
|
7
9
|
|
|
8
10
|
/** Zod schema for the `prompts lint` CLI arguments. */
|
|
9
11
|
export const lintArgs = z.object({
|
|
10
|
-
|
|
12
|
+
includes: z.array(z.string()).optional().describe("Glob patterns to scan for .prompt files"),
|
|
11
13
|
partials: z.string().optional().describe("Custom partials directory"),
|
|
12
14
|
silent: z.boolean().default(false).describe("Suppress output except errors"),
|
|
13
15
|
});
|
|
@@ -20,10 +22,11 @@ export type LintArgs = z.infer<typeof lintArgs>;
|
|
|
20
22
|
*/
|
|
21
23
|
export interface HandleLintParams {
|
|
22
24
|
readonly args: {
|
|
23
|
-
readonly
|
|
25
|
+
readonly includes?: readonly string[];
|
|
24
26
|
readonly partials?: string;
|
|
25
27
|
readonly silent: boolean;
|
|
26
28
|
};
|
|
29
|
+
readonly config?: FunkaiConfig["prompts"];
|
|
27
30
|
readonly logger: {
|
|
28
31
|
info: (msg: string) => void;
|
|
29
32
|
error: (msg: string) => void;
|
|
@@ -32,15 +35,57 @@ export interface HandleLintParams {
|
|
|
32
35
|
readonly fail: (msg: string) => never;
|
|
33
36
|
}
|
|
34
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Resolve lint args by merging CLI flags with config defaults.
|
|
40
|
+
*
|
|
41
|
+
* @param args - CLI arguments (take precedence).
|
|
42
|
+
* @param config - Prompts config from funkai.config.ts (fallback).
|
|
43
|
+
* @param fail - Error handler for missing required values.
|
|
44
|
+
* @returns Resolved args with required fields guaranteed.
|
|
45
|
+
*/
|
|
46
|
+
function resolveLintArgs(
|
|
47
|
+
args: HandleLintParams["args"],
|
|
48
|
+
config: FunkaiConfig["prompts"],
|
|
49
|
+
_fail: (msg: string) => never,
|
|
50
|
+
): {
|
|
51
|
+
readonly includes: readonly string[];
|
|
52
|
+
readonly excludes: readonly string[];
|
|
53
|
+
readonly partials?: string;
|
|
54
|
+
readonly silent: boolean;
|
|
55
|
+
} {
|
|
56
|
+
const includes = args.includes ?? (config && config.includes) ?? ["./**"];
|
|
57
|
+
const excludes = (config && config.excludes) ?? [];
|
|
58
|
+
const partials = args.partials ?? (config && config.partials);
|
|
59
|
+
|
|
60
|
+
const resolved: {
|
|
61
|
+
includes: readonly string[];
|
|
62
|
+
excludes: readonly string[];
|
|
63
|
+
silent: boolean;
|
|
64
|
+
partials?: string;
|
|
65
|
+
} = { includes, excludes, silent: args.silent };
|
|
66
|
+
if (partials !== undefined) {
|
|
67
|
+
resolved.partials = partials;
|
|
68
|
+
}
|
|
69
|
+
return resolved;
|
|
70
|
+
}
|
|
71
|
+
|
|
35
72
|
/**
|
|
36
73
|
* Shared handler for prompts lint/validation.
|
|
37
74
|
*
|
|
38
|
-
* @param params - Handler context with args, logger, and fail callback.
|
|
75
|
+
* @param params - Handler context with args, config, logger, and fail callback.
|
|
39
76
|
*/
|
|
40
|
-
export function handleLint({ args, logger, fail }: HandleLintParams): void {
|
|
41
|
-
const {
|
|
77
|
+
export function handleLint({ args, config, logger, fail }: HandleLintParams): void {
|
|
78
|
+
const { includes, excludes, partials, silent } = resolveLintArgs(args, config, fail);
|
|
42
79
|
|
|
43
|
-
const
|
|
80
|
+
const lintPipelineOptions: {
|
|
81
|
+
includes: readonly string[];
|
|
82
|
+
excludes: readonly string[];
|
|
83
|
+
partials?: string;
|
|
84
|
+
} = { includes, excludes };
|
|
85
|
+
if (partials !== undefined) {
|
|
86
|
+
lintPipelineOptions.partials = partials;
|
|
87
|
+
}
|
|
88
|
+
const { discovered, results } = runLintPipeline(lintPipelineOptions);
|
|
44
89
|
|
|
45
90
|
if (!silent) {
|
|
46
91
|
logger.info(`Linting ${discovered} prompt(s)...`);
|
|
@@ -80,6 +125,23 @@ export default command({
|
|
|
80
125
|
description: "Validate .prompt files for schema/template mismatches",
|
|
81
126
|
options: lintArgs,
|
|
82
127
|
handler(ctx) {
|
|
83
|
-
|
|
128
|
+
const config = getConfig(ctx);
|
|
129
|
+
const lintHandleArgs: {
|
|
130
|
+
silent: boolean;
|
|
131
|
+
includes?: readonly string[];
|
|
132
|
+
partials?: string;
|
|
133
|
+
} = { silent: ctx.args.silent };
|
|
134
|
+
if (ctx.args.includes !== undefined) {
|
|
135
|
+
lintHandleArgs.includes = ctx.args.includes;
|
|
136
|
+
}
|
|
137
|
+
if (ctx.args.partials !== undefined) {
|
|
138
|
+
lintHandleArgs.partials = ctx.args.partials;
|
|
139
|
+
}
|
|
140
|
+
handleLint({
|
|
141
|
+
args: lintHandleArgs,
|
|
142
|
+
config: config.prompts,
|
|
143
|
+
logger: ctx.logger,
|
|
144
|
+
fail: ctx.fail,
|
|
145
|
+
});
|
|
84
146
|
},
|
|
85
147
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
|
|
4
|
+
import type { Context } from "@kidd-cli/core";
|
|
4
5
|
import { command } from "@kidd-cli/core";
|
|
5
6
|
|
|
6
7
|
const VSCODE_DIR = ".vscode";
|
|
@@ -12,114 +13,121 @@ const GITIGNORE_ENTRY = ".prompts/client/";
|
|
|
12
13
|
const PROMPTS_ALIAS = "~prompts";
|
|
13
14
|
const PROMPTS_ALIAS_PATH = "./.prompts/client/index.ts";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
16
|
+
/**
|
|
17
|
+
* Shared prompts setup logic used by both `funkai prompts setup` and `funkai setup`.
|
|
18
|
+
*
|
|
19
|
+
* @param ctx - The CLI context with prompts and logger.
|
|
20
|
+
*/
|
|
21
|
+
export async function setupPrompts(ctx: Pick<Context, "prompts" | "logger">): Promise<void> {
|
|
22
|
+
const shouldConfigure = await ctx.prompts.confirm({
|
|
23
|
+
message: "Configure VSCode to treat .prompt files as Markdown with Liquid syntax?",
|
|
24
|
+
initialValue: true,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (shouldConfigure) {
|
|
28
|
+
const vscodeDir = resolve(VSCODE_DIR);
|
|
29
|
+
mkdirSync(vscodeDir, { recursive: true });
|
|
30
|
+
|
|
31
|
+
const settingsPath = resolve(vscodeDir, SETTINGS_FILE);
|
|
32
|
+
const settings = readJsonFile(settingsPath);
|
|
33
|
+
|
|
34
|
+
const updatedSettings = {
|
|
35
|
+
...settings,
|
|
36
|
+
"files.associations": {
|
|
37
|
+
...((settings["files.associations"] ?? {}) as Record<string, string>),
|
|
38
|
+
"*.prompt": "markdown",
|
|
39
|
+
},
|
|
40
|
+
"liquid.engine": "standard",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
writeFileSync(settingsPath, `${JSON.stringify(updatedSettings, null, 2)}\n`, "utf8");
|
|
44
|
+
ctx.logger.success(`Updated ${settingsPath}`);
|
|
45
|
+
}
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const shouldRecommend = await ctx.prompts.confirm({
|
|
48
|
+
message: "Add Shopify Liquid extension to VSCode recommendations?",
|
|
49
|
+
initialValue: true,
|
|
50
|
+
});
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
if (shouldRecommend) {
|
|
53
|
+
const vscodeDir = resolve(VSCODE_DIR);
|
|
54
|
+
mkdirSync(vscodeDir, { recursive: true });
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const extensionsPath = resolve(vscodeDir, EXTENSIONS_FILE);
|
|
57
|
+
const extensions = readJsonFile(extensionsPath);
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const currentRecs = (extensions["recommendations"] ?? []) as string[];
|
|
60
|
+
const extensionId = "sissel.shopify-liquid";
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
const recommendations = ensureRecommendation(currentRecs, extensionId);
|
|
63
|
+
const updatedExtensions = {
|
|
64
|
+
...extensions,
|
|
65
|
+
recommendations,
|
|
66
|
+
};
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
writeFileSync(extensionsPath, `${JSON.stringify(updatedExtensions, null, 2)}\n`, "utf8");
|
|
69
|
+
ctx.logger.success(`Updated ${extensionsPath}`);
|
|
70
|
+
}
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
72
|
+
const shouldGitignore = await ctx.prompts.confirm({
|
|
73
|
+
message: "Add .prompts/client/ to .gitignore? (generated client should not be committed)",
|
|
74
|
+
initialValue: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (shouldGitignore) {
|
|
78
|
+
const gitignorePath = resolve(GITIGNORE_FILE);
|
|
79
|
+
const existing = readFileOrEmpty(gitignorePath);
|
|
80
|
+
|
|
81
|
+
if (existing.includes(GITIGNORE_ENTRY)) {
|
|
82
|
+
ctx.logger.info(`${GITIGNORE_ENTRY} already in ${gitignorePath}`);
|
|
83
|
+
} else {
|
|
84
|
+
const separator = trailingSeparator(existing);
|
|
85
|
+
const block = `${separator}\n# Generated prompt client (created by \`funkai prompts generate\`)\n${GITIGNORE_ENTRY}\n`;
|
|
86
|
+
writeFileSync(gitignorePath, `${existing}${block}`, "utf8");
|
|
87
|
+
ctx.logger.success(`Added ${GITIGNORE_ENTRY} to ${gitignorePath}`);
|
|
87
88
|
}
|
|
89
|
+
}
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
},
|
|
91
|
+
const shouldTsconfig = await ctx.prompts.confirm({
|
|
92
|
+
message: "Add ~prompts path alias to tsconfig.json?",
|
|
93
|
+
initialValue: true,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (shouldTsconfig) {
|
|
97
|
+
const tsconfigPath = resolve(TSCONFIG_FILE);
|
|
98
|
+
const tsconfig = readJsonFile(tsconfigPath);
|
|
99
|
+
|
|
100
|
+
const compilerOptions = (tsconfig["compilerOptions"] ?? {}) as Record<string, unknown>;
|
|
101
|
+
const existingPaths = (compilerOptions["paths"] ?? {}) as Record<string, string[]>;
|
|
102
|
+
|
|
103
|
+
// oxlint-disable-next-line security/detect-object-injection -- safe: PROMPTS_ALIAS is a known constant string
|
|
104
|
+
if (existingPaths[PROMPTS_ALIAS]) {
|
|
105
|
+
ctx.logger.info(`${PROMPTS_ALIAS} alias already in ${tsconfigPath}`);
|
|
106
|
+
} else {
|
|
107
|
+
const updatedTsconfig = {
|
|
108
|
+
...tsconfig,
|
|
109
|
+
compilerOptions: {
|
|
110
|
+
...compilerOptions,
|
|
111
|
+
paths: {
|
|
112
|
+
...existingPaths,
|
|
113
|
+
// oxlint-disable-next-line security/detect-object-injection -- safe: PROMPTS_ALIAS is a known constant string
|
|
114
|
+
[PROMPTS_ALIAS]: [PROMPTS_ALIAS_PATH],
|
|
114
115
|
},
|
|
115
|
-
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
119
|
+
writeFileSync(tsconfigPath, `${JSON.stringify(updatedTsconfig, null, 2)}\n`, "utf8");
|
|
120
|
+
ctx.logger.success(`Added ${PROMPTS_ALIAS} alias to ${tsconfigPath}`);
|
|
120
121
|
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
121
124
|
|
|
122
|
-
|
|
125
|
+
export default command({
|
|
126
|
+
description: "Configure VSCode IDE settings for .prompt files",
|
|
127
|
+
async handler(ctx) {
|
|
128
|
+
ctx.logger.intro("Prompt SDK — Project Setup");
|
|
129
|
+
await setupPrompts(ctx);
|
|
130
|
+
ctx.logger.outro("Prompts setup complete.");
|
|
123
131
|
},
|
|
124
132
|
});
|
|
125
133
|
|