@funkai/cli 0.2.0 → 0.3.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 +32 -0
- package/dist/index.mjs +538 -222
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
- package/src/commands/generate.ts +8 -1
- package/src/commands/prompts/create.ts +30 -2
- package/src/commands/prompts/generate.ts +58 -11
- package/src/commands/prompts/lint.ts +41 -7
- package/src/commands/prompts/setup.ts +103 -95
- package/src/commands/setup.ts +129 -4
- package/src/commands/validate.ts +8 -1
- package/src/config.ts +28 -0
- package/src/index.ts +4 -0
- package/src/lib/prompts/codegen.ts +113 -43
- package/src/lib/prompts/paths.ts +67 -15
- package/src/lib/prompts/pipeline.ts +82 -7
package/dist/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import * as k$2 from "node:readline";
|
|
|
10
10
|
import ot from "node:readline";
|
|
11
11
|
import { ReadStream } from "node:tty";
|
|
12
12
|
import fs, { existsSync, lstatSync, mkdirSync, promises, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
13
|
-
import path, { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
13
|
+
import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
14
14
|
import { attempt, attemptAsync } from "es-toolkit";
|
|
15
15
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
16
16
|
import { URL as URL$1, fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -18,13 +18,15 @@ import { homedir } from "node:os";
|
|
|
18
18
|
import assert from "node:assert";
|
|
19
19
|
import v8 from "node:v8";
|
|
20
20
|
import { readFileSync as readFileSync$1, readdirSync as readdirSync$1, statSync as statSync$1, writeFile as writeFile$1 } from "fs";
|
|
21
|
-
import { basename as basename$1, dirname as dirname$1, extname as extname$1, join as join$1, normalize, relative, resolve as resolve$1 } from "path";
|
|
21
|
+
import { basename as basename$1, dirname as dirname$1, extname as extname$1, join as join$1, normalize, relative as relative$1, resolve as resolve$1 } from "path";
|
|
22
22
|
import { createHash } from "node:crypto";
|
|
23
23
|
import { parse, stringify } from "yaml";
|
|
24
24
|
import { notStrictEqual, strictEqual } from "assert";
|
|
25
25
|
import { format as format$1, inspect as inspect$1 } from "util";
|
|
26
26
|
import { fileURLToPath as fileURLToPath$1 } from "url";
|
|
27
|
+
import { configSchema } from "@funkai/config";
|
|
27
28
|
import { PARTIALS_DIR, clean } from "@funkai/prompts/cli";
|
|
29
|
+
import picomatch from "picomatch";
|
|
28
30
|
import { Liquid } from "liquidjs";
|
|
29
31
|
|
|
30
32
|
//#region \0rolldown/runtime.js
|
|
@@ -15337,7 +15339,7 @@ var init_esm = __esmMin((() => {
|
|
|
15337
15339
|
basename: basename$1,
|
|
15338
15340
|
dirname: dirname$1,
|
|
15339
15341
|
extname: extname$1,
|
|
15340
|
-
relative,
|
|
15342
|
+
relative: relative$1,
|
|
15341
15343
|
resolve: resolve$1,
|
|
15342
15344
|
join: join$1
|
|
15343
15345
|
},
|
|
@@ -18295,6 +18297,28 @@ var init_yargs = __esmMin((() => {
|
|
|
18295
18297
|
Yargs = YargsFactory(esm_default);
|
|
18296
18298
|
}));
|
|
18297
18299
|
|
|
18300
|
+
//#endregion
|
|
18301
|
+
//#region src/config.ts
|
|
18302
|
+
/**
|
|
18303
|
+
* Extract the typed funkai config from a command context.
|
|
18304
|
+
*
|
|
18305
|
+
* kidd-cli's `Merge<CliConfig, TConfig>` erases augmented keys when
|
|
18306
|
+
* `TConfig` defaults to `Record<string, unknown>`, so we cast here.
|
|
18307
|
+
*
|
|
18308
|
+
* @param ctx - The CLI context.
|
|
18309
|
+
* @returns The typed funkai configuration.
|
|
18310
|
+
*
|
|
18311
|
+
* @example
|
|
18312
|
+
* ```ts
|
|
18313
|
+
* const config = getConfig(ctx);
|
|
18314
|
+
* const promptsConfig = config.prompts;
|
|
18315
|
+
* ```
|
|
18316
|
+
*/
|
|
18317
|
+
function getConfig(ctx) {
|
|
18318
|
+
return ctx.config;
|
|
18319
|
+
}
|
|
18320
|
+
var init_config = __esmMin((() => {}));
|
|
18321
|
+
|
|
18298
18322
|
//#endregion
|
|
18299
18323
|
//#region src/lib/prompts/codegen.ts
|
|
18300
18324
|
/**
|
|
@@ -18336,6 +18360,11 @@ function formatGroupValue(group) {
|
|
|
18336
18360
|
return "undefined";
|
|
18337
18361
|
}
|
|
18338
18362
|
/** @private */
|
|
18363
|
+
function formatGroupJsdoc(group) {
|
|
18364
|
+
if (group) return [" *", ` * @group ${group}`];
|
|
18365
|
+
return [];
|
|
18366
|
+
}
|
|
18367
|
+
/** @private */
|
|
18339
18368
|
function parseGroupSegments(group) {
|
|
18340
18369
|
if (group) return group.split("/").map(toCamelCase);
|
|
18341
18370
|
return [];
|
|
@@ -18353,22 +18382,70 @@ function generateSchemaExpression(vars) {
|
|
|
18353
18382
|
return ` ${v.name}: ${expr},`;
|
|
18354
18383
|
}).join("\n")}\n})`;
|
|
18355
18384
|
}
|
|
18385
|
+
/** @private */
|
|
18386
|
+
function formatHeader(sourcePath) {
|
|
18387
|
+
let sourceLine = "";
|
|
18388
|
+
if (sourcePath) sourceLine = `// Source: ${sourcePath}\n`;
|
|
18389
|
+
return [
|
|
18390
|
+
"// ─── AUTO-GENERATED ────────────────────────────────────────",
|
|
18391
|
+
`${sourceLine}// Regenerate: funkai prompts generate`,
|
|
18392
|
+
"// ───────────────────────────────────────────────────────────"
|
|
18393
|
+
].join("\n");
|
|
18394
|
+
}
|
|
18395
|
+
/**
|
|
18396
|
+
* Derive a unique file slug from group + name.
|
|
18397
|
+
*
|
|
18398
|
+
* Ungrouped prompts use the name alone. Grouped prompts
|
|
18399
|
+
* join group segments and name with hyphens.
|
|
18400
|
+
*
|
|
18401
|
+
* @param name - The prompt name (kebab-case).
|
|
18402
|
+
* @param group - Optional group path (e.g., 'core/agent').
|
|
18403
|
+
* @returns The file slug string.
|
|
18404
|
+
*
|
|
18405
|
+
* @example
|
|
18406
|
+
* ```ts
|
|
18407
|
+
* toFileSlug('system', 'core/agent') // => 'core-agent-system'
|
|
18408
|
+
* toFileSlug('greeting', undefined) // => 'greeting'
|
|
18409
|
+
* ```
|
|
18410
|
+
*/
|
|
18411
|
+
function toFileSlug(name, group) {
|
|
18412
|
+
if (group) return `${group.replaceAll("/", "-")}-${name}`;
|
|
18413
|
+
return name;
|
|
18414
|
+
}
|
|
18415
|
+
/**
|
|
18416
|
+
* Derive a unique import name (camelCase) from group + name.
|
|
18417
|
+
*
|
|
18418
|
+
* @param name - The prompt name (kebab-case).
|
|
18419
|
+
* @param group - Optional group path (e.g., 'core/agent').
|
|
18420
|
+
* @returns The camelCase import identifier.
|
|
18421
|
+
*
|
|
18422
|
+
* @example
|
|
18423
|
+
* ```ts
|
|
18424
|
+
* toImportName('system', 'core/agent') // => 'coreAgentSystem'
|
|
18425
|
+
* toImportName('greeting', undefined) // => 'greeting'
|
|
18426
|
+
* ```
|
|
18427
|
+
*/
|
|
18428
|
+
function toImportName(name, group) {
|
|
18429
|
+
return toCamelCase(toFileSlug(name, group));
|
|
18430
|
+
}
|
|
18356
18431
|
/**
|
|
18357
18432
|
* Generate a per-prompt TypeScript module with a default export.
|
|
18358
18433
|
*
|
|
18359
|
-
* The module
|
|
18360
|
-
*
|
|
18434
|
+
* The module uses `createPrompt` from `@funkai/prompts` to
|
|
18435
|
+
* encapsulate the Zod schema, inlined template, and render logic.
|
|
18436
|
+
*
|
|
18437
|
+
* @param prompt - The parsed prompt configuration.
|
|
18438
|
+
* @returns The generated TypeScript module source code.
|
|
18361
18439
|
*/
|
|
18362
18440
|
function generatePromptModule(prompt) {
|
|
18363
18441
|
const escaped = escapeTemplateLiteral(prompt.template);
|
|
18364
18442
|
const schemaExpr = generateSchemaExpression(prompt.schema);
|
|
18365
18443
|
const groupValue = formatGroupValue(prompt.group);
|
|
18366
18444
|
return [
|
|
18367
|
-
|
|
18368
|
-
`// Source: ${prompt.sourcePath}`,
|
|
18445
|
+
formatHeader(prompt.sourcePath),
|
|
18369
18446
|
"",
|
|
18370
18447
|
"import { z } from 'zod'",
|
|
18371
|
-
"import {
|
|
18448
|
+
"import { createPrompt } from '@funkai/prompts'",
|
|
18372
18449
|
"",
|
|
18373
18450
|
`const schema = ${schemaExpr}`,
|
|
18374
18451
|
"",
|
|
@@ -18376,32 +18453,25 @@ function generatePromptModule(prompt) {
|
|
|
18376
18453
|
"",
|
|
18377
18454
|
`const template = \`${escaped}\``,
|
|
18378
18455
|
"",
|
|
18379
|
-
"
|
|
18380
|
-
`
|
|
18456
|
+
"/**",
|
|
18457
|
+
` * **${prompt.name}** prompt module.`,
|
|
18458
|
+
...formatGroupJsdoc(prompt.group),
|
|
18459
|
+
" */",
|
|
18460
|
+
"export default createPrompt<Variables>({",
|
|
18461
|
+
` name: '${prompt.name}',`,
|
|
18381
18462
|
` group: ${groupValue},`,
|
|
18463
|
+
" template,",
|
|
18382
18464
|
" schema,",
|
|
18383
|
-
|
|
18384
|
-
" render(variables?: undefined): string {",
|
|
18385
|
-
" return liquidEngine.parseAndRenderSync(template, {})",
|
|
18386
|
-
" },",
|
|
18387
|
-
" validate(variables?: undefined): Variables {",
|
|
18388
|
-
" return schema.parse(variables ?? {})",
|
|
18389
|
-
" },"
|
|
18390
|
-
]).otherwise(() => [
|
|
18391
|
-
" render(variables: Variables): string {",
|
|
18392
|
-
" return liquidEngine.parseAndRenderSync(template, schema.parse(variables))",
|
|
18393
|
-
" },",
|
|
18394
|
-
" validate(variables: unknown): Variables {",
|
|
18395
|
-
" return schema.parse(variables)",
|
|
18396
|
-
" },"
|
|
18397
|
-
]),
|
|
18398
|
-
"}",
|
|
18465
|
+
"})",
|
|
18399
18466
|
""
|
|
18400
18467
|
].join("\n");
|
|
18401
18468
|
}
|
|
18402
18469
|
/**
|
|
18403
18470
|
* Build a nested tree from sorted prompts, grouped by their `group` field.
|
|
18404
18471
|
*
|
|
18472
|
+
* Leaf values are the unique import name derived from group+name,
|
|
18473
|
+
* so prompts with the same name in different groups do not collide.
|
|
18474
|
+
*
|
|
18405
18475
|
* @param prompts - Sorted parsed prompts.
|
|
18406
18476
|
* @returns A tree where leaves are import names and branches are group namespaces.
|
|
18407
18477
|
* @throws If a prompt name collides with a group namespace at the same level.
|
|
@@ -18410,15 +18480,16 @@ function generatePromptModule(prompt) {
|
|
|
18410
18480
|
*/
|
|
18411
18481
|
function buildTree(prompts) {
|
|
18412
18482
|
return prompts.reduce((root, prompt) => {
|
|
18413
|
-
const
|
|
18483
|
+
const leafKey = toCamelCase(prompt.name);
|
|
18484
|
+
const importName = toImportName(prompt.name, prompt.group);
|
|
18414
18485
|
const target = parseGroupSegments(prompt.group).reduce((current, segment) => {
|
|
18415
18486
|
const existing = current[segment];
|
|
18416
18487
|
if (typeof existing === "string") throw new TypeError(`Collision: prompt "${existing}" and group namespace "${segment}" share the same key at the same level.`);
|
|
18417
18488
|
if (existing === null || existing === void 0) current[segment] = {};
|
|
18418
18489
|
return current[segment];
|
|
18419
18490
|
}, root);
|
|
18420
|
-
if (typeof target[
|
|
18421
|
-
target[
|
|
18491
|
+
if (typeof target[leafKey] === "object" && target[leafKey] !== null) throw new Error(`Collision: prompt "${leafKey}" conflicts with existing group namespace "${leafKey}" at the same level.`);
|
|
18492
|
+
target[leafKey] = importName;
|
|
18422
18493
|
return root;
|
|
18423
18494
|
}, {});
|
|
18424
18495
|
}
|
|
@@ -18433,7 +18504,10 @@ function buildTree(prompts) {
|
|
|
18433
18504
|
*/
|
|
18434
18505
|
function serializeTree(node, indent) {
|
|
18435
18506
|
const pad = " ".repeat(indent);
|
|
18436
|
-
return Object.entries(node).flatMap(([key, value]) => match(typeof value).with("string", () =>
|
|
18507
|
+
return Object.entries(node).flatMap(([key, value]) => match(typeof value).with("string", () => {
|
|
18508
|
+
if (key === value) return [`${pad}${key},`];
|
|
18509
|
+
return [`${pad}${key}: ${value},`];
|
|
18510
|
+
}).otherwise(() => [
|
|
18437
18511
|
`${pad}${key}: {`,
|
|
18438
18512
|
...serializeTree(value, indent + 1),
|
|
18439
18513
|
`${pad}},`
|
|
@@ -18445,34 +18519,41 @@ function serializeTree(node, indent) {
|
|
|
18445
18519
|
*
|
|
18446
18520
|
* Prompts are organized into a nested object structure based on their
|
|
18447
18521
|
* `group` field, with each `/`-separated segment becoming a nesting level.
|
|
18522
|
+
*
|
|
18523
|
+
* @param prompts - Sorted parsed prompts to include in the registry.
|
|
18524
|
+
* @returns The generated TypeScript source for the registry index module.
|
|
18525
|
+
*
|
|
18526
|
+
* @example
|
|
18527
|
+
* ```ts
|
|
18528
|
+
* const source = generateRegistry([
|
|
18529
|
+
* { name: 'system', group: 'core/agent', schema: [], template: '...', sourcePath: 'prompts/system.prompt' },
|
|
18530
|
+
* ])
|
|
18531
|
+
* writeFileSync('index.ts', source)
|
|
18532
|
+
* ```
|
|
18448
18533
|
*/
|
|
18449
18534
|
function generateRegistry(prompts) {
|
|
18450
|
-
const sorted = [...prompts].toSorted((a, b) =>
|
|
18535
|
+
const sorted = [...prompts].toSorted((a, b) => {
|
|
18536
|
+
const slugA = toFileSlug(a.name, a.group);
|
|
18537
|
+
const slugB = toFileSlug(b.name, b.group);
|
|
18538
|
+
return slugA.localeCompare(slugB);
|
|
18539
|
+
});
|
|
18540
|
+
const imports = sorted.map((p) => {
|
|
18541
|
+
return `import ${toImportName(p.name, p.group)} from './${toFileSlug(p.name, p.group)}.js'`;
|
|
18542
|
+
}).join("\n");
|
|
18543
|
+
const treeLines = serializeTree(buildTree(sorted), 1);
|
|
18451
18544
|
return [
|
|
18452
|
-
|
|
18545
|
+
formatHeader(),
|
|
18453
18546
|
"",
|
|
18454
18547
|
"import { createPromptRegistry } from '@funkai/prompts'",
|
|
18455
|
-
|
|
18548
|
+
imports,
|
|
18456
18549
|
"",
|
|
18457
18550
|
"export const prompts = createPromptRegistry({",
|
|
18458
|
-
...
|
|
18551
|
+
...treeLines,
|
|
18459
18552
|
"})",
|
|
18460
18553
|
""
|
|
18461
18554
|
].join("\n");
|
|
18462
18555
|
}
|
|
18463
|
-
var
|
|
18464
|
-
var init_codegen = __esmMin((() => {
|
|
18465
|
-
HEADER = [
|
|
18466
|
-
"/*",
|
|
18467
|
-
"|==========================================================================",
|
|
18468
|
-
"| AUTO-GENERATED — DO NOT EDIT",
|
|
18469
|
-
"|==========================================================================",
|
|
18470
|
-
"|",
|
|
18471
|
-
"| Run `funkai prompts generate` to regenerate.",
|
|
18472
|
-
"|",
|
|
18473
|
-
"*/"
|
|
18474
|
-
].join("\n");
|
|
18475
|
-
}));
|
|
18556
|
+
var init_codegen = __esmMin((() => {}));
|
|
18476
18557
|
|
|
18477
18558
|
//#endregion
|
|
18478
18559
|
//#region src/lib/prompts/lint.ts
|
|
@@ -18791,6 +18872,31 @@ function deriveNameFromPath(filePath) {
|
|
|
18791
18872
|
return stem;
|
|
18792
18873
|
}
|
|
18793
18874
|
/**
|
|
18875
|
+
* Extract the static base directory from a glob pattern.
|
|
18876
|
+
*
|
|
18877
|
+
* Returns the longest directory prefix before any glob characters
|
|
18878
|
+
* (`*`, `?`, `{`, `[`). Falls back to `'.'` if the pattern starts
|
|
18879
|
+
* with a glob character.
|
|
18880
|
+
*
|
|
18881
|
+
* @private
|
|
18882
|
+
*/
|
|
18883
|
+
function extractBaseDir(pattern) {
|
|
18884
|
+
const globChars = new Set([
|
|
18885
|
+
"*",
|
|
18886
|
+
"?",
|
|
18887
|
+
"{",
|
|
18888
|
+
"["
|
|
18889
|
+
]);
|
|
18890
|
+
const parts = pattern.split("/");
|
|
18891
|
+
const staticParts = [];
|
|
18892
|
+
for (const part of parts) {
|
|
18893
|
+
if ([...part].some((ch) => globChars.has(ch))) break;
|
|
18894
|
+
staticParts.push(part);
|
|
18895
|
+
}
|
|
18896
|
+
if (staticParts.length === 0) return ".";
|
|
18897
|
+
return staticParts.join("/");
|
|
18898
|
+
}
|
|
18899
|
+
/**
|
|
18794
18900
|
* Recursively scan a directory for `.prompt` files.
|
|
18795
18901
|
*
|
|
18796
18902
|
* @private
|
|
@@ -18815,21 +18921,29 @@ function scanDirectory(dir, depth) {
|
|
|
18815
18921
|
});
|
|
18816
18922
|
}
|
|
18817
18923
|
/**
|
|
18818
|
-
* Discover all `.prompt` files
|
|
18924
|
+
* Discover all `.prompt` files matching the given include/exclude patterns.
|
|
18925
|
+
*
|
|
18926
|
+
* Extracts base directories from the include patterns, scans them
|
|
18927
|
+
* recursively, then filters results through picomatch.
|
|
18928
|
+
*
|
|
18929
|
+
* Name uniqueness is **not** enforced here — prompts with the same name
|
|
18930
|
+
* are allowed as long as they belong to different groups. Uniqueness
|
|
18931
|
+
* is validated downstream in the pipeline after frontmatter parsing,
|
|
18932
|
+
* where group information is available.
|
|
18819
18933
|
*
|
|
18820
|
-
* @param
|
|
18821
|
-
* @returns Sorted
|
|
18822
|
-
* @throws If duplicate prompt names are found across roots.
|
|
18934
|
+
* @param options - Include and exclude glob patterns.
|
|
18935
|
+
* @returns Sorted list of discovered prompts.
|
|
18823
18936
|
*/
|
|
18824
|
-
function discoverPrompts(
|
|
18825
|
-
const
|
|
18826
|
-
const
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
|
|
18831
|
-
|
|
18832
|
-
|
|
18937
|
+
function discoverPrompts(options) {
|
|
18938
|
+
const { includes, excludes = [] } = options;
|
|
18939
|
+
const all = [...new Set(includes.map((pattern) => resolve(extractBaseDir(pattern))))].flatMap((dir) => scanDirectory(dir, 0));
|
|
18940
|
+
const isIncluded = picomatch(includes);
|
|
18941
|
+
const isExcluded = picomatch(excludes);
|
|
18942
|
+
const filtered = all.filter((prompt) => {
|
|
18943
|
+
const matchPath = relative(process.cwd(), prompt.filePath).replaceAll("\\", "/");
|
|
18944
|
+
return isIncluded(matchPath) && !isExcluded(matchPath);
|
|
18945
|
+
});
|
|
18946
|
+
return [...new Map(filtered.map((prompt) => [prompt.filePath, prompt])).values()].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
18833
18947
|
}
|
|
18834
18948
|
var MAX_DEPTH, PROMPT_EXT;
|
|
18835
18949
|
var init_paths = __esmMin((() => {
|
|
@@ -18841,6 +18955,42 @@ var init_paths = __esmMin((() => {
|
|
|
18841
18955
|
//#endregion
|
|
18842
18956
|
//#region src/lib/prompts/pipeline.ts
|
|
18843
18957
|
/**
|
|
18958
|
+
* Validate that no two prompts share the same group+name combination.
|
|
18959
|
+
*
|
|
18960
|
+
* @param prompts - Parsed prompts with group and name fields.
|
|
18961
|
+
* @throws If duplicate group+name combinations are found.
|
|
18962
|
+
*
|
|
18963
|
+
* @private
|
|
18964
|
+
*/
|
|
18965
|
+
function validateUniqueness(prompts) {
|
|
18966
|
+
const duplicate = [...Map.groupBy(prompts, (p) => toFileSlug(p.name, p.group)).entries()].find(([, entries]) => entries.length > 1);
|
|
18967
|
+
if (duplicate) {
|
|
18968
|
+
const [slug, entries] = duplicate;
|
|
18969
|
+
const paths = entries.map((p) => p.sourcePath).join("\n ");
|
|
18970
|
+
throw new Error(`Duplicate prompt "${slug}" (group+name) found in:\n ${paths}`);
|
|
18971
|
+
}
|
|
18972
|
+
}
|
|
18973
|
+
/**
|
|
18974
|
+
* Resolve a prompt's group from config-defined group patterns.
|
|
18975
|
+
*
|
|
18976
|
+
* Matches the prompt's file path against each group's `includes`/`excludes`
|
|
18977
|
+
* patterns. First matching group wins.
|
|
18978
|
+
*
|
|
18979
|
+
* @param filePath - Absolute path to the prompt file.
|
|
18980
|
+
* @param groups - Config-defined group definitions.
|
|
18981
|
+
* @returns The matching group name, or undefined if no match.
|
|
18982
|
+
*
|
|
18983
|
+
* @private
|
|
18984
|
+
*/
|
|
18985
|
+
function resolveGroupFromConfig(filePath, groups) {
|
|
18986
|
+
const matchPath = relative(process.cwd(), filePath).replaceAll("\\", "/");
|
|
18987
|
+
for (const group of groups) {
|
|
18988
|
+
const isIncluded = picomatch(group.includes);
|
|
18989
|
+
const isExcluded = picomatch(group.excludes ?? []);
|
|
18990
|
+
if (isIncluded(matchPath) && !isExcluded(matchPath)) return group.name;
|
|
18991
|
+
}
|
|
18992
|
+
}
|
|
18993
|
+
/**
|
|
18844
18994
|
* Resolve the list of partial directories to search.
|
|
18845
18995
|
*
|
|
18846
18996
|
* @private
|
|
@@ -18858,7 +19008,12 @@ function resolvePartialsDirs(customDir) {
|
|
|
18858
19008
|
* @returns Lint results for all discovered prompts.
|
|
18859
19009
|
*/
|
|
18860
19010
|
function runLintPipeline(options) {
|
|
18861
|
-
|
|
19011
|
+
let excludes;
|
|
19012
|
+
if (options.excludes) excludes = [...options.excludes];
|
|
19013
|
+
const discovered = discoverPrompts({
|
|
19014
|
+
includes: [...options.includes],
|
|
19015
|
+
excludes
|
|
19016
|
+
});
|
|
18862
19017
|
const partialsDirs = resolvePartialsDirs(resolve(options.partials ?? ".prompts/partials"));
|
|
18863
19018
|
const results = discovered.map((d) => {
|
|
18864
19019
|
const raw = readFileSync(d.filePath, "utf8");
|
|
@@ -18886,8 +19041,14 @@ function runLintPipeline(options) {
|
|
|
18886
19041
|
* @returns Parsed prompts ready for code generation, along with lint results.
|
|
18887
19042
|
*/
|
|
18888
19043
|
function runGeneratePipeline(options) {
|
|
18889
|
-
|
|
19044
|
+
let excludes;
|
|
19045
|
+
if (options.excludes) excludes = [...options.excludes];
|
|
19046
|
+
const discovered = discoverPrompts({
|
|
19047
|
+
includes: [...options.includes],
|
|
19048
|
+
excludes
|
|
19049
|
+
});
|
|
18890
19050
|
const partialsDirs = resolvePartialsDirs(resolve(options.partials ?? resolve(options.out, "../partials")));
|
|
19051
|
+
const configGroups = options.groups ?? [];
|
|
18891
19052
|
const processed = discovered.map((d) => {
|
|
18892
19053
|
const raw = readFileSync(d.filePath, "utf8");
|
|
18893
19054
|
const frontmatter = parseFrontmatter({
|
|
@@ -18899,24 +19060,28 @@ function runGeneratePipeline(options) {
|
|
|
18899
19060
|
partialsDirs
|
|
18900
19061
|
});
|
|
18901
19062
|
const templateVars = extractVariables(template);
|
|
19063
|
+
const group = frontmatter.group ?? resolveGroupFromConfig(d.filePath, configGroups);
|
|
18902
19064
|
return {
|
|
18903
19065
|
lintResult: lintPrompt(frontmatter.name, d.filePath, frontmatter.schema, templateVars),
|
|
18904
19066
|
prompt: {
|
|
18905
19067
|
name: frontmatter.name,
|
|
18906
|
-
group
|
|
19068
|
+
group,
|
|
18907
19069
|
schema: frontmatter.schema,
|
|
18908
19070
|
template,
|
|
18909
19071
|
sourcePath: d.filePath
|
|
18910
19072
|
}
|
|
18911
19073
|
};
|
|
18912
19074
|
});
|
|
19075
|
+
const prompts = processed.map((p) => p.prompt);
|
|
19076
|
+
validateUniqueness(prompts);
|
|
18913
19077
|
return {
|
|
18914
19078
|
discovered: discovered.length,
|
|
18915
19079
|
lintResults: processed.map((p) => p.lintResult),
|
|
18916
|
-
prompts
|
|
19080
|
+
prompts
|
|
18917
19081
|
};
|
|
18918
19082
|
}
|
|
18919
19083
|
var init_pipeline = __esmMin((() => {
|
|
19084
|
+
init_codegen();
|
|
18920
19085
|
init_extract_variables();
|
|
18921
19086
|
init_flatten();
|
|
18922
19087
|
init_frontmatter();
|
|
@@ -18927,16 +19092,40 @@ var init_pipeline = __esmMin((() => {
|
|
|
18927
19092
|
//#endregion
|
|
18928
19093
|
//#region src/commands/prompts/generate.ts
|
|
18929
19094
|
/**
|
|
19095
|
+
* Resolve generate args by merging CLI flags with config defaults.
|
|
19096
|
+
*
|
|
19097
|
+
* @param args - CLI arguments (take precedence).
|
|
19098
|
+
* @param config - Prompts config from funkai.config.ts (fallback).
|
|
19099
|
+
* @param fail - Error handler for missing required values.
|
|
19100
|
+
* @returns Resolved args with required fields guaranteed.
|
|
19101
|
+
*/
|
|
19102
|
+
function resolveGenerateArgs(args, config, fail) {
|
|
19103
|
+
const out = args.out ?? (config && config.out);
|
|
19104
|
+
const includes = args.includes ?? (config && config.includes) ?? ["./**"];
|
|
19105
|
+
const excludes = (config && config.excludes) ?? [];
|
|
19106
|
+
const partials = args.partials ?? (config && config.partials);
|
|
19107
|
+
if (!out) fail("Missing --out flag. Provide it via CLI or set prompts.out in funkai.config.ts.");
|
|
19108
|
+
return {
|
|
19109
|
+
out,
|
|
19110
|
+
includes,
|
|
19111
|
+
excludes,
|
|
19112
|
+
partials,
|
|
19113
|
+
silent: args.silent
|
|
19114
|
+
};
|
|
19115
|
+
}
|
|
19116
|
+
/**
|
|
18930
19117
|
* Shared handler for prompts code generation.
|
|
18931
19118
|
*
|
|
18932
|
-
* @param params - Handler context with args, logger, and fail callback.
|
|
19119
|
+
* @param params - Handler context with args, config, logger, and fail callback.
|
|
18933
19120
|
*/
|
|
18934
|
-
function handleGenerate({ args, logger, fail }) {
|
|
18935
|
-
const { out,
|
|
19121
|
+
function handleGenerate({ args, config, logger, fail }) {
|
|
19122
|
+
const { out, includes, excludes, partials, silent } = resolveGenerateArgs(args, config, fail);
|
|
18936
19123
|
const { discovered, lintResults, prompts } = runGeneratePipeline({
|
|
18937
|
-
|
|
19124
|
+
includes,
|
|
19125
|
+
excludes,
|
|
18938
19126
|
out,
|
|
18939
|
-
partials
|
|
19127
|
+
partials,
|
|
19128
|
+
groups: config && config.groups
|
|
18940
19129
|
});
|
|
18941
19130
|
if (!silent) logger.info(`Found ${discovered} prompt(s)`);
|
|
18942
19131
|
if (!silent) for (const prompt of prompts) {
|
|
@@ -18951,7 +19140,7 @@ function handleGenerate({ args, logger, fail }) {
|
|
|
18951
19140
|
mkdirSync(outDir, { recursive: true });
|
|
18952
19141
|
for (const prompt of prompts) {
|
|
18953
19142
|
const content = generatePromptModule(prompt);
|
|
18954
|
-
writeFileSync(resolve(outDir, `${prompt.name}.ts`), content, "utf8");
|
|
19143
|
+
writeFileSync(resolve(outDir, `${toFileSlug(prompt.name, prompt.group)}.ts`), content, "utf8");
|
|
18955
19144
|
}
|
|
18956
19145
|
const registryContent = generateRegistry(prompts);
|
|
18957
19146
|
writeFileSync(resolve(outDir, "index.ts"), registryContent, "utf8");
|
|
@@ -18965,12 +19154,13 @@ function formatVarList(schema) {
|
|
|
18965
19154
|
var generateArgs, generate_default$1;
|
|
18966
19155
|
var init_generate$1 = __esmMin((() => {
|
|
18967
19156
|
init_dist();
|
|
19157
|
+
init_config();
|
|
18968
19158
|
init_codegen();
|
|
18969
19159
|
init_lint$1();
|
|
18970
19160
|
init_pipeline();
|
|
18971
19161
|
generateArgs = z.object({
|
|
18972
|
-
out: z.string().describe("Output directory for generated files"),
|
|
18973
|
-
|
|
19162
|
+
out: z.string().optional().describe("Output directory for generated files"),
|
|
19163
|
+
includes: z.array(z.string()).optional().describe("Glob patterns to scan for .prompt files"),
|
|
18974
19164
|
partials: z.string().optional().describe("Custom partials directory"),
|
|
18975
19165
|
silent: z.boolean().default(false).describe("Suppress output except errors")
|
|
18976
19166
|
});
|
|
@@ -18978,8 +19168,10 @@ var init_generate$1 = __esmMin((() => {
|
|
|
18978
19168
|
description: "Generate TypeScript modules from .prompt files",
|
|
18979
19169
|
options: generateArgs,
|
|
18980
19170
|
handler(ctx) {
|
|
19171
|
+
const config = getConfig(ctx);
|
|
18981
19172
|
handleGenerate({
|
|
18982
19173
|
args: ctx.args,
|
|
19174
|
+
config: config.prompts,
|
|
18983
19175
|
logger: ctx.logger,
|
|
18984
19176
|
fail: ctx.fail
|
|
18985
19177
|
});
|
|
@@ -18993,14 +19185,17 @@ var generate_default;
|
|
|
18993
19185
|
var init_generate = __esmMin((() => {
|
|
18994
19186
|
init_dist();
|
|
18995
19187
|
init_generate$1();
|
|
19188
|
+
init_config();
|
|
18996
19189
|
generate_default = command({
|
|
18997
19190
|
description: "Run all code generation across the funkai SDK",
|
|
18998
19191
|
options: generateArgs,
|
|
18999
19192
|
handler(ctx) {
|
|
19000
19193
|
const { silent } = ctx.args;
|
|
19194
|
+
const config = getConfig(ctx);
|
|
19001
19195
|
if (!silent) ctx.logger.info("Running prompts code generation...");
|
|
19002
19196
|
handleGenerate({
|
|
19003
19197
|
args: ctx.args,
|
|
19198
|
+
config: config.prompts,
|
|
19004
19199
|
logger: ctx.logger,
|
|
19005
19200
|
fail: ctx.fail
|
|
19006
19201
|
});
|
|
@@ -19009,18 +19204,230 @@ var init_generate = __esmMin((() => {
|
|
|
19009
19204
|
}));
|
|
19010
19205
|
|
|
19011
19206
|
//#endregion
|
|
19012
|
-
//#region src/commands/setup.ts
|
|
19013
|
-
|
|
19207
|
+
//#region src/commands/prompts/setup.ts
|
|
19208
|
+
/**
|
|
19209
|
+
* Shared prompts setup logic used by both `funkai prompts setup` and `funkai setup`.
|
|
19210
|
+
*
|
|
19211
|
+
* @param ctx - The CLI context with prompts and logger.
|
|
19212
|
+
*/
|
|
19213
|
+
async function setupPrompts(ctx) {
|
|
19214
|
+
if (await ctx.prompts.confirm({
|
|
19215
|
+
message: "Configure VSCode to treat .prompt files as Markdown with Liquid syntax?",
|
|
19216
|
+
initialValue: true
|
|
19217
|
+
})) {
|
|
19218
|
+
const vscodeDir = resolve(VSCODE_DIR);
|
|
19219
|
+
mkdirSync(vscodeDir, { recursive: true });
|
|
19220
|
+
const settingsPath = resolve(vscodeDir, SETTINGS_FILE);
|
|
19221
|
+
const settings = readJsonFile(settingsPath);
|
|
19222
|
+
const updatedSettings = {
|
|
19223
|
+
...settings,
|
|
19224
|
+
"files.associations": {
|
|
19225
|
+
...settings["files.associations"] ?? {},
|
|
19226
|
+
"*.prompt": "markdown"
|
|
19227
|
+
},
|
|
19228
|
+
"liquid.engine": "standard"
|
|
19229
|
+
};
|
|
19230
|
+
writeFileSync(settingsPath, `${JSON.stringify(updatedSettings, null, 2)}\n`, "utf8");
|
|
19231
|
+
ctx.logger.success(`Updated ${settingsPath}`);
|
|
19232
|
+
}
|
|
19233
|
+
if (await ctx.prompts.confirm({
|
|
19234
|
+
message: "Add Shopify Liquid extension to VSCode recommendations?",
|
|
19235
|
+
initialValue: true
|
|
19236
|
+
})) {
|
|
19237
|
+
const vscodeDir = resolve(VSCODE_DIR);
|
|
19238
|
+
mkdirSync(vscodeDir, { recursive: true });
|
|
19239
|
+
const extensionsPath = resolve(vscodeDir, EXTENSIONS_FILE);
|
|
19240
|
+
const extensions = readJsonFile(extensionsPath);
|
|
19241
|
+
const recommendations = ensureRecommendation(extensions.recommendations ?? [], "sissel.shopify-liquid");
|
|
19242
|
+
const updatedExtensions = {
|
|
19243
|
+
...extensions,
|
|
19244
|
+
recommendations
|
|
19245
|
+
};
|
|
19246
|
+
writeFileSync(extensionsPath, `${JSON.stringify(updatedExtensions, null, 2)}\n`, "utf8");
|
|
19247
|
+
ctx.logger.success(`Updated ${extensionsPath}`);
|
|
19248
|
+
}
|
|
19249
|
+
if (await ctx.prompts.confirm({
|
|
19250
|
+
message: "Add .prompts/client/ to .gitignore? (generated client should not be committed)",
|
|
19251
|
+
initialValue: true
|
|
19252
|
+
})) {
|
|
19253
|
+
const gitignorePath = resolve(GITIGNORE_FILE);
|
|
19254
|
+
const existing = readFileOrEmpty(gitignorePath);
|
|
19255
|
+
if (existing.includes(GITIGNORE_ENTRY)) ctx.logger.info(`${GITIGNORE_ENTRY} already in ${gitignorePath}`);
|
|
19256
|
+
else {
|
|
19257
|
+
writeFileSync(gitignorePath, `${existing}${`${trailingSeparator(existing)}\n# Generated prompt client (created by \`funkai prompts generate\`)\n${GITIGNORE_ENTRY}\n`}`, "utf8");
|
|
19258
|
+
ctx.logger.success(`Added ${GITIGNORE_ENTRY} to ${gitignorePath}`);
|
|
19259
|
+
}
|
|
19260
|
+
}
|
|
19261
|
+
if (await ctx.prompts.confirm({
|
|
19262
|
+
message: "Add ~prompts path alias to tsconfig.json?",
|
|
19263
|
+
initialValue: true
|
|
19264
|
+
})) {
|
|
19265
|
+
const tsconfigPath = resolve(TSCONFIG_FILE);
|
|
19266
|
+
const tsconfig = readJsonFile(tsconfigPath);
|
|
19267
|
+
const compilerOptions = tsconfig.compilerOptions ?? {};
|
|
19268
|
+
const existingPaths = compilerOptions.paths ?? {};
|
|
19269
|
+
if (existingPaths[PROMPTS_ALIAS]) ctx.logger.info(`${PROMPTS_ALIAS} alias already in ${tsconfigPath}`);
|
|
19270
|
+
else {
|
|
19271
|
+
const updatedTsconfig = {
|
|
19272
|
+
...tsconfig,
|
|
19273
|
+
compilerOptions: {
|
|
19274
|
+
...compilerOptions,
|
|
19275
|
+
paths: {
|
|
19276
|
+
...existingPaths,
|
|
19277
|
+
[PROMPTS_ALIAS]: [PROMPTS_ALIAS_PATH]
|
|
19278
|
+
}
|
|
19279
|
+
}
|
|
19280
|
+
};
|
|
19281
|
+
writeFileSync(tsconfigPath, `${JSON.stringify(updatedTsconfig, null, 2)}\n`, "utf8");
|
|
19282
|
+
ctx.logger.success(`Added ${PROMPTS_ALIAS} alias to ${tsconfigPath}`);
|
|
19283
|
+
}
|
|
19284
|
+
}
|
|
19285
|
+
}
|
|
19286
|
+
/** @private */
|
|
19287
|
+
function errorMessage(error) {
|
|
19288
|
+
if (error instanceof Error) return error.message;
|
|
19289
|
+
return String(error);
|
|
19290
|
+
}
|
|
19291
|
+
/** @private */
|
|
19292
|
+
function ensureRecommendation(current, id) {
|
|
19293
|
+
if (current.includes(id)) return [...current];
|
|
19294
|
+
return [...current, id];
|
|
19295
|
+
}
|
|
19296
|
+
/** @private */
|
|
19297
|
+
function readFileOrEmpty(filePath) {
|
|
19298
|
+
if (existsSync(filePath)) return readFileSync(filePath, "utf8");
|
|
19299
|
+
return "";
|
|
19300
|
+
}
|
|
19301
|
+
/** @private */
|
|
19302
|
+
function trailingSeparator(content) {
|
|
19303
|
+
if (content.length > 0 && !content.endsWith("\n")) return "\n";
|
|
19304
|
+
return "";
|
|
19305
|
+
}
|
|
19306
|
+
/**
|
|
19307
|
+
* Read a JSON file, returning an empty object if it doesn't exist.
|
|
19308
|
+
* Throws if the file exists but contains invalid JSON, preventing
|
|
19309
|
+
* silent data loss from overwriting malformed config files.
|
|
19310
|
+
*
|
|
19311
|
+
* @private
|
|
19312
|
+
*/
|
|
19313
|
+
function readJsonFile(filePath) {
|
|
19314
|
+
if (!existsSync(filePath)) return {};
|
|
19315
|
+
const content = readFileSync(filePath, "utf8");
|
|
19316
|
+
try {
|
|
19317
|
+
return JSON.parse(content);
|
|
19318
|
+
} catch (error) {
|
|
19319
|
+
throw new Error(`Failed to parse ${filePath}: ${errorMessage(error)}. Fix the JSON syntax or remove the file before running setup.`, { cause: error });
|
|
19320
|
+
}
|
|
19321
|
+
}
|
|
19322
|
+
var VSCODE_DIR, SETTINGS_FILE, EXTENSIONS_FILE, GITIGNORE_FILE, TSCONFIG_FILE, GITIGNORE_ENTRY, PROMPTS_ALIAS, PROMPTS_ALIAS_PATH, setup_default$1;
|
|
19014
19323
|
var init_setup$1 = __esmMin((() => {
|
|
19015
19324
|
init_dist();
|
|
19325
|
+
VSCODE_DIR = ".vscode";
|
|
19326
|
+
SETTINGS_FILE = "settings.json";
|
|
19327
|
+
EXTENSIONS_FILE = "extensions.json";
|
|
19328
|
+
GITIGNORE_FILE = ".gitignore";
|
|
19329
|
+
TSCONFIG_FILE = "tsconfig.json";
|
|
19330
|
+
GITIGNORE_ENTRY = ".prompts/client/";
|
|
19331
|
+
PROMPTS_ALIAS = "~prompts";
|
|
19332
|
+
PROMPTS_ALIAS_PATH = "./.prompts/client/index.ts";
|
|
19016
19333
|
setup_default$1 = command({
|
|
19334
|
+
description: "Configure VSCode IDE settings for .prompt files",
|
|
19335
|
+
async handler(ctx) {
|
|
19336
|
+
ctx.logger.intro("Prompt SDK — Project Setup");
|
|
19337
|
+
await setupPrompts(ctx);
|
|
19338
|
+
ctx.logger.outro("Prompts setup complete.");
|
|
19339
|
+
}
|
|
19340
|
+
});
|
|
19341
|
+
}));
|
|
19342
|
+
|
|
19343
|
+
//#endregion
|
|
19344
|
+
//#region src/commands/setup.ts
|
|
19345
|
+
/** @private */
|
|
19346
|
+
function buildConfigTemplate({ hasPrompts, hasAgents, includes, out }) {
|
|
19347
|
+
if (hasPrompts && hasAgents) return buildCustomTemplate(includes, out, true);
|
|
19348
|
+
if (hasPrompts) return buildCustomTemplate(includes, out, false);
|
|
19349
|
+
return CONFIG_TEMPLATE_AGENTS_ONLY;
|
|
19350
|
+
}
|
|
19351
|
+
/** @private */
|
|
19352
|
+
function buildCustomTemplate(includes, out, includeAgents) {
|
|
19353
|
+
return `import { defineConfig } from "@funkai/config";
|
|
19354
|
+
|
|
19355
|
+
export default defineConfig({
|
|
19356
|
+
prompts: {
|
|
19357
|
+
includes: [${includes.map((r) => `"${r}"`).join(", ")}],
|
|
19358
|
+
out: "${out}",
|
|
19359
|
+
},${match(includeAgents).with(true, () => "\n agents: {},\n").with(false, () => "\n").exhaustive()}});
|
|
19360
|
+
`;
|
|
19361
|
+
}
|
|
19362
|
+
var CONFIG_TEMPLATE_AGENTS_ONLY, setup_default;
|
|
19363
|
+
var init_setup = __esmMin((() => {
|
|
19364
|
+
init_dist();
|
|
19365
|
+
init_setup$1();
|
|
19366
|
+
CONFIG_TEMPLATE_AGENTS_ONLY = `import { defineConfig } from "@funkai/config";
|
|
19367
|
+
|
|
19368
|
+
export default defineConfig({
|
|
19369
|
+
agents: {},
|
|
19370
|
+
});
|
|
19371
|
+
`;
|
|
19372
|
+
setup_default = command({
|
|
19017
19373
|
description: "Set up your project for the funkai SDK",
|
|
19018
19374
|
async handler(ctx) {
|
|
19019
19375
|
ctx.logger.intro("funkai — Project Setup");
|
|
19020
|
-
ctx.
|
|
19021
|
-
|
|
19022
|
-
|
|
19023
|
-
|
|
19376
|
+
const domains = await ctx.prompts.multiselect({
|
|
19377
|
+
message: "Which domains do you want to set up?",
|
|
19378
|
+
options: [{
|
|
19379
|
+
value: "prompts",
|
|
19380
|
+
label: "Prompts",
|
|
19381
|
+
hint: "LiquidJS templating, codegen, IDE integration"
|
|
19382
|
+
}, {
|
|
19383
|
+
value: "agents",
|
|
19384
|
+
label: "Agents",
|
|
19385
|
+
hint: "Agent scaffolding and configuration"
|
|
19386
|
+
}],
|
|
19387
|
+
initialValues: ["prompts"],
|
|
19388
|
+
required: true
|
|
19389
|
+
});
|
|
19390
|
+
const hasPrompts = domains.includes("prompts");
|
|
19391
|
+
const hasAgents = domains.includes("agents");
|
|
19392
|
+
if (await ctx.prompts.confirm({
|
|
19393
|
+
message: "Create funkai.config.ts?",
|
|
19394
|
+
initialValue: true
|
|
19395
|
+
})) {
|
|
19396
|
+
let includes = ["src/prompts/**"];
|
|
19397
|
+
let out = ".prompts/client";
|
|
19398
|
+
if (hasPrompts) {
|
|
19399
|
+
includes = (await ctx.prompts.text({
|
|
19400
|
+
message: "Prompt include patterns (comma-separated)",
|
|
19401
|
+
defaultValue: "src/prompts/**",
|
|
19402
|
+
placeholder: "src/prompts/**"
|
|
19403
|
+
})).split(",").map((r) => r.trim());
|
|
19404
|
+
out = await ctx.prompts.text({
|
|
19405
|
+
message: "Output directory for generated prompt modules",
|
|
19406
|
+
defaultValue: ".prompts/client",
|
|
19407
|
+
placeholder: ".prompts/client"
|
|
19408
|
+
});
|
|
19409
|
+
}
|
|
19410
|
+
const template = buildConfigTemplate({
|
|
19411
|
+
hasPrompts,
|
|
19412
|
+
hasAgents,
|
|
19413
|
+
includes,
|
|
19414
|
+
out
|
|
19415
|
+
});
|
|
19416
|
+
const configPath = resolve("funkai.config.ts");
|
|
19417
|
+
writeFileSync(configPath, template, "utf8");
|
|
19418
|
+
ctx.logger.success(`Created ${configPath}`);
|
|
19419
|
+
}
|
|
19420
|
+
if (hasPrompts) {
|
|
19421
|
+
ctx.logger.info("");
|
|
19422
|
+
ctx.logger.info("Configuring Prompts...");
|
|
19423
|
+
await setupPrompts(ctx);
|
|
19424
|
+
}
|
|
19425
|
+
if (hasAgents) {
|
|
19426
|
+
ctx.logger.info("");
|
|
19427
|
+
ctx.logger.info("Agents configuration is not yet available.");
|
|
19428
|
+
ctx.logger.info("The agents section has been added to your config for future use.");
|
|
19429
|
+
}
|
|
19430
|
+
ctx.logger.outro("Project setup complete.");
|
|
19024
19431
|
}
|
|
19025
19432
|
});
|
|
19026
19433
|
}));
|
|
@@ -19028,14 +19435,31 @@ var init_setup$1 = __esmMin((() => {
|
|
|
19028
19435
|
//#endregion
|
|
19029
19436
|
//#region src/commands/prompts/lint.ts
|
|
19030
19437
|
/**
|
|
19438
|
+
* Resolve lint args by merging CLI flags with config defaults.
|
|
19439
|
+
*
|
|
19440
|
+
* @param args - CLI arguments (take precedence).
|
|
19441
|
+
* @param config - Prompts config from funkai.config.ts (fallback).
|
|
19442
|
+
* @param fail - Error handler for missing required values.
|
|
19443
|
+
* @returns Resolved args with required fields guaranteed.
|
|
19444
|
+
*/
|
|
19445
|
+
function resolveLintArgs(args, config, _fail) {
|
|
19446
|
+
return {
|
|
19447
|
+
includes: args.includes ?? (config && config.includes) ?? ["./**"],
|
|
19448
|
+
excludes: (config && config.excludes) ?? [],
|
|
19449
|
+
partials: args.partials ?? (config && config.partials),
|
|
19450
|
+
silent: args.silent
|
|
19451
|
+
};
|
|
19452
|
+
}
|
|
19453
|
+
/**
|
|
19031
19454
|
* Shared handler for prompts lint/validation.
|
|
19032
19455
|
*
|
|
19033
|
-
* @param params - Handler context with args, logger, and fail callback.
|
|
19456
|
+
* @param params - Handler context with args, config, logger, and fail callback.
|
|
19034
19457
|
*/
|
|
19035
|
-
function handleLint({ args, logger, fail }) {
|
|
19036
|
-
const {
|
|
19458
|
+
function handleLint({ args, config, logger, fail }) {
|
|
19459
|
+
const { includes, excludes, partials, silent } = resolveLintArgs(args, config, fail);
|
|
19037
19460
|
const { discovered, results } = runLintPipeline({
|
|
19038
|
-
|
|
19461
|
+
includes,
|
|
19462
|
+
excludes,
|
|
19039
19463
|
partials
|
|
19040
19464
|
});
|
|
19041
19465
|
if (!silent) logger.info(`Linting ${discovered} prompt(s)...`);
|
|
@@ -19055,10 +19479,11 @@ function handleLint({ args, logger, fail }) {
|
|
|
19055
19479
|
var lintArgs, lint_default;
|
|
19056
19480
|
var init_lint = __esmMin((() => {
|
|
19057
19481
|
init_dist();
|
|
19482
|
+
init_config();
|
|
19058
19483
|
init_lint$1();
|
|
19059
19484
|
init_pipeline();
|
|
19060
19485
|
lintArgs = z.object({
|
|
19061
|
-
|
|
19486
|
+
includes: z.array(z.string()).optional().describe("Glob patterns to scan for .prompt files"),
|
|
19062
19487
|
partials: z.string().optional().describe("Custom partials directory"),
|
|
19063
19488
|
silent: z.boolean().default(false).describe("Suppress output except errors")
|
|
19064
19489
|
});
|
|
@@ -19066,8 +19491,10 @@ var init_lint = __esmMin((() => {
|
|
|
19066
19491
|
description: "Validate .prompt files for schema/template mismatches",
|
|
19067
19492
|
options: lintArgs,
|
|
19068
19493
|
handler(ctx) {
|
|
19494
|
+
const config = getConfig(ctx);
|
|
19069
19495
|
handleLint({
|
|
19070
19496
|
args: ctx.args,
|
|
19497
|
+
config: config.prompts,
|
|
19071
19498
|
logger: ctx.logger,
|
|
19072
19499
|
fail: ctx.fail
|
|
19073
19500
|
});
|
|
@@ -19081,14 +19508,17 @@ var validate_default$1;
|
|
|
19081
19508
|
var init_validate$1 = __esmMin((() => {
|
|
19082
19509
|
init_dist();
|
|
19083
19510
|
init_lint();
|
|
19511
|
+
init_config();
|
|
19084
19512
|
validate_default$1 = command({
|
|
19085
19513
|
description: "Run all validations across the funkai SDK",
|
|
19086
19514
|
options: lintArgs,
|
|
19087
19515
|
handler(ctx) {
|
|
19088
19516
|
const { silent } = ctx.args;
|
|
19517
|
+
const config = getConfig(ctx);
|
|
19089
19518
|
if (!silent) ctx.logger.info("Running prompts validation...");
|
|
19090
19519
|
handleLint({
|
|
19091
19520
|
args: ctx.args,
|
|
19521
|
+
config: config.prompts,
|
|
19092
19522
|
logger: ctx.logger,
|
|
19093
19523
|
fail: ctx.fail
|
|
19094
19524
|
});
|
|
@@ -19116,6 +19546,7 @@ var init_validate = __esmMin((() => {
|
|
|
19116
19546
|
var createTemplate, create_default;
|
|
19117
19547
|
var init_create = __esmMin((() => {
|
|
19118
19548
|
init_dist();
|
|
19549
|
+
init_config();
|
|
19119
19550
|
createTemplate = (name) => `---
|
|
19120
19551
|
name: ${name}
|
|
19121
19552
|
---
|
|
@@ -19125,15 +19556,27 @@ name: ${name}
|
|
|
19125
19556
|
description: "Create a new .prompt file",
|
|
19126
19557
|
options: z.object({
|
|
19127
19558
|
name: z.string().describe("Prompt name (kebab-case)"),
|
|
19128
|
-
out: z.string().optional().describe("Output directory (defaults to cwd)"),
|
|
19559
|
+
out: z.string().optional().describe("Output directory (defaults to first root in config or cwd)"),
|
|
19129
19560
|
partial: z.boolean().default(false).describe("Create as a partial in .prompts/partials/")
|
|
19130
19561
|
}),
|
|
19131
19562
|
handler(ctx) {
|
|
19132
19563
|
const { name, out, partial } = ctx.args;
|
|
19564
|
+
const promptsConfig = getConfig(ctx).prompts;
|
|
19565
|
+
const firstInclude = match(promptsConfig).with({ includes: P.array(P.string).select() }, (includes) => {
|
|
19566
|
+
if (includes.length > 0) {
|
|
19567
|
+
const [pattern] = includes;
|
|
19568
|
+
const staticParts = pattern.split("/").filter((p) => !p.includes("*") && !p.includes("?"));
|
|
19569
|
+
if (staticParts.length > 0) return staticParts.join("/");
|
|
19570
|
+
return;
|
|
19571
|
+
}
|
|
19572
|
+
}).otherwise(() => void 0);
|
|
19133
19573
|
const dir = match({
|
|
19134
19574
|
partial,
|
|
19135
19575
|
out
|
|
19136
|
-
}).with({ partial: true }, () => resolve(".prompts/partials")).with({ out: P.string }, ({ out: outDir }) => resolve(outDir)).otherwise(() =>
|
|
19576
|
+
}).with({ partial: true }, () => resolve(".prompts/partials")).with({ out: P.string }, ({ out: outDir }) => resolve(outDir)).otherwise(() => {
|
|
19577
|
+
if (firstInclude) return resolve(firstInclude);
|
|
19578
|
+
return process.cwd();
|
|
19579
|
+
});
|
|
19137
19580
|
const filePath = resolve(dir, `${name}.prompt`);
|
|
19138
19581
|
if (existsSync(filePath)) ctx.fail(`File already exists: ${filePath}`);
|
|
19139
19582
|
mkdirSync(dir, { recursive: true });
|
|
@@ -19143,135 +19586,6 @@ name: ${name}
|
|
|
19143
19586
|
});
|
|
19144
19587
|
}));
|
|
19145
19588
|
|
|
19146
|
-
//#endregion
|
|
19147
|
-
//#region src/commands/prompts/setup.ts
|
|
19148
|
-
/** @private */
|
|
19149
|
-
function errorMessage(error) {
|
|
19150
|
-
if (error instanceof Error) return error.message;
|
|
19151
|
-
return String(error);
|
|
19152
|
-
}
|
|
19153
|
-
/** @private */
|
|
19154
|
-
function ensureRecommendation(current, id) {
|
|
19155
|
-
if (current.includes(id)) return [...current];
|
|
19156
|
-
return [...current, id];
|
|
19157
|
-
}
|
|
19158
|
-
/** @private */
|
|
19159
|
-
function readFileOrEmpty(filePath) {
|
|
19160
|
-
if (existsSync(filePath)) return readFileSync(filePath, "utf8");
|
|
19161
|
-
return "";
|
|
19162
|
-
}
|
|
19163
|
-
/** @private */
|
|
19164
|
-
function trailingSeparator(content) {
|
|
19165
|
-
if (content.length > 0 && !content.endsWith("\n")) return "\n";
|
|
19166
|
-
return "";
|
|
19167
|
-
}
|
|
19168
|
-
/**
|
|
19169
|
-
* Read a JSON file, returning an empty object if it doesn't exist.
|
|
19170
|
-
* Throws if the file exists but contains invalid JSON, preventing
|
|
19171
|
-
* silent data loss from overwriting malformed config files.
|
|
19172
|
-
*
|
|
19173
|
-
* @private
|
|
19174
|
-
*/
|
|
19175
|
-
function readJsonFile(filePath) {
|
|
19176
|
-
if (!existsSync(filePath)) return {};
|
|
19177
|
-
const content = readFileSync(filePath, "utf8");
|
|
19178
|
-
try {
|
|
19179
|
-
return JSON.parse(content);
|
|
19180
|
-
} catch (error) {
|
|
19181
|
-
throw new Error(`Failed to parse ${filePath}: ${errorMessage(error)}. Fix the JSON syntax or remove the file before running setup.`, { cause: error });
|
|
19182
|
-
}
|
|
19183
|
-
}
|
|
19184
|
-
var VSCODE_DIR, SETTINGS_FILE, EXTENSIONS_FILE, GITIGNORE_FILE, TSCONFIG_FILE, GITIGNORE_ENTRY, PROMPTS_ALIAS, PROMPTS_ALIAS_PATH, setup_default;
|
|
19185
|
-
var init_setup = __esmMin((() => {
|
|
19186
|
-
init_dist();
|
|
19187
|
-
VSCODE_DIR = ".vscode";
|
|
19188
|
-
SETTINGS_FILE = "settings.json";
|
|
19189
|
-
EXTENSIONS_FILE = "extensions.json";
|
|
19190
|
-
GITIGNORE_FILE = ".gitignore";
|
|
19191
|
-
TSCONFIG_FILE = "tsconfig.json";
|
|
19192
|
-
GITIGNORE_ENTRY = ".prompts/client/";
|
|
19193
|
-
PROMPTS_ALIAS = "~prompts";
|
|
19194
|
-
PROMPTS_ALIAS_PATH = "./.prompts/client/index.ts";
|
|
19195
|
-
setup_default = command({
|
|
19196
|
-
description: "Configure VSCode IDE settings for .prompt files",
|
|
19197
|
-
async handler(ctx) {
|
|
19198
|
-
ctx.logger.intro("Prompt SDK — Project Setup");
|
|
19199
|
-
if (await ctx.prompts.confirm({
|
|
19200
|
-
message: "Configure VSCode to treat .prompt files as Markdown with Liquid syntax?",
|
|
19201
|
-
initialValue: true
|
|
19202
|
-
})) {
|
|
19203
|
-
const vscodeDir = resolve(VSCODE_DIR);
|
|
19204
|
-
mkdirSync(vscodeDir, { recursive: true });
|
|
19205
|
-
const settingsPath = resolve(vscodeDir, SETTINGS_FILE);
|
|
19206
|
-
const settings = readJsonFile(settingsPath);
|
|
19207
|
-
const updatedSettings = {
|
|
19208
|
-
...settings,
|
|
19209
|
-
"files.associations": {
|
|
19210
|
-
...settings["files.associations"] ?? {},
|
|
19211
|
-
"*.prompt": "markdown"
|
|
19212
|
-
},
|
|
19213
|
-
"liquid.engine": "standard"
|
|
19214
|
-
};
|
|
19215
|
-
writeFileSync(settingsPath, `${JSON.stringify(updatedSettings, null, 2)}\n`, "utf8");
|
|
19216
|
-
ctx.logger.success(`Updated ${settingsPath}`);
|
|
19217
|
-
}
|
|
19218
|
-
if (await ctx.prompts.confirm({
|
|
19219
|
-
message: "Add Shopify Liquid extension to VSCode recommendations?",
|
|
19220
|
-
initialValue: true
|
|
19221
|
-
})) {
|
|
19222
|
-
const vscodeDir = resolve(VSCODE_DIR);
|
|
19223
|
-
mkdirSync(vscodeDir, { recursive: true });
|
|
19224
|
-
const extensionsPath = resolve(vscodeDir, EXTENSIONS_FILE);
|
|
19225
|
-
const extensions = readJsonFile(extensionsPath);
|
|
19226
|
-
const recommendations = ensureRecommendation(extensions.recommendations ?? [], "sissel.shopify-liquid");
|
|
19227
|
-
const updatedExtensions = {
|
|
19228
|
-
...extensions,
|
|
19229
|
-
recommendations
|
|
19230
|
-
};
|
|
19231
|
-
writeFileSync(extensionsPath, `${JSON.stringify(updatedExtensions, null, 2)}\n`, "utf8");
|
|
19232
|
-
ctx.logger.success(`Updated ${extensionsPath}`);
|
|
19233
|
-
}
|
|
19234
|
-
if (await ctx.prompts.confirm({
|
|
19235
|
-
message: "Add .prompts/client/ to .gitignore? (generated client should not be committed)",
|
|
19236
|
-
initialValue: true
|
|
19237
|
-
})) {
|
|
19238
|
-
const gitignorePath = resolve(GITIGNORE_FILE);
|
|
19239
|
-
const existing = readFileOrEmpty(gitignorePath);
|
|
19240
|
-
if (existing.includes(GITIGNORE_ENTRY)) ctx.logger.info(`${GITIGNORE_ENTRY} already in ${gitignorePath}`);
|
|
19241
|
-
else {
|
|
19242
|
-
writeFileSync(gitignorePath, `${existing}${`${trailingSeparator(existing)}\n# Generated prompt client (created by \`funkai prompts generate\`)\n${GITIGNORE_ENTRY}\n`}`, "utf8");
|
|
19243
|
-
ctx.logger.success(`Added ${GITIGNORE_ENTRY} to ${gitignorePath}`);
|
|
19244
|
-
}
|
|
19245
|
-
}
|
|
19246
|
-
if (await ctx.prompts.confirm({
|
|
19247
|
-
message: "Add ~prompts path alias to tsconfig.json?",
|
|
19248
|
-
initialValue: true
|
|
19249
|
-
})) {
|
|
19250
|
-
const tsconfigPath = resolve(TSCONFIG_FILE);
|
|
19251
|
-
const tsconfig = readJsonFile(tsconfigPath);
|
|
19252
|
-
const compilerOptions = tsconfig.compilerOptions ?? {};
|
|
19253
|
-
const existingPaths = compilerOptions.paths ?? {};
|
|
19254
|
-
if (existingPaths[PROMPTS_ALIAS]) ctx.logger.info(`${PROMPTS_ALIAS} alias already in ${tsconfigPath}`);
|
|
19255
|
-
else {
|
|
19256
|
-
const updatedTsconfig = {
|
|
19257
|
-
...tsconfig,
|
|
19258
|
-
compilerOptions: {
|
|
19259
|
-
...compilerOptions,
|
|
19260
|
-
paths: {
|
|
19261
|
-
...existingPaths,
|
|
19262
|
-
[PROMPTS_ALIAS]: [PROMPTS_ALIAS_PATH]
|
|
19263
|
-
}
|
|
19264
|
-
}
|
|
19265
|
-
};
|
|
19266
|
-
writeFileSync(tsconfigPath, `${JSON.stringify(updatedTsconfig, null, 2)}\n`, "utf8");
|
|
19267
|
-
ctx.logger.success(`Added ${PROMPTS_ALIAS} alias to ${tsconfigPath}`);
|
|
19268
|
-
}
|
|
19269
|
-
}
|
|
19270
|
-
ctx.logger.outro("Project setup complete.");
|
|
19271
|
-
}
|
|
19272
|
-
});
|
|
19273
|
-
}));
|
|
19274
|
-
|
|
19275
19589
|
//#endregion
|
|
19276
19590
|
//#region \0virtual:kidd-static-commands
|
|
19277
19591
|
var _virtual_kidd_static_commands_exports = /* @__PURE__ */ __exportAll$1({ autoload: () => autoload$1 });
|
|
@@ -19282,23 +19596,23 @@ var commands;
|
|
|
19282
19596
|
var init__virtual_kidd_static_commands = __esmMin((() => {
|
|
19283
19597
|
init_tag();
|
|
19284
19598
|
init_generate();
|
|
19285
|
-
init_setup
|
|
19599
|
+
init_setup();
|
|
19286
19600
|
init_validate$1();
|
|
19287
19601
|
init_validate();
|
|
19288
19602
|
init_create();
|
|
19289
19603
|
init_generate$1();
|
|
19290
19604
|
init_lint();
|
|
19291
|
-
init_setup();
|
|
19605
|
+
init_setup$1();
|
|
19292
19606
|
commands = {
|
|
19293
19607
|
"generate": generate_default,
|
|
19294
|
-
"setup": setup_default
|
|
19608
|
+
"setup": setup_default,
|
|
19295
19609
|
"validate": validate_default$1,
|
|
19296
19610
|
"agents": withTag({ commands: { "validate": validate_default } }, "Command"),
|
|
19297
19611
|
"prompts": withTag({ commands: {
|
|
19298
19612
|
"create": create_default,
|
|
19299
19613
|
"generate": generate_default$1,
|
|
19300
19614
|
"lint": lint_default,
|
|
19301
|
-
"setup": setup_default
|
|
19615
|
+
"setup": setup_default$1
|
|
19302
19616
|
} }, "Command")
|
|
19303
19617
|
};
|
|
19304
19618
|
}));
|
|
@@ -20139,7 +20453,7 @@ function resolveVersion(explicit) {
|
|
|
20139
20453
|
return err(VERSION_ERROR);
|
|
20140
20454
|
}
|
|
20141
20455
|
{
|
|
20142
|
-
const parsed = VersionSchema.safeParse("0.
|
|
20456
|
+
const parsed = VersionSchema.safeParse("0.3.0");
|
|
20143
20457
|
if (parsed.success) return ok(parsed.data);
|
|
20144
20458
|
}
|
|
20145
20459
|
return err(VERSION_ERROR);
|
|
@@ -20327,16 +20641,18 @@ init_generate();
|
|
|
20327
20641
|
init_create();
|
|
20328
20642
|
init_generate$1();
|
|
20329
20643
|
init_lint();
|
|
20330
|
-
init_setup();
|
|
20331
20644
|
init_setup$1();
|
|
20645
|
+
init_setup();
|
|
20332
20646
|
init_validate$1();
|
|
20647
|
+
init_config();
|
|
20333
20648
|
await cli({
|
|
20334
20649
|
description: "CLI for the funkai AI SDK framework",
|
|
20335
20650
|
name: "funkai",
|
|
20336
20651
|
version: createRequire(import.meta.url)("../package.json").version,
|
|
20652
|
+
config: { schema: configSchema },
|
|
20337
20653
|
commands: {
|
|
20338
20654
|
generate: generate_default,
|
|
20339
|
-
setup: setup_default
|
|
20655
|
+
setup: setup_default,
|
|
20340
20656
|
validate: validate_default$1,
|
|
20341
20657
|
agents: command({
|
|
20342
20658
|
description: "Agent-related commands",
|
|
@@ -20348,7 +20664,7 @@ await cli({
|
|
|
20348
20664
|
create: create_default,
|
|
20349
20665
|
generate: generate_default$1,
|
|
20350
20666
|
lint: lint_default,
|
|
20351
|
-
setup: setup_default
|
|
20667
|
+
setup: setup_default$1
|
|
20352
20668
|
}
|
|
20353
20669
|
})
|
|
20354
20670
|
}
|