@oh-my-pi/pi-coding-agent 15.13.0 → 15.13.1
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/CHANGELOG.md +1656 -613
- package/dist/cli.js +12765 -12731
- package/dist/types/autolearn/managed-skills.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +2 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/flag-tables.d.ts +126 -0
- package/dist/types/cli/profile-alias.d.ts +29 -0
- package/dist/types/cli/profile-bootstrap.d.ts +55 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/model-roles.d.ts +3 -2
- package/dist/types/config/settings-schema.d.ts +2 -0
- package/dist/types/edit/file-snapshot-store.d.ts +14 -0
- package/dist/types/extensibility/extensions/runner.d.ts +11 -0
- package/dist/types/mcp/manager.d.ts +5 -1
- package/dist/types/mcp/oauth-credentials.d.ts +17 -0
- package/dist/types/mcp/oauth-flow.d.ts +41 -0
- package/dist/types/mcp/types.d.ts +2 -0
- package/dist/types/modes/components/background-tan-message.d.ts +9 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +9 -5
- package/dist/types/modes/interactive-mode.d.ts +4 -0
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/session/messages.d.ts +8 -0
- package/dist/types/session/session-manager.d.ts +6 -0
- package/dist/types/tools/builtin-names.d.ts +2 -0
- package/dist/types/tools/index.d.ts +3 -2
- package/dist/types/utils/external-editor.d.ts +11 -1
- package/package.json +12 -12
- package/src/autolearn/managed-skills.ts +3 -5
- package/src/capability/mcp.ts +2 -1
- package/src/cli/args.ts +61 -103
- package/src/cli/completion-gen.ts +2 -2
- package/src/cli/flag-tables.ts +270 -0
- package/src/cli/profile-alias.ts +338 -0
- package/src/cli/profile-bootstrap.ts +243 -0
- package/src/cli.ts +83 -16
- package/src/commands/launch.ts +7 -0
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-roles.ts +17 -4
- package/src/config/settings-schema.ts +2 -0
- package/src/discovery/builtin.ts +15 -9
- package/src/discovery/helpers.ts +25 -0
- package/src/discovery/mcp-json.ts +1 -0
- package/src/discovery/omp-extension-roots.ts +2 -2
- package/src/edit/file-snapshot-store.ts +43 -0
- package/src/eval/__tests__/agent-bridge.test.ts +3 -2
- package/src/eval/__tests__/helpers-local-roots.test.ts +1 -1
- package/src/eval/js/shared/runtime.ts +54 -0
- package/src/extensibility/extensions/runner.ts +25 -2
- package/src/goals/runtime.ts +4 -1
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/mcp/manager.ts +108 -71
- package/src/mcp/oauth-credentials.ts +104 -0
- package/src/mcp/oauth-flow.ts +67 -0
- package/src/mcp/types.ts +2 -0
- package/src/modes/components/agent-hub.ts +6 -0
- package/src/modes/components/background-tan-message.ts +36 -0
- package/src/modes/components/mcp-add-wizard.ts +17 -10
- package/src/modes/components/model-selector.ts +50 -6
- package/src/modes/components/tool-execution.ts +12 -0
- package/src/modes/controllers/input-controller.ts +21 -10
- package/src/modes/controllers/mcp-command-controller.ts +184 -112
- package/src/modes/controllers/tan-command-controller.ts +27 -11
- package/src/modes/interactive-mode.ts +6 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/ui-helpers.ts +6 -0
- package/src/prompts/bench.md +9 -4
- package/src/sdk.ts +6 -5
- package/src/session/agent-session.ts +30 -1
- package/src/session/messages.ts +9 -0
- package/src/session/session-manager.ts +7 -2
- package/src/tiny/text.ts +5 -1
- package/src/tools/ast-grep.ts +5 -1
- package/src/tools/builtin-names.ts +35 -0
- package/src/tools/index.ts +3 -2
- package/src/tools/read.ts +9 -0
- package/src/tools/search.ts +5 -1
- package/src/tts/tts-worker.ts +13 -5
- package/src/utils/external-editor.ts +15 -2
- package/src/utils/title-generator.ts +1 -1
- package/src/workspace-tree.ts +46 -6
- package/dist/types/utils/tools-manager.test.d.ts +0 -1
- package/src/utils/tools-manager.test.ts +0 -25
|
@@ -9,11 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { constants as fsConstants, type Stats } from "node:fs";
|
|
11
11
|
import * as fs from "node:fs/promises";
|
|
12
|
-
import * as os from "node:os";
|
|
13
12
|
import * as path from "node:path";
|
|
14
|
-
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
13
|
+
import { getAgentDir, isEnoent } from "@oh-my-pi/pi-utils";
|
|
15
14
|
import { YAML } from "bun";
|
|
16
|
-
import { SOURCE_PATHS } from "../discovery/helpers";
|
|
17
15
|
|
|
18
16
|
/** Provider id stamped on discovered managed skills (distinguishes them from authored). */
|
|
19
17
|
export const MANAGED_SKILLS_PROVIDER_ID = "omp-managed";
|
|
@@ -24,8 +22,8 @@ export const MAX_MANAGED_SKILL_BYTES = 64_000;
|
|
|
24
22
|
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
25
23
|
|
|
26
24
|
/** Resolve the isolated managed-skills directory (`~/.omp/agent/managed-skills`). */
|
|
27
|
-
export function getManagedSkillsDir(
|
|
28
|
-
return path.join(
|
|
25
|
+
export function getManagedSkillsDir(agentDir: string = getAgentDir()): string {
|
|
26
|
+
return path.join(agentDir, "managed-skills");
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
/**
|
package/src/capability/mcp.ts
CHANGED
|
@@ -38,13 +38,14 @@ export interface MCPServer {
|
|
|
38
38
|
clientSecret?: string;
|
|
39
39
|
resource?: string;
|
|
40
40
|
};
|
|
41
|
-
/** OAuth configuration (clientId, clientSecret, redirectUri, callbackPort, callbackPath) for servers requiring explicit client credentials */
|
|
41
|
+
/** OAuth configuration (clientId, clientSecret, redirectUri, callbackPort, callbackPath, prompt) for servers requiring explicit client credentials */
|
|
42
42
|
oauth?: {
|
|
43
43
|
clientId?: string;
|
|
44
44
|
clientSecret?: string;
|
|
45
45
|
redirectUri?: string;
|
|
46
46
|
callbackPort?: number;
|
|
47
47
|
callbackPath?: string;
|
|
48
|
+
prompt?: string;
|
|
48
49
|
};
|
|
49
50
|
/** Transport type */
|
|
50
51
|
transport?: "stdio" | "sse" | "http";
|
package/src/cli/args.ts
CHANGED
|
@@ -5,12 +5,22 @@ import { type Effort, THINKING_EFFORTS } from "@oh-my-pi/pi-catalog/effort";
|
|
|
5
5
|
import { APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { parseEffort } from "../thinking";
|
|
8
|
-
import {
|
|
8
|
+
import { BUILTIN_TOOL_NAMES } from "../tools/builtin-names";
|
|
9
|
+
import {
|
|
10
|
+
OPTIONAL_FLAGS,
|
|
11
|
+
OPTIONAL_VALUE_FLAGS,
|
|
12
|
+
type ParseDeps,
|
|
13
|
+
PROFILE_BOOTSTRAP_BOUNDARY_ARG,
|
|
14
|
+
STRING_SETTERS,
|
|
15
|
+
STRING_VALUE_FLAGS,
|
|
16
|
+
} from "./flag-tables";
|
|
9
17
|
|
|
10
18
|
export type Mode = "text" | "json" | "rpc" | "acp" | "rpc-ui";
|
|
11
19
|
|
|
12
20
|
export interface Args {
|
|
13
21
|
cwd?: string;
|
|
22
|
+
profile?: string;
|
|
23
|
+
alias?: string;
|
|
14
24
|
allowHome?: boolean;
|
|
15
25
|
provider?: string;
|
|
16
26
|
model?: string;
|
|
@@ -68,6 +78,19 @@ export interface Args {
|
|
|
68
78
|
unrecognizedFlags: string[];
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Runtime dependencies the data-driven setters need. Constructed once at
|
|
83
|
+
* module load and passed to every {@link STRING_SETTERS} call so the
|
|
84
|
+
* setter table itself can stay free of `@oh-my-pi/pi-utils` runtime imports
|
|
85
|
+
* (which would otherwise trip the profile bootstrap's env-init ordering).
|
|
86
|
+
*/
|
|
87
|
+
const PARSE_DEPS: ParseDeps = {
|
|
88
|
+
logger,
|
|
89
|
+
parseEffort,
|
|
90
|
+
builtinToolNames: BUILTIN_TOOL_NAMES,
|
|
91
|
+
thinkingEfforts: THINKING_EFFORTS,
|
|
92
|
+
};
|
|
93
|
+
|
|
71
94
|
export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { type: "boolean" | "string" }>): Args {
|
|
72
95
|
// Work on a copy: the `--option=value` handling below splices the value
|
|
73
96
|
// into the array, and callers reuse the same argv (the post-extension
|
|
@@ -81,19 +104,19 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
81
104
|
unrecognizedFlags: [],
|
|
82
105
|
};
|
|
83
106
|
|
|
107
|
+
// `--` ends option parsing (POSIX end-of-options). Everything after it is
|
|
108
|
+
// literal positional text, so flag-shaped messages are not parsed or rejected.
|
|
84
109
|
let sawSeparator = false;
|
|
85
110
|
for (let i = 0; i < args.length; i++) {
|
|
86
111
|
let arg = args[i];
|
|
87
|
-
const flagIndex = i;
|
|
88
|
-
|
|
89
|
-
// POSIX positional separator: once `--` lands, every remaining token is
|
|
90
|
-
// a positional regardless of shape. Without this, a flag-looking message
|
|
91
|
-
// (`omp -p -- --explain-this`) would be re-validated by the loop below
|
|
92
|
-
// and rejected by the unknown-flag guard (#2461 review).
|
|
93
112
|
if (sawSeparator) {
|
|
94
113
|
result.messages.push(arg);
|
|
95
114
|
continue;
|
|
96
115
|
}
|
|
116
|
+
if (arg === PROFILE_BOOTSTRAP_BOUNDARY_ARG) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const flagIndex = i;
|
|
97
120
|
|
|
98
121
|
// Support --flag=value syntax (e.g. --tools=ask,read). The value is
|
|
99
122
|
// spliced in as the next token so value-consuming flags pick it up via
|
|
@@ -120,112 +143,59 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
120
143
|
if (extFlag.type === "boolean") {
|
|
121
144
|
result.unknownFlags.set(flagName, true);
|
|
122
145
|
} else if (extFlag.type === "string" && i + 1 < args.length) {
|
|
123
|
-
// Consume the value in `--flag=value` form
|
|
124
|
-
//
|
|
125
|
-
//
|
|
146
|
+
// Consume the value in `--flag=value` form or when the next token is not
|
|
147
|
+
// flag-looking. A standalone `--` remains the end-of-options marker; use
|
|
148
|
+
// `--flag=--` when an extension needs a literal "--" string value.
|
|
126
149
|
if (equalsValueIndex !== -1 || !args[i + 1].startsWith("-")) {
|
|
127
150
|
result.unknownFlags.set(flagName, args[++i]);
|
|
128
151
|
}
|
|
129
152
|
}
|
|
153
|
+
} else if (STRING_VALUE_FLAGS.has(arg)) {
|
|
154
|
+
// Built-in string flags consume the next token even when it is flag-looking
|
|
155
|
+
// (`--system-prompt --profile foo` ⇒ the prompt is the literal "--profile").
|
|
156
|
+
// The one token they must never absorb is the profile bootstrap's internal
|
|
157
|
+
// boundary sentinel: an extension-shadowable built-in like `--plan` (parsed
|
|
158
|
+
// here only when its boolean extension is NOT loaded) would otherwise swallow
|
|
159
|
+
// the marker as its value and drop the user's trailing message.
|
|
160
|
+
if (i + 1 < args.length && args[i + 1] !== PROFILE_BOOTSTRAP_BOUNDARY_ARG) {
|
|
161
|
+
STRING_SETTERS[arg](result, args[++i], PARSE_DEPS);
|
|
162
|
+
}
|
|
163
|
+
} else if (OPTIONAL_VALUE_FLAGS.has(arg)) {
|
|
164
|
+
const config = OPTIONAL_FLAGS[arg];
|
|
165
|
+
const next = args[i + 1];
|
|
166
|
+
const consume =
|
|
167
|
+
next !== undefined && !next.startsWith("-") && !(config.rejectEmpty === true && next.length === 0);
|
|
168
|
+
config.set(result, consume ? args[++i] : undefined);
|
|
130
169
|
} else if (arg === "--help" || arg === "-h") {
|
|
131
170
|
result.help = true;
|
|
132
171
|
} else if (arg === "--version" || arg === "-v") {
|
|
133
172
|
result.version = true;
|
|
134
173
|
} else if (arg === "--allow-home") {
|
|
135
174
|
result.allowHome = true;
|
|
136
|
-
} else if (arg === "--
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
result.
|
|
140
|
-
} else if (arg
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
175
|
+
} else if (arg === "--profile" && i + 1 < args.length) {
|
|
176
|
+
// Normally stripped by `extractProfileFlags` before parseArgs sees it;
|
|
177
|
+
// kept here as a fallback for direct parseArgs callers.
|
|
178
|
+
result.profile = args[++i];
|
|
179
|
+
} else if (arg.startsWith("--profile=")) {
|
|
180
|
+
result.profile = arg.slice("--profile=".length);
|
|
181
|
+
} else if (arg === "--alias" && i + 1 < args.length) {
|
|
182
|
+
result.alias = args[++i];
|
|
183
|
+
} else if (arg.startsWith("--alias=")) {
|
|
184
|
+
result.alias = arg.slice("--alias=".length);
|
|
145
185
|
} else if (arg === "--continue" || arg === "-c") {
|
|
146
186
|
result.continue = true;
|
|
147
|
-
} else if (arg === "--resume" || arg === "-r" || arg === "--session") {
|
|
148
|
-
const next = args[i + 1];
|
|
149
|
-
if (next && !next.startsWith("-")) {
|
|
150
|
-
result.resume = args[++i];
|
|
151
|
-
} else {
|
|
152
|
-
result.resume = true;
|
|
153
|
-
}
|
|
154
|
-
} else if (arg === "--fork" && i + 1 < args.length) {
|
|
155
|
-
result.fork = args[++i];
|
|
156
|
-
} else if (arg === "--provider" && i + 1 < args.length) {
|
|
157
|
-
result.provider = args[++i];
|
|
158
|
-
} else if (arg === "--model" && i + 1 < args.length) {
|
|
159
|
-
result.model = args[++i];
|
|
160
|
-
} else if (arg === "--smol" && i + 1 < args.length) {
|
|
161
|
-
result.smol = args[++i];
|
|
162
|
-
} else if (arg === "--slow" && i + 1 < args.length) {
|
|
163
|
-
result.slow = args[++i];
|
|
164
|
-
} else if (arg === "--plan" && i + 1 < args.length) {
|
|
165
|
-
result.plan = args[++i];
|
|
166
|
-
} else if (arg === "--api-key" && i + 1 < args.length) {
|
|
167
|
-
result.apiKey = args[++i];
|
|
168
|
-
} else if (arg === "--system-prompt" && i + 1 < args.length) {
|
|
169
|
-
result.systemPrompt = args[++i];
|
|
170
|
-
} else if (arg === "--append-system-prompt" && i + 1 < args.length) {
|
|
171
|
-
result.appendSystemPrompt = args[++i];
|
|
172
|
-
} else if (arg === "--provider-session-id" && i + 1 < args.length) {
|
|
173
|
-
result.providerSessionId = args[++i];
|
|
174
187
|
} else if (arg === "--no-session") {
|
|
175
188
|
result.noSession = true;
|
|
176
|
-
} else if (arg === "--session-dir" && i + 1 < args.length) {
|
|
177
|
-
result.sessionDir = args[++i];
|
|
178
|
-
} else if (arg === "--models" && i + 1 < args.length) {
|
|
179
|
-
result.models = args[++i].split(",").map(s => s.trim());
|
|
180
189
|
} else if (arg === "--no-tools") {
|
|
181
190
|
result.noTools = true;
|
|
182
191
|
} else if (arg === "--no-lsp") {
|
|
183
192
|
result.noLsp = true;
|
|
184
193
|
} else if (arg === "--no-pty") {
|
|
185
194
|
result.noPty = true;
|
|
186
|
-
} else if (arg === "--tools" && i + 1 < args.length) {
|
|
187
|
-
const toolNames = args[++i]
|
|
188
|
-
.split(",")
|
|
189
|
-
.map(s => s.trim().toLowerCase())
|
|
190
|
-
.filter(Boolean);
|
|
191
|
-
const validTools: string[] = [];
|
|
192
|
-
for (const name of toolNames) {
|
|
193
|
-
if (name in BUILTIN_TOOLS) {
|
|
194
|
-
validTools.push(name);
|
|
195
|
-
} else {
|
|
196
|
-
logger.warn("Unknown tool passed to --tools", {
|
|
197
|
-
tool: name,
|
|
198
|
-
validTools: Object.keys(BUILTIN_TOOLS),
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
result.tools = validTools;
|
|
203
|
-
} else if (arg === "--thinking" && i + 1 < args.length) {
|
|
204
|
-
const rawThinking = args[++i];
|
|
205
|
-
const thinking = parseEffort(rawThinking);
|
|
206
|
-
if (thinking !== undefined) {
|
|
207
|
-
result.thinking = thinking;
|
|
208
|
-
} else {
|
|
209
|
-
logger.warn("Invalid thinking level passed to --thinking", {
|
|
210
|
-
level: rawThinking,
|
|
211
|
-
validThinkingLevels: THINKING_EFFORTS,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
195
|
} else if (arg === "--hide-thinking") {
|
|
215
196
|
result.hideThinking = true;
|
|
216
197
|
} else if (arg === "--print" || arg === "-p") {
|
|
217
198
|
result.print = true;
|
|
218
|
-
} else if (arg === "--export" && i + 1 < args.length) {
|
|
219
|
-
result.export = args[++i];
|
|
220
|
-
} else if (arg === "--hook" && i + 1 < args.length) {
|
|
221
|
-
result.hooks = result.hooks ?? [];
|
|
222
|
-
result.hooks.push(args[++i]);
|
|
223
|
-
} else if ((arg === "--extension" || arg === "-e") && i + 1 < args.length) {
|
|
224
|
-
result.extensions = result.extensions ?? [];
|
|
225
|
-
result.extensions.push(args[++i]);
|
|
226
|
-
} else if (arg === "--plugin-dir" && i + 1 < args.length) {
|
|
227
|
-
result.pluginDirs = result.pluginDirs ?? [];
|
|
228
|
-
result.pluginDirs.push(args[++i]);
|
|
229
199
|
} else if (arg === "--no-extensions") {
|
|
230
200
|
result.noExtensions = true;
|
|
231
201
|
} else if (arg === "--no-skills") {
|
|
@@ -236,19 +206,6 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
236
206
|
result.noTitle = true;
|
|
237
207
|
} else if (arg === "--auto-approve" || arg === "--yolo") {
|
|
238
208
|
result.autoApprove = true;
|
|
239
|
-
} else if (arg === "--approval-mode" && i + 1 < args.length) {
|
|
240
|
-
const mode = args[++i];
|
|
241
|
-
if (mode === "always-ask" || mode === "write" || mode === "yolo") {
|
|
242
|
-
result.approvalMode = mode;
|
|
243
|
-
} else {
|
|
244
|
-
logger.warn("Invalid value passed to --approval-mode", {
|
|
245
|
-
value: mode,
|
|
246
|
-
validValues: ["always-ask", "write", "yolo"],
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
} else if (arg === "--skills" && i + 1 < args.length) {
|
|
250
|
-
// Comma-separated glob patterns for skill filtering
|
|
251
|
-
result.skills = args[++i].split(",").map(s => s.trim());
|
|
252
209
|
} else if (arg.startsWith("@")) {
|
|
253
210
|
result.fileArgs.push(arg.slice(1)); // Remove @ prefix
|
|
254
211
|
} else if (!arg.startsWith("-") || arg === "-") {
|
|
@@ -344,13 +301,14 @@ export function getExtraHelpText(): string {
|
|
|
344
301
|
ANTHROPIC_SEARCH_BASE_URL - Anthropic web search base URL (override; pairs with ANTHROPIC_SEARCH_API_KEY)
|
|
345
302
|
|
|
346
303
|
${chalk.dim("# Configuration")}
|
|
304
|
+
OMP_PROFILE - Named profile for isolated agent state (same as --profile)
|
|
305
|
+
Use \`omp --profile <name> --alias <command>\` to create a shell shortcut for a profile
|
|
347
306
|
PI_CODING_AGENT_DIR - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
|
|
348
307
|
PI_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
|
|
349
308
|
PI_SMOL_MODEL - Override smol/fast model (see --smol)
|
|
350
309
|
PI_SLOW_MODEL - Override slow/reasoning model (see --slow)
|
|
351
310
|
PI_PLAN_MODEL - Override planning model (see --plan)
|
|
352
311
|
PI_NO_PTY - Disable PTY-based interactive bash execution
|
|
353
|
-
|
|
354
312
|
For complete environment variable reference, see:
|
|
355
313
|
${chalk.dim("docs/environment-variables.md")}
|
|
356
314
|
${chalk.bold("Available Tools (default-enabled unless noted):")}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* knob and is keyed by flag name so it stays stable as flags are added.
|
|
16
16
|
*/
|
|
17
17
|
import type { ArgDescriptor, CliConfig, CommandCtor, FlagDescriptor } from "@oh-my-pi/pi-utils/cli";
|
|
18
|
-
import {
|
|
18
|
+
import { BUILTIN_TOOL_NAMES } from "../tools/builtin-names";
|
|
19
19
|
|
|
20
20
|
export type Shell = "bash" | "zsh" | "fish";
|
|
21
21
|
|
|
@@ -77,7 +77,7 @@ function flagValue(name: string, desc: FlagDescriptor): ValueSource {
|
|
|
77
77
|
if (MODEL_FLAGS[name]) return { kind: "models", multiple: false };
|
|
78
78
|
if (name === "models") return { kind: "models", multiple: true };
|
|
79
79
|
if (SESSION_FLAGS[name]) return { kind: "sessions" };
|
|
80
|
-
if (name === "tools") return { kind: "list", values:
|
|
80
|
+
if (name === "tools") return { kind: "list", values: BUILTIN_TOOL_NAMES };
|
|
81
81
|
if (DIR_FLAGS[name]) return { kind: "dir" };
|
|
82
82
|
if (desc.kind === "integer") return { kind: "value" };
|
|
83
83
|
return { kind: "file" };
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for argv flag classification, shared by:
|
|
3
|
+
* - `parseArgs` in `./args.ts` (the launch-time CLI parser)
|
|
4
|
+
* - `extractProfileFlags` in `./profile-bootstrap.ts` (the early
|
|
5
|
+
* `--profile` / `--alias` pre-parser)
|
|
6
|
+
*
|
|
7
|
+
* `parseArgs` dispatches string-valued flags by looking up their setter in
|
|
8
|
+
* {@link STRING_SETTERS}. Optional-value flags use {@link OPTIONAL_FLAGS} so
|
|
9
|
+
* per-flag quirks (currently empty-string rejection for `--resume`) live here
|
|
10
|
+
* instead of being hard-coded in the dispatch loop.
|
|
11
|
+
*
|
|
12
|
+
* The bootstrap doesn't dispatch — it only needs to know which flags consume
|
|
13
|
+
* a value — so it consults {@link STRING_VALUE_FLAGS} and
|
|
14
|
+
* {@link OPTIONAL_VALUE_FLAGS}, both derived from `Object.keys(...)` on the
|
|
15
|
+
* setter/config records below.
|
|
16
|
+
*
|
|
17
|
+
* The deliberate consequence: a string-valued flag exists in this CLI surface
|
|
18
|
+
* iff it has an entry here. Adding a new string-valued flag means adding a
|
|
19
|
+
* setter/config entry in this file; both `args.ts` and the bootstrap pick it
|
|
20
|
+
* up automatically, so the two cannot drift out of sync.
|
|
21
|
+
*
|
|
22
|
+
* IMPORT RULE: this module MUST NOT import any runtime value from
|
|
23
|
+
* `@oh-my-pi/pi-utils` (or anything that transitively does). That package's
|
|
24
|
+
* `env.ts` eagerly loads `.env` files from `getAgentDir()` during module
|
|
25
|
+
* initialization, which would race the profile bootstrap. Type-only imports
|
|
26
|
+
* are erased at runtime and are therefore safe.
|
|
27
|
+
*
|
|
28
|
+
* If a setter needs runtime dependencies (logging, validators, lookup
|
|
29
|
+
* tables), they're passed in through {@link ParseDeps} and `args.ts` wires the
|
|
30
|
+
* real implementations at the dispatch site.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { Effort } from "@oh-my-pi/pi-ai";
|
|
34
|
+
import type { Args } from "./args";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Runtime dependencies injected into setters that need to validate input or
|
|
38
|
+
* warn about bad values. `args.ts` constructs one object at module load and
|
|
39
|
+
* passes it to each {@link STRING_SETTERS} call.
|
|
40
|
+
*
|
|
41
|
+
* Keeping these out of the setter closures means this module stays free of
|
|
42
|
+
* runtime imports from `@oh-my-pi/pi-utils`, which is the whole reason it can
|
|
43
|
+
* be safely imported by `profile-bootstrap.ts` before `setProfile` runs.
|
|
44
|
+
*/
|
|
45
|
+
export interface ParseDeps {
|
|
46
|
+
logger: { warn: (message: string, meta?: Record<string, unknown>) => void };
|
|
47
|
+
parseEffort: (value: string | null | undefined) => Effort | undefined;
|
|
48
|
+
builtinToolNames: readonly string[];
|
|
49
|
+
thinkingEfforts: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type StringSetter = (result: Args, value: string, deps: ParseDeps) => void;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Setter for a flag that may or may not consume the next argv token.
|
|
56
|
+
* Receives `undefined` for the bare form (`--resume` with no value, etc.).
|
|
57
|
+
*/
|
|
58
|
+
export type OptionalSetter = (result: Args, value: string | undefined) => void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Per-flag optional-value consumption policy.
|
|
62
|
+
*
|
|
63
|
+
* Every optional flag always rejects tokens that start with `-` — that shared
|
|
64
|
+
* rule lives in the dispatch site. These booleans capture the *additional*
|
|
65
|
+
* per-flag quirks:
|
|
66
|
+
*
|
|
67
|
+
* - `rejectEmpty`: treat `""` like “no value provided”. Needed for
|
|
68
|
+
* `--resume` / `-r` / `--session`. Without it, an empty string
|
|
69
|
+
* gets consumed as the session prefix and downstream resolution can match
|
|
70
|
+
* every session.
|
|
71
|
+
*/
|
|
72
|
+
export interface OptionalFlagConfig {
|
|
73
|
+
set: OptionalSetter;
|
|
74
|
+
rejectEmpty?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Shared setters for flags that alias the same field.
|
|
78
|
+
const setExtension: StringSetter = (result, value) => {
|
|
79
|
+
result.extensions = result.extensions ?? [];
|
|
80
|
+
result.extensions.push(value);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const setResume: OptionalSetter = (result, value) => {
|
|
84
|
+
result.resume = value !== undefined ? value : true;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Setters for flags with string values. Most built-ins consume the next argv
|
|
89
|
+
* token even when it starts with `-`; flags listed in
|
|
90
|
+
* {@link EXTENSION_SHADOWABLE_STRING_FLAGS} use extension-style consumption so
|
|
91
|
+
* a registered boolean extension can shadow them before profile bootstrap.
|
|
92
|
+
*/
|
|
93
|
+
export const STRING_SETTERS: Record<string, StringSetter> = {
|
|
94
|
+
"--cwd": (result, value) => {
|
|
95
|
+
result.cwd = value;
|
|
96
|
+
},
|
|
97
|
+
"--config": (result, value) => {
|
|
98
|
+
result.config = [...(result.config ?? []), value];
|
|
99
|
+
},
|
|
100
|
+
"--mode": (result, value) => {
|
|
101
|
+
if (value === "text" || value === "json" || value === "rpc" || value === "acp" || value === "rpc-ui") {
|
|
102
|
+
result.mode = value;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"--fork": (result, value) => {
|
|
106
|
+
result.fork = value;
|
|
107
|
+
},
|
|
108
|
+
"--provider": (result, value) => {
|
|
109
|
+
result.provider = value;
|
|
110
|
+
},
|
|
111
|
+
"--model": (result, value) => {
|
|
112
|
+
result.model = value;
|
|
113
|
+
},
|
|
114
|
+
"--smol": (result, value) => {
|
|
115
|
+
result.smol = value;
|
|
116
|
+
},
|
|
117
|
+
"--slow": (result, value) => {
|
|
118
|
+
result.slow = value;
|
|
119
|
+
},
|
|
120
|
+
"--plan": (result, value) => {
|
|
121
|
+
result.plan = value;
|
|
122
|
+
},
|
|
123
|
+
"--api-key": (result, value) => {
|
|
124
|
+
result.apiKey = value;
|
|
125
|
+
},
|
|
126
|
+
"--system-prompt": (result, value) => {
|
|
127
|
+
result.systemPrompt = value;
|
|
128
|
+
},
|
|
129
|
+
"--append-system-prompt": (result, value) => {
|
|
130
|
+
result.appendSystemPrompt = value;
|
|
131
|
+
},
|
|
132
|
+
"--provider-session-id": (result, value) => {
|
|
133
|
+
result.providerSessionId = value;
|
|
134
|
+
},
|
|
135
|
+
"--session-dir": (result, value) => {
|
|
136
|
+
result.sessionDir = value;
|
|
137
|
+
},
|
|
138
|
+
"--models": (result, value) => {
|
|
139
|
+
result.models = value.split(",").map(s => s.trim());
|
|
140
|
+
},
|
|
141
|
+
"--tools": (result, value, deps) => {
|
|
142
|
+
const names = value
|
|
143
|
+
.split(",")
|
|
144
|
+
.map(s => s.trim().toLowerCase())
|
|
145
|
+
.filter(Boolean);
|
|
146
|
+
const valid: string[] = [];
|
|
147
|
+
for (const name of names) {
|
|
148
|
+
if (deps.builtinToolNames.includes(name)) {
|
|
149
|
+
valid.push(name);
|
|
150
|
+
} else {
|
|
151
|
+
deps.logger.warn("Unknown tool passed to --tools", {
|
|
152
|
+
tool: name,
|
|
153
|
+
validTools: deps.builtinToolNames,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
result.tools = valid;
|
|
158
|
+
},
|
|
159
|
+
"--thinking": (result, value, deps) => {
|
|
160
|
+
const thinking = deps.parseEffort(value);
|
|
161
|
+
if (thinking !== undefined) {
|
|
162
|
+
result.thinking = thinking;
|
|
163
|
+
} else {
|
|
164
|
+
deps.logger.warn("Invalid thinking level passed to --thinking", {
|
|
165
|
+
level: value,
|
|
166
|
+
validThinkingLevels: deps.thinkingEfforts,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"--export": (result, value) => {
|
|
171
|
+
result.export = value;
|
|
172
|
+
},
|
|
173
|
+
"--hook": (result, value) => {
|
|
174
|
+
result.hooks = result.hooks ?? [];
|
|
175
|
+
result.hooks.push(value);
|
|
176
|
+
},
|
|
177
|
+
"--extension": setExtension,
|
|
178
|
+
"-e": setExtension,
|
|
179
|
+
"--plugin-dir": (result, value) => {
|
|
180
|
+
result.pluginDirs = result.pluginDirs ?? [];
|
|
181
|
+
result.pluginDirs.push(value);
|
|
182
|
+
},
|
|
183
|
+
"--skills": (result, value) => {
|
|
184
|
+
result.skills = value.split(",").map(s => s.trim());
|
|
185
|
+
},
|
|
186
|
+
"--approval-mode": (result, value, deps) => {
|
|
187
|
+
if (value === "always-ask" || value === "write" || value === "yolo") {
|
|
188
|
+
result.approvalMode = value;
|
|
189
|
+
} else {
|
|
190
|
+
deps.logger.warn("Invalid value passed to --approval-mode", {
|
|
191
|
+
value,
|
|
192
|
+
validValues: ["always-ask", "write", "yolo"],
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Optional-value flags. Setters receive `undefined` for the bare form.
|
|
200
|
+
*
|
|
201
|
+
* The dispatch in `args.ts` applies the shared "doesn't start with `-`"
|
|
202
|
+
* check for every flag, then consults the per-flag booleans below for the
|
|
203
|
+
* remaining quirks.
|
|
204
|
+
*/
|
|
205
|
+
export const OPTIONAL_FLAGS: Record<string, OptionalFlagConfig> = {
|
|
206
|
+
"--resume": { set: setResume, rejectEmpty: true },
|
|
207
|
+
"-r": { set: setResume, rejectEmpty: true },
|
|
208
|
+
"--session": { set: setResume, rejectEmpty: true },
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Derived from {@link STRING_SETTERS}. A flag is in this set if and only if
|
|
213
|
+
* it has a setter — by construction, drift between "the bootstrap thinks
|
|
214
|
+
* this flag accepts a value" and "the launch parser can set one" is
|
|
215
|
+
* structurally impossible.
|
|
216
|
+
*/
|
|
217
|
+
export const STRING_VALUE_FLAGS: ReadonlySet<string> = new Set(Object.keys(STRING_SETTERS));
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Built-in string flags known to be shadowed by bundled/common boolean
|
|
221
|
+
* extensions before extension metadata is available. They still accept a
|
|
222
|
+
* value-like successor for the built-in form (`--plan opus`), but a
|
|
223
|
+
* flag-looking successor remains a fresh flag (`--plan --profile work`).
|
|
224
|
+
*/
|
|
225
|
+
export const EXTENSION_SHADOWABLE_STRING_FLAGS: ReadonlySet<string> = new Set(["--plan"]);
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Derived from {@link OPTIONAL_FLAGS}. Same single-source contract as
|
|
229
|
+
* {@link STRING_VALUE_FLAGS}.
|
|
230
|
+
*/
|
|
231
|
+
export const OPTIONAL_VALUE_FLAGS: ReadonlySet<string> = new Set(Object.keys(OPTIONAL_FLAGS));
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Internal marker inserted by the profile bootstrap when removing `--profile`
|
|
235
|
+
* or `--alias` would otherwise make the following value-like token become the
|
|
236
|
+
* value of a preceding optional/extension flag. `parseArgs` ignores it, but its
|
|
237
|
+
* flag-looking shape preserves argv boundaries during the second parse.
|
|
238
|
+
*/
|
|
239
|
+
export const PROFILE_BOOTSTRAP_BOUNDARY_ARG = "--omp-profile-boundary";
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Long-form launch flags that take NO value (booleans). The bootstrap pre-parser
|
|
243
|
+
* needs this to tell a known value-less flag (whose successor is a fresh
|
|
244
|
+
* argument — `omp --print --profile work` still selects a profile) apart from an
|
|
245
|
+
* UNKNOWN long option that might be an extension string flag consuming the next
|
|
246
|
+
* token as its value (so the bootstrap must not steal that token as a global
|
|
247
|
+
* `--profile`/`--alias`). MUST mirror the value-less flag arms of `parseArgs`
|
|
248
|
+
* in `./args.ts`: adding a new boolean launch flag there means adding it here,
|
|
249
|
+
* or `--<newflag> --profile X` stops selecting a profile. Short aliases
|
|
250
|
+
* (`-h`/`-v`/`-c`/`-p`) are intentionally omitted — the protection rule only
|
|
251
|
+
* fires for `--`-prefixed tokens.
|
|
252
|
+
*/
|
|
253
|
+
export const VALUELESS_FLAGS: ReadonlySet<string> = new Set([
|
|
254
|
+
"--help",
|
|
255
|
+
"--version",
|
|
256
|
+
"--allow-home",
|
|
257
|
+
"--continue",
|
|
258
|
+
"--no-session",
|
|
259
|
+
"--no-tools",
|
|
260
|
+
"--no-lsp",
|
|
261
|
+
"--no-pty",
|
|
262
|
+
"--hide-thinking",
|
|
263
|
+
"--print",
|
|
264
|
+
"--no-extensions",
|
|
265
|
+
"--no-skills",
|
|
266
|
+
"--no-rules",
|
|
267
|
+
"--no-title",
|
|
268
|
+
"--auto-approve",
|
|
269
|
+
"--yolo",
|
|
270
|
+
]);
|