@unscientificjszhai/howto 1.0.0-alpha.2 → 1.0.0-alpha.3

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.CN.md ADDED
@@ -0,0 +1,203 @@
1
+ # howto
2
+
3
+ _在终端中用 AI 快速找到可执行命令。_
4
+
5
+ ![npm version](https://img.shields.io/npm/v/@unscientificjszhai/howto/latest)
6
+
7
+ [English](README.md) | 简体中文
8
+
9
+ [功能](#功能) • [安装](#安装) • [使用](#使用) • [配置](#配置) • [开发](#开发) • [故障排查](#故障排查)
10
+
11
+ `howto` 是一个 TypeScript CLI工具,使用自然语言向它提问,它会给你候选命令供你选择。它不会直接运行 AI 输出:你需要在终端中选择候选命令,填写占位符(如有),检查最终命令,然后确认执行。
12
+
13
+ > [!IMPORTANT]
14
+ > `howto` 是命令生成助手,不是沙箱。执行前请检查每一条命令,尤其是会修改文件、安装包、变更权限或使用提权操作的命令。
15
+
16
+ ## 功能
17
+
18
+ - **自然语言生成命令** - 描述任务后获得简洁的 shell 命令候选项。
19
+ - **指定工具模式** - 使用 `howto use <command>` 要求候选项围绕某个 CLI 工具生成。
20
+ - **交互式确认流程** - 选择候选项、填写占位符,并在执行前确认最终命令。
21
+ - **非交互打印模式** - 使用 `--print` 输出候选命令,不进入 TTY 交互,也不执行命令。
22
+ - **支持 OpenAI 和 Gemini** - 可通过 CLI 参数、环境变量或 `~/.howto/config.json` 配置 provider。
23
+ - **本地优先校验** - 本地校验 AI JSON 输出、占位符引用、`use <command>` 候选项以及明显危险命令。
24
+
25
+ ## 安装
26
+
27
+ 全局安装 CLI 包:
28
+
29
+ ```bash
30
+ npm install -g @unscientificjszhai/howto
31
+ ```
32
+
33
+ 也可以从克隆的仓库中运行:
34
+
35
+ ```bash
36
+ npm install
37
+ npm run build
38
+ npm link
39
+ ```
40
+
41
+ 之后即可使用 `howto` 命令。
42
+
43
+ ## 快速开始
44
+
45
+ 初始化 AI provider 配置:
46
+
47
+ ```bash
48
+ howto --init
49
+ ```
50
+
51
+ 初始化程序会把用户级配置写入 `~/.howto/config.json`。
52
+
53
+ > [!NOTE]
54
+ > OpenAI API key 可以为空,以支持本地 OpenAI 兼容服务。Gemini 必须提供非空 API key。
55
+
56
+ 询问一个命令:
57
+
58
+ ```bash
59
+ howto 找到最近7天修改的文件 .
60
+ ```
61
+
62
+ 限制候选项必须使用某个工具:
63
+
64
+ ```bash
65
+ howto use git 看看上周的提交
66
+ ```
67
+
68
+ 不进入交互 UI,只打印候选命令:
69
+
70
+ ```bash
71
+ howto --print 列出最大的文件 /var/log
72
+ ```
73
+
74
+ ## 使用
75
+
76
+ ```text
77
+ howto [options] [use <command>] <question> [<argument>...]
78
+ howto --init
79
+ ```
80
+
81
+ 示例:
82
+
83
+ ```bash
84
+ howto 找到当前目录的package.json
85
+ howto use find 寻找特定文件名的文件 package.json
86
+ howto 解释这个命令 -- --force
87
+ howto --ai-provider openai --print 列出监听的端口
88
+ ```
89
+
90
+ 参数:
91
+
92
+ - `--init` - 启动交互式 provider 配置,并保存 `~/.howto/config.json`。
93
+ - `--print` - 打印已校验的命令候选项并退出,不执行命令。
94
+ - `--ai-provider <openai|gemini>` - 选择 AI provider。
95
+ - `--gemini-api-key <key>` - 提供 Gemini API key。
96
+ - `--gemini-model <model>` - 覆盖 Gemini 模型。
97
+ - `--openai-api-url <url>` - 使用自定义 OpenAI 兼容 base URL。
98
+ - `--openai-api-key <key>` - 提供 OpenAI API key。
99
+ - `--openai-model <model>` - 覆盖 OpenAI 模型。
100
+ - `--structured-output <true|false>` - 启用 SDK 级 schema 结构化输出;默认 `true`。
101
+
102
+ ### 参数解析
103
+
104
+ `question` 是一个 shell 参数。包含空格时需要加引号:
105
+
106
+ ```bash
107
+ howto "find recently changed files" /tmp
108
+ ```
109
+
110
+ `question` 后面的内容会作为 `argument[]` 传给 AI。如果参数以 `--` 开头,请使用 `--` 结束 option 解析:
111
+
112
+ ```bash
113
+ howto "explain this flag" -- --force
114
+ ```
115
+
116
+ ## 配置
117
+
118
+ 配置按以下优先级解析:
119
+
120
+ 1. CLI 参数
121
+ 2. 环境变量
122
+ 3. `~/.howto/config.json`
123
+ 4. 内置默认值
124
+
125
+ 对每个配置项,优先级中第一个已配置的来源生效:
126
+
127
+ - `--ai-provider` / `HOWTO_AI_PROVIDER` / `aiProvider` - `openai` 或 `gemini`;无默认值。
128
+ - `--gemini-api-key` / `HOWTO_GEMINI_API_KEY` / `geminiApiKey` - Gemini API key;Gemini 必填。
129
+ - `--gemini-model` / `HOWTO_GEMINI_MODEL` / `geminiModel` - Gemini 模型;默认 `gemini-3.1-flash-lite`。
130
+ - `--openai-api-url` / `HOWTO_OPENAI_API_URL` / `openaiApiUrl` - OpenAI 兼容 base URL;默认使用 OpenAI SDK 默认值。
131
+ - `--openai-api-key` / `HOWTO_OPENAI_API_KEY` / `openaiApiKey` - OpenAI API key;默认为空字符串以支持本地服务。
132
+ - `--openai-model` / `HOWTO_OPENAI_MODEL` / `openaiModel` - OpenAI 模型;默认 `gpt-5.4-mini`。
133
+ - `--structured-output` / `HOWTO_STRUCTURED_OUTPUT` / `structuredOutput` - 使用 provider schema 结构化输出;默认 `true`。
134
+
135
+ 示例:
136
+
137
+ ```bash
138
+ HOWTO_AI_PROVIDER=openai \
139
+ HOWTO_OPENAI_MODEL=gpt-5.4-mini \
140
+ howto --print "show current branch"
141
+ ```
142
+
143
+ ## 安全模型
144
+
145
+ `howto` 将 AI 输出视为不可信数据。在进入执行前,CLI 会检查:
146
+
147
+ - AI 响应是符合命令 schema 的有效 JSON;
148
+ - 响应包含一到三个候选项;
149
+ - 所有占位符使用 `{{name}}` 语法,并且声明与引用一致;
150
+ - `use <command>` 候选项在保守处理前缀后,明确以指定工具开头;
151
+ - 明显危险的命令需要输入 `EXECUTE` 才能继续执行。
152
+
153
+ 危险命令检测当前覆盖递归破坏性 `rm`、磁盘和文件系统操作、大范围递归权限变更、下载脚本后直接交给 shell 执行、高影响包管理器操作以及服务变更等高风险模式。
154
+
155
+ > [!WARNING]
156
+ > 未被标记为危险并不表示命令一定安全。本地检查只会对已知高风险模式增加确认步骤,并不能证明命令安全。
157
+
158
+ ## 开发
159
+
160
+ 本项目使用 TypeScript、React、Ink、OpenAI SDK、Gemini GenAI SDK 和 Node 内置测试运行器构建。
161
+
162
+ ```bash
163
+ npm install
164
+ npm run build
165
+ npm test
166
+ npm run lint
167
+ npm run format:check
168
+ ```
169
+
170
+ 常用路径:
171
+
172
+ - `src/index.tsx` - CLI 编排和执行流程。
173
+ - `src/cli.ts` - 参数解析。
174
+ - `src/config.ts` - 配置合并和 provider 校验。
175
+ - `src/prompt.ts` - prompt 和 AI 输出契约。
176
+ - `src/validation/` - AI 响应和命令工具校验。
177
+ - `src/safety/` - 危险命令规则。
178
+ - `src/ui/` - 基于 Ink 的终端 UI。
179
+ - `tests/unit/` - CLI、配置、校验、执行、UI 和安全逻辑的单元测试。
180
+
181
+ ## 故障排查
182
+
183
+ ### AI provider 未配置
184
+
185
+ 运行:
186
+
187
+ ```bash
188
+ howto --init
189
+ ```
190
+
191
+ `--print` 会有意跳过初始化流程。请先配置 provider,或传入对应 CLI 参数/环境变量。
192
+
193
+ ### 非交互终端错误
194
+
195
+ 默认模式需要交互式 TTY 来选择和确认命令。在脚本或 CI 中请使用 `--print`:
196
+
197
+ ```bash
198
+ howto --print "show disk usage"
199
+ ```
200
+
201
+ ### Gemini key 必填
202
+
203
+ Gemini 不能在没有 API key 的情况下运行。请设置 `HOWTO_GEMINI_API_KEY`,传入 `--gemini-api-key`,或重新运行 `howto --init`。
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # howto
2
+
3
+ _Use AI to quickly find commands within the terminal._
4
+
5
+ ![npm version](https://img.shields.io/npm/v/@unscientificjszhai/howto/latest)
6
+
7
+ English | [简体中文](README.CN.md)
8
+
9
+ [Features](#features) • [Install](#install) • [Usage](#usage) • [Configuration](#configuration) • [Development](#development) • [Troubleshooting](#troubleshooting)
10
+
11
+ `howto` is a TypeScript CLI that turns a natural-language question into up to three executable command candidates. It never runs AI output directly: you choose a command in the terminal, fill any placeholders, review the final command, and confirm before execution.
12
+
13
+ > [!IMPORTANT]
14
+ > `howto` is an assistant for generating shell commands, not a sandbox. Review every command before running it, especially commands that modify files, install packages, change permissions, or use elevated privileges.
15
+
16
+ ## Features
17
+
18
+ - **Natural language to commands** - ask for a task and get concise shell command candidates.
19
+ - **Tool-constrained mode** - use `howto use <command>` to require candidates around a specific CLI tool.
20
+ - **Interactive review flow** - select a candidate, resolve placeholders, and confirm the final command before execution.
21
+ - **Non-interactive print mode** - use `--print` to output candidates without TTY interaction or command execution.
22
+ - **OpenAI and Gemini support** - configure either provider from CLI flags, environment variables, or `~/.howto/config.json`.
23
+ - **Local validation first** - AI JSON output, placeholder references, `use <command>` candidates, and obvious dangerous commands are checked locally.
24
+
25
+ ## Install
26
+
27
+ Install the CLI package globally:
28
+
29
+ ```bash
30
+ npm install -g @unscientificjszhai/howto
31
+ ```
32
+
33
+ Or run it from a cloned repository:
34
+
35
+ ```bash
36
+ npm install
37
+ npm run build
38
+ npm link
39
+ ```
40
+
41
+ You can then call the executable as `howto`.
42
+
43
+ ## Getting Started
44
+
45
+ Initialize your AI provider configuration:
46
+
47
+ ```bash
48
+ howto --init
49
+ ```
50
+
51
+ The initializer writes a user-level config file at `~/.howto/config.json`.
52
+
53
+ > [!NOTE]
54
+ > OpenAI API keys may be empty for local OpenAI-compatible services. Gemini requires a non-empty API key.
55
+
56
+ Ask for a command:
57
+
58
+ ```bash
59
+ howto "find files modified in the last 7 days" .
60
+ ```
61
+
62
+ Limit candidates to a specific tool:
63
+
64
+ ```bash
65
+ howto use git "show commits from last week"
66
+ ```
67
+
68
+ Print command candidates without entering the interactive UI:
69
+
70
+ ```bash
71
+ howto --print "list the largest files" /var/log
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ ```text
77
+ howto [options] [use <command>] <question> [<argument>...]
78
+ howto --init
79
+ ```
80
+
81
+ Examples:
82
+
83
+ ```bash
84
+ howto "find package.json under the current directory"
85
+ howto use find "find a filename" package.json
86
+ howto "explain this option" -- --force
87
+ howto --ai-provider openai --print "show listening ports"
88
+ ```
89
+
90
+ Options:
91
+
92
+ - `--init` - start interactive provider setup and save `~/.howto/config.json`.
93
+ - `--print` - print validated command candidates and exit without executing.
94
+ - `--ai-provider <openai|gemini>` - select the AI provider.
95
+ - `--gemini-api-key <key>` - provide a Gemini API key.
96
+ - `--gemini-model <model>` - override the Gemini model.
97
+ - `--openai-api-url <url>` - use a custom OpenAI-compatible base URL.
98
+ - `--openai-api-key <key>` - provide an OpenAI API key.
99
+ - `--openai-model <model>` - override the OpenAI model.
100
+ - `--structured-output <true|false>` - enable SDK-level schema structured output; default `true`.
101
+
102
+ ### Argument Parsing
103
+
104
+ `question` is one shell argument. Quote it when it contains spaces:
105
+
106
+ ```bash
107
+ howto "find recently changed files" /tmp
108
+ ```
109
+
110
+ Everything after the question is passed to the AI as `argument[]`. Use `--` when an argument starts with `--`:
111
+
112
+ ```bash
113
+ howto "explain this flag" -- --force
114
+ ```
115
+
116
+ ## Configuration
117
+
118
+ Configuration is resolved in this order:
119
+
120
+ 1. CLI options
121
+ 2. Environment variables
122
+ 3. `~/.howto/config.json`
123
+ 4. Built-in defaults
124
+
125
+ For each setting, the first configured source in that order wins:
126
+
127
+ - `--ai-provider` / `HOWTO_AI_PROVIDER` / `aiProvider` - `openai` or `gemini`; no default.
128
+ - `--gemini-api-key` / `HOWTO_GEMINI_API_KEY` / `geminiApiKey` - Gemini API key; required for Gemini.
129
+ - `--gemini-model` / `HOWTO_GEMINI_MODEL` / `geminiModel` - Gemini model; default `gemini-3.1-flash-lite`.
130
+ - `--openai-api-url` / `HOWTO_OPENAI_API_URL` / `openaiApiUrl` - OpenAI-compatible base URL; defaults to the OpenAI SDK default.
131
+ - `--openai-api-key` / `HOWTO_OPENAI_API_KEY` / `openaiApiKey` - OpenAI API key; defaults to an empty string for local services.
132
+ - `--openai-model` / `HOWTO_OPENAI_MODEL` / `openaiModel` - OpenAI model; default `gpt-5.4-mini`.
133
+ - `--structured-output` / `HOWTO_STRUCTURED_OUTPUT` / `structuredOutput` - use provider schema structured output; default `true`.
134
+
135
+ Example:
136
+
137
+ ```bash
138
+ HOWTO_AI_PROVIDER=openai \
139
+ HOWTO_OPENAI_MODEL=gpt-5.4-mini \
140
+ howto --print "show current branch"
141
+ ```
142
+
143
+ ## Safety Model
144
+
145
+ `howto` treats AI output as untrusted data. Before anything reaches execution, the CLI checks that:
146
+
147
+ - the AI response is valid JSON matching the required command schema;
148
+ - the response contains between one and three candidates;
149
+ - all placeholders use `{{name}}` syntax and are declared consistently;
150
+ - `use <command>` candidates clearly start with the requested tool after conservative prefix handling;
151
+ - obvious dangerous patterns require typing `EXECUTE` before they can run.
152
+
153
+ Dangerous-command detection currently covers high-risk patterns such as recursive destructive `rm`, disk and filesystem operations, broad recursive permission changes, downloaded scripts piped into a shell, high-impact package manager operations, and service changes.
154
+
155
+ > [!WARNING]
156
+ > A command not flagged as dangerous is not guaranteed to be safe. The local checks add friction for known high-risk patterns; they do not prove command safety.
157
+
158
+ ## Development
159
+
160
+ This project is built with TypeScript, React, Ink, OpenAI SDK, Gemini GenAI SDK, and Node's built-in test runner.
161
+
162
+ ```bash
163
+ npm install
164
+ npm run build
165
+ npm test
166
+ npm run lint
167
+ npm run format:check
168
+ ```
169
+
170
+ Useful paths:
171
+
172
+ - `src/index.tsx` - CLI orchestration and execution flow.
173
+ - `src/cli.ts` - argument parsing.
174
+ - `src/config.ts` - config merging and provider validation.
175
+ - `src/prompt.ts` - prompt and AI output contract.
176
+ - `src/validation/` - AI response and command-tool validation.
177
+ - `src/safety/` - dangerous command rules.
178
+ - `src/ui/` - Ink-based terminal UI.
179
+ - `tests/unit/` - unit tests for CLI, config, validation, execution, UI, and safety logic.
180
+
181
+ ## Troubleshooting
182
+
183
+ ### AI provider is not configured
184
+
185
+ Run:
186
+
187
+ ```bash
188
+ howto --init
189
+ ```
190
+
191
+ For `--print`, initialization is intentionally skipped. Configure the provider first or pass the relevant CLI flags/environment variables.
192
+
193
+ ### Non-interactive terminal error
194
+
195
+ The default mode needs an interactive TTY for selection and confirmation. Use `--print` in scripts or CI:
196
+
197
+ ```bash
198
+ howto --print "show disk usage"
199
+ ```
200
+
201
+ ### Gemini key is required
202
+
203
+ Gemini cannot run without an API key. Set `HOWTO_GEMINI_API_KEY`, pass `--gemini-api-key`, or rerun `howto --init`.
@@ -0,0 +1,42 @@
1
+ export const COMMAND_GENERATION_SCHEMA = {
2
+ type: "object",
3
+ additionalProperties: false,
4
+ required: ["commands"],
5
+ properties: {
6
+ commands: {
7
+ type: "array",
8
+ items: {
9
+ type: "object",
10
+ additionalProperties: false,
11
+ required: ["title", "command", "description", "placeholders"],
12
+ properties: {
13
+ title: {
14
+ type: "string",
15
+ },
16
+ command: {
17
+ type: "string",
18
+ },
19
+ description: {
20
+ type: "string",
21
+ },
22
+ placeholders: {
23
+ type: "array",
24
+ items: {
25
+ type: "object",
26
+ additionalProperties: false,
27
+ required: ["name", "description"],
28
+ properties: {
29
+ name: {
30
+ type: "string",
31
+ },
32
+ description: {
33
+ type: "string",
34
+ },
35
+ },
36
+ },
37
+ },
38
+ },
39
+ },
40
+ },
41
+ },
42
+ };
package/dist/ai/gemini.js CHANGED
@@ -8,6 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { GoogleGenAI } from "@google/genai";
11
+ import { COMMAND_GENERATION_SCHEMA } from "./command-schema.js";
11
12
  import { AiProviderError } from "./errors.js";
12
13
  export class GeminiCommandProvider {
13
14
  constructor(config) {
@@ -17,14 +18,7 @@ export class GeminiCommandProvider {
17
18
  generateCommands(request) {
18
19
  return __awaiter(this, void 0, void 0, function* () {
19
20
  try {
20
- const response = yield this.client.models.generateContent({
21
- model: this.model,
22
- contents: request.userPrompt,
23
- config: {
24
- systemInstruction: request.systemPrompt,
25
- responseMimeType: "application/json",
26
- },
27
- });
21
+ const response = yield this.client.models.generateContent(buildGeminiGenerateContentRequest(this.model, request));
28
22
  const rawText = response.text;
29
23
  if (rawText === undefined || rawText.trim() === "") {
30
24
  throw new Error("provider returned an empty response");
@@ -37,3 +31,10 @@ export class GeminiCommandProvider {
37
31
  });
38
32
  }
39
33
  }
34
+ export function buildGeminiGenerateContentRequest(model, request) {
35
+ return {
36
+ model,
37
+ contents: request.userPrompt,
38
+ config: Object.assign({ systemInstruction: request.systemPrompt, responseMimeType: "application/json" }, (request.structuredOutput ? { responseJsonSchema: COMMAND_GENERATION_SCHEMA } : {})),
39
+ };
40
+ }
package/dist/ai/openai.js CHANGED
@@ -8,27 +8,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import OpenAI from "openai";
11
+ import { COMMAND_GENERATION_SCHEMA } from "./command-schema.js";
11
12
  import { AiProviderError } from "./errors.js";
12
13
  export class OpenAiCommandProvider {
13
14
  constructor(config) {
14
15
  this.model = config.model;
15
- this.client = new OpenAI({
16
- apiKey: config.apiKey,
17
- baseURL: config.baseUrl,
18
- });
16
+ this.client = new OpenAI(buildOpenAiClientOptions(config));
19
17
  }
20
18
  generateCommands(request) {
21
19
  return __awaiter(this, void 0, void 0, function* () {
22
20
  var _a, _b;
23
21
  try {
24
- const response = yield this.client.chat.completions.create({
25
- model: this.model,
26
- messages: [
27
- { role: "system", content: request.systemPrompt },
28
- { role: "user", content: request.userPrompt },
29
- ],
30
- response_format: { type: "json_object" },
31
- });
22
+ const response = yield this.client.chat.completions.create(buildOpenAiChatCompletionRequest(this.model, request));
32
23
  const rawText = (_b = (_a = response.choices[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content;
33
24
  if (rawText === undefined || rawText === null || rawText.trim() === "") {
34
25
  throw new Error("provider returned an empty response");
@@ -41,3 +32,38 @@ export class OpenAiCommandProvider {
41
32
  });
42
33
  }
43
34
  }
35
+ export function buildOpenAiClientOptions(config) {
36
+ if (config.apiKey.trim() !== "") {
37
+ return {
38
+ apiKey: config.apiKey,
39
+ baseURL: config.baseUrl,
40
+ };
41
+ }
42
+ return {
43
+ apiKey: "howto-empty-api-key",
44
+ baseURL: config.baseUrl,
45
+ defaultHeaders: {
46
+ Authorization: null,
47
+ },
48
+ };
49
+ }
50
+ export function buildOpenAiChatCompletionRequest(model, request) {
51
+ return {
52
+ model,
53
+ messages: [
54
+ { role: "system", content: request.systemPrompt },
55
+ { role: "user", content: request.userPrompt },
56
+ ],
57
+ response_format: request.structuredOutput
58
+ ? {
59
+ type: "json_schema",
60
+ json_schema: {
61
+ name: "command_generation",
62
+ description: "Shell command candidates generated for howto.",
63
+ schema: COMMAND_GENERATION_SCHEMA,
64
+ strict: true,
65
+ },
66
+ }
67
+ : { type: "json_object" },
68
+ };
69
+ }
package/dist/cli.js CHANGED
@@ -11,6 +11,7 @@ const VALUE_OPTIONS = new Set([
11
11
  "--openai-api-url",
12
12
  "--openai-api-key",
13
13
  "--openai-model",
14
+ "--structured-output",
14
15
  ]);
15
16
  const BOOLEAN_OPTIONS = new Set(["--print", "--init"]);
16
17
  export const USAGE = `Usage: howto [options] [use <command>] <question> [<argument>...]
@@ -25,6 +26,7 @@ Options:
25
26
  --openai-api-url <url>
26
27
  --openai-api-key <key>
27
28
  --openai-model <model>
29
+ --structured-output <true|false>
28
30
 
29
31
  Try: howto "list files changed today"`;
30
32
  export function parseCliArgs(argv) {
@@ -89,6 +91,9 @@ function assignOptionValue(options, optionName, value) {
89
91
  case "--openai-model":
90
92
  options.openaiModel = value;
91
93
  return;
94
+ case "--structured-output":
95
+ options.structuredOutput = value;
96
+ return;
92
97
  default:
93
98
  throw new CliParseError(`unsupported option: ${optionName}`);
94
99
  }
@@ -19,6 +19,7 @@ const CONFIG_FILE_FIELDS = new Set([
19
19
  "openaiApiUrl",
20
20
  "openaiApiKey",
21
21
  "openaiModel",
22
+ "structuredOutput",
22
23
  ]);
23
24
  export function getConfigFilePath(env = process.env) {
24
25
  var _a;
@@ -44,6 +45,13 @@ export function readUserConfigFile() {
44
45
  if (!CONFIG_FILE_FIELDS.has(key)) {
45
46
  continue;
46
47
  }
48
+ if (key === "structuredOutput") {
49
+ if (typeof value !== "string" && typeof value !== "boolean") {
50
+ throw new ConfigError(`config file field ${key} must be a boolean or string`);
51
+ }
52
+ config.structuredOutput = value;
53
+ continue;
54
+ }
47
55
  if (typeof value !== "string") {
48
56
  throw new ConfigError(`config file field ${key} must be a string`);
49
57
  }
package/dist/config.js CHANGED
@@ -28,6 +28,7 @@ export function loadConfig(options, env, fileConfig = {}) {
28
28
  model: (_c = pickConfigValue(options.openaiModel, env.HOWTO_OPENAI_MODEL, fileConfig.openaiModel)) !== null && _c !== void 0 ? _c : DEFAULT_OPENAI_MODEL,
29
29
  baseUrl: pickConfigValue(options.openaiApiUrl, env.HOWTO_OPENAI_API_URL, fileConfig.openaiApiUrl),
30
30
  },
31
+ structuredOutput: parseStructuredOutput(pickConfigValue(options.structuredOutput, env.HOWTO_STRUCTURED_OUTPUT, fileConfig.structuredOutput)),
31
32
  };
32
33
  if (config.aiProvider === "gemini" && isBlank(config.gemini.apiKey)) {
33
34
  throw new ConfigError("Gemini provider requires --gemini-api-key or HOWTO_GEMINI_API_KEY");
@@ -47,3 +48,18 @@ function parseAiProvider(value) {
47
48
  function isBlank(value) {
48
49
  return value === undefined || value.trim() === "";
49
50
  }
51
+ function parseStructuredOutput(value) {
52
+ if (value === undefined) {
53
+ return true;
54
+ }
55
+ if (typeof value === "boolean") {
56
+ return value;
57
+ }
58
+ if (value === "true") {
59
+ return true;
60
+ }
61
+ if (value === "false") {
62
+ return false;
63
+ }
64
+ throw new ConfigError("invalid structuredOutput value; expected true or false");
65
+ }
package/dist/index.js CHANGED
@@ -66,6 +66,7 @@ function run(argv) {
66
66
  question: parsedCli.question,
67
67
  arguments: parsedCli.arguments,
68
68
  useCommand: parsedCli.useCommand,
69
+ structuredOutput: config.structuredOutput,
69
70
  });
70
71
  const { systemPrompt, userPrompt } = buildCommandGenerationPrompt(promptRequest);
71
72
  const provider = createCommandProvider(config);