@posthog/wizard 2.78.0 → 2.79.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 +20 -11
- package/dist/PickerMenu-dG5DQCZA.js +758 -0
- package/dist/PickerMenu-dG5DQCZA.js.map +1 -0
- package/dist/{add-mcp-server-to-clients-JMv8LP-O.js → add-mcp-server-to-clients-DIaTRWMY.js} +7 -18
- package/dist/{add-mcp-server-to-clients-JMv8LP-O.js.map → add-mcp-server-to-clients-DIaTRWMY.js.map} +1 -1
- package/dist/add-mcp-server-to-clients-artmN7mz.js +2 -0
- package/dist/agent-BEjARD2a.js +6 -0
- package/dist/{agent-bCZ3i3fh.js → agent-BWFhuLgo.js} +131 -38
- package/dist/agent-BWFhuLgo.js.map +1 -0
- package/dist/{agent-interface-BAOIHuXS.js → agent-interface-FkbaBJwz.js} +66 -28
- package/dist/agent-interface-FkbaBJwz.js.map +1 -0
- package/dist/{agent-prompt-loader-7Pd4CfZO.js → agent-prompt-loader-D0weQ-7k.js} +5 -5
- package/dist/{agent-prompt-loader-7Pd4CfZO.js.map → agent-prompt-loader-D0weQ-7k.js.map} +1 -1
- package/dist/analytics-DFtbRjYh.js +2 -0
- package/dist/{analytics-CHCZ_qUR.js → analytics-DXK1gc12.js} +4 -4
- package/dist/{analytics-CHCZ_qUR.js.map → analytics-DXK1gc12.js.map} +1 -1
- package/dist/api-Cm9UtKZh.js +2 -0
- package/dist/{api-Diy_2fp8.js → api-CrbCIQOz.js} +4 -16
- package/dist/{api-Diy_2fp8.js.map → api-CrbCIQOz.js.map} +1 -1
- package/dist/bin.js +5 -2957
- package/dist/bin.js.map +1 -1
- package/dist/{bounded-fs-Bx9zTe6m.js → bounded-fs-vAq-T-K7.js} +3 -3
- package/dist/{bounded-fs-Bx9zTe6m.js.map → bounded-fs-vAq-T-K7.js.map} +1 -1
- package/dist/{ci-install-CDdQYsPe.js → ci-install-COcV8SY2.js} +8 -7
- package/dist/ci-install-COcV8SY2.js.map +1 -0
- package/dist/{constants-jFI2SaE1.js → constants-DHCBSfnx.js} +3 -3
- package/dist/{constants-jFI2SaE1.js.map → constants-DHCBSfnx.js.map} +1 -1
- package/dist/{debug-M2gV2iDq.js → debug-BuYHGr6_.js} +1 -1
- package/dist/{debug-Ch4BDOtD.js → debug-kAHwWGg7.js} +2 -2
- package/dist/{debug-Ch4BDOtD.js.map → debug-kAHwWGg7.js.map} +1 -1
- package/dist/{defaults-C6J7yL4O.js → defaults-a9TelDhJ.js} +1 -1
- package/dist/{defaults-C6J7yL4O.js.map → defaults-a9TelDhJ.js.map} +1 -1
- package/dist/emit-BSetXzDV.js +69 -0
- package/dist/emit-BSetXzDV.js.map +1 -0
- package/dist/{endpoints-DCVbM06L.js → endpoints-BVMYm9F4.js} +3 -3
- package/dist/{endpoints-DCVbM06L.js.map → endpoints-BVMYm9F4.js.map} +1 -1
- package/dist/{env-api-key-Qx8NnYHm.js → env-api-key-BP-NFFXt.js} +3 -5
- package/dist/{env-api-key-Qx8NnYHm.js.map → env-api-key-BP-NFFXt.js.map} +1 -1
- package/dist/env-api-key-CaWEDj_9.js +2 -0
- package/dist/{environment-B07Bmxae.js → environment-BATlQxcQ.js} +5 -10
- package/dist/{environment-B07Bmxae.js.map → environment-BATlQxcQ.js.map} +1 -1
- package/dist/environment-CwD8OPYj.js +2 -0
- package/dist/errors-C2domSkK.js +79 -0
- package/dist/errors-C2domSkK.js.map +1 -0
- package/dist/file-C5JMsyR1.js +2 -0
- package/dist/{file-BcLTjU16.js → file-DcRKycV8.js} +4 -4
- package/dist/file-DcRKycV8.js.map +1 -0
- package/dist/{file-watcher-DWc_bTs6.js → file-watcher-Brq2nnHV.js} +2 -2
- package/dist/{file-watcher-DWc_bTs6.js.map → file-watcher-Brq2nnHV.js.map} +1 -1
- package/dist/{gateway-CjoShdLa.js → gateway-C9-GlLO9.js} +39 -8
- package/dist/gateway-C9-GlLO9.js.map +1 -0
- package/dist/{headless-ui-BFFmYC4v.js → headless-ui-CqI2gXlD.js} +2 -2
- package/dist/{headless-ui-BFFmYC4v.js.map → headless-ui-CqI2gXlD.js.map} +1 -1
- package/dist/{interactive-D6zV7brJ.js → interactive-B_Ti_CsS.js} +3 -3
- package/dist/interactive-B_Ti_CsS.js.map +1 -0
- package/dist/{logging-ui-_TwoMNbG.js → logging-ui-Dza4D0MK.js} +5 -5
- package/dist/{logging-ui-_TwoMNbG.js.map → logging-ui-Dza4D0MK.js.map} +1 -1
- package/dist/main-CgKxbwMP.js +1396 -0
- package/dist/main-CgKxbwMP.js.map +1 -0
- package/dist/{mcp-DAgaoGk5.js → mcp-DnAyW0-w.js} +4 -4
- package/dist/{mcp-DAgaoGk5.js.map → mcp-DnAyW0-w.js.map} +1 -1
- package/dist/{mcp-prompt-streaming-I2U_X7BC.js → mcp-prompt-streaming-DDWQ-FCA.js} +6 -6
- package/dist/mcp-prompt-streaming-DDWQ-FCA.js.map +1 -0
- package/dist/{mint-failure-D4nd2XmZ.js → mint-failure-u8xQKNOn.js} +689 -126
- package/dist/mint-failure-u8xQKNOn.js.map +1 -0
- package/dist/{models-DDdBJKwT.js → models-WcwUnSKb.js} +2 -2
- package/dist/{models-DDdBJKwT.js.map → models-WcwUnSKb.js.map} +1 -1
- package/dist/{non-interactive-Dzow7YKY.js → non-interactive-DyVC1AGc.js} +4 -3
- package/dist/{non-interactive-Dzow7YKY.js.map → non-interactive-DyVC1AGc.js.map} +1 -1
- package/dist/{oauth-session-DJ8h6GFa.js → oauth-session-DYM2Hrl2.js} +2 -2
- package/dist/{oauth-session-DJ8h6GFa.js.map → oauth-session-DYM2Hrl2.js.map} +1 -1
- package/dist/{orchestrator-tools-szcdscwn.js → orchestrator-tools-DbgNBFGA.js} +3 -3
- package/dist/{orchestrator-tools-szcdscwn.js.map → orchestrator-tools-DbgNBFGA.js.map} +1 -1
- package/dist/{package-json-DY70kkNp.js → package-json-Atg34fa4.js} +2 -8
- package/dist/{package-json-DY70kkNp.js.map → package-json-Atg34fa4.js.map} +1 -1
- package/dist/package-json-D8Ga03tK.js +2 -0
- package/dist/{pi-D3h5Ok0i.js → pi-8KsYsPmM.js} +145 -45
- package/dist/pi-8KsYsPmM.js.map +1 -0
- package/dist/{playground-5AB4WgNF.js → playground-BlYRBMj7.js} +11 -11
- package/dist/playground-BlYRBMj7.js.map +1 -0
- package/dist/{posthog-DNn574nW.js → posthog-Bt0Aplb8.js} +4 -9
- package/dist/posthog-Bt0Aplb8.js.map +1 -0
- package/dist/posthog-CUgCEhH0.js +2 -0
- package/dist/{provisioning-B8uSqRZk.js → provisioning-Bo2NRxFz.js} +5 -11
- package/dist/{provisioning-B8uSqRZk.js.map → provisioning-Bo2NRxFz.js.map} +1 -1
- package/dist/provisioning-OGFGlzf7.js +2 -0
- package/dist/{queue-tools-9OOVx5TE.js → queue-tools-C8mXOh7e.js} +3 -3
- package/dist/{queue-tools-9OOVx5TE.js.map → queue-tools-C8mXOh7e.js.map} +1 -1
- package/dist/registry-C06e_x-p.js +2 -0
- package/dist/{registry-8YG-kuXk.js → registry-CD5RQt38.js} +51 -53
- package/dist/registry-CD5RQt38.js.map +1 -0
- package/dist/run-agent-legacy-ChacZImU.js +2 -0
- package/dist/{run-agent-legacy-d7rWlzQR.js → run-agent-legacy-DOlM6DQu.js} +823 -1022
- package/dist/run-agent-legacy-DOlM6DQu.js.map +1 -0
- package/dist/runners-ov3EXF1n.js +834 -0
- package/dist/runners-ov3EXF1n.js.map +1 -0
- package/dist/{security-Cg_4a7vB.js → security-C71pkwqH.js} +16 -6
- package/dist/security-C71pkwqH.js.map +1 -0
- package/dist/{setup-utils-CmTCTPkX.js → setup-utils-B26Lf2yF.js} +12 -22
- package/dist/setup-utils-B26Lf2yF.js.map +1 -0
- package/dist/setup-utils-CVS86NYR.js +2 -0
- package/dist/{start-tui-CB2PZs1x.js → start-tui-Bo2L0bar.js} +30 -28
- package/dist/start-tui-Bo2L0bar.js.map +1 -0
- package/dist/{steps-BZcXRJNY.js → steps-BPcg3_3Q.js} +7 -7
- package/dist/{steps-BZcXRJNY.js.map → steps-BPcg3_3Q.js.map} +1 -1
- package/dist/{store-UN7c-ASf.js → store-BTzcvRGT.js} +12 -18
- package/dist/store-BTzcvRGT.js.map +1 -0
- package/dist/store-BpqySPXm.js +5 -0
- package/dist/{subagent-BofIDK-O.js → subagent-D1TrTchE.js} +3 -3
- package/dist/{subagent-BofIDK-O.js.map → subagent-D1TrTchE.js.map} +1 -1
- package/dist/{task-r9t1UhQP.js → task-DqRlQBfs.js} +18 -16
- package/dist/task-DqRlQBfs.js.map +1 -0
- package/dist/{task-stream-CMUilu54.js → task-stream-Bw6JaLCX.js} +10 -10
- package/dist/task-stream-Bw6JaLCX.js.map +1 -0
- package/dist/{tasks-Bbv_lOBG.js → tasks-ChWufsdO.js} +1 -1
- package/dist/{tasks-Bbv_lOBG.js.map → tasks-ChWufsdO.js.map} +1 -1
- package/dist/{telemetry-D6ruCtnv.js → telemetry-B7zJdRmA.js} +3 -3
- package/dist/{telemetry-D6ruCtnv.js.map → telemetry-B7zJdRmA.js.map} +1 -1
- package/dist/{terminal-DrGyci_a.js → terminal-zgkEfoCL.js} +17 -17
- package/dist/terminal-zgkEfoCL.js.map +1 -0
- package/dist/{tools-Dyfh9aCD.js → tools-CkJ2y9Wt.js} +9 -9
- package/dist/tools-CkJ2y9Wt.js.map +1 -0
- package/dist/{ui-BBVuTM1v.js → ui-Q6-RvERF.js} +3 -3
- package/dist/{ui-BBVuTM1v.js.map → ui-Q6-RvERF.js.map} +1 -1
- package/dist/{version-aXxpgRKx.js → version-CHhJjz_H.js} +2 -2
- package/dist/{version-aXxpgRKx.js.map → version-CHhJjz_H.js.map} +1 -1
- package/dist/{wizard-abort-BBkiUxfS.js → wizard-abort-BPdv07np.js} +7 -6
- package/dist/{wizard-abort-BBkiUxfS.js.map → wizard-abort-BPdv07np.js.map} +1 -1
- package/dist/wizard-abort-Br1qMDNa.js +3 -0
- package/dist/wizard-session-2w4Ru-Wz.js.map +1 -1
- package/dist/{wizard-ui-DoyOZxK8.js → wizard-ui-DVqrYaOM.js} +1 -1
- package/dist/{wizard-ui-DoyOZxK8.js.map → wizard-ui-DVqrYaOM.js.map} +1 -1
- package/dist/{yara-hooks-DCeMDd1o.js → yara-hooks-BnxejtXK.js} +5 -5
- package/dist/{yara-hooks-DCeMDd1o.js.map → yara-hooks-BnxejtXK.js.map} +1 -1
- package/dist/yara-hooks-DSuExwb4.js +2 -0
- package/package.json +2 -1
- package/dist/agent-bCZ3i3fh.js.map +0 -1
- package/dist/agent-interface-BAOIHuXS.js.map +0 -1
- package/dist/analytics-BcV-WCM4.js +0 -2
- package/dist/ci-install-CDdQYsPe.js.map +0 -1
- package/dist/errors-CQ9kttJD.js +0 -185
- package/dist/errors-CQ9kttJD.js.map +0 -1
- package/dist/file-BcLTjU16.js.map +0 -1
- package/dist/gateway-CjoShdLa.js.map +0 -1
- package/dist/interactive-D6zV7brJ.js.map +0 -1
- package/dist/mcp-prompt-streaming-I2U_X7BC.js.map +0 -1
- package/dist/mint-failure-D4nd2XmZ.js.map +0 -1
- package/dist/pi-D3h5Ok0i.js.map +0 -1
- package/dist/playground-5AB4WgNF.js.map +0 -1
- package/dist/posthog-DNn574nW.js.map +0 -1
- package/dist/registry-8YG-kuXk.js.map +0 -1
- package/dist/rolldown-runtime-B_-DWIq7.js +0 -15
- package/dist/run-agent-legacy-d7rWlzQR.js.map +0 -1
- package/dist/security-Cg_4a7vB.js.map +0 -1
- package/dist/setup-utils-CmTCTPkX.js.map +0 -1
- package/dist/start-tui-CB2PZs1x.js.map +0 -1
- package/dist/store-UN7c-ASf.js.map +0 -1
- package/dist/task-r9t1UhQP.js.map +0 -1
- package/dist/task-stream-CMUilu54.js.map +0 -1
- package/dist/terminal-DrGyci_a.js.map +0 -1
- package/dist/tools-Dyfh9aCD.js.map +0 -1
- package/dist/wizard-abort-BU4aLyvh.js +0 -3
- package/dist/yara-hooks-DSGuCU9A.js +0 -2
package/dist/bin.js
CHANGED
|
@@ -1,2951 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { t as VERSION } from "./version-aXxpgRKx.js";
|
|
5
|
-
import { a as getLocalDev, i as checkLocalServices, o as initLocalDev, r as POSTHOG_LOCAL_URL, s as localMcpSkillsNotice } from "./local-dev-McsurOC-.js";
|
|
6
|
-
import { $ as getSkillsBaseUrl, k as POSTHOG_DOCS_URL } from "./constants-jFI2SaE1.js";
|
|
7
|
-
import "./wizard-session-2w4Ru-Wz.js";
|
|
8
|
-
import { n as analytics } from "./analytics-CHCZ_qUR.js";
|
|
9
|
-
import { r as setEntryCommand } from "./telemetry-D6ruCtnv.js";
|
|
10
|
-
import { t as LoggingUI } from "./logging-ui-_TwoMNbG.js";
|
|
11
|
-
import { n as setUI, t as getUI } from "./ui-BBVuTM1v.js";
|
|
12
|
-
import { n as registerShutdown, r as runCleanups } from "./wizard-abort-BBkiUxfS.js";
|
|
13
|
-
import { n as isNonInteractiveEnvironment } from "./environment-B07Bmxae.js";
|
|
14
|
-
import { a as getAuditChecks, s as maybeStampAiSdkDetected, t as runProgramAgent, x as authenticate } from "./run-agent-legacy-d7rWlzQR.js";
|
|
15
|
-
import { r as currentCredentials } from "./oauth-session-DJ8h6GFa.js";
|
|
16
|
-
import { I as WIZARD_TOOL_NAMES, et as fetchSkillMenu, m as isGrantRevoked, u as recoverOrphanedSettingsBackups } from "./agent-interface-BAOIHuXS.js";
|
|
17
|
-
import "./agent-bCZ3i3fh.js";
|
|
18
|
-
import { h as AUDIT_CHECKS_FILE } from "./yara-hooks-DCeMDd1o.js";
|
|
19
|
-
import { $ as wordWrap, A as installOrUpdatePostHogCli, B as webAnalyticsDoctorConfig, D as CLI_STEERING_TARGETS, E as errorTrackingUploadSourceMapsConfig, G as createSkillProgram, H as getKindMeta, K as warehouseSourceConfig, O as detectTargets, S as errorTrackingConfig, T as replayVisionConfig, U as fetchHealthIssues, V as posthogDoctorConfig, W as auditConfig, d as metricsConfig, f as aiObservabilityConfig, i as isRunFailure, it as Icons, j as installSteeringSnippet, k as findTarget, m as selfDrivingConfig, o as Program, p as mcpAnalyticsConfig, q as revenueAnalyticsConfig, rt as Colors, s as agentSkillConfig, u as getProgramConfig, z as migrationConfig } from "./mint-failure-D4nd2XmZ.js";
|
|
20
|
-
import { n as parseRetryAfter } from "./posthog-DNn574nW.js";
|
|
21
|
-
import { n as readApiKeyFromEnv } from "./env-api-key-Qx8NnYHm.js";
|
|
22
|
-
import { satisfies, valid } from "semver";
|
|
23
|
-
import { Agent, setGlobalDispatcher } from "undici";
|
|
24
|
-
import yargs from "yargs";
|
|
25
|
-
import { hideBin } from "yargs/helpers";
|
|
26
|
-
import { validate } from "uuid";
|
|
27
|
-
import * as path$1 from "node:path";
|
|
28
|
-
import { basename, join, resolve } from "node:path";
|
|
29
|
-
import { randomUUID } from "node:crypto";
|
|
30
|
-
import { Box, Text, render, useInput, useStdout } from "ink";
|
|
31
|
-
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
32
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
33
|
-
import Fuse from "fuse.js";
|
|
34
|
-
import * as readline from "node:readline/promises";
|
|
35
|
-
//#region src/commands/command.ts
|
|
36
|
-
/** Extract the bare command word(s) from a yargs name spec, dropping positionals and aliases' arg syntax. */
|
|
37
|
-
function commandKeys(name) {
|
|
38
|
-
return (typeof name === "string" ? [name] : name).map((n) => n.trim().split(/\s+/)[0]);
|
|
39
|
-
}
|
|
40
|
-
function toCommandModule(cmd, parentPath) {
|
|
41
|
-
const entryCommand = [...parentPath, commandKeys(cmd.name)[0]].filter((key) => key !== "$0").join("-");
|
|
42
|
-
return {
|
|
43
|
-
command: cmd.name,
|
|
44
|
-
describe: cmd.description,
|
|
45
|
-
builder: (y) => {
|
|
46
|
-
let next = cmd.options ? y.options(cmd.options) : y;
|
|
47
|
-
for (const [key, opts] of Object.entries(cmd.positionals ?? {})) next = next.positional(key, opts);
|
|
48
|
-
if (cmd.check) next = next.check(cmd.check);
|
|
49
|
-
for (const [usage, description] of cmd.examples ?? []) next = next.example(usage, description);
|
|
50
|
-
const ownPath = [...parentPath, commandKeys(cmd.name)[0]];
|
|
51
|
-
for (const child of cmd.children ?? []) next = next.command(toCommandModule(child, ownPath));
|
|
52
|
-
if (cmd.children?.length && !cmd.handler && !cmd.interactiveDefault) next = next.demandCommand(1);
|
|
53
|
-
return next;
|
|
54
|
-
},
|
|
55
|
-
handler: (argv) => {
|
|
56
|
-
if (entryCommand) setEntryCommand(entryCommand);
|
|
57
|
-
(cmd.handler ?? cmd.interactiveDefault ?? (() => void 0))(argv);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region src/wizard.ts
|
|
63
|
-
/**
|
|
64
|
-
* Global yargs options applied to every command. These are read from the
|
|
65
|
-
* `POSTHOG_WIZARD` env prefix as well as flags.
|
|
66
|
-
*
|
|
67
|
-
* Options with `hidden: true` are "internal modes" — they don't show up in
|
|
68
|
-
* `--help` but are still accepted on every command. The `--local-*` flags live
|
|
69
|
-
* in the constructor's `!IS_PRODUCTION_BUILD` block instead, so published
|
|
70
|
-
* builds reject them; see `docs/local-dev.md`.
|
|
71
|
-
*/
|
|
72
|
-
const GLOBAL_OPTIONS = {
|
|
73
|
-
debug: {
|
|
74
|
-
default: false,
|
|
75
|
-
describe: "Enable verbose logging\nenv: POSTHOG_WIZARD_DEBUG",
|
|
76
|
-
type: "boolean"
|
|
77
|
-
},
|
|
78
|
-
signup: {
|
|
79
|
-
default: false,
|
|
80
|
-
describe: "Create a new PostHog account during setup\nenv: POSTHOG_WIZARD_SIGNUP",
|
|
81
|
-
type: "boolean"
|
|
82
|
-
},
|
|
83
|
-
telemetry: {
|
|
84
|
-
default: true,
|
|
85
|
-
describe: "Send wizard run state to PostHog (pass --no-telemetry to disable)\nenv: POSTHOG_WIZARD_TELEMETRY",
|
|
86
|
-
type: "boolean"
|
|
87
|
-
},
|
|
88
|
-
"api-key": {
|
|
89
|
-
describe: "PostHog personal API key (phx_xxx) for authentication\nenv: POSTHOG_WIZARD_API_KEY",
|
|
90
|
-
type: "string"
|
|
91
|
-
},
|
|
92
|
-
"project-id": {
|
|
93
|
-
describe: "PostHog project ID to use (optional; when not set, uses default from API key or OAuth)\nenv: POSTHOG_WIZARD_PROJECT_ID",
|
|
94
|
-
type: "string"
|
|
95
|
-
},
|
|
96
|
-
email: {
|
|
97
|
-
describe: "Email address for signup (used with --signup)\nenv: POSTHOG_WIZARD_EMAIL",
|
|
98
|
-
type: "string"
|
|
99
|
-
},
|
|
100
|
-
"base-url": {
|
|
101
|
-
describe: "Override the PostHog base URL (e.g. http://localhost:8010), bypassing region resolution. Pins the API host, cloud URL, and OAuth server.\nenv: POSTHOG_WIZARD_BASE_URL",
|
|
102
|
-
type: "string",
|
|
103
|
-
hidden: true
|
|
104
|
-
},
|
|
105
|
-
"run-id": {
|
|
106
|
-
describe: "Assigned cloud WizardRun UUID",
|
|
107
|
-
type: "string",
|
|
108
|
-
hidden: true
|
|
109
|
-
},
|
|
110
|
-
benchmark: {
|
|
111
|
-
default: false,
|
|
112
|
-
describe: "Run in benchmark mode with per-phase token tracking\nenv: POSTHOG_WIZARD_BENCHMARK",
|
|
113
|
-
type: "boolean",
|
|
114
|
-
hidden: true
|
|
115
|
-
},
|
|
116
|
-
"yara-report": {
|
|
117
|
-
default: false,
|
|
118
|
-
describe: "Print YARA scanner summary after the agent run\nenv: POSTHOG_WIZARD_YARA_REPORT",
|
|
119
|
-
type: "boolean",
|
|
120
|
-
hidden: true
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
var Wizard = class Wizard {
|
|
124
|
-
cli;
|
|
125
|
-
constructor() {
|
|
126
|
-
let cli = yargs(hideBin(process.argv)).env("POSTHOG_WIZARD").options(GLOBAL_OPTIONS);
|
|
127
|
-
this.cli = cli.middleware((argv) => {
|
|
128
|
-
initLocalDev(argv);
|
|
129
|
-
const notice = localMcpSkillsNotice({
|
|
130
|
-
localDev: argv.localDev,
|
|
131
|
-
localMcp: argv.localMcp,
|
|
132
|
-
localContextMill: argv.localContextMill
|
|
133
|
-
});
|
|
134
|
-
if (notice) process.stderr.write(`\n\x1b[33m! ${notice}\x1b[0m\n\n`);
|
|
135
|
-
}).strictOptions().strictCommands().fail((msg, err) => {
|
|
136
|
-
const text = msg || err && err.message || "Invalid arguments";
|
|
137
|
-
process.stderr.write(`\n\x1b[1;91m✖ ${text}\x1b[0m\n Run \`wizard --help\` to see available commands and options.\n\n`);
|
|
138
|
-
emitWizardError({
|
|
139
|
-
code: ErrorCodes.CliBadArgs,
|
|
140
|
-
message: text
|
|
141
|
-
});
|
|
142
|
-
process.exit(1);
|
|
143
|
-
}).help().alias("help", "h").version().alias("version", "v");
|
|
144
|
-
}
|
|
145
|
-
/** Start a chain; equivalent to `new Wizard().use(...cmds)`. */
|
|
146
|
-
static use(...cmds) {
|
|
147
|
-
return new Wizard().use(...cmds);
|
|
148
|
-
}
|
|
149
|
-
/** Register one or more commands with yargs. */
|
|
150
|
-
use(...cmds) {
|
|
151
|
-
for (const cmd of cmds) this.cli = this.cli.command(toCommandModule(cmd, []));
|
|
152
|
-
return this;
|
|
153
|
-
}
|
|
154
|
-
/** Parse argv and dispatch to the matching registered command. */
|
|
155
|
-
init() {
|
|
156
|
-
{
|
|
157
|
-
const args = process.argv.slice(2);
|
|
158
|
-
const argvHasCI = args.some((a) => a === "--ci" || a === "--no-ci" || a.startsWith("--ci="));
|
|
159
|
-
const envHasCI = process.env.POSTHOG_WIZARD_CI != null && process.env.POSTHOG_WIZARD_CI !== "";
|
|
160
|
-
if (argvHasCI || envHasCI) {
|
|
161
|
-
process.stderr.write(`\n\x1b[1;91m✖ CI mode is not currently supported in published builds.\x1b[0m\n\n`);
|
|
162
|
-
emitWizardError({
|
|
163
|
-
code: ErrorCodes.CliFlagUnavailable,
|
|
164
|
-
message: "CI mode is not supported in published builds."
|
|
165
|
-
});
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
const argvHasOverride = args.some((a) => a === "--harness" || a.startsWith("--harness=") || a === "--sequence" || a.startsWith("--sequence=") || a === "--model" || a.startsWith("--model=") || a === "--capture-aio" || a === "--no-capture-aio" || a.startsWith("--capture-aio="));
|
|
169
|
-
const envHasOverride = process.env.POSTHOG_WIZARD_HARNESS != null && process.env.POSTHOG_WIZARD_HARNESS !== "" || process.env.POSTHOG_WIZARD_SEQUENCE != null && process.env.POSTHOG_WIZARD_SEQUENCE !== "" || process.env.POSTHOG_WIZARD_MODEL != null && process.env.POSTHOG_WIZARD_MODEL !== "" || process.env.POSTHOG_WIZARD_CAPTURE_AIO != null && process.env.POSTHOG_WIZARD_CAPTURE_AIO !== "";
|
|
170
|
-
if (argvHasOverride || envHasOverride) {
|
|
171
|
-
process.stderr.write(`\n\x1b[1;91m✖ The --harness, --sequence, --model, and --capture-aio overrides are not available in published builds.\x1b[0m\n\n`);
|
|
172
|
-
emitWizardError({
|
|
173
|
-
code: ErrorCodes.CliFlagUnavailable,
|
|
174
|
-
message: "The --harness, --sequence, --model, and --capture-aio overrides are not available in published builds."
|
|
175
|
-
});
|
|
176
|
-
process.exit(1);
|
|
177
|
-
}
|
|
178
|
-
const argvHasLocalTarget = args.some((a) => LOCAL_TARGET_FLAGS.some((f) => a === `--${f}` || a === `--no-${f}` || a.startsWith(`--${f}=`)));
|
|
179
|
-
const envHasLocalTarget = LOCAL_TARGET_ENV_VARS.some((k) => process.env[k] != null && process.env[k] !== "");
|
|
180
|
-
if (argvHasLocalTarget || envHasLocalTarget) {
|
|
181
|
-
process.stderr.write("\n\x1B[1;91m✖ The --local-dev, --local-context-mill, --local-mcp, and --local-posthog targets are not available in published builds.\x1B[0m\n They point the wizard at development servers on localhost.\n\n");
|
|
182
|
-
emitWizardError({
|
|
183
|
-
code: ErrorCodes.CliFlagUnavailable,
|
|
184
|
-
message: "The --local-dev, --local-context-mill, --local-mcp, and --local-posthog targets are not available in published builds."
|
|
185
|
-
});
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
this.cli.wrap(process.stdout.isTTY ? this.cli.terminalWidth() : 80).argv;
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
/** Excludes bare `local`: `wizard mcp add --local` stays available in prod. */
|
|
193
|
-
const LOCAL_TARGET_FLAGS = [
|
|
194
|
-
"local-dev",
|
|
195
|
-
"local-context-mill",
|
|
196
|
-
"local-mcp",
|
|
197
|
-
"local-posthog"
|
|
198
|
-
];
|
|
199
|
-
const LOCAL_TARGET_ENV_VARS = [
|
|
200
|
-
"POSTHOG_WIZARD_LOCAL_DEV",
|
|
201
|
-
"POSTHOG_WIZARD_LOCAL_CONTEXT_MILL",
|
|
202
|
-
"POSTHOG_WIZARD_LOCAL_MCP",
|
|
203
|
-
"POSTHOG_WIZARD_LOCAL_POSTHOG"
|
|
204
|
-
];
|
|
205
|
-
//#endregion
|
|
206
|
-
//#region src/commands/provision.ts
|
|
207
|
-
const provisionCommand = {
|
|
208
|
-
name: "provision",
|
|
209
|
-
description: "Create a new PostHog account (headless, no TUI)",
|
|
210
|
-
options: {
|
|
211
|
-
email: {
|
|
212
|
-
describe: "Email address for the new account",
|
|
213
|
-
type: "string",
|
|
214
|
-
demandOption: true
|
|
215
|
-
},
|
|
216
|
-
region: {
|
|
217
|
-
describe: "Cloud region (us or eu)",
|
|
218
|
-
choices: ["us", "eu"],
|
|
219
|
-
default: "us"
|
|
220
|
-
},
|
|
221
|
-
name: {
|
|
222
|
-
describe: "Name for the new account",
|
|
223
|
-
type: "string",
|
|
224
|
-
default: ""
|
|
225
|
-
},
|
|
226
|
-
json: {
|
|
227
|
-
describe: "Emit JSON result to stdout (defaults to true when stdout is not a TTY)",
|
|
228
|
-
type: "boolean"
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
examples: [["wizard provision --email matt+test@posthog.com --region us", ""], ["wizard provision --email user@example.com --region eu --json", ""]],
|
|
232
|
-
handler: runProvision
|
|
233
|
-
};
|
|
234
|
-
function runProvision(argv) {
|
|
235
|
-
const jsonMode = argv.json === void 0 ? !process.stdout.isTTY : Boolean(argv.json);
|
|
236
|
-
if (!jsonMode) setUI(new LoggingUI());
|
|
237
|
-
provision({
|
|
238
|
-
email: argv.email,
|
|
239
|
-
region: argv.region.toUpperCase(),
|
|
240
|
-
name: argv.name ?? "",
|
|
241
|
-
baseUrl: argv.baseUrl,
|
|
242
|
-
jsonMode
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
async function provision({ email, region, name, baseUrl, jsonMode }) {
|
|
246
|
-
try {
|
|
247
|
-
const { provisionNewAccount } = await import("./provisioning-B8uSqRZk.js").then((n) => n.r);
|
|
248
|
-
if (!jsonMode) getUI().log.info(`Provisioning account for ${email} in ${region}...`);
|
|
249
|
-
emitResult(await provisionNewAccount(email, name, region, { baseUrl }), jsonMode);
|
|
250
|
-
process.exit(0);
|
|
251
|
-
} catch (error) {
|
|
252
|
-
emitError(error, jsonMode);
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
function emitResult(result, jsonMode) {
|
|
257
|
-
if (jsonMode) {
|
|
258
|
-
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
getUI().log.success("Account provisioned successfully:");
|
|
262
|
-
getUI().log.info(` API Key: ${result.projectApiKey}`);
|
|
263
|
-
getUI().log.info(` Host: ${result.host}`);
|
|
264
|
-
getUI().log.info(` Project ID: ${result.projectId}`);
|
|
265
|
-
getUI().log.info(` Account ID: ${result.accountId}`);
|
|
266
|
-
getUI().log.info(` Access Token: ${result.accessToken}`);
|
|
267
|
-
getUI().log.info(` Refresh Token: ${result.refreshToken}`);
|
|
268
|
-
if (result.personalApiKey) getUI().log.info(` Personal API Key: ${result.personalApiKey}`);
|
|
269
|
-
}
|
|
270
|
-
function emitError(error, jsonMode) {
|
|
271
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
272
|
-
const code = msg.includes("already associated") ? "email_exists" : "provisioning_failed";
|
|
273
|
-
if (jsonMode) {
|
|
274
|
-
process.stderr.write(`${JSON.stringify({
|
|
275
|
-
error: msg,
|
|
276
|
-
code
|
|
277
|
-
})}\n`);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
getUI().log.error(`Provisioning failed: ${msg}`);
|
|
281
|
-
}
|
|
282
|
-
//#endregion
|
|
283
|
-
//#region src/commands/basic-integration/index.ts
|
|
284
|
-
const basicIntegrationCommand = {
|
|
285
|
-
name: ["$0"],
|
|
286
|
-
description: "Run the PostHog setup wizard",
|
|
287
|
-
children: [provisionCommand],
|
|
288
|
-
options: {
|
|
289
|
-
"install-dir": {
|
|
290
|
-
describe: "Directory to install PostHog in\nenv: POSTHOG_WIZARD_INSTALL_DIR",
|
|
291
|
-
type: "string"
|
|
292
|
-
},
|
|
293
|
-
name: {
|
|
294
|
-
describe: "Name for account creation with --ci --signup\nenv: POSTHOG_WIZARD_NAME",
|
|
295
|
-
type: "string"
|
|
296
|
-
},
|
|
297
|
-
playground: {
|
|
298
|
-
default: false,
|
|
299
|
-
describe: "Launch the TUI primitives playground",
|
|
300
|
-
type: "boolean",
|
|
301
|
-
hidden: true
|
|
302
|
-
},
|
|
303
|
-
...headlessOption,
|
|
304
|
-
...regionOption
|
|
305
|
-
},
|
|
306
|
-
check: (argv) => {
|
|
307
|
-
if (argv.playground && (argv.ci || isHeadless(argv))) throw new Error("--playground cannot be combined with a headless run.");
|
|
308
|
-
return true;
|
|
309
|
-
},
|
|
310
|
-
handler: (argv) => {
|
|
311
|
-
setEntryCommand("integrate");
|
|
312
|
-
(async () => {
|
|
313
|
-
if (isHeadless(argv)) {
|
|
314
|
-
const { runHeadlessInstall } = await import("./ci-install-CDdQYsPe.js");
|
|
315
|
-
return runHeadlessInstall(argv);
|
|
316
|
-
}
|
|
317
|
-
if (argv.ci) {
|
|
318
|
-
const { runCIInstall } = await import("./ci-install-CDdQYsPe.js");
|
|
319
|
-
return runCIInstall(argv);
|
|
320
|
-
}
|
|
321
|
-
if (isNonInteractiveEnvironment()) {
|
|
322
|
-
const { failNonInteractive } = await import("./non-interactive-Dzow7YKY.js");
|
|
323
|
-
return failNonInteractive();
|
|
324
|
-
}
|
|
325
|
-
if (argv.playground) {
|
|
326
|
-
const { runPlayground } = await import("./playground-5AB4WgNF.js");
|
|
327
|
-
return runPlayground();
|
|
328
|
-
}
|
|
329
|
-
const { runInteractive } = await import("./interactive-D6zV7brJ.js");
|
|
330
|
-
runInteractive(argv);
|
|
331
|
-
})();
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
//#endregion
|
|
335
|
-
//#region src/commands/mcp/tui-availability.ts
|
|
336
|
-
/**
|
|
337
|
-
* Ink throws "Raw mode is not supported" when stdin has no TTY (piped input,
|
|
338
|
-
* CI, some IDE terminals). That is the only TUI failure the mcp commands
|
|
339
|
-
* degrade to LoggingUI for — any other error from the TUI path is a real bug
|
|
340
|
-
* and must surface rather than be silently swallowed.
|
|
341
|
-
*/
|
|
342
|
-
function isTUIUnavailable(error) {
|
|
343
|
-
return error instanceof Error && /raw mode is not supported/i.test(error.message);
|
|
344
|
-
}
|
|
345
|
-
//#endregion
|
|
346
|
-
//#region src/commands/mcp/add.ts
|
|
347
|
-
const mcpAddCommand = {
|
|
348
|
-
name: "add",
|
|
349
|
-
description: "Install PostHog MCP server to supported clients",
|
|
350
|
-
options: {
|
|
351
|
-
local: {
|
|
352
|
-
default: false,
|
|
353
|
-
describe: "Add local development MCP server (http://localhost:8787)",
|
|
354
|
-
type: "boolean"
|
|
355
|
-
},
|
|
356
|
-
features: {
|
|
357
|
-
describe: "Comma-separated list of features to enable (default: all)",
|
|
358
|
-
type: "string"
|
|
359
|
-
},
|
|
360
|
-
"api-key": {
|
|
361
|
-
describe: "PostHog personal API key (phx_xxx) for MCP authentication",
|
|
362
|
-
type: "string"
|
|
363
|
-
},
|
|
364
|
-
...headlessOption
|
|
365
|
-
},
|
|
366
|
-
handler: runMcpAdd
|
|
367
|
-
};
|
|
368
|
-
function runMcpAdd(argv) {
|
|
369
|
-
const features = parseFeatures(argv.features);
|
|
370
|
-
(async () => {
|
|
371
|
-
const { readApiKeyFromEnv } = await import("./env-api-key-Qx8NnYHm.js").then((n) => n.t);
|
|
372
|
-
const apiKey = argv.apiKey || readApiKeyFromEnv();
|
|
373
|
-
const debug = argv.debug;
|
|
374
|
-
const localMcp = argv.local;
|
|
375
|
-
const args = {
|
|
376
|
-
local: localMcp,
|
|
377
|
-
features,
|
|
378
|
-
apiKey
|
|
379
|
-
};
|
|
380
|
-
if (isHeadless(argv)) {
|
|
381
|
-
await runHeadlessAdd(args);
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
try {
|
|
385
|
-
const { startTUI } = await import("./start-tui-CB2PZs1x.js");
|
|
386
|
-
const { buildSession } = await import("./wizard-session-DGaUHOF3.js");
|
|
387
|
-
const tui = startTUI(VERSION, Program.McpAdd);
|
|
388
|
-
tui.store.session = buildSession({
|
|
389
|
-
debug,
|
|
390
|
-
localMcp,
|
|
391
|
-
mcpFeatures: features,
|
|
392
|
-
apiKey,
|
|
393
|
-
baseUrl: argv.baseUrl
|
|
394
|
-
});
|
|
395
|
-
} catch (error) {
|
|
396
|
-
if (!isTUIUnavailable(error)) throw error;
|
|
397
|
-
await runHeadlessAdd(args);
|
|
398
|
-
}
|
|
399
|
-
})();
|
|
400
|
-
}
|
|
401
|
-
async function runHeadlessAdd(args) {
|
|
402
|
-
setUI(new LoggingUI());
|
|
403
|
-
const { addMCPServerToClientsStep } = await import("./add-mcp-server-to-clients-JMv8LP-O.js").then((n) => n.r);
|
|
404
|
-
const { installed, failed } = await addMCPServerToClientsStep(args);
|
|
405
|
-
if (failed.length > 0 || installed.length === 0) process.exitCode = 1;
|
|
406
|
-
}
|
|
407
|
-
function parseFeatures(raw) {
|
|
408
|
-
if (typeof raw !== "string") return void 0;
|
|
409
|
-
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
410
|
-
}
|
|
411
|
-
//#endregion
|
|
412
|
-
//#region src/commands/mcp/remove.ts
|
|
413
|
-
const mcpRemoveCommand = {
|
|
414
|
-
name: "remove",
|
|
415
|
-
description: "Remove PostHog MCP server from supported clients",
|
|
416
|
-
options: {
|
|
417
|
-
local: {
|
|
418
|
-
default: false,
|
|
419
|
-
describe: "Remove local development MCP server (http://localhost:8787)",
|
|
420
|
-
type: "boolean"
|
|
421
|
-
},
|
|
422
|
-
...headlessOption
|
|
423
|
-
},
|
|
424
|
-
handler: runMcpRemove
|
|
425
|
-
};
|
|
426
|
-
function runMcpRemove(argv) {
|
|
427
|
-
(async () => {
|
|
428
|
-
const debug = argv.debug;
|
|
429
|
-
const localMcp = argv.local;
|
|
430
|
-
if (isHeadless(argv)) {
|
|
431
|
-
await runHeadlessRemove(localMcp);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
try {
|
|
435
|
-
const { startTUI } = await import("./start-tui-CB2PZs1x.js");
|
|
436
|
-
const { buildSession } = await import("./wizard-session-DGaUHOF3.js");
|
|
437
|
-
const tui = startTUI(VERSION, Program.McpRemove);
|
|
438
|
-
tui.store.session = buildSession({
|
|
439
|
-
debug,
|
|
440
|
-
localMcp,
|
|
441
|
-
baseUrl: argv.baseUrl
|
|
442
|
-
});
|
|
443
|
-
} catch (error) {
|
|
444
|
-
if (!isTUIUnavailable(error)) throw error;
|
|
445
|
-
await runHeadlessRemove(localMcp);
|
|
446
|
-
}
|
|
447
|
-
})();
|
|
448
|
-
}
|
|
449
|
-
/** No exit code on an empty result: nothing to remove is the requested end state. */
|
|
450
|
-
async function runHeadlessRemove(local) {
|
|
451
|
-
setUI(new LoggingUI());
|
|
452
|
-
const { removeMCPServerFromClientsStep } = await import("./add-mcp-server-to-clients-JMv8LP-O.js").then((n) => n.r);
|
|
453
|
-
await removeMCPServerFromClientsStep({ local });
|
|
454
|
-
}
|
|
455
|
-
//#endregion
|
|
456
|
-
//#region src/commands/mcp/tutorial.ts
|
|
457
|
-
const mcpTutorialCommand = {
|
|
458
|
-
name: "tutorial",
|
|
459
|
-
description: "Try the PostHog MCP with your agent (no install needed)",
|
|
460
|
-
options: { local: {
|
|
461
|
-
default: false,
|
|
462
|
-
describe: "Point the tutorial at the local MCP server (http://localhost:8787)",
|
|
463
|
-
type: "boolean"
|
|
464
|
-
} },
|
|
465
|
-
handler: runMcpTutorial
|
|
466
|
-
};
|
|
467
|
-
function runMcpTutorial(argv) {
|
|
468
|
-
(async () => {
|
|
469
|
-
const debug = argv.debug;
|
|
470
|
-
const localMcp = argv.local;
|
|
471
|
-
try {
|
|
472
|
-
const { startTUI } = await import("./start-tui-CB2PZs1x.js");
|
|
473
|
-
const { buildSession } = await import("./wizard-session-DGaUHOF3.js");
|
|
474
|
-
const tui = startTUI(VERSION, Program.McpTutorial);
|
|
475
|
-
tui.store.session = buildSession({
|
|
476
|
-
debug,
|
|
477
|
-
localMcp,
|
|
478
|
-
baseUrl: argv.baseUrl
|
|
479
|
-
});
|
|
480
|
-
} catch (err) {
|
|
481
|
-
setUI(new LoggingUI());
|
|
482
|
-
getUI().log.error(`The MCP tutorial requires an interactive terminal. ${err instanceof Error ? err.message : String(err)}`);
|
|
483
|
-
emitWizardError({
|
|
484
|
-
code: ErrorCodes.CliInteractiveRequired,
|
|
485
|
-
message: "The MCP tutorial requires an interactive terminal."
|
|
486
|
-
});
|
|
487
|
-
process.exit(1);
|
|
488
|
-
}
|
|
489
|
-
})();
|
|
490
|
-
}
|
|
491
|
-
//#endregion
|
|
492
|
-
//#region src/commands/mcp/index.ts
|
|
493
|
-
const mcpCommand = {
|
|
494
|
-
name: "mcp",
|
|
495
|
-
description: "MCP server management commands",
|
|
496
|
-
children: [
|
|
497
|
-
mcpAddCommand,
|
|
498
|
-
mcpRemoveCommand,
|
|
499
|
-
mcpTutorialCommand
|
|
500
|
-
]
|
|
501
|
-
};
|
|
502
|
-
//#endregion
|
|
503
|
-
//#region src/lib/runners/resolve-no-telemetry.ts
|
|
504
|
-
/**
|
|
505
|
-
* `--no-telemetry` flips `telemetry: false` via yargs negation;
|
|
506
|
-
* `POSTHOG_WIZARD_NO_TELEMETRY` is honoured separately so the env-var
|
|
507
|
-
* form documented in the README keeps working.
|
|
508
|
-
*/
|
|
509
|
-
function resolveNoTelemetry(options) {
|
|
510
|
-
if (options.telemetry === false) return true;
|
|
511
|
-
const env = process.env.POSTHOG_WIZARD_NO_TELEMETRY;
|
|
512
|
-
if (env == null || env === "") return false;
|
|
513
|
-
const norm = env.toLowerCase();
|
|
514
|
-
return norm !== "0" && norm !== "false";
|
|
515
|
-
}
|
|
516
|
-
//#endregion
|
|
517
|
-
//#region src/lib/task-stream/wizard-run-sync.ts
|
|
518
|
-
const STATUS = {
|
|
519
|
-
pending: "created",
|
|
520
|
-
in_progress: "running",
|
|
521
|
-
completed: "completed",
|
|
522
|
-
failed: "failed",
|
|
523
|
-
skipped: "completed"
|
|
524
|
-
};
|
|
525
|
-
var RunTaskNames = class {
|
|
526
|
-
names = /* @__PURE__ */ new Map();
|
|
527
|
-
used = /* @__PURE__ */ new Set();
|
|
528
|
-
snapshot(items) {
|
|
529
|
-
if (items.length > 100) throw new Error("task limit exceeded (100)");
|
|
530
|
-
const occurrences = /* @__PURE__ */ new Map();
|
|
531
|
-
const ids = /* @__PURE__ */ new Set();
|
|
532
|
-
return items.map((item) => {
|
|
533
|
-
const subject = item.label.trim();
|
|
534
|
-
if (!subject) throw new Error("blank task name");
|
|
535
|
-
const rawStatus = item.sourceStatus ?? item.status;
|
|
536
|
-
const status = STATUS[rawStatus];
|
|
537
|
-
if (!Object.hasOwn(STATUS, rawStatus)) throw new Error("invalid task status");
|
|
538
|
-
const occurrence = (occurrences.get(subject) ?? 0) + 1;
|
|
539
|
-
occurrences.set(subject, occurrence);
|
|
540
|
-
const id = item.id ? JSON.stringify([item.source, item.id]) : JSON.stringify([subject, occurrence]);
|
|
541
|
-
if (ids.has(id)) throw new Error("duplicate task identity");
|
|
542
|
-
ids.add(id);
|
|
543
|
-
let name = this.names.get(id);
|
|
544
|
-
if (!name) {
|
|
545
|
-
name = subject.slice(0, 255);
|
|
546
|
-
for (let n = 2; this.used.has(name); n++) {
|
|
547
|
-
const suffix = ` (${n})`;
|
|
548
|
-
name = subject.slice(0, 255 - suffix.length) + suffix;
|
|
549
|
-
}
|
|
550
|
-
this.names.set(id, name);
|
|
551
|
-
this.used.add(name);
|
|
552
|
-
}
|
|
553
|
-
return {
|
|
554
|
-
name,
|
|
555
|
-
status
|
|
556
|
-
};
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
function createWizardRunSync(options) {
|
|
561
|
-
if (options.noTelemetry || options.mode === "ci") return;
|
|
562
|
-
if (options.mode === "headless" && options.assignedId === void 0) return;
|
|
563
|
-
return new WizardRunSync({
|
|
564
|
-
...options,
|
|
565
|
-
mode: options.assignedId !== void 0 ? "cloud" : "local"
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
var WizardRunSync = class {
|
|
569
|
-
names = new RunTaskNames();
|
|
570
|
-
tasksAbort = new AbortController();
|
|
571
|
-
reported = /* @__PURE__ */ new Set();
|
|
572
|
-
context;
|
|
573
|
-
queue = Promise.resolve();
|
|
574
|
-
lastBody;
|
|
575
|
-
stopped = false;
|
|
576
|
-
tasksDisabled = false;
|
|
577
|
-
authDisabled = false;
|
|
578
|
-
closing;
|
|
579
|
-
constructor(options) {
|
|
580
|
-
this.options = options;
|
|
581
|
-
}
|
|
582
|
-
capture(items) {
|
|
583
|
-
if (this.stopped || this.tasksDisabled || !items) return;
|
|
584
|
-
if (this.options.mode === "cloud" && !validate(this.options.assignedId ?? "")) {
|
|
585
|
-
this.tasksDisabled = true;
|
|
586
|
-
this.report("invalid cloud run assignment");
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
const session = this.options.getSession();
|
|
590
|
-
if (session.runPhase === "idle") return;
|
|
591
|
-
if (!this.context) {
|
|
592
|
-
if (session.runPhase !== "running" || !session.credentials) return;
|
|
593
|
-
const { host, projectId } = session.credentials;
|
|
594
|
-
this.context = {
|
|
595
|
-
apiHost: host.appHost.replace(/\/$/, ""),
|
|
596
|
-
projectId,
|
|
597
|
-
programId: this.options.programId,
|
|
598
|
-
mode: this.options.mode,
|
|
599
|
-
id: this.options.assignedId
|
|
600
|
-
};
|
|
601
|
-
this.queue = this.initialize(session);
|
|
602
|
-
}
|
|
603
|
-
try {
|
|
604
|
-
const body = JSON.stringify({ tasks: this.names.snapshot(items) });
|
|
605
|
-
if (body === this.lastBody) return;
|
|
606
|
-
this.lastBody = body;
|
|
607
|
-
this.queue = this.queue.then(async () => {
|
|
608
|
-
if (this.tasksDisabled || this.tasksAbort.signal.aborted || !this.context?.id) return;
|
|
609
|
-
if (!await this.request("PUT", `${this.context.id}/tasks/`, body, this.tasksAbort.signal)) this.tasksDisabled = true;
|
|
610
|
-
});
|
|
611
|
-
} catch {
|
|
612
|
-
this.report("invalid task snapshot (names, statuses, identities or 100-task limit)");
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
shutdown(outcome, timeoutMs) {
|
|
616
|
-
if (this.closing) return this.closing;
|
|
617
|
-
this.stopped = true;
|
|
618
|
-
this.closing = this.finish(outcome, Math.max(0, timeoutMs));
|
|
619
|
-
return this.closing;
|
|
620
|
-
}
|
|
621
|
-
async initialize(session) {
|
|
622
|
-
const context = this.context;
|
|
623
|
-
if (!context || context.mode === "cloud") return;
|
|
624
|
-
const projectName = basename(resolve(session.installDir)).trim().slice(0, 255);
|
|
625
|
-
if (!projectName) {
|
|
626
|
-
this.tasksDisabled = true;
|
|
627
|
-
this.report("unsupported program or workspace configuration");
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
const body = JSON.stringify({
|
|
631
|
-
program_id: context.programId,
|
|
632
|
-
environment: "local",
|
|
633
|
-
workspace: {
|
|
634
|
-
type: "local_folder",
|
|
635
|
-
project_name: projectName
|
|
636
|
-
},
|
|
637
|
-
...valid("2.78.0") ? { wizard_version: VERSION } : {},
|
|
638
|
-
idempotency_key: randomUUID()
|
|
639
|
-
});
|
|
640
|
-
const result = await this.request("POST", "", body, this.tasksAbort.signal);
|
|
641
|
-
if (result && typeof result === "object" && "id" in result && typeof result.id === "string" && validate(result.id)) context.id = result.id;
|
|
642
|
-
else {
|
|
643
|
-
this.tasksDisabled = true;
|
|
644
|
-
this.report("local creation failed or returned an invalid ID; creation was not retried");
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
credentials(creds = this.options.getSession().credentials) {
|
|
648
|
-
const context = this.context;
|
|
649
|
-
if (!context) return;
|
|
650
|
-
if (!creds || creds.host.appHost.replace(/\/$/, "") !== context.apiHost || creds.projectId !== context.projectId) {
|
|
651
|
-
this.report("execution credentials changed host or project");
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
if (creds.missingScopes?.includes("wizard_run:write") || !creds.accessToken.startsWith("pha_")) {
|
|
655
|
-
this.authDisabled = true;
|
|
656
|
-
this.report("wizard_run:write OAuth grant required; authorize again");
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
return creds;
|
|
660
|
-
}
|
|
661
|
-
async request(method, suffix, body, signal) {
|
|
662
|
-
const context = this.context;
|
|
663
|
-
if (!context || this.authDisabled || signal.aborted) return;
|
|
664
|
-
let refreshed = false;
|
|
665
|
-
let rateLimited = false;
|
|
666
|
-
const attempts = method === "POST" ? 1 : 3;
|
|
667
|
-
for (let attempt = 0; attempt < attempts && !signal.aborted; attempt++) {
|
|
668
|
-
let retryMs = 500 * 2 ** attempt;
|
|
669
|
-
try {
|
|
670
|
-
const creds = this.credentials();
|
|
671
|
-
if (!creds) return;
|
|
672
|
-
const { res, data, current } = await this.bounded(async (requestSignal) => {
|
|
673
|
-
const current = this.credentials(await currentCredentials(creds));
|
|
674
|
-
if (!current) throw new Error("credentials unavailable");
|
|
675
|
-
const res = await (this.options.fetchImpl ?? fetch)(`${context.apiHost}/api/projects/${context.projectId}/wizard/runs/${suffix}`, {
|
|
676
|
-
method,
|
|
677
|
-
body,
|
|
678
|
-
signal: requestSignal,
|
|
679
|
-
headers: {
|
|
680
|
-
Authorization: `Bearer ${current.accessToken}`,
|
|
681
|
-
"Content-Type": "application/json"
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
return {
|
|
685
|
-
res,
|
|
686
|
-
data: method === "POST" && (res.status === 200 || res.status === 201) ? await res.json() : void 0,
|
|
687
|
-
current
|
|
688
|
-
};
|
|
689
|
-
}, signal, 5e3);
|
|
690
|
-
if (method === "POST" && [200, 201].includes(res.status) || method === "PUT" && res.status === 204 || method === "PATCH" && res.status === 200) return data ?? true;
|
|
691
|
-
if (res.status === 401 && current.refreshToken && !isGrantRevoked() && !refreshed && method !== "POST") {
|
|
692
|
-
refreshed = true;
|
|
693
|
-
if ((await this.bounded(() => currentCredentials(current, true), signal, 5e3)).accessToken === current.accessToken) {
|
|
694
|
-
this.authDisabled = true;
|
|
695
|
-
this.report("OAuth refresh could not recover the grant; authorize again");
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
continue;
|
|
699
|
-
}
|
|
700
|
-
if (res.status === 401 || res.status === 403) this.authDisabled = true;
|
|
701
|
-
if (res.status === 429 && !rateLimited) {
|
|
702
|
-
rateLimited = true;
|
|
703
|
-
retryMs = parseRetryAfter(res.headers.get("Retry-After"));
|
|
704
|
-
} else if (res.status < 500 || res.status >= 600) {
|
|
705
|
-
this.report(`${method} rejected (${res.status}); check assignment, grant and program configuration`);
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
} catch {
|
|
709
|
-
if (signal.aborted) return;
|
|
710
|
-
}
|
|
711
|
-
if (attempt + 1 < attempts) try {
|
|
712
|
-
await this.sleep(retryMs, signal);
|
|
713
|
-
} catch {
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
this.report(`${method} delivery exhausted`);
|
|
718
|
-
}
|
|
719
|
-
async finish(outcome, timeoutMs) {
|
|
720
|
-
const deadline = Date.now() + timeoutMs;
|
|
721
|
-
try {
|
|
722
|
-
await this.bounded(() => this.queue, new AbortController().signal, Math.floor(timeoutMs * .75));
|
|
723
|
-
} catch {
|
|
724
|
-
this.report("task drain timed out");
|
|
725
|
-
}
|
|
726
|
-
this.tasksAbort.abort();
|
|
727
|
-
if (this.context?.mode !== "local" || !this.context.id || this.authDisabled) return;
|
|
728
|
-
if (Date.now() >= deadline) {
|
|
729
|
-
this.report("terminal update budget exhausted");
|
|
730
|
-
return;
|
|
731
|
-
}
|
|
732
|
-
const finalAbort = new AbortController();
|
|
733
|
-
const timer = setTimeout(() => finalAbort.abort(), Math.max(0, deadline - Date.now()));
|
|
734
|
-
try {
|
|
735
|
-
await this.request("PATCH", `${this.context.id}/`, JSON.stringify({ status: outcome }), finalAbort.signal);
|
|
736
|
-
} finally {
|
|
737
|
-
clearTimeout(timer);
|
|
738
|
-
finalAbort.abort();
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
async bounded(operation, parent, ms) {
|
|
742
|
-
const controller = new AbortController();
|
|
743
|
-
const abort = () => controller.abort();
|
|
744
|
-
parent.addEventListener("abort", abort, { once: true });
|
|
745
|
-
if (parent.aborted) controller.abort();
|
|
746
|
-
const timer = setTimeout(abort, ms);
|
|
747
|
-
let onAbort = () => void 0;
|
|
748
|
-
const cancelled = new Promise((_, reject) => {
|
|
749
|
-
onAbort = () => reject(/* @__PURE__ */ new Error("request cancelled"));
|
|
750
|
-
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
751
|
-
if (controller.signal.aborted) onAbort();
|
|
752
|
-
});
|
|
753
|
-
try {
|
|
754
|
-
return await Promise.race([operation(controller.signal), cancelled]);
|
|
755
|
-
} finally {
|
|
756
|
-
clearTimeout(timer);
|
|
757
|
-
parent.removeEventListener("abort", abort);
|
|
758
|
-
controller.signal.removeEventListener("abort", onAbort);
|
|
759
|
-
controller.abort();
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
sleep(ms, signal) {
|
|
763
|
-
return new Promise((resolve) => {
|
|
764
|
-
const finish = () => {
|
|
765
|
-
clearTimeout(timer);
|
|
766
|
-
signal.removeEventListener("abort", finish);
|
|
767
|
-
resolve();
|
|
768
|
-
};
|
|
769
|
-
const timer = setTimeout(finish, ms);
|
|
770
|
-
signal.addEventListener("abort", finish, { once: true });
|
|
771
|
-
if (signal.aborted) finish();
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
report(message) {
|
|
775
|
-
if (this.reported.has(message)) return;
|
|
776
|
-
this.reported.add(message);
|
|
777
|
-
(this.options.onError ?? ((m) => logToFile(`[wizard-run-sync] ${m}`)))(message);
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
//#endregion
|
|
781
|
-
//#region src/lib/runners/run-wizard.ts
|
|
782
|
-
const WIZARD_VERSION = VERSION;
|
|
783
|
-
/** The session a run step's agent runs in: scoped to the step's target dir
|
|
784
|
-
* (e.g. a monorepo sub-app) with its own framework context, after any prep.
|
|
785
|
-
* A step without `targetDir` runs in the live session, unchanged.
|
|
786
|
-
* The frameworkContext copy is shallow and unfiltered — name keys per owning program. */
|
|
787
|
-
async function prepareRunSession(step, live) {
|
|
788
|
-
const session = step.targetDir ? {
|
|
789
|
-
...live,
|
|
790
|
-
installDir: step.targetDir(live),
|
|
791
|
-
frameworkContext: { ...live.frameworkContext }
|
|
792
|
-
} : live;
|
|
793
|
-
if (step.onRunPrep) await step.onRunPrep(session);
|
|
794
|
-
return session;
|
|
795
|
-
}
|
|
796
|
-
/** Advance one step of a composed run to completion: the auth screen
|
|
797
|
-
* authenticates (every later run reuses it); a step carrying its own `run`
|
|
798
|
-
* thunk runs that agent in its dir and is recorded in `completedRuns`; the
|
|
799
|
-
* host program's own run screen runs `config.run`; any other screen waits for
|
|
800
|
-
* the user to satisfy `isComplete`. */
|
|
801
|
-
async function advanceStep(step, store, config) {
|
|
802
|
-
if (step.screenId === "auth") {
|
|
803
|
-
await authenticate(store.session, config.id);
|
|
804
|
-
maybeStampAiSdkDetected(store.session);
|
|
805
|
-
} else if (step.run) {
|
|
806
|
-
await step.run(await prepareRunSession(step, store.session));
|
|
807
|
-
store.completeRunStep(step.id);
|
|
808
|
-
} else if (step.screenId === "run") await runProgramAgent(config, await prepareRunSession(step, store.session));
|
|
809
|
-
else if (step.isComplete) await store.waitUntil(step.isComplete);
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Run a full wizard program in the TUI. Handles the full lifecycle: start TUI,
|
|
813
|
-
* build session, run detection, wait for intro gate, execute the
|
|
814
|
-
* agent pipeline, wait for outro dismissal, then exit.
|
|
815
|
-
*/
|
|
816
|
-
function runWizard(config, options) {
|
|
817
|
-
let tui = null;
|
|
818
|
-
let taskStream = null;
|
|
819
|
-
let onSignal = null;
|
|
820
|
-
let exitInProgress = false;
|
|
821
|
-
let signalled = false;
|
|
822
|
-
let unregisterShutdown;
|
|
823
|
-
(async () => {
|
|
824
|
-
try {
|
|
825
|
-
const installDir = options.installDir || process.cwd();
|
|
826
|
-
const { startTUI } = await import("./start-tui-CB2PZs1x.js");
|
|
827
|
-
const { buildSession, RunPhase } = await import("./wizard-session-DGaUHOF3.js");
|
|
828
|
-
const { TaskStreamPush } = await import("./task-stream-CMUilu54.js");
|
|
829
|
-
const { PostHogDestination } = await import("./posthog-DNn574nW.js").then((n) => n.r);
|
|
830
|
-
const { createFileDestination } = await import("./file-BcLTjU16.js");
|
|
831
|
-
const local = getLocalDev();
|
|
832
|
-
const localServicesError = await checkLocalServices({
|
|
833
|
-
...local,
|
|
834
|
-
localPosthog: local.localPosthog && !options.baseUrl
|
|
835
|
-
});
|
|
836
|
-
if (localServicesError) {
|
|
837
|
-
const { wizardAbort } = await import("./wizard-abort-BU4aLyvh.js");
|
|
838
|
-
await wizardAbort({ message: localServicesError });
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
tui = startTUI(WIZARD_VERSION, config.id, () => onSignal?.());
|
|
842
|
-
const activeTui = tui;
|
|
843
|
-
const session = buildSession({
|
|
844
|
-
debug: options.debug,
|
|
845
|
-
localDev: options.localDev,
|
|
846
|
-
localMcp: options.localMcp,
|
|
847
|
-
localPosthog: options.localPosthog,
|
|
848
|
-
installDir,
|
|
849
|
-
ci: false,
|
|
850
|
-
signup: options.signup,
|
|
851
|
-
apiKey: options.apiKey,
|
|
852
|
-
projectId: options.projectId,
|
|
853
|
-
email: options.email,
|
|
854
|
-
baseUrl: options.baseUrl,
|
|
855
|
-
benchmark: options.benchmark,
|
|
856
|
-
yaraReport: options.yaraReport,
|
|
857
|
-
noTelemetry: resolveNoTelemetry(options),
|
|
858
|
-
harness: options.harness,
|
|
859
|
-
sequence: options.sequence,
|
|
860
|
-
model: options.model,
|
|
861
|
-
integrate: options.integrate,
|
|
862
|
-
captureAio: options.captureAio
|
|
863
|
-
});
|
|
864
|
-
session.programLabel = config.id;
|
|
865
|
-
if (options.skillId) session.skillId = options.skillId;
|
|
866
|
-
else if (config.skillId) session.skillId = config.skillId;
|
|
867
|
-
activeTui.store.session = session;
|
|
868
|
-
onSignal = () => {
|
|
869
|
-
if (signalled || exitInProgress) return;
|
|
870
|
-
signalled = true;
|
|
871
|
-
logToFile("[run-wizard] signal received, flushing task stream");
|
|
872
|
-
runCleanups();
|
|
873
|
-
if (activeTui.store.session.runPhase === RunPhase.Running) activeTui.store.setRunPhase(RunPhase.Error);
|
|
874
|
-
const teardown = () => {
|
|
875
|
-
unregisterShutdown?.();
|
|
876
|
-
if (onSignal) {
|
|
877
|
-
process.off("SIGINT", onSignal);
|
|
878
|
-
process.off("SIGTERM", onSignal);
|
|
879
|
-
}
|
|
880
|
-
try {
|
|
881
|
-
activeTui.unmount();
|
|
882
|
-
} catch {}
|
|
883
|
-
process.exit(130);
|
|
884
|
-
};
|
|
885
|
-
Promise.all([taskStream?.shutdown(2e3, "cancelled"), analytics.shutdown("cancelled")]).catch(() => logToFile("[run-wizard] cancellation shutdown failed")).finally(teardown);
|
|
886
|
-
};
|
|
887
|
-
process.on("SIGINT", onSignal);
|
|
888
|
-
process.on("SIGTERM", onSignal);
|
|
889
|
-
for (;;) {
|
|
890
|
-
await activeTui.store.runReadyHooks();
|
|
891
|
-
await activeTui.store.getGate("intro");
|
|
892
|
-
const active = activeTui.store.router.activeProgram;
|
|
893
|
-
if (active === config.id) break;
|
|
894
|
-
config = getProgramConfig(active);
|
|
895
|
-
}
|
|
896
|
-
const fileDestination = createFileDestination(options.taskStreamLog);
|
|
897
|
-
const destinations = [...session.noTelemetry ? [] : [new PostHogDestination({
|
|
898
|
-
getCredentials: () => activeTui.store.session.credentials,
|
|
899
|
-
onError: (err) => logToFile("[task-stream-push]", err.message)
|
|
900
|
-
})], ...fileDestination ? [fileDestination] : []];
|
|
901
|
-
const taskStreamEnabled = destinations.length > 0;
|
|
902
|
-
const activeStream = new TaskStreamPush({
|
|
903
|
-
store: activeTui.store,
|
|
904
|
-
getFlags: () => analytics.getCachedWizardFlags(),
|
|
905
|
-
programId: config.streamWorkflowId ?? config.id,
|
|
906
|
-
runSync: createWizardRunSync({
|
|
907
|
-
mode: "local",
|
|
908
|
-
programId: config.id,
|
|
909
|
-
assignedId: options.runId ?? runtimeEnv("POSTHOG_WIZARD_RUN_ID"),
|
|
910
|
-
noTelemetry: session.noTelemetry,
|
|
911
|
-
getSession: () => activeTui.store.session
|
|
912
|
-
}),
|
|
913
|
-
destinations,
|
|
914
|
-
eventPlanPath: config.eventPlanFile ? join(session.installDir, config.eventPlanFile) : void 0,
|
|
915
|
-
auditChecks: config.auditLedgerFile ? () => getAuditChecks(activeTui.store.session) : void 0,
|
|
916
|
-
enabled: taskStreamEnabled
|
|
917
|
-
});
|
|
918
|
-
taskStream = activeStream;
|
|
919
|
-
activeStream.attach();
|
|
920
|
-
unregisterShutdown = registerShutdown((outcome) => {
|
|
921
|
-
if (activeTui.store.session.runPhase === RunPhase.Running) activeTui.store.setRunPhase(RunPhase.Error);
|
|
922
|
-
return activeStream.shutdown(2e3, outcome);
|
|
923
|
-
});
|
|
924
|
-
await activeTui.store.getGate("integration-check");
|
|
925
|
-
await activeTui.store.getGate("health-check");
|
|
926
|
-
const skipAgent = config.run == null;
|
|
927
|
-
const shown = (s) => !s.show || s.show(activeTui.store.session);
|
|
928
|
-
if (config.steps.some((s) => s.run || s.targetDir)) for (const step of config.steps) {
|
|
929
|
-
if (step.screenId === "outro") break;
|
|
930
|
-
if (shown(step)) await advanceStep(step, activeTui.store, config);
|
|
931
|
-
}
|
|
932
|
-
else if (skipAgent) {
|
|
933
|
-
const { getOrAskForProjectData } = await import("./setup-utils-CmTCTPkX.js").then((n) => n.r);
|
|
934
|
-
const { projectApiKey, host, accessToken, projectId } = await getOrAskForProjectData({
|
|
935
|
-
signup: session.signup,
|
|
936
|
-
ci: session.ci,
|
|
937
|
-
apiKey: session.apiKey,
|
|
938
|
-
projectId: session.projectId,
|
|
939
|
-
baseUrl: session.baseUrl,
|
|
940
|
-
programId: config.id
|
|
941
|
-
});
|
|
942
|
-
activeTui.store.setCredentials({
|
|
943
|
-
accessToken,
|
|
944
|
-
projectApiKey,
|
|
945
|
-
host,
|
|
946
|
-
projectId
|
|
947
|
-
});
|
|
948
|
-
} else try {
|
|
949
|
-
await runProgramAgent(config, activeTui.store.session);
|
|
950
|
-
} catch (error) {
|
|
951
|
-
const failure = classifyRunFailure(error);
|
|
952
|
-
logToFile("[run-wizard] run failed, handing off:", error);
|
|
953
|
-
runCleanups();
|
|
954
|
-
analytics.captureException(error instanceof Error ? error : new Error(String(error)), { error_code: failure.code });
|
|
955
|
-
getUI().outroError({
|
|
956
|
-
kind: "error",
|
|
957
|
-
errorCode: failure.code,
|
|
958
|
-
message: failure.message
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
if (signalled) return;
|
|
962
|
-
const runFailed = isRunFailure(activeTui.store.session);
|
|
963
|
-
await activeStream.finishRun(runFailed ? "failed" : "completed");
|
|
964
|
-
await activeTui.store.waitUntil((s) => {
|
|
965
|
-
if (s.mintHandoff === "exit") return true;
|
|
966
|
-
if (skipAgent && !runFailed) return s.outroDismissed;
|
|
967
|
-
return s.skillsComplete;
|
|
968
|
-
});
|
|
969
|
-
exitInProgress = true;
|
|
970
|
-
await activeStream.shutdown(2e3);
|
|
971
|
-
unregisterShutdown?.();
|
|
972
|
-
process.off("SIGINT", onSignal);
|
|
973
|
-
process.off("SIGTERM", onSignal);
|
|
974
|
-
if (runFailed) await analytics.shutdown("error");
|
|
975
|
-
activeTui.unmount();
|
|
976
|
-
process.exit(runFailed ? 1 : 0);
|
|
977
|
-
} catch (err) {
|
|
978
|
-
if (signalled) return;
|
|
979
|
-
logToFile("[run-wizard] FATAL:", err);
|
|
980
|
-
runCleanups();
|
|
981
|
-
exitInProgress = true;
|
|
982
|
-
if (onSignal) {
|
|
983
|
-
process.off("SIGINT", onSignal);
|
|
984
|
-
process.off("SIGTERM", onSignal);
|
|
985
|
-
}
|
|
986
|
-
if (taskStream) try {
|
|
987
|
-
await taskStream.shutdown(2e3, "failed");
|
|
988
|
-
} catch {}
|
|
989
|
-
unregisterShutdown?.();
|
|
990
|
-
if (tui) try {
|
|
991
|
-
tui.unmount();
|
|
992
|
-
} catch {}
|
|
993
|
-
const failure = classifyRunFailure(err);
|
|
994
|
-
if (failure.coded) console.error(failure.message);
|
|
995
|
-
else console.error("Wizard run failed:", err);
|
|
996
|
-
console.error(`Full logs: ${getLogFilePath()}`);
|
|
997
|
-
emitWizardError({
|
|
998
|
-
code: failure.code,
|
|
999
|
-
message: failure.message
|
|
1000
|
-
});
|
|
1001
|
-
process.exit(1);
|
|
1002
|
-
}
|
|
1003
|
-
})();
|
|
1004
|
-
}
|
|
1005
|
-
//#endregion
|
|
1006
|
-
//#region src/lib/programs/detect-map.ts
|
|
1007
|
-
const DETECT_CODES = {
|
|
1008
|
-
"bad-directory": ErrorCodes.DetectBadDirectory,
|
|
1009
|
-
"unsupported-platform": ErrorCodes.DetectUnsupportedPlatform,
|
|
1010
|
-
"no-project-files": ErrorCodes.DetectNoProjectFiles,
|
|
1011
|
-
"no-sources": ErrorCodes.DetectNoSources,
|
|
1012
|
-
"no-package-json": ErrorCodes.DetectNoPackageJson,
|
|
1013
|
-
"no-sdks": ErrorCodes.DetectNoSdks,
|
|
1014
|
-
"missing-stripe": ErrorCodes.DetectMissingStripe,
|
|
1015
|
-
"no-posthog-sdk": ErrorCodes.DetectNoPosthogSdk,
|
|
1016
|
-
"no-posthog": ErrorCodes.DetectNoPosthogSdk,
|
|
1017
|
-
"missing-posthog": ErrorCodes.DetectNoPosthogSdk
|
|
1018
|
-
};
|
|
1019
|
-
/**
|
|
1020
|
-
* `kind` arrives as a bare string — `frameworkContext.detectError` is untyped
|
|
1021
|
-
* storage — so the lookup can still miss. It falls back to a detect-group code
|
|
1022
|
-
* with `retry: 'no'`, never to `InternalUnhandled`: an unrecognized precondition
|
|
1023
|
-
* failure is still a precondition failure, and telling a sandbox to retry one
|
|
1024
|
-
* costs it the whole budget.
|
|
1025
|
-
*/
|
|
1026
|
-
function detectErrorCode(kind) {
|
|
1027
|
-
return DETECT_CODES[kind] ?? ErrorCodes.DetectUnclassified;
|
|
1028
|
-
}
|
|
1029
|
-
//#endregion
|
|
1030
|
-
//#region src/lib/runners/run-non-interactive.ts
|
|
1031
|
-
/** User-facing label for a non-interactive mode. */
|
|
1032
|
-
function modeLabel(mode) {
|
|
1033
|
-
return mode === "headless" ? "Headless" : "CI";
|
|
1034
|
-
}
|
|
1035
|
-
/** The credentials every non-interactive mode accepts, for error messages. */
|
|
1036
|
-
const API_KEY_HINT = "personal API key phx_xxx or wizard-app OAuth access token pha_xxx";
|
|
1037
|
-
/**
|
|
1038
|
-
* The single non-interactive validation layer: requires api-key and
|
|
1039
|
-
* install-dir. Every non-interactive entry point routes through
|
|
1040
|
-
* `runNonInteractive`, so this is the one place these checks live. UI must be
|
|
1041
|
-
* initialized before calling.
|
|
1042
|
-
*/
|
|
1043
|
-
function validateNonInteractiveOptions(options, mode) {
|
|
1044
|
-
const label = modeLabel(mode);
|
|
1045
|
-
if (!options.apiKey) {
|
|
1046
|
-
getUI().intro("PostHog Wizard");
|
|
1047
|
-
getUI().log.error(`${label} mode requires --api-key (${API_KEY_HINT})`);
|
|
1048
|
-
emitWizardError({
|
|
1049
|
-
code: ErrorCodes.ArgsMissingApiKey,
|
|
1050
|
-
message: `${label} mode requires --api-key (${API_KEY_HINT})`
|
|
1051
|
-
});
|
|
1052
|
-
process.exit(1);
|
|
1053
|
-
}
|
|
1054
|
-
if (!options.installDir) {
|
|
1055
|
-
getUI().intro("PostHog Wizard");
|
|
1056
|
-
getUI().log.error(`${label} mode requires --install-dir (directory to install in)`);
|
|
1057
|
-
emitWizardError({
|
|
1058
|
-
code: ErrorCodes.ArgsMissingInstallDir,
|
|
1059
|
-
message: `${label} mode requires --install-dir`
|
|
1060
|
-
});
|
|
1061
|
-
process.exit(1);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
/**
|
|
1065
|
-
* Non-interactive pipeline shared by CI (`runWizardCI`) and headless
|
|
1066
|
-
* (`runWizardHeadless`) runs.
|
|
1067
|
-
*
|
|
1068
|
-
* Validates flags, builds a `ci:true` session, runs `config.ciPreRun` (or the
|
|
1069
|
-
* program's `onReady` hooks by default), executes `runProgramAgent`, and routes any
|
|
1070
|
-
* failure through `wizardAbort`. `wizardAbort` owns all exits — never add a
|
|
1071
|
-
* raw `process.exit` here.
|
|
1072
|
-
*
|
|
1073
|
-
* `mode` is the only difference between the two callers today (it sets the
|
|
1074
|
-
* analytics build tag and the user-facing label). Keeping it a parameter is
|
|
1075
|
-
* what lets CI and headless share this body now and diverge later — branch on
|
|
1076
|
-
* `mode` here, or stop sharing this function entirely.
|
|
1077
|
-
*/
|
|
1078
|
-
function runNonInteractive(config, options, mode) {
|
|
1079
|
-
setUI(new LoggingUI());
|
|
1080
|
-
validateNonInteractiveOptions(options, mode);
|
|
1081
|
-
analytics.setTag("build", mode);
|
|
1082
|
-
(async () => {
|
|
1083
|
-
const path = await import("path");
|
|
1084
|
-
const { buildSession, RunPhase, OutroKind } = await import("./wizard-session-DGaUHOF3.js");
|
|
1085
|
-
const { readEnvironment } = await import("./environment-B07Bmxae.js").then((n) => n.t);
|
|
1086
|
-
const { readApiKeyFromEnv } = await import("./env-api-key-Qx8NnYHm.js").then((n) => n.t);
|
|
1087
|
-
const { configureLogFileFromEnvironment, logToFile } = await import("./debug-M2gV2iDq.js");
|
|
1088
|
-
const { wizardAbort, WizardError } = await import("./wizard-abort-BU4aLyvh.js");
|
|
1089
|
-
configureLogFileFromEnvironment();
|
|
1090
|
-
const env = readEnvironment();
|
|
1091
|
-
const apiKey = options.apiKey ?? readApiKeyFromEnv() ?? void 0;
|
|
1092
|
-
const installDir = path.isAbsolute(options.installDir) ? options.installDir : path.join(process.cwd(), options.installDir);
|
|
1093
|
-
const session = buildSession({
|
|
1094
|
-
debug: options.debug,
|
|
1095
|
-
installDir,
|
|
1096
|
-
ci: true,
|
|
1097
|
-
signup: options.signup,
|
|
1098
|
-
localDev: options.localDev,
|
|
1099
|
-
localMcp: options.localMcp,
|
|
1100
|
-
localPosthog: options.localPosthog,
|
|
1101
|
-
apiKey,
|
|
1102
|
-
email: options.email,
|
|
1103
|
-
projectId: options.projectId,
|
|
1104
|
-
baseUrl: options.baseUrl,
|
|
1105
|
-
benchmark: options.benchmark,
|
|
1106
|
-
yaraReport: options.yaraReport,
|
|
1107
|
-
noTelemetry: resolveNoTelemetry(options),
|
|
1108
|
-
harness: options.harness,
|
|
1109
|
-
sequence: options.sequence,
|
|
1110
|
-
model: options.model,
|
|
1111
|
-
captureAio: options.captureAio,
|
|
1112
|
-
...env,
|
|
1113
|
-
region: options.region ?? env.region
|
|
1114
|
-
});
|
|
1115
|
-
session.programLabel = config.id;
|
|
1116
|
-
if (config.skillId) session.skillId = config.skillId;
|
|
1117
|
-
const runDef = typeof config.run === "object" ? config.run : null;
|
|
1118
|
-
getUI().intro("Welcome to the PostHog setup wizard");
|
|
1119
|
-
getUI().log.info(`Running ${config.id} in ${modeLabel(mode)} mode`);
|
|
1120
|
-
const localServicesError = await checkLocalServices({
|
|
1121
|
-
...getLocalDev(),
|
|
1122
|
-
localMcp: session.localMcp,
|
|
1123
|
-
localPosthog: session.baseUrl === POSTHOG_LOCAL_URL
|
|
1124
|
-
});
|
|
1125
|
-
if (localServicesError) {
|
|
1126
|
-
await wizardAbort({
|
|
1127
|
-
code: ErrorCodes.EnvLocalServicesDown,
|
|
1128
|
-
message: localServicesError
|
|
1129
|
-
});
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
|
-
let store = null;
|
|
1133
|
-
let taskStream = null;
|
|
1134
|
-
{
|
|
1135
|
-
const { WizardStore } = await import("./store-UN7c-ASf.js").then((n) => n.n);
|
|
1136
|
-
const { HeadlessUI } = await import("./headless-ui-BFFmYC4v.js");
|
|
1137
|
-
const { TaskStreamPush, PostHogDestination, createFileDestination } = await import("./task-stream-CMUilu54.js");
|
|
1138
|
-
const fileDestination = createFileDestination(mode === "ci" ? options.taskStreamLog ?? "" : options.taskStreamLog);
|
|
1139
|
-
const posthogDestination = mode === "headless" && !session.noTelemetry ? new PostHogDestination({
|
|
1140
|
-
getCredentials: () => store?.session.credentials ?? session.credentials,
|
|
1141
|
-
onError: (e) => logToFile("[headless task-stream]", e.message)
|
|
1142
|
-
}) : null;
|
|
1143
|
-
const destinations = [...posthogDestination ? [posthogDestination] : [], ...fileDestination ? [fileDestination] : []];
|
|
1144
|
-
const headlessStore = new WizardStore(config.id);
|
|
1145
|
-
store = headlessStore;
|
|
1146
|
-
headlessStore.session = session;
|
|
1147
|
-
setUI(new HeadlessUI(headlessStore));
|
|
1148
|
-
taskStream = new TaskStreamPush({
|
|
1149
|
-
store: headlessStore,
|
|
1150
|
-
getFlags: () => analytics.getCachedWizardFlags(),
|
|
1151
|
-
programId: config.streamWorkflowId ?? config.id,
|
|
1152
|
-
runSync: createWizardRunSync({
|
|
1153
|
-
mode,
|
|
1154
|
-
programId: config.id,
|
|
1155
|
-
assignedId: options.runId ?? runtimeEnv("POSTHOG_WIZARD_RUN_ID"),
|
|
1156
|
-
noTelemetry: session.noTelemetry,
|
|
1157
|
-
getSession: () => headlessStore.session
|
|
1158
|
-
}),
|
|
1159
|
-
destinations,
|
|
1160
|
-
eventPlanPath: config.eventPlanFile ? join(session.installDir, config.eventPlanFile) : void 0,
|
|
1161
|
-
auditChecks: config.auditLedgerFile ? () => getAuditChecks(headlessStore.session) : void 0,
|
|
1162
|
-
enabled: destinations.length > 0
|
|
1163
|
-
});
|
|
1164
|
-
taskStream.attach();
|
|
1165
|
-
if (fileDestination) logToFile(`[task-stream] ${mode} dump: ${fileDestination.path}`);
|
|
1166
|
-
}
|
|
1167
|
-
const settleStream = async (phase, outroData, outcome = phase === RunPhase.Completed ? "completed" : "failed") => {
|
|
1168
|
-
if (!store || !taskStream) return;
|
|
1169
|
-
if (outroData) store.setOutroData(outroData);
|
|
1170
|
-
store.setRunPhase(phase);
|
|
1171
|
-
await taskStream.shutdown(2e3, outcome);
|
|
1172
|
-
process.off("SIGINT", onSignal);
|
|
1173
|
-
process.off("SIGTERM", onSignal);
|
|
1174
|
-
unregisterShutdown();
|
|
1175
|
-
};
|
|
1176
|
-
const unregisterShutdown = registerShutdown((outcome) => settleStream(RunPhase.Error, void 0, outcome));
|
|
1177
|
-
let signalled = false;
|
|
1178
|
-
const onSignal = () => {
|
|
1179
|
-
if (signalled) return;
|
|
1180
|
-
signalled = true;
|
|
1181
|
-
runCleanups();
|
|
1182
|
-
settleStream(RunPhase.Error, void 0, "cancelled").then(() => wizardAbort({ exitCode: 130 }));
|
|
1183
|
-
};
|
|
1184
|
-
process.on("SIGINT", onSignal);
|
|
1185
|
-
process.on("SIGTERM", onSignal);
|
|
1186
|
-
try {
|
|
1187
|
-
if (mode === "ci") {
|
|
1188
|
-
const { configureGatewayFromCIEnvironment } = await import("./agent-bCZ3i3fh.js").then((n) => n.t);
|
|
1189
|
-
configureGatewayFromCIEnvironment(Number(session.projectId), session.region ?? "us");
|
|
1190
|
-
}
|
|
1191
|
-
if (config.ciPreRun) await config.ciPreRun(session);
|
|
1192
|
-
else {
|
|
1193
|
-
const readyCtx = {
|
|
1194
|
-
session,
|
|
1195
|
-
setFrameworkContext: (key, value) => {
|
|
1196
|
-
session.frameworkContext[key] = value;
|
|
1197
|
-
},
|
|
1198
|
-
setFrameworkConfig: () => void 0,
|
|
1199
|
-
setDetectedFramework: () => void 0,
|
|
1200
|
-
setSkillId: (skillId) => {
|
|
1201
|
-
session.skillId = skillId;
|
|
1202
|
-
},
|
|
1203
|
-
setUnsupportedVersion: (info) => {
|
|
1204
|
-
session.unsupportedVersion = info;
|
|
1205
|
-
},
|
|
1206
|
-
addDiscoveredFeature: () => void 0,
|
|
1207
|
-
setDetectionComplete: () => void 0,
|
|
1208
|
-
setPosthogSdkDetected: (detected) => {
|
|
1209
|
-
session.posthogSdkDetected = detected;
|
|
1210
|
-
}
|
|
1211
|
-
};
|
|
1212
|
-
for (const step of config.steps) if (step.onReady) await step.onReady(readyCtx);
|
|
1213
|
-
const detectError = session.frameworkContext.detectError;
|
|
1214
|
-
if (session.unsupportedVersion) {
|
|
1215
|
-
const { current, minimum, docsUrl } = session.unsupportedVersion;
|
|
1216
|
-
const message = `Detected framework version ${current} is not supported. Minimum supported version is ${minimum}.`;
|
|
1217
|
-
await settleStream(RunPhase.Error, {
|
|
1218
|
-
kind: OutroKind.Error,
|
|
1219
|
-
message,
|
|
1220
|
-
errorCode: ErrorCodes.DetectUnsupportedVersion
|
|
1221
|
-
});
|
|
1222
|
-
await wizardAbort({
|
|
1223
|
-
code: ErrorCodes.DetectUnsupportedVersion,
|
|
1224
|
-
message: `${message}\n\nSee ${docsUrl}`,
|
|
1225
|
-
error: new WizardError(`${config.id} unsupported framework version`, {
|
|
1226
|
-
integration: config.id,
|
|
1227
|
-
current,
|
|
1228
|
-
minimum
|
|
1229
|
-
}, ErrorCodes.DetectUnsupportedVersion)
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
if (detectError) {
|
|
1233
|
-
const code = detectErrorCode(detectError.kind);
|
|
1234
|
-
const detectKind = detectError.kind;
|
|
1235
|
-
const detail = { ...detectError };
|
|
1236
|
-
await settleStream(RunPhase.Error, {
|
|
1237
|
-
kind: OutroKind.Error,
|
|
1238
|
-
message: `Prerequisites not met: ${detectKind}`,
|
|
1239
|
-
errorCode: code,
|
|
1240
|
-
errorDetail: detail
|
|
1241
|
-
});
|
|
1242
|
-
await wizardAbort({
|
|
1243
|
-
code,
|
|
1244
|
-
detail,
|
|
1245
|
-
message: `Prerequisites not met: ${detectKind}\n\nSee ${runDef?.docsUrl ?? "https://posthog.com/docs"}`,
|
|
1246
|
-
error: new WizardError(`${config.id} prerequisites failed`, {
|
|
1247
|
-
integration: config.id,
|
|
1248
|
-
detect_error_kind: detectKind
|
|
1249
|
-
}, code)
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
const { runProgramAgent } = await import("./run-agent-legacy-d7rWlzQR.js").then((n) => n.n);
|
|
1254
|
-
await runProgramAgent(config, session);
|
|
1255
|
-
if (signalled) return;
|
|
1256
|
-
await settleStream(RunPhase.Completed);
|
|
1257
|
-
} catch (error) {
|
|
1258
|
-
if (signalled) return;
|
|
1259
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1260
|
-
const errorStack = error instanceof Error && error.stack ? error.stack : void 0;
|
|
1261
|
-
logToFile(`[${mode}] ERROR: ${errorMessage}`);
|
|
1262
|
-
if (errorStack) logToFile(`[${mode}] STACK: ${errorStack}`);
|
|
1263
|
-
const debugInfo = session.debug && errorStack ? `\n\n${errorStack}` : "";
|
|
1264
|
-
const docsUrl = session.frameworkConfig?.metadata.docsUrl ?? runDef?.docsUrl ?? "https://posthog.com/docs";
|
|
1265
|
-
const failure = classifyRunFailure(error);
|
|
1266
|
-
await settleStream(RunPhase.Error, {
|
|
1267
|
-
kind: OutroKind.Error,
|
|
1268
|
-
message: errorMessage,
|
|
1269
|
-
errorCode: failure.code
|
|
1270
|
-
});
|
|
1271
|
-
await wizardAbort({
|
|
1272
|
-
code: failure.code,
|
|
1273
|
-
message: failure.coded ? `${errorMessage}${debugInfo}` : `Something went wrong: ${errorMessage}\n\nYou can read the documentation at ${docsUrl} to set up manually.${debugInfo}`,
|
|
1274
|
-
error
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
})().catch((error) => {
|
|
1278
|
-
emitWizardError({
|
|
1279
|
-
code: ErrorCodes.InternalUnhandled,
|
|
1280
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1281
|
-
});
|
|
1282
|
-
process.exit(1);
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
//#endregion
|
|
1286
|
-
//#region src/lib/runners/run-wizard-ci.ts
|
|
1287
|
-
/**
|
|
1288
|
-
* CI-mode entry point (`--ci`, dev/test builds). A thin shell over the shared
|
|
1289
|
-
* non-interactive pipeline — see `runNonInteractive`. Kept as its own function
|
|
1290
|
-
* so CI and headless (`runWizardHeadless`) can diverge without re-threading the
|
|
1291
|
-
* many callers that route here.
|
|
1292
|
-
*/
|
|
1293
|
-
function runWizardCI(config, options) {
|
|
1294
|
-
runNonInteractive(config, options, "ci");
|
|
1295
|
-
}
|
|
1296
|
-
//#endregion
|
|
1297
|
-
//#region src/lib/runners/run-wizard-headless.ts
|
|
1298
|
-
/**
|
|
1299
|
-
* Headless entry point (the experimental published-build, non-interactive run
|
|
1300
|
-
* path; see @lib/headless-mode). A thin shell over the shared non-interactive
|
|
1301
|
-
* pipeline — see `runNonInteractive`. Today it behaves exactly like
|
|
1302
|
-
* `runWizardCI`; it exists as a separate function so headless can diverge later
|
|
1303
|
-
* (its own auth handling, telemetry, prompts, …) without touching CI or its
|
|
1304
|
-
* callers. Diverge by branching on the mode inside `runNonInteractive`, or by
|
|
1305
|
-
* giving this function its own body.
|
|
1306
|
-
*/
|
|
1307
|
-
function runWizardHeadless(config, options) {
|
|
1308
|
-
runNonInteractive(config, options, "headless");
|
|
1309
|
-
}
|
|
1310
|
-
//#endregion
|
|
1311
|
-
//#region src/commands/skill-program-options.ts
|
|
1312
|
-
/**
|
|
1313
|
-
* Per-command options shared by every skill-based program command
|
|
1314
|
-
* (`audit events`, `migrate statsig`, `revenue`, `source-maps`, …).
|
|
1315
|
-
*
|
|
1316
|
-
* Only flags unique to skill commands live here. Global flags are declared
|
|
1317
|
-
* once in `wizard.ts` — both in `GLOBAL_OPTIONS` and, for the dev-only ones,
|
|
1318
|
-
* the `Wizard` constructor — and apply to every command automatically.
|
|
1319
|
-
*/
|
|
1320
|
-
const skillProgramOptions = { "install-dir": {
|
|
1321
|
-
describe: "Directory to install in",
|
|
1322
|
-
type: "string"
|
|
1323
|
-
} };
|
|
1324
|
-
//#endregion
|
|
1325
|
-
//#region src/commands/factories/shared.ts
|
|
1326
|
-
/**
|
|
1327
|
-
* Dispatch a parsed yargs invocation to the wizard runner. Applies the
|
|
1328
|
-
* program's `mapCliOptions` transform, then routes to `runWizardHeadless`,
|
|
1329
|
-
* `runWizardCI`, or `runWizard` based on the headless / `--ci` flags.
|
|
1330
|
-
*
|
|
1331
|
-
* Every command file used to inline this; the factories call it instead.
|
|
1332
|
-
*/
|
|
1333
|
-
/**
|
|
1334
|
-
* Run a command's async body as fire-and-forget while still surfacing failures.
|
|
1335
|
-
* yargs handlers are synchronous, so async work kicks off a detached promise —
|
|
1336
|
-
* without this, a rejection becomes an unhandled promise rejection (no message,
|
|
1337
|
-
* wrong exit code). This awaits the work and turns any error into a clean
|
|
1338
|
-
* message + non-zero exit.
|
|
1339
|
-
*/
|
|
1340
|
-
function runCommandHandler(work) {
|
|
1341
|
-
(async () => {
|
|
1342
|
-
try {
|
|
1343
|
-
await work();
|
|
1344
|
-
} catch (err) {
|
|
1345
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1346
|
-
process.stderr.write(`\n\x1b[1;91m✖ ${msg}\x1b[0m\n\n`);
|
|
1347
|
-
emitWizardError({
|
|
1348
|
-
code: ErrorCodes.InternalUnhandled,
|
|
1349
|
-
message: msg
|
|
1350
|
-
});
|
|
1351
|
-
process.exit(1);
|
|
1352
|
-
}
|
|
1353
|
-
})();
|
|
1354
|
-
}
|
|
1355
|
-
function dispatchProgram(config, argv) {
|
|
1356
|
-
const argvRecord = { ...argv };
|
|
1357
|
-
const extras = config.mapCliOptions?.(argvRecord) ?? {};
|
|
1358
|
-
const options = {
|
|
1359
|
-
...argvRecord,
|
|
1360
|
-
...extras
|
|
1361
|
-
};
|
|
1362
|
-
if (isHeadless(options)) runWizardHeadless(config, options);
|
|
1363
|
-
else if (options.ci) runWizardCI(config, options);
|
|
1364
|
-
else runWizard(config, options);
|
|
1365
|
-
}
|
|
1366
|
-
/**
|
|
1367
|
-
* Merge the standard skill-program flags (`--debug`, `--install-dir`, etc.)
|
|
1368
|
-
* with any program-specific options declared on `cliOptions`.
|
|
1369
|
-
*
|
|
1370
|
-
* Program-specific options shadow the standard ones — that's intentional, so
|
|
1371
|
-
* a program can override a default flag if it ever needs to.
|
|
1372
|
-
*/
|
|
1373
|
-
function mergeCommandOptions(config) {
|
|
1374
|
-
return {
|
|
1375
|
-
...skillProgramOptions,
|
|
1376
|
-
...config.cliOptions ?? {}
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
//#endregion
|
|
1380
|
-
//#region src/commands/factories/native-command-factory.ts
|
|
1381
|
-
/**
|
|
1382
|
-
* Build a yargs `Command` from a wizard-native `ProgramConfig`.
|
|
1383
|
-
*
|
|
1384
|
-
* Collapses the previously duplicated boilerplate (read `config.command`,
|
|
1385
|
-
* merge skill-program flags with program-specific options, dispatch via
|
|
1386
|
-
* `runWizard` / `runWizardCI`) into a single call.
|
|
1387
|
-
*/
|
|
1388
|
-
function nativeCommandFactory(config, opts = {}) {
|
|
1389
|
-
if (!config.command) throw new Error(`nativeCommandFactory: program "${config.id}" has no \`command\` — wizard-native programs must declare a CLI name`);
|
|
1390
|
-
return {
|
|
1391
|
-
name: config.command,
|
|
1392
|
-
description: config.description,
|
|
1393
|
-
options: mergeCommandOptions(config),
|
|
1394
|
-
children: opts.children,
|
|
1395
|
-
handler: (argv) => dispatchProgram(config, argv)
|
|
1396
|
-
};
|
|
1397
|
-
}
|
|
1398
|
-
//#endregion
|
|
1399
|
-
//#region src/commands/mcp-analytics.ts
|
|
1400
|
-
/**
|
|
1401
|
-
* `wizard mcp-analytics` — flat skill command, instrument-an-MCP-server today.
|
|
1402
|
-
*
|
|
1403
|
-
* Distinct from `wizard mcp add`: this instruments the user's own MCP server
|
|
1404
|
-
* with the `@posthog/mcp` SDK, rather than installing the PostHog MCP server
|
|
1405
|
-
* into a coding agent. Stays flat while instrumenting is the only action.
|
|
1406
|
-
*/
|
|
1407
|
-
const mcpAnalyticsCommand = nativeCommandFactory(mcpAnalyticsConfig);
|
|
1408
|
-
//#endregion
|
|
1409
|
-
//#region src/commands/replay-vision.ts
|
|
1410
|
-
/**
|
|
1411
|
-
* `wizard replay-vision` — flat skill command, set up Replay vision today.
|
|
1412
|
-
*
|
|
1413
|
-
* Enables session replay recording and creates vision scanners tailored to
|
|
1414
|
-
* the product's key flows. Runs the `replay-vision` orchestrator flow, which
|
|
1415
|
-
* reuses the integration-v2 install/init mini-agents when the repo has no
|
|
1416
|
-
* PostHog integration yet.
|
|
1417
|
-
*/
|
|
1418
|
-
const replayVisionCommand = nativeCommandFactory(replayVisionConfig);
|
|
1419
|
-
//#endregion
|
|
1420
|
-
//#region src/commands/ai-observability.ts
|
|
1421
|
-
/**
|
|
1422
|
-
* `wizard ai-observability` — flat skill command, wire AI Observability into a
|
|
1423
|
-
* project today.
|
|
1424
|
-
*
|
|
1425
|
-
* Installs the OpenTelemetry SDK, the PostHog span processor, and the
|
|
1426
|
-
* provider-specific instrumentation so LLM calls emit `$ai_generation` events.
|
|
1427
|
-
* The `ai-observability` context-mill skill has one variant per (LLM provider ×
|
|
1428
|
-
* language); the agent picks the right one at run time by scanning the
|
|
1429
|
-
* project's manifest and (when ambiguous) asking the user via `wizard_ask`.
|
|
1430
|
-
* Stays flat while a single "add AIO to a project" flow is the only action.
|
|
1431
|
-
*/
|
|
1432
|
-
const aiObservabilityCommand = nativeCommandFactory(aiObservabilityConfig);
|
|
1433
|
-
//#endregion
|
|
1434
|
-
//#region src/commands/metrics.ts
|
|
1435
|
-
/**
|
|
1436
|
-
* `wizard metrics` — flat skill command, wire PostHog application metrics
|
|
1437
|
-
* (counters, gauges, histograms via \`posthog.metrics\`) into a project.
|
|
1438
|
-
*
|
|
1439
|
-
* The `metrics` context-mill skill has one variant per platform (python,
|
|
1440
|
-
* nodejs, javascript, kubernetes, other/OTLP); the agent picks the right one
|
|
1441
|
-
* at run time by scanning the project's manifest and (when ambiguous) asking
|
|
1442
|
-
* the user via `wizard_ask`. Stays flat while a single "add metrics to a
|
|
1443
|
-
* project" flow is the only action.
|
|
1444
|
-
*/
|
|
1445
|
-
const metricsCommand = nativeCommandFactory(metricsConfig);
|
|
1446
|
-
//#endregion
|
|
1447
|
-
//#region src/lib/programs/dispatch-family.ts
|
|
1448
|
-
/**
|
|
1449
|
-
* Capture a CLI dispatch error, flush analytics, and exit. The wizard never
|
|
1450
|
-
* starts a run from these paths — use flush() (not shutdown()) so we don't
|
|
1451
|
-
* fire a "setup wizard finished" event for a parse error that didn't run.
|
|
1452
|
-
*/
|
|
1453
|
-
async function exitDispatchError(reason, properties, message, code = 1) {
|
|
1454
|
-
analytics.wizardCapture("cli dispatch error", {
|
|
1455
|
-
reason,
|
|
1456
|
-
...properties
|
|
1457
|
-
});
|
|
1458
|
-
try {
|
|
1459
|
-
await analytics.flush();
|
|
1460
|
-
} catch {}
|
|
1461
|
-
process.stderr.write(message);
|
|
1462
|
-
emitWizardError({
|
|
1463
|
-
code: ErrorCodes.CliBadArgs,
|
|
1464
|
-
message: reason
|
|
1465
|
-
});
|
|
1466
|
-
return process.exit(code);
|
|
1467
|
-
}
|
|
1468
|
-
/**
|
|
1469
|
-
* Family commands (`wizard audit`, `wizard migrate`, ...) resolve their
|
|
1470
|
-
* subcommands at runtime against the published `cliEntries` inside
|
|
1471
|
-
* `skill-menu.json`. Adding a subcommand is a context-mill release — no
|
|
1472
|
-
* wizard release needed.
|
|
1473
|
-
*
|
|
1474
|
-
* Wizard-native subcommands (programs that aren't backed by a single skill,
|
|
1475
|
-
* e.g. `wizard audit web-analytics`) live here in code, dispatched directly
|
|
1476
|
-
* without touching the registry. Adding a native is a wizard PR.
|
|
1477
|
-
*/
|
|
1478
|
-
/** Wizard-native subcommands keyed by family. */
|
|
1479
|
-
const NATIVE_HANDLERS = { audit: { "web-analytics": webAnalyticsDoctorConfig } };
|
|
1480
|
-
/**
|
|
1481
|
-
* Resolve a fetched CliEntry to the ProgramConfig that actually runs it.
|
|
1482
|
-
* Most entries run via the generic agent-skill program with the entry's
|
|
1483
|
-
* `skillId` injected. The comprehensive `audit all` is the one exception —
|
|
1484
|
-
* skillId 'audit' triggers the specialized auditConfig (custom hooks,
|
|
1485
|
-
* content blocks, screens).
|
|
1486
|
-
*
|
|
1487
|
-
* This is the one place that knows a subcommand belongs to `audit`, so the
|
|
1488
|
-
* generic skill program picks up the ledger here rather than for every skill.
|
|
1489
|
-
*/
|
|
1490
|
-
function configForCliEntry(entry, family) {
|
|
1491
|
-
if (entry.skillId === "audit") return auditConfig;
|
|
1492
|
-
return {
|
|
1493
|
-
...agentSkillConfig,
|
|
1494
|
-
skillId: entry.skillId,
|
|
1495
|
-
...family === "audit" ? {
|
|
1496
|
-
auditLedgerFile: AUDIT_CHECKS_FILE,
|
|
1497
|
-
streamWorkflowId: family,
|
|
1498
|
-
allowedTools: [
|
|
1499
|
-
...agentSkillConfig.allowedTools ?? [],
|
|
1500
|
-
WIZARD_TOOL_NAMES.auditSeedChecks,
|
|
1501
|
-
WIZARD_TOOL_NAMES.auditAddChecks,
|
|
1502
|
-
WIZARD_TOOL_NAMES.auditResolveChecks
|
|
1503
|
-
]
|
|
1504
|
-
} : {}
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
function familyEntries(family, entries) {
|
|
1508
|
-
return entries.filter((e) => e.role === "command" && e.parentCommand === family && Boolean(e.command));
|
|
1509
|
-
}
|
|
1510
|
-
/**
|
|
1511
|
-
* Dispatch `wizard <family> <sub>` to the right program.
|
|
1512
|
-
*
|
|
1513
|
-
* Order:
|
|
1514
|
-
* 1. Native handler for (family, sub) — runs immediately, no network.
|
|
1515
|
-
* 2. Fetched CliEntry — runs the resolved skill.
|
|
1516
|
-
* 3. Unknown — prints the available list and exits non-zero.
|
|
1517
|
-
*/
|
|
1518
|
-
async function dispatchFamily(family, argv) {
|
|
1519
|
-
const sub = argv.skill?.trim();
|
|
1520
|
-
if (!sub) return exitDispatchError("missing subcommand", { family }, `\n\x1b[1;91m✖ \`wizard ${family}\` requires a subcommand.\x1b[0m\n Pass one (e.g. \`wizard ${family} <subcommand>\`), or run it in an interactive terminal to pick from a menu.\n\n`);
|
|
1521
|
-
const native = NATIVE_HANDLERS[family]?.[sub];
|
|
1522
|
-
if (native) {
|
|
1523
|
-
dispatchProgram(native, argv);
|
|
1524
|
-
return;
|
|
1525
|
-
}
|
|
1526
|
-
const skillsBaseUrl = getSkillsBaseUrl();
|
|
1527
|
-
const menu = await fetchSkillMenu(skillsBaseUrl);
|
|
1528
|
-
if (!menu) return exitDispatchError("registry unreachable", {
|
|
1529
|
-
family,
|
|
1530
|
-
sub,
|
|
1531
|
-
skillsBaseUrl
|
|
1532
|
-
}, `\n\x1b[1;91m✖ Couldn't reach the skill registry at ${skillsBaseUrl}.\x1b[0m\n Check your network connection and try again.\n\n`);
|
|
1533
|
-
const entries = menu.cliEntries ?? [];
|
|
1534
|
-
const entry = familyEntries(family, entries).find((e) => e.command === sub);
|
|
1535
|
-
if (entry) {
|
|
1536
|
-
dispatchProgram(configForCliEntry(entry, family), argv);
|
|
1537
|
-
return;
|
|
1538
|
-
}
|
|
1539
|
-
const available = [...Object.keys(NATIVE_HANDLERS[family] ?? {}), ...familyEntries(family, entries).map((e) => e.command)].sort();
|
|
1540
|
-
return exitDispatchError("unknown subcommand", {
|
|
1541
|
-
family,
|
|
1542
|
-
sub,
|
|
1543
|
-
available
|
|
1544
|
-
}, `\n\x1b[1;91m✖ Unknown subcommand "${sub}" under \`${family}\`.\x1b[0m\n` + (available.length ? ` Available: ${available.join(", ")}\n\n` : ` No subcommands published for "${family}" yet.\n\n`));
|
|
1545
|
-
}
|
|
1546
|
-
/**
|
|
1547
|
-
* Build the children list shown in the family's interactive picker.
|
|
1548
|
-
* Combines native handlers with skill-backed entries from the live registry.
|
|
1549
|
-
* Used by `familyCommandFactory`'s `interactiveDefault`.
|
|
1550
|
-
*/
|
|
1551
|
-
function buildFamilyPickerChildren(family, entries) {
|
|
1552
|
-
const natives = Object.entries(NATIVE_HANDLERS[family] ?? {}).map(([cmd, program]) => ({
|
|
1553
|
-
name: cmd,
|
|
1554
|
-
description: program.description,
|
|
1555
|
-
handler: (argv) => dispatchProgram(program, argv)
|
|
1556
|
-
}));
|
|
1557
|
-
const live = familyEntries(family, entries).map((entry) => ({
|
|
1558
|
-
name: entry.command,
|
|
1559
|
-
description: entry.description,
|
|
1560
|
-
handler: (argv) => {
|
|
1561
|
-
dispatchFamily(family, {
|
|
1562
|
-
...argv,
|
|
1563
|
-
skill: entry.command
|
|
1564
|
-
});
|
|
1565
|
-
},
|
|
1566
|
-
default: entry.default
|
|
1567
|
-
}));
|
|
1568
|
-
return [...natives, ...live];
|
|
1569
|
-
}
|
|
1570
|
-
/**
|
|
1571
|
-
* The children the family picker shows **today**: only the leaf marked
|
|
1572
|
-
* `default` (e.g. `audit events`). Every other subcommand stays runnable
|
|
1573
|
-
* directly (`wizard audit <name>`) — they just aren't listed in the picker yet.
|
|
1574
|
-
* Falls back to all children when nothing is marked `default`.
|
|
1575
|
-
*
|
|
1576
|
-
* Temporary: when we're ready to surface the full menu, return `children`
|
|
1577
|
-
* unchanged (and delete this note).
|
|
1578
|
-
*/
|
|
1579
|
-
function pickerChildrenToShow(children) {
|
|
1580
|
-
const defaults = children.filter((c) => c.default);
|
|
1581
|
-
return defaults.length > 0 ? [...defaults] : [...children];
|
|
1582
|
-
}
|
|
1583
|
-
//#endregion
|
|
1584
|
-
//#region src/ui/tui/primitives/picker-filter.ts
|
|
1585
|
-
/**
|
|
1586
|
-
* Filter-as-you-type matching for PickerMenu. Split out from the component so the
|
|
1587
|
-
* ranking is testable on its own — the component only decides when to call it.
|
|
1588
|
-
*/
|
|
1589
|
-
/**
|
|
1590
|
-
* Fuzzy fallback config. `ignoreLocation` because a term matching at the end of a
|
|
1591
|
-
* description is as good as one at the start, and Fuse's default distance window
|
|
1592
|
-
* would score the late match away. 0.5 is the loosest threshold that still bottoms
|
|
1593
|
-
* out: below it `gthb` misses `GitHub` entirely, which is the whole point of having
|
|
1594
|
-
* a fuzzy pass; above it the tail grows without surfacing anything new at the top.
|
|
1595
|
-
*/
|
|
1596
|
-
const FUSE_OPTIONS = {
|
|
1597
|
-
keys: [
|
|
1598
|
-
{
|
|
1599
|
-
name: "label",
|
|
1600
|
-
weight: 3
|
|
1601
|
-
},
|
|
1602
|
-
{
|
|
1603
|
-
name: "hint",
|
|
1604
|
-
weight: 1
|
|
1605
|
-
},
|
|
1606
|
-
{
|
|
1607
|
-
name: "description",
|
|
1608
|
-
weight: 1
|
|
1609
|
-
}
|
|
1610
|
-
],
|
|
1611
|
-
ignoreLocation: true,
|
|
1612
|
-
threshold: .5,
|
|
1613
|
-
includeScore: true
|
|
1614
|
-
};
|
|
1615
|
-
function buildPickerIndex(options) {
|
|
1616
|
-
return new Fuse(options, FUSE_OPTIONS);
|
|
1617
|
-
}
|
|
1618
|
-
function haystack(option) {
|
|
1619
|
-
return `${option.label} ${option.hint ?? ""} ${option.description ?? ""}`.toLowerCase();
|
|
1620
|
-
}
|
|
1621
|
-
/**
|
|
1622
|
-
* Rank options against a query, best first, or `null` for an empty query.
|
|
1623
|
-
*
|
|
1624
|
-
* Two passes, literal before fuzzy. Anything that literally contains every
|
|
1625
|
-
* whitespace-separated term wins outright, in the list's own order — that covers how
|
|
1626
|
-
* people actually type (`git iss` → `GitHub issues`) and keeps the common case as
|
|
1627
|
-
* tight as a plain substring filter, which fuzzy matching on its own is not: Fuse at
|
|
1628
|
-
* a threshold loose enough to forgive `gthb` also lets a third of the list through
|
|
1629
|
-
* on `replay`.
|
|
1630
|
-
*
|
|
1631
|
-
* Only when nothing matches literally does Fuse get a turn, so a typo degrades to a
|
|
1632
|
-
* ranked guess instead of an empty list. Terms are AND-ed there too, each matched
|
|
1633
|
-
* fuzzily on its own — Fuse's bitap pass over the whole query would otherwise need
|
|
1634
|
-
* `git iss` to be a near-edit of the text it's matching. Scores are distances (0 is
|
|
1635
|
-
* exact) and sum across terms, hence the ascending sort; ties keep the order the
|
|
1636
|
-
* first term put them in, since the sort is stable.
|
|
1637
|
-
*/
|
|
1638
|
-
function rankOptions(fuse, options, query) {
|
|
1639
|
-
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1640
|
-
if (!terms.length) return null;
|
|
1641
|
-
const literal = options.filter((option) => terms.every((term) => haystack(option).includes(term)));
|
|
1642
|
-
if (literal.length) return literal;
|
|
1643
|
-
let surviving = null;
|
|
1644
|
-
for (const term of terms) {
|
|
1645
|
-
const hits = new Map(fuse.search(term).map(({ item, score }) => [item, score ?? 0]));
|
|
1646
|
-
if (surviving === null) {
|
|
1647
|
-
surviving = hits;
|
|
1648
|
-
continue;
|
|
1649
|
-
}
|
|
1650
|
-
for (const [option, total] of surviving) {
|
|
1651
|
-
const score = hits.get(option);
|
|
1652
|
-
if (score === void 0) surviving.delete(option);
|
|
1653
|
-
else surviving.set(option, total + score);
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
return [...surviving ?? []].sort(([, a], [, b]) => a - b).map(([option]) => option);
|
|
1657
|
-
}
|
|
1658
|
-
//#endregion
|
|
1659
|
-
//#region src/ui/tui/primitives/PromptLabel.tsx
|
|
1660
|
-
/**
|
|
1661
|
-
* PromptLabel — Compact inline label for input prompts.
|
|
1662
|
-
*
|
|
1663
|
-
* Renders: [!] message
|
|
1664
|
-
* where [!] is black text on accent background.
|
|
1665
|
-
*/
|
|
1666
|
-
const PromptLabel = ({ message }) => {
|
|
1667
|
-
if (!message) return null;
|
|
1668
|
-
return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, {
|
|
1669
|
-
bold: true,
|
|
1670
|
-
color: Colors.accent,
|
|
1671
|
-
children: [" ", message]
|
|
1672
|
-
}) });
|
|
1673
|
-
};
|
|
1674
|
-
//#endregion
|
|
1675
|
-
//#region src/ui/tui/primitives/ConfirmButton.tsx
|
|
1676
|
-
/**
|
|
1677
|
-
* ConfirmButton — the confirm row used to submit a selection.
|
|
1678
|
-
*
|
|
1679
|
-
* Pure render. Multi-select menus (PickerMenu mode="multi", GroupedPickerMenu)
|
|
1680
|
-
* append this below their options as the final focusable row: the user toggles
|
|
1681
|
-
* options with enter, then arrows down onto this row and presses enter to
|
|
1682
|
-
* submit. This replaces the older "enter anywhere submits" pattern, which
|
|
1683
|
-
* confused people who expected enter to toggle the focused item.
|
|
1684
|
-
*
|
|
1685
|
-
* Renders flat, mirroring the option rows — a focus triangle and the label,
|
|
1686
|
-
* accent and bold when focused, dimmed otherwise — so it lines up under the
|
|
1687
|
-
* options instead of sitting in a separate boxed target.
|
|
1688
|
-
*/
|
|
1689
|
-
const ConfirmButton = ({ label = "Confirm", focused, count }) => {
|
|
1690
|
-
const text = count && count > 0 ? `${label} (${count})` : label;
|
|
1691
|
-
return /* @__PURE__ */ jsxs(Text, {
|
|
1692
|
-
color: focused ? Colors.accent : void 0,
|
|
1693
|
-
bold: focused,
|
|
1694
|
-
dimColor: !focused,
|
|
1695
|
-
children: [
|
|
1696
|
-
focused ? Icons.triangleSmallRight : " ",
|
|
1697
|
-
" ",
|
|
1698
|
-
text
|
|
1699
|
-
]
|
|
1700
|
-
});
|
|
1701
|
-
};
|
|
1702
|
-
//#endregion
|
|
1703
|
-
//#region src/ui/tui/hooks/useStdoutDimensions.ts
|
|
1704
|
-
/**
|
|
1705
|
-
* useStdoutDimensions — Returns [columns, rows] and re-renders on terminal resize.
|
|
1706
|
-
*
|
|
1707
|
-
* Ink's useStdout() does not subscribe to resize events, so layout only updates
|
|
1708
|
-
* when something else causes a re-render. This hook listens to the stream's
|
|
1709
|
-
* 'resize' event (Node TTY) and updates state so the component re-renders
|
|
1710
|
-
* with the new dimensions.
|
|
1711
|
-
*/
|
|
1712
|
-
function useStdoutDimensions() {
|
|
1713
|
-
const { stdout } = useStdout();
|
|
1714
|
-
const [size, setSize] = useState(() => [stdout.columns || 80, stdout.rows || 24]);
|
|
1715
|
-
useEffect(() => {
|
|
1716
|
-
setSize([stdout.columns || 80, stdout.rows || 24]);
|
|
1717
|
-
const stream = stdout;
|
|
1718
|
-
if (typeof stream.on !== "function") return;
|
|
1719
|
-
const onResize = () => {
|
|
1720
|
-
const c = stdout.columns || 80;
|
|
1721
|
-
const r = stdout.rows || 24;
|
|
1722
|
-
if (c > 0 && r > 0) setSize([c, r]);
|
|
1723
|
-
};
|
|
1724
|
-
stream.on("resize", onResize);
|
|
1725
|
-
return () => {
|
|
1726
|
-
stream.off?.("resize", onResize);
|
|
1727
|
-
};
|
|
1728
|
-
}, [stdout]);
|
|
1729
|
-
return size;
|
|
1730
|
-
}
|
|
1731
|
-
//#endregion
|
|
1732
|
-
//#region src/ui/tui/hooks/keyboard-hints-utils.ts
|
|
1733
|
-
/** Default priorities by key type. */
|
|
1734
|
-
const DEFAULT_PRIORITY = {
|
|
1735
|
-
["upArrow"]: 0,
|
|
1736
|
-
["downArrow"]: 0,
|
|
1737
|
-
["leftArrow"]: 1,
|
|
1738
|
-
["rightArrow"]: 1,
|
|
1739
|
-
["space"]: 10,
|
|
1740
|
-
["printable"]: 12,
|
|
1741
|
-
["backspace"]: 13,
|
|
1742
|
-
["escape"]: 20,
|
|
1743
|
-
["return"]: 21
|
|
1744
|
-
};
|
|
1745
|
-
/** Get the default display priority for a key match. */
|
|
1746
|
-
function getDefaultPriority(match) {
|
|
1747
|
-
return DEFAULT_PRIORITY[Array.isArray(match) ? match[0] : match] ?? 15;
|
|
1748
|
-
}
|
|
1749
|
-
/** Check if a KeyMatchOrChar matches the given input string and key flags. */
|
|
1750
|
-
function matchesKey(m, input, key) {
|
|
1751
|
-
switch (m) {
|
|
1752
|
-
case "upArrow": return !!key.upArrow;
|
|
1753
|
-
case "downArrow": return !!key.downArrow;
|
|
1754
|
-
case "leftArrow": return !!key.leftArrow;
|
|
1755
|
-
case "rightArrow": return !!key.rightArrow;
|
|
1756
|
-
case "return": return !!key.return;
|
|
1757
|
-
case "escape": return !!key.escape;
|
|
1758
|
-
case "space": return input === " ";
|
|
1759
|
-
case "backspace": return !!key.backspace || !!key.delete;
|
|
1760
|
-
case "printable": return input.length === 1 && input >= " " && input !== "" && Object.entries(key).every(([flag, on]) => !on || flag === "shift");
|
|
1761
|
-
default: return input === m;
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
/** Serialize hints for comparison. */
|
|
1765
|
-
function hintsKey(hints) {
|
|
1766
|
-
return hints.map((h) => `${h.label}:${h.action}`).join("|");
|
|
1767
|
-
}
|
|
1768
|
-
/** Deduplicate hints by label+action and sort by priority. */
|
|
1769
|
-
function deduplicateAndSort(hints) {
|
|
1770
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1771
|
-
const deduped = [];
|
|
1772
|
-
for (const hint of hints) {
|
|
1773
|
-
const k = `${hint.label}:${hint.action}`;
|
|
1774
|
-
if (!seen.has(k)) {
|
|
1775
|
-
seen.add(k);
|
|
1776
|
-
deduped.push(hint);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
deduped.sort((a, b) => a.priority - b.priority);
|
|
1780
|
-
return deduped;
|
|
1781
|
-
}
|
|
1782
|
-
//#endregion
|
|
1783
|
-
//#region src/ui/tui/hooks/useKeyboardHints.tsx
|
|
1784
|
-
/**
|
|
1785
|
-
* KeyboardHintsProvider — Context for collecting and displaying keyboard hints.
|
|
1786
|
-
*
|
|
1787
|
-
* Input components register their hints via useKeyBindings. The provider
|
|
1788
|
-
* flattens, deduplicates, and sorts them. The hints bar stays visible for as
|
|
1789
|
-
* long as a screen has registered hints — it never auto-dismisses.
|
|
1790
|
-
*/
|
|
1791
|
-
const KeyboardHintsContext = createContext({
|
|
1792
|
-
register: () => void 0,
|
|
1793
|
-
unregister: () => void 0,
|
|
1794
|
-
hints: []
|
|
1795
|
-
});
|
|
1796
|
-
const useKeyboardHintsContext = () => useContext(KeyboardHintsContext);
|
|
1797
|
-
const KeyboardHintsProvider = ({ children }) => {
|
|
1798
|
-
const registrationsRef = useRef(/* @__PURE__ */ new Map());
|
|
1799
|
-
const [hints, setHints] = useState([]);
|
|
1800
|
-
const prevHintsKeyRef = useRef("");
|
|
1801
|
-
const recompute = useCallback(() => {
|
|
1802
|
-
const all = [];
|
|
1803
|
-
for (const h of registrationsRef.current.values()) all.push(...h);
|
|
1804
|
-
const deduped = deduplicateAndSort(all);
|
|
1805
|
-
const newKey = hintsKey(deduped);
|
|
1806
|
-
if (newKey !== prevHintsKeyRef.current) {
|
|
1807
|
-
prevHintsKeyRef.current = newKey;
|
|
1808
|
-
setHints(deduped);
|
|
1809
|
-
}
|
|
1810
|
-
}, []);
|
|
1811
|
-
const register = useCallback((id, h) => {
|
|
1812
|
-
registrationsRef.current.set(id, h);
|
|
1813
|
-
recompute();
|
|
1814
|
-
}, [recompute]);
|
|
1815
|
-
const unregister = useCallback((id) => {
|
|
1816
|
-
registrationsRef.current.delete(id);
|
|
1817
|
-
recompute();
|
|
1818
|
-
}, [recompute]);
|
|
1819
|
-
return /* @__PURE__ */ jsx(KeyboardHintsContext.Provider, {
|
|
1820
|
-
value: {
|
|
1821
|
-
register,
|
|
1822
|
-
unregister,
|
|
1823
|
-
hints
|
|
1824
|
-
},
|
|
1825
|
-
children
|
|
1826
|
-
});
|
|
1827
|
-
};
|
|
1828
|
-
//#endregion
|
|
1829
|
-
//#region src/ui/tui/hooks/useKeyBindings.ts
|
|
1830
|
-
/**
|
|
1831
|
-
* useKeyBindings — Declarative keyboard input + automatic hint registration.
|
|
1832
|
-
*
|
|
1833
|
-
* Replaces raw `useInput` in input components. Define bindings as data;
|
|
1834
|
-
* the hook wires up the Ink input handler AND registers hints in the
|
|
1835
|
-
* KeyboardHintsProvider. One source of truth for keys and their labels.
|
|
1836
|
-
*/
|
|
1837
|
-
/**
|
|
1838
|
-
* Declarative key bindings hook. Replaces `useInput` in input components.
|
|
1839
|
-
* Registers hints automatically with the KeyboardHintsProvider.
|
|
1840
|
-
*
|
|
1841
|
-
* @param id Unique identifier for this component's hints registration
|
|
1842
|
-
* @param bindings Array of key binding definitions
|
|
1843
|
-
*/
|
|
1844
|
-
function useKeyBindings(id, bindings) {
|
|
1845
|
-
const ctx = useKeyboardHintsContext();
|
|
1846
|
-
const hintsRef = useRef("");
|
|
1847
|
-
const hints = bindings.map((b) => ({
|
|
1848
|
-
label: b.label,
|
|
1849
|
-
action: b.action,
|
|
1850
|
-
priority: b.priority ?? getDefaultPriority(b.match)
|
|
1851
|
-
}));
|
|
1852
|
-
const serialized = hints.map((h) => `${h.label}:${h.action}:${h.priority}`).join("|");
|
|
1853
|
-
useEffect(() => {
|
|
1854
|
-
if (serialized !== hintsRef.current) {
|
|
1855
|
-
hintsRef.current = serialized;
|
|
1856
|
-
ctx.register(id, hints);
|
|
1857
|
-
}
|
|
1858
|
-
return () => ctx.unregister(id);
|
|
1859
|
-
}, [id, serialized]);
|
|
1860
|
-
useInput((input, key) => {
|
|
1861
|
-
for (const binding of bindings) if ((Array.isArray(binding.match) ? binding.match : [binding.match]).some((m) => matchesKey(m, input, key))) {
|
|
1862
|
-
binding.handler(input, key);
|
|
1863
|
-
return;
|
|
1864
|
-
}
|
|
1865
|
-
});
|
|
1866
|
-
}
|
|
1867
|
-
//#endregion
|
|
1868
|
-
//#region src/ui/tui/primitives/PickerMenu.tsx
|
|
1869
|
-
/**
|
|
1870
|
-
* PickerMenu — Single and multi select.
|
|
1871
|
-
* Single mode: custom renderer with small triangle indicator; enter selects.
|
|
1872
|
-
* Multi mode: checkbox glyphs toggled with enter, plus a focusable
|
|
1873
|
-
* Confirm button below the options. The cursor moves onto the button and
|
|
1874
|
-
* enter submits — see MultiPickerMenu for the rationale.
|
|
1875
|
-
*
|
|
1876
|
-
* Key bindings are declared via useKeyBindings, which auto-registers
|
|
1877
|
-
* hints in the KeyboardHintsBar.
|
|
1878
|
-
*/
|
|
1879
|
-
/**
|
|
1880
|
-
* Step through a column's options in `dir`, wrapping, until an enabled
|
|
1881
|
-
* option is found. Returns `from` unchanged if the column is entirely
|
|
1882
|
-
* disabled.
|
|
1883
|
-
*/
|
|
1884
|
-
function stepEnabled(options, rows, from, dir) {
|
|
1885
|
-
const colStart = Math.floor(from / rows) * rows;
|
|
1886
|
-
const colLen = Math.min(rows, options.length - colStart);
|
|
1887
|
-
let row = from % rows;
|
|
1888
|
-
for (let i = 0; i < colLen; i++) {
|
|
1889
|
-
row = (row + dir + colLen) % colLen;
|
|
1890
|
-
const idx = colStart + row;
|
|
1891
|
-
if (!options[idx]?.disabled) return idx;
|
|
1892
|
-
}
|
|
1893
|
-
return from;
|
|
1894
|
-
}
|
|
1895
|
-
/** First enabled option in [start, end), for initial focus and for entering
|
|
1896
|
-
* a page from the Confirm button. */
|
|
1897
|
-
function firstEnabled(options, start = 0, end = options.length) {
|
|
1898
|
-
for (let i = start; i < end; i++) if (!options[i]?.disabled) return i;
|
|
1899
|
-
return start;
|
|
1900
|
-
}
|
|
1901
|
-
/** Last enabled option in [start, end), for wrapping from the button onto
|
|
1902
|
-
* the bottom of the page. */
|
|
1903
|
-
function lastEnabled(options, start = 0, end = options.length) {
|
|
1904
|
-
for (let i = end - 1; i >= start; i--) if (!options[i]?.disabled) return i;
|
|
1905
|
-
return end - 1;
|
|
1906
|
-
}
|
|
1907
|
-
/**
|
|
1908
|
-
* Rows the surrounding screen or overlay consumes above and below a picker —
|
|
1909
|
-
* border + padding, title, prompt text, and the keyboard-hints bar. A
|
|
1910
|
-
* deliberately generous estimate: overshooting just shows a few fewer rows,
|
|
1911
|
-
* whereas undershooting lets a long list overflow the viewport (the bug this
|
|
1912
|
-
* windowing guards against). Mirrors GroupedPickerMenu's budgeting.
|
|
1913
|
-
*/
|
|
1914
|
-
const CHROME_OVERHEAD = 13;
|
|
1915
|
-
/**
|
|
1916
|
-
* Max visual rows a picker renders regardless of terminal height. Without a
|
|
1917
|
-
* ceiling a tall terminal lets a long list fill the whole viewport, which
|
|
1918
|
-
* reads as a wall of options; ~12 rows keeps the menu scannable and leaves
|
|
1919
|
-
* known breathing room above and below.
|
|
1920
|
-
*/
|
|
1921
|
-
const MAX_LIST_ROWS = 12;
|
|
1922
|
-
/** Extra rows a multi-select adds below its options: marginTop + Confirm button. */
|
|
1923
|
-
const CONFIRM_CHROME = 3;
|
|
1924
|
-
/** Lists shorter than this never page — tall rows would split 2-3 options into pages of one. */
|
|
1925
|
-
const MIN_COUNT_TO_PAGE = 5;
|
|
1926
|
-
/** Width the multi-select wraps option descriptions to (matches the render). */
|
|
1927
|
-
const DESCRIPTION_WIDTH = 56;
|
|
1928
|
-
/**
|
|
1929
|
-
* Pages a single-column option list to the terminal height. The visible page
|
|
1930
|
-
* is derived from the focused index — no scroll state — so ↑/↓ flip pages as
|
|
1931
|
-
* focus crosses a page edge and n/p jump a whole page. Pages hold a fixed
|
|
1932
|
-
* option count sized to the tallest row (`rowCost`), trading a sparser page
|
|
1933
|
-
* on mixed-height lists for arithmetic-only paging. Engages only for
|
|
1934
|
-
* single-column pickers — multi-column grids already compress vertically.
|
|
1935
|
-
*/
|
|
1936
|
-
function usePickerViewport(count, rowCost, chromeBelow, enabled, focused) {
|
|
1937
|
-
const [, termRows] = useStdoutDimensions();
|
|
1938
|
-
const budget = Math.max(5, Math.min(termRows - CHROME_OVERHEAD - chromeBelow, MAX_LIST_ROWS));
|
|
1939
|
-
const needsScroll = enabled && count >= MIN_COUNT_TO_PAGE && count * rowCost > budget;
|
|
1940
|
-
const perPage = needsScroll ? Math.max(1, Math.floor((budget - 2) / rowCost)) : count;
|
|
1941
|
-
const pageCount = Math.max(1, Math.ceil(count / perPage));
|
|
1942
|
-
const start = Math.floor(focused / perPage) * perPage;
|
|
1943
|
-
const end = Math.min(start + perPage, count);
|
|
1944
|
-
return {
|
|
1945
|
-
needsScroll,
|
|
1946
|
-
start,
|
|
1947
|
-
end,
|
|
1948
|
-
hiddenAbove: start,
|
|
1949
|
-
hiddenBelow: count - end,
|
|
1950
|
-
pageStep: (f, dir) => (Math.floor(f / perPage) + dir + pageCount) % pageCount * perPage
|
|
1951
|
-
};
|
|
1952
|
-
}
|
|
1953
|
-
/** Lists at least this long get filtering. Shorter ones fit on a page or two,
|
|
1954
|
-
* where a filter row costs more rows than it saves. */
|
|
1955
|
-
const FILTER_MIN_OPTIONS = 10;
|
|
1956
|
-
const PickerMenu = ({ message, options, mode = "single", centered = false, columns = 1, optionMarginBottom = 0, filterable, onSelect }) => {
|
|
1957
|
-
const canFilter = filterable ?? options.length >= FILTER_MIN_OPTIONS;
|
|
1958
|
-
const [filter, setFilter] = useState("");
|
|
1959
|
-
const fuse = useMemo(() => canFilter ? buildPickerIndex(options) : null, [options, canFilter]);
|
|
1960
|
-
const visible = useMemo(() => fuse ? rankOptions(fuse, options, filter) ?? options : options, [
|
|
1961
|
-
fuse,
|
|
1962
|
-
options,
|
|
1963
|
-
filter
|
|
1964
|
-
]);
|
|
1965
|
-
const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
|
|
1966
|
-
const shared = {
|
|
1967
|
-
message,
|
|
1968
|
-
options: visible,
|
|
1969
|
-
centered,
|
|
1970
|
-
optionMarginBottom,
|
|
1971
|
-
onSelect,
|
|
1972
|
-
filter: canFilter ? filter : null,
|
|
1973
|
-
setFilter,
|
|
1974
|
-
totalCount: options.length
|
|
1975
|
-
};
|
|
1976
|
-
const key = String(filter);
|
|
1977
|
-
if (mode === "multi") return /* @__PURE__ */ jsx(MultiPickerMenu, {
|
|
1978
|
-
...shared,
|
|
1979
|
-
columns,
|
|
1980
|
-
allOptions: options,
|
|
1981
|
-
selected,
|
|
1982
|
-
setSelected
|
|
1983
|
-
}, key);
|
|
1984
|
-
return /* @__PURE__ */ jsx(SinglePickerMenu, {
|
|
1985
|
-
...shared,
|
|
1986
|
-
columns
|
|
1987
|
-
}, key);
|
|
1988
|
-
};
|
|
1989
|
-
/**
|
|
1990
|
-
* Backspace and free typing. Append these last: `Printable` matches almost any
|
|
1991
|
-
* key, and bindings resolve in order, so anything specific has to be registered
|
|
1992
|
-
* first to keep working. Both share a label/action so the hints bar shows one.
|
|
1993
|
-
*/
|
|
1994
|
-
function filterBindings(filter, setFilter) {
|
|
1995
|
-
return [{
|
|
1996
|
-
match: "backspace",
|
|
1997
|
-
label: "type",
|
|
1998
|
-
action: "filter",
|
|
1999
|
-
handler: () => setFilter(filter.slice(0, -1))
|
|
2000
|
-
}, {
|
|
2001
|
-
match: "printable",
|
|
2002
|
-
label: "type",
|
|
2003
|
-
action: "filter",
|
|
2004
|
-
handler: (input) => setFilter(filter + input)
|
|
2005
|
-
}];
|
|
2006
|
-
}
|
|
2007
|
-
/** The filter row above a filterable list: what's typed, and how much it left. */
|
|
2008
|
-
const FilterRow = ({ filter, shown, total }) => /* @__PURE__ */ jsx(Box, {
|
|
2009
|
-
paddingLeft: 1,
|
|
2010
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
2011
|
-
dimColor: true,
|
|
2012
|
-
children: filter ? `Filter: ${filter} (${shown} of ${total})` : `Type to filter ${total} options`
|
|
2013
|
-
})
|
|
2014
|
-
});
|
|
2015
|
-
/** Custom single-select with triangle indicator and accent highlight. */
|
|
2016
|
-
const SinglePickerMenu = ({ message, options, centered = false, columns = 1, optionMarginBottom = 0, filter, setFilter, totalCount, onSelect }) => {
|
|
2017
|
-
const [focused, setFocused] = useState(() => firstEnabled(options));
|
|
2018
|
-
const rows = Math.ceil(options.length / columns);
|
|
2019
|
-
const viewport = usePickerViewport(options.length, 1 + optionMarginBottom, filter !== null ? 1 : 0, columns === 1, focused);
|
|
2020
|
-
useEffect(() => {
|
|
2021
|
-
if (focused >= options.length || options[focused]?.disabled) setFocused(firstEnabled(options));
|
|
2022
|
-
}, [options, focused]);
|
|
2023
|
-
const bindings = [
|
|
2024
|
-
{
|
|
2025
|
-
match: ["upArrow", "downArrow"],
|
|
2026
|
-
label: "↑↓",
|
|
2027
|
-
action: "navigate",
|
|
2028
|
-
handler: (_input, key) => {
|
|
2029
|
-
setFocused(stepEnabled(options, rows, focused, key.upArrow ? -1 : 1));
|
|
2030
|
-
}
|
|
2031
|
-
},
|
|
2032
|
-
...viewport.needsScroll && filter === null ? [{
|
|
2033
|
-
match: ["n", "p"],
|
|
2034
|
-
label: "n/p",
|
|
2035
|
-
action: "page",
|
|
2036
|
-
handler: (input) => {
|
|
2037
|
-
const target = viewport.pageStep(focused, input === "n" ? 1 : -1);
|
|
2038
|
-
setFocused(options[target]?.disabled ? stepEnabled(options, rows, target, 1) : target);
|
|
2039
|
-
}
|
|
2040
|
-
}] : [],
|
|
2041
|
-
{
|
|
2042
|
-
match: "return",
|
|
2043
|
-
label: "enter",
|
|
2044
|
-
action: "select",
|
|
2045
|
-
handler: () => {
|
|
2046
|
-
const selected = options[focused];
|
|
2047
|
-
if (selected && !selected.disabled) onSelect(selected.value);
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
];
|
|
2051
|
-
if (columns > 1) bindings.splice(1, 0, {
|
|
2052
|
-
match: ["leftArrow", "rightArrow"],
|
|
2053
|
-
label: "←→",
|
|
2054
|
-
action: "navigate",
|
|
2055
|
-
handler: (_input, key) => {
|
|
2056
|
-
const col = Math.floor(focused / rows);
|
|
2057
|
-
const row = focused % rows;
|
|
2058
|
-
let next = focused;
|
|
2059
|
-
if (key.leftArrow) {
|
|
2060
|
-
const prevCol = col > 0 ? col - 1 : columns - 1;
|
|
2061
|
-
next = Math.min(prevCol * rows + row, options.length - 1);
|
|
2062
|
-
}
|
|
2063
|
-
if (key.rightArrow) {
|
|
2064
|
-
const nextCol = col < columns - 1 ? col + 1 : 0;
|
|
2065
|
-
next = Math.min(nextCol * rows + row, options.length - 1);
|
|
2066
|
-
}
|
|
2067
|
-
if (options[next]?.disabled) next = stepEnabled(options, rows, next, 1);
|
|
2068
|
-
setFocused(next);
|
|
2069
|
-
}
|
|
2070
|
-
});
|
|
2071
|
-
if (filter !== null) bindings.push(...filterBindings(filter, setFilter));
|
|
2072
|
-
useKeyBindings("single-picker", bindings);
|
|
2073
|
-
const columnArrays = [];
|
|
2074
|
-
for (let c = 0; c < columns; c++) columnArrays.push(options.slice(c * rows, c * rows + rows));
|
|
2075
|
-
const align = centered ? "center" : void 0;
|
|
2076
|
-
const renderOption = (opt, flatIdx) => {
|
|
2077
|
-
const isFocused = flatIdx === focused;
|
|
2078
|
-
const base = opt.hint ? `${opt.label} (${opt.hint})` : opt.label;
|
|
2079
|
-
const label = opt.indent ? ` ${base}` : base;
|
|
2080
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
2081
|
-
gap: 1,
|
|
2082
|
-
marginBottom: optionMarginBottom,
|
|
2083
|
-
children: [
|
|
2084
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2085
|
-
color: isFocused ? Colors.accent : void 0,
|
|
2086
|
-
dimColor: !isFocused,
|
|
2087
|
-
children: isFocused && !opt.header ? Icons.triangleSmallRight : " "
|
|
2088
|
-
}),
|
|
2089
|
-
opt.icon && /* @__PURE__ */ jsx(Text, {
|
|
2090
|
-
color: opt.icon.color,
|
|
2091
|
-
children: opt.icon.glyph
|
|
2092
|
-
}),
|
|
2093
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2094
|
-
color: opt.header ? void 0 : opt.disabled ? Colors.muted : isFocused ? Colors.accent : void 0,
|
|
2095
|
-
bold: opt.header || isFocused && !opt.disabled,
|
|
2096
|
-
dimColor: !opt.header && (!isFocused || opt.disabled),
|
|
2097
|
-
children: label
|
|
2098
|
-
})
|
|
2099
|
-
]
|
|
2100
|
-
}, flatIdx);
|
|
2101
|
-
};
|
|
2102
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
2103
|
-
flexDirection: "column",
|
|
2104
|
-
alignItems: align,
|
|
2105
|
-
children: [
|
|
2106
|
-
/* @__PURE__ */ jsx(PromptLabel, { message }),
|
|
2107
|
-
filter !== null && /* @__PURE__ */ jsx(FilterRow, {
|
|
2108
|
-
filter,
|
|
2109
|
-
shown: options.length,
|
|
2110
|
-
total: totalCount
|
|
2111
|
-
}),
|
|
2112
|
-
filter && options.length === 0 ? /* @__PURE__ */ jsx(Text, {
|
|
2113
|
-
dimColor: true,
|
|
2114
|
-
children: "No options match. Backspace to widen the filter."
|
|
2115
|
-
}) : viewport.needsScroll ? /* @__PURE__ */ jsxs(Box, {
|
|
2116
|
-
flexDirection: "column",
|
|
2117
|
-
children: [
|
|
2118
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2119
|
-
dimColor: true,
|
|
2120
|
-
children: viewport.hiddenAbove > 0 ? `↑ ${viewport.hiddenAbove} more${filter === null ? " [P] for previous page" : ""}` : " "
|
|
2121
|
-
}),
|
|
2122
|
-
options.slice(viewport.start, viewport.end).map((opt, relIdx) => renderOption(opt, viewport.start + relIdx)),
|
|
2123
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2124
|
-
dimColor: true,
|
|
2125
|
-
children: viewport.hiddenBelow > 0 ? `↓ ${viewport.hiddenBelow} more${filter === null ? " [N] for next page" : ""}` : " "
|
|
2126
|
-
})
|
|
2127
|
-
]
|
|
2128
|
-
}) : /* @__PURE__ */ jsx(Box, {
|
|
2129
|
-
flexDirection: "row",
|
|
2130
|
-
gap: 4,
|
|
2131
|
-
children: columnArrays.map((colOpts, colIdx) => /* @__PURE__ */ jsx(Box, {
|
|
2132
|
-
flexDirection: "column",
|
|
2133
|
-
children: colOpts.map((opt, rowIdx) => renderOption(opt, colIdx * rows + rowIdx))
|
|
2134
|
-
}, colIdx))
|
|
2135
|
-
})
|
|
2136
|
-
]
|
|
2137
|
-
});
|
|
2138
|
-
};
|
|
2139
|
-
/**
|
|
2140
|
-
* Custom multi-select with checkbox glyphs and accent highlight.
|
|
2141
|
-
*
|
|
2142
|
-
* Interaction model (shared with GroupedPickerMenu):
|
|
2143
|
-
* - \u2191\u2193 move the cursor through the options AND onto the Confirm button,
|
|
2144
|
-
* which lives just past the last option.
|
|
2145
|
-
* - enter toggles the focused option (no more "space toggles but enter
|
|
2146
|
-
* advances" split that tripped people up). Space is kept as an
|
|
2147
|
-
* undocumented alias, but the hints bar advertises only enter.
|
|
2148
|
-
* - moving onto the Confirm button and pressing enter submits the
|
|
2149
|
-
* current selection.
|
|
2150
|
-
*/
|
|
2151
|
-
const MultiPickerMenu = ({ message, options, centered = false, columns = 1, optionMarginBottom = 0, filter, setFilter, totalCount, allOptions, selected, setSelected, onSelect }) => {
|
|
2152
|
-
const [focused, setFocused] = useState(() => firstEnabled(options));
|
|
2153
|
-
const [onButton, setOnButton] = useState(false);
|
|
2154
|
-
const rows = Math.ceil(options.length / columns);
|
|
2155
|
-
const rowCost = options.reduce((max, opt) => Math.max(max, 1 + optionMarginBottom + (opt.description ? wordWrap(opt.description, DESCRIPTION_WIDTH).length : 0)), 1);
|
|
2156
|
-
const viewport = usePickerViewport(options.length, rowCost, CONFIRM_CHROME + (filter !== null ? 1 : 0), columns === 1, focused);
|
|
2157
|
-
useEffect(() => {
|
|
2158
|
-
if (focused >= options.length || options[focused]?.disabled) setFocused(firstEnabled(options));
|
|
2159
|
-
}, [options, focused]);
|
|
2160
|
-
const confirm = () => {
|
|
2161
|
-
onSelect(allOptions.filter((option) => selected.has(option.label)).map((option) => option.value));
|
|
2162
|
-
};
|
|
2163
|
-
const bindings = [
|
|
2164
|
-
{
|
|
2165
|
-
match: ["upArrow", "downArrow"],
|
|
2166
|
-
label: "↑↓",
|
|
2167
|
-
action: "navigate",
|
|
2168
|
-
handler: (_input, key) => {
|
|
2169
|
-
const dir = key.upArrow ? -1 : 1;
|
|
2170
|
-
if (onButton) {
|
|
2171
|
-
setOnButton(false);
|
|
2172
|
-
setFocused(dir === -1 ? lastEnabled(options, viewport.start, viewport.end) : firstEnabled(options, viewport.start, viewport.end));
|
|
2173
|
-
return;
|
|
2174
|
-
}
|
|
2175
|
-
const col = Math.floor(focused / rows);
|
|
2176
|
-
const row = focused % rows;
|
|
2177
|
-
const colLen = Math.min(rows, options.length - col * rows);
|
|
2178
|
-
let r = row + dir;
|
|
2179
|
-
while (r >= 0 && r < colLen && options[col * rows + r]?.disabled) r += dir;
|
|
2180
|
-
if (r >= 0 && r < colLen) setFocused(col * rows + r);
|
|
2181
|
-
else setOnButton(true);
|
|
2182
|
-
}
|
|
2183
|
-
},
|
|
2184
|
-
...viewport.needsScroll && filter === null ? [{
|
|
2185
|
-
match: ["n", "p"],
|
|
2186
|
-
label: "n/p",
|
|
2187
|
-
action: "page",
|
|
2188
|
-
handler: (input) => {
|
|
2189
|
-
const target = viewport.pageStep(focused, input === "n" ? 1 : -1);
|
|
2190
|
-
setOnButton(false);
|
|
2191
|
-
setFocused(options[target]?.disabled ? stepEnabled(options, rows, target, 1) : target);
|
|
2192
|
-
}
|
|
2193
|
-
}] : [],
|
|
2194
|
-
{
|
|
2195
|
-
match: filter !== null ? "return" : ["space", "return"],
|
|
2196
|
-
label: "enter",
|
|
2197
|
-
action: "select",
|
|
2198
|
-
handler: () => {
|
|
2199
|
-
if (onButton) {
|
|
2200
|
-
confirm();
|
|
2201
|
-
return;
|
|
2202
|
-
}
|
|
2203
|
-
const option = options[focused];
|
|
2204
|
-
if (!option || option.disabled) return;
|
|
2205
|
-
const label = option.label;
|
|
2206
|
-
setSelected((prev) => {
|
|
2207
|
-
const next = new Set(prev);
|
|
2208
|
-
if (next.has(label)) {
|
|
2209
|
-
next.delete(label);
|
|
2210
|
-
return next;
|
|
2211
|
-
}
|
|
2212
|
-
if (option.exclusive) return new Set([label]);
|
|
2213
|
-
for (const picked of next) if (allOptions.find((o) => o.label === picked)?.exclusive) next.delete(picked);
|
|
2214
|
-
next.add(label);
|
|
2215
|
-
return next;
|
|
2216
|
-
});
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
];
|
|
2220
|
-
if (columns > 1) bindings.splice(1, 0, {
|
|
2221
|
-
match: ["leftArrow", "rightArrow"],
|
|
2222
|
-
label: "←→",
|
|
2223
|
-
action: "navigate",
|
|
2224
|
-
handler: (_input, key) => {
|
|
2225
|
-
if (onButton) return;
|
|
2226
|
-
const col = Math.floor(focused / rows);
|
|
2227
|
-
const row = focused % rows;
|
|
2228
|
-
let next = focused;
|
|
2229
|
-
if (key.leftArrow) {
|
|
2230
|
-
const prevCol = col > 0 ? col - 1 : columns - 1;
|
|
2231
|
-
next = Math.min(prevCol * rows + row, options.length - 1);
|
|
2232
|
-
}
|
|
2233
|
-
if (key.rightArrow) {
|
|
2234
|
-
const nextCol = col < columns - 1 ? col + 1 : 0;
|
|
2235
|
-
next = Math.min(nextCol * rows + row, options.length - 1);
|
|
2236
|
-
}
|
|
2237
|
-
if (options[next]?.disabled) next = stepEnabled(options, rows, next, 1);
|
|
2238
|
-
setFocused(next);
|
|
2239
|
-
}
|
|
2240
|
-
});
|
|
2241
|
-
if (filter !== null) bindings.push(...filterBindings(filter, setFilter));
|
|
2242
|
-
useKeyBindings("multi-picker", bindings);
|
|
2243
|
-
const columnArrays = [];
|
|
2244
|
-
for (let c = 0; c < columns; c++) columnArrays.push(options.slice(c * rows, c * rows + rows));
|
|
2245
|
-
const renderOption = (opt, flatIdx) => {
|
|
2246
|
-
const isFocused = !onButton && flatIdx === focused;
|
|
2247
|
-
const isSelected = selected.has(opt.label);
|
|
2248
|
-
const label = opt.hint ? `${opt.label} (${opt.hint})` : opt.label;
|
|
2249
|
-
const checkbox = isSelected ? Icons.squareFilled : Icons.squareOpen;
|
|
2250
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
2251
|
-
flexDirection: "column",
|
|
2252
|
-
marginBottom: optionMarginBottom,
|
|
2253
|
-
children: [/* @__PURE__ */ jsxs(Box, {
|
|
2254
|
-
gap: 1,
|
|
2255
|
-
children: [
|
|
2256
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2257
|
-
color: isSelected ? "white" : Colors.muted,
|
|
2258
|
-
dimColor: !isFocused && !isSelected,
|
|
2259
|
-
children: checkbox
|
|
2260
|
-
}),
|
|
2261
|
-
opt.icon && /* @__PURE__ */ jsx(Text, {
|
|
2262
|
-
color: opt.icon.color,
|
|
2263
|
-
children: opt.icon.glyph
|
|
2264
|
-
}),
|
|
2265
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2266
|
-
color: opt.disabled ? Colors.muted : isFocused ? Colors.accent : void 0,
|
|
2267
|
-
bold: isFocused && !opt.disabled,
|
|
2268
|
-
dimColor: !isFocused || opt.disabled,
|
|
2269
|
-
children: label
|
|
2270
|
-
})
|
|
2271
|
-
]
|
|
2272
|
-
}), opt.description && /* @__PURE__ */ jsx(Box, {
|
|
2273
|
-
marginLeft: 4,
|
|
2274
|
-
width: DESCRIPTION_WIDTH,
|
|
2275
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
2276
|
-
dimColor: true,
|
|
2277
|
-
wrap: "wrap",
|
|
2278
|
-
children: opt.description
|
|
2279
|
-
})
|
|
2280
|
-
})]
|
|
2281
|
-
}, flatIdx);
|
|
2282
|
-
};
|
|
2283
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
2284
|
-
flexDirection: "column",
|
|
2285
|
-
alignItems: centered ? "center" : void 0,
|
|
2286
|
-
children: [
|
|
2287
|
-
/* @__PURE__ */ jsx(PromptLabel, { message }),
|
|
2288
|
-
filter !== null && /* @__PURE__ */ jsx(FilterRow, {
|
|
2289
|
-
filter,
|
|
2290
|
-
shown: options.length,
|
|
2291
|
-
total: totalCount
|
|
2292
|
-
}),
|
|
2293
|
-
filter && options.length === 0 && /* @__PURE__ */ jsx(Text, {
|
|
2294
|
-
dimColor: true,
|
|
2295
|
-
children: "No options match. Backspace to widen the filter."
|
|
2296
|
-
}),
|
|
2297
|
-
viewport.needsScroll ? /* @__PURE__ */ jsxs(Box, {
|
|
2298
|
-
flexDirection: "column",
|
|
2299
|
-
marginLeft: centered ? 0 : 2,
|
|
2300
|
-
marginTop: message ? 1 : 0,
|
|
2301
|
-
children: [
|
|
2302
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2303
|
-
dimColor: true,
|
|
2304
|
-
children: viewport.hiddenAbove > 0 ? `↑ ${viewport.hiddenAbove} more${filter === null ? " [P] for previous page" : ""}` : " "
|
|
2305
|
-
}),
|
|
2306
|
-
options.slice(viewport.start, viewport.end).map((opt, relIdx) => renderOption(opt, viewport.start + relIdx)),
|
|
2307
|
-
/* @__PURE__ */ jsx(Text, {
|
|
2308
|
-
dimColor: true,
|
|
2309
|
-
children: viewport.hiddenBelow > 0 ? `↓ ${viewport.hiddenBelow} more${filter === null ? " [N] for next page" : ""}` : " "
|
|
2310
|
-
})
|
|
2311
|
-
]
|
|
2312
|
-
}) : /* @__PURE__ */ jsx(Box, {
|
|
2313
|
-
flexDirection: "row",
|
|
2314
|
-
gap: 4,
|
|
2315
|
-
marginLeft: centered ? 0 : 2,
|
|
2316
|
-
marginTop: message ? 1 : 0,
|
|
2317
|
-
children: columnArrays.map((colOpts, colIdx) => /* @__PURE__ */ jsx(Box, {
|
|
2318
|
-
flexDirection: "column",
|
|
2319
|
-
children: colOpts.map((opt, rowIdx) => renderOption(opt, colIdx * rows + rowIdx))
|
|
2320
|
-
}, colIdx))
|
|
2321
|
-
}),
|
|
2322
|
-
/* @__PURE__ */ jsx(Box, {
|
|
2323
|
-
marginTop: 1,
|
|
2324
|
-
marginLeft: centered ? 0 : 2,
|
|
2325
|
-
children: /* @__PURE__ */ jsx(ConfirmButton, {
|
|
2326
|
-
focused: onButton,
|
|
2327
|
-
count: selected.size
|
|
2328
|
-
})
|
|
2329
|
-
})
|
|
2330
|
-
]
|
|
2331
|
-
});
|
|
2332
|
-
};
|
|
2333
|
-
//#endregion
|
|
2334
|
-
//#region src/commands/factories/family-picker.tsx
|
|
2335
|
-
function FamilyPickerApp(props) {
|
|
2336
|
-
return createElement(Box, {
|
|
2337
|
-
flexDirection: "column",
|
|
2338
|
-
paddingX: 1,
|
|
2339
|
-
paddingY: 1
|
|
2340
|
-
}, createElement(Text, {
|
|
2341
|
-
bold: true,
|
|
2342
|
-
color: Colors.accent
|
|
2343
|
-
}, props.parentLabel), createElement(Box, { height: 1 }), createElement(PickerMenu, {
|
|
2344
|
-
message: "Pick a subcommand",
|
|
2345
|
-
options: props.options,
|
|
2346
|
-
optionMarginBottom: 1,
|
|
2347
|
-
onSelect: (value) => {
|
|
2348
|
-
const cmd = Array.isArray(value) ? value[0] : value;
|
|
2349
|
-
if (cmd) props.onSelect(cmd);
|
|
2350
|
-
}
|
|
2351
|
-
}));
|
|
2352
|
-
}
|
|
2353
|
-
function describe(child) {
|
|
2354
|
-
return commandKeys(child.name)[0] ?? "";
|
|
2355
|
-
}
|
|
2356
|
-
/**
|
|
2357
|
-
* Reorder children so the `default`-marked entry is first, while
|
|
2358
|
-
* preserving the relative order of the rest. The picker's initial
|
|
2359
|
-
* focus is index 0, so this is what makes "press Enter on
|
|
2360
|
-
* `wizard audit`" run the default leaf (today `audit events`).
|
|
2361
|
-
*
|
|
2362
|
-
* Exported for testability — the ordering logic stays pure and
|
|
2363
|
-
* inspectable without mounting Ink.
|
|
2364
|
-
*/
|
|
2365
|
-
function orderFamilyChildren(children) {
|
|
2366
|
-
const selectable = children.filter((c) => c.handler || c.children?.length);
|
|
2367
|
-
const defaults = selectable.filter((c) => c.default);
|
|
2368
|
-
const rest = selectable.filter((c) => !c.default);
|
|
2369
|
-
return [...defaults, ...rest];
|
|
2370
|
-
}
|
|
2371
|
-
/**
|
|
2372
|
-
* Render the picker. Resolves once the user has selected a child;
|
|
2373
|
-
* dispatching the child's handler is the caller's responsibility (so this
|
|
2374
|
-
* function stays pure-UI and easy to test by stubbing `render`).
|
|
2375
|
-
*/
|
|
2376
|
-
function chooseFamilyChild(parentLabel, children) {
|
|
2377
|
-
const ordered = orderFamilyChildren(children);
|
|
2378
|
-
if (ordered.length === 0) return Promise.resolve(null);
|
|
2379
|
-
const options = ordered.map((child) => ({
|
|
2380
|
-
label: describe(child),
|
|
2381
|
-
value: child,
|
|
2382
|
-
hint: child.description
|
|
2383
|
-
}));
|
|
2384
|
-
return new Promise((resolve) => {
|
|
2385
|
-
let app = null;
|
|
2386
|
-
const handleSelect = (cmd) => {
|
|
2387
|
-
app?.unmount();
|
|
2388
|
-
resolve(cmd);
|
|
2389
|
-
};
|
|
2390
|
-
app = render(createElement(FamilyPickerApp, {
|
|
2391
|
-
parentLabel,
|
|
2392
|
-
options,
|
|
2393
|
-
onSelect: handleSelect
|
|
2394
|
-
}));
|
|
2395
|
-
});
|
|
2396
|
-
}
|
|
2397
|
-
/**
|
|
2398
|
-
* Returns an `interactiveDefault` handler for a family parent's no-leaf
|
|
2399
|
-
* invocation. Always opens the picker; the `default`-marked child is
|
|
2400
|
-
* shown first (pre-highlighted), so a single Enter keystroke runs it.
|
|
2401
|
-
*
|
|
2402
|
-
* Discovery + consent in one extra keystroke vs. auto-running silently.
|
|
2403
|
-
*
|
|
2404
|
-
* Wire onto a family parent:
|
|
2405
|
-
* export const auditCommand: Command = {
|
|
2406
|
-
* name: 'audit',
|
|
2407
|
-
* description: '...',
|
|
2408
|
-
* children: [...],
|
|
2409
|
-
* interactiveDefault: createFamilyPickerDefault('audit', auditChildren),
|
|
2410
|
-
* };
|
|
2411
|
-
*/
|
|
2412
|
-
function createFamilyPickerDefault(parentLabel, children, chooser = chooseFamilyChild) {
|
|
2413
|
-
return async (argv) => {
|
|
2414
|
-
const chosen = await chooser(parentLabel, children);
|
|
2415
|
-
if (!chosen) return;
|
|
2416
|
-
await Promise.resolve(chosen.handler?.(argv));
|
|
2417
|
-
};
|
|
2418
|
-
}
|
|
2419
|
-
//#endregion
|
|
2420
|
-
//#region src/commands/factories/family-command-factory.ts
|
|
2421
|
-
/**
|
|
2422
|
-
* Build a yargs `Command` for a family parent (`wizard audit`, etc.).
|
|
2423
|
-
*
|
|
2424
|
-
* - `wizard <family> <sub>` — `dispatchFamily` resolves `<sub>` against
|
|
2425
|
-
* native handlers first, then the live `cliEntries` from
|
|
2426
|
-
* `skill-menu.json`. Unknown subs error with the available list.
|
|
2427
|
-
* - `wizard <family>` (no positional) — in an interactive terminal, runs the
|
|
2428
|
-
* family's single shown entry directly (today `audit events`, so the user
|
|
2429
|
-
* lands on its intro screen); opens the picker once a family shows more than
|
|
2430
|
-
* one. In non-TTY/CI, falls through to `dispatchFamily`, which prints
|
|
2431
|
-
* "requires a subcommand" rather than running something unprompted.
|
|
2432
|
-
*
|
|
2433
|
-
* No static yargs children. New skill-backed subcommands appear after a
|
|
2434
|
-
* context-mill release without a wizard release. New *native* subcommands
|
|
2435
|
-
* (rare) are added by updating `NATIVE_HANDLERS` in `dispatch-family.ts`.
|
|
2436
|
-
*/
|
|
2437
|
-
function familyCommandFactory({ family, description, optionsFrom }) {
|
|
2438
|
-
const openFamilyEntry = async (argv) => {
|
|
2439
|
-
const toShow = pickerChildrenToShow(buildFamilyPickerChildren(family, (await fetchSkillMenu(getSkillsBaseUrl()))?.cliEntries ?? []));
|
|
2440
|
-
if (toShow.length === 1 && toShow[0]?.handler) {
|
|
2441
|
-
await Promise.resolve(toShow[0].handler(argv));
|
|
2442
|
-
return;
|
|
2443
|
-
}
|
|
2444
|
-
await createFamilyPickerDefault(`wizard ${family}`, toShow)(argv);
|
|
2445
|
-
};
|
|
2446
|
-
return {
|
|
2447
|
-
name: `${family} [skill]`,
|
|
2448
|
-
description,
|
|
2449
|
-
options: mergeCommandOptions(optionsFrom),
|
|
2450
|
-
positionals: { skill: {
|
|
2451
|
-
type: "string",
|
|
2452
|
-
describe: "Subcommand to run (omit to run the default)"
|
|
2453
|
-
} },
|
|
2454
|
-
handler: (argv) => {
|
|
2455
|
-
const sub = argv.skill?.trim();
|
|
2456
|
-
runCommandHandler(() => sub || !process.stdout.isTTY ? dispatchFamily(family, argv) : openFamilyEntry(argv));
|
|
2457
|
-
},
|
|
2458
|
-
interactiveDefault: openFamilyEntry
|
|
2459
|
-
};
|
|
2460
|
-
}
|
|
2461
|
-
//#endregion
|
|
2462
|
-
//#region src/commands/audit.ts
|
|
2463
|
-
/**
|
|
2464
|
-
* The `wizard audit` family.
|
|
2465
|
-
*
|
|
2466
|
-
* Subcommands are resolved at runtime: the wizard fetches `cliEntries` from
|
|
2467
|
-
* `skill-menu.json` and dispatches based on `parentCommand: 'audit'`. The
|
|
2468
|
-
* wizard-native handler for `web-analytics` lives in `NATIVE_HANDLERS` over
|
|
2469
|
-
* in `dispatch-family.ts`. `wizard audit` with no positional opens the
|
|
2470
|
-
* family picker, which combines native + live entries.
|
|
2471
|
-
*
|
|
2472
|
-
* Adding a new skill-backed audit subcommand is a context-mill release —
|
|
2473
|
-
* no wizard release needed.
|
|
2474
|
-
*/
|
|
2475
|
-
const auditCommand = familyCommandFactory({
|
|
2476
|
-
family: "audit",
|
|
2477
|
-
description: auditConfig.description,
|
|
2478
|
-
optionsFrom: auditConfig
|
|
2479
|
-
});
|
|
2480
|
-
//#endregion
|
|
2481
|
-
//#region src/commands/doctor.ts
|
|
2482
|
-
const doctorCommand = {
|
|
2483
|
-
name: "doctor",
|
|
2484
|
-
description: posthogDoctorConfig.description,
|
|
2485
|
-
options: {
|
|
2486
|
-
...skillProgramOptions,
|
|
2487
|
-
...posthogDoctorConfig.cliOptions ?? {}
|
|
2488
|
-
},
|
|
2489
|
-
handler: (argv) => {
|
|
2490
|
-
const extras = posthogDoctorConfig.mapCliOptions?.(argv) ?? {};
|
|
2491
|
-
const options = {
|
|
2492
|
-
...argv,
|
|
2493
|
-
...extras
|
|
2494
|
-
};
|
|
2495
|
-
if (options.ci) runDoctorCI(options);
|
|
2496
|
-
else runWizard(posthogDoctorConfig, options);
|
|
2497
|
-
}
|
|
2498
|
-
};
|
|
2499
|
-
const SEVERITY_ORDER = {
|
|
2500
|
-
critical: 0,
|
|
2501
|
-
warning: 1,
|
|
2502
|
-
info: 2
|
|
2503
|
-
};
|
|
2504
|
-
async function runDoctorCI(options) {
|
|
2505
|
-
setUI(new LoggingUI());
|
|
2506
|
-
const apiKey = options.apiKey ?? readApiKeyFromEnv() ?? void 0;
|
|
2507
|
-
if (!apiKey) {
|
|
2508
|
-
getUI().intro("PostHog Wizard");
|
|
2509
|
-
getUI().log.error("CI mode requires --api-key (personal API key phx_xxx)");
|
|
2510
|
-
emitWizardError({
|
|
2511
|
-
code: ErrorCodes.ArgsMissingApiKey,
|
|
2512
|
-
message: "CI mode requires --api-key (personal API key phx_xxx)"
|
|
2513
|
-
});
|
|
2514
|
-
process.exit(1);
|
|
2515
|
-
}
|
|
2516
|
-
getUI().intro("Welcome to the PostHog setup wizard");
|
|
2517
|
-
getUI().log.info("Running posthog-doctor in CI mode");
|
|
2518
|
-
try {
|
|
2519
|
-
const { getOrAskForProjectData } = await import("./setup-utils-CmTCTPkX.js").then((n) => n.r);
|
|
2520
|
-
const { host, accessToken, projectId } = await getOrAskForProjectData({
|
|
2521
|
-
signup: false,
|
|
2522
|
-
ci: true,
|
|
2523
|
-
apiKey,
|
|
2524
|
-
projectId: options.projectId ? Number(options.projectId) : void 0,
|
|
2525
|
-
baseUrl: options.baseUrl
|
|
2526
|
-
});
|
|
2527
|
-
const issues = await fetchHealthIssues(accessToken, host.apiHost, projectId);
|
|
2528
|
-
if (issues.length === 0) {
|
|
2529
|
-
getUI().log.success("No active issues — your project looks healthy.");
|
|
2530
|
-
process.exit(0);
|
|
2531
|
-
}
|
|
2532
|
-
const sorted = [...issues].sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]);
|
|
2533
|
-
getUI().log.warn(`${issues.length} active issue${issues.length === 1 ? "" : "s"} found:`);
|
|
2534
|
-
for (const issue of sorted) getUI().log.info(` • [${issue.severity}] ${getKindMeta(issue.kind).title}`);
|
|
2535
|
-
process.exit(1);
|
|
2536
|
-
} catch (error) {
|
|
2537
|
-
const { ApiError } = await import("./api-Diy_2fp8.js").then((n) => n.n);
|
|
2538
|
-
const message = error instanceof ApiError && error.statusCode === 401 ? "Your PostHog API key is invalid or expired." : error instanceof Error ? error.message : String(error);
|
|
2539
|
-
getUI().log.error(`Doctor failed: ${message}`);
|
|
2540
|
-
emitWizardError({
|
|
2541
|
-
code: error instanceof ApiError && error.statusCode === 401 ? ErrorCodes.AuthInvalidOrExpired : ErrorCodes.InternalUnhandled,
|
|
2542
|
-
message
|
|
2543
|
-
});
|
|
2544
|
-
process.exit(1);
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
2547
|
-
//#endregion
|
|
2548
|
-
//#region src/commands/migrate.ts
|
|
2549
|
-
/**
|
|
2550
|
-
* `wizard migrate` — flat skill command, Statsig today.
|
|
2551
|
-
*
|
|
2552
|
-
* Stays flat while there's only one vendor. When a second vendor lands,
|
|
2553
|
-
* restructure into a family with `familyCommandFactory` and publish each
|
|
2554
|
-
* vendor as a `cliEntries` entry with `parentCommand: 'migrate'` from
|
|
2555
|
-
* context-mill. That move is a deliberate breaking change for users
|
|
2556
|
-
* (`wizard migrate` stops running Statsig directly), so do it explicitly
|
|
2557
|
-
* when the second vendor arrives, not pre-emptively.
|
|
2558
|
-
*/
|
|
2559
|
-
const migrateCommand = nativeCommandFactory(migrationConfig);
|
|
2560
|
-
//#endregion
|
|
2561
|
-
//#region src/commands/revenue.ts
|
|
2562
|
-
/**
|
|
2563
|
-
* `wizard revenue-analytics` — flat skill command, Stripe today.
|
|
2564
|
-
*
|
|
2565
|
-
* Stays flat while there's only one provider. Restructure into a family
|
|
2566
|
-
* if/when a second provider lands.
|
|
2567
|
-
*/
|
|
2568
|
-
const revenueCommand = nativeCommandFactory(revenueAnalyticsConfig);
|
|
2569
|
-
//#endregion
|
|
2570
|
-
//#region src/commands/warehouse.ts
|
|
2571
|
-
/**
|
|
2572
|
-
* `wizard warehouse` — detect and connect a data warehouse source.
|
|
2573
|
-
*
|
|
2574
|
-
* Mirrors `revenue-analytics`: flat skill command driven by the
|
|
2575
|
-
* warehouse-source program.
|
|
2576
|
-
*/
|
|
2577
|
-
const warehouseCommand = nativeCommandFactory(warehouseSourceConfig);
|
|
2578
|
-
//#endregion
|
|
2579
|
-
//#region src/commands/self-driving.ts
|
|
2580
|
-
const selfDrivingCommand = {
|
|
2581
|
-
name: "self-driving",
|
|
2582
|
-
description: selfDrivingConfig.description,
|
|
2583
|
-
options: {
|
|
2584
|
-
...skillProgramOptions,
|
|
2585
|
-
integrate: {
|
|
2586
|
-
describe: "Integrate the PostHog SDK first, then set up Self-driving — skips the integration prompt and logs you in via OAuth (no \"create an account?\" question). Use when the project isn't set up yet but you already have a PostHog account.",
|
|
2587
|
-
type: "boolean",
|
|
2588
|
-
default: false
|
|
2589
|
-
},
|
|
2590
|
-
...selfDrivingConfig.cliOptions ?? {}
|
|
2591
|
-
},
|
|
2592
|
-
check: (argv) => {
|
|
2593
|
-
if (argv.signup) throw new Error("`self-driving` cannot run with --signup. Just run `wizard self-driving`: when your project has no PostHog, it asks whether you already have an account and offers to create one for you (no flag needed).");
|
|
2594
|
-
if (argv.ci) throw new Error("`self-driving` cannot run in CI mode — it requires interactive steps (GitHub connect, issue-tracker selection, custom-scout approval).");
|
|
2595
|
-
return true;
|
|
2596
|
-
},
|
|
2597
|
-
handler: (argv) => {
|
|
2598
|
-
const extras = selfDrivingConfig.mapCliOptions?.(argv) ?? {};
|
|
2599
|
-
const options = {
|
|
2600
|
-
...argv,
|
|
2601
|
-
...extras
|
|
2602
|
-
};
|
|
2603
|
-
if (options.ci) runWizardCI(selfDrivingConfig, options);
|
|
2604
|
-
else runWizard(selfDrivingConfig, options);
|
|
2605
|
-
}
|
|
2606
|
-
};
|
|
2607
|
-
//#endregion
|
|
2608
|
-
//#region src/commands/slack.ts
|
|
2609
|
-
const slackCommand = {
|
|
2610
|
-
name: "slack",
|
|
2611
|
-
description: "Connect PostHog to your Slack",
|
|
2612
|
-
handler: runSlackConnect,
|
|
2613
|
-
children: [{
|
|
2614
|
-
name: "add",
|
|
2615
|
-
description: "Connect PostHog to your Slack",
|
|
2616
|
-
handler: runSlackConnect
|
|
2617
|
-
}]
|
|
2618
|
-
};
|
|
2619
|
-
function runSlackConnect(argv) {
|
|
2620
|
-
(async () => {
|
|
2621
|
-
const debug = argv.debug;
|
|
2622
|
-
try {
|
|
2623
|
-
const { startTUI } = await import("./start-tui-CB2PZs1x.js");
|
|
2624
|
-
const { buildSession } = await import("./wizard-session-DGaUHOF3.js");
|
|
2625
|
-
const tui = startTUI(VERSION, Program.SlackConnect);
|
|
2626
|
-
tui.store.session = buildSession({
|
|
2627
|
-
debug,
|
|
2628
|
-
baseUrl: argv.baseUrl
|
|
2629
|
-
});
|
|
2630
|
-
} catch (err) {
|
|
2631
|
-
setUI(new LoggingUI());
|
|
2632
|
-
getUI().log.error(`Connecting Slack requires an interactive terminal. ${err instanceof Error ? err.message : String(err)}`);
|
|
2633
|
-
emitWizardError({
|
|
2634
|
-
code: ErrorCodes.CliInteractiveRequired,
|
|
2635
|
-
message: "Connecting Slack requires an interactive terminal."
|
|
2636
|
-
});
|
|
2637
|
-
process.exit(1);
|
|
2638
|
-
}
|
|
2639
|
-
})();
|
|
2640
|
-
}
|
|
2641
|
-
//#endregion
|
|
2642
|
-
//#region src/commands/upload-sourcemaps.ts
|
|
2643
|
-
const uploadSourcemapsCommand = {
|
|
2644
|
-
name: [errorTrackingUploadSourceMapsConfig.command, "upload-sourcemaps"],
|
|
2645
|
-
description: errorTrackingUploadSourceMapsConfig.description,
|
|
2646
|
-
options: {
|
|
2647
|
-
...skillProgramOptions,
|
|
2648
|
-
...errorTrackingUploadSourceMapsConfig.cliOptions ?? {}
|
|
2649
|
-
},
|
|
2650
|
-
handler: (argv) => {
|
|
2651
|
-
const extras = errorTrackingUploadSourceMapsConfig.mapCliOptions?.(argv) ?? {};
|
|
2652
|
-
const options = {
|
|
2653
|
-
...argv,
|
|
2654
|
-
...extras
|
|
2655
|
-
};
|
|
2656
|
-
if (options.ci) runWizardCI(errorTrackingUploadSourceMapsConfig, options);
|
|
2657
|
-
else runWizard(errorTrackingUploadSourceMapsConfig, options);
|
|
2658
|
-
}
|
|
2659
|
-
};
|
|
2660
|
-
//#endregion
|
|
2661
|
-
//#region src/commands/error-tracking.ts
|
|
2662
|
-
/**
|
|
2663
|
-
* `wizard error-tracking` — flat skill command, set up error tracking today.
|
|
2664
|
-
*
|
|
2665
|
-
* Wires up exception capture and — where the platform needs it — source-map /
|
|
2666
|
-
* debug-symbol upload. Runs the `error-tracking` orchestrator flow, which
|
|
2667
|
-
* reuses the integration-v2 install/init mini-agents when the repo has no
|
|
2668
|
-
* PostHog integration yet, so it works on uninstrumented projects too.
|
|
2669
|
-
*/
|
|
2670
|
-
const errorTrackingCommand = nativeCommandFactory(errorTrackingConfig);
|
|
2671
|
-
//#endregion
|
|
2672
|
-
//#region src/commands/basic-integration/skill.ts
|
|
2673
|
-
/** Run an arbitrary context-mill skill by id (`wizard skill <id>`, headless with `--ci`). */
|
|
2674
|
-
function runSkillMode(argv) {
|
|
2675
|
-
const skillId = argv.skill;
|
|
2676
|
-
const config = createSkillProgram({
|
|
2677
|
-
skillId,
|
|
2678
|
-
command: "skill",
|
|
2679
|
-
id: "agent-skill",
|
|
2680
|
-
description: `Run skill: ${skillId}`,
|
|
2681
|
-
integrationLabel: skillId,
|
|
2682
|
-
successMessage: `${skillId} completed!`,
|
|
2683
|
-
reportFile: `posthog-${skillId}-report.md`,
|
|
2684
|
-
docsUrl: POSTHOG_DOCS_URL,
|
|
2685
|
-
spinnerMessage: `Running ${skillId}...`,
|
|
2686
|
-
estimatedDurationMinutes: 5
|
|
2687
|
-
});
|
|
2688
|
-
const options = {
|
|
2689
|
-
...argv,
|
|
2690
|
-
skillId
|
|
2691
|
-
};
|
|
2692
|
-
if (argv.ci) runWizardCI(config, options);
|
|
2693
|
-
else runWizard(config, options);
|
|
2694
|
-
}
|
|
2695
|
-
//#endregion
|
|
2696
|
-
//#region src/commands/skill.ts
|
|
2697
|
-
/** Read the `<skill-name>` positional (yargs camelCases the hyphenated key). */
|
|
2698
|
-
function readSkillName(argv) {
|
|
2699
|
-
return String(argv.skillName ?? argv["skill-name"] ?? "").trim();
|
|
2700
|
-
}
|
|
2701
|
-
const BROWSABLE_ROLES = new Set(["command", "skill"]);
|
|
2702
|
-
/**
|
|
2703
|
-
* Reject an unknown skill id before the wizard authenticates.
|
|
2704
|
-
*
|
|
2705
|
-
* Without this, `readSkillName` accepts any non-empty string and the name is
|
|
2706
|
-
* only validated far downstream, when the agent tries to download it — after
|
|
2707
|
-
* the user has already logged in. A typo then costs a full auth round-trip
|
|
2708
|
-
* (the concern raised in review). We resolve against the same set
|
|
2709
|
-
* `downloadSkill` uses (`categories` flattened by `id`), so this never
|
|
2710
|
-
* false-rejects a skill the download would have accepted.
|
|
2711
|
-
*
|
|
2712
|
-
* If the registry is unreachable we stay silent and defer to the download
|
|
2713
|
-
* step's own `menu-fetch-failed` handling, rather than adding a second network
|
|
2714
|
-
* dependency that could block an otherwise-valid run.
|
|
2715
|
-
*/
|
|
2716
|
-
async function assertSkillExists(skillName) {
|
|
2717
|
-
const skillsBaseUrl = getSkillsBaseUrl();
|
|
2718
|
-
const menu = await fetchSkillMenu(skillsBaseUrl);
|
|
2719
|
-
if (!menu) return;
|
|
2720
|
-
if (Object.values(menu.categories).flat().some((s) => s.id === skillName)) return;
|
|
2721
|
-
analytics.wizardCapture("cli dispatch error", {
|
|
2722
|
-
reason: "unknown skill",
|
|
2723
|
-
family: "skill",
|
|
2724
|
-
sub: skillName,
|
|
2725
|
-
skillsBaseUrl
|
|
2726
|
-
});
|
|
2727
|
-
try {
|
|
2728
|
-
await analytics.flush();
|
|
2729
|
-
} catch {}
|
|
2730
|
-
throw new Error(`Unknown skill "${skillName}". Run \`wizard skill list\` to see available skills.`);
|
|
2731
|
-
}
|
|
2732
|
-
function formatEntry(entry) {
|
|
2733
|
-
const path = entry.parentCommand ? `wizard ${entry.parentCommand} ${entry.command}` : entry.command ? `wizard ${entry.command}` : `wizard skill ${entry.skillId}`;
|
|
2734
|
-
return ` ${entry.skillId.padEnd(38)} ${path.padEnd(36)} ${entry.description}`;
|
|
2735
|
-
}
|
|
2736
|
-
/**
|
|
2737
|
-
* `wizard skill <skill-name>` — run a single context-mill skill by id.
|
|
2738
|
-
* `wizard skill list` — list every browsable skill in the catalog.
|
|
2739
|
-
*
|
|
2740
|
-
* Replaces the old `--skill=<id>` flag on the default command. The skill id
|
|
2741
|
-
* is fetched from context-mill's release at runtime (same mechanism the flag
|
|
2742
|
-
* used), so any published skill id works. Pass `--ci` to run headlessly.
|
|
2743
|
-
*/
|
|
2744
|
-
const skillCommand = {
|
|
2745
|
-
name: "skill <skill-name>",
|
|
2746
|
-
description: "Run a specific context-mill skill by name (or `list` them)",
|
|
2747
|
-
children: [{
|
|
2748
|
-
name: "list",
|
|
2749
|
-
description: "List every browsable skill in the catalog",
|
|
2750
|
-
handler: () => {
|
|
2751
|
-
runCommandHandler(async () => {
|
|
2752
|
-
const skillsBaseUrl = getSkillsBaseUrl();
|
|
2753
|
-
const menu = await fetchSkillMenu(skillsBaseUrl);
|
|
2754
|
-
if (!menu) {
|
|
2755
|
-
analytics.wizardCapture("cli dispatch error", {
|
|
2756
|
-
reason: "registry unreachable",
|
|
2757
|
-
family: "skill",
|
|
2758
|
-
sub: "list",
|
|
2759
|
-
skillsBaseUrl
|
|
2760
|
-
});
|
|
2761
|
-
try {
|
|
2762
|
-
await analytics.flush();
|
|
2763
|
-
} catch {}
|
|
2764
|
-
process.stderr.write("\n\x1B[1;91m✖ Couldn't reach the skill registry.\x1B[0m\n Check your network connection and try again.\n\n");
|
|
2765
|
-
emitWizardError({
|
|
2766
|
-
code: ErrorCodes.SkillMenuFetchFailed,
|
|
2767
|
-
message: "Couldn't reach the skill registry."
|
|
2768
|
-
});
|
|
2769
|
-
process.exit(1);
|
|
2770
|
-
}
|
|
2771
|
-
const entries = (menu.cliEntries ?? []).filter((e) => BROWSABLE_ROLES.has(e.role));
|
|
2772
|
-
if (entries.length === 0) {
|
|
2773
|
-
process.stdout.write("No skills found.\n");
|
|
2774
|
-
return;
|
|
2775
|
-
}
|
|
2776
|
-
process.stdout.write(`${entries.length} skill${entries.length === 1 ? "" : "s"}:\n`);
|
|
2777
|
-
process.stdout.write(` ${"SKILL ID".padEnd(38)} ${"COMMAND".padEnd(36)} DESCRIPTION\n`);
|
|
2778
|
-
for (const entry of entries) process.stdout.write(`${formatEntry(entry)}\n`);
|
|
2779
|
-
});
|
|
2780
|
-
}
|
|
2781
|
-
}],
|
|
2782
|
-
options: { ...skillProgramOptions },
|
|
2783
|
-
positionals: { "skill-name": {
|
|
2784
|
-
type: "string",
|
|
2785
|
-
describe: "Skill id to run (e.g. audit-events), or `list`"
|
|
2786
|
-
} },
|
|
2787
|
-
check: (argv) => {
|
|
2788
|
-
if (argv.skillName == null && argv["skill-name"] == null) return true;
|
|
2789
|
-
if (!readSkillName(argv)) throw new Error("skill needs a skill name, e.g. `wizard skill audit-events`");
|
|
2790
|
-
return true;
|
|
2791
|
-
},
|
|
2792
|
-
handler: (argv) => {
|
|
2793
|
-
runCommandHandler(async () => {
|
|
2794
|
-
const skillName = readSkillName(argv);
|
|
2795
|
-
await assertSkillExists(skillName);
|
|
2796
|
-
runSkillMode({
|
|
2797
|
-
...argv,
|
|
2798
|
-
skill: skillName
|
|
2799
|
-
});
|
|
2800
|
-
});
|
|
2801
|
-
}
|
|
2802
|
-
};
|
|
2803
|
-
//#endregion
|
|
2804
|
-
//#region src/commands/cli/add.ts
|
|
2805
|
-
const cliAddCommand = {
|
|
2806
|
-
name: "add",
|
|
2807
|
-
description: "Install or update PostHog CLI and add steering instructions to your coding agent's global instructions file",
|
|
2808
|
-
options: {
|
|
2809
|
-
agent: {
|
|
2810
|
-
describe: "Agent to install the instructions for",
|
|
2811
|
-
choices: CLI_STEERING_TARGETS.map((target) => target.id),
|
|
2812
|
-
type: "string"
|
|
2813
|
-
},
|
|
2814
|
-
path: {
|
|
2815
|
-
describe: "Write to an explicit instructions file instead of a detected agent",
|
|
2816
|
-
type: "string"
|
|
2817
|
-
},
|
|
2818
|
-
all: {
|
|
2819
|
-
default: false,
|
|
2820
|
-
describe: "Install for every detected agent without prompting",
|
|
2821
|
-
type: "boolean"
|
|
2822
|
-
}
|
|
2823
|
-
},
|
|
2824
|
-
examples: [
|
|
2825
|
-
["wizard cli add", "Detect your coding agents and pick one"],
|
|
2826
|
-
["wizard cli add --agent claude-code", "Install for Claude Code (~/.claude/CLAUDE.md)"],
|
|
2827
|
-
["wizard cli add --all", "Install for every detected agent"],
|
|
2828
|
-
["wizard cli add --path ./AGENTS.md", "Install into a specific instructions file"]
|
|
2829
|
-
],
|
|
2830
|
-
check: (argv) => {
|
|
2831
|
-
if (argv.all && (argv.agent || argv.path)) throw new Error("--all cannot be combined with --agent or --path");
|
|
2832
|
-
return true;
|
|
2833
|
-
},
|
|
2834
|
-
handler: (argv) => {
|
|
2835
|
-
runCliAdd(argv);
|
|
2836
|
-
}
|
|
2837
|
-
};
|
|
2838
|
-
async function runCliAdd(argv) {
|
|
2839
|
-
setUI(new LoggingUI());
|
|
2840
|
-
const ui = getUI();
|
|
2841
|
-
ui.intro("PostHog CLI setup");
|
|
2842
|
-
const files = await resolveTargetFiles(argv);
|
|
2843
|
-
if (files.length === 0) {
|
|
2844
|
-
process.exit(1);
|
|
2845
|
-
return;
|
|
2846
|
-
}
|
|
2847
|
-
ui.log.info("Installing or updating PostHog CLI...");
|
|
2848
|
-
const cliInstallResult = installOrUpdatePostHogCli();
|
|
2849
|
-
if (!cliInstallResult.success) {
|
|
2850
|
-
ui.log.error(`Failed to install or update PostHog CLI: ${cliInstallResult.error ?? ""}`);
|
|
2851
|
-
analytics.wizardCapture("cli steering installed", {
|
|
2852
|
-
files: files.length,
|
|
2853
|
-
failures: files.length,
|
|
2854
|
-
cli_install_failed: true,
|
|
2855
|
-
agent: typeof argv.agent === "string" ? argv.agent : void 0
|
|
2856
|
-
});
|
|
2857
|
-
process.exit(1);
|
|
2858
|
-
return;
|
|
2859
|
-
}
|
|
2860
|
-
ui.log.success("Installed or updated PostHog CLI.");
|
|
2861
|
-
let failures = 0;
|
|
2862
|
-
for (const file of files) {
|
|
2863
|
-
const result = installSteeringSnippet(file);
|
|
2864
|
-
if (result.success) ui.log.success(`Installed PostHog steering instructions in ${file}`);
|
|
2865
|
-
else {
|
|
2866
|
-
failures += 1;
|
|
2867
|
-
ui.log.error(`Failed to update ${file}: ${result.error ?? ""}`);
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
analytics.wizardCapture("cli steering installed", {
|
|
2871
|
-
files: files.length,
|
|
2872
|
-
failures,
|
|
2873
|
-
agent: typeof argv.agent === "string" ? argv.agent : void 0
|
|
2874
|
-
});
|
|
2875
|
-
if (failures > 0) {
|
|
2876
|
-
process.exit(1);
|
|
2877
|
-
return;
|
|
2878
|
-
}
|
|
2879
|
-
ui.outro("Done. PostHog CLI is installed and your agent will now use `posthog-cli api` for PostHog tasks.");
|
|
2880
|
-
process.exit(0);
|
|
2881
|
-
}
|
|
2882
|
-
/** Resolve which instruction files to write, from flags, detection, or a prompt. */
|
|
2883
|
-
async function resolveTargetFiles(argv) {
|
|
2884
|
-
const ui = getUI();
|
|
2885
|
-
if (typeof argv.path === "string" && argv.path.trim()) return [path$1.resolve(argv.path.trim())];
|
|
2886
|
-
if (typeof argv.agent === "string") {
|
|
2887
|
-
const target = findTarget(argv.agent);
|
|
2888
|
-
if (!target) {
|
|
2889
|
-
ui.log.error(`Unsupported agent: ${argv.agent}`);
|
|
2890
|
-
return [];
|
|
2891
|
-
}
|
|
2892
|
-
return [target.instructionsPath()];
|
|
2893
|
-
}
|
|
2894
|
-
const detected = detectTargets();
|
|
2895
|
-
if (detected.length === 0) {
|
|
2896
|
-
ui.log.error("No supported coding agents detected. Pass --agent <id> or --path <file> to choose a target.");
|
|
2897
|
-
ui.log.info(`Supported agents: ${CLI_STEERING_TARGETS.map((t) => t.id).join(", ")}`);
|
|
2898
|
-
return [];
|
|
2899
|
-
}
|
|
2900
|
-
if (argv.all === true) {
|
|
2901
|
-
ui.log.info(`Installing for all detected agents: ${detected.map((t) => t.name).join(", ")}`);
|
|
2902
|
-
return detected.map((target) => target.instructionsPath());
|
|
2903
|
-
}
|
|
2904
|
-
if (detected.length === 1) {
|
|
2905
|
-
ui.log.info(`Detected ${detected[0].name} (${detected[0].instructionsPath()})`);
|
|
2906
|
-
return [detected[0].instructionsPath()];
|
|
2907
|
-
}
|
|
2908
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
2909
|
-
ui.log.info(`Installing for all detected agents: ${detected.map((t) => t.name).join(", ")}`);
|
|
2910
|
-
return detected.map((target) => target.instructionsPath());
|
|
2911
|
-
}
|
|
2912
|
-
return (await promptForTargets(detected)).map((target) => target.instructionsPath());
|
|
2913
|
-
}
|
|
2914
|
-
/** Minimal numbered selection — this command is intentionally not a TUI flow. */
|
|
2915
|
-
async function promptForTargets(detected) {
|
|
2916
|
-
const ui = getUI();
|
|
2917
|
-
ui.log.info("Which coding agent are you using?");
|
|
2918
|
-
detected.forEach((target, index) => {
|
|
2919
|
-
ui.log.info(` ${index + 1}) ${target.name} (${target.instructionsPath()})`);
|
|
2920
|
-
});
|
|
2921
|
-
ui.log.info(` ${detected.length + 1}) All of the above`);
|
|
2922
|
-
const rl = readline.createInterface({
|
|
2923
|
-
input: process.stdin,
|
|
2924
|
-
output: process.stdout
|
|
2925
|
-
});
|
|
2926
|
-
try {
|
|
2927
|
-
for (;;) {
|
|
2928
|
-
const answer = (await rl.question(`Select 1-${detected.length + 1}: `)).trim();
|
|
2929
|
-
const choice = Number(answer);
|
|
2930
|
-
if (Number.isInteger(choice) && choice >= 1 && choice <= detected.length) return [detected[choice - 1]];
|
|
2931
|
-
if (choice === detected.length + 1) return detected;
|
|
2932
|
-
ui.log.warn(`Enter a number between 1 and ${detected.length + 1}.`);
|
|
2933
|
-
}
|
|
2934
|
-
} finally {
|
|
2935
|
-
rl.close();
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
//#endregion
|
|
2939
|
-
//#region src/commands/cli/index.ts
|
|
2940
|
-
const cliCommand = {
|
|
2941
|
-
name: "cli",
|
|
2942
|
-
description: "PostHog CLI agent integration commands",
|
|
2943
|
-
children: [cliAddCommand]
|
|
2944
|
-
};
|
|
2945
|
-
//#endregion
|
|
2
|
+
import { n as ErrorCodes, t as emitWizardError } from "./emit-BSetXzDV.js";
|
|
3
|
+
import { satisfies } from "semver";
|
|
2946
4
|
//#region bin.ts
|
|
2947
5
|
const NODE_VERSION_RANGE = ">=22.22.0";
|
|
2948
|
-
setGlobalDispatcher(new Agent({ allowH2: false }));
|
|
2949
6
|
if (!satisfies(process.version, NODE_VERSION_RANGE)) {
|
|
2950
7
|
console.log([
|
|
2951
8
|
`The PostHog wizard needs a newer version of Node.js to run.`,
|
|
@@ -2956,7 +13,7 @@ if (!satisfies(process.version, NODE_VERSION_RANGE)) {
|
|
|
2956
13
|
`To update Node.js:`,
|
|
2957
14
|
``,
|
|
2958
15
|
` Download the latest version from https://nodejs.org/en/download`,
|
|
2959
|
-
` Or, if you use nvm, run: nvm install
|
|
16
|
+
` Or, if you use nvm, run: nvm install 24 && nvm use 24`,
|
|
2960
17
|
``,
|
|
2961
18
|
`Then run the wizard again. Stuck? Email wizard@posthog.com and we'll help.`
|
|
2962
19
|
].join("\n"));
|
|
@@ -2966,17 +23,8 @@ if (!satisfies(process.version, NODE_VERSION_RANGE)) {
|
|
|
2966
23
|
});
|
|
2967
24
|
process.exit(1);
|
|
2968
25
|
}
|
|
2969
|
-
|
|
2970
|
-
function resolveInstallDir() {
|
|
2971
|
-
const args = process.argv.slice(2);
|
|
2972
|
-
const flagIndex = args.indexOf("--install-dir");
|
|
2973
|
-
if (flagIndex !== -1 && args[flagIndex + 1]) return args[flagIndex + 1];
|
|
2974
|
-
const inline = args.find((a) => a.startsWith("--install-dir="));
|
|
2975
|
-
if (inline) return inline.slice(14);
|
|
2976
|
-
return process.env.POSTHOG_WIZARD_INSTALL_DIR ?? process.cwd();
|
|
2977
|
-
}
|
|
2978
|
-
Wizard.use(basicIntegrationCommand).use(mcpCommand).use(mcpAnalyticsCommand).use(replayVisionCommand).use(aiObservabilityCommand).use(metricsCommand).use(cliCommand).use(auditCommand).use(doctorCommand).use(migrateCommand).use(revenueCommand).use(warehouseCommand).use(selfDrivingCommand).use(slackCommand).use(uploadSourcemapsCommand).use(errorTrackingCommand).use(skillCommand).init();
|
|
26
|
+
await import("./main-CgKxbwMP.js");
|
|
2979
27
|
//#endregion
|
|
2980
|
-
export {
|
|
28
|
+
export {};
|
|
2981
29
|
|
|
2982
30
|
//# sourceMappingURL=bin.js.map
|