@posthog/wizard 2.32.0 → 2.34.0
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/dist/{add-mcp-server-to-clients-Dc0yssCM.js → add-mcp-server-to-clients-BjH4z7gH.js} +5 -5
- package/dist/{add-mcp-server-to-clients-Dc0yssCM.js.map → add-mcp-server-to-clients-BjH4z7gH.js.map} +1 -1
- package/dist/{agent-interface-c7B2JZEd.js → agent-interface-BcUv1qeh.js} +330 -45
- package/dist/agent-interface-BcUv1qeh.js.map +1 -0
- package/dist/{agent-runner-Am34bBUT.js → agent-runner-8g_pDAVs.js} +30 -11
- package/dist/agent-runner-8g_pDAVs.js.map +1 -0
- package/dist/{analytics-CBIKy9PZ.js → analytics-DQ8IS1Hu.js} +3 -3
- package/dist/analytics-DQ8IS1Hu.js.map +1 -0
- package/dist/{api-Blg3nvvZ.js → api-EghEV_u9.js} +3 -3
- package/dist/{api-Blg3nvvZ.js.map → api-EghEV_u9.js.map} +1 -1
- package/dist/bin.js +203 -75
- package/dist/bin.js.map +1 -1
- package/dist/check-screens.tsx +25 -0
- package/dist/ci-install-ouGSf4zp.js +113 -0
- package/dist/ci-install-ouGSf4zp.js.map +1 -0
- package/dist/{debug-cy_jyRb4.js → debug-BM7yop-T.js} +1 -1
- package/dist/{debug-AdvgwKEw.js → debug-Cn4L5Ype.js} +2 -2
- package/dist/{debug-AdvgwKEw.js.map → debug-Cn4L5Ype.js.map} +1 -1
- package/dist/{defaults-BNWIWzjc.js → defaults-DII5CAog.js} +1 -1
- package/dist/{defaults-BNWIWzjc.js.map → defaults-DII5CAog.js.map} +1 -1
- package/dist/{environment-B6TW5v9d.js → environment-D2WUIm8o.js} +3 -3
- package/dist/{environment-B6TW5v9d.js.map → environment-D2WUIm8o.js.map} +1 -1
- package/dist/{file-utils-8tUk_eEX.js → file-utils-CdQn6073.js} +2 -2
- package/dist/{file-utils-8tUk_eEX.js.map → file-utils-CdQn6073.js.map} +1 -1
- package/dist/headless-ui-CSqtmW-d.js +24 -0
- package/dist/headless-ui-CSqtmW-d.js.map +1 -0
- package/dist/{interactive-CApktTrj.js → interactive-CFGcWw43.js} +3 -3
- package/dist/{interactive-CApktTrj.js.map → interactive-CFGcWw43.js.map} +1 -1
- package/dist/{mcp-prompt-streaming-BKcU9yuz.js → mcp-prompt-streaming-XZSpcXkG.js} +9 -6
- package/dist/mcp-prompt-streaming-XZSpcXkG.js.map +1 -0
- package/dist/{non-interactive-DejTdRTW.js → non-interactive-D6eksWIw.js} +2 -2
- package/dist/{non-interactive-DejTdRTW.js.map → non-interactive-D6eksWIw.js.map} +1 -1
- package/dist/{package-manager-DBfgSXNn.js → package-manager-Dozr4x6K.js} +2 -2
- package/dist/{package-manager-DBfgSXNn.js.map → package-manager-Dozr4x6K.js.map} +1 -1
- package/dist/{playground-BOg2U1AT.js → playground-BB2O8GyY.js} +7 -5
- package/dist/{playground-BOg2U1AT.js.map → playground-BB2O8GyY.js.map} +1 -1
- package/dist/{posthog-Cr37rnla.js → posthog-DU6JXG00.js} +1 -1
- package/dist/{posthog-Cr37rnla.js.map → posthog-DU6JXG00.js.map} +1 -1
- package/dist/{posthog-integration-gLhOUdPJ.js → posthog-integration-C3m_uR5A.js} +13 -13
- package/dist/{posthog-integration-gLhOUdPJ.js.map → posthog-integration-C3m_uR5A.js.map} +1 -1
- package/dist/{provisioning-DuzclqPB.js → provisioning-wMzPfaaP.js} +3 -3
- package/dist/{provisioning-DuzclqPB.js.map → provisioning-wMzPfaaP.js.map} +1 -1
- package/dist/{registry-Dbl-5SnO.js → registry-BfTeAX2z.js} +4 -4
- package/dist/{registry-Dbl-5SnO.js.map → registry-BfTeAX2z.js.map} +1 -1
- package/dist/{setup-utils-B6wbp3s0.js → setup-utils-B0oz3raQ.js} +53 -11
- package/dist/setup-utils-B0oz3raQ.js.map +1 -0
- package/dist/smoke-test.sh +25 -0
- package/dist/{start-tui-IoQh-Nhj.js → start-tui-B9qDcoGA.js} +151 -31
- package/dist/{start-tui-IoQh-Nhj.js.map → start-tui-B9qDcoGA.js.map} +1 -1
- package/dist/{steps-CJrqlHbo.js → steps-BhERMRRI.js} +7 -7
- package/dist/{steps-CJrqlHbo.js.map → steps-BhERMRRI.js.map} +1 -1
- package/dist/store-J2Rv955W.js +763 -0
- package/dist/store-J2Rv955W.js.map +1 -0
- package/dist/{task-stream-BQNSp0qR.js → task-stream-CPjpHFhI.js} +4 -4
- package/dist/{task-stream-BQNSp0qR.js.map → task-stream-CPjpHFhI.js.map} +1 -1
- package/dist/{telemetry-1m0CyTry.js → telemetry-BTLJ-YnG.js} +3 -3
- package/dist/{telemetry-1m0CyTry.js.map → telemetry-BTLJ-YnG.js.map} +1 -1
- package/dist/{terminal-BKI4i72f.js → terminal-UwqFnPrL.js} +87 -786
- package/dist/terminal-UwqFnPrL.js.map +1 -0
- package/dist/{urls-B3JumpLT.js → urls-DAVzgHGZ.js} +2 -2
- package/dist/{urls-B3JumpLT.js.map → urls-DAVzgHGZ.js.map} +1 -1
- package/dist/{wizard-abort-D7SzKUgE.js → wizard-abort-BAB8yvGF.js} +1 -1
- package/dist/{wizard-abort-PqLMKSh1.js → wizard-abort-DqeMgCVr.js} +4 -3
- package/dist/{wizard-abort-PqLMKSh1.js.map → wizard-abort-DqeMgCVr.js.map} +1 -1
- package/dist/wizard-session-BKEdX9mO.js +2 -0
- package/dist/{wizard-session-G3VWD6hv.js → wizard-session-CN55LYyZ.js} +9 -2
- package/dist/{wizard-session-G3VWD6hv.js.map → wizard-session-CN55LYyZ.js.map} +1 -1
- package/dist/wizard-ui-WZ48rUgr.js.map +1 -1
- package/package.json +1 -1
- package/dist/agent-interface-c7B2JZEd.js.map +0 -1
- package/dist/agent-runner-Am34bBUT.js.map +0 -1
- package/dist/analytics-CBIKy9PZ.js.map +0 -1
- package/dist/ci-install-51ntd9x5.js +0 -73
- package/dist/ci-install-51ntd9x5.js.map +0 -1
- package/dist/mcp-prompt-streaming-BKcU9yuz.js.map +0 -1
- package/dist/setup-utils-B6wbp3s0.js.map +0 -1
- package/dist/terminal-BKI4i72f.js.map +0 -1
- package/dist/wizard-session-wPJtNl4c.js +0 -2
|
@@ -1,771 +1,21 @@
|
|
|
1
|
-
import { P as POSTHOG_APP_URL, et as getSkillsBaseUrl, g as SERVICE_LABELS, s as logToFile
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-
|
|
1
|
+
import { P as POSTHOG_APP_URL, et as getSkillsBaseUrl, g as SERVICE_LABELS, s as logToFile } from "./debug-Cn4L5Ype.js";
|
|
2
|
+
import { t as analytics } from "./analytics-DQ8IS1Hu.js";
|
|
3
|
+
import { i as withUtm, n as openTrackedLink } from "./telemetry-BTLJ-YnG.js";
|
|
4
|
+
import { t as getOrAskForProjectData } from "./setup-utils-B0oz3raQ.js";
|
|
5
|
+
import { n as getCloudUrlFromRegion } from "./urls-DAVzgHGZ.js";
|
|
6
|
+
import { a as fetchUserData, i as fetchSlackConnected } from "./api-EghEV_u9.js";
|
|
7
|
+
import "./wizard-session-CN55LYyZ.js";
|
|
8
|
+
import { O as AUDIT_SEVERITY_STYLE, b as fetchSkillMenu } from "./agent-interface-BcUv1qeh.js";
|
|
9
|
+
import { c as isClearBlock, d as Colors, f as Icons, l as isLinesBlock, o as TextBlock, s as computeVisibleRange, u as isObjectBlock } from "./posthog-integration-C3m_uR5A.js";
|
|
10
|
+
import { a as ConfirmButton, d as Program, g as getKindMeta, i as useKeyboardHintsContext, n as useKeyBindings, o as PromptLabel, r as KeyboardHintsProvider, t as PickerMenu } from "./bin.js";
|
|
11
|
+
import "./store-J2Rv955W.js";
|
|
12
|
+
import { n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-DII5CAog.js";
|
|
13
13
|
import opn from "opn";
|
|
14
14
|
import * as fs$1 from "fs";
|
|
15
15
|
import { Box, Text, measureElement, useInput, useStdout } from "ink";
|
|
16
16
|
import { Component, Fragment, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
17
17
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
18
|
-
import { atom, map } from "nanostores";
|
|
19
18
|
import { Spinner } from "@inkjs/ui";
|
|
20
|
-
//#region src/lib/programs/program-step.ts
|
|
21
|
-
/**
|
|
22
|
-
* Project program steps into the narrower Screen shape the router consumes.
|
|
23
|
-
*
|
|
24
|
-
* Two things happen here:
|
|
25
|
-
* 1. Headless steps (no `screenId`) are filtered out. The router walks
|
|
26
|
-
* visible screens; gate-only steps like `detect` are store concerns.
|
|
27
|
-
* 2. The step is narrowed to just { id, show, isComplete } — the
|
|
28
|
-
* router has no business touching gate, onInit, or label.
|
|
29
|
-
*
|
|
30
|
-
* This intentional separation keeps the router focused on one question:
|
|
31
|
-
* "Which screen should be rendered right now?"
|
|
32
|
-
*/
|
|
33
|
-
function createProgramSequence(steps) {
|
|
34
|
-
const entries = steps.filter((step) => step.screenId != null).map((step) => ({
|
|
35
|
-
id: step.screenId,
|
|
36
|
-
show: step.show,
|
|
37
|
-
isComplete: step.isComplete ?? step.gate
|
|
38
|
-
}));
|
|
39
|
-
entries.push({
|
|
40
|
-
id: "exit",
|
|
41
|
-
show: void 0,
|
|
42
|
-
isComplete: void 0
|
|
43
|
-
});
|
|
44
|
-
return entries;
|
|
45
|
-
}
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/lib/programs/ai-opt-in-gate.ts
|
|
48
|
-
/** Step id — also the ScreenId.AiOptIn enum value in screen-sequences. */
|
|
49
|
-
const AI_OPT_IN_STEP_ID = "ai-opt-in";
|
|
50
|
-
function aiApproved(session) {
|
|
51
|
-
return !!session.apiUser?.organization?.is_ai_data_processing_approved;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Returns the program's steps with the AI opt-in gate injected after
|
|
55
|
-
* `auth`. Programs with `requiresAi: false` or no auth step pass
|
|
56
|
-
* through unchanged — without auth, `apiUser` would never be populated
|
|
57
|
-
* for evaluation anyway.
|
|
58
|
-
*/
|
|
59
|
-
function withAiOptInGate(config) {
|
|
60
|
-
if (config.requiresAi === false) return config.steps;
|
|
61
|
-
const authIdx = config.steps.findIndex((s) => s.id === "auth");
|
|
62
|
-
if (authIdx === -1) return config.steps;
|
|
63
|
-
const gateStep = {
|
|
64
|
-
id: AI_OPT_IN_STEP_ID,
|
|
65
|
-
label: "AI opt-in check",
|
|
66
|
-
screenId: AI_OPT_IN_STEP_ID,
|
|
67
|
-
show: (session) => !session.ci && session.apiUser != null && !aiApproved(session),
|
|
68
|
-
isComplete: (session) => session.ci || aiApproved(session),
|
|
69
|
-
gate: (session) => session.ci || aiApproved(session)
|
|
70
|
-
};
|
|
71
|
-
return [
|
|
72
|
-
...config.steps.slice(0, authIdx + 1),
|
|
73
|
-
gateStep,
|
|
74
|
-
...config.steps.slice(authIdx + 1)
|
|
75
|
-
];
|
|
76
|
-
}
|
|
77
|
-
//#endregion
|
|
78
|
-
//#region src/ui/tui/screen-sequences.ts
|
|
79
|
-
/** All program screen sequences keyed by program id. */
|
|
80
|
-
const PROGRAM_SEQUENCES = Object.fromEntries(PROGRAM_REGISTRY.map((c) => [c.id, createProgramSequence(withAiOptInGate(c))]));
|
|
81
|
-
//#endregion
|
|
82
|
-
//#region src/ui/tui/router.ts
|
|
83
|
-
var WizardRouter = class {
|
|
84
|
-
sequence;
|
|
85
|
-
programId;
|
|
86
|
-
overlays = [];
|
|
87
|
-
constructor(programId = Program.PostHogIntegration) {
|
|
88
|
-
this.programId = programId;
|
|
89
|
-
this.sequence = PROGRAM_SEQUENCES[programId];
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Resolve which screen should be active based on session state.
|
|
93
|
-
* Walks the program sequence, skipping hidden entries and completed entries,
|
|
94
|
-
* returns the first incomplete screen.
|
|
95
|
-
*/
|
|
96
|
-
resolve(session) {
|
|
97
|
-
if (this.overlays.length > 0) return this.overlays[this.overlays.length - 1];
|
|
98
|
-
for (const entry of this.sequence) {
|
|
99
|
-
if (entry.show && !entry.show(session)) continue;
|
|
100
|
-
if (entry.isComplete && entry.isComplete(session)) continue;
|
|
101
|
-
return entry.id;
|
|
102
|
-
}
|
|
103
|
-
return this.sequence[this.sequence.length - 1].id;
|
|
104
|
-
}
|
|
105
|
-
/** The screen that should be rendered right now. */
|
|
106
|
-
get activeScreen() {
|
|
107
|
-
if (this.overlays.length > 0) return this.overlays[this.overlays.length - 1];
|
|
108
|
-
return this.sequence[0].id;
|
|
109
|
-
}
|
|
110
|
-
/** The id of the active program. */
|
|
111
|
-
get activeProgram() {
|
|
112
|
-
return this.programId;
|
|
113
|
-
}
|
|
114
|
-
/** Whether an overlay is currently active. */
|
|
115
|
-
get hasOverlay() {
|
|
116
|
-
return this.overlays.length > 0;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Push an overlay that interrupts the current program.
|
|
120
|
-
* The program resumes when the overlay is dismissed via popOverlay().
|
|
121
|
-
*/
|
|
122
|
-
pushOverlay(overlay) {
|
|
123
|
-
this.overlays.push(overlay);
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Dismiss the topmost overlay. The program screen underneath resumes.
|
|
127
|
-
*/
|
|
128
|
-
popOverlay() {
|
|
129
|
-
this.overlays.pop();
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Direction hint for screen transitions.
|
|
133
|
-
*/
|
|
134
|
-
_lastDirection = null;
|
|
135
|
-
get lastNavDirection() {
|
|
136
|
-
return this._lastDirection;
|
|
137
|
-
}
|
|
138
|
-
/** @internal — called by store wrapper to track direction */
|
|
139
|
-
_setDirection(dir) {
|
|
140
|
-
this._lastDirection = dir;
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
//#endregion
|
|
144
|
-
//#region src/ui/tui/store.ts
|
|
145
|
-
/**
|
|
146
|
-
* WizardStore — Nanostore-backed reactive store for the TUI.
|
|
147
|
-
* React components subscribe via useSyncExternalStore.
|
|
148
|
-
*
|
|
149
|
-
* The active screen is derived from session state — WizardRouter walks
|
|
150
|
-
* the flow and shows the first step whose `isComplete` is still false.
|
|
151
|
-
*
|
|
152
|
-
* Define a step `gate` if your screen needs to await user interactions.
|
|
153
|
-
* bin.ts calls `await store.getGate(stepId)` to pause until the gate
|
|
154
|
-
* predicate becomes true.
|
|
155
|
-
*
|
|
156
|
-
* All session mutations that affect screen resolution go through
|
|
157
|
-
* explicit setters so emitChange() is always called.
|
|
158
|
-
*/
|
|
159
|
-
/**
|
|
160
|
-
* FIFO cap on retained status lines. The status bar is the only consumer and
|
|
161
|
-
* renders at most EXPANDED_COUNT lines, so there is no reason to retain more —
|
|
162
|
-
* the cap is tied to the window it feeds.
|
|
163
|
-
*/
|
|
164
|
-
const MAX_STATUS_MESSAGES = 10;
|
|
165
|
-
/**
|
|
166
|
-
* Fired once per blocked readiness result, so we can quantify how often
|
|
167
|
-
* the wizard refuses to start and — crucially — split that between
|
|
168
|
-
* confirmed PostHog outages and probe-level reachability failures that
|
|
169
|
-
* are most likely the user's network. Helps us decide whether the
|
|
170
|
-
* health-check UX is over-firing.
|
|
171
|
-
*/
|
|
172
|
-
function captureHealthCheckBlocked(result) {
|
|
173
|
-
try {
|
|
174
|
-
const health = result.health;
|
|
175
|
-
const blockingKeys = getBlockingServiceKeys(health);
|
|
176
|
-
const blockingStatuses = blockingKeys.map((k) => health[k]?.status);
|
|
177
|
-
const allNoConnection = blockingStatuses.length > 0 && blockingStatuses.every((s) => s === "no-connection");
|
|
178
|
-
const decision = blockingKeys.length === 1 && blockingKeys[0] === "githubReleases" ? "github-releases-down" : allNoConnection ? "no-connection" : "confirmed-outage";
|
|
179
|
-
const posthogStatus = health.posthogOverall?.status;
|
|
180
|
-
const retriesUsed = Math.max(0, ...[
|
|
181
|
-
"llmGateway",
|
|
182
|
-
"mcp",
|
|
183
|
-
"githubReleases"
|
|
184
|
-
].map((k) => {
|
|
185
|
-
const m = (health[k]?.rawIndicator ?? "").match(/attempts=(\d+)/);
|
|
186
|
-
return m ? Number(m[1]) - 1 : 0;
|
|
187
|
-
}));
|
|
188
|
-
analytics.wizardCapture("health check blocked", {
|
|
189
|
-
decision,
|
|
190
|
-
blocking_keys: blockingKeys,
|
|
191
|
-
posthog_status_reachable: posthogStatus !== "no-connection",
|
|
192
|
-
posthog_status_reports_incident: posthogStatus === "down" || posthogStatus === "degraded",
|
|
193
|
-
retries_used: retriesUsed
|
|
194
|
-
});
|
|
195
|
-
} catch (err) {
|
|
196
|
-
logToFile(`[health-checks] failed to capture analytics: ${err instanceof Error ? err.message : String(err)}`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
var WizardStore = class {
|
|
200
|
-
$session = map(buildSession({}));
|
|
201
|
-
$statusMessages = atom([]);
|
|
202
|
-
$statusExpanded = atom(false);
|
|
203
|
-
$tasks = atom([]);
|
|
204
|
-
$eventPlan = atom([]);
|
|
205
|
-
$learnCardBlockIdx = atom(0);
|
|
206
|
-
$learnCardComplete = atom(false);
|
|
207
|
-
$version = atom(0);
|
|
208
|
-
$currentStage = atom(null);
|
|
209
|
-
_onTasksChanged = null;
|
|
210
|
-
/** Last screen seen — used to detect screen transitions for analytics. */
|
|
211
|
-
_lastScreen = null;
|
|
212
|
-
/** Hooks run when transitioning onto a screen. */
|
|
213
|
-
_enterScreenHooks = /* @__PURE__ */ new Map();
|
|
214
|
-
/** Gate promises derived from program step definitions. */
|
|
215
|
-
_gates = /* @__PURE__ */ new Map();
|
|
216
|
-
version = "";
|
|
217
|
-
/** Navigation router — resolves active screen from session state. */
|
|
218
|
-
router;
|
|
219
|
-
/** Blocks agent execution until the settings-override overlay is dismissed. */
|
|
220
|
-
_resolveSettingsOverride = null;
|
|
221
|
-
_backupAndFixSettings = null;
|
|
222
|
-
/** Blocks OAuth flow until the port-conflict overlay is dismissed. */
|
|
223
|
-
_resolvePortConflict = null;
|
|
224
|
-
/** Resolves the OAuth flow with a manually-entered authorization code. */
|
|
225
|
-
_resolveManualAuthCode = null;
|
|
226
|
-
/** Resolves the in-flight wizard_ask request. */
|
|
227
|
-
_resolvePendingQuestion = null;
|
|
228
|
-
constructor(program = Program.PostHogIntegration) {
|
|
229
|
-
this.router = new WizardRouter(program);
|
|
230
|
-
this._initFromProgram(program);
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Scan program steps for gate predicates and create gate promises.
|
|
234
|
-
*
|
|
235
|
-
* Steps are wrapped with withAiOptInGate so the injected ai-opt-in
|
|
236
|
-
* step's gate registers here — the agent runner awaits it (via
|
|
237
|
-
* WizardUI.waitForAiOptIn) before any source leaves the machine.
|
|
238
|
-
* Same wrapper screen-sequences.ts uses, so the gate and its screen
|
|
239
|
-
* can't drift apart.
|
|
240
|
-
*/
|
|
241
|
-
_initFromProgram(program) {
|
|
242
|
-
const steps = withAiOptInGate(getProgramConfig(program));
|
|
243
|
-
for (const step of steps) if (step.gate) {
|
|
244
|
-
let resolve;
|
|
245
|
-
const promise = new Promise((r) => {
|
|
246
|
-
resolve = r;
|
|
247
|
-
});
|
|
248
|
-
this._gates.set(step.id, {
|
|
249
|
-
predicate: step.gate,
|
|
250
|
-
promise,
|
|
251
|
-
resolve,
|
|
252
|
-
resolved: false
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Run the program steps' onInit callbacks. startTUI calls this once
|
|
258
|
-
* the screens are actually rendering — constructing a store alone
|
|
259
|
-
* (tests, playground) must not fire init work like the health-check
|
|
260
|
-
* pre-flight, whose probes belong only to flows that show its screen.
|
|
261
|
-
*/
|
|
262
|
-
runInitHooks() {
|
|
263
|
-
const steps = getProgramConfig(this.router.activeProgram).steps;
|
|
264
|
-
const getSession = () => this.session;
|
|
265
|
-
const ctx = {
|
|
266
|
-
get session() {
|
|
267
|
-
return getSession();
|
|
268
|
-
},
|
|
269
|
-
setReadinessResult: (r) => this.setReadinessResult(r),
|
|
270
|
-
setFrameworkContext: (k, v) => this.setFrameworkContext(k, v),
|
|
271
|
-
emitChange: () => this.emitChange()
|
|
272
|
-
};
|
|
273
|
-
for (const step of steps) step.onInit?.(ctx);
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Run all `onReady` hooks declared by the current flow's steps, in
|
|
277
|
-
* order. Must be called after `store.session = session` so hooks see
|
|
278
|
-
* the real installDir. bin.ts calls this generically — it doesn't
|
|
279
|
-
* need to know which program has which pre-flow work.
|
|
280
|
-
*/
|
|
281
|
-
async runReadyHooks() {
|
|
282
|
-
const steps = getProgramConfig(this.router.activeProgram).steps;
|
|
283
|
-
const ctx = {
|
|
284
|
-
session: this.session,
|
|
285
|
-
setFrameworkContext: (k, v) => this.setFrameworkContext(k, v),
|
|
286
|
-
setFrameworkConfig: (i, c) => this.setFrameworkConfig(i, c),
|
|
287
|
-
setDetectedFramework: (l) => this.setDetectedFramework(l),
|
|
288
|
-
setSkillId: (id) => this.setSkillId(id),
|
|
289
|
-
setUnsupportedVersion: (info) => this.setUnsupportedVersion(info),
|
|
290
|
-
addDiscoveredFeature: (f) => this.addDiscoveredFeature(f),
|
|
291
|
-
setDetectionComplete: () => this.setDetectionComplete()
|
|
292
|
-
};
|
|
293
|
-
for (const step of steps) if (step.onReady) await step.onReady(ctx);
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Get a gate promise by step ID — the primary blocking checkpoint API
|
|
297
|
-
* for bin.ts. `await store.getGate('...')` parks the caller until the
|
|
298
|
-
* corresponding program step's gate predicate flips to true (if the
|
|
299
|
-
* predicate stays false, the caller stays parked indefinitely — the
|
|
300
|
-
* TUI keeps rendering so the user can resolve whatever is blocking).
|
|
301
|
-
*
|
|
302
|
-
* If the program doesn't define a step with this ID, or the step
|
|
303
|
-
* has no `gate` predicate, this returns an already-resolved promise
|
|
304
|
-
* so bin.ts flows straight through. This lets programs opt in to
|
|
305
|
-
* gates on a per-step basis without bin.ts needing to know which
|
|
306
|
-
* gates exist in which flow.
|
|
307
|
-
*/
|
|
308
|
-
getGate(stepId) {
|
|
309
|
-
return this._gates.get(stepId)?.promise ?? Promise.resolve();
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Re-evaluate every gate predicate against the current session and
|
|
313
|
-
* resolve any whose predicate now returns true. Called after every
|
|
314
|
-
* emitChange(), so gates unblock as soon as the session mutation
|
|
315
|
-
* that satisfies them lands. Gates only resolve once — a predicate
|
|
316
|
-
* that goes true → false → true will NOT re-block a caller that
|
|
317
|
-
* already awaited through.
|
|
318
|
-
*/
|
|
319
|
-
_checkGates() {
|
|
320
|
-
for (const [, gate] of this._gates) if (!gate.resolved && gate.predicate(this.session)) {
|
|
321
|
-
gate.resolved = true;
|
|
322
|
-
gate.resolve();
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
get session() {
|
|
326
|
-
return this.$session.get();
|
|
327
|
-
}
|
|
328
|
-
set session(value) {
|
|
329
|
-
this.$session.set(value);
|
|
330
|
-
this.emitChange();
|
|
331
|
-
}
|
|
332
|
-
get statusMessages() {
|
|
333
|
-
return this.$statusMessages.get();
|
|
334
|
-
}
|
|
335
|
-
get tasks() {
|
|
336
|
-
return this.$tasks.get();
|
|
337
|
-
}
|
|
338
|
-
get eventPlan() {
|
|
339
|
-
return this.$eventPlan.get();
|
|
340
|
-
}
|
|
341
|
-
get currentStage() {
|
|
342
|
-
return this.$currentStage.get();
|
|
343
|
-
}
|
|
344
|
-
/** No-op when the stage hasn't changed, so `startedAt` survives across
|
|
345
|
-
* re-renders and tab switches and measures real stage time. */
|
|
346
|
-
setCurrentStage(stage) {
|
|
347
|
-
if (this.$currentStage.get()?.stage === stage) return;
|
|
348
|
-
this.$currentStage.set({
|
|
349
|
-
stage,
|
|
350
|
-
startedAt: Date.now()
|
|
351
|
-
});
|
|
352
|
-
this.emitChange();
|
|
353
|
-
}
|
|
354
|
-
get statusExpanded() {
|
|
355
|
-
return this.$statusExpanded.get();
|
|
356
|
-
}
|
|
357
|
-
toggleStatusExpanded() {
|
|
358
|
-
this.$statusExpanded.set(!this.$statusExpanded.get());
|
|
359
|
-
this.emitChange();
|
|
360
|
-
}
|
|
361
|
-
setStatusExpanded(expanded) {
|
|
362
|
-
if (this.$statusExpanded.get() !== expanded) {
|
|
363
|
-
this.$statusExpanded.set(expanded);
|
|
364
|
-
this.emitChange();
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
/** Sets setupConfirmed. Gate resolves via _checkGates(). */
|
|
368
|
-
completeSetup() {
|
|
369
|
-
this.$session.setKey("setupConfirmed", true);
|
|
370
|
-
analytics.wizardCapture("setup confirmed", sessionProperties(this.session));
|
|
371
|
-
this.emitChange();
|
|
372
|
-
}
|
|
373
|
-
setRunPhase(phase) {
|
|
374
|
-
this.$session.setKey("runPhase", phase);
|
|
375
|
-
this.emitChange();
|
|
376
|
-
}
|
|
377
|
-
setCredentials(credentials) {
|
|
378
|
-
this.$session.setKey("credentials", credentials);
|
|
379
|
-
if (credentials?.projectId) analytics.setTag("project_id", credentials.projectId);
|
|
380
|
-
analytics.wizardCapture("auth complete", { project_id: credentials?.projectId });
|
|
381
|
-
this.emitChange();
|
|
382
|
-
}
|
|
383
|
-
setRoleAtOrganization(role) {
|
|
384
|
-
this.$session.setKey("roleAtOrganization", role);
|
|
385
|
-
this.emitChange();
|
|
386
|
-
}
|
|
387
|
-
setApiUser(user) {
|
|
388
|
-
this.$session.setKey("apiUser", user);
|
|
389
|
-
this.emitChange();
|
|
390
|
-
}
|
|
391
|
-
setFrameworkConfig(integration, config) {
|
|
392
|
-
this.$session.setKey("integration", integration);
|
|
393
|
-
this.$session.setKey("frameworkConfig", config);
|
|
394
|
-
this.$session.setKey("unsupportedVersion", null);
|
|
395
|
-
if (integration) analytics.setTag("integration", integration);
|
|
396
|
-
this.emitChange();
|
|
397
|
-
}
|
|
398
|
-
setDetectionComplete() {
|
|
399
|
-
this.$session.setKey("detectionComplete", true);
|
|
400
|
-
this.emitChange();
|
|
401
|
-
}
|
|
402
|
-
setDetectedFramework(label) {
|
|
403
|
-
this.$session.setKey("detectedFrameworkLabel", label);
|
|
404
|
-
analytics.setTag("detected_framework", label);
|
|
405
|
-
this.emitChange();
|
|
406
|
-
}
|
|
407
|
-
setSkillId(skillId) {
|
|
408
|
-
this.$session.setKey("skillId", skillId);
|
|
409
|
-
this.emitChange();
|
|
410
|
-
}
|
|
411
|
-
setUnsupportedVersion(info) {
|
|
412
|
-
this.$session.setKey("unsupportedVersion", info);
|
|
413
|
-
this.emitChange();
|
|
414
|
-
}
|
|
415
|
-
setLoginUrl(url) {
|
|
416
|
-
this.$session.setKey("loginUrl", url);
|
|
417
|
-
this.emitChange();
|
|
418
|
-
}
|
|
419
|
-
setAuthorizeUrl(url) {
|
|
420
|
-
this.$session.setKey("authorizeUrl", url);
|
|
421
|
-
this.emitChange();
|
|
422
|
-
}
|
|
423
|
-
setReadinessResult(result) {
|
|
424
|
-
this.$session.setKey("readinessResult", result);
|
|
425
|
-
if (result && result.decision === "no") captureHealthCheckBlocked(result);
|
|
426
|
-
this.emitChange();
|
|
427
|
-
}
|
|
428
|
-
/** User dismissed the blocking outage screen. Gate resolves via _checkGates(). */
|
|
429
|
-
dismissOutage() {
|
|
430
|
-
logToFile("[health-checks] user dismissed outage screen, continuing");
|
|
431
|
-
this.$session.setKey("outageDismissed", true);
|
|
432
|
-
this.emitChange();
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Push the settings-override overlay and return a promise that blocks
|
|
436
|
-
* until the user dismisses it via backupAndFixSettingsOverride().
|
|
437
|
-
*/
|
|
438
|
-
showSettingsOverride(conflicts, backupAndFix) {
|
|
439
|
-
const allKeys = conflicts.flatMap((c) => c.keys);
|
|
440
|
-
this.$session.setKey("settingsOverrideKeys", allKeys);
|
|
441
|
-
this.$session.setKey("settingsConflicts", conflicts);
|
|
442
|
-
this._backupAndFixSettings = backupAndFix;
|
|
443
|
-
if (conflicts.some((c) => !c.writable)) this.pushOverlay("managed-settings");
|
|
444
|
-
else this.pushOverlay("settings-override");
|
|
445
|
-
return new Promise((resolve) => {
|
|
446
|
-
this._resolveSettingsOverride = resolve;
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Push the port-conflict overlay and return a promise that blocks
|
|
451
|
-
* until the user frees the ports and retries, or exits.
|
|
452
|
-
*/
|
|
453
|
-
showPortConflict(processInfo) {
|
|
454
|
-
this.$session.setKey("portConflictProcess", processInfo);
|
|
455
|
-
this.pushOverlay("port-conflict");
|
|
456
|
-
return new Promise((resolve) => {
|
|
457
|
-
this._resolvePortConflict = resolve;
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
/** Dismiss the port-conflict overlay and retry the OAuth port loop. */
|
|
461
|
-
resolvePortConflict() {
|
|
462
|
-
this.$session.setKey("portConflictProcess", null);
|
|
463
|
-
this.popOverlay();
|
|
464
|
-
this._resolvePortConflict?.();
|
|
465
|
-
this._resolvePortConflict = null;
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Return a promise that resolves when the user submits a manually-entered
|
|
469
|
-
* OAuth code via the paste modal. The OAuth flow races this against the
|
|
470
|
-
* local callback server — see `performOAuthFlow`.
|
|
471
|
-
*/
|
|
472
|
-
waitForManualAuthCode() {
|
|
473
|
-
return new Promise((resolve) => {
|
|
474
|
-
this._resolveManualAuthCode = resolve;
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
/** Open the manual OAuth code-entry overlay over the auth screen. */
|
|
478
|
-
showManualAuthCode() {
|
|
479
|
-
this.pushOverlay("manual-auth-code");
|
|
480
|
-
}
|
|
481
|
-
/** Dismiss the manual OAuth code overlay without submitting. */
|
|
482
|
-
dismissManualAuthCode() {
|
|
483
|
-
this.popOverlay();
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Submit a manually-entered authorization code: dismiss the overlay and
|
|
487
|
-
* resolve the in-flight OAuth flow so it can exchange the code for a token.
|
|
488
|
-
*/
|
|
489
|
-
submitManualAuthCode(code) {
|
|
490
|
-
this.popOverlay();
|
|
491
|
-
this._resolveManualAuthCode?.(code);
|
|
492
|
-
this._resolveManualAuthCode = null;
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Open the WizardAsk overlay with a set of questions and return a promise
|
|
496
|
-
* that resolves once the user submits answers (or the request is cancelled).
|
|
497
|
-
*
|
|
498
|
-
* Only one request is in flight at a time — calling this while a request
|
|
499
|
-
* is already pending throws.
|
|
500
|
-
*/
|
|
501
|
-
requestQuestion(question) {
|
|
502
|
-
if (this._resolvePendingQuestion) throw new Error("requestQuestion called while another wizard_ask request is pending");
|
|
503
|
-
this.$session.setKey("pendingQuestion", question);
|
|
504
|
-
this.pushOverlay("wizard-ask");
|
|
505
|
-
analytics.wizardCapture("wizard_ask shown", {
|
|
506
|
-
source: question.source,
|
|
507
|
-
question_count: question.questions.length,
|
|
508
|
-
kinds: question.questions.map((q) => q.kind)
|
|
509
|
-
});
|
|
510
|
-
return new Promise((resolve) => {
|
|
511
|
-
this._resolvePendingQuestion = resolve;
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* Resolve the in-flight wizard_ask request with the user's answers and
|
|
516
|
-
* dismiss the overlay. Answers flow back to the agent as the tool result.
|
|
517
|
-
*/
|
|
518
|
-
resolvePendingQuestion(answers) {
|
|
519
|
-
const resolve = this._resolvePendingQuestion;
|
|
520
|
-
this._resolvePendingQuestion = null;
|
|
521
|
-
this.$session.setKey("pendingQuestion", null);
|
|
522
|
-
this.popOverlay();
|
|
523
|
-
resolve?.(answers);
|
|
524
|
-
}
|
|
525
|
-
/**
|
|
526
|
-
* Cancel the in-flight wizard_ask request — the bridge sends a sentinel
|
|
527
|
-
* answer ("__cancelled__") so the skill can decide how to handle it.
|
|
528
|
-
*/
|
|
529
|
-
cancelPendingQuestion() {
|
|
530
|
-
const pending = this.session.pendingQuestion;
|
|
531
|
-
if (!pending) return;
|
|
532
|
-
const cancelled = {};
|
|
533
|
-
for (const q of pending.questions) cancelled[q.id] = "__cancelled__";
|
|
534
|
-
this.resolvePendingQuestion(cancelled);
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Back up .claude/settings.json. Dismisses the overlay on success.
|
|
538
|
-
*/
|
|
539
|
-
backupAndFixSettingsOverride() {
|
|
540
|
-
const ok = this._backupAndFixSettings?.() ?? false;
|
|
541
|
-
if (ok) {
|
|
542
|
-
this.$session.setKey("settingsOverrideKeys", null);
|
|
543
|
-
this.$session.setKey("settingsConflicts", null);
|
|
544
|
-
this.popOverlay();
|
|
545
|
-
this._resolveSettingsOverride?.();
|
|
546
|
-
this._resolveSettingsOverride = null;
|
|
547
|
-
this._backupAndFixSettings = null;
|
|
548
|
-
}
|
|
549
|
-
return ok;
|
|
550
|
-
}
|
|
551
|
-
/** Push the auth-error overlay (no dismiss — user must exit). */
|
|
552
|
-
showAuthError(detail) {
|
|
553
|
-
this.$session.setKey("authErrorDetail", detail ?? null);
|
|
554
|
-
this.pushOverlay("auth-error");
|
|
555
|
-
}
|
|
556
|
-
/** Push the session-timeout overlay (no dismiss — user must exit). */
|
|
557
|
-
showSessionTimeout() {
|
|
558
|
-
this.pushOverlay("session-timeout");
|
|
559
|
-
}
|
|
560
|
-
addDiscoveredFeature(feature) {
|
|
561
|
-
if (!this.session.discoveredFeatures.includes(feature)) {
|
|
562
|
-
this.session.discoveredFeatures.push(feature);
|
|
563
|
-
this.emitChange();
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Enable an additional feature: enqueue it for the stop hook
|
|
568
|
-
* and set any feature-specific session flags.
|
|
569
|
-
*/
|
|
570
|
-
enableFeature(feature) {
|
|
571
|
-
if (!this.session.additionalFeatureQueue.includes(feature)) this.session.additionalFeatureQueue.push(feature);
|
|
572
|
-
if (feature === "llm") this.session.llmOptIn = true;
|
|
573
|
-
analytics.wizardCapture("feature enabled", { feature });
|
|
574
|
-
this.emitChange();
|
|
575
|
-
}
|
|
576
|
-
setMcpComplete(outcome = "skipped", installedClients = [], featuresSelected) {
|
|
577
|
-
this.$session.setKey("mcpComplete", true);
|
|
578
|
-
this.$session.setKey("mcpOutcome", outcome);
|
|
579
|
-
this.$session.setKey("mcpInstalledClients", installedClients);
|
|
580
|
-
const featuresPayload = outcome === "installed" && featuresSelected !== void 0 ? { mcp_features_selected: featuresSelected } : {};
|
|
581
|
-
analytics.wizardCapture("mcp complete", {
|
|
582
|
-
mcp_outcome: outcome,
|
|
583
|
-
mcp_installed_clients: installedClients,
|
|
584
|
-
...featuresPayload,
|
|
585
|
-
...sessionProperties(this.session)
|
|
586
|
-
});
|
|
587
|
-
this.emitChange();
|
|
588
|
-
}
|
|
589
|
-
setSkillsComplete(kept) {
|
|
590
|
-
this.$session.setKey("skillsComplete", true);
|
|
591
|
-
analytics.wizardCapture("skills complete", {
|
|
592
|
-
skills_kept: kept,
|
|
593
|
-
...sessionProperties(this.session)
|
|
594
|
-
});
|
|
595
|
-
this.emitChange();
|
|
596
|
-
}
|
|
597
|
-
setMcpSuggestedPromptsDismissed() {
|
|
598
|
-
this.$session.setKey("mcpSuggestedPromptsDismissed", true);
|
|
599
|
-
this.emitChange();
|
|
600
|
-
}
|
|
601
|
-
setSlackStepDismissed() {
|
|
602
|
-
this.$session.setKey("slackStepDismissed", true);
|
|
603
|
-
this.emitChange();
|
|
604
|
-
}
|
|
605
|
-
setSlackConnected(connected) {
|
|
606
|
-
this.$session.setKey("slackConnected", connected);
|
|
607
|
-
this.emitChange();
|
|
608
|
-
}
|
|
609
|
-
setOutroDismissed() {
|
|
610
|
-
this.$session.setKey("outroDismissed", true);
|
|
611
|
-
this.emitChange();
|
|
612
|
-
}
|
|
613
|
-
setOutroData(data) {
|
|
614
|
-
this.$session.setKey("outroData", data);
|
|
615
|
-
this.emitChange();
|
|
616
|
-
}
|
|
617
|
-
setDashboardUrl(url) {
|
|
618
|
-
logToFile(`store.setDashboardUrl: ${url}`);
|
|
619
|
-
this.$session.setKey("dashboardUrl", url);
|
|
620
|
-
this.emitChange();
|
|
621
|
-
}
|
|
622
|
-
setNotebookUrl(url) {
|
|
623
|
-
logToFile(`store.setNotebookUrl: ${url}`);
|
|
624
|
-
this.$session.setKey("notebookUrl", url);
|
|
625
|
-
this.emitChange();
|
|
626
|
-
}
|
|
627
|
-
setFrameworkContext(key, value) {
|
|
628
|
-
const ctx = {
|
|
629
|
-
...this.$session.get().frameworkContext,
|
|
630
|
-
[key]: value
|
|
631
|
-
};
|
|
632
|
-
this.$session.setKey("frameworkContext", ctx);
|
|
633
|
-
this.emitChange();
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* The screen that should be rendered right now.
|
|
637
|
-
* Derived from session state via the router.
|
|
638
|
-
*/
|
|
639
|
-
get currentScreen() {
|
|
640
|
-
return this.router.resolve(this.session);
|
|
641
|
-
}
|
|
642
|
-
/** Direction hint for screen transitions. */
|
|
643
|
-
get lastNavDirection() {
|
|
644
|
-
return this.router.lastNavDirection;
|
|
645
|
-
}
|
|
646
|
-
getVersion() {
|
|
647
|
-
return this.$version.get();
|
|
648
|
-
}
|
|
649
|
-
/**
|
|
650
|
-
* Notify React that state has changed.
|
|
651
|
-
* The router re-resolves the active screen on next render.
|
|
652
|
-
* Gate predicates are checked and resolved if ready.
|
|
653
|
-
*/
|
|
654
|
-
emitChange() {
|
|
655
|
-
this.router._setDirection("push");
|
|
656
|
-
this.$version.set(this.$version.get() + 1);
|
|
657
|
-
this._checkGates();
|
|
658
|
-
this._detectTransition();
|
|
659
|
-
}
|
|
660
|
-
pushOverlay(overlay) {
|
|
661
|
-
this.router._setDirection("push");
|
|
662
|
-
this.router.pushOverlay(overlay);
|
|
663
|
-
this.$version.set(this.$version.get() + 1);
|
|
664
|
-
this._detectTransition();
|
|
665
|
-
}
|
|
666
|
-
popOverlay() {
|
|
667
|
-
this.router._setDirection("pop");
|
|
668
|
-
this.router.popOverlay();
|
|
669
|
-
this.$version.set(this.$version.get() + 1);
|
|
670
|
-
this._detectTransition();
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* Register a callback to run when transitioning onto the given screen.
|
|
674
|
-
* Fires after every transition that lands on this screen.
|
|
675
|
-
*/
|
|
676
|
-
onEnterScreen(screen, fn) {
|
|
677
|
-
const list = this._enterScreenHooks.get(screen) ?? [];
|
|
678
|
-
list.push(fn);
|
|
679
|
-
this._enterScreenHooks.set(screen, list);
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Detect screen transitions, run enter-screen hooks, and fire analytics.
|
|
683
|
-
* Called at the end of emitChange/pushOverlay/popOverlay.
|
|
684
|
-
*/
|
|
685
|
-
_detectTransition() {
|
|
686
|
-
const next = this.router.resolve(this.session);
|
|
687
|
-
const prev = this._lastScreen;
|
|
688
|
-
if (next !== prev) analytics.setTag("$screen_name", next);
|
|
689
|
-
if (prev !== null && next !== prev) {
|
|
690
|
-
const hooks = this._enterScreenHooks.get(next);
|
|
691
|
-
if (hooks) for (const fn of hooks) fn();
|
|
692
|
-
analytics.wizardCapture(`screen ${next}`, {
|
|
693
|
-
from_screen: prev,
|
|
694
|
-
program_id: this.router.activeProgram,
|
|
695
|
-
...sessionProperties(this.session)
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
this._lastScreen = next;
|
|
699
|
-
}
|
|
700
|
-
pushStatus(message) {
|
|
701
|
-
const msgs = this.$statusMessages.get();
|
|
702
|
-
if (msgs.length > 0 && msgs[msgs.length - 1] === message) return;
|
|
703
|
-
const next = msgs.length >= MAX_STATUS_MESSAGES ? [...msgs.slice(msgs.length - MAX_STATUS_MESSAGES + 1), message] : [...msgs, message];
|
|
704
|
-
this.$statusMessages.set(next);
|
|
705
|
-
this.emitChange();
|
|
706
|
-
}
|
|
707
|
-
setTasks(tasks) {
|
|
708
|
-
this.$tasks.set(tasks);
|
|
709
|
-
this.emitChange();
|
|
710
|
-
}
|
|
711
|
-
updateTask(index, done) {
|
|
712
|
-
const tasks = this.$tasks.get();
|
|
713
|
-
if (tasks[index]) {
|
|
714
|
-
const updated = [...tasks];
|
|
715
|
-
updated[index] = {
|
|
716
|
-
...updated[index],
|
|
717
|
-
done,
|
|
718
|
-
status: done ? "completed" : "pending"
|
|
719
|
-
};
|
|
720
|
-
this.$tasks.set(updated);
|
|
721
|
-
this.emitChange();
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
setEventPlan(events) {
|
|
725
|
-
this.$eventPlan.set(events);
|
|
726
|
-
this.emitChange();
|
|
727
|
-
}
|
|
728
|
-
get learnCardBlockIdx() {
|
|
729
|
-
return this.$learnCardBlockIdx.get();
|
|
730
|
-
}
|
|
731
|
-
setLearnCardBlockIdx(idx) {
|
|
732
|
-
this.$learnCardBlockIdx.set(idx);
|
|
733
|
-
}
|
|
734
|
-
get learnCardComplete() {
|
|
735
|
-
return this.$learnCardComplete.get();
|
|
736
|
-
}
|
|
737
|
-
setLearnCardComplete() {
|
|
738
|
-
this.$learnCardComplete.set(true);
|
|
739
|
-
this.emitChange();
|
|
740
|
-
}
|
|
741
|
-
syncTodos(todos) {
|
|
742
|
-
const incoming = todos.map((t) => {
|
|
743
|
-
const status = isTaskStatus(t.status) ? t.status : "pending";
|
|
744
|
-
return {
|
|
745
|
-
label: t.content,
|
|
746
|
-
activeForm: t.activeForm,
|
|
747
|
-
status,
|
|
748
|
-
done: status === "completed"
|
|
749
|
-
};
|
|
750
|
-
});
|
|
751
|
-
const incomingLabels = new Set(incoming.map((t) => t.label));
|
|
752
|
-
const retained = this.$tasks.get().filter((t) => t.done && !incomingLabels.has(t.label));
|
|
753
|
-
this.$tasks.set([...retained, ...incoming]);
|
|
754
|
-
this.emitChange();
|
|
755
|
-
this._onTasksChanged?.();
|
|
756
|
-
}
|
|
757
|
-
/** Register a listener for task state changes (e.g. task stream push). */
|
|
758
|
-
set onTasksChanged(fn) {
|
|
759
|
-
this._onTasksChanged = fn;
|
|
760
|
-
}
|
|
761
|
-
subscribe(callback) {
|
|
762
|
-
return this.$version.listen(() => callback());
|
|
763
|
-
}
|
|
764
|
-
getSnapshot() {
|
|
765
|
-
return this.$version.get();
|
|
766
|
-
}
|
|
767
|
-
};
|
|
768
|
-
//#endregion
|
|
769
19
|
//#region src/ui/tui/primitives/CardLayout.tsx
|
|
770
20
|
/**
|
|
771
21
|
* CardLayout — Aligns a single child within available space.
|
|
@@ -1249,10 +499,9 @@ const ModalOverlay = ({ borderColor, title, titleColor, width = 68, children, fe
|
|
|
1249
499
|
const ESC = String.fromCharCode(27);
|
|
1250
500
|
const BEL = String.fromCharCode(7);
|
|
1251
501
|
const OSC_8 = `${ESC}]8;;`;
|
|
502
|
+
const ELLIPSIS = "…";
|
|
1252
503
|
/** Matches an http(s) URL run (no surrounding whitespace). */
|
|
1253
504
|
const URL_RUN = /https?:\/\/[^\s]+/g;
|
|
1254
|
-
/** A line whose entire (trimmed) content is a single URL. */
|
|
1255
|
-
const STANDALONE_URL_LINE = /^https?:\/\/\S+$/;
|
|
1256
505
|
/** Trailing characters that are punctuation around a URL, not part of it. */
|
|
1257
506
|
const TRAILING_PUNCTUATION = /[.,;:!?)\]}>'"]+$/;
|
|
1258
507
|
function trimTrailingPunctuation(url) {
|
|
@@ -1266,17 +515,49 @@ function trimTrailingPunctuation(url) {
|
|
|
1266
515
|
function osc8Hyperlink(url, label = url) {
|
|
1267
516
|
return `${OSC_8}${url}${BEL}${label}${OSC_8}${BEL}`;
|
|
1268
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Shorten a URL for *display only*, keeping the trust-relevant head visible.
|
|
520
|
+
*
|
|
521
|
+
* A long authorize URL is mostly query-string noise (`?client_id=…&state=…`).
|
|
522
|
+
* The part a user needs to judge where they're being sent, the scheme and host,
|
|
523
|
+
* sits at the front. So we always keep `scheme://host` intact and collapse the
|
|
524
|
+
* long path/query tail to an ellipsis, never the other way round.
|
|
525
|
+
*
|
|
526
|
+
* This is purely the visible label. Callers pair it with `osc8Hyperlink(url,
|
|
527
|
+
* label)` so the click target, and the copy/open keybinds, stay the full URL.
|
|
528
|
+
* Returns the URL unchanged when it already fits within `maxLength`.
|
|
529
|
+
*/
|
|
530
|
+
function truncateUrlLabel(url, maxLength = 56) {
|
|
531
|
+
if (url.length <= maxLength) return url;
|
|
532
|
+
let head;
|
|
533
|
+
let tail;
|
|
534
|
+
try {
|
|
535
|
+
const parsed = new URL(url);
|
|
536
|
+
head = `${parsed.protocol}//${parsed.host}`;
|
|
537
|
+
tail = url.slice(head.length);
|
|
538
|
+
} catch {
|
|
539
|
+
return url.slice(0, Math.max(1, maxLength - 1)) + ELLIPSIS;
|
|
540
|
+
}
|
|
541
|
+
if (head.length >= maxLength - 1) return tail.length > 0 ? head + ELLIPSIS : head;
|
|
542
|
+
const room = maxLength - head.length - 1;
|
|
543
|
+
return head + tail.slice(0, room) + ELLIPSIS;
|
|
544
|
+
}
|
|
1269
545
|
/** Extract every http(s) URL in `text`, trailing punctuation removed. */
|
|
1270
546
|
function extractUrls(text) {
|
|
1271
547
|
return (text.match(URL_RUN) ?? []).map(trimTrailingPunctuation);
|
|
1272
548
|
}
|
|
1273
549
|
/**
|
|
1274
|
-
* Split prompt text into renderable segments, breaking
|
|
1275
|
-
*
|
|
1276
|
-
*
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
550
|
+
* Split prompt text into renderable segments, breaking *every* URL out onto its
|
|
551
|
+
* own `url` segment — whether it sits alone on a line or inline within prose.
|
|
552
|
+
* Pulling it out matters: `LinkText` renders a `url` segment as a single OSC 8
|
|
553
|
+
* hyperlink, so the click target stays the exact full URL even when it wraps. A
|
|
554
|
+
* URL left inline in a `text` segment is plain text the terminal may auto-detect
|
|
555
|
+
* per *visual* line, which opens a broken half-URL when it wraps.
|
|
556
|
+
*
|
|
557
|
+
* Consecutive URL-free lines stay grouped in one `text` segment (newlines
|
|
558
|
+
* preserved) so paragraph spacing is unchanged. The prose surrounding an inline
|
|
559
|
+
* URL is kept as `text` segments before/after it (trailing URL punctuation stays
|
|
560
|
+
* with the prose), so it reads the same minus the link being on its own line.
|
|
1280
561
|
*/
|
|
1281
562
|
function splitPromptIntoSegments(text) {
|
|
1282
563
|
const segments = [];
|
|
@@ -1291,14 +572,34 @@ function splitPromptIntoSegments(text) {
|
|
|
1291
572
|
}
|
|
1292
573
|
};
|
|
1293
574
|
for (const line of text.split("\n")) {
|
|
1294
|
-
|
|
1295
|
-
if (
|
|
1296
|
-
|
|
575
|
+
URL_RUN.lastIndex = 0;
|
|
576
|
+
if (!URL_RUN.test(line)) {
|
|
577
|
+
buffer.push(line);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
flush();
|
|
581
|
+
let cursor = 0;
|
|
582
|
+
URL_RUN.lastIndex = 0;
|
|
583
|
+
let match;
|
|
584
|
+
while ((match = URL_RUN.exec(line)) !== null) {
|
|
585
|
+
const url = trimTrailingPunctuation(match[0]);
|
|
586
|
+
const before = line.slice(cursor, match.index).trim();
|
|
587
|
+
if (before) segments.push({
|
|
588
|
+
type: "text",
|
|
589
|
+
value: before
|
|
590
|
+
});
|
|
1297
591
|
segments.push({
|
|
1298
592
|
type: "url",
|
|
1299
|
-
value:
|
|
593
|
+
value: url
|
|
1300
594
|
});
|
|
1301
|
-
|
|
595
|
+
cursor = match.index + url.length;
|
|
596
|
+
URL_RUN.lastIndex = cursor;
|
|
597
|
+
}
|
|
598
|
+
const after = line.slice(cursor).trim();
|
|
599
|
+
if (after) segments.push({
|
|
600
|
+
type: "text",
|
|
601
|
+
value: after
|
|
602
|
+
});
|
|
1302
603
|
}
|
|
1303
604
|
flush();
|
|
1304
605
|
return segments;
|
|
@@ -1306,15 +607,15 @@ function splitPromptIntoSegments(text) {
|
|
|
1306
607
|
//#endregion
|
|
1307
608
|
//#region src/ui/tui/primitives/LinkText.tsx
|
|
1308
609
|
/**
|
|
1309
|
-
* LinkText — renders prompt text with
|
|
610
|
+
* LinkText — renders prompt text with URLs as OSC 8 hyperlinks.
|
|
1310
611
|
*
|
|
1311
|
-
*
|
|
1312
|
-
*
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
612
|
+
* Every URL (standalone or inline in prose) is pulled onto its own line. Long
|
|
613
|
+
* URLs are truncated for display (`scheme://host` kept, the noisy path/query
|
|
614
|
+
* tail collapsed to an ellipsis) so the overlay stays clean instead of a URL
|
|
615
|
+
* wrapping across several lines. The OSC 8 escape carries the *full* URL as the
|
|
616
|
+
* click target regardless of the shortened label, and `WizardAskScreen` opens
|
|
617
|
+
* (`o`) / copies (`c`) the full URL too, so no path loses the real destination.
|
|
618
|
+
* Prose renders unchanged.
|
|
1318
619
|
*
|
|
1319
620
|
* Used by the `wizard_ask` overlay only for programs that opt into rich link
|
|
1320
621
|
* rendering (see `PendingQuestion.richLinks`). Other flows are untouched.
|
|
@@ -1326,7 +627,7 @@ const LinkText = ({ text }) => {
|
|
|
1326
627
|
color: Colors.accent,
|
|
1327
628
|
underline: true,
|
|
1328
629
|
wrap: "wrap",
|
|
1329
|
-
children: osc8Hyperlink(segment.value)
|
|
630
|
+
children: osc8Hyperlink(segment.value, truncateUrlLabel(segment.value))
|
|
1330
631
|
}, i) : /* @__PURE__ */ jsx(Text, { children: segment.value }, i))
|
|
1331
632
|
});
|
|
1332
633
|
};
|
|
@@ -6819,6 +6120,6 @@ function releaseTerminal() {
|
|
|
6819
6120
|
process.stdout.write(RESET_ATTRS + LEAVE_ALT_SCREEN);
|
|
6820
6121
|
}
|
|
6821
6122
|
//#endregion
|
|
6822
|
-
export { ConfirmationInput as A, TabContainer as C, LinkText as D, LogViewer as E, SplitView as F, CardLayout as I,
|
|
6123
|
+
export { ConfirmationInput as A, TabContainer as C, LinkText as D, LogViewer as E, SplitView as F, CardLayout as I, useStdoutDimensions as M, ProgressList as N, extractUrls as O, LoadingBox as P, HNViewer as S, EventPlanViewer as T, ServiceHealthList as _, useSkillEntry as a, LearnCard as b, AUDIT_AREA_SLIDES as c, McpSuggestedPromptsScreen as d, TAILORED_ROLES as f, SEVERITY_ORDER as g, SEVERITY_LABEL as h, SkillSourceInfo as i, GroupedPickerMenu as j, ModalOverlay as k, VisualBox as l, IssueTable as m, releaseTerminal as n, OutroScreen as o, McpScreen as p, AiOptInRequiredScreen as r, SlackConnectScreen as s, enterDarkTerminal as t, AuditChecksViewer as u, VisualizerTab as v, ScreenContainer as w, ContentSequencer as x, TipsCard as y };
|
|
6823
6124
|
|
|
6824
|
-
//# sourceMappingURL=terminal-
|
|
6125
|
+
//# sourceMappingURL=terminal-UwqFnPrL.js.map
|