Skip to content

Commit ce8f0e9

Browse files
ruttybobDietrich Gebert
andauthored
feat(pi-extension): autocomplete /ponytail arguments (#1080)
* feat(pi-extension): autocomplete /ponytail arguments Wire getArgumentCompletions into the /ponytail command, mirroring Pi's built-in /mcp pattern: first word completes the modes (lite, full, ultra, off) plus status and default; default <mode> completes runtime levels on the second word. Unknown or extra words return null. * pi: complete values only, no second copy of the level descriptions * pi: every completion is a full command, Enter submits it --------- Co-authored-by: Dietrich Gebert <dietrichgebert@gmail.com>
1 parent e39ce9b commit ce8f0e9

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

‎pi-extension/index.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ export const readQuietStartup = getQuietStartup;
2121
const RUNTIME_MODE_LIST = RUNTIME_MODES.join("|");
2222
const PONYTAIL_COMMAND_DESCRIPTION = `Set mode: ${RUNTIME_MODE_LIST}. Commands: status, default <mode>`;
2323

24+
// Every item is a complete command: pi submits on Enter, so a bare "default" would run without a mode.
25+
// Values only: per-level descriptions would be a second copy of the Levels table in SKILL.md.
26+
export function completePonytailArgs(prefix) {
27+
const typed = String(prefix || "").trimStart();
28+
const values = [...RUNTIME_MODES, "status", ...RUNTIME_MODES.map((m) => `default ${m}`)].filter((v) => v.startsWith(typed));
29+
return values.length ? values.map((value) => ({ value, label: value })) : null;
30+
}
31+
2432
export function resolveSessionMode(entries, fallbackMode = DEFAULT_MODE) {
2533
const fallback = normalizePersistedMode(fallbackMode) || DEFAULT_MODE;
2634
if (!Array.isArray(entries)) return fallback;
@@ -118,6 +126,7 @@ export default function ponytailExtension(pi) {
118126

119127
pi.registerCommand("ponytail", {
120128
description: PONYTAIL_COMMAND_DESCRIPTION,
129+
getArgumentCompletions: (prefix) => completePonytailArgs(prefix),
121130
handler: async (args, ctx) => {
122131
const parsed = parsePonytailCommand(args, configuredDefaultMode, currentMode);
123132

‎pi-extension/test/extension.test.js‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
44
import { join } from "node:path";
55
import test from "node:test";
66

7-
import ponytailExtension from "../index.js";
7+
import ponytailExtension, { parsePonytailCommand } from "../index.js";
88

99
function createPiHarness() {
1010
const events = new Map();
@@ -327,3 +327,22 @@ test("PONYTAIL_HIDE_STATUS=0 does not hide the indicator", async () => withTempC
327327

328328
assert.ok(statusWrites.length > 0, "0 must be treated as 'do not hide'");
329329
}));
330+
331+
test("/ponytail autocompletes modes, commands and default <mode>", () => {
332+
const { commands } = createPiHarness();
333+
const complete = (prefix) => commands.get("ponytail").getArgumentCompletions(prefix);
334+
335+
assert.deepEqual(complete("").map((item) => item.value),
336+
["off", "lite", "full", "ultra", "status", "default off", "default lite", "default full", "default ultra"]);
337+
assert.deepEqual(complete("fu").map((item) => item.value), ["full"]);
338+
assert.deepEqual(complete("default ").map((item) => item.value), ["default off", "default lite", "default full", "default ultra"]);
339+
assert.deepEqual(complete("default l").map((item) => item.value), ["default lite"]);
340+
assert.deepEqual(complete("default").map((item) => item.value),
341+
["default off", "default lite", "default full", "default ultra"], "never a bare default: Enter would submit it without a mode");
342+
assert.equal(complete("status x"), null, "no command takes a second word after status");
343+
assert.equal(complete("lite x"), null);
344+
assert.equal(complete("bogus"), null, "nothing matches an unknown prefix");
345+
for (const { value } of complete("")) {
346+
assert.notEqual(parsePonytailCommand(value).type, "invalid", `Enter on "${value}" must run a valid command`);
347+
}
348+
});

0 commit comments

Comments
 (0)