Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/programs/agent-skill/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,32 @@
export { AGENT_SKILL_STEPS } from '../../tui/programs/shared/skill-flow.js';
import type { ProgramConfig } from '../program-step.js';
import { POSTHOG_DOCS_URL } from '@shared/constants.js';
import { AGENT_SKILL_SCOPE_ADDITIONS } from './scopes.js';

// Generic skill program — runs an arbitrary context-mill skill chosen at
// dispatch time (session.skillId) rather than a registered named program.
// Backs `wizard skill <name>` and the narrow `audit` leaves (events,
// feature-flags, identify, session-replay, autocapture); each injects its
// skillId onto the config, which lands on session.skillId before the run.
//
// The `run` recipe is a function rather than a static block because the
// skillId isn't known until dispatch. Without a `run` recipe runProgram fails
// with "has no run configuration" and the skill never executes — so we derive
// generic run metadata from the resolved skill id at run time.
export const config: ProgramConfig = {
id: 'agent-skill',
description: 'Run an arbitrary context-mill skill',
allowedTools: ['Agent'],
oauthScopeAdditions: AGENT_SKILL_SCOPE_ADDITIONS,
run: (session) => {
const skillId = session.skillId ?? 'agent-skill';
return Promise.resolve({
skillId,
integrationLabel: skillId,
spinnerMessage: `Running ${skillId}...`,
successMessage: `${skillId} complete!`,
estimatedDurationMinutes: 5,
reportFile: `posthog-${skillId}-report.md`,
docsUrl: POSTHOG_DOCS_URL,
});
},
};
19 changes: 9 additions & 10 deletions src/programs/ai-observability/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import type { ProgramConfig, ProgramStep } from '@programs/program-step';
import { AGENT_SKILL_STEPS } from '@programs/agent-skill/index';
import { getContentBlocks } from '@tui/programs/shared/skill-deck';
import { Harness, Sequence, GPT5_6_TERRA_MODEL } from '@shared/constants';
import type { ProgramConfig } from '../program-step';
import { headlessOption, regionOption } from '@shared/headless-mode';

const AI_OBSERVABILITY_STEPS: ProgramStep[] = AGENT_SKILL_STEPS.map((step) =>
step.id === 'intro' ? { ...step, screenId: 'ai-observability-intro' } : step,
);

const AI_OBSERVABILITY_REPORT_FILE = 'posthog-ai-observability-report.md';

/**
Expand All @@ -19,14 +14,18 @@ const AI_OBSERVABILITY_REPORT_FILE = 'posthog-ai-observability-report.md';
* the right variant itself (see `customPrompt`). Stays flat while a single
* "add AIO to a project" flow is the only action.
*/
export const aiObservabilityConfig: ProgramConfig = {
export const config: ProgramConfig = {
binding: {
sequence: Sequence.linear,
harness: Harness.pi,
model: GPT5_6_TERRA_MODEL,
thinkingLevel: 'high',
},
command: 'ai-observability',
description: 'Add PostHog AI Observability to your LLM calls',
id: 'ai-observability',
cliOptions: { ...headlessOption, ...regionOption },
steps: AI_OBSERVABILITY_STEPS,
reportFile: AI_OBSERVABILITY_REPORT_FILE,
getContentBlocks,
run: {
integrationLabel: 'ai-observability',
// No `skillId`: linear.ts skips its pre-install step (see the gate on
Expand Down
22 changes: 9 additions & 13 deletions src/programs/audit/events/config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { ProgramConfig } from '@programs/program-step';
import type { ProgramRun } from '@programs/program-run';
import type { WizardSession } from '@programs/session/wizard-session';
import { OutroKind } from '@programs/session/wizard-session';
import { SPINNER_MESSAGE } from '@programs/framework-config';
import type { ProgramConfig } from '../../program-step';
import type { ProgramRun } from '../../program-run';
import type { ProgramSession } from '../../program-session';
import { OutroKind } from '@shared/outro';
import { SPINNER_MESSAGE } from '../../framework-config';
import { isUsingTypeScript } from '@utils/setup-utils';
import { WIZARD_TOOL_NAMES } from '@agent';
import { EVENTS_AUDIT_PROGRAM } from '../../../tui/programs/audit/events-flow.js';
import { AUDIT_CHECKS_FILE, AUDIT_CHECKS_KEY } from '@programs/audit/types';
import { seedAuditLedger } from '@programs/audit/seed';
import { AUDIT_CHECKS_FILE, AUDIT_CHECKS_KEY } from '../types.js';
import { seedAuditLedger } from '../seed.js';
import { EVENTS_AUDIT_SEED_CHECKS } from './seed.js';

// SETUP_REPORT_FILE is also re-exported for backward compat with existing
Expand All @@ -25,11 +24,10 @@ const DOCS_URL = 'https://posthog.com/docs/product-analytics/best-practices';
* skill (whose id AuditRunScreen keys its slides on), not to this config.
* Registered so its id stays resolvable; nothing dispatches to it today.
*/
export const eventsAuditConfig: ProgramConfig = {
export const config: ProgramConfig = {
description: 'Audit PostHog event tracking in this project',
id: 'events-audit',
skillId: 'events-audit',
steps: EVENTS_AUDIT_PROGRAM,
// Top-level reportFile so AuditRunScreen can resolve the report path
// synchronously without unwrapping the deferred `run` function.
reportFile: SETUP_REPORT_FILE,
Expand All @@ -42,7 +40,7 @@ export const eventsAuditConfig: ProgramConfig = {
],
disallowedTools: [WIZARD_TOOL_NAMES.wizardAsk],

run: (session: WizardSession): Promise<ProgramRun> => {
run: (session: ProgramSession): Promise<ProgramRun> => {
const typeScriptDetected = isUsingTypeScript({
installDir: session.installDir,
});
Expand Down Expand Up @@ -105,5 +103,3 @@ Project context:
});
},
};

export { EVENTS_AUDIT_PROGRAM } from '../../../tui/programs/audit/events-flow.js';
2 changes: 1 addition & 1 deletion src/programs/audit/events/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Leaf-level constants for the events-audit program. The document names live
* in `@lib/constants` so infrastructure (the scanner's allowlist) can name
* in `@shared/constants` so infrastructure (the scanner's allowlist) can name
* them without importing this program.
*/

Expand Down
2 changes: 1 addition & 1 deletion src/programs/audit/events/seed.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AuditCheck } from '@programs/audit/types';
import type { AuditCheck } from '../types.js';

/**
* The 7 phases the events-audit skill marches through. One check per area
Expand Down
53 changes: 33 additions & 20 deletions src/programs/audit/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { AGENT_SKILL_STEPS } from '@programs/agent-skill/index';
import { createSkillProgram } from '@programs/shared/skill-program';
import { withAuditScreens } from '@tui/programs/audit/flow';
import type { ProgramStep, ProgramConfig } from '@programs/program-step';
import type { ProgramRun } from '@programs/program-run';
import type { RunnerContext } from '@programs/runner-context';
import type { WizardSession } from '@programs/session/wizard-session';
import { OutroKind } from '@programs/session/wizard-session';
import { createSkillProgram } from '../shared/skill-program';
import type { ProgramConfig } from '../program-step';
import type { ProgramRun } from '../program-run';
import type { RunnerContext } from '../runner-context';
import type { ProgramSession } from '../program-session';
import { OutroKind } from '@shared/outro';
import { WIZARD_TOOL_NAMES } from '@agent';
import { headlessOption, regionOption } from '@shared/headless-mode';
import { AUDIT_ABORT_CASES } from './detect.js';
Expand All @@ -15,14 +13,13 @@ import {
AUDIT_REPORT_FILE,
} from './types.js';
import { AUDIT_SEED_CHECKS, seedAuditLedger } from './seed.js';
import { config as eventsAudit } from './events/config.js';

const seedBeforeAuditRun = (session: WizardSession): void => {
const seedBeforeAuditRun = (session: ProgramSession): void => {
seedAuditLedger(session.installDir);
session.frameworkContext[AUDIT_CHECKS_KEY] = AUDIT_SEED_CHECKS;
};

const auditSteps: ProgramStep[] = withAuditScreens(AGENT_SKILL_STEPS);

const baseConfig = createSkillProgram({
skillId: 'audit',
command: 'audit',
Expand All @@ -42,7 +39,7 @@ const baseConfig = createSkillProgram({
});

const auditRun = async (
session: WizardSession,
session: ProgramSession,
runner: RunnerContext,
): Promise<ProgramRun> => {
seedBeforeAuditRun(session);
Expand All @@ -67,12 +64,8 @@ const auditRun = async (
? `${cloudUrl}/products?source=wizard`
: undefined;

// Note: `sess` here is the agent-runner's snapshot of session at
// runAgent() invocation time. Any URL emissions during the run land
// on the live store, NOT on this snapshot. The UI layer
// (InkUI.setOutroData) merges live URLs in on top of this return
// value, so it's safe to leave dashboardUrl/notebookUrl as undefined
// here when the snapshot doesn't have them.
// The session store lays any URL the agent emitted during the run over
// this outro, so dashboardUrl/notebookUrl may stay undefined here.
return {
kind: OutroKind.Success as const,
message: baseRun.successMessage,
Expand All @@ -86,9 +79,8 @@ const auditRun = async (
};
};

export const auditConfig: ProgramConfig = {
const audit: ProgramConfig = {
...baseConfig,
steps: auditSteps,
run: auditRun,
auditLedgerFile: AUDIT_CHECKS_FILE,
// Ledger tools are opt-in per program; pi matches on the short name.
Expand All @@ -104,3 +96,24 @@ export const auditConfig: ProgramConfig = {
// `wizard audit` command; dispatchProgram routes it to runWizardHeadless.
cliOptions: { ...headlessOption, ...regionOption },
};

/** Registers `audit` and `events-audit`, which has no entry of its own. */
export const configs = [
audit,
eventsAudit,
] as const satisfies readonly ProgramConfig[];

export {
AUDIT_CHECKS_FILE,
AUDIT_CHECKS_KEY,
AUDIT_REPORT_FILE,
getAuditChecks,
type AuditCheck,
type AuditStatus,
} from './types.js';
export {
removeAuditLedger,
startAuditLedgerWatcher,
} from './ledger-watcher.js';
/** The checks every audit run starts from. */
export { AUDIT_SEED_CHECKS } from './seed.js';
11 changes: 6 additions & 5 deletions src/programs/audit/ledger-watcher.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
/**
* Mirrors the agent's `.posthog-audit-checks.json` into the session, so the TUI
* screens and the task stream read one value. `runProgramAgent` owns the
* screens and the task stream read one value. `runProgram` owns the
* lifecycle and removes the file at run end, so every path gets it — including
* the e2e host, which builds no task stream.
*/

import fs from 'fs';
import path from 'path';
import { getUI } from '@ui';
import type { RunnerContext } from '../runner-context';
import {
startFileWatcher,
type FileWatcherHandle,
type FileWatcherOptions,
} from '@shared/utils/file-watcher';
} from '@utils/file-watcher';
import { logToFile } from '@utils/debug';
import { AUDIT_CHECKS_KEY, coerceAuditChecks } from './types.js';
import { AUDIT_CHECKS_KEY, coerceAuditChecks } from './types';

const MAX_LEDGER_FILE_BYTES = 256 * 1024;

export function startAuditLedgerWatcher(
installDir: string,
file: string,
runner: Pick<RunnerContext, 'setFrameworkContext'>,
options: FileWatcherOptions = {},
): FileWatcherHandle {
const target = path.join(installDir, file);
Expand All @@ -29,7 +30,7 @@ export function startAuditLedgerWatcher(
return startFileWatcher(
target,
(parsed) =>
getUI().setFrameworkContext(AUDIT_CHECKS_KEY, coerceAuditChecks(parsed)),
runner.setFrameworkContext(AUDIT_CHECKS_KEY, coerceAuditChecks(parsed)),
{
// A ledger an earlier run left behind stays ignored until this run writes.
ignoreInitialFile: true,
Expand Down
2 changes: 1 addition & 1 deletion src/programs/audit/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type AuditStatus,
} from '@shared/audit-ledger';

// The ledger contract lives in `@lib/audit-ledger`; re-exported so the audit
// The ledger contract lives in `@shared/audit-ledger`; re-exported so the audit
// views and the ledger watcher keep their import path.
export { AUDIT_CHECKS_FILE, AUDIT_REPORT_FILE, coerceAuditChecks };
export type { AuditCheck, AuditStatus };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {
type DetectTarget,
type AgenticDetectionReport,
type DetectEvent,
} from '@programs/detection/agentic';
import type { WizardSession } from '@programs/session/wizard-session';
type DetectProgress,
} from '../detection/agentic';
import type { ProgramSession } from '../program-session';
import {
VARIANT_DISPLAY_NAME,
AUTOMATABLE_VARIANTS,
Expand Down Expand Up @@ -401,15 +402,17 @@ export function coerceReport(

/** Run the Haiku detector over the repo and classify projects for source maps. */
export async function detectSourceMapsProjects(
session: WizardSession,
session: ProgramSession,
onEvent?: DetectEvent,
onProgress?: DetectProgress,
): Promise<DetectionReport> {
const report = await detectProjectsWithAgent(session, {
targets: SOURCE_MAPS_TARGETS,
programId: 'error-tracking-upload-source-maps',
purpose: 'set up PostHog Error Tracking source-map upload',
rerankIds: JS_RERANK_VARIANTS,
onEvent,
onProgress,
});
return toSourceMapsReport(report, {
hasBuildTarget: (path) => projectHasBuildTarget(session.installDir, path),
Expand Down
4 changes: 2 additions & 2 deletions src/programs/error-tracking-upload-source-maps/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
MAX_WALK_FILES,
safeReadFile,
} from '@utils/bounded-fs';
import type { WizardSession } from '@programs/session/wizard-session';
import type { ProgramSession } from '../program-session';
import type { AbortCase } from '@agent/types';
import { ErrorCodes } from '@shared/errors';

Expand Down Expand Up @@ -357,7 +357,7 @@ export const SOURCE_MAPS_CONTEXT_KEYS = {
* only picks which variant the prompt should ask the agent to load.
*/
export function detectSourceMapsPrerequisites(
session: WizardSession,
session: Pick<ProgramSession, 'installDir'>,
setFrameworkContext: (key: string, value: unknown) => void,
): void {
const fail = (error: SourceMapsDetectError) =>
Expand Down
31 changes: 17 additions & 14 deletions src/programs/error-tracking-upload-source-maps/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { ProgramConfig } from '@programs/program-step';
import type { ProgramRun } from '@programs/program-run';
import type { WizardSession } from '@programs/session/wizard-session';
import { OutroKind } from '@programs/session/wizard-session';
import type { RunnerContext } from '@programs/runner-context';
import { ERROR_TRACKING_UPLOAD_SOURCE_MAPS_PROGRAM } from '../../tui/programs/error-tracking-upload-source-maps/flow.js';
import type { ProgramConfig } from '../program-step';
import type { ProgramRun } from '../program-run';
import type { ProgramSession } from '../program-session';
import { OutroKind } from '@shared/outro';
import type { RunnerContext } from '../runner-context';
import {
buildSourceMapsUploadPrompt,
SOURCE_MAPS_DETECTION_FAILED_PROMPT,
Expand All @@ -14,8 +13,7 @@ import {
VARIANTS_REQUIRING_POSTHOG_CLI,
type SkillVariant,
} from './detect.js';
import { getContentBlocks } from '../../tui/programs/shared/deck/source-maps.js';
import { preinstallPostHogCliOnce } from '@programs/shared/posthog-cli-preinstall';
import { preinstallPostHogCliOnce } from '../shared/posthog-cli-preinstall';

const REPORT_FILE = 'posthog-source-maps-report.md';
const DOCS_URL = 'https://posthog.com/docs/error-tracking/upload-source-maps';
Expand All @@ -36,18 +34,17 @@ function ensurePostHogCli(
);
}

export const errorTrackingUploadSourceMapsConfig: ProgramConfig = {
export const config: ProgramConfig = {
command: 'upload-source-maps',
description: 'Upload source maps to PostHog Error Tracking',
id: 'error-tracking-upload-source-maps',
requiresAi: true,
steps: ERROR_TRACKING_UPLOAD_SOURCE_MAPS_PROGRAM,
// No health-check screen in the TUI flow; the run skips the readiness check.
healthCheck: false,
reportFile: REPORT_FILE,
getContentBlocks,
requires: ['posthog-integration'],

run: (
_session: WizardSession,
_session: ProgramSession,
runner: RunnerContext,
): Promise<ProgramRun> => {
// Read the picked project LIVE at prompt-build time, not here: the picker
Expand Down Expand Up @@ -136,13 +133,19 @@ export const errorTrackingUploadSourceMapsConfig: ProgramConfig = {
},
};

export { ERROR_TRACKING_UPLOAD_SOURCE_MAPS_PROGRAM } from '../../tui/programs/error-tracking-upload-source-maps/flow.js';
export {
detectSourceMapsPrerequisites,
SOURCE_MAPS_ABORT_CASES,
SOURCE_MAPS_CONTEXT_KEYS,
VARIANT_DISPLAY_NAME,
VARIANTS_REQUIRING_POSTHOG_CLI,
MANUAL_SDK_VARIANTS,
type SkillVariant,
type SourceMapsDetectError,
} from './detect.js';

export {
detectSourceMapsProjects,
type DetectedProject,
type DetectionReport,
} from './detect-agentic.js';
Loading
Loading