@justram/pie 0.1.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +236 -0
  4. package/bin/pie +14 -0
  5. package/dist/cache/index.d.ts +4 -0
  6. package/dist/cache/index.js +3 -0
  7. package/dist/cache/warm.d.ts +3 -0
  8. package/dist/cache/warm.js +23 -0
  9. package/dist/cli/args.d.ts +30 -0
  10. package/dist/cli/args.js +185 -0
  11. package/dist/cli/attachments.d.ts +7 -0
  12. package/dist/cli/attachments.js +29 -0
  13. package/dist/cli/config.d.ts +22 -0
  14. package/dist/cli/config.js +20 -0
  15. package/dist/cli/image.d.ts +17 -0
  16. package/dist/cli/image.js +73 -0
  17. package/dist/cli/index.d.ts +2 -0
  18. package/dist/cli/index.js +1 -0
  19. package/dist/cli/oauth.d.ts +14 -0
  20. package/dist/cli/oauth.js +178 -0
  21. package/dist/cli/stream.d.ts +7 -0
  22. package/dist/cli/stream.js +73 -0
  23. package/dist/cli.d.ts +2 -0
  24. package/dist/cli.js +15 -0
  25. package/dist/core/cache/file.d.ts +4 -0
  26. package/dist/core/cache/file.js +44 -0
  27. package/dist/core/cache/key.d.ts +2 -0
  28. package/dist/core/cache/key.js +12 -0
  29. package/dist/core/cache/memory.d.ts +4 -0
  30. package/dist/core/cache/memory.js +33 -0
  31. package/dist/core/cache/types.d.ts +19 -0
  32. package/dist/core/cache/types.js +1 -0
  33. package/dist/core/errors.d.ts +39 -0
  34. package/dist/core/errors.js +50 -0
  35. package/dist/core/events.d.ts +87 -0
  36. package/dist/core/events.js +1 -0
  37. package/dist/core/extract.d.ts +4 -0
  38. package/dist/core/extract.js +384 -0
  39. package/dist/core/frontmatter.d.ts +5 -0
  40. package/dist/core/frontmatter.js +58 -0
  41. package/dist/core/helpers.d.ts +5 -0
  42. package/dist/core/helpers.js +80 -0
  43. package/dist/core/schema/normalize.d.ts +7 -0
  44. package/dist/core/schema/normalize.js +187 -0
  45. package/dist/core/setup.d.ts +13 -0
  46. package/dist/core/setup.js +174 -0
  47. package/dist/core/types.d.ts +143 -0
  48. package/dist/core/types.js +1 -0
  49. package/dist/core/validators/assert.d.ts +1 -0
  50. package/dist/core/validators/assert.js +18 -0
  51. package/dist/core/validators/command.d.ts +1 -0
  52. package/dist/core/validators/command.js +10 -0
  53. package/dist/core/validators/http.d.ts +1 -0
  54. package/dist/core/validators/http.js +28 -0
  55. package/dist/core/validators/index.d.ts +22 -0
  56. package/dist/core/validators/index.js +55 -0
  57. package/dist/core/validators/shell.d.ts +9 -0
  58. package/dist/core/validators/shell.js +24 -0
  59. package/dist/errors.d.ts +1 -0
  60. package/dist/errors.js +1 -0
  61. package/dist/events.d.ts +1 -0
  62. package/dist/events.js +1 -0
  63. package/dist/extract.d.ts +4 -0
  64. package/dist/extract.js +18 -0
  65. package/dist/index.d.ts +13 -0
  66. package/dist/index.js +8 -0
  67. package/dist/main.d.ts +9 -0
  68. package/dist/main.js +571 -0
  69. package/dist/models.d.ts +21 -0
  70. package/dist/models.js +21 -0
  71. package/dist/recipes/index.d.ts +34 -0
  72. package/dist/recipes/index.js +185 -0
  73. package/dist/runtime/node.d.ts +2 -0
  74. package/dist/runtime/node.js +71 -0
  75. package/dist/runtime/types.d.ts +32 -0
  76. package/dist/runtime/types.js +1 -0
  77. package/dist/setup.d.ts +2 -0
  78. package/dist/setup.js +1 -0
  79. package/dist/types.d.ts +1 -0
  80. package/dist/types.js +1 -0
  81. package/dist/utils/helpers.d.ts +5 -0
  82. package/dist/utils/helpers.js +80 -0
  83. package/package.json +71 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Breaking Changes
6
+
7
+ - Published as `@justram/pie` because the unscoped name is already taken on npm.
8
+
9
+ ### Added
10
+
11
+ ### Changed
12
+
13
+ ### Fixed
14
+
15
+ ### Removed
16
+
17
+ ## 0.1.0
18
+
19
+ - Initial release of the SDK and CLI.
20
+ - Mini agent extraction loop with schema validation and retries.
21
+ - Validator layers (sync/async, shell, HTTP).
22
+ - Response caching with memory and file stores.
23
+ - Recipe system for reusable extraction setups.
24
+ - Added `src/main.ts` and moved CLI orchestration behind a thin `src/cli.ts` entry.
25
+ - Moved internal CLI helpers from `src/core/helpers.ts` to `src/utils/helpers.ts`.
26
+ - Routed cache/errors/setup exports through public wrapper modules and kept `bin/pie` as a thin entry.
27
+ - Documented the CLI entry flow and added architecture notes.
28
+ - Split TypeScript config into `tsconfig.base.json` and `tsconfig.build.json`.
29
+ - Updated `.gitignore` for macOS and example artifacts.
30
+ - Updated the thinking-comparison example default model to an available Gemini variant.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jheng Hong Yang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # pie
2
+
3
+ Structured extraction library and CLI built on `@mariozechner/pi-ai`.
4
+
5
+ - Mini agent loop: think → respond → validate → retry
6
+ - Schema-first with TypeBox + AJV
7
+ - Layered validation (schema, sync/async functions, shell, HTTP)
8
+ - Streaming events for observability
9
+ - Unix-friendly CLI
10
+ - Optional response caching
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @justram/pie
16
+ ```
17
+
18
+ ## Getting Started
19
+
20
+ ### Pick a model
21
+
22
+ `pie` uses the provider/model registry from `@mariozechner/pi-ai`. In the CLI, pass `--model provider/model`:
23
+
24
+ ```bash
25
+ cat input.txt | pie \
26
+ -s schema.json \
27
+ -p "Extract fields" \
28
+ --model anthropic/claude-sonnet-4-5
29
+ ```
30
+
31
+ ### Authentication (API keys and OAuth)
32
+
33
+ `pie` does not require Pi to be installed. It uses the OAuth helpers from `@mariozechner/pi-ai` and stores credentials in `~/.pi/agent/auth.json` (created automatically on first login).
34
+
35
+ Credential resolution order:
36
+
37
+ 1. `~/.pi/agent/auth.json` (API keys or OAuth tokens)
38
+ 2. Environment variables (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`)
39
+ 3. OAuth login (only for supported providers)
40
+
41
+ #### OAuth login
42
+
43
+ Run `--login` to authenticate with an OAuth-capable provider. The CLI prints a URL and prompts you to complete login in your browser.
44
+
45
+ ```bash
46
+ pie --login openai-codex
47
+ ```
48
+
49
+ Supported OAuth providers:
50
+ - `anthropic`
51
+ - `github-copilot`
52
+ - `google-gemini-cli`
53
+ - `google-antigravity`
54
+ - `openai-codex`
55
+
56
+ If you skip this step, `pie` will still prompt for OAuth the first time you run a command that requires it.
57
+
58
+ ```bash
59
+ cat input.txt | pie \
60
+ -s schema.json \
61
+ -p "Extract fields" \
62
+ --model github-copilot/gpt-4o
63
+ ```
64
+
65
+ #### API keys (recommended for examples)
66
+
67
+ Use environment variables:
68
+
69
+ ```bash
70
+ export ANTHROPIC_API_KEY=sk-ant-...
71
+ ```
72
+
73
+ Or add API keys to `~/.pi/agent/auth.json`:
74
+
75
+ ```json
76
+ {
77
+ "anthropic": { "type": "api_key", "key": "sk-ant-..." },
78
+ "openai": { "type": "api_key", "key": "sk-..." }
79
+ }
80
+ ```
81
+
82
+ Auth file entries take precedence over environment variables. To switch from OAuth to API keys, remove the OAuth entry for that provider and add an `api_key` entry.
83
+
84
+ #### Already using Pi?
85
+
86
+ `pie` shares the same auth store as Pi: `~/.pi/agent/auth.json`. If you have already run `/login` in Pi, `pie` will reuse those credentials automatically. You can also add API keys to the same file and both tools will pick them up.
87
+
88
+ #### Resetting credentials
89
+
90
+ Edit or delete `~/.pi/agent/auth.json` to remove a provider and force re-login.
91
+
92
+ ## SDK Usage
93
+
94
+ ```ts
95
+ import { extractSync, getModel, Type } from "@justram/pie";
96
+
97
+ const schema = Type.Object({
98
+ sentiment: Type.Union([
99
+ Type.Literal("positive"),
100
+ Type.Literal("negative"),
101
+ Type.Literal("neutral"),
102
+ ]),
103
+ confidence: Type.Number({ minimum: 0, maximum: 1 }),
104
+ });
105
+
106
+ const model = getModel("anthropic", "claude-sonnet-4-5");
107
+
108
+ const data = await extractSync("I love this product!", {
109
+ schema,
110
+ prompt: "Classify the sentiment.",
111
+ model,
112
+ });
113
+
114
+ console.log(data);
115
+ ```
116
+
117
+ ### Streaming events
118
+
119
+ ```ts
120
+ import { extract } from "@justram/pie";
121
+
122
+ const stream = extract("Example text", {
123
+ schema,
124
+ prompt: "Extract fields.",
125
+ model: getModel("openai", "gpt-4o"),
126
+ });
127
+
128
+ for await (const event of stream) {
129
+ if (event.type === "validation_error") {
130
+ console.error(event.layer, event.error);
131
+ }
132
+ }
133
+
134
+ const result = await stream.result();
135
+ ```
136
+
137
+ ### Caching
138
+
139
+ ```ts
140
+ import { createFileCache, warmCache } from "@justram/pie/cache";
141
+ import { getModel } from "@justram/pie";
142
+
143
+ const model = getModel("anthropic", "claude-sonnet-4-5");
144
+ const store = createFileCache({ directory: "./cache" });
145
+
146
+ await warmCache(["doc1", "doc2"], {
147
+ schema,
148
+ prompt: "Extract fields.",
149
+ model,
150
+ cache: { store },
151
+ });
152
+ ```
153
+
154
+ ### Validation examples
155
+
156
+ #### Shell validator
157
+
158
+ ```ts
159
+ const data = await extractSync(input, {
160
+ schema,
161
+ prompt: "Extract fields.",
162
+ model,
163
+ validateCommand: "jq -e '.score > 0.5'",
164
+ });
165
+ ```
166
+
167
+ ```bash
168
+ cat input.txt | pie -s schema.json -p "Extract" \
169
+ --validate "jq -e '.score > 0.5'"
170
+ ```
171
+
172
+ #### HTTP validator
173
+
174
+ ```ts
175
+ const data = await extractSync(input, {
176
+ schema,
177
+ prompt: "Extract fields.",
178
+ model,
179
+ validateUrl: "https://api.example.com/validate",
180
+ });
181
+ ```
182
+
183
+ ```bash
184
+ cat input.txt | pie -s schema.json -p "Extract" \
185
+ --validate-url "https://api.example.com/validate"
186
+ ```
187
+
188
+ ## CLI Usage
189
+
190
+ ```bash
191
+ pie --help
192
+
193
+ # Basic extraction
194
+ cat input.txt | pie \
195
+ -s schema.json \
196
+ -p "Extract fields" \
197
+ --model anthropic/claude-sonnet-4-5
198
+
199
+ # Stream partial JSON to stderr
200
+ cat input.txt | pie -s schema.json -p "Extract" --stream 2>progress.jsonl
201
+ ```
202
+
203
+ ### CLI Requirements
204
+
205
+ - A configured model credential (see [Authentication](#authentication-api-keys-and-oauth)).
206
+ - A JSON Schema (`--schema`) and prompt (`--prompt`/`--prompt-file`) unless you use `--config` or `--recipe`.
207
+
208
+ ## Recipes
209
+
210
+ Recipes live in `~/.pie/recipes` or `./.pie/recipes` and allow reusable setups.
211
+
212
+ ```bash
213
+ pie --list-recipes
214
+ pie --recipe support-triage --input ticket.txt
215
+ ```
216
+
217
+ ## Public API Surface
218
+
219
+ - The package root (`@justram/pie`) exposes the SDK surface from `src/index.ts` (extract, models, recipes, cache, errors, types).
220
+ - Cache helpers are also available via the `@justram/pie/cache` subpath.
221
+
222
+ ### CLI entry flow (contributors)
223
+
224
+ - `src/cli.ts` is the executable entry (shebang) and delegates to `src/main.ts`.
225
+ - `src/cli/index.ts` re-exports `runCli` for tests and internal callers.
226
+
227
+ ## Development
228
+
229
+ ```bash
230
+ npm run build
231
+ npm run test
232
+ ```
233
+
234
+ ## License
235
+
236
+ MIT
package/bin/pie ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runCli } from "../dist/cli.js";
4
+
5
+ async function main(): Promise<void> {
6
+ const exitCode = await runCli(process.argv.slice(2));
7
+ process.exitCode = exitCode;
8
+ }
9
+
10
+ void main().catch((error) => {
11
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
12
+ console.error(message);
13
+ process.exitCode = 1;
14
+ });
@@ -0,0 +1,4 @@
1
+ export { createFileCache } from "../core/cache/file.js";
2
+ export { createMemoryCache } from "../core/cache/memory.js";
3
+ export type { CacheEntry, CacheOptions, CacheStore } from "../core/cache/types.js";
4
+ export { warmCache } from "./warm.js";
@@ -0,0 +1,3 @@
1
+ export { createFileCache } from "../core/cache/file.js";
2
+ export { createMemoryCache } from "../core/cache/memory.js";
3
+ export { warmCache } from "./warm.js";
@@ -0,0 +1,3 @@
1
+ import type { Message } from "@mariozechner/pi-ai";
2
+ import type { ExtractOptions } from "../types.js";
3
+ export declare function warmCache<T>(inputs: Array<string | Message[]>, options: ExtractOptions<T>): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import { extractSync } from "../extract.js";
2
+ function normalizeCache(options) {
3
+ const raw = options.cache;
4
+ const hasFunctionValidators = Boolean(options.validate || options.validateAsync);
5
+ if (!raw) {
6
+ return hasFunctionValidators ? { revalidate: true } : true;
7
+ }
8
+ if (raw === true) {
9
+ return hasFunctionValidators ? { revalidate: true } : true;
10
+ }
11
+ const cache = { ...raw };
12
+ if (hasFunctionValidators && cache.revalidate === undefined) {
13
+ cache.revalidate = true;
14
+ }
15
+ return cache;
16
+ }
17
+ export async function warmCache(inputs, options) {
18
+ const cache = normalizeCache(options);
19
+ const warmedOptions = { ...options, cache };
20
+ for (const input of inputs) {
21
+ await extractSync(input, warmedOptions);
22
+ }
23
+ }
@@ -0,0 +1,30 @@
1
+ export interface CliArgs {
2
+ schema?: string;
3
+ prompt?: string;
4
+ promptFile?: string;
5
+ input?: string;
6
+ attachments: string[];
7
+ output?: string;
8
+ model?: string;
9
+ maxTurns?: number;
10
+ validateCommand?: string;
11
+ validateUrl?: string;
12
+ stream?: boolean;
13
+ verbose?: boolean;
14
+ quiet?: boolean;
15
+ jsonSchema?: boolean;
16
+ config?: string;
17
+ recipe?: string;
18
+ recipeConfig?: string;
19
+ recipeVars?: string;
20
+ listRecipes?: boolean;
21
+ login?: string;
22
+ help?: boolean;
23
+ version?: boolean;
24
+ }
25
+ export interface ParsedArgs {
26
+ args: CliArgs;
27
+ errors: string[];
28
+ }
29
+ export declare function parseArgs(argv: string[]): ParsedArgs;
30
+ export declare function renderHelp(): string;
@@ -0,0 +1,185 @@
1
+ export function parseArgs(argv) {
2
+ const args = {
3
+ attachments: [],
4
+ };
5
+ const errors = [];
6
+ for (let i = 0; i < argv.length; i++) {
7
+ const arg = argv[i];
8
+ const nextValue = (flag) => {
9
+ if (i + 1 >= argv.length) {
10
+ errors.push(`Missing value for ${flag}.`);
11
+ return undefined;
12
+ }
13
+ return argv[++i];
14
+ };
15
+ switch (arg) {
16
+ case "-s":
17
+ case "--schema": {
18
+ const value = nextValue(arg);
19
+ if (value)
20
+ args.schema = value;
21
+ break;
22
+ }
23
+ case "-p":
24
+ case "--prompt": {
25
+ const value = nextValue(arg);
26
+ if (value)
27
+ args.prompt = value;
28
+ break;
29
+ }
30
+ case "--prompt-file": {
31
+ const value = nextValue(arg);
32
+ if (value)
33
+ args.promptFile = value;
34
+ break;
35
+ }
36
+ case "-i":
37
+ case "--input": {
38
+ const value = nextValue(arg);
39
+ if (value)
40
+ args.input = value;
41
+ break;
42
+ }
43
+ case "-a":
44
+ case "--attach": {
45
+ const value = nextValue(arg);
46
+ if (value)
47
+ args.attachments.push(value);
48
+ break;
49
+ }
50
+ case "-o":
51
+ case "--output": {
52
+ const value = nextValue(arg);
53
+ if (value)
54
+ args.output = value;
55
+ break;
56
+ }
57
+ case "-m":
58
+ case "--model": {
59
+ const value = nextValue(arg);
60
+ if (value)
61
+ args.model = value;
62
+ break;
63
+ }
64
+ case "-t":
65
+ case "--max-turns": {
66
+ const value = nextValue(arg);
67
+ if (!value)
68
+ break;
69
+ const parsed = Number.parseInt(value, 10);
70
+ if (!Number.isFinite(parsed) || parsed <= 0) {
71
+ errors.push(`Invalid value for ${arg}: ${value}`);
72
+ }
73
+ else {
74
+ args.maxTurns = parsed;
75
+ }
76
+ break;
77
+ }
78
+ case "--validate": {
79
+ const value = nextValue(arg);
80
+ if (value)
81
+ args.validateCommand = value;
82
+ break;
83
+ }
84
+ case "--validate-url": {
85
+ const value = nextValue(arg);
86
+ if (value)
87
+ args.validateUrl = value;
88
+ break;
89
+ }
90
+ case "--stream":
91
+ args.stream = true;
92
+ break;
93
+ case "--verbose":
94
+ args.verbose = true;
95
+ break;
96
+ case "--quiet":
97
+ args.quiet = true;
98
+ break;
99
+ case "--json-schema":
100
+ args.jsonSchema = true;
101
+ break;
102
+ case "-c":
103
+ case "--config": {
104
+ const value = nextValue(arg);
105
+ if (value)
106
+ args.config = value;
107
+ break;
108
+ }
109
+ case "--recipe": {
110
+ const value = nextValue(arg);
111
+ if (value)
112
+ args.recipe = value;
113
+ break;
114
+ }
115
+ case "--recipe-config": {
116
+ const value = nextValue(arg);
117
+ if (value)
118
+ args.recipeConfig = value;
119
+ break;
120
+ }
121
+ case "--recipe-vars": {
122
+ const value = nextValue(arg);
123
+ if (value)
124
+ args.recipeVars = value;
125
+ break;
126
+ }
127
+ case "--list-recipes":
128
+ args.listRecipes = true;
129
+ break;
130
+ case "--login": {
131
+ const value = nextValue(arg);
132
+ if (value)
133
+ args.login = value;
134
+ break;
135
+ }
136
+ case "-h":
137
+ case "--help":
138
+ args.help = true;
139
+ break;
140
+ case "-v":
141
+ case "--version":
142
+ args.version = true;
143
+ break;
144
+ default:
145
+ if (arg.startsWith("-")) {
146
+ errors.push(`Unknown option: ${arg}`);
147
+ }
148
+ else {
149
+ errors.push(`Unexpected argument: ${arg}`);
150
+ }
151
+ }
152
+ }
153
+ return { args, errors };
154
+ }
155
+ export function renderHelp() {
156
+ return `pie - Structured extraction CLI
157
+
158
+ Usage:
159
+ pie [options]
160
+
161
+ Options:
162
+ -s, --schema <file|json> JSON Schema (required unless --config)
163
+ -p, --prompt <text> System prompt (or setup frontmatter + Jinja template)
164
+ --prompt-file <file> Load prompt or setup document from file
165
+ -i, --input <file> Input file (default: stdin)
166
+ -a, --attach <file> Attach file (repeatable) - text or image
167
+ -o, --output <file> Output file (default: stdout)
168
+ -m, --model <name> Model name (default: best available)
169
+ -t, --max-turns <n> Max turns in extraction loop (default: 3)
170
+ --validate <cmd> Shell validator command
171
+ --validate-url <url> HTTP validator endpoint
172
+ --stream Stream partial JSON to stderr
173
+ --verbose Show detailed progress
174
+ --quiet Suppress non-essential output
175
+ --json-schema Output the generated JSON Schema and exit
176
+ -c, --config <file> Load JS config for advanced extraction
177
+ --recipe <name> Use a named recipe from ~/.pie/recipes or ./.pie/recipes
178
+ --recipe-config <file> Use a recipe config file (default: setup.md)
179
+ --recipe-vars <json> JSON object for recipe template vars
180
+ --list-recipes List available recipes and exit
181
+ --login <provider> OAuth login for a provider (e.g. anthropic)
182
+ -h, --help Show help
183
+ -v, --version Show version
184
+ `;
185
+ }
@@ -0,0 +1,7 @@
1
+ import type { ImageContent } from "@mariozechner/pi-ai";
2
+ export interface AttachmentLoadResult {
3
+ textPrefix: string;
4
+ images: ImageContent[];
5
+ }
6
+ export declare function loadAttachments(paths: string[]): AttachmentLoadResult;
7
+ export declare function renderFileBlock(path: string, content: string, mimeType?: string): string;
@@ -0,0 +1,29 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { isImagePath, mimeTypeForImage, resizeImage } from "./image.js";
4
+ export function loadAttachments(paths) {
5
+ const images = [];
6
+ const blocks = [];
7
+ for (const inputPath of paths) {
8
+ const resolved = resolve(inputPath);
9
+ if (isImagePath(resolved)) {
10
+ const buffer = readFileSync(resolved);
11
+ const mimeType = mimeTypeForImage(resolved);
12
+ const base64 = buffer.toString("base64");
13
+ const resized = resizeImage({ type: "image", mimeType, data: base64 });
14
+ images.push(resized.image);
15
+ blocks.push(renderFileBlock(resolved, "[image attached]", resized.image.mimeType));
16
+ continue;
17
+ }
18
+ const content = readFileSync(resolved, "utf8");
19
+ blocks.push(renderFileBlock(resolved, content));
20
+ }
21
+ return {
22
+ textPrefix: blocks.join("\n\n"),
23
+ images,
24
+ };
25
+ }
26
+ export function renderFileBlock(path, content, mimeType) {
27
+ const typeAttr = mimeType ? ` type="${mimeType}"` : "";
28
+ return `<file name="${path}"${typeAttr}>\n${content}\n</file>`;
29
+ }
@@ -0,0 +1,22 @@
1
+ import type { Message, Model } from "@mariozechner/pi-ai";
2
+ import type { ExtractOptions } from "../core/types.js";
3
+ import type { CliArgs } from "./args.js";
4
+ import type { AttachmentLoadResult } from "./attachments.js";
5
+ export interface CliConfigContext {
6
+ args: CliArgs;
7
+ input: string;
8
+ inputText: string;
9
+ attachments: AttachmentLoadResult;
10
+ resolveModel: (value: string) => Model<any>;
11
+ resolveApiKeyForProvider: (provider: string) => Promise<string | undefined>;
12
+ }
13
+ export interface CliConfigResult<T> {
14
+ options: ExtractOptions<T>;
15
+ input?: string | Message[];
16
+ output?: string;
17
+ stream?: boolean;
18
+ verbose?: boolean;
19
+ quiet?: boolean;
20
+ }
21
+ export type CliConfig<T> = (context: CliConfigContext) => CliConfigResult<T> | Promise<CliConfigResult<T>>;
22
+ export declare function loadCliConfig(path: string): Promise<CliConfig<unknown>>;
@@ -0,0 +1,20 @@
1
+ import { resolve } from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ export async function loadCliConfig(path) {
4
+ const resolved = resolve(path);
5
+ const url = pathToFileURL(resolved).href;
6
+ let moduleExports;
7
+ try {
8
+ moduleExports = await import(url);
9
+ }
10
+ catch (error) {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ throw new Error(`Failed to load config file ${resolved}: ${message}`);
13
+ }
14
+ const candidate = moduleExports.default ??
15
+ moduleExports.config;
16
+ if (typeof candidate !== "function") {
17
+ throw new Error(`Config file ${resolved} must export a default function or a named export "config".`);
18
+ }
19
+ return candidate;
20
+ }
@@ -0,0 +1,17 @@
1
+ import type { ImageContent } from "@mariozechner/pi-ai";
2
+ export interface ImageResizeOptions {
3
+ maxWidth?: number;
4
+ maxHeight?: number;
5
+ jpegQuality?: number;
6
+ }
7
+ export interface ResizedImage {
8
+ image: ImageContent;
9
+ originalWidth: number;
10
+ originalHeight: number;
11
+ width: number;
12
+ height: number;
13
+ wasResized: boolean;
14
+ }
15
+ export declare function resizeImage(image: ImageContent, options?: ImageResizeOptions): ResizedImage;
16
+ export declare function isImagePath(path: string): boolean;
17
+ export declare function mimeTypeForImage(path: string): string;