@markbrutx/promptbook-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/README.md CHANGED
@@ -19,8 +19,11 @@ promptbook view # open the viewer (needs @markbrutx/prom
19
19
  promptbook annotations list|resolve|clear
20
20
  ```
21
21
 
22
- The prompts folder is resolved from `--dir`, then `promptbook.json`, then
23
- `./prompts`.
22
+ The prompts folder is resolved from `--dir`, then `promptbook.json` (the
23
+ nearest one found by walking up from the current directory — same model as
24
+ `git`/`biome`/`eslint`; the `promptsDir` value is taken relative to wherever
25
+ the config file lives, so one config at the repo root works from every
26
+ subfolder), then `./prompts`.
24
27
 
25
28
  ```
26
29
  promptbook resolve assistant --ctx mode=terse --ctx locale=ru
@@ -18,20 +18,32 @@ interface PromptbookConfig {
18
18
  lint?: unknown;
19
19
  eval?: unknown;
20
20
  }
21
+ /** A loaded `promptbook.json`: parsed data plus the directory that held it. */
22
+ export interface LoadedConfig {
23
+ /** Parsed JSON object (or `{}` when no file was found or parsing failed). */
24
+ data: PromptbookConfig;
25
+ /** Absolute directory where the config was found; `undefined` if nothing matched up the tree. */
26
+ dir?: string;
27
+ }
21
28
  /** lint options sourced from the `lint` section of `promptbook.json`. */
22
29
  export interface LintConfig {
23
30
  maxTokens?: number;
24
31
  bannedTokens?: string[];
25
32
  }
26
33
  /**
27
- * Read and parse `promptbook.json` from cwd once. Missing, unreadable or
28
- * malformed config yields an empty object, so callers treat it as best-effort
29
- * and layer flags on top. Pass the result to {@link resolvePromptsDir} and
30
- * {@link lintConfigFrom} to avoid re-reading the file per command.
34
+ * Walk up from `io.cwd()` to find the first `promptbook.json`, parse it, and
35
+ * return its data + the directory it lived in. Walking up (rather than only
36
+ * checking cwd) is what makes `promptbook` work like `git`/`biome`/`eslint`:
37
+ * one config at the repo root reaches every subfolder. Path-valued keys
38
+ * (currently just `promptsDir`) are resolved relative to {@link LoadedConfig.dir}
39
+ * by {@link resolvePromptsDir}, not relative to wherever the shell happens to
40
+ * be — so `pnpm exec` snapping cwd to a workspace package cannot break the
41
+ * lookup. Missing, unreadable or malformed files yield an empty config
42
+ * (best-effort), so callers can layer flags on top.
31
43
  */
32
- export declare function loadConfig(io: IO): Promise<PromptbookConfig>;
44
+ export declare function loadConfig(io: IO): Promise<LoadedConfig>;
33
45
  /** Extract the `lint` section from an already-loaded config. */
34
- export declare function lintConfigFrom(config: PromptbookConfig): LintConfig;
46
+ export declare function lintConfigFrom(loaded: LoadedConfig): LintConfig;
35
47
  /** Convenience wrapper: load config and extract its `lint` section. */
36
48
  export declare function loadLintConfig(io: IO): Promise<LintConfig>;
37
49
  /** eval options sourced from the `eval` section of `promptbook.json`. */
@@ -40,17 +52,22 @@ export interface EvalConfig {
40
52
  baseUrl?: string;
41
53
  }
42
54
  /** Extract the `eval` section from an already-loaded config. */
43
- export declare function evalConfigFrom(config: PromptbookConfig): EvalConfig;
55
+ export declare function evalConfigFrom(loaded: LoadedConfig): EvalConfig;
44
56
  /**
45
- * Resolve the prompts folder by priority: `--dir` flag > `promptbook.json`
46
- * (`promptsDir` key) in cwd > `./prompts`. All results are absolute. Pass a
47
- * preloaded `config` to reuse a single read; otherwise it is loaded here.
57
+ * Resolve the prompts folder by priority:
58
+ * 1. `--dir <path>` relative to **cwd** (explicit per-invocation override).
59
+ * 2. `promptbook.json` `promptsDir` relative to **the config file's directory**
60
+ * (so the value can stay stable while the user shells around in subfolders).
61
+ * 3. `./prompts` — relative to cwd (back-compat default when no config exists).
62
+ *
63
+ * All results are absolute. Pass a preloaded {@link LoadedConfig} to reuse a
64
+ * single read; otherwise it is loaded here.
48
65
  */
49
- export declare function resolvePromptsDir(io: IO, dirFlag?: string, config?: PromptbookConfig): Promise<string>;
66
+ export declare function resolvePromptsDir(io: IO, dirFlag?: string, loaded?: LoadedConfig): Promise<string>;
50
67
  /**
51
68
  * Resolve the prompts folder and confirm it exists. On a missing folder, write
52
69
  * a clear error to stderr and return null so the caller can exit non-zero.
53
70
  */
54
- export declare function requirePromptsDir(io: IO, dirFlag?: string, config?: PromptbookConfig): Promise<string | null>;
71
+ export declare function requirePromptsDir(io: IO, dirFlag?: string, loaded?: LoadedConfig): Promise<string | null>;
55
72
  export {};
56
73
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AASlC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAWtD;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CActD;AAuBD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAalG;AAED,UAAU,gBAAgB;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAclE;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAanE;AAED,uEAAuE;AACvE,wBAAsB,cAAc,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAEhE;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAanE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CASjB;AAYD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AASlC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAWtD;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CActD;AAuBD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAalG;AAED,UAAU,gBAAgB;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,6EAA6E;IAC7E,IAAI,EAAE,gBAAgB,CAAC;IACvB,iGAAiG;IACjG,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAwB9D;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,CAa/D;AAED,uEAAuE;AACvE,wBAAsB,cAAc,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAEhE;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,CAa/D;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CASxG;AAYD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,YAAY,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxB"}
@@ -1,4 +1,4 @@
1
- import { resolve as resolvePath } from "node:path";
1
+ import { dirname, resolve as resolvePath } from "node:path";
2
2
  const NUMERIC = /^-?\d+(?:\.\d+)?$/;
3
3
  /** True for a plain JSON object: an object that is neither null nor an array. */
4
4
  function isJsonObject(value) {
@@ -79,31 +79,46 @@ export async function buildContext(io, pairs, contextFile) {
79
79
  return { ...fileContext, ...parseCtxPairs(pairs) };
80
80
  }
81
81
  /**
82
- * Read and parse `promptbook.json` from cwd once. Missing, unreadable or
83
- * malformed config yields an empty object, so callers treat it as best-effort
84
- * and layer flags on top. Pass the result to {@link resolvePromptsDir} and
85
- * {@link lintConfigFrom} to avoid re-reading the file per command.
82
+ * Walk up from `io.cwd()` to find the first `promptbook.json`, parse it, and
83
+ * return its data + the directory it lived in. Walking up (rather than only
84
+ * checking cwd) is what makes `promptbook` work like `git`/`biome`/`eslint`:
85
+ * one config at the repo root reaches every subfolder. Path-valued keys
86
+ * (currently just `promptsDir`) are resolved relative to {@link LoadedConfig.dir}
87
+ * by {@link resolvePromptsDir}, not relative to wherever the shell happens to
88
+ * be — so `pnpm exec` snapping cwd to a workspace package cannot break the
89
+ * lookup. Missing, unreadable or malformed files yield an empty config
90
+ * (best-effort), so callers can layer flags on top.
86
91
  */
87
92
  export async function loadConfig(io) {
88
- const configPath = resolvePath(io.cwd(), "promptbook.json");
89
- let raw;
90
- try {
91
- raw = await io.fs.readFile(configPath);
92
- }
93
- catch {
94
- return {};
95
- }
96
- try {
97
- const parsed = JSON.parse(raw);
98
- return isJsonObject(parsed) ? parsed : {};
99
- }
100
- catch {
101
- return {};
93
+ let dir = resolvePath(io.cwd());
94
+ for (;;) {
95
+ const configPath = resolvePath(dir, "promptbook.json");
96
+ let raw;
97
+ try {
98
+ raw = await io.fs.readFile(configPath);
99
+ }
100
+ catch {
101
+ // not found at this level; try the parent
102
+ }
103
+ if (raw !== undefined) {
104
+ try {
105
+ const parsed = JSON.parse(raw);
106
+ return { data: isJsonObject(parsed) ? parsed : {}, dir };
107
+ }
108
+ catch {
109
+ return { data: {}, dir };
110
+ }
111
+ }
112
+ const parent = dirname(dir);
113
+ if (parent === dir) {
114
+ return { data: {} };
115
+ }
116
+ dir = parent;
102
117
  }
103
118
  }
104
119
  /** Extract the `lint` section from an already-loaded config. */
105
- export function lintConfigFrom(config) {
106
- const section = config.lint;
120
+ export function lintConfigFrom(loaded) {
121
+ const section = loaded.data.lint;
107
122
  if (!isJsonObject(section)) {
108
123
  return {};
109
124
  }
@@ -121,8 +136,8 @@ export async function loadLintConfig(io) {
121
136
  return lintConfigFrom(await loadConfig(io));
122
137
  }
123
138
  /** Extract the `eval` section from an already-loaded config. */
124
- export function evalConfigFrom(config) {
125
- const section = config.eval;
139
+ export function evalConfigFrom(loaded) {
140
+ const section = loaded.data.eval;
126
141
  if (!isJsonObject(section)) {
127
142
  return {};
128
143
  }
@@ -136,17 +151,22 @@ export function evalConfigFrom(config) {
136
151
  return evalConfig;
137
152
  }
138
153
  /**
139
- * Resolve the prompts folder by priority: `--dir` flag > `promptbook.json`
140
- * (`promptsDir` key) in cwd > `./prompts`. All results are absolute. Pass a
141
- * preloaded `config` to reuse a single read; otherwise it is loaded here.
154
+ * Resolve the prompts folder by priority:
155
+ * 1. `--dir <path>` relative to **cwd** (explicit per-invocation override).
156
+ * 2. `promptbook.json` `promptsDir` relative to **the config file's directory**
157
+ * (so the value can stay stable while the user shells around in subfolders).
158
+ * 3. `./prompts` — relative to cwd (back-compat default when no config exists).
159
+ *
160
+ * All results are absolute. Pass a preloaded {@link LoadedConfig} to reuse a
161
+ * single read; otherwise it is loaded here.
142
162
  */
143
- export async function resolvePromptsDir(io, dirFlag, config) {
163
+ export async function resolvePromptsDir(io, dirFlag, loaded) {
144
164
  if (dirFlag !== undefined) {
145
165
  return resolvePath(io.cwd(), dirFlag);
146
166
  }
147
- const resolved = config ?? (await loadConfig(io));
148
- if (typeof resolved.promptsDir === "string") {
149
- return resolvePath(io.cwd(), resolved.promptsDir);
167
+ const resolved = loaded ?? (await loadConfig(io));
168
+ if (typeof resolved.data.promptsDir === "string" && resolved.dir !== undefined) {
169
+ return resolvePath(resolved.dir, resolved.data.promptsDir);
150
170
  }
151
171
  return resolvePath(io.cwd(), "prompts");
152
172
  }
@@ -164,8 +184,8 @@ async function dirExists(io, dir) {
164
184
  * Resolve the prompts folder and confirm it exists. On a missing folder, write
165
185
  * a clear error to stderr and return null so the caller can exit non-zero.
166
186
  */
167
- export async function requirePromptsDir(io, dirFlag, config) {
168
- const promptsDir = await resolvePromptsDir(io, dirFlag, config);
187
+ export async function requirePromptsDir(io, dirFlag, loaded) {
188
+ const promptsDir = await resolvePromptsDir(io, dirFlag, loaded);
169
189
  if (!(await dirExists(io, promptsDir))) {
170
190
  io.stderr(`error: prompts folder not found: ${promptsDir}\n`);
171
191
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markbrutx/promptbook-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Thin terminal surface over @markbrutx/promptbook-core: resolve and ls for agents and CI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -48,11 +48,11 @@
48
48
  "check": "biome check ."
49
49
  },
50
50
  "dependencies": {
51
- "@markbrutx/promptbook-core": "^0.2.0",
52
- "@markbrutx/promptbook-openrouter": "^0.2.0"
51
+ "@markbrutx/promptbook-core": "^0.3.0",
52
+ "@markbrutx/promptbook-openrouter": "^0.3.0"
53
53
  },
54
54
  "optionalDependencies": {
55
- "@markbrutx/promptbook-viewer": "^0.2.0"
55
+ "@markbrutx/promptbook-viewer": "^0.3.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@biomejs/biome": "latest",
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { resolve as resolvePath } from "node:path";
1
+ import { dirname, resolve as resolvePath } from "node:path";
2
2
  import type { Context, ContextValue } from "@markbrutx/promptbook-core";
3
3
  import type { IO } from "./io.js";
4
4
 
@@ -90,6 +90,14 @@ interface PromptbookConfig {
90
90
  eval?: unknown;
91
91
  }
92
92
 
93
+ /** A loaded `promptbook.json`: parsed data plus the directory that held it. */
94
+ export interface LoadedConfig {
95
+ /** Parsed JSON object (or `{}` when no file was found or parsing failed). */
96
+ data: PromptbookConfig;
97
+ /** Absolute directory where the config was found; `undefined` if nothing matched up the tree. */
98
+ dir?: string;
99
+ }
100
+
93
101
  /** lint options sourced from the `lint` section of `promptbook.json`. */
94
102
  export interface LintConfig {
95
103
  maxTokens?: number;
@@ -97,30 +105,45 @@ export interface LintConfig {
97
105
  }
98
106
 
99
107
  /**
100
- * Read and parse `promptbook.json` from cwd once. Missing, unreadable or
101
- * malformed config yields an empty object, so callers treat it as best-effort
102
- * and layer flags on top. Pass the result to {@link resolvePromptsDir} and
103
- * {@link lintConfigFrom} to avoid re-reading the file per command.
108
+ * Walk up from `io.cwd()` to find the first `promptbook.json`, parse it, and
109
+ * return its data + the directory it lived in. Walking up (rather than only
110
+ * checking cwd) is what makes `promptbook` work like `git`/`biome`/`eslint`:
111
+ * one config at the repo root reaches every subfolder. Path-valued keys
112
+ * (currently just `promptsDir`) are resolved relative to {@link LoadedConfig.dir}
113
+ * by {@link resolvePromptsDir}, not relative to wherever the shell happens to
114
+ * be — so `pnpm exec` snapping cwd to a workspace package cannot break the
115
+ * lookup. Missing, unreadable or malformed files yield an empty config
116
+ * (best-effort), so callers can layer flags on top.
104
117
  */
105
- export async function loadConfig(io: IO): Promise<PromptbookConfig> {
106
- const configPath = resolvePath(io.cwd(), "promptbook.json");
107
- let raw: string;
108
- try {
109
- raw = await io.fs.readFile(configPath);
110
- } catch {
111
- return {};
112
- }
113
- try {
114
- const parsed = JSON.parse(raw) as unknown;
115
- return isJsonObject(parsed) ? parsed : {};
116
- } catch {
117
- return {};
118
+ export async function loadConfig(io: IO): Promise<LoadedConfig> {
119
+ let dir = resolvePath(io.cwd());
120
+ for (;;) {
121
+ const configPath = resolvePath(dir, "promptbook.json");
122
+ let raw: string | undefined;
123
+ try {
124
+ raw = await io.fs.readFile(configPath);
125
+ } catch {
126
+ // not found at this level; try the parent
127
+ }
128
+ if (raw !== undefined) {
129
+ try {
130
+ const parsed = JSON.parse(raw) as unknown;
131
+ return { data: isJsonObject(parsed) ? parsed : {}, dir };
132
+ } catch {
133
+ return { data: {}, dir };
134
+ }
135
+ }
136
+ const parent = dirname(dir);
137
+ if (parent === dir) {
138
+ return { data: {} };
139
+ }
140
+ dir = parent;
118
141
  }
119
142
  }
120
143
 
121
144
  /** Extract the `lint` section from an already-loaded config. */
122
- export function lintConfigFrom(config: PromptbookConfig): LintConfig {
123
- const section = config.lint;
145
+ export function lintConfigFrom(loaded: LoadedConfig): LintConfig {
146
+ const section = loaded.data.lint;
124
147
  if (!isJsonObject(section)) {
125
148
  return {};
126
149
  }
@@ -146,8 +169,8 @@ export interface EvalConfig {
146
169
  }
147
170
 
148
171
  /** Extract the `eval` section from an already-loaded config. */
149
- export function evalConfigFrom(config: PromptbookConfig): EvalConfig {
150
- const section = config.eval;
172
+ export function evalConfigFrom(loaded: LoadedConfig): EvalConfig {
173
+ const section = loaded.data.eval;
151
174
  if (!isJsonObject(section)) {
152
175
  return {};
153
176
  }
@@ -162,21 +185,22 @@ export function evalConfigFrom(config: PromptbookConfig): EvalConfig {
162
185
  }
163
186
 
164
187
  /**
165
- * Resolve the prompts folder by priority: `--dir` flag > `promptbook.json`
166
- * (`promptsDir` key) in cwd > `./prompts`. All results are absolute. Pass a
167
- * preloaded `config` to reuse a single read; otherwise it is loaded here.
188
+ * Resolve the prompts folder by priority:
189
+ * 1. `--dir <path>` relative to **cwd** (explicit per-invocation override).
190
+ * 2. `promptbook.json` `promptsDir` relative to **the config file's directory**
191
+ * (so the value can stay stable while the user shells around in subfolders).
192
+ * 3. `./prompts` — relative to cwd (back-compat default when no config exists).
193
+ *
194
+ * All results are absolute. Pass a preloaded {@link LoadedConfig} to reuse a
195
+ * single read; otherwise it is loaded here.
168
196
  */
169
- export async function resolvePromptsDir(
170
- io: IO,
171
- dirFlag?: string,
172
- config?: PromptbookConfig,
173
- ): Promise<string> {
197
+ export async function resolvePromptsDir(io: IO, dirFlag?: string, loaded?: LoadedConfig): Promise<string> {
174
198
  if (dirFlag !== undefined) {
175
199
  return resolvePath(io.cwd(), dirFlag);
176
200
  }
177
- const resolved = config ?? (await loadConfig(io));
178
- if (typeof resolved.promptsDir === "string") {
179
- return resolvePath(io.cwd(), resolved.promptsDir);
201
+ const resolved = loaded ?? (await loadConfig(io));
202
+ if (typeof resolved.data.promptsDir === "string" && resolved.dir !== undefined) {
203
+ return resolvePath(resolved.dir, resolved.data.promptsDir);
180
204
  }
181
205
  return resolvePath(io.cwd(), "prompts");
182
206
  }
@@ -198,9 +222,9 @@ async function dirExists(io: IO, dir: string): Promise<boolean> {
198
222
  export async function requirePromptsDir(
199
223
  io: IO,
200
224
  dirFlag?: string,
201
- config?: PromptbookConfig,
225
+ loaded?: LoadedConfig,
202
226
  ): Promise<string | null> {
203
- const promptsDir = await resolvePromptsDir(io, dirFlag, config);
227
+ const promptsDir = await resolvePromptsDir(io, dirFlag, loaded);
204
228
  if (!(await dirExists(io, promptsDir))) {
205
229
  io.stderr(`error: prompts folder not found: ${promptsDir}\n`);
206
230
  return null;