@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/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 contains the Zod schema, inlined template, and
18360
- * `render` / `validate` functions.
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
- HEADER,
18368
- `// Source: ${prompt.sourcePath}`,
18445
+ formatHeader(prompt.sourcePath),
18369
18446
  "",
18370
18447
  "import { z } from 'zod'",
18371
- "import { liquidEngine } from '@funkai/prompts/runtime'",
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
- "export default {",
18380
- ` name: '${prompt.name}' as const,`,
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
- ...match(prompt.schema.length).with(0, () => [
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 importName = toCamelCase(prompt.name);
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[importName] === "object" && target[importName] !== null) throw new Error(`Collision: prompt "${importName}" conflicts with existing group namespace "${importName}" at the same level.`);
18421
- target[importName] = importName;
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", () => [`${pad}${key},`]).otherwise(() => [
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) => a.name.localeCompare(b.name));
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
- HEADER,
18545
+ formatHeader(),
18453
18546
  "",
18454
18547
  "import { createPromptRegistry } from '@funkai/prompts'",
18455
- sorted.map((p) => `import ${toCamelCase(p.name)} from './${p.name}.js'`).join("\n"),
18548
+ imports,
18456
18549
  "",
18457
18550
  "export const prompts = createPromptRegistry({",
18458
- ...serializeTree(buildTree(sorted), 1),
18551
+ ...treeLines,
18459
18552
  "})",
18460
18553
  ""
18461
18554
  ].join("\n");
18462
18555
  }
18463
- var HEADER;
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 from the given root directories.
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 roots - Directories to scan recursively.
18821
- * @returns Sorted, deduplicated list of discovered prompts.
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(roots) {
18825
- const all = roots.flatMap((root) => scanDirectory(resolve(root), 0));
18826
- const duplicate = [...Map.groupBy(all, (prompt) => prompt.name).entries()].find(([, prompts]) => prompts.length > 1);
18827
- if (duplicate) {
18828
- const [name, prompts] = duplicate;
18829
- const paths = prompts.map((p) => p.filePath).join("\n ");
18830
- throw new Error(`Duplicate prompt name "${name}" found in:\n ${paths}`);
18831
- }
18832
- return all.toSorted((a, b) => a.name.localeCompare(b.name));
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
- const discovered = discoverPrompts([...options.roots]);
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
- const discovered = discoverPrompts([...options.roots]);
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: frontmatter.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: processed.map((p) => p.prompt)
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, roots, partials, silent } = args;
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
- roots,
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
- roots: z.array(z.string()).describe("Root directories to scan for .prompt files"),
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
- var setup_default$1;
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.logger.info("Run domain-specific setup commands:");
19021
- ctx.logger.step("funkai prompts setup — Configure IDE and project for .prompt files");
19022
- ctx.logger.step("funkai agents setup — (coming soon)");
19023
- ctx.logger.outro("Choose the setup command for your domain.");
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 { roots, partials, silent } = args;
19458
+ function handleLint({ args, config, logger, fail }) {
19459
+ const { includes, excludes, partials, silent } = resolveLintArgs(args, config, fail);
19037
19460
  const { discovered, results } = runLintPipeline({
19038
- roots,
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
- roots: z.array(z.string()).describe("Root directories to scan for .prompt files"),
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(() => process.cwd());
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$1();
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$1,
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.2.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$1,
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
  }