@posthog/wizard 2.80.0 → 2.80.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -57
- package/dist/{PickerMenu-m4CCPX4f.js → PickerMenu-BGGeYqd-.js} +104 -4
- package/dist/PickerMenu-BGGeYqd-.js.map +1 -0
- package/dist/README.md +9 -6
- package/dist/{agent-interface-CzyCmp7q.js → agent-interface-CemBvTAA.js} +115 -346
- package/dist/agent-interface-CemBvTAA.js.map +1 -0
- package/dist/{agent-prompt-loader-4wMxlYSX.js → agent-prompt-loader-DPnj6SwU.js} +6 -5
- package/dist/agent-prompt-loader-DPnj6SwU.js.map +1 -0
- package/dist/analytics-B3ROGuIr.js +2 -0
- package/dist/analytics-D21z3E5l.js +732 -0
- package/dist/analytics-D21z3E5l.js.map +1 -0
- package/dist/{api-C3Dc4LFl.js → api-DtGIqQ2U.js} +2 -3
- package/dist/api-DtGIqQ2U.js.map +1 -0
- package/dist/api-IhROOQNv.js +2 -0
- package/dist/api-key-login-OEntFvu5.js +2 -0
- package/dist/api-key-login-h3E2G3zu.js +55 -0
- package/dist/api-key-login-h3E2G3zu.js.map +1 -0
- package/dist/bin.js +2 -2
- package/dist/bin.js.map +1 -1
- package/dist/{bounded-fs-CC_NDtoP.js → bounded-fs-CAsuF6al.js} +3 -3
- package/dist/{bounded-fs-CC_NDtoP.js.map → bounded-fs-CAsuF6al.js.map} +1 -1
- package/dist/{ci-install-DfcAMhlu.js → ci-install-CEYE02t-.js} +21 -25
- package/dist/ci-install-CEYE02t-.js.map +1 -0
- package/dist/console-log-O828zyiq.js +32 -0
- package/dist/console-log-O828zyiq.js.map +1 -0
- package/dist/{debug-DyARf5TF.js → debug-BfQPD2Hg.js} +18 -52
- package/dist/debug-BfQPD2Hg.js.map +1 -0
- package/dist/{defaults-DpfMnH2q.js → defaults-DDVl3tKN.js} +2 -51
- package/dist/defaults-DDVl3tKN.js.map +1 -0
- package/dist/{emit-BSetXzDV.js → emit-RfAewcWv.js} +1 -1
- package/dist/{emit-BSetXzDV.js.map → emit-RfAewcWv.js.map} +1 -1
- package/dist/{errors-C2domSkK.js → errors-CHrJzAZb.js} +4 -4
- package/dist/{errors-C2domSkK.js.map → errors-CHrJzAZb.js.map} +1 -1
- package/dist/family-picker-BKs-tTyu.js +46 -0
- package/dist/family-picker-BKs-tTyu.js.map +1 -0
- package/dist/{gateway-BmjYUSua.js → gateway-D3gIlR6b.js} +122 -11
- package/dist/gateway-D3gIlR6b.js.map +1 -0
- package/dist/headless-BJjhP0SI.js +10 -0
- package/dist/headless-BJjhP0SI.js.map +1 -0
- package/dist/headless-DIX68UUA.js +3 -0
- package/dist/{host-resolution-DVSb7kXb.js → host-resolution-D8GhEVoX.js} +4 -4
- package/dist/{host-resolution-DVSb7kXb.js.map → host-resolution-D8GhEVoX.js.map} +1 -1
- package/dist/install-Bw8tvDvS.js +2 -0
- package/dist/{install-Bk5TNk27.js → install-paPCzPHI.js} +26 -93
- package/dist/install-paPCzPHI.js.map +1 -0
- package/dist/interactive-tdxZKWax.js +11 -0
- package/dist/interactive-tdxZKWax.js.map +1 -0
- package/dist/{links-9LWZPSCq.js → links-Cyj6v8kg.js} +3 -3
- package/dist/{links-9LWZPSCq.js.map → links-Cyj6v8kg.js.map} +1 -1
- package/dist/{main-DKPjFHEV.js → main-0p-YxUNa.js} +204 -469
- package/dist/main-0p-YxUNa.js.map +1 -0
- package/dist/{mcp-Co8VRF62.js → mcp-D3Z5MXhA.js} +3 -4
- package/dist/{mcp-Co8VRF62.js.map → mcp-D3Z5MXhA.js.map} +1 -1
- package/dist/mcp-install-smoke-test.ts +2 -2
- package/dist/{mcp-prompt-streaming-ljCVoQ9I.js → mcp-prompt-streaming-COVbWnHt.js} +12 -12
- package/dist/mcp-prompt-streaming-COVbWnHt.js.map +1 -0
- package/dist/mode-label-DB3ol_sj.js +9 -0
- package/dist/mode-label-DB3ol_sj.js.map +1 -0
- package/dist/{models-Dba8ITeo.js → models-D1SnCzvl.js} +2 -2
- package/dist/{models-Dba8ITeo.js.map → models-D1SnCzvl.js.map} +1 -1
- package/dist/non-interactive-DxRBc73O.js +18 -0
- package/dist/non-interactive-DxRBc73O.js.map +1 -0
- package/dist/{oauth-session-D-RfcVmr.js → oauth-session-Dde79Ycm.js} +3 -3
- package/dist/{oauth-session-D-RfcVmr.js.map → oauth-session-Dde79Ycm.js.map} +1 -1
- package/dist/{orchestrator-tools-CVJ1F9Ub.js → orchestrator-tools-BSNhzzkT.js} +3 -3
- package/dist/{orchestrator-tools-CVJ1F9Ub.js.map → orchestrator-tools-BSNhzzkT.js.map} +1 -1
- package/dist/{package-json-Cowya9-3.js → package-json-2UrhE6kP.js} +1 -1
- package/dist/{package-json-Cowya9-3.js.map → package-json-2UrhE6kP.js.map} +1 -1
- package/dist/package-json-Dblip2_X.js +2 -0
- package/dist/package-manager-BRXlXo0p.js +105 -0
- package/dist/package-manager-BRXlXo0p.js.map +1 -0
- package/dist/{pi-B5YTBRDv.js → pi-cM_4Gd_9.js} +16 -18
- package/dist/pi-cM_4Gd_9.js.map +1 -0
- package/dist/playground-DTKy0XHT.js +12 -0
- package/dist/playground-DTKy0XHT.js.map +1 -0
- package/dist/posthog-integration-bwHwE87a.js +10291 -0
- package/dist/posthog-integration-bwHwE87a.js.map +1 -0
- package/dist/programs-BhtyA3lE.js +3 -0
- package/dist/programs-SeT1qNik.js +5348 -0
- package/dist/programs-SeT1qNik.js.map +1 -0
- package/dist/provisioning-BcXfeCMc.js +2 -0
- package/dist/{provisioning-CLhuKqXH.js → provisioning-JbJtq7hq.js} +4 -5
- package/dist/{provisioning-CLhuKqXH.js.map → provisioning-JbJtq7hq.js.map} +1 -1
- package/dist/{queue-tools-knnPQP0e.js → queue-tools-BIZSJyt6.js} +6 -8
- package/dist/queue-tools-BIZSJyt6.js.map +1 -0
- package/dist/results-DtKICAdz.js +55 -0
- package/dist/results-DtKICAdz.js.map +1 -0
- package/dist/run-CDurA5tO.js +248 -0
- package/dist/run-CDurA5tO.js.map +1 -0
- package/dist/run-DDCvLILJ.js +246 -0
- package/dist/run-DDCvLILJ.js.map +1 -0
- package/dist/run-tool-CTq9OOdT.js +2 -0
- package/dist/run-tool-DML8JJvt.js +2420 -0
- package/dist/run-tool-DML8JJvt.js.map +1 -0
- package/dist/runners-Dhu4pZTN.js +253 -0
- package/dist/runners-Dhu4pZTN.js.map +1 -0
- package/dist/{security-BvdSFDuM.js → security-DEH8Rn0_.js} +5 -5
- package/dist/security-DEH8Rn0_.js.map +1 -0
- package/dist/setup-utils-C5-WM-hS.js +118 -0
- package/dist/setup-utils-C5-WM-hS.js.map +1 -0
- package/dist/smoke-test.sh +1 -1
- package/dist/{playground-BLsCfHmQ.js → start-playground-DL4mVEUx.js} +43 -34
- package/dist/start-playground-DL4mVEUx.js.map +1 -0
- package/dist/{subagent-D_Sqak94.js → subagent-CnFvAq6E.js} +3 -3
- package/dist/{subagent-D_Sqak94.js.map → subagent-CnFvAq6E.js.map} +1 -1
- package/dist/{task-DiTI6Sak.js → task-BOScSm4W.js} +16 -17
- package/dist/task-BOScSm4W.js.map +1 -0
- package/dist/{tasks-ChZ_F1YP.js → tasks-BfJhVky2.js} +1 -1
- package/dist/tasks-BfJhVky2.js.map +1 -0
- package/dist/{telemetry-BNKsDxNS.js → telemetry-CP4rGicd.js} +2 -2
- package/dist/{telemetry-BNKsDxNS.js.map → telemetry-CP4rGicd.js.map} +1 -1
- package/dist/terminal-J3OD3dN0.js +16162 -0
- package/dist/terminal-J3OD3dN0.js.map +1 -0
- package/dist/tools-CCMV3DmB.js +544 -0
- package/dist/tools-CCMV3DmB.js.map +1 -0
- package/dist/{tools-C8LQ8QFz.js → tools-Cz8tDptL.js} +9 -9
- package/dist/tools-Cz8tDptL.js.map +1 -0
- package/dist/tui-BUZ2PWqN.js +2 -0
- package/dist/tui-Csf7VJxt.js +25 -0
- package/dist/tui-Csf7VJxt.js.map +1 -0
- package/dist/{steps-Hc28zVc_.js → upload-step-CEacB2RV.js} +14 -16
- package/dist/upload-step-CEacB2RV.js.map +1 -0
- package/dist/{urls-BI4kJ4qQ.js → urls-BDELGZHq.js} +2 -2
- package/dist/{urls-BI4kJ4qQ.js.map → urls-BDELGZHq.js.map} +1 -1
- package/dist/warlock-smoke-test.ts +1 -2
- package/dist/{wizard-abort-DBKtfnBR.js → wizard-abort-D3-75KyV.js} +40 -33
- package/dist/wizard-abort-D3-75KyV.js.map +1 -0
- package/dist/yara-hooks-CZ1tKxy_.js +2 -0
- package/dist/{yara-hooks-C3L6lBij.js → yara-hooks-D8YDyX7o.js} +283 -21
- package/dist/yara-hooks-D8YDyX7o.js.map +1 -0
- package/package.json +6 -6
- package/dist/PickerMenu-m4CCPX4f.js.map +0 -1
- package/dist/agent-BkBbVuNq.js +0 -3189
- package/dist/agent-BkBbVuNq.js.map +0 -1
- package/dist/agent-QGK2YzlX.js +0 -6
- package/dist/agent-interface-CzyCmp7q.js.map +0 -1
- package/dist/agent-prompt-loader-4wMxlYSX.js.map +0 -1
- package/dist/analytics-B6q5PN7a.js +0 -301
- package/dist/analytics-B6q5PN7a.js.map +0 -1
- package/dist/analytics-BXtfK9o7.js +0 -2
- package/dist/api-C3Dc4LFl.js.map +0 -1
- package/dist/api-yNjrfZs2.js +0 -2
- package/dist/check-screens.tsx +0 -190
- package/dist/ci-install-DfcAMhlu.js.map +0 -1
- package/dist/constants-DO4X70po.js +0 -260
- package/dist/constants-DO4X70po.js.map +0 -1
- package/dist/debug-BueK5mRR.js +0 -2
- package/dist/debug-DyARf5TF.js.map +0 -1
- package/dist/defaults-DpfMnH2q.js.map +0 -1
- package/dist/endpoints-D-4KxZiB.js +0 -108
- package/dist/endpoints-D-4KxZiB.js.map +0 -1
- package/dist/env-api-key-CHGLe0iz.js +0 -2
- package/dist/env-api-key-COf0U-Au.js +0 -17
- package/dist/env-api-key-COf0U-Au.js.map +0 -1
- package/dist/environment-DrLaSsA8.js +0 -39
- package/dist/environment-DrLaSsA8.js.map +0 -1
- package/dist/environment-hBj7ngmO.js +0 -2
- package/dist/file-CVA9ocIC.js +0 -60
- package/dist/file-CVA9ocIC.js.map +0 -1
- package/dist/file-CmoKK_Pp.js +0 -2
- package/dist/file-watcher-C3Su8vXm.js +0 -123
- package/dist/file-watcher-C3Su8vXm.js.map +0 -1
- package/dist/gateway-BmjYUSua.js.map +0 -1
- package/dist/headless-ui-CUbv12Yx.js +0 -44
- package/dist/headless-ui-CUbv12Yx.js.map +0 -1
- package/dist/install-Bk5TNk27.js.map +0 -1
- package/dist/install-F4wm3RSl.js +0 -2
- package/dist/interactive-C5LNvDj6.js +0 -11
- package/dist/interactive-C5LNvDj6.js.map +0 -1
- package/dist/local-dev-McsurOC-.js +0 -135
- package/dist/local-dev-McsurOC-.js.map +0 -1
- package/dist/logging-ui-p6XKVmTx.js +0 -226
- package/dist/logging-ui-p6XKVmTx.js.map +0 -1
- package/dist/main-DKPjFHEV.js.map +0 -1
- package/dist/mcp-prompt-streaming-ljCVoQ9I.js.map +0 -1
- package/dist/mint-failure-C0HyvEwO.js +0 -6403
- package/dist/mint-failure-C0HyvEwO.js.map +0 -1
- package/dist/non-interactive-CLMJrj_7.js +0 -18
- package/dist/non-interactive-CLMJrj_7.js.map +0 -1
- package/dist/package-json--ppioh2F.js +0 -2
- package/dist/pi-B5YTBRDv.js.map +0 -1
- package/dist/playground-BLsCfHmQ.js.map +0 -1
- package/dist/posthog-B2E0hpZt.js +0 -2
- package/dist/posthog-B42rt_1S.js +0 -124
- package/dist/posthog-B42rt_1S.js.map +0 -1
- package/dist/project-data--L-WZyvj.js +0 -2
- package/dist/project-data-BO9IXn9y.js +0 -1131
- package/dist/project-data-BO9IXn9y.js.map +0 -1
- package/dist/provisioning-C01REIU2.js +0 -2
- package/dist/queue-tools-knnPQP0e.js.map +0 -1
- package/dist/registry-B3rC5U4b.js +0 -3464
- package/dist/registry-B3rC5U4b.js.map +0 -1
- package/dist/registry-BamsM7J9.js +0 -2
- package/dist/run-agent-legacy-CwUmPfQF.js +0 -2
- package/dist/run-agent-legacy-DWBajsMh.js +0 -3467
- package/dist/run-agent-legacy-DWBajsMh.js.map +0 -1
- package/dist/runners-D1PqqpjA.js +0 -834
- package/dist/runners-D1PqqpjA.js.map +0 -1
- package/dist/security-BvdSFDuM.js.map +0 -1
- package/dist/setup-utils-ChXXTi1m.js +0 -301
- package/dist/setup-utils-ChXXTi1m.js.map +0 -1
- package/dist/start-tui-DicLfgsj.js +0 -6964
- package/dist/start-tui-DicLfgsj.js.map +0 -1
- package/dist/steps-Hc28zVc_.js.map +0 -1
- package/dist/store-COCO6U0P.js +0 -5
- package/dist/store-D3509G87.js +0 -1030
- package/dist/store-D3509G87.js.map +0 -1
- package/dist/task-DiTI6Sak.js.map +0 -1
- package/dist/task-status-CmrrR7zo.js +0 -16
- package/dist/task-status-CmrrR7zo.js.map +0 -1
- package/dist/task-stream-BckhG_cB.js +0 -363
- package/dist/task-stream-BckhG_cB.js.map +0 -1
- package/dist/tasks-ChZ_F1YP.js.map +0 -1
- package/dist/terminal-PK9bCLRi.js +0 -7222
- package/dist/terminal-PK9bCLRi.js.map +0 -1
- package/dist/tools-C8LQ8QFz.js.map +0 -1
- package/dist/ui-tPNpM6vj.js +0 -15
- package/dist/ui-tPNpM6vj.js.map +0 -1
- package/dist/version-5_ydHkM6.js +0 -6
- package/dist/version-5_ydHkM6.js.map +0 -1
- package/dist/wizard-abort-BVjqLyeA.js +0 -3
- package/dist/wizard-abort-DBKtfnBR.js.map +0 -1
- package/dist/wizard-session-B7BHnay0.js +0 -2
- package/dist/wizard-session-BYw6LzpS.js +0 -133
- package/dist/wizard-session-BYw6LzpS.js.map +0 -1
- package/dist/yara-hooks-C3L6lBij.js.map +0 -1
- package/dist/yara-hooks-CF_IsoaA.js +0 -2
|
@@ -0,0 +1,2420 @@
|
|
|
1
|
+
import { n as ErrorCodes, t as emitWizardError } from "./emit-RfAewcWv.js";
|
|
2
|
+
import { f as VERSION, i as getLogFilePath, o as logToFile } from "./debug-BfQPD2Hg.js";
|
|
3
|
+
import { M as POSTHOG_DOCS_URL, O as OAUTH_PORTS, ct as getLocalDev, k as OAUTH_TIMEOUT_MS, n as analytics, rt as getSkillsBaseUrl, st as checkLocalServices, y as GITHUB_SKILLS_BASE_URL } from "./analytics-D21z3E5l.js";
|
|
4
|
+
import { c as buildSession, it as getBlockingServiceKeys, k as getProgramConfig, m as logIn, nt as SIGNUP_WIZARD_READINESS_CONFIG } from "./programs-SeT1qNik.js";
|
|
5
|
+
import { a as WizardError, t as classifyRunFailure } from "./errors-CHrJzAZb.js";
|
|
6
|
+
import { n as printAbortOutro } from "./console-log-O828zyiq.js";
|
|
7
|
+
import { m as runCleanups } from "./agent-interface-CemBvTAA.js";
|
|
8
|
+
import { E as scanInstalledSkill, M as formatTokenCount, j as formatCostUsd } from "./posthog-integration-bwHwE87a.js";
|
|
9
|
+
import { _ as downloadSkill, y as fetchSkillMenu } from "./yara-hooks-D8YDyX7o.js";
|
|
10
|
+
import { i as redactSecrets, r as namesWithStatus, s as toClientResult } from "./results-DtKICAdz.js";
|
|
11
|
+
import { c as flushAnalytics } from "./tools-CCMV3DmB.js";
|
|
12
|
+
import { t as ALL_FEATURE_VALUES } from "./defaults-DDVl3tKN.js";
|
|
13
|
+
import { n as startHostExit, r as wizardAbort } from "./wizard-abort-D3-75KyV.js";
|
|
14
|
+
import { a as installPlugins, i as getSupportedPluginClients, n as getInstalledClients, o as removeMCPServer, r as getSupportedClients, s as isPluginCapable } from "./install-paPCzPHI.js";
|
|
15
|
+
import { a as useStdoutDimensions, h as Icons, m as Colors, n as useKeyBindings, t as PickerMenu } from "./PickerMenu-BGGeYqd-.js";
|
|
16
|
+
import { B as totalTokenCount, G as ModalOverlay, H as LogViewer, I as HNViewer, J as ProgressList, K as ConfirmationInput, L as TabContainer, M as IntroScreenLayout, N as PRIVACY_PANEL_LABEL, P as PrivacyPanel, Q as getContentBlocks, R as ScreenContainer, T as McpScreen, U as LinkText, V as EventPlanViewer, W as extractUrls, X as SplitView, Y as LoadingBox, _ as getTuiTool, a as SlackConnectScreen, b as extractOAuthCode, c as LearnCard, d as MINT_FAILURE_BODY, f as MINT_FAILURE_CONTACT, g as listFlowOwners, h as flowOwner, i as OutroScreen, k as useDismissOnAnyKey, l as AiOptInRequiredScreen, m as isRunFailure, n as releaseTerminal, o as TipsCard, p as MINT_FAILURE_MESSAGE, r as WizardStore, s as VisualizerTab, t as enterDarkTerminal, u as ServiceHealthList, x as abortOnScreens, y as getOrAskForProjectData } from "./terminal-J3OD3dN0.js";
|
|
17
|
+
import path from "path";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import path$1, { join as join$1 } from "node:path";
|
|
20
|
+
import { spawn } from "node:child_process";
|
|
21
|
+
import { constants } from "node:fs";
|
|
22
|
+
import fs from "fs/promises";
|
|
23
|
+
import { access, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
24
|
+
import { Box, Text, render, useInput } from "ink";
|
|
25
|
+
import { createElement, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
26
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
27
|
+
import { PasswordInput, TextInput } from "@inkjs/ui";
|
|
28
|
+
//#region src/tui/screens/health/HealthCheckScreen.tsx
|
|
29
|
+
/**
|
|
30
|
+
* HealthCheckScreen — Program screen between Intro and Auth.
|
|
31
|
+
*
|
|
32
|
+
* Three states:
|
|
33
|
+
* 1. Checking: spinner while health check runs
|
|
34
|
+
* 2. Healthy: isComplete returns true, router auto-advances to Auth
|
|
35
|
+
* 3. Blocking outage: shows affected services with Continue/Exit
|
|
36
|
+
*/
|
|
37
|
+
const EXAMPLE_PROMPT = "Integrate PostHog into this project using the skill files in .posthog/skills/. Read SKILL.md first, then follow the numbered program files in order.";
|
|
38
|
+
const SkillsDownloadedScreen = ({ store }) => {
|
|
39
|
+
useDismissOnAnyKey(() => store.requestExit(0));
|
|
40
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
41
|
+
flexDirection: "column",
|
|
42
|
+
flexGrow: 1,
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
45
|
+
color: "green",
|
|
46
|
+
bold: true,
|
|
47
|
+
children: [Icons.check, " Skills downloaded to .posthog/skills/"]
|
|
48
|
+
}),
|
|
49
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
50
|
+
marginTop: 1,
|
|
51
|
+
flexDirection: "column",
|
|
52
|
+
children: [/* @__PURE__ */ jsx(Text, { children: "You can continue setup with another agent using this prompt:" }), /* @__PURE__ */ jsx(Box, {
|
|
53
|
+
marginTop: 1,
|
|
54
|
+
paddingLeft: 2,
|
|
55
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
56
|
+
color: "cyan",
|
|
57
|
+
children: EXAMPLE_PROMPT
|
|
58
|
+
})
|
|
59
|
+
})]
|
|
60
|
+
}),
|
|
61
|
+
/* @__PURE__ */ jsx(Box, {
|
|
62
|
+
marginTop: 1,
|
|
63
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
64
|
+
color: Colors.muted,
|
|
65
|
+
children: "Press any key to exit"
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
const HealthCheckScreen = ({ store }) => {
|
|
72
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
73
|
+
const [downloaded, setDownloaded] = useState(false);
|
|
74
|
+
const [downloading, setDownloading] = useState(false);
|
|
75
|
+
const result = store.session.readinessResult;
|
|
76
|
+
if (downloaded) return /* @__PURE__ */ jsx(SkillsDownloadedScreen, { store });
|
|
77
|
+
if (!result) return /* @__PURE__ */ jsx(Box, {
|
|
78
|
+
flexDirection: "column",
|
|
79
|
+
flexGrow: 1,
|
|
80
|
+
alignItems: "center",
|
|
81
|
+
justifyContent: "center",
|
|
82
|
+
children: /* @__PURE__ */ jsx(LoadingBox, { message: "Checking service status..." })
|
|
83
|
+
});
|
|
84
|
+
const isSignup = store.session.signup;
|
|
85
|
+
const blockingKeys = getBlockingServiceKeys(result.health, isSignup ? SIGNUP_WIZARD_READINESS_CONFIG : void 0);
|
|
86
|
+
const warningKeys = isSignup ? getBlockingServiceKeys(result.health).filter((k) => !blockingKeys.includes(k)) : [];
|
|
87
|
+
const hasHardBlock = blockingKeys.length > 0;
|
|
88
|
+
const displayKeys = hasHardBlock ? blockingKeys : warningKeys;
|
|
89
|
+
if (displayKeys.length === 0) return null;
|
|
90
|
+
const isSkillsOriginDown = hasHardBlock && blockingKeys.includes("skillsOrigin");
|
|
91
|
+
const canDownloadSkills = result.health.skillsOrigin.status === "healthy";
|
|
92
|
+
const integration = store.session.integration;
|
|
93
|
+
const allBlockingHaveNoConnection = hasHardBlock && displayKeys.every((k) => result.health[k].status === "no-connection");
|
|
94
|
+
const title = isSkillsOriginDown ? "Ongoing service disruptions" : allBlockingHaveNoConnection ? "Couldn't reach PostHog" : hasHardBlock ? "Ongoing service disruptions" : "Service disruption detected";
|
|
95
|
+
const docsUrl = store.session.frameworkConfig?.metadata.docsUrl;
|
|
96
|
+
const description = isSkillsOriginDown ? "The Wizard can't download the skills it needs — neither GitHub Releases nor PostHog's mirror is reachable right now." : allBlockingHaveNoConnection ? "We couldn't reach these services from this machine. PostHog's status page shows no incidents, so this is most likely a network issue — VPN, firewall, captive portal, or flaky Wi-Fi." : hasHardBlock ? "The Wizard cannot start while these services are down." : "Some services are degraded. You can continue, but parts of the wizard may not work reliably.";
|
|
97
|
+
const handleDownloadAndExit = async () => {
|
|
98
|
+
if (downloading) return;
|
|
99
|
+
setDownloading(true);
|
|
100
|
+
const menu = await fetchSkillMenu(GITHUB_SKILLS_BASE_URL);
|
|
101
|
+
if (menu) {
|
|
102
|
+
const prefix = `integration-${integration}`;
|
|
103
|
+
const skills = (menu.categories["integration"] ?? []).filter((s) => s.id.startsWith(prefix));
|
|
104
|
+
for (const skill of skills) await downloadSkill(skill, store.session.installDir, {
|
|
105
|
+
skillsRoot: ".posthog/skills",
|
|
106
|
+
scan: (dir) => scanInstalledSkill(dir, void 0)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
setDownloaded(true);
|
|
110
|
+
};
|
|
111
|
+
return /* @__PURE__ */ jsxs(ModalOverlay, {
|
|
112
|
+
borderColor: hasHardBlock && !allBlockingHaveNoConnection ? "red" : "yellow",
|
|
113
|
+
title,
|
|
114
|
+
width: 72,
|
|
115
|
+
footer: isSkillsOriginDown ? /* @__PURE__ */ jsx(ConfirmationInput, {
|
|
116
|
+
message: "",
|
|
117
|
+
confirmLabel: "",
|
|
118
|
+
cancelLabel: "Exit [Esc]",
|
|
119
|
+
onConfirm: () => void abortOnScreens(store, {
|
|
120
|
+
code: ErrorCodes.EnvServiceOutage,
|
|
121
|
+
message: "Exited due to service outage."
|
|
122
|
+
}),
|
|
123
|
+
onCancel: () => void abortOnScreens(store, {
|
|
124
|
+
code: ErrorCodes.EnvServiceOutage,
|
|
125
|
+
message: "Exited due to service outage."
|
|
126
|
+
})
|
|
127
|
+
}) : /* @__PURE__ */ jsx(ConfirmationInput, {
|
|
128
|
+
message: "Continue anyway?",
|
|
129
|
+
confirmLabel: "Continue [Enter]",
|
|
130
|
+
cancelLabel: canDownloadSkills && !isSkillsOriginDown ? downloading ? "Downloading..." : "Download skills & Exit [Esc]" : "Exit [Esc]",
|
|
131
|
+
onConfirm: () => store.dismissOutage(),
|
|
132
|
+
onCancel: canDownloadSkills && !isSkillsOriginDown ? () => void handleDownloadAndExit() : () => void abortOnScreens(store, {
|
|
133
|
+
code: ErrorCodes.EnvServiceOutage,
|
|
134
|
+
message: "Exited due to service outage."
|
|
135
|
+
})
|
|
136
|
+
}),
|
|
137
|
+
children: [
|
|
138
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
139
|
+
flexDirection: "column",
|
|
140
|
+
marginBottom: 1,
|
|
141
|
+
children: [/* @__PURE__ */ jsx(Box, {
|
|
142
|
+
marginBottom: 1,
|
|
143
|
+
children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
144
|
+
/* @__PURE__ */ jsx(Text, {
|
|
145
|
+
color: "red",
|
|
146
|
+
children: Icons.squareFilled
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ jsx(Text, {
|
|
149
|
+
dimColor: true,
|
|
150
|
+
children: " Down "
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ jsx(Text, {
|
|
153
|
+
color: "#DC9300",
|
|
154
|
+
children: Icons.squareFilled
|
|
155
|
+
}),
|
|
156
|
+
/* @__PURE__ */ jsx(Text, {
|
|
157
|
+
dimColor: true,
|
|
158
|
+
children: " Degraded "
|
|
159
|
+
}),
|
|
160
|
+
/* @__PURE__ */ jsx(Text, {
|
|
161
|
+
color: "gray",
|
|
162
|
+
children: Icons.squareFilled
|
|
163
|
+
}),
|
|
164
|
+
/* @__PURE__ */ jsx(Text, {
|
|
165
|
+
dimColor: true,
|
|
166
|
+
children: " No connection"
|
|
167
|
+
})
|
|
168
|
+
] })
|
|
169
|
+
}), /* @__PURE__ */ jsx(ServiceHealthList, {
|
|
170
|
+
health: result.health,
|
|
171
|
+
filterKeys: displayKeys,
|
|
172
|
+
showHealthy: false
|
|
173
|
+
})]
|
|
174
|
+
}),
|
|
175
|
+
/* @__PURE__ */ jsx(Text, {
|
|
176
|
+
dimColor: true,
|
|
177
|
+
children: description
|
|
178
|
+
}),
|
|
179
|
+
isSkillsOriginDown && docsUrl && /* @__PURE__ */ jsx(Box, {
|
|
180
|
+
marginTop: 1,
|
|
181
|
+
children: /* @__PURE__ */ jsxs(Text, { children: ["Set up manually: ", /* @__PURE__ */ jsx(Text, {
|
|
182
|
+
color: "cyan",
|
|
183
|
+
children: docsUrl
|
|
184
|
+
})] })
|
|
185
|
+
}),
|
|
186
|
+
canDownloadSkills && !isSkillsOriginDown && /* @__PURE__ */ jsx(Box, {
|
|
187
|
+
marginTop: 1,
|
|
188
|
+
children: /* @__PURE__ */ jsx(Text, { children: "You can still download the PostHog integration skills and continue with another agent." })
|
|
189
|
+
})
|
|
190
|
+
]
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/tui/screens/SettingsOverrideScreen.tsx
|
|
195
|
+
const SettingsOverrideScreen = ({ store }) => {
|
|
196
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
197
|
+
const [feedback, setFeedback] = useState(null);
|
|
198
|
+
const conflicts = store.settingsConflicts?.filter((c) => c.writable);
|
|
199
|
+
const hasConflicts = Boolean(conflicts && conflicts.length > 0);
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (hasConflicts) analytics.wizardCapture("settings conflict shown", { kind: "override" });
|
|
202
|
+
}, [hasConflicts]);
|
|
203
|
+
if (!conflicts || conflicts.length === 0) return null;
|
|
204
|
+
return /* @__PURE__ */ jsxs(ModalOverlay, {
|
|
205
|
+
borderColor: "red",
|
|
206
|
+
title: `${Icons.warning} Settings conflict`,
|
|
207
|
+
width: 64,
|
|
208
|
+
feedback: feedback ? `${Icons.warning} ${feedback}` : null,
|
|
209
|
+
footer: /* @__PURE__ */ jsx(ConfirmationInput, {
|
|
210
|
+
message: "Back up to .wizard-backup and continue?",
|
|
211
|
+
confirmLabel: "Backup & continue [Enter]",
|
|
212
|
+
cancelLabel: "Exit [Esc]",
|
|
213
|
+
onConfirm: () => {
|
|
214
|
+
analytics.wizardCapture("settings conflict accepted", { kind: "override" });
|
|
215
|
+
if (!store.backupAndFixSettingsOverride()) setFeedback("Could not back up the settings file.");
|
|
216
|
+
},
|
|
217
|
+
onCancel: () => store.requestExit(1)
|
|
218
|
+
}),
|
|
219
|
+
children: [conflicts.map((conflict) => /* @__PURE__ */ jsxs(Box, {
|
|
220
|
+
flexDirection: "column",
|
|
221
|
+
marginBottom: 1,
|
|
222
|
+
children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
223
|
+
"Your settings file at ",
|
|
224
|
+
/* @__PURE__ */ jsx(Text, {
|
|
225
|
+
bold: true,
|
|
226
|
+
children: conflict.path
|
|
227
|
+
}),
|
|
228
|
+
" sets:"
|
|
229
|
+
] }), /* @__PURE__ */ jsx(Box, {
|
|
230
|
+
flexDirection: "column",
|
|
231
|
+
paddingLeft: 2,
|
|
232
|
+
children: conflict.keys.map((key) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
233
|
+
Icons.bullet,
|
|
234
|
+
" ",
|
|
235
|
+
/* @__PURE__ */ jsx(Text, {
|
|
236
|
+
color: "yellow",
|
|
237
|
+
bold: true,
|
|
238
|
+
children: key
|
|
239
|
+
})
|
|
240
|
+
] }, key))
|
|
241
|
+
})]
|
|
242
|
+
}, conflict.path)), /* @__PURE__ */ jsx(Text, {
|
|
243
|
+
dimColor: true,
|
|
244
|
+
children: "These settings override credentials and prevent the Wizard from reaching the PostHog LLM Gateway. We can back up the file and continue."
|
|
245
|
+
})]
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/tui/screens/ManagedSettingsScreen.tsx
|
|
250
|
+
/**
|
|
251
|
+
* ManagedSettingsScreen — Modal for read-only settings conflicts that block
|
|
252
|
+
* the Wizard from reaching the PostHog LLM Gateway: org-managed settings, the
|
|
253
|
+
* user's global `~/.claude` config, and gitignored project-local overrides.
|
|
254
|
+
*
|
|
255
|
+
* Unlike SettingsOverrideScreen, the wizard cannot safely back up or remove
|
|
256
|
+
* these files for the user, so it names the exact file and key and asks the
|
|
257
|
+
* user to fix it and re-run. Managed (root-owned) files need an IT admin; the
|
|
258
|
+
* rest the user can edit themselves.
|
|
259
|
+
*/
|
|
260
|
+
function sourceLabel(source) {
|
|
261
|
+
switch (source) {
|
|
262
|
+
case "managed": return "Organization-managed settings";
|
|
263
|
+
case "user": return "Your global Claude Code settings";
|
|
264
|
+
case "project-local": return "Project-local settings";
|
|
265
|
+
default: return source;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const ManagedSettingsScreen = ({ store }) => {
|
|
269
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
270
|
+
const readOnlyConflicts = store.settingsConflicts?.filter((c) => !c.writable);
|
|
271
|
+
const hasManaged = Boolean(readOnlyConflicts?.some((c) => c.source === "managed"));
|
|
272
|
+
const hasReadOnly = Boolean(readOnlyConflicts && readOnlyConflicts.length > 0);
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
if (hasReadOnly) analytics.wizardCapture("settings conflict shown", {
|
|
275
|
+
kind: "managed",
|
|
276
|
+
has_managed: hasManaged
|
|
277
|
+
});
|
|
278
|
+
}, [hasReadOnly, hasManaged]);
|
|
279
|
+
if (!readOnlyConflicts || readOnlyConflicts.length === 0) return null;
|
|
280
|
+
return /* @__PURE__ */ jsxs(ModalOverlay, {
|
|
281
|
+
borderColor: "red",
|
|
282
|
+
title: `${Icons.warning} Settings conflict`,
|
|
283
|
+
width: 72,
|
|
284
|
+
footer: /* @__PURE__ */ jsx(ConfirmationInput, {
|
|
285
|
+
message: "Fix the file(s) above, then re-run the Wizard.",
|
|
286
|
+
confirmLabel: "",
|
|
287
|
+
cancelLabel: "Exit [Esc]",
|
|
288
|
+
onConfirm: () => store.requestExit(1),
|
|
289
|
+
onCancel: () => store.requestExit(1)
|
|
290
|
+
}),
|
|
291
|
+
children: [
|
|
292
|
+
/* @__PURE__ */ jsx(Text, {
|
|
293
|
+
dimColor: true,
|
|
294
|
+
children: "These Claude Code settings override credentials and prevent the Wizard from reaching the PostHog LLM Gateway."
|
|
295
|
+
}),
|
|
296
|
+
readOnlyConflicts.map((conflict) => /* @__PURE__ */ jsxs(Box, {
|
|
297
|
+
flexDirection: "column",
|
|
298
|
+
marginTop: 1,
|
|
299
|
+
children: [
|
|
300
|
+
/* @__PURE__ */ jsx(Text, {
|
|
301
|
+
bold: true,
|
|
302
|
+
children: sourceLabel(conflict.source)
|
|
303
|
+
}),
|
|
304
|
+
/* @__PURE__ */ jsx(Text, {
|
|
305
|
+
dimColor: true,
|
|
306
|
+
children: conflict.path
|
|
307
|
+
}),
|
|
308
|
+
/* @__PURE__ */ jsx(Box, {
|
|
309
|
+
flexDirection: "column",
|
|
310
|
+
paddingLeft: 2,
|
|
311
|
+
children: conflict.keys.map((key) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
312
|
+
Icons.bullet,
|
|
313
|
+
" ",
|
|
314
|
+
/* @__PURE__ */ jsx(Text, {
|
|
315
|
+
color: "yellow",
|
|
316
|
+
bold: true,
|
|
317
|
+
children: key
|
|
318
|
+
})
|
|
319
|
+
] }, key))
|
|
320
|
+
})
|
|
321
|
+
]
|
|
322
|
+
}, conflict.path)),
|
|
323
|
+
/* @__PURE__ */ jsx(Box, {
|
|
324
|
+
marginTop: 1,
|
|
325
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
326
|
+
dimColor: true,
|
|
327
|
+
children: hasManaged ? "Remove these keys (or run \"claude auth logout\"). Managed files are root-owned — ask your IT administrator." : "Remove these keys, or run \"claude auth logout\", then re-run the Wizard."
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
]
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/tui/screens/PortConflictScreen.tsx
|
|
335
|
+
/**
|
|
336
|
+
* PortConflictScreen — Modal when all OAuth port candidates are occupied.
|
|
337
|
+
*
|
|
338
|
+
* Shows every port the wizard tried and asks the user to free them manually.
|
|
339
|
+
*/
|
|
340
|
+
const PortConflictScreen = ({ store }) => {
|
|
341
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
342
|
+
if (!store.portConflictProcess) return null;
|
|
343
|
+
return /* @__PURE__ */ jsxs(ModalOverlay, {
|
|
344
|
+
borderColor: "#DC9300",
|
|
345
|
+
title: "OAuth ports in use",
|
|
346
|
+
width: 72,
|
|
347
|
+
footer: /* @__PURE__ */ jsx(ConfirmationInput, {
|
|
348
|
+
message: "Retry after freeing ports?",
|
|
349
|
+
confirmLabel: "Retry [Enter]",
|
|
350
|
+
cancelLabel: "Exit [Esc]",
|
|
351
|
+
onConfirm: () => store.resolvePortConflict(),
|
|
352
|
+
onCancel: () => store.requestExit(1)
|
|
353
|
+
}),
|
|
354
|
+
children: [
|
|
355
|
+
/* @__PURE__ */ jsx(Text, { children: "The wizard needs a local port for OAuth. We tried these ports which are all in use:" }),
|
|
356
|
+
/* @__PURE__ */ jsx(Box, {
|
|
357
|
+
flexDirection: "column",
|
|
358
|
+
marginY: 1,
|
|
359
|
+
paddingLeft: 2,
|
|
360
|
+
gap: 0,
|
|
361
|
+
children: OAUTH_PORTS.map((port) => /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
362
|
+
dimColor: true,
|
|
363
|
+
children: "Port "
|
|
364
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
365
|
+
bold: true,
|
|
366
|
+
children: port
|
|
367
|
+
})] }, port))
|
|
368
|
+
}),
|
|
369
|
+
/* @__PURE__ */ jsx(Text, {
|
|
370
|
+
dimColor: true,
|
|
371
|
+
children: "Please free one of these ports and retry."
|
|
372
|
+
})
|
|
373
|
+
]
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
//#endregion
|
|
377
|
+
//#region src/tui/screens/TaskNoticeScreen.tsx
|
|
378
|
+
/**
|
|
379
|
+
* TaskNoticeScreen — Modal shown before an optional step runs.
|
|
380
|
+
*
|
|
381
|
+
* Some steps stop to ask the user for something. This tells them so before the
|
|
382
|
+
* step starts, and lets them decline it. The copy comes from the program that
|
|
383
|
+
* owns the step; this screen only renders what it is given.
|
|
384
|
+
*/
|
|
385
|
+
const TaskNoticeScreen = ({ store }) => {
|
|
386
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
387
|
+
const notice = store.session.taskNotice;
|
|
388
|
+
if (!notice) return null;
|
|
389
|
+
return /* @__PURE__ */ jsxs(ModalOverlay, {
|
|
390
|
+
borderColor: Colors.primary,
|
|
391
|
+
title: notice.title,
|
|
392
|
+
width: 76,
|
|
393
|
+
footer: /* @__PURE__ */ jsx(ConfirmationInput, {
|
|
394
|
+
message: notice.prompt,
|
|
395
|
+
confirmLabel: notice.confirmLabel,
|
|
396
|
+
cancelLabel: notice.cancelLabel,
|
|
397
|
+
onConfirm: () => store.resolveTaskNotice(true),
|
|
398
|
+
onCancel: () => store.resolveTaskNotice(false)
|
|
399
|
+
}),
|
|
400
|
+
children: [
|
|
401
|
+
/* @__PURE__ */ jsx(Box, {
|
|
402
|
+
flexDirection: "column",
|
|
403
|
+
gap: 1,
|
|
404
|
+
children: notice.body.map((paragraph) => /* @__PURE__ */ jsx(Text, { children: paragraph }, paragraph))
|
|
405
|
+
}),
|
|
406
|
+
notice.items && notice.items.length > 0 && /* @__PURE__ */ jsx(Box, {
|
|
407
|
+
marginTop: 1,
|
|
408
|
+
paddingLeft: 2,
|
|
409
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
410
|
+
bold: true,
|
|
411
|
+
children: notice.items.join(", ")
|
|
412
|
+
})
|
|
413
|
+
}),
|
|
414
|
+
notice.docsUrl && /* @__PURE__ */ jsx(Box, {
|
|
415
|
+
marginTop: 1,
|
|
416
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
417
|
+
dimColor: true,
|
|
418
|
+
children: [
|
|
419
|
+
notice.docsLabel ?? "Learn more",
|
|
420
|
+
": ",
|
|
421
|
+
notice.docsUrl
|
|
422
|
+
]
|
|
423
|
+
})
|
|
424
|
+
})
|
|
425
|
+
]
|
|
426
|
+
});
|
|
427
|
+
};
|
|
428
|
+
//#endregion
|
|
429
|
+
//#region src/tui/screens/ManualAuthCodeScreen.tsx
|
|
430
|
+
/**
|
|
431
|
+
* ManualAuthCodeScreen — Paste an OAuth authorization code by hand.
|
|
432
|
+
*
|
|
433
|
+
* Fallback for headless/remote shells where the browser can't reach the
|
|
434
|
+
* wizard's local callback server. Shows the direct authorize URL (the localhost
|
|
435
|
+
* one is unreachable from another machine), then the user pastes either the
|
|
436
|
+
* full callback URL the browser was redirected to
|
|
437
|
+
* (`http://localhost:8239/callback?code=...`) or just the code. On submit we
|
|
438
|
+
* extract the code and resolve the in-flight OAuth flow; bad input shows inline
|
|
439
|
+
* feedback without leaving the screen.
|
|
440
|
+
*
|
|
441
|
+
* Opened from AuthScreen via a keypress; Esc dismisses it.
|
|
442
|
+
*/
|
|
443
|
+
const ManualAuthCodeScreen = ({ store }) => {
|
|
444
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
445
|
+
const { authorizeUrl } = store;
|
|
446
|
+
const [error, setError] = useState(null);
|
|
447
|
+
useInput((_input, key) => {
|
|
448
|
+
if (key.escape) store.dismissManualAuthCode();
|
|
449
|
+
});
|
|
450
|
+
const handleSubmit = (value) => {
|
|
451
|
+
const code = extractOAuthCode(value);
|
|
452
|
+
if (!code) {
|
|
453
|
+
setError("Couldn't find a code in that input. Paste the full callback URL or just the code.");
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
store.submitManualAuthCode(code);
|
|
457
|
+
};
|
|
458
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
459
|
+
flexDirection: "column",
|
|
460
|
+
flexGrow: 1,
|
|
461
|
+
children: [
|
|
462
|
+
/* @__PURE__ */ jsx(Box, {
|
|
463
|
+
marginBottom: 1,
|
|
464
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
465
|
+
bold: true,
|
|
466
|
+
color: Colors.accent,
|
|
467
|
+
children: [Icons.diamond, " Paste authorization code"]
|
|
468
|
+
})
|
|
469
|
+
}),
|
|
470
|
+
authorizeUrl && /* @__PURE__ */ jsxs(Box, {
|
|
471
|
+
flexDirection: "column",
|
|
472
|
+
marginBottom: 1,
|
|
473
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
474
|
+
dimColor: true,
|
|
475
|
+
children: "On a remote/headless machine the local login link won't open. Open this URL in a browser on any machine to authorize:"
|
|
476
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
477
|
+
flexShrink: 0,
|
|
478
|
+
marginTop: 1,
|
|
479
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
480
|
+
color: "cyan",
|
|
481
|
+
children: authorizeUrl
|
|
482
|
+
})
|
|
483
|
+
})]
|
|
484
|
+
}),
|
|
485
|
+
/* @__PURE__ */ jsx(Text, { children: "After authorizing, paste the callback URL it lands on — or just the code from it — here:" }),
|
|
486
|
+
/* @__PURE__ */ jsx(Box, {
|
|
487
|
+
marginTop: 1,
|
|
488
|
+
width: "100%",
|
|
489
|
+
children: /* @__PURE__ */ jsx(TextInput, {
|
|
490
|
+
placeholder: "http://localhost:8239/callback?code=… or the code",
|
|
491
|
+
onSubmit: handleSubmit
|
|
492
|
+
})
|
|
493
|
+
}),
|
|
494
|
+
error && /* @__PURE__ */ jsx(Box, {
|
|
495
|
+
marginTop: 1,
|
|
496
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
497
|
+
color: "yellow",
|
|
498
|
+
children: error
|
|
499
|
+
})
|
|
500
|
+
}),
|
|
501
|
+
/* @__PURE__ */ jsx(Box, {
|
|
502
|
+
marginTop: 1,
|
|
503
|
+
children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
504
|
+
/* @__PURE__ */ jsx(Text, {
|
|
505
|
+
color: Colors.accent,
|
|
506
|
+
children: "ENTER"
|
|
507
|
+
}),
|
|
508
|
+
/* @__PURE__ */ jsx(Text, {
|
|
509
|
+
dimColor: true,
|
|
510
|
+
children: " submit"
|
|
511
|
+
}),
|
|
512
|
+
/* @__PURE__ */ jsx(Text, {
|
|
513
|
+
dimColor: true,
|
|
514
|
+
children: " · "
|
|
515
|
+
}),
|
|
516
|
+
/* @__PURE__ */ jsx(Text, {
|
|
517
|
+
color: Colors.accent,
|
|
518
|
+
children: "ESC"
|
|
519
|
+
}),
|
|
520
|
+
/* @__PURE__ */ jsx(Text, {
|
|
521
|
+
dimColor: true,
|
|
522
|
+
children: " cancel"
|
|
523
|
+
})
|
|
524
|
+
] })
|
|
525
|
+
})
|
|
526
|
+
]
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/tui/screens/SetupScreen.tsx
|
|
531
|
+
/**
|
|
532
|
+
* SetupScreen — Generic framework disambiguation.
|
|
533
|
+
*
|
|
534
|
+
* Iterates unresolved setup questions from the FrameworkConfig
|
|
535
|
+
* and renders a PickerMenu for each. If all questions are auto-resolved,
|
|
536
|
+
* this screen is skipped entirely (the router skips it via its show() predicate).
|
|
537
|
+
*/
|
|
538
|
+
const SetupScreen = ({ store }) => {
|
|
539
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
540
|
+
const config = store.session.frameworkConfig;
|
|
541
|
+
const questions = config?.metadata.setup?.questions ?? [];
|
|
542
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
543
|
+
const [resolving, setResolving] = useState(true);
|
|
544
|
+
useEffect(() => {
|
|
545
|
+
(async () => {
|
|
546
|
+
for (const q of questions) {
|
|
547
|
+
if (q.key in store.session.frameworkContext) continue;
|
|
548
|
+
try {
|
|
549
|
+
const detected = await q.detect({ installDir: store.session.installDir });
|
|
550
|
+
if (detected !== null) store.setFrameworkContext(q.key, detected);
|
|
551
|
+
} catch {}
|
|
552
|
+
}
|
|
553
|
+
setResolving(false);
|
|
554
|
+
})();
|
|
555
|
+
}, []);
|
|
556
|
+
if (resolving) return /* @__PURE__ */ jsx(Box, {
|
|
557
|
+
flexDirection: "column",
|
|
558
|
+
flexGrow: 1,
|
|
559
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
560
|
+
dimColor: true,
|
|
561
|
+
children: "Detecting project configuration..."
|
|
562
|
+
})
|
|
563
|
+
});
|
|
564
|
+
const unresolved = questions.filter((q) => !(q.key in store.session.frameworkContext));
|
|
565
|
+
if (unresolved.length === 0) return null;
|
|
566
|
+
const question = unresolved[currentIndex] ?? unresolved[0];
|
|
567
|
+
if (!question) return null;
|
|
568
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
569
|
+
flexDirection: "column",
|
|
570
|
+
flexGrow: 1,
|
|
571
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
572
|
+
flexDirection: "column",
|
|
573
|
+
marginBottom: 1,
|
|
574
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
575
|
+
bold: true,
|
|
576
|
+
color: Colors.accent,
|
|
577
|
+
children: "Project Setup"
|
|
578
|
+
}), config && /* @__PURE__ */ jsxs(Text, {
|
|
579
|
+
dimColor: true,
|
|
580
|
+
children: [
|
|
581
|
+
"Configuring ",
|
|
582
|
+
config.metadata.name,
|
|
583
|
+
" integration"
|
|
584
|
+
]
|
|
585
|
+
})]
|
|
586
|
+
}), /* @__PURE__ */ jsx(PickerMenu, {
|
|
587
|
+
message: question.message,
|
|
588
|
+
options: question.options.map((o) => ({
|
|
589
|
+
label: o.label,
|
|
590
|
+
value: o.value,
|
|
591
|
+
hint: o.hint
|
|
592
|
+
})),
|
|
593
|
+
onSelect: (value) => {
|
|
594
|
+
const selected = Array.isArray(value) ? value[0] : value;
|
|
595
|
+
store.setFrameworkContext(question.key, selected);
|
|
596
|
+
if (unresolved.filter((q) => q.key !== question.key && !(q.key in store.session.frameworkContext)).length > 0) setCurrentIndex((i) => i + 1);
|
|
597
|
+
}
|
|
598
|
+
})]
|
|
599
|
+
});
|
|
600
|
+
};
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region src/shared/utils/clipboard.ts
|
|
603
|
+
/**
|
|
604
|
+
* Best-effort platform shell-outs for making a URL usable in any terminal —
|
|
605
|
+
* no third-party dependency.
|
|
606
|
+
*
|
|
607
|
+
* Two helpers back the `wizard_ask` link overlay, for terminals that can't
|
|
608
|
+
* linkify a wrapped URL (e.g. macOS Terminal.app, which lacks OSC 8 hyperlink
|
|
609
|
+
* support):
|
|
610
|
+
*
|
|
611
|
+
* - `copyToClipboard` — pipe the URL to the platform clipboard binary so the
|
|
612
|
+
* user can paste it into a browser.
|
|
613
|
+
* - `openInBrowser` — hand the URL to the platform opener so it launches
|
|
614
|
+
* directly, no copy/paste needed.
|
|
615
|
+
*
|
|
616
|
+
* Both shell out with `spawn` and never a shell, and pass the URL as an argv
|
|
617
|
+
* (clipboard via stdin) — so there's no shell-injection surface. Neither throws:
|
|
618
|
+
* they return false when no command is reachable (CI, headless, or the binary
|
|
619
|
+
* isn't installed).
|
|
620
|
+
*/
|
|
621
|
+
/** Platform clipboard commands, in preference order. */
|
|
622
|
+
function clipboardCommands() {
|
|
623
|
+
if (process.platform === "darwin") return [{
|
|
624
|
+
cmd: "pbcopy",
|
|
625
|
+
args: []
|
|
626
|
+
}];
|
|
627
|
+
if (process.platform === "win32") return [{
|
|
628
|
+
cmd: "clip",
|
|
629
|
+
args: []
|
|
630
|
+
}];
|
|
631
|
+
return [
|
|
632
|
+
{
|
|
633
|
+
cmd: "wl-copy",
|
|
634
|
+
args: []
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
cmd: "xclip",
|
|
638
|
+
args: ["-selection", "clipboard"]
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
cmd: "xsel",
|
|
642
|
+
args: ["--clipboard", "--input"]
|
|
643
|
+
}
|
|
644
|
+
];
|
|
645
|
+
}
|
|
646
|
+
function writeWith({ cmd, args }, text) {
|
|
647
|
+
return new Promise((resolve) => {
|
|
648
|
+
try {
|
|
649
|
+
const child = spawn(cmd, args);
|
|
650
|
+
child.on("error", () => resolve(false));
|
|
651
|
+
child.stdin.on("error", () => resolve(false));
|
|
652
|
+
child.on("close", (code) => resolve(code === 0));
|
|
653
|
+
child.stdin.end(text);
|
|
654
|
+
} catch {
|
|
655
|
+
resolve(false);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
async function copyToClipboard(text) {
|
|
660
|
+
for (const command of clipboardCommands()) if (await writeWith(command, text)) return true;
|
|
661
|
+
logToFile("[clipboard] no working clipboard command found");
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Platform browser-open commands, in preference order. The URL is one argv
|
|
666
|
+
* element — never concatenated into a shell string — so a `?kind=…` query or
|
|
667
|
+
* any other character can't escape into the command line.
|
|
668
|
+
*
|
|
669
|
+
* `win32` has no shell-free single binary for this: `start` is a `cmd` builtin,
|
|
670
|
+
* so we invoke `cmd /c start "" <url>` (the empty `""` is `start`'s window-title
|
|
671
|
+
* argument, which would otherwise swallow the URL). The URL is still a discrete
|
|
672
|
+
* argv element, not interpolated into the command string.
|
|
673
|
+
*
|
|
674
|
+
* Takes `platform` as an argument (defaulting to the host) so the mapping is a
|
|
675
|
+
* pure function the tests can exercise across platforms.
|
|
676
|
+
*/
|
|
677
|
+
function browserOpenCommands(url, platform = process.platform) {
|
|
678
|
+
if (platform === "darwin") return [{
|
|
679
|
+
cmd: "open",
|
|
680
|
+
args: [url]
|
|
681
|
+
}];
|
|
682
|
+
if (platform === "win32") return [{
|
|
683
|
+
cmd: "cmd",
|
|
684
|
+
args: [
|
|
685
|
+
"/c",
|
|
686
|
+
"start",
|
|
687
|
+
"",
|
|
688
|
+
url
|
|
689
|
+
]
|
|
690
|
+
}];
|
|
691
|
+
return [{
|
|
692
|
+
cmd: "xdg-open",
|
|
693
|
+
args: [url]
|
|
694
|
+
}, {
|
|
695
|
+
cmd: "x-www-browser",
|
|
696
|
+
args: [url]
|
|
697
|
+
}];
|
|
698
|
+
}
|
|
699
|
+
function spawnOpener({ cmd, args }) {
|
|
700
|
+
return new Promise((resolve) => {
|
|
701
|
+
try {
|
|
702
|
+
const child = spawn(cmd, args, { stdio: "ignore" });
|
|
703
|
+
child.on("error", () => resolve(false));
|
|
704
|
+
child.on("close", (code) => resolve(code === 0));
|
|
705
|
+
} catch {
|
|
706
|
+
resolve(false);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
async function openInBrowser(url) {
|
|
711
|
+
for (const command of browserOpenCommands(url)) if (await spawnOpener(command)) return true;
|
|
712
|
+
logToFile("[clipboard] no working browser-open command found");
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/tui/screens/AuthScreen.tsx
|
|
717
|
+
/**
|
|
718
|
+
* AuthScreen — Shown while waiting for OAuth authentication.
|
|
719
|
+
*
|
|
720
|
+
* Displays framework detection, a compressed privacy summary, a waiting
|
|
721
|
+
* spinner, and the login URL when available. [I] opens the full
|
|
722
|
+
* PrivacyPanel as an overlay. [P] (when loginUrl is set) lets the user
|
|
723
|
+
* paste the callback URL by hand. [C] copies the login URL to the
|
|
724
|
+
* clipboard — the exact string, so it can't be chopped by a soft-wrap.
|
|
725
|
+
*
|
|
726
|
+
* The router resolves past this screen once session.credentials is set.
|
|
727
|
+
*/
|
|
728
|
+
const AuthScreen = ({ store }) => {
|
|
729
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
730
|
+
const [showPrivacy, setShowPrivacy] = useState(false);
|
|
731
|
+
const [copyStatus, setCopyStatus] = useState("idle");
|
|
732
|
+
const [columns] = useStdoutDimensions();
|
|
733
|
+
const { session } = store;
|
|
734
|
+
const loginUrl = store.loginUrl;
|
|
735
|
+
const canPasteCode = Boolean(loginUrl);
|
|
736
|
+
const availableWidth = Math.min(columns, 120) - 2;
|
|
737
|
+
const urlFits = loginUrl ? loginUrl.length <= availableWidth : true;
|
|
738
|
+
const bindings = [];
|
|
739
|
+
if (!showPrivacy) {
|
|
740
|
+
if (canPasteCode && loginUrl) {
|
|
741
|
+
bindings.push({
|
|
742
|
+
match: ["p", "P"],
|
|
743
|
+
label: "P",
|
|
744
|
+
action: "paste auth code",
|
|
745
|
+
handler: () => store.showManualAuthCode()
|
|
746
|
+
});
|
|
747
|
+
bindings.push({
|
|
748
|
+
match: ["c", "C"],
|
|
749
|
+
label: "C",
|
|
750
|
+
action: "copy link",
|
|
751
|
+
handler: () => {
|
|
752
|
+
copyToClipboard(loginUrl).then((ok) => setCopyStatus(ok ? "copied" : "failed"));
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
bindings.push({
|
|
757
|
+
match: ["i", "I"],
|
|
758
|
+
label: "I",
|
|
759
|
+
action: PRIVACY_PANEL_LABEL.toLowerCase(),
|
|
760
|
+
handler: () => setShowPrivacy(true)
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
useKeyBindings("auth", bindings);
|
|
764
|
+
if (showPrivacy) return /* @__PURE__ */ jsx(IntroScreenLayout, {
|
|
765
|
+
installDir: session.installDir,
|
|
766
|
+
title: PRIVACY_PANEL_LABEL,
|
|
767
|
+
showSubtitle: false,
|
|
768
|
+
showDetection: false,
|
|
769
|
+
body: /* @__PURE__ */ jsx(PrivacyPanel, {}),
|
|
770
|
+
showPrivacy: false,
|
|
771
|
+
menuOptions: [{
|
|
772
|
+
label: "Back",
|
|
773
|
+
value: "back"
|
|
774
|
+
}],
|
|
775
|
+
menuAlign: "left",
|
|
776
|
+
onSelect: () => setShowPrivacy(false)
|
|
777
|
+
});
|
|
778
|
+
const config = session.frameworkConfig;
|
|
779
|
+
const frameworkLabel = session.detectedFrameworkLabel ?? config?.metadata.name;
|
|
780
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
781
|
+
flexDirection: "column",
|
|
782
|
+
flexGrow: 1,
|
|
783
|
+
children: [
|
|
784
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
785
|
+
flexDirection: "column",
|
|
786
|
+
marginBottom: 1,
|
|
787
|
+
children: [
|
|
788
|
+
/* @__PURE__ */ jsx(Text, {
|
|
789
|
+
bold: true,
|
|
790
|
+
color: Colors.accent,
|
|
791
|
+
children: "PostHog Setup Wizard"
|
|
792
|
+
}),
|
|
793
|
+
frameworkLabel && /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
794
|
+
color: "green",
|
|
795
|
+
children: ["✔", " "]
|
|
796
|
+
}), /* @__PURE__ */ jsxs(Text, { children: ["Framework: ", frameworkLabel] })] }),
|
|
797
|
+
config?.metadata.beta && /* @__PURE__ */ jsxs(Text, {
|
|
798
|
+
color: "yellow",
|
|
799
|
+
children: [
|
|
800
|
+
"[BETA] The ",
|
|
801
|
+
config.metadata.name,
|
|
802
|
+
" wizard is in beta. Questions or feedback? Email wizard@posthog.com"
|
|
803
|
+
]
|
|
804
|
+
}),
|
|
805
|
+
config?.metadata.preRunNotice && /* @__PURE__ */ jsx(Text, {
|
|
806
|
+
color: "yellow",
|
|
807
|
+
children: config.metadata.preRunNotice
|
|
808
|
+
})
|
|
809
|
+
]
|
|
810
|
+
}),
|
|
811
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
812
|
+
flexDirection: "column",
|
|
813
|
+
marginBottom: 1,
|
|
814
|
+
children: [
|
|
815
|
+
/* @__PURE__ */ jsx(Text, {
|
|
816
|
+
bold: true,
|
|
817
|
+
dimColor: true,
|
|
818
|
+
children: PRIVACY_PANEL_LABEL
|
|
819
|
+
}),
|
|
820
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
821
|
+
dimColor: true,
|
|
822
|
+
children: ["•", " Source files are read by Claude for AI context"]
|
|
823
|
+
}),
|
|
824
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
825
|
+
dimColor: true,
|
|
826
|
+
children: ["•", " .env* and secrets stay on your machine"]
|
|
827
|
+
}),
|
|
828
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
829
|
+
dimColor: true,
|
|
830
|
+
children: [
|
|
831
|
+
"•",
|
|
832
|
+
" Press ",
|
|
833
|
+
/* @__PURE__ */ jsx(Text, {
|
|
834
|
+
color: Colors.accent,
|
|
835
|
+
children: "[I]"
|
|
836
|
+
}),
|
|
837
|
+
" for full privacy & usage info"
|
|
838
|
+
]
|
|
839
|
+
})
|
|
840
|
+
]
|
|
841
|
+
}),
|
|
842
|
+
/* @__PURE__ */ jsx(LoadingBox, { message: "Waiting for authentication..." }),
|
|
843
|
+
loginUrl && /* @__PURE__ */ jsxs(Box, {
|
|
844
|
+
marginTop: 1,
|
|
845
|
+
marginBottom: 1,
|
|
846
|
+
flexDirection: "column",
|
|
847
|
+
children: [
|
|
848
|
+
urlFits ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
849
|
+
/* @__PURE__ */ jsx(Text, {
|
|
850
|
+
dimColor: true,
|
|
851
|
+
children: "If the browser didn't open, copy and paste this URL:"
|
|
852
|
+
}),
|
|
853
|
+
"\n\n",
|
|
854
|
+
/* @__PURE__ */ jsx(Text, {
|
|
855
|
+
color: "cyan",
|
|
856
|
+
children: loginUrl
|
|
857
|
+
})
|
|
858
|
+
] }) : /* @__PURE__ */ jsx(Text, {
|
|
859
|
+
color: "yellow",
|
|
860
|
+
children: "[This terminal is too small to show the full link for copying, resize your terminal]"
|
|
861
|
+
}),
|
|
862
|
+
/* @__PURE__ */ jsx(Box, {
|
|
863
|
+
marginTop: 1,
|
|
864
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
865
|
+
dimColor: true,
|
|
866
|
+
children: [
|
|
867
|
+
"Press ",
|
|
868
|
+
/* @__PURE__ */ jsx(Text, {
|
|
869
|
+
color: Colors.accent,
|
|
870
|
+
children: "[C]"
|
|
871
|
+
}),
|
|
872
|
+
" to copy the link · on a remote machine or devbox? Press",
|
|
873
|
+
" ",
|
|
874
|
+
/* @__PURE__ */ jsx(Text, {
|
|
875
|
+
color: Colors.accent,
|
|
876
|
+
children: "[P]"
|
|
877
|
+
}),
|
|
878
|
+
" to paste the callback URL."
|
|
879
|
+
]
|
|
880
|
+
})
|
|
881
|
+
}),
|
|
882
|
+
copyStatus === "copied" && /* @__PURE__ */ jsx(Box, {
|
|
883
|
+
marginTop: 1,
|
|
884
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
885
|
+
color: Colors.success,
|
|
886
|
+
children: [Icons.check, " Link copied to clipboard."]
|
|
887
|
+
})
|
|
888
|
+
}),
|
|
889
|
+
copyStatus === "failed" && /* @__PURE__ */ jsx(Box, {
|
|
890
|
+
marginTop: 1,
|
|
891
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
892
|
+
color: "yellow",
|
|
893
|
+
children: [
|
|
894
|
+
"Couldn't reach a clipboard on this machine — press",
|
|
895
|
+
" ",
|
|
896
|
+
/* @__PURE__ */ jsx(Text, {
|
|
897
|
+
color: Colors.accent,
|
|
898
|
+
children: "[P]"
|
|
899
|
+
}),
|
|
900
|
+
" to paste the callback URL instead."
|
|
901
|
+
]
|
|
902
|
+
})
|
|
903
|
+
})
|
|
904
|
+
]
|
|
905
|
+
})
|
|
906
|
+
]
|
|
907
|
+
});
|
|
908
|
+
};
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region src/tui/screens/RunScreen.tsx
|
|
911
|
+
/**
|
|
912
|
+
* RunScreen — Default observational view of the agent run.
|
|
913
|
+
*
|
|
914
|
+
* Tabs: Status (LearnCard + ProgressList), Event plan (when present),
|
|
915
|
+
* Tail logs, HN. Programs that need a different tab list ship their own
|
|
916
|
+
* screen component (see audit/AuditRunScreen.tsx).
|
|
917
|
+
*/
|
|
918
|
+
const RunScreen = ({ store }) => {
|
|
919
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
920
|
+
const [columns] = useStdoutDimensions();
|
|
921
|
+
const progressItems = store.tasks.map((t) => ({
|
|
922
|
+
label: t.label,
|
|
923
|
+
activeForm: t.activeForm,
|
|
924
|
+
status: t.status
|
|
925
|
+
}));
|
|
926
|
+
const statuses = store.statusMessages.length > 0 ? store.statusMessages : void 0;
|
|
927
|
+
const activeProgram = store.router.activeProgram;
|
|
928
|
+
const learnBlocks = useMemo(() => {
|
|
929
|
+
return (flowOwner(activeProgram).deck ?? getContentBlocks)(store);
|
|
930
|
+
}, [store, activeProgram]);
|
|
931
|
+
const programTips = flowOwner(activeProgram).tips?.(store);
|
|
932
|
+
const leftPane = store.learnCardComplete ? /* @__PURE__ */ jsx(TipsCard, {
|
|
933
|
+
store,
|
|
934
|
+
tips: programTips
|
|
935
|
+
}) : /* @__PURE__ */ jsx(LearnCard, {
|
|
936
|
+
store,
|
|
937
|
+
blocks: learnBlocks,
|
|
938
|
+
onComplete: () => store.setLearnCardComplete()
|
|
939
|
+
});
|
|
940
|
+
const progressList = /* @__PURE__ */ jsx(ProgressList, {
|
|
941
|
+
items: progressItems,
|
|
942
|
+
title: "Tasks"
|
|
943
|
+
});
|
|
944
|
+
return /* @__PURE__ */ jsx(TabContainer, {
|
|
945
|
+
tabs: [
|
|
946
|
+
{
|
|
947
|
+
id: "status",
|
|
948
|
+
label: "Status",
|
|
949
|
+
component: columns < 80 ? /* @__PURE__ */ jsx(Box, {
|
|
950
|
+
flexDirection: "column",
|
|
951
|
+
flexGrow: 1,
|
|
952
|
+
children: progressList
|
|
953
|
+
}) : /* @__PURE__ */ jsx(SplitView, {
|
|
954
|
+
left: leftPane,
|
|
955
|
+
right: progressList
|
|
956
|
+
})
|
|
957
|
+
},
|
|
958
|
+
...store.eventPlan.length > 0 ? [{
|
|
959
|
+
id: "events",
|
|
960
|
+
label: "Event plan",
|
|
961
|
+
component: /* @__PURE__ */ jsx(EventPlanViewer, { events: store.eventPlan })
|
|
962
|
+
}] : [],
|
|
963
|
+
{
|
|
964
|
+
id: "logs",
|
|
965
|
+
label: "Tail logs",
|
|
966
|
+
component: /* @__PURE__ */ jsx(LogViewer, { filePath: getLogFilePath() })
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
id: "visualizer",
|
|
970
|
+
label: "Visualizer",
|
|
971
|
+
component: /* @__PURE__ */ jsx(VisualizerTab, { store })
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
id: "hn",
|
|
975
|
+
label: "HN",
|
|
976
|
+
component: /* @__PURE__ */ jsx(HNViewer, {})
|
|
977
|
+
}
|
|
978
|
+
],
|
|
979
|
+
statusMessage: statuses,
|
|
980
|
+
expandableStatus: true,
|
|
981
|
+
store
|
|
982
|
+
});
|
|
983
|
+
};
|
|
984
|
+
//#endregion
|
|
985
|
+
//#region src/tui/screens/KeepSkillsScreen.tsx
|
|
986
|
+
/**
|
|
987
|
+
* KeepSkillsScreen — Ask whether to keep installed skills in .claude/skills/.
|
|
988
|
+
*
|
|
989
|
+
* Shown after the outro summary so users see the agent's output first,
|
|
990
|
+
* then decide whether to keep the skills that powered it.
|
|
991
|
+
*
|
|
992
|
+
* When done, calls store.setSkillsComplete() and exits the process.
|
|
993
|
+
*/
|
|
994
|
+
const WIZARD_MARKER = ".posthog-wizard";
|
|
995
|
+
const KeepSkillsScreen = ({ store }) => {
|
|
996
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
997
|
+
const [phase, setPhase] = useState("loading");
|
|
998
|
+
const [skills, setSkills] = useState([]);
|
|
999
|
+
const skillsDir = join$1(store.session.installDir, ".claude", "skills");
|
|
1000
|
+
useEffect(() => {
|
|
1001
|
+
(async () => {
|
|
1002
|
+
try {
|
|
1003
|
+
const dirs = (await readdir(skillsDir, { withFileTypes: true })).filter((e) => e.isDirectory());
|
|
1004
|
+
const result = [];
|
|
1005
|
+
for (const dir of dirs) {
|
|
1006
|
+
try {
|
|
1007
|
+
await access(join$1(skillsDir, dir.name, WIZARD_MARKER));
|
|
1008
|
+
} catch {
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
const children = (await readdir(join$1(skillsDir, dir.name))).filter((c) => c !== WIZARD_MARKER);
|
|
1012
|
+
result.push({
|
|
1013
|
+
name: dir.name,
|
|
1014
|
+
children
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
if (result.length === 0) {
|
|
1018
|
+
store.setSkillsComplete(true);
|
|
1019
|
+
exit();
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
setSkills(result);
|
|
1023
|
+
setPhase("ask");
|
|
1024
|
+
} catch {
|
|
1025
|
+
store.setSkillsComplete(true);
|
|
1026
|
+
exit();
|
|
1027
|
+
}
|
|
1028
|
+
})();
|
|
1029
|
+
}, []);
|
|
1030
|
+
const exit = () => {
|
|
1031
|
+
if (!store.mintHandoff) store.requestExit(0);
|
|
1032
|
+
};
|
|
1033
|
+
const handleKeep = () => {
|
|
1034
|
+
store.setSkillsComplete(true);
|
|
1035
|
+
exit();
|
|
1036
|
+
};
|
|
1037
|
+
const handleRemove = async () => {
|
|
1038
|
+
setPhase("removing");
|
|
1039
|
+
for (const skill of skills) try {
|
|
1040
|
+
await rm(join$1(skillsDir, skill.name), {
|
|
1041
|
+
recursive: true,
|
|
1042
|
+
force: true
|
|
1043
|
+
});
|
|
1044
|
+
} catch {}
|
|
1045
|
+
try {
|
|
1046
|
+
if ((await readdir(skillsDir)).length === 0) await rm(skillsDir, {
|
|
1047
|
+
recursive: true,
|
|
1048
|
+
force: true
|
|
1049
|
+
});
|
|
1050
|
+
} catch {}
|
|
1051
|
+
setPhase("done");
|
|
1052
|
+
setTimeout(() => {
|
|
1053
|
+
store.setSkillsComplete(false);
|
|
1054
|
+
exit();
|
|
1055
|
+
}, 600);
|
|
1056
|
+
};
|
|
1057
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1058
|
+
flexDirection: "column",
|
|
1059
|
+
flexGrow: 1,
|
|
1060
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1061
|
+
bold: true,
|
|
1062
|
+
color: Colors.accent,
|
|
1063
|
+
children: "Keep the skills?"
|
|
1064
|
+
}), /* @__PURE__ */ jsxs(Box, {
|
|
1065
|
+
marginTop: 1,
|
|
1066
|
+
flexDirection: "column",
|
|
1067
|
+
children: [
|
|
1068
|
+
phase === "loading" && /* @__PURE__ */ jsx(Text, {
|
|
1069
|
+
dimColor: true,
|
|
1070
|
+
children: "Checking installed skills..."
|
|
1071
|
+
}),
|
|
1072
|
+
phase === "ask" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1073
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1074
|
+
dimColor: true,
|
|
1075
|
+
children: "The wizard installed open-source skills that help AI coding agents integrate PostHog into your project:"
|
|
1076
|
+
}),
|
|
1077
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
1078
|
+
marginTop: 1,
|
|
1079
|
+
flexDirection: "column",
|
|
1080
|
+
marginLeft: 2,
|
|
1081
|
+
children: [
|
|
1082
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1083
|
+
dimColor: true,
|
|
1084
|
+
children: ".claude/"
|
|
1085
|
+
}),
|
|
1086
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1087
|
+
dimColor: true,
|
|
1088
|
+
children: " skills/"
|
|
1089
|
+
}),
|
|
1090
|
+
skills.map((skill) => /* @__PURE__ */ jsxs(Box, {
|
|
1091
|
+
flexDirection: "column",
|
|
1092
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1093
|
+
dimColor: true,
|
|
1094
|
+
children: [
|
|
1095
|
+
" ",
|
|
1096
|
+
skill.name,
|
|
1097
|
+
"/"
|
|
1098
|
+
]
|
|
1099
|
+
}), skill.children.map((child) => /* @__PURE__ */ jsxs(Text, {
|
|
1100
|
+
dimColor: true,
|
|
1101
|
+
children: [" ", child]
|
|
1102
|
+
}, child))]
|
|
1103
|
+
}, skill.name))
|
|
1104
|
+
]
|
|
1105
|
+
}),
|
|
1106
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1107
|
+
marginTop: 1,
|
|
1108
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
1109
|
+
dimColor: true,
|
|
1110
|
+
children: ["Source: ", /* @__PURE__ */ jsx(Text, {
|
|
1111
|
+
color: "cyan",
|
|
1112
|
+
children: "https://github.com/PostHog/context-mill"
|
|
1113
|
+
})]
|
|
1114
|
+
})
|
|
1115
|
+
}),
|
|
1116
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1117
|
+
marginTop: 1,
|
|
1118
|
+
children: /* @__PURE__ */ jsx(ConfirmationInput, {
|
|
1119
|
+
message: "Keep the installed skills?",
|
|
1120
|
+
confirmLabel: "Keep [Enter]",
|
|
1121
|
+
cancelLabel: "Remove [Esc]",
|
|
1122
|
+
onConfirm: handleKeep,
|
|
1123
|
+
onCancel: () => void handleRemove()
|
|
1124
|
+
})
|
|
1125
|
+
})
|
|
1126
|
+
] }),
|
|
1127
|
+
phase === "removing" && /* @__PURE__ */ jsx(Text, {
|
|
1128
|
+
dimColor: true,
|
|
1129
|
+
children: "Removing skills..."
|
|
1130
|
+
}),
|
|
1131
|
+
phase === "done" && /* @__PURE__ */ jsx(Text, {
|
|
1132
|
+
dimColor: true,
|
|
1133
|
+
children: "Skills removed."
|
|
1134
|
+
})
|
|
1135
|
+
]
|
|
1136
|
+
})]
|
|
1137
|
+
});
|
|
1138
|
+
};
|
|
1139
|
+
//#endregion
|
|
1140
|
+
//#region src/tui/screens/MintFailureScreen.tsx
|
|
1141
|
+
/** Two blocks of the PostHog flag, left of the headline. */
|
|
1142
|
+
const Mark = () => /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
1143
|
+
color: "#1D4AFF",
|
|
1144
|
+
children: "█"
|
|
1145
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1146
|
+
color: Colors.accent,
|
|
1147
|
+
children: "█"
|
|
1148
|
+
})] });
|
|
1149
|
+
function MintFailureScreen({ store, services }) {
|
|
1150
|
+
const [columns] = useStdoutDimensions();
|
|
1151
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
1152
|
+
const busy = useRef(false);
|
|
1153
|
+
const [working, setWorking] = useState(null);
|
|
1154
|
+
const [error, setError] = useState(null);
|
|
1155
|
+
const [report, setReport] = useState(false);
|
|
1156
|
+
const [retry, setRetry] = useState("save");
|
|
1157
|
+
const { spellbook } = store;
|
|
1158
|
+
const select = async (action) => {
|
|
1159
|
+
if (busy.current) return;
|
|
1160
|
+
if (action === "exit" || action === "continue") return store.setMintHandoff(action);
|
|
1161
|
+
if (action === "report") return setReport(true);
|
|
1162
|
+
const task = action === "retry" ? retry : action;
|
|
1163
|
+
setRetry(task);
|
|
1164
|
+
busy.current = true;
|
|
1165
|
+
setError(null);
|
|
1166
|
+
setWorking("Saving skill...");
|
|
1167
|
+
try {
|
|
1168
|
+
const saved = store.spellbook ?? await services.leaveSpellbook();
|
|
1169
|
+
store.setSpellbook(saved);
|
|
1170
|
+
if (task !== "save") {
|
|
1171
|
+
setWorking(`Opening ${task === "claude" ? "Claude Code" : "Codex"}...`);
|
|
1172
|
+
await services.openAgent(task, saved.path);
|
|
1173
|
+
store.setMintHandoff("continue");
|
|
1174
|
+
}
|
|
1175
|
+
} catch (err) {
|
|
1176
|
+
setError(store.spellbook ? err instanceof Error ? err.message : "Could not open your agent." : "Could not save the skill. Check this folder is writable and try again.");
|
|
1177
|
+
} finally {
|
|
1178
|
+
busy.current = false;
|
|
1179
|
+
setWorking(null);
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
const options = error ? [
|
|
1183
|
+
{
|
|
1184
|
+
label: "Try again",
|
|
1185
|
+
value: "retry"
|
|
1186
|
+
},
|
|
1187
|
+
...spellbook ? [{
|
|
1188
|
+
label: "Continue setup",
|
|
1189
|
+
value: "continue"
|
|
1190
|
+
}] : [],
|
|
1191
|
+
{
|
|
1192
|
+
label: "Exit",
|
|
1193
|
+
value: "exit"
|
|
1194
|
+
}
|
|
1195
|
+
] : spellbook ? [{
|
|
1196
|
+
label: "Continue",
|
|
1197
|
+
value: "continue"
|
|
1198
|
+
}, {
|
|
1199
|
+
label: "Exit",
|
|
1200
|
+
value: "exit"
|
|
1201
|
+
}] : report ? [{
|
|
1202
|
+
label: "Save skill",
|
|
1203
|
+
value: "save"
|
|
1204
|
+
}, {
|
|
1205
|
+
label: "Exit",
|
|
1206
|
+
value: "exit"
|
|
1207
|
+
}] : [
|
|
1208
|
+
{
|
|
1209
|
+
label: "Save skill",
|
|
1210
|
+
value: "save"
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
label: "Open in Claude Code",
|
|
1214
|
+
value: "claude"
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
label: "Open in Codex",
|
|
1218
|
+
value: "codex"
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
label: "Report this issue",
|
|
1222
|
+
value: "report"
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
label: "Exit",
|
|
1226
|
+
value: "exit"
|
|
1227
|
+
}
|
|
1228
|
+
];
|
|
1229
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1230
|
+
flexDirection: "column",
|
|
1231
|
+
flexGrow: 1,
|
|
1232
|
+
alignItems: "center",
|
|
1233
|
+
justifyContent: "center",
|
|
1234
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
1235
|
+
width: Math.max(1, Math.min(78, columns - 4)),
|
|
1236
|
+
flexDirection: "column",
|
|
1237
|
+
alignItems: "center",
|
|
1238
|
+
children: [
|
|
1239
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1240
|
+
bold: true,
|
|
1241
|
+
children: [
|
|
1242
|
+
/* @__PURE__ */ jsx(Mark, {}),
|
|
1243
|
+
" ",
|
|
1244
|
+
spellbook ? "Skill saved" : MINT_FAILURE_MESSAGE,
|
|
1245
|
+
" 🦔"
|
|
1246
|
+
]
|
|
1247
|
+
}),
|
|
1248
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1249
|
+
marginTop: 1,
|
|
1250
|
+
children: /* @__PURE__ */ jsx(Text, { children: spellbook ? spellbook.skillsIncluded ? "Ask your agent to read this file and complete the task." : "The skill could not be downloaded. This file has instructions and links for your agent." : report ? MINT_FAILURE_CONTACT : MINT_FAILURE_BODY })
|
|
1251
|
+
}),
|
|
1252
|
+
report && !spellbook && /* @__PURE__ */ jsx(Box, {
|
|
1253
|
+
marginTop: 1,
|
|
1254
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1255
|
+
color: "cyan",
|
|
1256
|
+
wrap: "wrap",
|
|
1257
|
+
children: services.logPath
|
|
1258
|
+
})
|
|
1259
|
+
}),
|
|
1260
|
+
spellbook && /* @__PURE__ */ jsx(Box, {
|
|
1261
|
+
marginTop: 1,
|
|
1262
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1263
|
+
color: "cyan",
|
|
1264
|
+
wrap: "wrap",
|
|
1265
|
+
children: spellbook.path
|
|
1266
|
+
})
|
|
1267
|
+
}),
|
|
1268
|
+
error && /* @__PURE__ */ jsx(Box, {
|
|
1269
|
+
marginTop: 1,
|
|
1270
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1271
|
+
color: Colors.error,
|
|
1272
|
+
children: error
|
|
1273
|
+
})
|
|
1274
|
+
}),
|
|
1275
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1276
|
+
marginTop: 1,
|
|
1277
|
+
flexDirection: "column",
|
|
1278
|
+
children: working ? /* @__PURE__ */ jsx(Text, { children: working }) : /* @__PURE__ */ jsx(PickerMenu, {
|
|
1279
|
+
options,
|
|
1280
|
+
onSelect: (action) => void select(action)
|
|
1281
|
+
}, [
|
|
1282
|
+
report,
|
|
1283
|
+
Boolean(spellbook),
|
|
1284
|
+
Boolean(error)
|
|
1285
|
+
].join("-"))
|
|
1286
|
+
})
|
|
1287
|
+
]
|
|
1288
|
+
})
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
//#endregion
|
|
1292
|
+
//#region src/tui/services/coding-agent-launcher.ts
|
|
1293
|
+
const quote = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
1294
|
+
const batchQuote = (value) => `"${value.replace(/%/g, "%%")}"`;
|
|
1295
|
+
async function executable(name) {
|
|
1296
|
+
const extensions = process.platform === "win32" ? [
|
|
1297
|
+
".exe",
|
|
1298
|
+
".cmd",
|
|
1299
|
+
".bat"
|
|
1300
|
+
] : [""];
|
|
1301
|
+
for (const directory of (process.env.PATH ?? "").split(path$1.delimiter)) {
|
|
1302
|
+
if (!path$1.isAbsolute(directory)) continue;
|
|
1303
|
+
for (const extension of extensions) {
|
|
1304
|
+
const candidate = path$1.join(directory, name + extension);
|
|
1305
|
+
try {
|
|
1306
|
+
await access(candidate, constants.X_OK);
|
|
1307
|
+
return candidate;
|
|
1308
|
+
} catch {
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
throw new Error(`${name === "claude" ? "Claude Code" : name === "codex" ? "Codex" : name} is not installed or could not be found.`);
|
|
1314
|
+
}
|
|
1315
|
+
function launch(command, args, cwd, verbatim = false) {
|
|
1316
|
+
return new Promise((resolve, reject) => {
|
|
1317
|
+
const child = spawn(command, args, {
|
|
1318
|
+
cwd,
|
|
1319
|
+
stdio: "ignore",
|
|
1320
|
+
detached: true,
|
|
1321
|
+
shell: false,
|
|
1322
|
+
windowsVerbatimArguments: verbatim
|
|
1323
|
+
});
|
|
1324
|
+
const timer = setTimeout(() => {
|
|
1325
|
+
child.unref();
|
|
1326
|
+
resolve();
|
|
1327
|
+
}, 500);
|
|
1328
|
+
child.once("error", (error) => {
|
|
1329
|
+
clearTimeout(timer);
|
|
1330
|
+
reject(error);
|
|
1331
|
+
});
|
|
1332
|
+
child.once("exit", (code) => {
|
|
1333
|
+
clearTimeout(timer);
|
|
1334
|
+
if (code === 0) resolve();
|
|
1335
|
+
else reject(/* @__PURE__ */ new Error("Could not open a terminal for your agent."));
|
|
1336
|
+
});
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
async function openCodingAgent(agent, directory, spellbookPath) {
|
|
1340
|
+
const cwd = path$1.resolve(directory);
|
|
1341
|
+
const command = await executable(agent);
|
|
1342
|
+
const prompt = `Read <${path$1.resolve(spellbookPath)}> and complete the task described in the Wizard's spell books. Inspect what has already been done before making changes.`;
|
|
1343
|
+
if (process.platform === "linux") {
|
|
1344
|
+
await launch(await executable("x-terminal-emulator"), [
|
|
1345
|
+
"-e",
|
|
1346
|
+
command,
|
|
1347
|
+
prompt
|
|
1348
|
+
], cwd);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const mac = process.platform === "darwin";
|
|
1352
|
+
if (!mac && process.platform !== "win32") throw new Error("Opening an agent is not supported on this system. Use the saved skill path to continue.");
|
|
1353
|
+
const folder = await mkdtemp(path$1.join(tmpdir(), "wizard-handoff-"));
|
|
1354
|
+
const script = path$1.join(folder, mac ? "open-agent.command" : "open-agent.cmd");
|
|
1355
|
+
try {
|
|
1356
|
+
await writeFile(script, (mac ? [
|
|
1357
|
+
"#!/bin/sh",
|
|
1358
|
+
`rm -f ${quote(script)}`,
|
|
1359
|
+
`rmdir ${quote(folder)}`,
|
|
1360
|
+
`export PATH=${quote(process.env.PATH ?? "")}`,
|
|
1361
|
+
`cd ${quote(cwd)} || exit 1`,
|
|
1362
|
+
`exec ${quote(command)} ${quote(prompt)}`
|
|
1363
|
+
] : [
|
|
1364
|
+
"@echo off",
|
|
1365
|
+
"chcp 65001 >nul",
|
|
1366
|
+
"setlocal DisableDelayedExpansion",
|
|
1367
|
+
`cd /d ${batchQuote(cwd)} || exit /b 1`,
|
|
1368
|
+
`del "%~f0" & rmdir ${batchQuote(folder)} & ${batchQuote(command)} ${batchQuote(prompt)}`
|
|
1369
|
+
]).join(mac ? "\n" : "\r\n") + "\n", { mode: 448 });
|
|
1370
|
+
if (mac) {
|
|
1371
|
+
await launch("/usr/bin/open", [script], cwd);
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const shell = process.env.ComSpec ?? "cmd.exe";
|
|
1375
|
+
await launch(shell, [
|
|
1376
|
+
"/d",
|
|
1377
|
+
"/s",
|
|
1378
|
+
"/c",
|
|
1379
|
+
`"start "" ${shell} /d /c "${script}""`
|
|
1380
|
+
], folder, true);
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
await rm(folder, {
|
|
1383
|
+
recursive: true,
|
|
1384
|
+
force: true
|
|
1385
|
+
});
|
|
1386
|
+
throw error;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
//#endregion
|
|
1390
|
+
//#region src/tui/services/wizard-spellbook.ts
|
|
1391
|
+
function selectSkills(menu, session, program) {
|
|
1392
|
+
const entries = Object.values(menu.categories).flat().filter((entry) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(entry.id));
|
|
1393
|
+
const skillId = program.skillId ?? session.skillId;
|
|
1394
|
+
const exact = entries.find((entry) => entry.id === skillId);
|
|
1395
|
+
if (exact) return [exact];
|
|
1396
|
+
const framework = session.integration;
|
|
1397
|
+
const referenceGroup = skillId === framework ? "integration" : skillId;
|
|
1398
|
+
const references = entries.filter((entry) => entry.group === referenceGroup && entry.framework === framework);
|
|
1399
|
+
const reference = references.find((entry) => entry.default) ?? references[0];
|
|
1400
|
+
if (reference) return [reference];
|
|
1401
|
+
const flow = program.agentFlow ?? program.id;
|
|
1402
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1403
|
+
for (const entry of entries) {
|
|
1404
|
+
const group = entry.group;
|
|
1405
|
+
if (!group || group !== flow && !group.startsWith(`${flow}-`) || entry.framework && entry.framework !== framework) continue;
|
|
1406
|
+
if (!groups.has(group) || entry.default) groups.set(group, entry);
|
|
1407
|
+
}
|
|
1408
|
+
return [...groups.values()];
|
|
1409
|
+
}
|
|
1410
|
+
function spellbookText(session, program, skills) {
|
|
1411
|
+
const framework = session.frameworkConfig?.metadata;
|
|
1412
|
+
const runDocs = typeof program.run === "object" ? program.run.docsUrl : void 0;
|
|
1413
|
+
const docs = [...new Set([
|
|
1414
|
+
framework?.docsUrl,
|
|
1415
|
+
runDocs,
|
|
1416
|
+
POSTHOG_DOCS_URL
|
|
1417
|
+
])].filter((url) => Boolean(url));
|
|
1418
|
+
return [
|
|
1419
|
+
"# Wizard spell book",
|
|
1420
|
+
"",
|
|
1421
|
+
"Complete this task in the project containing this spell book:",
|
|
1422
|
+
"",
|
|
1423
|
+
program.description,
|
|
1424
|
+
"",
|
|
1425
|
+
`Wizard program: ${program.id}`,
|
|
1426
|
+
...framework ? [`Detected framework: ${framework.name}`] : [],
|
|
1427
|
+
"",
|
|
1428
|
+
"Inspect the existing project and its instructions before making changes. Check what has already been done, complete the requested task within its stated scope, then run the relevant checks and explain any remaining manual steps.",
|
|
1429
|
+
"",
|
|
1430
|
+
"Use your own coding agent and its authentication. The Wizard could not continue its inference session. No Wizard credentials are included; ask the user for any credentials the task requires and keep them out of source code.",
|
|
1431
|
+
"",
|
|
1432
|
+
"## Setup instructions",
|
|
1433
|
+
"",
|
|
1434
|
+
...skills.length ? [
|
|
1435
|
+
"Read the downloaded skills and their referenced files:",
|
|
1436
|
+
"",
|
|
1437
|
+
...skills.map((skill) => `- [${skill.id}](skills/${skill.id}/SKILL.md)`),
|
|
1438
|
+
"",
|
|
1439
|
+
"Downloaded skills may cover only part of the task. Use the official documentation below to fill any gaps."
|
|
1440
|
+
] : ["No matching skills could be downloaded. Use the official documentation below to complete the task."],
|
|
1441
|
+
"",
|
|
1442
|
+
"## Official documentation",
|
|
1443
|
+
"",
|
|
1444
|
+
...docs.map((url) => `- ${url}`),
|
|
1445
|
+
""
|
|
1446
|
+
].join("\n");
|
|
1447
|
+
}
|
|
1448
|
+
async function writeWizardSpellbook(session, program) {
|
|
1449
|
+
const parent = path.join(session.installDir, ".posthog");
|
|
1450
|
+
await fs.mkdir(parent, { recursive: true });
|
|
1451
|
+
const directory = await fs.mkdtemp(path.join(parent, "wizard-spellbook-"));
|
|
1452
|
+
const readme = path.join(directory, "README.md");
|
|
1453
|
+
await fs.writeFile(readme, spellbookText(session, program, []));
|
|
1454
|
+
const menu = await fetchSkillMenu(getSkillsBaseUrl(), {
|
|
1455
|
+
timeoutMs: 1e4,
|
|
1456
|
+
maxAttempts: 1
|
|
1457
|
+
});
|
|
1458
|
+
const installed = [];
|
|
1459
|
+
for (const skill of menu ? selectSkills(menu, session, program) : []) if ((await downloadSkill(skill, directory, {
|
|
1460
|
+
skillsRoot: "skills",
|
|
1461
|
+
scan: (dir) => scanInstalledSkill(dir, void 0)
|
|
1462
|
+
})).success) installed.push(skill);
|
|
1463
|
+
else await fs.rm(path.join(directory, "skills", skill.id), {
|
|
1464
|
+
recursive: true,
|
|
1465
|
+
force: true
|
|
1466
|
+
});
|
|
1467
|
+
await fs.writeFile(readme, spellbookText(session, program, installed));
|
|
1468
|
+
return {
|
|
1469
|
+
path: readme,
|
|
1470
|
+
skillsIncluded: installed.length > 0
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
//#endregion
|
|
1474
|
+
//#region src/tui/screens/ExitScreen.tsx
|
|
1475
|
+
/**
|
|
1476
|
+
* ExitScreen — Final step in every program.
|
|
1477
|
+
*
|
|
1478
|
+
* Renders nothing. Immediately asks the host to end the run with 0.
|
|
1479
|
+
* The cleanup handler in start-tui.ts handles the exit summary line.
|
|
1480
|
+
*/
|
|
1481
|
+
const ExitScreen = ({ store }) => {
|
|
1482
|
+
useEffect(() => {
|
|
1483
|
+
if (!store.mintHandoff) store.requestExit(0);
|
|
1484
|
+
}, []);
|
|
1485
|
+
return null;
|
|
1486
|
+
};
|
|
1487
|
+
//#endregion
|
|
1488
|
+
//#region src/tui/screens/AuthErrorScreen.tsx
|
|
1489
|
+
/**
|
|
1490
|
+
* AuthErrorScreen — Shown when the PostHog LLM Gateway returns a 401.
|
|
1491
|
+
*
|
|
1492
|
+
* Distinct causes, most specific first:
|
|
1493
|
+
* 0. The OAuth grant is gone — a pre-run refresh already got `invalid_grant`
|
|
1494
|
+
* from the token endpoint. The only branch backed by a server verdict
|
|
1495
|
+
* rather than inference, so it wins; re-running is the whole fix.
|
|
1496
|
+
* 1. Claude Code settings.json / managed-settings overrides ANTHROPIC_*
|
|
1497
|
+
* env vars — auth conflict. Tell the user to log out of Claude Code.
|
|
1498
|
+
* 2. The PostHog API key itself was rejected — bad prefix, missing scope,
|
|
1499
|
+
* expired, or wrong region. Don't blame Claude Code in this case.
|
|
1500
|
+
*/
|
|
1501
|
+
const AuthErrorScreen = ({ store }) => {
|
|
1502
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
1503
|
+
useDismissOnAnyKey(() => store.requestExit(1));
|
|
1504
|
+
const detail = store.authErrorDetail;
|
|
1505
|
+
const hasSettingsConflict = detail?.hasSettingsConflict ?? true;
|
|
1506
|
+
const conflicts = detail?.conflicts ?? [];
|
|
1507
|
+
const usingManagedLogin = detail?.usingManagedLogin ?? false;
|
|
1508
|
+
const credentialPlaces = detail?.credentialPlaces ?? [];
|
|
1509
|
+
const sessionExpired = detail?.sessionExpired ?? false;
|
|
1510
|
+
const logFilePath = detail?.logFilePath;
|
|
1511
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1512
|
+
flexDirection: "column",
|
|
1513
|
+
flexGrow: 1,
|
|
1514
|
+
children: [
|
|
1515
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1516
|
+
color: "red",
|
|
1517
|
+
bold: true,
|
|
1518
|
+
children: ["✘", " Authentication error"]
|
|
1519
|
+
}),
|
|
1520
|
+
sessionExpired ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1521
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1522
|
+
flexDirection: "column",
|
|
1523
|
+
marginTop: 1,
|
|
1524
|
+
children: /* @__PURE__ */ jsx(Text, { children: "Your PostHog login expired while the wizard was running, so the LLM Gateway rejected it (401). Nothing on this machine is misconfigured — the session simply ran out." })
|
|
1525
|
+
}),
|
|
1526
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1527
|
+
marginTop: 1,
|
|
1528
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1529
|
+
dimColor: true,
|
|
1530
|
+
children: "Re-run the wizard and log in again:"
|
|
1531
|
+
})
|
|
1532
|
+
}),
|
|
1533
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1534
|
+
flexDirection: "column",
|
|
1535
|
+
marginTop: 1,
|
|
1536
|
+
paddingLeft: 2,
|
|
1537
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1538
|
+
color: "cyan",
|
|
1539
|
+
children: "npx @posthog/wizard"
|
|
1540
|
+
})
|
|
1541
|
+
}),
|
|
1542
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1543
|
+
marginTop: 1,
|
|
1544
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1545
|
+
dimColor: true,
|
|
1546
|
+
children: "Any files the agent already wrote are still in your project."
|
|
1547
|
+
})
|
|
1548
|
+
})
|
|
1549
|
+
] }) : usingManagedLogin ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1550
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1551
|
+
flexDirection: "column",
|
|
1552
|
+
marginTop: 1,
|
|
1553
|
+
children: /* @__PURE__ */ jsx(Text, { children: "Conflicting Anthropic credentials. The agent signed in with an existing Claude login instead of the PostHog token the Wizard provided, so the LLM Gateway rejected it (401)." })
|
|
1554
|
+
}),
|
|
1555
|
+
credentialPlaces.length > 0 && /* @__PURE__ */ jsxs(Box, {
|
|
1556
|
+
flexDirection: "column",
|
|
1557
|
+
marginTop: 1,
|
|
1558
|
+
paddingLeft: 2,
|
|
1559
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1560
|
+
dimColor: true,
|
|
1561
|
+
children: "Conflicting credentials may come from:"
|
|
1562
|
+
}), credentialPlaces.map((place) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
1563
|
+
"•",
|
|
1564
|
+
" ",
|
|
1565
|
+
place
|
|
1566
|
+
] }, place))]
|
|
1567
|
+
}),
|
|
1568
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1569
|
+
marginTop: 1,
|
|
1570
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1571
|
+
dimColor: true,
|
|
1572
|
+
children: "Log out of Claude Code (clears the stored login), then re-run the Wizard:"
|
|
1573
|
+
})
|
|
1574
|
+
}),
|
|
1575
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1576
|
+
flexDirection: "column",
|
|
1577
|
+
marginTop: 1,
|
|
1578
|
+
paddingLeft: 2,
|
|
1579
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1580
|
+
color: "cyan",
|
|
1581
|
+
children: "claude auth logout"
|
|
1582
|
+
})
|
|
1583
|
+
})
|
|
1584
|
+
] }) : hasSettingsConflict ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1585
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1586
|
+
flexDirection: "column",
|
|
1587
|
+
marginTop: 1,
|
|
1588
|
+
children: /* @__PURE__ */ jsx(Text, { children: "The Wizard couldn't connect to the PostHog LLM Gateway. Claude Code settings on this machine override the Wizard's credentials." })
|
|
1589
|
+
}),
|
|
1590
|
+
conflicts.length > 0 && /* @__PURE__ */ jsx(Box, {
|
|
1591
|
+
flexDirection: "column",
|
|
1592
|
+
marginTop: 1,
|
|
1593
|
+
paddingLeft: 2,
|
|
1594
|
+
children: conflicts.map((conflict) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
1595
|
+
"•",
|
|
1596
|
+
" ",
|
|
1597
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1598
|
+
bold: true,
|
|
1599
|
+
children: conflict.path
|
|
1600
|
+
}),
|
|
1601
|
+
" sets",
|
|
1602
|
+
" ",
|
|
1603
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1604
|
+
color: "yellow",
|
|
1605
|
+
children: conflict.keys.join(", ")
|
|
1606
|
+
})
|
|
1607
|
+
] }, conflict.path))
|
|
1608
|
+
}),
|
|
1609
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1610
|
+
marginTop: 1,
|
|
1611
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1612
|
+
dimColor: true,
|
|
1613
|
+
children: "Remove those keys from the file(s) above, or log out of Claude Code, then re-run the Wizard:"
|
|
1614
|
+
})
|
|
1615
|
+
}),
|
|
1616
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1617
|
+
flexDirection: "column",
|
|
1618
|
+
marginTop: 1,
|
|
1619
|
+
paddingLeft: 2,
|
|
1620
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1621
|
+
color: "cyan",
|
|
1622
|
+
children: "claude auth logout"
|
|
1623
|
+
})
|
|
1624
|
+
})
|
|
1625
|
+
] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Box, {
|
|
1626
|
+
flexDirection: "column",
|
|
1627
|
+
marginTop: 1,
|
|
1628
|
+
children: /* @__PURE__ */ jsx(Text, { children: "The PostHog LLM Gateway rejected the API key. Common causes:" })
|
|
1629
|
+
}), /* @__PURE__ */ jsxs(Box, {
|
|
1630
|
+
flexDirection: "column",
|
|
1631
|
+
marginTop: 1,
|
|
1632
|
+
paddingLeft: 2,
|
|
1633
|
+
children: [
|
|
1634
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1635
|
+
"•",
|
|
1636
|
+
" Wrong key type — pass a personal API key (",
|
|
1637
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1638
|
+
color: "cyan",
|
|
1639
|
+
children: "phx_xxx"
|
|
1640
|
+
}),
|
|
1641
|
+
")."
|
|
1642
|
+
] }),
|
|
1643
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1644
|
+
dimColor: true,
|
|
1645
|
+
children: [" ", "pha_ is an OAuth access token, phc_ is a project key."]
|
|
1646
|
+
}),
|
|
1647
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1648
|
+
"•",
|
|
1649
|
+
" Missing scope — the key needs",
|
|
1650
|
+
" ",
|
|
1651
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1652
|
+
color: "cyan",
|
|
1653
|
+
children: "llm_gateway:read"
|
|
1654
|
+
}),
|
|
1655
|
+
"."
|
|
1656
|
+
] }),
|
|
1657
|
+
/* @__PURE__ */ jsxs(Text, { children: ["•", " Expired or revoked key."] }),
|
|
1658
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1659
|
+
"•",
|
|
1660
|
+
" Region mismatch — ",
|
|
1661
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1662
|
+
color: "cyan",
|
|
1663
|
+
children: "--region"
|
|
1664
|
+
}),
|
|
1665
|
+
" must match where the key was issued (us vs eu)."
|
|
1666
|
+
] })
|
|
1667
|
+
]
|
|
1668
|
+
})] }),
|
|
1669
|
+
logFilePath && /* @__PURE__ */ jsx(Box, {
|
|
1670
|
+
marginTop: 1,
|
|
1671
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
1672
|
+
dimColor: true,
|
|
1673
|
+
children: ["Verbose log: ", logFilePath]
|
|
1674
|
+
})
|
|
1675
|
+
}),
|
|
1676
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1677
|
+
marginTop: 1,
|
|
1678
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1679
|
+
color: Colors.muted,
|
|
1680
|
+
children: "Press any key to exit"
|
|
1681
|
+
})
|
|
1682
|
+
})
|
|
1683
|
+
]
|
|
1684
|
+
});
|
|
1685
|
+
};
|
|
1686
|
+
//#endregion
|
|
1687
|
+
//#region src/tui/screens/SessionTimeoutScreen.tsx
|
|
1688
|
+
/**
|
|
1689
|
+
* SessionTimeoutScreen — shown when the OAuth login window expires before the
|
|
1690
|
+
* user completes authorization.
|
|
1691
|
+
*
|
|
1692
|
+
* Pushed as an overlay so it takes priority over the gated AuthScreen (which
|
|
1693
|
+
* never completes without credentials). Terminal state: any key exits, since
|
|
1694
|
+
* the only way forward is to re-run the wizard for a fresh login window.
|
|
1695
|
+
*/
|
|
1696
|
+
const TIMEOUT_MINUTES = Math.round(OAUTH_TIMEOUT_MS / 6e4);
|
|
1697
|
+
const SessionTimeoutScreen = ({ store }) => {
|
|
1698
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
1699
|
+
useDismissOnAnyKey(() => store.requestExit(1));
|
|
1700
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1701
|
+
flexDirection: "column",
|
|
1702
|
+
flexGrow: 1,
|
|
1703
|
+
children: [
|
|
1704
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1705
|
+
color: "red",
|
|
1706
|
+
bold: true,
|
|
1707
|
+
children: ["✘", " Login timed out"]
|
|
1708
|
+
}),
|
|
1709
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1710
|
+
marginTop: 1,
|
|
1711
|
+
children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
1712
|
+
"The OAuth link timed out after ",
|
|
1713
|
+
TIMEOUT_MINUTES,
|
|
1714
|
+
" minutes."
|
|
1715
|
+
] })
|
|
1716
|
+
}),
|
|
1717
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1718
|
+
marginTop: 1,
|
|
1719
|
+
children: /* @__PURE__ */ jsx(Text, { children: "Re-run the wizard to get a fresh link and try again." })
|
|
1720
|
+
}),
|
|
1721
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1722
|
+
marginTop: 1,
|
|
1723
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1724
|
+
color: Colors.muted,
|
|
1725
|
+
children: "Press any key to exit"
|
|
1726
|
+
})
|
|
1727
|
+
})
|
|
1728
|
+
]
|
|
1729
|
+
});
|
|
1730
|
+
};
|
|
1731
|
+
//#endregion
|
|
1732
|
+
//#region src/tui/screens/WizardAskScreen.tsx
|
|
1733
|
+
/**
|
|
1734
|
+
* WizardAskScreen — Overlay for the `wizard_ask` MCP tool.
|
|
1735
|
+
*
|
|
1736
|
+
* Walks the agent's question list one at a time and accumulates answers.
|
|
1737
|
+
* When the user submits the last question, the store resolves the
|
|
1738
|
+
* pending request and the overlay pops, returning the agent to its run.
|
|
1739
|
+
*/
|
|
1740
|
+
/**
|
|
1741
|
+
* Key policy for the ask overlay: Esc declines the whole request, everything
|
|
1742
|
+
* else is left to the focused input. Extracted so the wiring is unit-testable
|
|
1743
|
+
* without a live Ink render (the test suite stubs `useInput` to a no-op).
|
|
1744
|
+
*/
|
|
1745
|
+
function handleAskKey(key, store) {
|
|
1746
|
+
if (key.escape) store.cancelPendingQuestion();
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* What pressing Esc actually does, phrased for the footer hint.
|
|
1750
|
+
*
|
|
1751
|
+
* Esc declines the *whole* request — {@link WizardStore.cancelPendingQuestion}
|
|
1752
|
+
* builds a cancelled answer for every question, so the ones already typed are
|
|
1753
|
+
* discarded too. The footer used to label that "skip", which on a multi-question
|
|
1754
|
+
* request reads as "skip this field": the warehouse task walks a source's
|
|
1755
|
+
* credentials one field at a time, several of them optional, and a user who
|
|
1756
|
+
* pressed Esc to pass on an optional field instead threw away the whole source
|
|
1757
|
+
* and dropped the agent onto its browser-handoff fallback. Naming the scope
|
|
1758
|
+
* costs a few characters and makes the destructive key read as destructive.
|
|
1759
|
+
*/
|
|
1760
|
+
function askEscapeHint(total, answered) {
|
|
1761
|
+
if (total <= 1) return "skip";
|
|
1762
|
+
if (answered <= 0) return `skip all ${total} questions`;
|
|
1763
|
+
return `skip all ${total} questions, discarding the ${answered} you answered`;
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Whether the overlay must mask what the user types for this question.
|
|
1767
|
+
*
|
|
1768
|
+
* A `sensitive` answer is one the wizard has already promised to treat as a
|
|
1769
|
+
* secret: `wizard_ask` vaults it and hands the agent an opaque `secretRef`, so
|
|
1770
|
+
* the raw string never enters the model's conversation. Every other boundary
|
|
1771
|
+
* guards it the same way — the skip events carry no free text, handoff prose is
|
|
1772
|
+
* kept out of telemetry — but the overlay that collects it echoed it back in
|
|
1773
|
+
* plain text as it was typed, which is the one place a database password or an
|
|
1774
|
+
* API key is read by a person other than its owner: a shared screen, a pairing
|
|
1775
|
+
* session, a recorded terminal.
|
|
1776
|
+
*
|
|
1777
|
+
* `kind` is checked as well as the flag. The tool already rejects `sensitive`
|
|
1778
|
+
* on a picker, and a picker has nothing to mask, so this keeps the predicate
|
|
1779
|
+
* total over a question rather than relying on that rejection.
|
|
1780
|
+
*/
|
|
1781
|
+
function shouldMaskAnswer(question) {
|
|
1782
|
+
return question.kind === "text" && question.sensitive === true;
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Whether an answer would leave a required question effectively unanswered.
|
|
1786
|
+
*
|
|
1787
|
+
* The `wizard_ask` schema marks fields `required` (defaulting to true), but the
|
|
1788
|
+
* overlay used to submit whatever the input held — so pressing Enter on an empty
|
|
1789
|
+
* required credential field sent an empty string on to the agent. A blank host
|
|
1790
|
+
* or password is indistinguishable there from a real answer, so the warehouse
|
|
1791
|
+
* task ran on it, failed source creation, and dead-ended with no signal that the
|
|
1792
|
+
* user had in effect declined. Blocking the empty submit keeps the contract the
|
|
1793
|
+
* schema already advertises; Esc stays the way to skip. Optional fields
|
|
1794
|
+
* (`required === false`) are never blocked, and pickers always yield a value.
|
|
1795
|
+
*/
|
|
1796
|
+
function isRequiredButEmpty(question, value) {
|
|
1797
|
+
if (question.required === false) return false;
|
|
1798
|
+
return Array.isArray(value) ? value.length === 0 : value.trim().length === 0;
|
|
1799
|
+
}
|
|
1800
|
+
const WizardAskScreen = ({ store }) => {
|
|
1801
|
+
useSyncExternalStore((cb) => store.subscribe(cb), () => store.getSnapshot());
|
|
1802
|
+
const pending = store.session.pendingQuestion;
|
|
1803
|
+
const [index, setIndex] = useState(0);
|
|
1804
|
+
const [answers, setAnswers] = useState({});
|
|
1805
|
+
const [lastPendingId, setLastPendingId] = useState(null);
|
|
1806
|
+
const [requiredHint, setRequiredHint] = useState(false);
|
|
1807
|
+
const [linkStatus, setLinkStatus] = useState("idle");
|
|
1808
|
+
const richLinks = pending?.richLinks ?? false;
|
|
1809
|
+
const currentPrompt = pending?.questions[index]?.prompt ?? "";
|
|
1810
|
+
const promptUrls = richLinks ? extractUrls(currentPrompt) : [];
|
|
1811
|
+
const soleUrl = promptUrls.length === 1 ? promptUrls[0] : null;
|
|
1812
|
+
const isTextQuestion = pending?.questions[index]?.kind === "text";
|
|
1813
|
+
const canActOnLink = !!soleUrl && !isTextQuestion;
|
|
1814
|
+
const linkSplitIndex = canActOnLink && soleUrl ? currentPrompt.indexOf(soleUrl) : -1;
|
|
1815
|
+
const splitLink = soleUrl !== null && linkSplitIndex !== -1;
|
|
1816
|
+
const linkEnd = splitLink && soleUrl ? linkSplitIndex + soleUrl.length : 0;
|
|
1817
|
+
const promptHead = splitLink ? currentPrompt.slice(0, linkEnd) : currentPrompt;
|
|
1818
|
+
const linkTrailingText = splitLink ? currentPrompt.slice(linkEnd).replace(/^\s+/, "") : "";
|
|
1819
|
+
useEffect(() => {
|
|
1820
|
+
setLinkStatus("idle");
|
|
1821
|
+
if (!soleUrl) return;
|
|
1822
|
+
copyToClipboard(soleUrl);
|
|
1823
|
+
}, [soleUrl]);
|
|
1824
|
+
useKeyBindings("wizard-ask-link", canActOnLink && soleUrl ? [{
|
|
1825
|
+
match: "o",
|
|
1826
|
+
label: "o",
|
|
1827
|
+
action: "open in browser",
|
|
1828
|
+
handler: () => {
|
|
1829
|
+
openInBrowser(soleUrl).then((ok) => {
|
|
1830
|
+
if (ok) setLinkStatus("opened");
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
}, {
|
|
1834
|
+
match: "c",
|
|
1835
|
+
label: "c",
|
|
1836
|
+
action: "copy link",
|
|
1837
|
+
handler: () => {
|
|
1838
|
+
copyToClipboard(soleUrl).then((ok) => {
|
|
1839
|
+
if (ok) setLinkStatus("copied");
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
}] : []);
|
|
1843
|
+
useInput((_input, key) => handleAskKey(key, store));
|
|
1844
|
+
if (!pending) return null;
|
|
1845
|
+
if (pending.id !== lastPendingId) {
|
|
1846
|
+
setLastPendingId(pending.id);
|
|
1847
|
+
setIndex(0);
|
|
1848
|
+
setAnswers({});
|
|
1849
|
+
setRequiredHint(false);
|
|
1850
|
+
return null;
|
|
1851
|
+
}
|
|
1852
|
+
const question = pending.questions[index];
|
|
1853
|
+
if (!question) return null;
|
|
1854
|
+
const total = pending.questions.length;
|
|
1855
|
+
const progress = total > 1 ? `Question ${index + 1} of ${total}` : null;
|
|
1856
|
+
const escapeHint = askEscapeHint(total, index);
|
|
1857
|
+
const canSkipOne = question.kind === "text" && question.required === false;
|
|
1858
|
+
const submit = (value) => {
|
|
1859
|
+
if (isRequiredButEmpty(question, value)) {
|
|
1860
|
+
setRequiredHint(true);
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
setRequiredHint(false);
|
|
1864
|
+
const next = {
|
|
1865
|
+
...answers,
|
|
1866
|
+
[question.id]: value
|
|
1867
|
+
};
|
|
1868
|
+
if (index + 1 < total) {
|
|
1869
|
+
setAnswers(next);
|
|
1870
|
+
setIndex(index + 1);
|
|
1871
|
+
store.noteAskProgress();
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
store.resolvePendingQuestion(next);
|
|
1875
|
+
};
|
|
1876
|
+
return /* @__PURE__ */ jsxs(ModalOverlay, {
|
|
1877
|
+
borderColor: Colors.accent,
|
|
1878
|
+
title: `${Icons.diamond} ${pending.source}`,
|
|
1879
|
+
titleColor: Colors.accent,
|
|
1880
|
+
width: 72,
|
|
1881
|
+
children: [
|
|
1882
|
+
progress && /* @__PURE__ */ jsx(Box, {
|
|
1883
|
+
marginBottom: 1,
|
|
1884
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1885
|
+
dimColor: true,
|
|
1886
|
+
children: progress
|
|
1887
|
+
})
|
|
1888
|
+
}),
|
|
1889
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1890
|
+
flexDirection: "column",
|
|
1891
|
+
children: pending.richLinks ? /* @__PURE__ */ jsx(LinkText, { text: promptHead }) : /* @__PURE__ */ jsx(Text, { children: question.prompt })
|
|
1892
|
+
}),
|
|
1893
|
+
canActOnLink && /* @__PURE__ */ jsx(Box, {
|
|
1894
|
+
marginTop: 1,
|
|
1895
|
+
children: linkStatus === "idle" ? /* @__PURE__ */ jsxs(Text, {
|
|
1896
|
+
dimColor: true,
|
|
1897
|
+
children: [
|
|
1898
|
+
"Press ",
|
|
1899
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1900
|
+
color: Colors.accent,
|
|
1901
|
+
children: "o"
|
|
1902
|
+
}),
|
|
1903
|
+
" to open it in your browser, or ",
|
|
1904
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1905
|
+
color: Colors.accent,
|
|
1906
|
+
children: "c"
|
|
1907
|
+
}),
|
|
1908
|
+
" to copy the link."
|
|
1909
|
+
]
|
|
1910
|
+
}) : linkStatus === "opened" ? /* @__PURE__ */ jsxs(Text, {
|
|
1911
|
+
color: Colors.success,
|
|
1912
|
+
children: [Icons.check, " Opening the link in your browser…"]
|
|
1913
|
+
}) : /* @__PURE__ */ jsxs(Text, {
|
|
1914
|
+
color: Colors.success,
|
|
1915
|
+
children: [Icons.check, " Link copied to clipboard. Paste it in your browser."]
|
|
1916
|
+
})
|
|
1917
|
+
}),
|
|
1918
|
+
linkTrailingText && /* @__PURE__ */ jsx(Box, {
|
|
1919
|
+
marginTop: 1,
|
|
1920
|
+
children: /* @__PURE__ */ jsx(Text, { children: linkTrailingText })
|
|
1921
|
+
}),
|
|
1922
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1923
|
+
marginTop: 1,
|
|
1924
|
+
children: /* @__PURE__ */ jsx(QuestionInput, {
|
|
1925
|
+
question,
|
|
1926
|
+
onSubmit: submit
|
|
1927
|
+
}, `${pending.id}:${question.id}`)
|
|
1928
|
+
}),
|
|
1929
|
+
requiredHint && /* @__PURE__ */ jsx(Box, {
|
|
1930
|
+
marginTop: 1,
|
|
1931
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
1932
|
+
color: Colors.accent,
|
|
1933
|
+
children: [
|
|
1934
|
+
Icons.warning,
|
|
1935
|
+
" This field is required — type an answer, or press ESC to ",
|
|
1936
|
+
escapeHint,
|
|
1937
|
+
"."
|
|
1938
|
+
]
|
|
1939
|
+
})
|
|
1940
|
+
}),
|
|
1941
|
+
canSkipOne && /* @__PURE__ */ jsx(Box, {
|
|
1942
|
+
marginTop: 1,
|
|
1943
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
1944
|
+
dimColor: true,
|
|
1945
|
+
children: [
|
|
1946
|
+
"Optional — press ",
|
|
1947
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1948
|
+
color: Colors.accent,
|
|
1949
|
+
children: "ENTER"
|
|
1950
|
+
}),
|
|
1951
|
+
" on an empty answer to skip just this one."
|
|
1952
|
+
]
|
|
1953
|
+
})
|
|
1954
|
+
}),
|
|
1955
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1956
|
+
marginTop: 1,
|
|
1957
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
1958
|
+
dimColor: true,
|
|
1959
|
+
children: [
|
|
1960
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1961
|
+
color: Colors.accent,
|
|
1962
|
+
children: "ESC"
|
|
1963
|
+
}),
|
|
1964
|
+
" ",
|
|
1965
|
+
escapeHint
|
|
1966
|
+
]
|
|
1967
|
+
})
|
|
1968
|
+
})
|
|
1969
|
+
]
|
|
1970
|
+
});
|
|
1971
|
+
};
|
|
1972
|
+
const QuestionInput = ({ question, onSubmit }) => {
|
|
1973
|
+
switch (question.kind) {
|
|
1974
|
+
case "single": return /* @__PURE__ */ jsx(PickerMenu, {
|
|
1975
|
+
options: (question.options ?? []).map((o) => ({
|
|
1976
|
+
label: o.label,
|
|
1977
|
+
value: o.value
|
|
1978
|
+
})),
|
|
1979
|
+
onSelect: (value) => {
|
|
1980
|
+
onSubmit(Array.isArray(value) ? value[0] : value);
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1983
|
+
case "multi": {
|
|
1984
|
+
const multiOptions = (question.options ?? []).map((o) => ({
|
|
1985
|
+
label: o.label,
|
|
1986
|
+
value: o.value,
|
|
1987
|
+
description: o.description
|
|
1988
|
+
}));
|
|
1989
|
+
return /* @__PURE__ */ jsx(PickerMenu, {
|
|
1990
|
+
mode: "multi",
|
|
1991
|
+
optionMarginBottom: multiOptions.some((o) => o.description) ? 1 : 0,
|
|
1992
|
+
options: multiOptions,
|
|
1993
|
+
onSelect: (value) => {
|
|
1994
|
+
onSubmit(Array.isArray(value) ? value : [value]);
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
case "text": return /* @__PURE__ */ jsxs(Box, {
|
|
1999
|
+
flexDirection: "column",
|
|
2000
|
+
width: "100%",
|
|
2001
|
+
children: [/* @__PURE__ */ jsx(shouldMaskAnswer(question) ? PasswordInput : TextInput, {
|
|
2002
|
+
placeholder: "Type your answer",
|
|
2003
|
+
onSubmit: (value) => onSubmit(value)
|
|
2004
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
2005
|
+
marginTop: 1,
|
|
2006
|
+
width: "100%",
|
|
2007
|
+
justifyContent: "flex-end",
|
|
2008
|
+
children: /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
2009
|
+
color: Colors.accent,
|
|
2010
|
+
children: "ENTER"
|
|
2011
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
2012
|
+
dimColor: true,
|
|
2013
|
+
children: " submit"
|
|
2014
|
+
})] })
|
|
2015
|
+
})]
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
//#endregion
|
|
2020
|
+
//#region src/shared/mcp-clients/login-client.ts
|
|
2021
|
+
function isLoginCapable(client) {
|
|
2022
|
+
return typeof client === "object" && client !== null && "loginCommand" in client;
|
|
2023
|
+
}
|
|
2024
|
+
//#endregion
|
|
2025
|
+
//#region src/shared/mcp-clients/browser-client.ts
|
|
2026
|
+
function isBrowserFinishable(c) {
|
|
2027
|
+
return typeof c === "object" && c !== null && "connectorUrl" in c && "finishInstruction" in c;
|
|
2028
|
+
}
|
|
2029
|
+
//#endregion
|
|
2030
|
+
//#region src/tui/services/mcp-installer.ts
|
|
2031
|
+
/**
|
|
2032
|
+
* McpInstaller — service layer between McpScreen and MCP business logic.
|
|
2033
|
+
*
|
|
2034
|
+
* Decouples the screen from step internals. Testable, swappable,
|
|
2035
|
+
* no dynamic imports in React components.
|
|
2036
|
+
*/
|
|
2037
|
+
/**
|
|
2038
|
+
* Production McpInstaller backed by real MCP client detection and installation.
|
|
2039
|
+
*/
|
|
2040
|
+
function createMcpInstaller() {
|
|
2041
|
+
let cachedClients = [];
|
|
2042
|
+
return {
|
|
2043
|
+
async detectClients() {
|
|
2044
|
+
const supported = await getSupportedClients();
|
|
2045
|
+
cachedClients = supported.map((c) => ({
|
|
2046
|
+
name: c.name,
|
|
2047
|
+
raw: c
|
|
2048
|
+
}));
|
|
2049
|
+
return supported.map((c) => ({
|
|
2050
|
+
name: c.name,
|
|
2051
|
+
supportsPlugin: isPluginCapable(c) && c.supportsPlugin(),
|
|
2052
|
+
pluginBundlesMcp: isPluginCapable(c) && c.pluginBundlesMcpServer(),
|
|
2053
|
+
finish: isBrowserFinishable(c) ? {
|
|
2054
|
+
url: c.connectorUrl,
|
|
2055
|
+
instruction: c.finishInstruction
|
|
2056
|
+
} : void 0,
|
|
2057
|
+
loginCommand: isLoginCapable(c) && c.loginCommand(false) || void 0,
|
|
2058
|
+
pluginLoginCommand: isLoginCapable(c) && c.pluginLoginCommand?.() || void 0
|
|
2059
|
+
}));
|
|
2060
|
+
},
|
|
2061
|
+
async install(clientNames, features, apiKey) {
|
|
2062
|
+
const resolvedFeatures = features ?? [...ALL_FEATURE_VALUES];
|
|
2063
|
+
const toInstall = cachedClients.filter((c) => clientNames.includes(c.name)).map((c) => c.raw);
|
|
2064
|
+
if (toInstall.length === 0) {
|
|
2065
|
+
logToFile(`[McpInstaller] No clients matched. clientNames=${JSON.stringify(clientNames)}, cached=${JSON.stringify(cachedClients.map((c) => c.name))}`);
|
|
2066
|
+
return [];
|
|
2067
|
+
}
|
|
2068
|
+
const results = [];
|
|
2069
|
+
for (const client of toInstall) {
|
|
2070
|
+
const name = client.name;
|
|
2071
|
+
try {
|
|
2072
|
+
const result = await client.addServer(apiKey, resolvedFeatures, false);
|
|
2073
|
+
results.push(toClientResult(name, result));
|
|
2074
|
+
if (!result?.success) logToFile(`[McpInstaller] addServer returned success=false for ${name}: ${redactSecrets(result?.reason ?? "no reason given")}`);
|
|
2075
|
+
} catch (err) {
|
|
2076
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
2077
|
+
logToFile(`[McpInstaller] addServer threw for ${name}: ${redactSecrets(reason)}`);
|
|
2078
|
+
results.push(toClientResult(name, {
|
|
2079
|
+
success: false,
|
|
2080
|
+
reason
|
|
2081
|
+
}));
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
return results;
|
|
2085
|
+
},
|
|
2086
|
+
async remove(local) {
|
|
2087
|
+
const installed = await getInstalledClients(local);
|
|
2088
|
+
if (installed.length === 0) return [];
|
|
2089
|
+
return removeMCPServer(installed, local);
|
|
2090
|
+
},
|
|
2091
|
+
async installPlugins(clientNames) {
|
|
2092
|
+
const pluginClients = getSupportedPluginClients(cachedClients.filter((c) => clientNames.includes(c.name)).map((c) => c.raw));
|
|
2093
|
+
const results = await installPlugins(pluginClients);
|
|
2094
|
+
const already = namesWithStatus(results, "unchanged");
|
|
2095
|
+
analytics.wizardCapture("mcp plugins installed", {
|
|
2096
|
+
clients: [...namesWithStatus(results, "changed"), ...already],
|
|
2097
|
+
already_installed: already,
|
|
2098
|
+
attempted: pluginClients.map((c) => c.name)
|
|
2099
|
+
});
|
|
2100
|
+
return results;
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
//#endregion
|
|
2105
|
+
//#region src/tui/screen-registry.tsx
|
|
2106
|
+
function createServices(store) {
|
|
2107
|
+
return {
|
|
2108
|
+
get logPath() {
|
|
2109
|
+
return path$1.resolve(getLogFilePath());
|
|
2110
|
+
},
|
|
2111
|
+
openAgent: (agent, spellbookPath) => openCodingAgent(agent, store.session.installDir, spellbookPath),
|
|
2112
|
+
leaveSpellbook: () => writeWizardSpellbook(store.session, getProgramConfig(store.router.activeProgram)),
|
|
2113
|
+
mcpInstaller: createMcpInstaller()
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
function createScreens(store, services) {
|
|
2117
|
+
const programScreens = {};
|
|
2118
|
+
for (const program of listFlowOwners()) for (const [id, render] of Object.entries(program.screens ?? {})) programScreens[id] ??= render(store, services);
|
|
2119
|
+
const core = {
|
|
2120
|
+
["settings-override"]: /* @__PURE__ */ jsx(SettingsOverrideScreen, { store }),
|
|
2121
|
+
["managed-settings"]: /* @__PURE__ */ jsx(ManagedSettingsScreen, { store }),
|
|
2122
|
+
["port-conflict"]: /* @__PURE__ */ jsx(PortConflictScreen, { store }),
|
|
2123
|
+
["task-notice"]: /* @__PURE__ */ jsx(TaskNoticeScreen, { store }),
|
|
2124
|
+
["manual-auth-code"]: /* @__PURE__ */ jsx(ManualAuthCodeScreen, { store }),
|
|
2125
|
+
["auth-error"]: /* @__PURE__ */ jsx(AuthErrorScreen, { store }),
|
|
2126
|
+
["session-timeout"]: /* @__PURE__ */ jsx(SessionTimeoutScreen, { store }),
|
|
2127
|
+
["wizard-ask"]: /* @__PURE__ */ jsx(WizardAskScreen, { store }),
|
|
2128
|
+
["health-check"]: /* @__PURE__ */ jsx(HealthCheckScreen, { store }),
|
|
2129
|
+
["setup"]: /* @__PURE__ */ jsx(SetupScreen, { store }),
|
|
2130
|
+
["auth"]: /* @__PURE__ */ jsx(AuthScreen, { store }),
|
|
2131
|
+
["ai-opt-in"]: /* @__PURE__ */ jsx(AiOptInRequiredScreen, { store }),
|
|
2132
|
+
["run"]: /* @__PURE__ */ jsx(RunScreen, { store }),
|
|
2133
|
+
["mcp"]: /* @__PURE__ */ jsx(McpScreen, {
|
|
2134
|
+
store,
|
|
2135
|
+
installer: services.mcpInstaller
|
|
2136
|
+
}),
|
|
2137
|
+
["slack-connect"]: /* @__PURE__ */ jsx(SlackConnectScreen, { store }),
|
|
2138
|
+
["keep-skills"]: /* @__PURE__ */ jsx(KeepSkillsScreen, { store }),
|
|
2139
|
+
["outro"]: /* @__PURE__ */ jsx(OutroScreen, { store }),
|
|
2140
|
+
["mint-failure"]: /* @__PURE__ */ jsx(MintFailureScreen, {
|
|
2141
|
+
store,
|
|
2142
|
+
services
|
|
2143
|
+
}),
|
|
2144
|
+
["exit"]: /* @__PURE__ */ jsx(ExitScreen, { store })
|
|
2145
|
+
};
|
|
2146
|
+
return {
|
|
2147
|
+
...programScreens,
|
|
2148
|
+
...core
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
//#endregion
|
|
2152
|
+
//#region src/tui/auth/login.ts
|
|
2153
|
+
/** Log in once through OAuth for the session's launch values. */
|
|
2154
|
+
async function oauthLogin(session, programId, store) {
|
|
2155
|
+
logToFile("[agent-runner] starting OAuth");
|
|
2156
|
+
const data = await getOrAskForProjectData({
|
|
2157
|
+
store,
|
|
2158
|
+
signup: session.signup,
|
|
2159
|
+
projectId: session.projectId,
|
|
2160
|
+
email: session.email,
|
|
2161
|
+
region: session.region,
|
|
2162
|
+
baseUrl: session.baseUrl,
|
|
2163
|
+
localMcp: session.localMcp,
|
|
2164
|
+
programId
|
|
2165
|
+
});
|
|
2166
|
+
analytics.wizardCapture("auth complete", { project_id: data.projectId });
|
|
2167
|
+
return {
|
|
2168
|
+
posthog: {
|
|
2169
|
+
accessToken: data.accessToken,
|
|
2170
|
+
refreshToken: data.refreshToken,
|
|
2171
|
+
expiresAt: data.expiresAt,
|
|
2172
|
+
oauthClientId: data.oauthClientId,
|
|
2173
|
+
projectApiKey: data.projectApiKey,
|
|
2174
|
+
host: data.host,
|
|
2175
|
+
projectId: data.projectId,
|
|
2176
|
+
missingScopes: data.missingScopes
|
|
2177
|
+
},
|
|
2178
|
+
project: data.project,
|
|
2179
|
+
apiUser: data.user,
|
|
2180
|
+
roleAtOrganization: data.roleAtOrganization
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
/** The OAuth login as `runProgram`'s provider, reading the store's session when it is asked. */
|
|
2184
|
+
function oauthCredentials(store) {
|
|
2185
|
+
return { resolve: (programId) => oauthLogin(store.session, programId, store) };
|
|
2186
|
+
}
|
|
2187
|
+
//#endregion
|
|
2188
|
+
//#region src/tui/App.tsx
|
|
2189
|
+
const App = ({ store }) => {
|
|
2190
|
+
const services = useMemo(() => createServices(store), [store]);
|
|
2191
|
+
return /* @__PURE__ */ jsx(ScreenContainer, {
|
|
2192
|
+
store,
|
|
2193
|
+
screens: useMemo(() => createScreens(store, services), [store, services])
|
|
2194
|
+
});
|
|
2195
|
+
};
|
|
2196
|
+
//#endregion
|
|
2197
|
+
//#region src/tui/exit-line.ts
|
|
2198
|
+
const RESET_ATTRS = "\x1B[0m";
|
|
2199
|
+
const GREEN = "\x1B[32m";
|
|
2200
|
+
const BOLD = "\x1B[1m";
|
|
2201
|
+
const DIM = "\x1B[2m";
|
|
2202
|
+
/**
|
|
2203
|
+
* Mirrors the hidden Ctrl+T HUD's tally into post-exit scrollback — but only
|
|
2204
|
+
* when the HUD is actually visible at exit (`store.tokenHudVisible`, which
|
|
2205
|
+
* defaults on in dev/test and off in production; `useDismissOnAnyKey`
|
|
2206
|
+
* already keeps Ctrl+T from also dismissing the outro screen underneath
|
|
2207
|
+
* it). A production run where the user never toggled it on shouldn't have a
|
|
2208
|
+
* cost number appear from nowhere once the TUI tears down. `null` when the
|
|
2209
|
+
* HUD is hidden, or the run never produced any usage (e.g. non-agent
|
|
2210
|
+
* programs).
|
|
2211
|
+
*/
|
|
2212
|
+
function tokenCostLine(store) {
|
|
2213
|
+
if (!store.tokenHudVisible) return null;
|
|
2214
|
+
const usage = store.tokenUsage;
|
|
2215
|
+
if (totalTokenCount(usage) === 0) return null;
|
|
2216
|
+
return `${DIM}${usage.costIsFinal ? "Final cost" : "Cost (estimate)"}: ${formatCostUsd(usage.costUsd)} (in ${formatTokenCount(usage.inputTokens)} · out ${formatTokenCount(usage.outputTokens)} · cache read ${formatTokenCount(usage.cacheReadTokens)} · cache write ${formatTokenCount(usage.cacheCreationTokens)})${RESET_ATTRS}`;
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* The one manual step the wizard can't do itself: the editor's own MCP login.
|
|
2220
|
+
* Echoed into scrollback like the handoff prompt — command on its own plain
|
|
2221
|
+
* line so a terminal can triple-click-select it.
|
|
2222
|
+
*/
|
|
2223
|
+
function mcpLoginBlock(store) {
|
|
2224
|
+
const commands = store.mcpLoginCommands;
|
|
2225
|
+
if (!commands || commands.length === 0) return null;
|
|
2226
|
+
return `${GREEN}${BOLD}\u2714 Authenticate to finish (opens your browser):${RESET_ATTRS}\n` + commands.join("\n");
|
|
2227
|
+
}
|
|
2228
|
+
function getExitLine(store) {
|
|
2229
|
+
const outro = store.session.outroData;
|
|
2230
|
+
const label = store.programLabel ?? "Wizard";
|
|
2231
|
+
const costLine = tokenCostLine(store);
|
|
2232
|
+
const loginBlock = mcpLoginBlock(store);
|
|
2233
|
+
if (isRunFailure(store.session)) {
|
|
2234
|
+
const spellbook = store.spellbook;
|
|
2235
|
+
return [
|
|
2236
|
+
"The wizard is unavailable. Setup has not been completed.",
|
|
2237
|
+
spellbook && `${DIM}Point your agent at this skill (triple-click to select):${RESET_ATTRS}\n${spellbook.path}`,
|
|
2238
|
+
`${DIM}${MINT_FAILURE_CONTACT}${RESET_ATTRS}\n${getLogFilePath()}`,
|
|
2239
|
+
loginBlock,
|
|
2240
|
+
costLine
|
|
2241
|
+
].filter(Boolean).join("\n\n");
|
|
2242
|
+
}
|
|
2243
|
+
if (outro?.kind === "success") {
|
|
2244
|
+
const message = outro.message ?? `${label} completed successfully.`;
|
|
2245
|
+
const parts = [`${GREEN}${BOLD}✔${RESET_ATTRS} ${message}${outro.reportFile && !message.includes(outro.reportFile) ? ` Check ./${outro.reportFile} for details.` : ""}`];
|
|
2246
|
+
if (outro.primaryLink) parts.push(`${DIM}${outro.primaryLink.label}:${RESET_ATTRS}\n${outro.primaryLink.url}`);
|
|
2247
|
+
if (outro.nextSteps) {
|
|
2248
|
+
const bullets = outro.nextSteps.items.map((item) => `${DIM} • ${item}${RESET_ATTRS}`).join("\n");
|
|
2249
|
+
parts.push(`${DIM}${outro.nextSteps.heading}${RESET_ATTRS}\n${bullets}`);
|
|
2250
|
+
}
|
|
2251
|
+
if (outro.handoffPrompt) parts.push(`${DIM}Hand this to your coding agent to finish up (triple-click to select):${RESET_ATTRS}\n` + outro.handoffPrompt);
|
|
2252
|
+
if (loginBlock) parts.push(loginBlock);
|
|
2253
|
+
if (costLine) parts.push(costLine);
|
|
2254
|
+
return parts.join("\n\n");
|
|
2255
|
+
}
|
|
2256
|
+
const parts = loginBlock ? [loginBlock] : [`${DIM}${label} exited.${RESET_ATTRS}`];
|
|
2257
|
+
if (costLine) parts.push(costLine);
|
|
2258
|
+
return parts.join("\n\n");
|
|
2259
|
+
}
|
|
2260
|
+
//#endregion
|
|
2261
|
+
//#region src/tui/start-tui.ts
|
|
2262
|
+
/**
|
|
2263
|
+
* start-tui.ts — Sets up the Ink TUI renderer over a new store.
|
|
2264
|
+
*
|
|
2265
|
+
* Renders in the terminal's alternate screen buffer so the wizard
|
|
2266
|
+
* doesn't pollute scrollback history. On exit, the previous terminal
|
|
2267
|
+
* content is restored and a single exit summary line is printed.
|
|
2268
|
+
*/
|
|
2269
|
+
/** Render the app for `program`. `onInterrupt` runs when Ink tears itself down on Ctrl+C; the caller ends the run. */
|
|
2270
|
+
function startTUI(version, program, onInterrupt) {
|
|
2271
|
+
if (!process.stdin.isTTY) throw new WizardError("This command needs an interactive terminal (stdin is not a TTY). Run the wizard directly in a terminal, not through a pipe or script.", void 0, ErrorCodes.CliInteractiveRequired);
|
|
2272
|
+
enterDarkTerminal();
|
|
2273
|
+
const store = new WizardStore(program);
|
|
2274
|
+
store.version = version;
|
|
2275
|
+
const { unmount: inkUnmount, waitUntilExit } = render(createElement(App, { store }));
|
|
2276
|
+
analytics.setTag("program_id", program);
|
|
2277
|
+
analytics.wizardCapture("started", { program_id: program });
|
|
2278
|
+
store.runInitHooks();
|
|
2279
|
+
process.stdin.on("error", (err) => {
|
|
2280
|
+
if (err.code !== "EIO") throw err;
|
|
2281
|
+
});
|
|
2282
|
+
let cleaned = false;
|
|
2283
|
+
const cleanup = () => {
|
|
2284
|
+
if (cleaned) return;
|
|
2285
|
+
cleaned = true;
|
|
2286
|
+
logToFile(`[start-tui] unmounting TUI, leaving alt screen (exitCode=${process.exitCode ?? "unset"})`);
|
|
2287
|
+
inkUnmount();
|
|
2288
|
+
releaseTerminal();
|
|
2289
|
+
process.stdout.write(getExitLine(store) + "\n");
|
|
2290
|
+
};
|
|
2291
|
+
process.on("exit", cleanup);
|
|
2292
|
+
waitUntilExit().then(() => {
|
|
2293
|
+
if (!cleaned) onInterrupt();
|
|
2294
|
+
});
|
|
2295
|
+
return {
|
|
2296
|
+
unmount: cleanup,
|
|
2297
|
+
store,
|
|
2298
|
+
waitForSetup: () => store.getGate("intro")
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
//#endregion
|
|
2302
|
+
//#region src/tui/run-tool.ts
|
|
2303
|
+
/**
|
|
2304
|
+
* The TUI host for a tool: its screens on their own store, with no program
|
|
2305
|
+
* run, no task stream and no WizardRun. The CLI builds the launch values, owns
|
|
2306
|
+
* the signals and applies the exit code.
|
|
2307
|
+
*/
|
|
2308
|
+
/**
|
|
2309
|
+
* Run `toolId`'s screens. Resolves with the code a screen ends them with
|
|
2310
|
+
* (after the analytics flush), an abort's, 1 on a crash, or 130 or 143 on a
|
|
2311
|
+
* signal. Rejects when the TUI cannot start, as with no terminal.
|
|
2312
|
+
*/
|
|
2313
|
+
async function runTuiTool(toolId, launch) {
|
|
2314
|
+
const exit = startHostExit();
|
|
2315
|
+
let unmountTui = () => void 0;
|
|
2316
|
+
let stopping = false;
|
|
2317
|
+
const stop = (code) => {
|
|
2318
|
+
if (stopping || exit.ended) return;
|
|
2319
|
+
stopping = true;
|
|
2320
|
+
unmountTui();
|
|
2321
|
+
(async () => {
|
|
2322
|
+
try {
|
|
2323
|
+
await analytics.shutdown("cancelled");
|
|
2324
|
+
} catch {}
|
|
2325
|
+
exit.end(code);
|
|
2326
|
+
})();
|
|
2327
|
+
};
|
|
2328
|
+
const onAbort = () => stop(launch.signal.reason === "SIGTERM" ? 143 : 130);
|
|
2329
|
+
if (launch.signal.aborted) onAbort();
|
|
2330
|
+
else launch.signal.addEventListener("abort", onAbort, { once: true });
|
|
2331
|
+
const isDoctor = toolId === "posthog-doctor";
|
|
2332
|
+
const localError = isDoctor ? await localServicesError(launch.session.baseUrl) : void 0;
|
|
2333
|
+
if (stopping) return exit.exited;
|
|
2334
|
+
if (localError) {
|
|
2335
|
+
wizardAbort({
|
|
2336
|
+
present: printAbortOutro,
|
|
2337
|
+
exit
|
|
2338
|
+
}, { message: localError }).catch((error) => exit.fail(error));
|
|
2339
|
+
return exit.exited;
|
|
2340
|
+
}
|
|
2341
|
+
const tool = getTuiTool(toolId);
|
|
2342
|
+
if (!tool) throw new Error(`The ${toolId} tool has no screens.`);
|
|
2343
|
+
const tui = startTUI(VERSION, toolId, () => stop(130));
|
|
2344
|
+
unmountTui = () => tui.unmount();
|
|
2345
|
+
tui.store.launch(buildSession(launch.session), launch.session, isDoctor ? toolId : null);
|
|
2346
|
+
driveTuiTool(tui.store, tool, {
|
|
2347
|
+
toolId,
|
|
2348
|
+
signal: launch.signal
|
|
2349
|
+
}).then(async (code) => {
|
|
2350
|
+
if (stopping || exit.ended) return;
|
|
2351
|
+
stopping = true;
|
|
2352
|
+
tui.unmount();
|
|
2353
|
+
await flushAnalytics();
|
|
2354
|
+
exit.end(code);
|
|
2355
|
+
}, (error) => {
|
|
2356
|
+
if (stopping || exit.ended) return;
|
|
2357
|
+
stopping = true;
|
|
2358
|
+
logToFile("[run-wizard] FATAL:", error);
|
|
2359
|
+
runCleanups();
|
|
2360
|
+
try {
|
|
2361
|
+
tui.unmount();
|
|
2362
|
+
} catch {}
|
|
2363
|
+
reportFatal(error);
|
|
2364
|
+
exit.end(1);
|
|
2365
|
+
});
|
|
2366
|
+
return exit.exited;
|
|
2367
|
+
}
|
|
2368
|
+
/**
|
|
2369
|
+
* Drive a tool's screens on a mounted store: run its `start`, and resolve with
|
|
2370
|
+
* the code the first screen exit request carries. Rejects when `start` does.
|
|
2371
|
+
* The intro hands off to a tool through here too.
|
|
2372
|
+
*/
|
|
2373
|
+
function driveTuiTool(store, tool, { toolId, signal }) {
|
|
2374
|
+
return new Promise((resolve, reject) => {
|
|
2375
|
+
const settle = () => {
|
|
2376
|
+
const code = store.exitRequest;
|
|
2377
|
+
if (code === null) return;
|
|
2378
|
+
unsubscribe();
|
|
2379
|
+
resolve(code);
|
|
2380
|
+
};
|
|
2381
|
+
const unsubscribe = store.subscribe(settle);
|
|
2382
|
+
settle();
|
|
2383
|
+
tool.start?.({
|
|
2384
|
+
store,
|
|
2385
|
+
signal,
|
|
2386
|
+
logIn: async () => {
|
|
2387
|
+
await logIn(toolId, store.sessions, {
|
|
2388
|
+
provider: oauthCredentials(store),
|
|
2389
|
+
signal
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
}).catch((error) => {
|
|
2393
|
+
unsubscribe();
|
|
2394
|
+
reject(error);
|
|
2395
|
+
});
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
/** Before the TUI mounts: the local dev services this run points at, if one is down. */
|
|
2399
|
+
function localServicesError(baseUrl) {
|
|
2400
|
+
const local = getLocalDev();
|
|
2401
|
+
return checkLocalServices({
|
|
2402
|
+
...local,
|
|
2403
|
+
localPosthog: local.localPosthog && !baseUrl
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
/** Print a crash with the log path and the PHW line. */
|
|
2407
|
+
function reportFatal(error) {
|
|
2408
|
+
const failure = classifyRunFailure(error);
|
|
2409
|
+
if (failure.coded) console.error(failure.message);
|
|
2410
|
+
else console.error("Wizard run failed:", error);
|
|
2411
|
+
console.error(`Full logs: ${getLogFilePath()}`);
|
|
2412
|
+
emitWizardError({
|
|
2413
|
+
code: failure.code,
|
|
2414
|
+
message: failure.message
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
//#endregion
|
|
2418
|
+
export { startTUI as a, runTuiTool as i, localServicesError as n, oauthCredentials as o, reportFatal as r, driveTuiTool as t };
|
|
2419
|
+
|
|
2420
|
+
//# sourceMappingURL=run-tool-DML8JJvt.js.map
|