Skip to content

Commit d3264cc

Browse files
committed
fix: satisfy Ponytail lint and type checks
1 parent 66a05d2 commit d3264cc

2 files changed

Lines changed: 234 additions & 149 deletions

File tree

plugins/ponytail.ts

Lines changed: 146 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// Oxlint: This distributable plugin intentionally embeds Ponytail's instructions and uses Amp's async APIs.
2+
// oxlint-disable eslint/max-lines, eslint/max-lines-per-function, eslint/max-statements, oxc/no-async-await
13
import type { PluginAPI, PluginCommandContext, ThreadID } from "@ampcode/plugin";
24

35
export const description =
@@ -15,9 +17,12 @@ export type PonytailCommand =
1517
const DEFAULT_MODE: PonytailMode = "full";
1618
const DEFAULT_MODE_KEY = "ponytail.defaultMode";
1719
const PONYTAIL_URL = "https://github.com/dietrichgebert/ponytail";
20+
const PONYTAIL_COMMAND_PATTERN = /^(?:[/@$])?ponytail(?::ponytail)?(?:\s+(?<arguments>.*))?$/u;
21+
const TABLE_MODE_PATTERN = /^\|\s*\*\*(?<mode>.+?)\*\*\s*\|/u;
22+
const EXAMPLE_MODE_PATTERN = /^-\s*(?<mode>[^:]+):\s*"/u;
1823

19-
// Adapted from Ponytail's canonical skills/ponytail/SKILL.md. Keeping the
20-
// complete body here makes the installed, single-file Amp plugin work offline.
24+
// Adapted from Ponytail's canonical skills/ponytail/SKILL.md.
25+
// Keeping the complete body here makes the installed, single-file Amp plugin work offline.
2126
const PONYTAIL_SKILL_BODY = `# Ponytail
2227
2328
You are a lazy senior developer. Lazy means efficient, not careless. You have
@@ -120,102 +125,132 @@ changed or session end.
120125
121126
The shortest path to done is the right path.`;
122127

123-
export function normalizeMode(value: unknown): PonytailMode | null {
124-
if (typeof value !== "string") return null;
128+
export const normalizeMode = (value: unknown): PonytailMode | undefined => {
129+
if (typeof value !== "string") {
130+
return;
131+
}
125132

126133
const normalized = value.trim().toLowerCase();
127-
return PONYTAIL_MODES.includes(normalized as PonytailMode) ? (normalized as PonytailMode) : null;
128-
}
134+
if (!PONYTAIL_MODES.includes(normalized as PonytailMode)) {
135+
return;
136+
}
137+
138+
return normalized as PonytailMode;
139+
};
129140

130-
export function isDeactivationCommand(value: unknown): boolean {
141+
export const isDeactivationCommand = (value: unknown): boolean => {
131142
const normalized = String(value ?? "")
132143
.trim()
133144
.toLowerCase()
134-
.replace(/[.!?\s]+$/, "");
145+
.replace(/[.!?\s]+$/u, "");
135146

136147
return normalized === "stop ponytail" || normalized === "normal mode";
137-
}
148+
};
138149

139-
export function parsePonytailCommand(value: unknown): PonytailCommand | null {
150+
export const parsePonytailCommand = (value: unknown): PonytailCommand | undefined => {
140151
if (isDeactivationCommand(value)) {
141-
return { type: "set-mode", mode: "off" };
152+
return { mode: "off", type: "set-mode" };
142153
}
143154

144155
const command = String(value ?? "")
145156
.trim()
146157
.toLowerCase()
147-
.match(/^(?:[/@$])?ponytail(?::ponytail)?(?:\s+(.*))?$/);
158+
.match(PONYTAIL_COMMAND_PATTERN);
159+
if (!command || !command.groups) {
160+
return;
161+
}
148162

149-
if (!command) return null;
163+
const { arguments: argumentText } = command.groups;
164+
let argumentsValue = "";
165+
if (argumentText) {
166+
argumentsValue = argumentText.trim();
167+
}
168+
if (!argumentsValue || argumentsValue === "status") {
169+
return { type: "status" };
170+
}
150171

151-
const args = command[1]?.trim();
152-
if (!args || args === "status") return { type: "status" };
172+
const [primary, secondary, extra] = argumentsValue.split(/\s+/u);
173+
if (primary === "default") {
174+
if (!extra) {
175+
const mode = normalizeMode(secondary);
176+
if (mode) {
177+
return { mode, type: "set-default" };
178+
}
179+
}
153180

154-
const parts = args.split(/\s+/);
155-
if (parts[0] === "default") {
156-
const mode = parts.length === 2 ? normalizeMode(parts[1]) : null;
157-
return mode ? { type: "set-default", mode } : { type: "invalid" };
181+
return { type: "invalid" };
182+
}
183+
184+
if (!secondary) {
185+
const mode = normalizeMode(primary);
186+
if (mode) {
187+
return { mode, type: "set-mode" };
188+
}
158189
}
159190

160-
const mode = parts.length === 1 ? normalizeMode(parts[0]) : null;
161-
return mode ? { type: "set-mode", mode } : { type: "invalid" };
162-
}
191+
return { type: "invalid" };
192+
};
163193

164-
export function filterSkillBodyForMode(body: string, mode: PonytailMode): string {
165-
return body
166-
.split(/\r?\n/)
194+
const modeFromMatch = (match: RegExpMatchArray | null): PonytailMode | undefined => {
195+
if (!match || !match.groups) {
196+
return;
197+
}
198+
return normalizeMode(match.groups.mode);
199+
};
200+
201+
export const filterSkillBodyForMode = (body: string, mode: PonytailMode): string =>
202+
body
203+
.split(/\r?\n/u)
167204
.filter((line) => {
168-
const tableLabel = line.match(/^\|\s*\*\*(.+?)\*\*\s*\|/);
169-
if (tableLabel) {
170-
const labelMode = normalizeMode(tableLabel[1]);
171-
if (labelMode && labelMode !== "off") return labelMode === mode;
205+
const tableMode = modeFromMatch(line.match(TABLE_MODE_PATTERN));
206+
if (tableMode && tableMode !== "off") {
207+
return tableMode === mode;
172208
}
173209

174-
const exampleLabel = line.match(/^-\s*([^:]+):\s*"/);
175-
if (exampleLabel) {
176-
const labelMode = normalizeMode(exampleLabel[1]);
177-
if (labelMode && labelMode !== "off") return labelMode === mode;
210+
const exampleMode = modeFromMatch(line.match(EXAMPLE_MODE_PATTERN));
211+
if (exampleMode && exampleMode !== "off") {
212+
return exampleMode === mode;
178213
}
179214

180215
return true;
181216
})
182217
.join("\n");
183-
}
184218

185-
export function getPonytailInstructions(mode: PonytailMode): string {
186-
if (mode === "off") return "";
219+
export const getPonytailInstructions = (mode: PonytailMode): string => {
220+
if (mode === "off") {
221+
return "";
222+
}
187223

188224
return `PONYTAIL MODE ACTIVE — level: ${mode}\n\n${filterSkillBodyForMode(PONYTAIL_SKILL_BODY, mode)}`;
189-
}
225+
};
190226

191-
function effectiveDefaultMode(config: Record<string, unknown>): PonytailMode {
192-
return (
193-
normalizeMode(process.env.PONYTAIL_DEFAULT_MODE) ??
194-
normalizeMode(config[DEFAULT_MODE_KEY]) ??
195-
DEFAULT_MODE
196-
);
197-
}
227+
const effectiveDefaultMode = (config: Record<string, unknown>): PonytailMode =>
228+
normalizeMode(process.env.PONYTAIL_DEFAULT_MODE) ??
229+
normalizeMode(config[DEFAULT_MODE_KEY]) ??
230+
DEFAULT_MODE;
198231

199-
function requireThread(ctx: PluginCommandContext): ctx is PluginCommandContext & {
232+
const requireThread = (
233+
ctx: PluginCommandContext,
234+
): ctx is PluginCommandContext & {
200235
thread: NonNullable<PluginCommandContext["thread"]>;
201-
} {
202-
return Boolean(ctx.thread);
203-
}
236+
} => Boolean(ctx.thread);
204237

205-
export default function ponytailPlugin(amp: PluginAPI) {
238+
const ponytailPlugin = (amp: PluginAPI): void => {
206239
const threadModes = new Map<ThreadID, PonytailMode>();
207240
let configuredDefaultMode: PonytailMode =
208241
normalizeMode(process.env.PONYTAIL_DEFAULT_MODE) ?? DEFAULT_MODE;
209242
let receivedConfigurationUpdate = false;
210243

211-
const applyConfiguration = (config: Record<string, unknown>) => {
244+
const applyConfiguration = (config: Record<string, unknown>): void => {
212245
configuredDefaultMode = effectiveDefaultMode(config);
213246
};
214247

215248
const loadConfiguration = amp.configuration
216249
.get()
217250
.then((config) => {
218-
if (!receivedConfigurationUpdate) applyConfiguration(config);
251+
if (!receivedConfigurationUpdate) {
252+
applyConfiguration(config);
253+
}
219254
})
220255
.catch((error: unknown) => {
221256
amp.logger.log("Unable to read Ponytail configuration; using the fallback default.", error);
@@ -229,24 +264,27 @@ export default function ponytailPlugin(amp: PluginAPI) {
229264
configurationSubscription.unsubscribe();
230265
});
231266

232-
const getThreadMode = async (threadID: ThreadID) => {
267+
const getThreadMode = async (threadID: ThreadID): Promise<PonytailMode> => {
233268
await loadConfiguration;
234-
if (!threadModes.has(threadID)) {
235-
threadModes.set(threadID, configuredDefaultMode);
269+
const currentMode = threadModes.get(threadID);
270+
if (currentMode) {
271+
return currentMode;
236272
}
237-
return threadModes.get(threadID) as PonytailMode;
273+
274+
threadModes.set(threadID, configuredDefaultMode);
275+
return configuredDefaultMode;
238276
};
239277

240-
const setThreadMode = (threadID: ThreadID, mode: PonytailMode) => {
278+
const setThreadMode = (threadID: ThreadID, mode: PonytailMode): void => {
241279
threadModes.set(threadID, mode);
242280
};
243281

244-
const setDefaultMode = async (mode: PonytailMode) => {
282+
const setDefaultMode = async (mode: PonytailMode): Promise<void> => {
245283
await amp.configuration.update({ [DEFAULT_MODE_KEY]: mode }, "global");
246284
configuredDefaultMode = normalizeMode(process.env.PONYTAIL_DEFAULT_MODE) ?? mode;
247285
};
248286

249-
const notify = async (message: string, ui: PluginCommandContext["ui"]) => {
287+
const notify = async (message: string, ui: PluginCommandContext["ui"]): Promise<void> => {
250288
try {
251289
await ui.notify(message);
252290
} catch (error) {
@@ -265,37 +303,44 @@ export default function ponytailPlugin(amp: PluginAPI) {
265303
const command = parsePonytailCommand(event.message);
266304
let commandResult = "";
267305

268-
if (command?.type === "set-mode") {
269-
currentMode = command.mode;
270-
setThreadMode(event.thread.id, currentMode);
271-
commandResult = `Ponytail mode changed to ${currentMode} for this thread.`;
272-
} else if (command?.type === "set-default") {
273-
await setDefaultMode(command.mode);
274-
commandResult =
275-
configuredDefaultMode === command.mode
276-
? `Default Ponytail mode set to ${command.mode}. The current thread remains ${currentMode}.`
277-
: `Saved default ${command.mode}, but PONYTAIL_DEFAULT_MODE keeps the effective default at ${configuredDefaultMode}. The current thread remains ${currentMode}.`;
278-
} else if (command?.type === "status") {
279-
commandResult = `Ponytail status: current ${currentMode}; default ${configuredDefaultMode}.`;
280-
} else if (command?.type === "invalid") {
281-
commandResult =
282-
"Unknown Ponytail mode. Use off, lite, full, ultra, status, or default <mode>.";
306+
if (command) {
307+
if (command.type === "set-mode") {
308+
currentMode = command.mode;
309+
setThreadMode(event.thread.id, currentMode);
310+
commandResult = `Ponytail mode changed to ${currentMode} for this thread.`;
311+
} else if (command.type === "set-default") {
312+
await setDefaultMode(command.mode);
313+
commandResult = `Saved default ${command.mode}, but PONYTAIL_DEFAULT_MODE keeps the effective default at ${configuredDefaultMode}. The current thread remains ${currentMode}.`;
314+
if (configuredDefaultMode === command.mode) {
315+
commandResult = `Default Ponytail mode set to ${command.mode}. The current thread remains ${currentMode}.`;
316+
}
317+
} else if (command.type === "status") {
318+
commandResult = `Ponytail status: current ${currentMode}; default ${configuredDefaultMode}.`;
319+
} else {
320+
commandResult =
321+
"Unknown Ponytail mode. Use off, lite, full, ultra, status, or default <mode>.";
322+
}
283323
}
284324

285-
if (commandResult) await notify(commandResult, ctx.ui);
325+
if (commandResult) {
326+
await notify(commandResult, ctx.ui);
327+
}
286328

287329
const instructions = getPonytailInstructions(currentMode);
288330
const content = [commandResult, instructions].filter(Boolean).join("\n\n");
289331

290-
return content ? { message: { content } } : undefined;
332+
if (content) {
333+
return { message: { content } };
334+
}
335+
return {};
291336
});
292337

293338
amp.registerCommand(
294339
"ponytail-mode",
295340
{
296-
title: "Change mode",
297341
category: "ponytail",
298342
description: "Set Ponytail intensity for the active thread.",
343+
title: "Change mode",
299344
},
300345
async (ctx) => {
301346
if (!requireThread(ctx)) {
@@ -305,14 +350,16 @@ export default function ponytailPlugin(amp: PluginAPI) {
305350

306351
const currentMode = await getThreadMode(ctx.thread.id);
307352
const selected = await ctx.ui.select({
308-
title: "Ponytail mode",
353+
initialValue: currentMode,
309354
message:
310355
"lite suggests the lazier option; full enforces the ladder; ultra challenges unnecessary work.",
311356
options: [...PONYTAIL_MODES],
312-
initialValue: currentMode,
357+
title: "Ponytail mode",
313358
});
314359
const mode = normalizeMode(selected);
315-
if (!mode) return;
360+
if (!mode) {
361+
return;
362+
}
316363

317364
setThreadMode(ctx.thread.id, mode);
318365
await ctx.ui.notify(`Ponytail mode set to ${mode} for this thread.`);
@@ -322,57 +369,64 @@ export default function ponytailPlugin(amp: PluginAPI) {
322369
amp.registerCommand(
323370
"ponytail-status",
324371
{
325-
title: "Show status",
326372
category: "ponytail",
327373
description: "Show the active thread mode and configured default.",
374+
title: "Show status",
328375
},
329376
async (ctx) => {
330377
await loadConfiguration;
331-
const current = ctx.thread ? await getThreadMode(ctx.thread.id) : "(no active thread)";
378+
let current: PonytailMode | "(no active thread)" = "(no active thread)";
379+
if (ctx.thread) {
380+
current = await getThreadMode(ctx.thread.id);
381+
}
332382
await ctx.ui.notify(`Ponytail: current ${current}; default ${configuredDefaultMode}.`);
333383
},
334384
);
335385

336386
amp.registerCommand(
337387
"ponytail-default-mode",
338388
{
339-
title: "Set default mode",
340389
category: "ponytail",
341390
description: "Set the Ponytail mode used by new Amp threads.",
391+
title: "Set default mode",
342392
},
343393
async (ctx) => {
344394
await loadConfiguration;
345395
const selected = await ctx.ui.select({
346-
title: "Default Ponytail mode",
396+
initialValue: configuredDefaultMode,
347397
message: "This applies to new threads and is saved in Amp settings.",
348398
options: [...PONYTAIL_MODES],
349-
initialValue: configuredDefaultMode,
399+
title: "Default Ponytail mode",
350400
});
351401
const mode = normalizeMode(selected);
352-
if (!mode) return;
402+
if (!mode) {
403+
return;
404+
}
353405

354406
await setDefaultMode(mode);
355407
const overridden = configuredDefaultMode !== mode;
356-
await ctx.ui.notify(
357-
overridden
358-
? `Saved ${mode}, but PONYTAIL_DEFAULT_MODE keeps the effective default at ${configuredDefaultMode}.`
359-
: `Default Ponytail mode set to ${mode}.`,
360-
);
408+
let message = `Default Ponytail mode set to ${mode}.`;
409+
if (overridden) {
410+
message = `Saved ${mode}, but PONYTAIL_DEFAULT_MODE keeps the effective default at ${configuredDefaultMode}.`;
411+
}
412+
await ctx.ui.notify(message);
361413
},
362414
);
363415

364416
amp.registerCommand(
365417
"ponytail-help",
366418
{
367-
title: "Open documentation",
368419
category: "ponytail",
369420
description: "Open the Ponytail documentation on GitHub.",
421+
title: "Open documentation",
370422
},
371423
async (ctx) => {
372424
await ctx.system.open(PONYTAIL_URL);
373425
},
374426
);
375-
}
427+
};
428+
429+
export default ponytailPlugin;
376430

377431
/*
378432
MIT License

0 commit comments

Comments
 (0)