@oh-my-pi/pi-coding-agent 17.2.11 → 17.2.12
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 +13 -0
- package/dist/{CHANGELOG-d9xenpn9.md → CHANGELOG-k9ghy5sn.md} +13 -0
- package/dist/cli.js +10795 -10791
- package/dist/types/advisor/runtime.d.ts +1 -1
- package/dist/types/config/custom-models.d.ts +31 -0
- package/dist/types/config/model-config-values.d.ts +19 -0
- package/dist/types/config/model-patch.d.ts +93 -0
- package/dist/types/config/model-provider-discovery.d.ts +51 -0
- package/dist/types/config/model-registry.d.ts +5 -52
- package/dist/types/config/settings.d.ts +5 -3
- package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
- package/dist/types/discovery/agent-plugin-format.d.ts +6 -13
- package/dist/types/discovery/contained-path.d.ts +2 -3
- package/dist/types/eval/executor-base.d.ts +8 -2
- package/dist/types/eval/kernel-session-registry.d.ts +60 -0
- package/dist/types/export/share.d.ts +1 -1
- package/dist/types/lsp/diagnostics.d.ts +96 -0
- package/dist/types/lsp/index.d.ts +7 -128
- package/dist/types/lsp/servers.d.ts +72 -0
- package/dist/types/lsp/tool.d.ts +42 -0
- package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
- package/dist/types/lsp/writethrough.d.ts +33 -0
- package/dist/types/mcp/transports/header-policy.d.ts +2 -1
- package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
- package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
- package/dist/types/modes/theme/color.d.ts +19 -0
- package/dist/types/modes/theme/loader.d.ts +19 -0
- package/dist/types/modes/theme/schema.d.ts +175 -0
- package/dist/types/modes/theme/symbols.d.ts +28 -0
- package/dist/types/modes/theme/theme-class.d.ts +244 -0
- package/dist/types/modes/theme/theme.d.ts +9 -280
- package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
- package/dist/types/registry/agent-registry.d.ts +1 -3
- package/dist/types/secrets/index.d.ts +3 -1
- package/dist/types/secrets/message-transform.d.ts +40 -0
- package/dist/types/secrets/obfuscator.d.ts +0 -108
- package/dist/types/secrets/placeholder-scan.d.ts +95 -0
- package/dist/types/secrets/placeholder.d.ts +94 -0
- package/dist/types/secrets/replacement.d.ts +82 -0
- package/dist/types/session/agent-session-types.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/session-handoff.d.ts +3 -3
- package/dist/types/session/session-manager.d.ts +32 -0
- package/dist/types/session/session-provider-boundary.d.ts +1 -1
- package/dist/types/session/turn-recovery.d.ts +2 -2
- package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
- package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
- package/dist/types/slash-commands/builtin-control.d.ts +2 -0
- package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
- package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
- package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/slash-commands/builtin-session.d.ts +2 -0
- package/dist/types/tools/gh-common.d.ts +69 -0
- package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
- package/dist/types/tools/gh-pr-diff.d.ts +102 -0
- package/dist/types/tools/gh-run-watch.d.ts +78 -0
- package/dist/types/tools/gh-search.d.ts +61 -0
- package/dist/types/tools/gh-types.d.ts +340 -0
- package/dist/types/tools/gh-view.d.ts +88 -0
- package/dist/types/tools/gh.d.ts +4 -201
- package/dist/types/tools/read-archive.d.ts +16 -0
- package/dist/types/tools/read-format.d.ts +104 -0
- package/dist/types/tools/read-path-resolution.d.ts +19 -0
- package/dist/types/tools/read-pdf-images.d.ts +12 -0
- package/dist/types/tools/read-renderer.d.ts +24 -0
- package/dist/types/tools/read-selector.d.ts +27 -0
- package/dist/types/tools/read-sqlite.d.ts +16 -0
- package/dist/types/tools/read-summary.d.ts +19 -0
- package/dist/types/tools/read.d.ts +1 -23
- package/dist/types/tools/shell-tokenize.d.ts +2 -1
- package/dist/types/utils/changelog.d.ts +0 -5
- package/package.json +13 -13
- package/src/advisor/runtime.ts +12 -7
- package/src/config/custom-models.ts +188 -0
- package/src/config/model-config-values.ts +128 -0
- package/src/config/model-patch.ts +252 -0
- package/src/config/model-provider-discovery.ts +132 -0
- package/src/config/model-registry.ts +64 -704
- package/src/config/settings.ts +7 -3
- package/src/debug/raw-sse-buffer.ts +20 -0
- package/src/discovery/agent-plugin-format.ts +7 -7
- package/src/discovery/contained-path.ts +2 -5
- package/src/edit/hashline/diff.ts +5 -1
- package/src/eval/executor-base.ts +39 -6
- package/src/eval/jl/executor.ts +82 -371
- package/src/eval/kernel-session-registry.ts +398 -0
- package/src/eval/py/executor.ts +53 -261
- package/src/eval/rb/executor.ts +36 -279
- package/src/export/share.ts +2 -1
- package/src/lsp/diagnostics.ts +516 -0
- package/src/lsp/index.ts +20 -2819
- package/src/lsp/servers.ts +296 -0
- package/src/lsp/tool.ts +1352 -0
- package/src/lsp/workspace-diagnostics.ts +170 -0
- package/src/lsp/writethrough.ts +561 -0
- package/src/mcp/transports/header-policy.ts +1 -1
- package/src/modes/components/agent-hub-projection.ts +2 -2
- package/src/modes/components/agent-hub-renderer.ts +3 -7
- package/src/modes/controllers/command-controller.ts +4 -1
- package/src/modes/theme/color.ts +133 -0
- package/src/modes/theme/loader.ts +178 -0
- package/src/modes/theme/schema.ts +263 -0
- package/src/modes/theme/symbols.ts +1000 -0
- package/src/modes/theme/theme-class.ts +611 -0
- package/src/modes/theme/theme.ts +27 -2453
- package/src/modes/theme/tui-adapters.ts +279 -0
- package/src/registry/agent-registry.ts +2 -2
- package/src/secrets/index.ts +6 -12
- package/src/secrets/message-transform.ts +287 -0
- package/src/secrets/obfuscator.ts +39 -1303
- package/src/secrets/placeholder-scan.ts +506 -0
- package/src/secrets/placeholder.ts +309 -0
- package/src/secrets/replacement.ts +216 -0
- package/src/session/agent-session-types.ts +6 -0
- package/src/session/agent-session.ts +113 -7
- package/src/session/indexed-session-storage.ts +7 -2
- package/src/session/session-advisors.ts +32 -34
- package/src/session/session-handoff.ts +39 -20
- package/src/session/session-manager.ts +67 -3
- package/src/session/session-provider-boundary.ts +3 -6
- package/src/session/turn-recovery.ts +21 -12
- package/src/slash-commands/builtin-collaboration.ts +504 -0
- package/src/slash-commands/builtin-completions.ts +291 -0
- package/src/slash-commands/builtin-control.ts +78 -0
- package/src/slash-commands/builtin-lifecycle.ts +506 -0
- package/src/slash-commands/builtin-marketplace.ts +567 -0
- package/src/slash-commands/builtin-modes.ts +518 -0
- package/src/slash-commands/builtin-registry.ts +21 -3000
- package/src/slash-commands/builtin-session.ts +592 -0
- package/src/task/executor.ts +17 -14
- package/src/tools/gh-common.ts +288 -0
- package/src/tools/gh-pr-checkout.ts +679 -0
- package/src/tools/gh-pr-diff.ts +473 -0
- package/src/tools/gh-run-watch.ts +1015 -0
- package/src/tools/gh-search.ts +500 -0
- package/src/tools/gh-types.ts +379 -0
- package/src/tools/gh-view.ts +612 -0
- package/src/tools/gh.ts +109 -3821
- package/src/tools/read-archive.ts +209 -0
- package/src/tools/read-format.ts +593 -0
- package/src/tools/read-path-resolution.ts +36 -0
- package/src/tools/read-pdf-images.ts +250 -0
- package/src/tools/read-renderer.ts +289 -0
- package/src/tools/read-selector.ts +84 -0
- package/src/tools/read-sqlite.ts +215 -0
- package/src/tools/read-summary.ts +199 -0
- package/src/tools/read.ts +77 -1820
- package/src/tools/shell-tokenize.ts +1 -1
- package/src/utils/changelog.ts +1 -1
- package/src/utils/title-generator.ts +3 -1
- package/src/web/search/providers/zai.ts +12 -3
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { getMCPConfigPath, getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { readMCPConfigFile } from "../mcp/config-writer";
|
|
7
|
+
import { collectMcpServerNames } from "../modes/controllers/mcp-command-controller";
|
|
8
|
+
import { expandTilde } from "../tools/path-utils";
|
|
9
|
+
import type { SubcommandDef, TuiSlashCommandRuntime } from "./types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build getArgumentCompletions from declarative subcommand definitions.
|
|
13
|
+
* Returns subcommand names filtered by prefix in the dropdown.
|
|
14
|
+
*/
|
|
15
|
+
export function buildArgumentCompletions(subcommands: SubcommandDef[]): (prefix: string) => AutocompleteItem[] | null {
|
|
16
|
+
return (argumentPrefix: string) => {
|
|
17
|
+
if (argumentPrefix.includes(" ")) return null; // past the subcommand
|
|
18
|
+
const lower = argumentPrefix.toLowerCase();
|
|
19
|
+
const matches = subcommands
|
|
20
|
+
.filter(s => s.name.startsWith(lower))
|
|
21
|
+
.map(s => ({
|
|
22
|
+
value: `${s.name} `,
|
|
23
|
+
label: s.name,
|
|
24
|
+
description: s.description,
|
|
25
|
+
hint: s.usage,
|
|
26
|
+
}));
|
|
27
|
+
return matches.length > 0 ? matches : null;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** /mcp subcommands whose argument is a server name (per their `usage: "<name>..."`). */
|
|
32
|
+
const MCP_SERVER_NAME_SUBCOMMANDS: Readonly<Record<string, true>> = {
|
|
33
|
+
enable: true,
|
|
34
|
+
disable: true,
|
|
35
|
+
test: true,
|
|
36
|
+
remove: true,
|
|
37
|
+
reconnect: true,
|
|
38
|
+
reauth: true,
|
|
39
|
+
unauth: true,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Subcommands that accept names found only in `userConfig.disabledServers`. */
|
|
43
|
+
const MCP_DISABLED_ONLY_ELIGIBLE_SUBCOMMANDS: Readonly<Record<string, true>> = {
|
|
44
|
+
enable: true,
|
|
45
|
+
disable: true,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Subcommands that accept configured servers whose `enabled` flag is false.
|
|
50
|
+
* `unauth` can clear persisted credentials without connecting; test,
|
|
51
|
+
* reconnect, and reauth explicitly require an enabled server.
|
|
52
|
+
*/
|
|
53
|
+
const MCP_DISABLED_CONFIG_ELIGIBLE_SUBCOMMANDS: Readonly<Record<string, true>> = {
|
|
54
|
+
enable: true,
|
|
55
|
+
disable: true,
|
|
56
|
+
unauth: true,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build getArgumentCompletions for /mcp. Delegates to the generic
|
|
61
|
+
* declarative subcommand completer while the subcommand name itself is
|
|
62
|
+
* still being typed, then switches to MCP server-name completion (sourced
|
|
63
|
+
* from {@link collectMcpServerNames}) once a recognized server-name
|
|
64
|
+
* subcommand (enable/disable/test/remove/reconnect/reauth/unauth) is
|
|
65
|
+
* followed by a space. `remove` gets its own scope-aware completions (see
|
|
66
|
+
* {@link buildMcpRemoveCompletions}) since — unlike the others —
|
|
67
|
+
* it only ever succeeds against a config-file entry. Subcommands with a
|
|
68
|
+
* different argument shape (add, smithery-search, ...) get no argument
|
|
69
|
+
* completion.
|
|
70
|
+
*/
|
|
71
|
+
export function buildMcpArgumentCompletions(
|
|
72
|
+
subcommands: SubcommandDef[],
|
|
73
|
+
runtime: TuiSlashCommandRuntime,
|
|
74
|
+
): (argumentPrefix: string) => Promise<AutocompleteItem[] | null> {
|
|
75
|
+
const genericCompletions = buildArgumentCompletions(subcommands);
|
|
76
|
+
return async (argumentPrefix: string) => {
|
|
77
|
+
const spaceIndex = argumentPrefix.indexOf(" ");
|
|
78
|
+
if (spaceIndex === -1) return genericCompletions(argumentPrefix);
|
|
79
|
+
|
|
80
|
+
const rawSubcommand = argumentPrefix.slice(0, spaceIndex);
|
|
81
|
+
const lowerSubcommand = rawSubcommand.toLowerCase();
|
|
82
|
+
if (MCP_SERVER_NAME_SUBCOMMANDS[lowerSubcommand] !== true) return null;
|
|
83
|
+
const namePrefix = argumentPrefix.slice(spaceIndex + 1).toLowerCase();
|
|
84
|
+
if (lowerSubcommand === "remove") {
|
|
85
|
+
return await buildMcpRemoveCompletions(rawSubcommand, namePrefix);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let serverNames: string[];
|
|
89
|
+
try {
|
|
90
|
+
serverNames = await collectMcpServerNames(
|
|
91
|
+
runtime.ctx,
|
|
92
|
+
undefined,
|
|
93
|
+
MCP_DISABLED_ONLY_ELIGIBLE_SUBCOMMANDS[lowerSubcommand] === true,
|
|
94
|
+
MCP_DISABLED_CONFIG_ELIGIBLE_SUBCOMMANDS[lowerSubcommand] === true,
|
|
95
|
+
);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
logger.warn("MCP server-name autocomplete failed to read config", { error });
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const matches: AutocompleteItem[] = serverNames
|
|
101
|
+
.filter(name => name.toLowerCase().startsWith(namePrefix))
|
|
102
|
+
.map(name => ({ value: `${rawSubcommand} ${name} `, label: name }));
|
|
103
|
+
return matches.length > 0 ? matches : null;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Build `/mcp remove <name>` completions. Unlike the other server-name
|
|
109
|
+
* subcommands, `#handleRemove` only ever succeeds against a config-file
|
|
110
|
+
* `mcpServers` entry in the target scope (project by default, user with an
|
|
111
|
+
* explicit `--scope user`) — a purely runtime-discovered server has no
|
|
112
|
+
* config entry to remove and always fails with `Server "<name>" not found
|
|
113
|
+
* in <scope> config.`. Completions are therefore restricted to config-file
|
|
114
|
+
* names, and a name that exists only in the user config is completed with
|
|
115
|
+
* `--scope user` appended so the inserted command is directly executable.
|
|
116
|
+
*/
|
|
117
|
+
async function buildMcpRemoveCompletions(
|
|
118
|
+
rawSubcommand: string,
|
|
119
|
+
namePrefix: string,
|
|
120
|
+
): Promise<AutocompleteItem[] | null> {
|
|
121
|
+
const cwd = getProjectDir();
|
|
122
|
+
let projectNames: string[];
|
|
123
|
+
let userNames: string[];
|
|
124
|
+
try {
|
|
125
|
+
const [projectConfig, userConfig] = await Promise.all([
|
|
126
|
+
readMCPConfigFile(getMCPConfigPath("project", cwd)),
|
|
127
|
+
readMCPConfigFile(getMCPConfigPath("user", cwd)),
|
|
128
|
+
]);
|
|
129
|
+
projectNames = Object.keys(projectConfig.mcpServers ?? {});
|
|
130
|
+
userNames = Object.keys(userConfig.mcpServers ?? {});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logger.warn("MCP remove autocomplete failed to read config", { error });
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const projectNameSet = new Set(projectNames);
|
|
137
|
+
const allNames = new Set([...projectNames, ...userNames]);
|
|
138
|
+
const matches: AutocompleteItem[] = [...allNames]
|
|
139
|
+
.filter(name => name.toLowerCase().startsWith(namePrefix))
|
|
140
|
+
.map(name =>
|
|
141
|
+
projectNameSet.has(name)
|
|
142
|
+
? { value: `${rawSubcommand} ${name} `, label: name }
|
|
143
|
+
: { value: `${rawSubcommand} ${name} --scope user `, label: `${name} (user)` },
|
|
144
|
+
)
|
|
145
|
+
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
|
|
146
|
+
return matches.length > 0 ? matches : null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Build getInlineHint from declarative subcommand definitions.
|
|
151
|
+
* Shows remaining completion + usage as dim ghost text after cursor.
|
|
152
|
+
*/
|
|
153
|
+
export function buildSubcommandInlineHint(subcommands: SubcommandDef[]): (argumentText: string) => string | null {
|
|
154
|
+
return (argumentText: string) => {
|
|
155
|
+
const trimmed = argumentText.trimStart();
|
|
156
|
+
const spaceIndex = trimmed.indexOf(" ");
|
|
157
|
+
|
|
158
|
+
if (spaceIndex === -1) {
|
|
159
|
+
// Still typing subcommand name — show remaining chars + usage
|
|
160
|
+
const prefix = trimmed.toLowerCase();
|
|
161
|
+
if (prefix.length === 0) return null;
|
|
162
|
+
const match = subcommands.find(s => s.name.startsWith(prefix));
|
|
163
|
+
if (!match) return null;
|
|
164
|
+
const remaining = match.name.slice(prefix.length);
|
|
165
|
+
return remaining + (match.usage ? ` ${match.usage}` : "");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Subcommand typed — show remaining usage params
|
|
169
|
+
const subName = trimmed.slice(0, spaceIndex).toLowerCase();
|
|
170
|
+
const afterSub = trimmed.slice(spaceIndex + 1);
|
|
171
|
+
const sub = subcommands.find(s => s.name === subName);
|
|
172
|
+
if (!sub?.usage) return null;
|
|
173
|
+
|
|
174
|
+
if (afterSub.length > 0) {
|
|
175
|
+
const usageParts = sub.usage.split(" ");
|
|
176
|
+
const inputParts = afterSub.trim().split(/\s+/);
|
|
177
|
+
const remaining = usageParts.slice(inputParts.length);
|
|
178
|
+
return remaining.length > 0 ? remaining.join(" ") : null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return sub.usage;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Build getInlineHint for commands with a simple static hint string.
|
|
187
|
+
* Shows the hint only when no arguments have been typed yet.
|
|
188
|
+
*/
|
|
189
|
+
export function buildStaticInlineHint(hint: string): (argumentText: string) => string | null {
|
|
190
|
+
return (argumentText: string) => (argumentText.trim().length === 0 ? hint : null);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Build getArgumentCompletions that suggests directories relative to the
|
|
195
|
+
* current project directory. Used by /move so users can Tab-complete the
|
|
196
|
+
* destination directory.
|
|
197
|
+
*/
|
|
198
|
+
export function buildDirectoryArgumentCompletions(): (prefix: string) => Promise<AutocompleteItem[] | null> {
|
|
199
|
+
return async (argumentPrefix: string) => {
|
|
200
|
+
const prefix = argumentPrefix.trim();
|
|
201
|
+
|
|
202
|
+
const cwd = getProjectDir();
|
|
203
|
+
const expandedPrefix = expandTilde(prefix);
|
|
204
|
+
const isAbsolute = path.isAbsolute(expandedPrefix);
|
|
205
|
+
|
|
206
|
+
let searchDir: string;
|
|
207
|
+
let searchPrefix: string;
|
|
208
|
+
if (
|
|
209
|
+
prefix === "" ||
|
|
210
|
+
prefix === "." ||
|
|
211
|
+
prefix === "./" ||
|
|
212
|
+
prefix === ".." ||
|
|
213
|
+
prefix === "../" ||
|
|
214
|
+
prefix === "~" ||
|
|
215
|
+
prefix === "~/" ||
|
|
216
|
+
prefix === "/"
|
|
217
|
+
) {
|
|
218
|
+
searchDir = isAbsolute ? expandedPrefix : path.join(cwd, expandedPrefix);
|
|
219
|
+
searchPrefix = "";
|
|
220
|
+
} else if (expandedPrefix.endsWith("/")) {
|
|
221
|
+
searchDir = isAbsolute ? expandedPrefix : path.join(cwd, expandedPrefix);
|
|
222
|
+
searchPrefix = "";
|
|
223
|
+
} else {
|
|
224
|
+
const dir = path.dirname(expandedPrefix);
|
|
225
|
+
searchDir = isAbsolute ? dir : path.join(cwd, dir);
|
|
226
|
+
searchPrefix = path.basename(expandedPrefix);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const entries = await fs.readdir(searchDir, { withFileTypes: true });
|
|
231
|
+
const suggestions: AutocompleteItem[] = [];
|
|
232
|
+
for (const entry of entries) {
|
|
233
|
+
if (!entry.name.toLowerCase().startsWith(searchPrefix.toLowerCase())) continue;
|
|
234
|
+
if (entry.name === ".git") continue;
|
|
235
|
+
|
|
236
|
+
let isDirectory = entry.isDirectory();
|
|
237
|
+
if (!isDirectory && entry.isSymbolicLink()) {
|
|
238
|
+
try {
|
|
239
|
+
isDirectory = (await fs.stat(path.join(searchDir, entry.name))).isDirectory();
|
|
240
|
+
} catch {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!isDirectory) continue;
|
|
245
|
+
|
|
246
|
+
const absoluteValue = path.join(searchDir, entry.name);
|
|
247
|
+
const displayValue = buildDirectoryCompletionDisplayValue(prefix, absoluteValue, cwd);
|
|
248
|
+
suggestions.push({ value: displayValue, label: `${entry.name}/` });
|
|
249
|
+
}
|
|
250
|
+
suggestions.sort((a, b) => a.label.localeCompare(b.label));
|
|
251
|
+
return suggestions.length > 0 ? suggestions : null;
|
|
252
|
+
} catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function buildDirectoryCompletionDisplayValue(prefix: string, absoluteValue: string, cwd: string): string {
|
|
258
|
+
// Preserve the user's prefix style where possible, but always return a
|
|
259
|
+
// value that /move can resolve (absolute or relative) without escaping.
|
|
260
|
+
const normalized = path.normalize(absoluteValue);
|
|
261
|
+
|
|
262
|
+
if (prefix.startsWith("~/")) {
|
|
263
|
+
const home = os.homedir();
|
|
264
|
+
const homeRelative = path.relative(home, normalized);
|
|
265
|
+
return `~/${homeRelative.replaceAll("\\", "/")}/`;
|
|
266
|
+
}
|
|
267
|
+
if (prefix === "~") {
|
|
268
|
+
const home = os.homedir();
|
|
269
|
+
const homeRelative = path.relative(home, normalized);
|
|
270
|
+
return `~/${homeRelative.replaceAll("\\", "/")}/`;
|
|
271
|
+
}
|
|
272
|
+
if (prefix.startsWith("/")) {
|
|
273
|
+
return `${normalized.replaceAll("\\", "/")}/`;
|
|
274
|
+
}
|
|
275
|
+
if (prefix.startsWith("./")) {
|
|
276
|
+
const relative = path.relative(cwd, normalized);
|
|
277
|
+
return `./${relative.replaceAll("\\", "/")}/`;
|
|
278
|
+
}
|
|
279
|
+
if (prefix.startsWith("../")) {
|
|
280
|
+
const relative = path.relative(cwd, normalized);
|
|
281
|
+
return `${relative.replaceAll("\\", "/")}/`;
|
|
282
|
+
}
|
|
283
|
+
if (prefix === "..") {
|
|
284
|
+
const relative = path.relative(cwd, normalized);
|
|
285
|
+
return `${relative.replaceAll("\\", "/")}/`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Default: relative to cwd.
|
|
289
|
+
const relative = path.relative(cwd, normalized);
|
|
290
|
+
return `${relative.replaceAll("\\", "/")}/`;
|
|
291
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { runPauseScreen } from "../modes/components/pause-screen";
|
|
2
|
+
import { shutdownHandlerTui } from "./builtin-lifecycle";
|
|
3
|
+
import { commandConsumed, errorMessage, usage } from "./helpers/parse";
|
|
4
|
+
import type { SlashCommandSpec } from "./types";
|
|
5
|
+
|
|
6
|
+
export const BUILTIN_CONTROL_SLASH_COMMANDS: ReadonlyArray<SlashCommandSpec> = [
|
|
7
|
+
{
|
|
8
|
+
name: "force",
|
|
9
|
+
description: "Force next turn to use a specific tool",
|
|
10
|
+
aliases: ["force:"],
|
|
11
|
+
inlineHint: "<tool-name> [prompt]",
|
|
12
|
+
allowArgs: true,
|
|
13
|
+
getTuiAutocompleteDescription: runtime => {
|
|
14
|
+
const count = runtime.ctx.session.getActiveToolNames().length;
|
|
15
|
+
return count === 0 ? "Force: no active tools" : `Force: ${count} active tools`;
|
|
16
|
+
},
|
|
17
|
+
handle: async (command, runtime) => {
|
|
18
|
+
const spaceIdx = command.args.indexOf(" ");
|
|
19
|
+
const toolName = spaceIdx === -1 ? command.args : command.args.slice(0, spaceIdx);
|
|
20
|
+
const prompt = spaceIdx === -1 ? "" : command.args.slice(spaceIdx + 1).trim();
|
|
21
|
+
if (!toolName) return usage("Usage: /force:<tool-name> [prompt]", runtime);
|
|
22
|
+
try {
|
|
23
|
+
runtime.session.setForcedToolChoice(toolName);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return usage(errorMessage(err), runtime);
|
|
26
|
+
}
|
|
27
|
+
await runtime.output(`Next turn forced to use ${toolName}.`);
|
|
28
|
+
return prompt ? { prompt } : commandConsumed();
|
|
29
|
+
},
|
|
30
|
+
handleTui: (command, runtime) => {
|
|
31
|
+
const spaceIdx = command.args.indexOf(" ");
|
|
32
|
+
const toolName = spaceIdx === -1 ? command.args : command.args.slice(0, spaceIdx);
|
|
33
|
+
const prompt = spaceIdx === -1 ? "" : command.args.slice(spaceIdx + 1).trim();
|
|
34
|
+
|
|
35
|
+
if (!toolName) {
|
|
36
|
+
runtime.ctx.showError("Usage: /force:<tool-name> [prompt]");
|
|
37
|
+
runtime.ctx.editor.setText("");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
runtime.ctx.session.setForcedToolChoice(toolName);
|
|
43
|
+
runtime.ctx.showStatus(`Next turn forced to use ${toolName}.`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
runtime.ctx.showError(errorMessage(error));
|
|
46
|
+
runtime.ctx.editor.setText("");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
runtime.ctx.editor.setText("");
|
|
51
|
+
|
|
52
|
+
// If a prompt was provided, pass it through as input
|
|
53
|
+
if (prompt) return { prompt };
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "live",
|
|
58
|
+
description: "Start Codex-backed realtime voice mode",
|
|
59
|
+
handleTui: async (_command, runtime) => {
|
|
60
|
+
runtime.ctx.editor.setText("");
|
|
61
|
+
await runtime.ctx.handleLiveCommand();
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "pause",
|
|
66
|
+
description: "Freeze all agents (main, subagents, advisor) until resumed",
|
|
67
|
+
handleTui: async (_command, runtime) => {
|
|
68
|
+
runtime.ctx.editor.setText("");
|
|
69
|
+
await runPauseScreen(runtime.ctx);
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "quit",
|
|
74
|
+
aliases: ["q"],
|
|
75
|
+
description: "Quit the application",
|
|
76
|
+
handleTui: shutdownHandlerTui,
|
|
77
|
+
},
|
|
78
|
+
];
|