- Static site: /manifesto, /live, /hire pages - Ship-log Pi extension: calvana_ship, calvana_oops, calvana_deploy tools - Docker + nginx deploy to calvana.quikcue.com - Terminal-ish dark aesthetic, mobile responsive - Auto-updating /live page from extension state
85 lines
2.3 KiB
TypeScript
85 lines
2.3 KiB
TypeScript
/**
|
|
* Purpose Gate — Forces the engineer to declare intent before working
|
|
*
|
|
* On session start, immediately asks "What is the purpose of this agent?"
|
|
* via a text input dialog. A persistent widget shows the purpose for the
|
|
* rest of the session, keeping focus. Blocks all prompts until answered.
|
|
*
|
|
* Usage: pi -e extensions/purpose-gate.ts
|
|
*/
|
|
|
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
import { Text, truncateToWidth } from "@mariozechner/pi-tui";
|
|
import { applyExtensionDefaults } from "./themeMap.ts";
|
|
|
|
// synthwave: bgWarm #4a1e6a → rgb(74,30,106)
|
|
function bg(s: string): string {
|
|
return `\x1b[48;2;74;30;106m${s}\x1b[49m`;
|
|
}
|
|
|
|
// synthwave: pink #ff7edb
|
|
function pink(s: string): string {
|
|
return `\x1b[38;2;255;126;219m${s}\x1b[39m`;
|
|
}
|
|
|
|
// synthwave: cyan #36f9f6
|
|
function cyan(s: string): string {
|
|
return `\x1b[38;2;54;249;246m${s}\x1b[39m`;
|
|
}
|
|
|
|
function bold(s: string): string {
|
|
return `\x1b[1m${s}\x1b[22m`;
|
|
}
|
|
|
|
export default function (pi: ExtensionAPI) {
|
|
let purpose: string | undefined;
|
|
|
|
async function askForPurpose(ctx: any) {
|
|
while (!purpose) {
|
|
const answer = await ctx.ui.input(
|
|
"What is the purpose of this agent?",
|
|
"e.g. Refactor the auth module to use JWT"
|
|
);
|
|
|
|
if (answer && answer.trim()) {
|
|
purpose = answer.trim();
|
|
} else {
|
|
ctx.ui.notify("Purpose is required.", "warning");
|
|
}
|
|
}
|
|
|
|
ctx.ui.setWidget("purpose", () => {
|
|
return {
|
|
render(width: number): string[] {
|
|
const pad = bg(" ".repeat(width));
|
|
const label = pink(bold(" PURPOSE: "));
|
|
const msg = cyan(bold(purpose!));
|
|
const content = bg(truncateToWidth(label + msg + " ".repeat(width), width, ""));
|
|
return [pad, content, pad];
|
|
},
|
|
invalidate() {},
|
|
};
|
|
});
|
|
}
|
|
|
|
pi.on("session_start", async (_event, ctx) => {
|
|
applyExtensionDefaults(import.meta.url, ctx);
|
|
void askForPurpose(ctx);
|
|
});
|
|
|
|
pi.on("before_agent_start", async (event) => {
|
|
if (!purpose) return;
|
|
return {
|
|
systemPrompt: event.systemPrompt + `\n\n<purpose>\nYour singular purpose this session: ${purpose}\nStay focused on this goal. If a request drifts from this purpose, gently remind the user.\n</purpose>`,
|
|
};
|
|
});
|
|
|
|
pi.on("input", async (_event, ctx) => {
|
|
if (!purpose) {
|
|
ctx.ui.notify("Set a purpose first.", "warning");
|
|
return { action: "handled" as const };
|
|
}
|
|
return { action: "continue" as const };
|
|
});
|
|
}
|