diff --git a/bin.ts b/bin.ts index f4bb1bd5..3eb4a659 100644 --- a/bin.ts +++ b/bin.ts @@ -58,6 +58,7 @@ import { mcpAnalyticsCommand } from './src/commands/mcp-analytics'; import { replayVisionCommand } from './src/commands/replay-vision'; import { aiObservabilityCommand } from './src/commands/ai-observability'; import { metricsCommand } from './src/commands/metrics'; +import { shakespeareCommand } from './src/commands/shakespeare'; import { auditCommand } from './src/commands/audit'; import { doctorCommand } from './src/commands/doctor'; import { migrateCommand } from './src/commands/migrate'; @@ -91,6 +92,7 @@ Wizard.use(basicIntegrationCommand) .use(replayVisionCommand) .use(aiObservabilityCommand) .use(metricsCommand) + .use(shakespeareCommand) .use(cliCommand) .use(auditCommand) .use(doctorCommand) diff --git a/src/commands/shakespeare.ts b/src/commands/shakespeare.ts new file mode 100644 index 00000000..240b81e2 --- /dev/null +++ b/src/commands/shakespeare.ts @@ -0,0 +1,11 @@ +import { shakespeareConfig } from '@lib/programs/shakespeare/index'; + +import type { Command } from './command'; +import { nativeCommandFactory } from './factories/native-command-factory'; + +/** + * `wizard shakespeare` — internal test-only command. See + * `src/lib/programs/shakespeare/index.ts` for why this exists. + */ +export const shakespeareCommand: Command = + nativeCommandFactory(shakespeareConfig); diff --git a/src/lib/agent/runner/switchboard/index.ts b/src/lib/agent/runner/switchboard/index.ts index fb423203..56023931 100644 --- a/src/lib/agent/runner/switchboard/index.ts +++ b/src/lib/agent/runner/switchboard/index.ts @@ -160,6 +160,9 @@ export const PROGRAM_BINDINGS: Partial> = { model: SONNET_5_MODEL, }, slack: DEFAULT_BINDING, + // Fixed skillId, single variant — no self-selection reasoning needed, so + // the plain default binding (unlike ai-observability). + shakespeare: DEFAULT_BINDING, }; // ── Unified resolver ──────────────────────────────────────────────────── diff --git a/src/lib/programs/program-registry.ts b/src/lib/programs/program-registry.ts index 2633c295..920abd33 100644 --- a/src/lib/programs/program-registry.ts +++ b/src/lib/programs/program-registry.ts @@ -34,6 +34,7 @@ import { replayVisionConfig } from './replay-vision/index.js'; import { aiObservabilityConfig } from './ai-observability/index.js'; import { metricsConfig } from './metrics/index.js'; import { slackConnectConfig } from './slack/index.js'; +import { shakespeareConfig } from './shakespeare/index.js'; // Generic skill program — runs an arbitrary context-mill skill chosen at // dispatch time (session.skillId) rather than a registered named program. @@ -85,6 +86,7 @@ export const PROGRAM_REGISTRY = [ aiObservabilityConfig, metricsConfig, slackConnectConfig, + shakespeareConfig, ] as const satisfies readonly ProgramConfig[]; /** @@ -112,6 +114,7 @@ export const Program = { AiObservability: aiObservabilityConfig.id, Metrics: metricsConfig.id, SlackConnect: slackConnectConfig.id, + Shakespeare: shakespeareConfig.id, } as const; /** Compile-time union of every registered program id. */ diff --git a/src/lib/programs/shakespeare/content/index.tsx b/src/lib/programs/shakespeare/content/index.tsx new file mode 100644 index 00000000..cc1dc335 --- /dev/null +++ b/src/lib/programs/shakespeare/content/index.tsx @@ -0,0 +1,65 @@ +/** + * Shakespeare learn-deck — portrait, then what the program does, then a + * few lines it might drop into your code. Overrides the generic + * agent-skill deck (`programs/agent-skill/content/index.tsx`) that + * `createSkillProgram` wires up by default. + */ + +import { TextRevealMode } from '@ui/tui/primitives/TextBlock'; +import type { ContentBlock } from '@ui/tui/primitives/content-types'; +import type { WizardStore } from '@ui/tui/store'; +import { PORTRAIT_BLOCK } from './portrait.js'; + +const CLEAR: ContentBlock = { type: 'clear', pause: 1500 }; + +export const getContentBlocks = (_store?: WizardStore): ContentBlock[] => [ + // Scene 1 — portrait + PORTRAIT_BLOCK, + + CLEAR, + + // Scene 2 — what this program actually does + { + content: 'This one is an internal test, not a PostHog feature.', + pause: 4000, + mode: TextRevealMode.Typewriter, + animationInterval: 24, + }, + { + content: + 'It installs a single context-mill skill with no SDK and no product logic.', + pause: 5000, + }, + { + content: + "Just one line of Shakespeare, dropped into your project's most prominent file.", + pause: 5000, + }, + { + content: "It's here to test the skill-to-wizard-program pipeline end to end.", + pause: 5000, + }, + + CLEAR, + + // Scene 3 — a few lines it might choose from + { content: 'A few of the lines it might pick:', pause: 2500 }, + + { + content: '"All the world\'s a stage, and all the men and women merely players."', + pause: 5000, + mode: TextRevealMode.SentenceFade, + }, + + { + content: '"To be, or not to be, that is the question."', + pause: 5000, + mode: TextRevealMode.SentenceFade, + }, + + { + content: '"We know what we are, but know not what we may be."', + pause: 5000, + mode: TextRevealMode.SentenceFade, + }, +]; diff --git a/src/lib/programs/shakespeare/content/portrait.tsx b/src/lib/programs/shakespeare/content/portrait.tsx new file mode 100644 index 00000000..d44b42b3 --- /dev/null +++ b/src/lib/programs/shakespeare/content/portrait.tsx @@ -0,0 +1,39 @@ +/** + * PORTRAIT_BLOCK — ASCII portrait shown first in the shakespeare Learn + * deck. Fixed at 37 chars wide (14 rows) to fit the LearnCard pane at an + * 80-column terminal — see the pane-width math in `LearnCard.tsx` and the + * same convention self-driving's `PIPELINE_BLOCK` follows. Rendered as + * exact `lines` (not a wrapped string) so word-wrap can't corrupt it. + */ + +import { Text } from 'ink'; +import { Colors } from '@ui/tui/styles'; +import type { ContentBlock } from '@ui/tui/primitives/content-types'; + +const PORTRAIT_ROWS = [ + '%%%%%%%%%%%%%###=+*##%##%%%%%%%%%%%%%', + '%%%%%%%%%%##:*###**=--*#####%%%%%%%%%', + '%%%%%######:#%%%%%%#==:=#########%%%%', + '%%%######-:+##%####*+:-.:=*########%%', + '%#######+=:--+*=--=+=-:-..:**######%%', + '%#####**=--*++*#**#*=::-:..:******###', + '%###*****--+#%**#**+=:.:..:=*+*****##', + '####***+*+=:-==:=**--=#+:.-=++*++*+*#', + '####***+++*+-+**+-::+%*#*--++++*+++*#', + '##****+++++*+.:--.-%%####..:++++++**#', + '##***+++=:####:##%##%#%*:.:..::++==**', + '##****-.::#:.#*.#.##*.:-.-:.::..:--+*', + '%##*=:.=.+=.=..:.-:-:-:-==-=-:=:::==#', + '%##*-.-....+.=-.:.::=+:=+-+:-+=+:*=+#', +]; + +export const PORTRAIT_BLOCK: ContentBlock = { + type: 'lines', + interval: 50, + pause: 4000, + lines: PORTRAIT_ROWS.map((row, i) => ( + + {row} + + )), +}; diff --git a/src/lib/programs/shakespeare/index.ts b/src/lib/programs/shakespeare/index.ts new file mode 100644 index 00000000..f2d801af --- /dev/null +++ b/src/lib/programs/shakespeare/index.ts @@ -0,0 +1,35 @@ +import { createSkillProgram } from '@lib/programs/agent-skill/index'; +import type { ProgramConfig } from '@lib/programs/program-step'; +import { getContentBlocks } from './content/index.js'; + +const SHAKESPEARE_REPORT_FILE = 'posthog-shakespeare-report.md'; + +const baseConfig = createSkillProgram({ + skillId: 'shakespeare', + command: 'shakespeare', + id: 'shakespeare', + description: + 'Add a line of Shakespeare to your codebase (internal test only)', + integrationLabel: 'shakespeare', + successMessage: `Shakespeare quote added! View the report at ./${SHAKESPEARE_REPORT_FILE}`, + reportFile: SHAKESPEARE_REPORT_FILE, + docsUrl: 'https://github.com/PostHog/context-mill', + spinnerMessage: 'Consulting the Bard...', + estimatedDurationMinutes: 1, +}); + +/** + * `wizard shakespeare` — internal test-only program. Installs the + * context-mill `shakespeare` skill (single variant, no framework matching) + * and lets the agent run it end to end. Exists purely to exercise the + * skill-authoring → wizard-program pipeline in isolation; not a real + * PostHog product flow. + * + * Overrides `getContentBlocks` with its own Learn deck (portrait → what + * this program does → a few lines it might pick) instead of the generic + * three-line agent-skill deck `createSkillProgram` wires up by default. + */ +export const shakespeareConfig: ProgramConfig = { + ...baseConfig, + getContentBlocks, +};